From 0ffb256052afccfd14c5b799df68bc5fef84b6c3 Mon Sep 17 00:00:00 2001 From: Kurt Date: Sat, 15 Jun 2024 00:14:49 -0500 Subject: [PATCH] Add slot source legality checks Useful for save files with misplaced data (you really have to be using the program weirdly to get these flagged). Stuff like Eggs deposited in Daycare, non-fuseable species in the Fused slots, etc. Allow HaX to view any slot, & Delete if Set is allowed --- PKHeX.Core/Editing/Saves/Slots/Extensions.cs | 30 ++++++------ .../Editing/Saves/Slots/Info/ISlotInfo.cs | 2 +- .../Editing/Saves/Slots/Info/SlotInfoBox.cs | 2 +- .../Editing/Saves/Slots/Info/SlotInfoFile.cs | 4 +- .../Saves/Slots/Info/SlotInfoLoader.cs | 5 +- .../Editing/Saves/Slots/Info/SlotInfoMisc.cs | 3 +- .../Editing/Saves/Slots/Info/SlotInfoParty.cs | 2 +- .../Editing/Saves/Slots/Info/SlotOrigin.cs | 18 ------- .../Editing/Saves/Slots/StorageSlotType.cs | 13 ++++- PKHeX.Core/Legality/Bulk/BulkAnalysis.cs | 2 +- .../Formatting/LegalityCheckStrings.cs | 3 ++ PKHeX.Core/Legality/LegalityAnalysis.cs | 13 +++-- PKHeX.Core/Legality/LegalityAnalyzers.cs | 1 + .../Legality/Structures/CheckIdentifier.cs | 5 ++ .../Verifiers/FormArgumentVerifier.cs | 2 +- PKHeX.Core/Legality/Verifiers/FormVerifier.cs | 2 +- .../Legality/Verifiers/SlotTypeVerifier.cs | 47 +++++++++++++++++++ PKHeX.Drawing.PokeSprite/Util/SpriteUtil.cs | 9 ++-- .../Controls/SAV Editor/ContextMenuSAV.cs | 39 +++++---------- .../Controls/SAV Editor/SAVEditor.cs | 2 +- PKHeX.WinForms/Controls/Slots/SlotList.cs | 24 +++++++--- PKHeX.WinForms/Controls/Slots/SlotUtil.cs | 6 +-- PKHeX.WinForms/MainWindow/Main.cs | 2 +- PKHeX.WinForms/Subforms/SAV_Database.cs | 2 +- .../Save Editors/Gen7/SAV_FestivalPlaza.cs | 2 +- .../Subforms/Save Editors/SAV_GroupViewer.cs | 2 +- PKHeX.WinForms/Util/DevUtil.cs | 3 +- 27 files changed, 148 insertions(+), 97 deletions(-) delete mode 100644 PKHeX.Core/Editing/Saves/Slots/Info/SlotOrigin.cs create mode 100644 PKHeX.Core/Legality/Verifiers/SlotTypeVerifier.cs diff --git a/PKHeX.Core/Editing/Saves/Slots/Extensions.cs b/PKHeX.Core/Editing/Saves/Slots/Extensions.cs index 4ced2cca6..187cb1a6c 100644 --- a/PKHeX.Core/Editing/Saves/Slots/Extensions.cs +++ b/PKHeX.Core/Editing/Saves/Slots/Extensions.cs @@ -92,7 +92,7 @@ private static List GetExtraSlots5(SAV5 sav) }; if (sav is SAV5B2W2 b2w2) - list.Insert(1, new(b2w2.Forest.Fused, 0) { Type = StorageSlotType.Fused }); + list.Insert(1, new(b2w2.Forest.Fused, 0) { Type = StorageSlotType.FusedKyurem }); return list; } @@ -102,7 +102,7 @@ private static List GetExtraSlots6XY(SAV6XY sav) return [ new(sav.GTS.Upload, 0) {Type = StorageSlotType.GTS}, - new(sav.Fused[0], 0) {Type = StorageSlotType.Fused}, + new(sav.Fused[0], 0) {Type = StorageSlotType.FusedKyurem}, new(sav.SUBE.GiveSlot, 0) {Type = StorageSlotType.Misc}, // Old Man new(sav.BattleBox[0], 0) {Type = StorageSlotType.BattleBox}, @@ -119,7 +119,7 @@ private static List GetExtraSlots6AO(SAV6AO sav) return [ new(sav.GTS.Upload, 0) { Type = StorageSlotType.GTS }, - new(sav.Fused[0], 0) { Type = StorageSlotType.Fused }, + new(sav.Fused[0], 0) { Type = StorageSlotType.FusedKyurem }, new(sav.SUBE.GiveSlot, 0) {Type = StorageSlotType.Misc}, new(sav.BattleBox[0], 0) {Type = StorageSlotType.BattleBox}, @@ -136,14 +136,14 @@ private static List GetExtraSlots7(SAV7 sav, bool all) var list = new List { new(sav.GTS.Upload, 0) {Type = StorageSlotType.GTS}, - new(sav.Fused[0], 0, PartyFormat: true) {Type = StorageSlotType.Fused}, + new(sav.Fused[0], 0, PartyFormat: true) {Type = StorageSlotType.FusedKyurem}, }; if (sav is SAV7USUM uu) { list.AddRange( [ - new SlotInfoMisc(uu.Fused[1], 1, PartyFormat: true) {Type = StorageSlotType.Fused}, - new SlotInfoMisc(uu.Fused[2], 2, PartyFormat: true) {Type = StorageSlotType.Fused}, + new SlotInfoMisc(uu.Fused[1], 1, PartyFormat: true) {Type = StorageSlotType.FusedNecrozmaS}, + new SlotInfoMisc(uu.Fused[2], 2, PartyFormat: true) {Type = StorageSlotType.FusedNecrozmaM}, ]); list.AddRange( [ @@ -175,9 +175,9 @@ private static List GetExtraSlots8(ISaveBlock8Main sav) var dc = sav.Daycare; var list = new List { - new(fused[0], 0, true) {Type = StorageSlotType.Fused}, - new(fused[1], 1, true) {Type = StorageSlotType.Fused}, - new(fused[2], 2, true) {Type = StorageSlotType.Fused}, + new(fused[0], 0, true) {Type = StorageSlotType.FusedKyurem}, + new(fused[1], 1, true) {Type = StorageSlotType.FusedNecrozmaS}, + new(fused[2], 2, true) {Type = StorageSlotType.FusedNecrozmaM}, new(dc[0], 0) {Type = StorageSlotType.Daycare}, new(dc[1], 1) {Type = StorageSlotType.Daycare}, @@ -188,7 +188,7 @@ private static List GetExtraSlots8(ISaveBlock8Main sav) if (sav is SAV8SWSH {SaveRevision: >= 2} s8) { var block = s8.Blocks.GetBlockSafe(SaveBlockAccessor8SWSH.KFusedCalyrex); - var c = new SlotInfoMisc(block.Data, 3, true) {Type = StorageSlotType.Fused}; + var c = new SlotInfoMisc(block.Data, 3, true) {Type = StorageSlotType.FusedCalyrex}; list.Insert(3, c); } @@ -222,18 +222,18 @@ private static List GetExtraSlots9(SAV9SV sav) var list = new List { // Ride Legend - new(sav.BoxInfo.Raw.Slice(afterBox, PokeCrypto.SIZE_9PARTY), 0, true, Mutable: true) { Type = StorageSlotType.Party }, + new(sav.BoxInfo.Raw.Slice(afterBox, PokeCrypto.SIZE_9PARTY), 0, true, Mutable: true) { Type = StorageSlotType.Ride }, }; var block = sav.Blocks.GetBlock(SaveBlockAccessor9SV.KFusedCalyrex); - list.Add(new(block.Data, 0, true) { Type = StorageSlotType.Fused }); + list.Add(new(block.Data, 0, true) { Type = StorageSlotType.FusedCalyrex }); if (sav.Blocks.TryGetBlock(SaveBlockAccessor9SV.KFusedKyurem, out var kyurem)) - list.Add(new(kyurem.Data, 1, true) { Type = StorageSlotType.Fused }); + list.Add(new(kyurem.Data, 1, true) { Type = StorageSlotType.FusedKyurem }); if (sav.Blocks.TryGetBlock(SaveBlockAccessor9SV.KFusedNecrozmaS, out var solgaleo)) - list.Add(new(solgaleo.Data, 2, true) { Type = StorageSlotType.Fused }); + list.Add(new(solgaleo.Data, 2, true) { Type = StorageSlotType.FusedNecrozmaS }); if (sav.Blocks.TryGetBlock(SaveBlockAccessor9SV.KFusedNecrozmaM, out var lunala)) - list.Add(new(lunala.Data, 3, true) { Type = StorageSlotType.Fused }); + list.Add(new(lunala.Data, 3, true) { Type = StorageSlotType.FusedNecrozmaM }); if (sav.Blocks.TryGetBlock(SaveBlockAccessor9SV.KSurpriseTrade, out var surprise)) { diff --git a/PKHeX.Core/Editing/Saves/Slots/Info/ISlotInfo.cs b/PKHeX.Core/Editing/Saves/Slots/Info/ISlotInfo.cs index 8b1e20661..470bb7b40 100644 --- a/PKHeX.Core/Editing/Saves/Slots/Info/ISlotInfo.cs +++ b/PKHeX.Core/Editing/Saves/Slots/Info/ISlotInfo.cs @@ -8,7 +8,7 @@ public interface ISlotInfo /// /// Indicates the type of format the slot originates. Useful for legality purposes. /// - SlotOrigin Origin { get; } + StorageSlotType Type { get; } /// /// Differentiating slot number from other infos of the same type. diff --git a/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoBox.cs b/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoBox.cs index e5983a19e..ec85d7fee 100644 --- a/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoBox.cs +++ b/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoBox.cs @@ -5,7 +5,7 @@ namespace PKHeX.Core; /// public sealed record SlotInfoBox(int Box, int Slot) : ISlotInfo { - public SlotOrigin Origin => SlotOrigin.Box; + public StorageSlotType Type => StorageSlotType.Box; public bool CanWriteTo(SaveFile sav) => sav.HasBox && !sav.IsSlotLocked(Box, Slot); public WriteBlockedMessage CanWriteTo(SaveFile sav, PKM pk) => WriteBlockedMessage.None; diff --git a/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoFile.cs b/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoFile.cs index 2f633e735..6d43c5e1a 100644 --- a/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoFile.cs +++ b/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoFile.cs @@ -3,10 +3,10 @@ namespace PKHeX.Core; /// /// Records data for that originates from an external file. /// -/// +/// Path the file was loaded from. public sealed record SlotInfoFile(string Path) : ISlotInfo { - public SlotOrigin Origin => SlotOrigin.Party; + public StorageSlotType Type => StorageSlotType.Party; public int Slot => 0; public bool CanWriteTo(SaveFile sav) => false; diff --git a/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoLoader.cs b/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoLoader.cs index dee212adf..b5550eead 100644 --- a/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoLoader.cs +++ b/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoLoader.cs @@ -30,8 +30,9 @@ public static void AddFromLocalFile(string file, ConcurrentBag db, IT return; var data = File.ReadAllBytes(file); - _ = FileUtil.TryGetPKM(data, out var pk, fi.Extension, dest); - if (pk?.Species is not > 0) + if (!FileUtil.TryGetPKM(data, out var pk, fi.Extension, dest)) + return; + if (pk.Species is 0) return; var info = new SlotInfoFile(file); diff --git a/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoMisc.cs b/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoMisc.cs index dd0fe0160..60abbe987 100644 --- a/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoMisc.cs +++ b/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoMisc.cs @@ -7,10 +7,9 @@ namespace PKHeX.Core; /// public sealed record SlotInfoMisc(Memory Data, int Slot, bool PartyFormat = false, bool Mutable = false) : ISlotInfo { - public SlotOrigin Origin => PartyFormat ? SlotOrigin.Party : SlotOrigin.Box; + public required StorageSlotType Type { get; init; } public bool CanWriteTo(SaveFile sav) => Mutable; public WriteBlockedMessage CanWriteTo(SaveFile sav, PKM pk) => Mutable ? WriteBlockedMessage.None : WriteBlockedMessage.InvalidDestination; - public StorageSlotType Type { get; init; } public bool WriteTo(SaveFile sav, PKM pk, PKMImportSetting setting = PKMImportSetting.UseDefault) { diff --git a/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoParty.cs b/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoParty.cs index 72992e06e..af9778c6d 100644 --- a/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoParty.cs +++ b/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoParty.cs @@ -8,7 +8,7 @@ namespace PKHeX.Core; public sealed record SlotInfoParty(int Slot) : ISlotInfo { public int Slot { get; private set; } = Slot; - public SlotOrigin Origin => SlotOrigin.Party; + public StorageSlotType Type => StorageSlotType.Party; public bool CanWriteTo(SaveFile sav) => sav.HasParty; public WriteBlockedMessage CanWriteTo(SaveFile sav, PKM pk) => pk.IsEgg && sav.IsPartyAllEggs(Slot) diff --git a/PKHeX.Core/Editing/Saves/Slots/Info/SlotOrigin.cs b/PKHeX.Core/Editing/Saves/Slots/Info/SlotOrigin.cs deleted file mode 100644 index 6e894c61b..000000000 --- a/PKHeX.Core/Editing/Saves/Slots/Info/SlotOrigin.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace PKHeX.Core; - -/// -/// Indicates where the slot data originated from. -/// -public enum SlotOrigin : byte -{ - /// - /// Slot data originated from the Party, or follows "party format" data rules. - /// - /// Some games do not permit forms to exist outside the party. - Party = 0, - - /// - /// Slot data originated from the Box, or follows "stored" data rules. - /// - Box = 1, -} diff --git a/PKHeX.Core/Editing/Saves/Slots/StorageSlotType.cs b/PKHeX.Core/Editing/Saves/Slots/StorageSlotType.cs index 36a0f5a22..e2ae7e904 100644 --- a/PKHeX.Core/Editing/Saves/Slots/StorageSlotType.cs +++ b/PKHeX.Core/Editing/Saves/Slots/StorageSlotType.cs @@ -3,8 +3,10 @@ namespace PKHeX.Core; /// /// Extra Slot enumeration to indicate a general type of slot source. /// -public enum StorageSlotType +public enum StorageSlotType : byte { + None = 0, + Box, Party, @@ -14,10 +16,17 @@ public enum StorageSlotType Daycare, /// Global Trade Station (GTS) GTS, + /// Fused Legendary Storage - Fused, + FusedKyurem, + FusedNecrozmaS, + FusedNecrozmaM, + FusedCalyrex, + /// Miscellaneous Misc, /// Poké Pelago (Gen7) Resort, + /// Ride Legendary Slot (S/V) + Ride, } diff --git a/PKHeX.Core/Legality/Bulk/BulkAnalysis.cs b/PKHeX.Core/Legality/Bulk/BulkAnalysis.cs index 4ebcd1952..ac4eb0565 100644 --- a/PKHeX.Core/Legality/Bulk/BulkAnalysis.cs +++ b/PKHeX.Core/Legality/Bulk/BulkAnalysis.cs @@ -103,5 +103,5 @@ private static LegalityAnalysis[] GetIndividualAnalysis(IReadOnlyList return results; } - private static LegalityAnalysis Get(SlotCache cache) => new(cache.Entity, cache.SAV.Personal, cache.Source.Origin); + private static LegalityAnalysis Get(SlotCache cache) => new(cache.Entity, cache.SAV.Personal, cache.Source.Type); } diff --git a/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs b/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs index 30c4f62c3..c7c2dc013 100644 --- a/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs +++ b/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs @@ -463,6 +463,9 @@ public static class LegalityCheckStrings public static string LStatNobleInvalid { get; set; } = "Noble Flag mismatch."; public static string LStatAlphaInvalid { get; set; } = "Alpha Flag mismatch."; + public static string LStoredSourceEgg { get; set; } = "Egg must be in Box or Party."; + public static string LStoredSourceInvalid_0 { get; set; } = "Invalid Stored Source: {0}"; + 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."; diff --git a/PKHeX.Core/Legality/LegalityAnalysis.cs b/PKHeX.Core/Legality/LegalityAnalysis.cs index 07e4b7f96..20a0ef27b 100644 --- a/PKHeX.Core/Legality/LegalityAnalysis.cs +++ b/PKHeX.Core/Legality/LegalityAnalysis.cs @@ -43,7 +43,7 @@ public sealed class LegalityAnalysis /// /// Indicates where the originated. /// - public readonly SlotOrigin SlotOrigin; + public readonly StorageSlotType SlotOrigin; /// /// Indicates if all checks ran to completion. @@ -61,20 +61,24 @@ public sealed class LegalityAnalysis /// public readonly LegalInfo Info; + private const StorageSlotType Ignore = StorageSlotType.None; + + internal bool IsStoredSlot(StorageSlotType type) => SlotOrigin == type || SlotOrigin is Ignore; + /// /// 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, IPersonalTable table, SlotOrigin source = SlotOrigin.Party) : this(pk, table.GetFormEntry(pk.Species, pk.Form), source) { } + public LegalityAnalysis(PKM pk, IPersonalTable table, StorageSlotType source = Ignore) : 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) { } + public LegalityAnalysis(PKM pk, StorageSlotType source = Ignore) : this(pk, pk.PersonalInfo, source) { } /// /// Checks the input data for legality. @@ -82,7 +86,7 @@ public sealed class LegalityAnalysis /// Input data to check /// Personal info to parse with /// Details about where the originated from. - public LegalityAnalysis(PKM pk, IPersonalInfo pi, SlotOrigin source = SlotOrigin.Party) + public LegalityAnalysis(PKM pk, IPersonalInfo pi, StorageSlotType source = Ignore) { Entity = pk; PersonalInfo = pi; @@ -302,6 +306,7 @@ private void UpdateChecks() if (format is 4 or 5 or 6) // Gen 6->7 transfer removes this property. Gen4GroundTile.Verify(this); + SlotType.Verify(this); if (format < 6) return; diff --git a/PKHeX.Core/Legality/LegalityAnalyzers.cs b/PKHeX.Core/Legality/LegalityAnalyzers.cs index 9e3069d6a..8bbf7f27a 100644 --- a/PKHeX.Core/Legality/LegalityAnalyzers.cs +++ b/PKHeX.Core/Legality/LegalityAnalyzers.cs @@ -36,4 +36,5 @@ internal static class LegalityAnalyzers public static readonly LegendsArceusVerifier Arceus = new(); public static readonly AwakenedValueVerifier Awakening = new(); public static readonly TrashByteVerifier Trash = new(); + public static readonly SlotTypeVerifier SlotType = new(); } diff --git a/PKHeX.Core/Legality/Structures/CheckIdentifier.cs b/PKHeX.Core/Legality/Structures/CheckIdentifier.cs index bdca02bbe..b6d16db9b 100644 --- a/PKHeX.Core/Legality/Structures/CheckIdentifier.cs +++ b/PKHeX.Core/Legality/Structures/CheckIdentifier.cs @@ -163,4 +163,9 @@ public enum CheckIdentifier : byte /// The pertains to string . /// TrashBytes, + + /// + /// The pertains to the . + /// + SlotType, } diff --git a/PKHeX.Core/Legality/Verifiers/FormArgumentVerifier.cs b/PKHeX.Core/Legality/Verifiers/FormArgumentVerifier.cs index 4e29059d4..213a257d7 100644 --- a/PKHeX.Core/Legality/Verifiers/FormArgumentVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/FormArgumentVerifier.cs @@ -111,7 +111,7 @@ private CheckResult VerifyFormArgument(LegalityAnalysis data, IFormArgument f) EncounterStatic9 { StarterBoxLegend: true } => arg switch { < 1 => GetInvalid(LFormArgumentLow), - 1 => data.SlotOrigin != SlotOrigin.Party ? GetInvalid(LFormParty) : GetValid(LFormArgumentValid), + 1 => !data.IsStoredSlot(StorageSlotType.Ride) ? GetInvalid(LFormParty) : GetValid(LFormArgumentValid), > 1 => GetInvalid(LFormArgumentHigh), }, _ => arg switch diff --git a/PKHeX.Core/Legality/Verifiers/FormVerifier.cs b/PKHeX.Core/Legality/Verifiers/FormVerifier.cs index d78749fd7..0ea53ba3b 100644 --- a/PKHeX.Core/Legality/Verifiers/FormVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/FormVerifier.cs @@ -172,7 +172,7 @@ private CheckResult VerifyForm(LegalityAnalysis data) case Shaymin: case Furfrou: case Hoopa: - if (form != 0 && data.SlotOrigin is not SlotOrigin.Party && pk.Format <= 6) // has form but stored in box + if (form != 0 && !data.IsStoredSlot(StorageSlotType.Party) && pk.Format <= 6) // has form but stored in box return GetInvalid(LFormParty); break; } diff --git a/PKHeX.Core/Legality/Verifiers/SlotTypeVerifier.cs b/PKHeX.Core/Legality/Verifiers/SlotTypeVerifier.cs new file mode 100644 index 000000000..a3faba764 --- /dev/null +++ b/PKHeX.Core/Legality/Verifiers/SlotTypeVerifier.cs @@ -0,0 +1,47 @@ +using static PKHeX.Core.LegalityCheckStrings; + +namespace PKHeX.Core; + +public sealed class SlotTypeVerifier : Verifier +{ + protected override CheckIdentifier Identifier => CheckIdentifier.SlotType; + + public override void Verify(LegalityAnalysis data) + { + var source = data.SlotOrigin; + if (source == 0) + return; // not provided, ignore + + var pk = data.Entity; + if (pk.IsEgg) + { + if (!IsSourceValidEgg(pk, source)) + data.AddLine(GetInvalid(LStoredSourceEgg)); + } + else + { + if (!IsSourceValid(pk, source)) + data.AddLine(GetInvalid(string.Format(LStoredSourceInvalid_0, source))); + } + } + + public static bool IsSourceValid(PKM pk, StorageSlotType source) => source switch + { + StorageSlotType.FusedKyurem => pk.Species is (int)Species.Reshiram or (int)Species.Zekrom, + StorageSlotType.FusedNecrozmaS => pk.Species is (int)Species.Solgaleo, + StorageSlotType.FusedNecrozmaM => pk.Species is (int)Species.Lunala, + StorageSlotType.FusedCalyrex => pk.Species is (int)Species.Glastrier or (int)Species.Spectrier, + + StorageSlotType.Ride => pk.Species is (int)Species.Koraidon or (int)Species.Miraidon && pk is PK9 {FormArgument: 1}, + _ => true, + }; + + public static bool IsSourceValidEgg(PKM pk, StorageSlotType source) => source switch + { + // Eggs should normally only be in Box or Party. + StorageSlotType.Box or StorageSlotType.Party => true, + StorageSlotType.Resort => true, // Poké Pelago can incubate eggs + StorageSlotType.Daycare when pk.Format == 2 => true, // ignore the "current egg" slot + _ => false, + }; +} diff --git a/PKHeX.Drawing.PokeSprite/Util/SpriteUtil.cs b/PKHeX.Drawing.PokeSprite/Util/SpriteUtil.cs index 93a380168..98bf1b16c 100644 --- a/PKHeX.Drawing.PokeSprite/Util/SpriteUtil.cs +++ b/PKHeX.Drawing.PokeSprite/Util/SpriteUtil.cs @@ -94,7 +94,7 @@ private static Bitmap GetSprite(PKM pk) return img; } - private static Bitmap GetSprite(PKM pk, SaveFile sav, int box, int slot, bool flagIllegal = false) + private static Bitmap GetSprite(PKM pk, SaveFile sav, int box, int slot, bool flagIllegal = false, StorageSlotType storage = StorageSlotType.None) { bool inBox = (uint)slot < MaxSlotCount; bool empty = pk.Species == 0; @@ -110,7 +110,7 @@ private static Bitmap GetSprite(PKM pk, SaveFile sav, int box, int slot, bool fl } if (flagIllegal) { - var la = new LegalityAnalysis(pk, sav.Personal, box != -1 ? SlotOrigin.Box : SlotOrigin.Party); + var la = new LegalityAnalysis(pk, sav.Personal, storage); if (!la.Valid) sprite = ImageUtil.LayerImage(sprite, Resources.warn, 0, FlagIllegalShiftY); else if (pk.Format >= 8 && MoveInfo.IsDummiedMoveAny(pk)) @@ -286,8 +286,9 @@ public static Bitmap Sprite(this IEncounterTemplate enc) _ => 0, }; - public static Bitmap Sprite(this PKM pk, SaveFile sav, int box, int slot, bool flagIllegal = false) - => GetSprite(pk, sav, box, slot, flagIllegal); + public static Bitmap Sprite(this PKM pk, SaveFile sav, int box = -1, int slot = -1, + bool flagIllegal = false, StorageSlotType storage = StorageSlotType.None) + => GetSprite(pk, sav, box, slot, flagIllegal, storage); public static Bitmap GetMysteryGiftPreviewPoke(MysteryGift gift) { diff --git a/PKHeX.WinForms/Controls/SAV Editor/ContextMenuSAV.cs b/PKHeX.WinForms/Controls/SAV Editor/ContextMenuSAV.cs index 1914601ab..d71277f44 100644 --- a/PKHeX.WinForms/Controls/SAV Editor/ContextMenuSAV.cs +++ b/PKHeX.WinForms/Controls/SAV Editor/ContextMenuSAV.cs @@ -112,28 +112,25 @@ private void ClickShowLegality(object sender, EventArgs e) var info = GetSenderInfo(sender); var sav = info.View.SAV; var pk = info.Slot.Read(sav); - var type = info.Slot is SlotInfoBox ? SlotOrigin.Box : SlotOrigin.Party; + var type = info.Slot.Type; var la = new LegalityAnalysis(pk, sav.Personal, type); RequestEditorLegality?.Invoke(la); } private void MenuOpening(object sender, CancelEventArgs e) { - var items = ((ContextMenuStrip)sender).Items; + var info = GetSenderInfo(sender); + bool canView = !info.IsEmpty() || Main.HaX; + bool canSet = info.CanWriteTo(); + bool canDelete = canSet && canView; + bool canLegality = ModifierKeys == Keys.Control && canView && RequestEditorLegality != null; - object? ctrl = ((ContextMenuStrip)sender).SourceControl; - if (ctrl is null) - return; - var info = GetSenderInfo(ctrl); - bool SlotFull = !info.IsEmpty(); - bool Editable = info.CanWriteTo(); - 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); + ToggleItem(mnuView, canView); + ToggleItem(mnuSet, canSet); + ToggleItem(mnuDelete, canDelete); + ToggleItem(mnuLegality, canLegality); - if (items.Count == 0) + if (!canView && !canSet && !canDelete) e.Cancel = true; } @@ -147,18 +144,8 @@ private static SlotViewInfo GetSenderInfo(object sender) return new SlotViewInfo(loc, view); } - private static void ToggleItem(ToolStripItemCollection items, ToolStripItem item, bool visible, bool first = false) + private static void ToggleItem(ToolStripItem item, bool visible) { - if (visible) - { - if (first) - items.Insert(0, item); - else - items.Add(item); - } - else if (items.Contains(item)) - { - items.Remove(item); - } + item.Visible = visible; } } diff --git a/PKHeX.WinForms/Controls/SAV Editor/SAVEditor.cs b/PKHeX.WinForms/Controls/SAV Editor/SAVEditor.cs index 2873b9f9a..f6160958a 100644 --- a/PKHeX.WinForms/Controls/SAV Editor/SAVEditor.cs +++ b/PKHeX.WinForms/Controls/SAV Editor/SAVEditor.cs @@ -209,7 +209,7 @@ private SlotInfoMisc GetSlotData(int index) { if (GetCurrentDaycare() is not { } s) throw new Exception(); - return new SlotInfoMisc(s.GetDaycareSlot(index), index); + return new SlotInfoMisc(s.GetDaycareSlot(index), index) {Type = StorageSlotType.Daycare}; } public void SetPKMBoxes() diff --git a/PKHeX.WinForms/Controls/Slots/SlotList.cs b/PKHeX.WinForms/Controls/Slots/SlotList.cs index dc63762e3..d08c4305e 100644 --- a/PKHeX.WinForms/Controls/Slots/SlotList.cs +++ b/PKHeX.WinForms/Controls/Slots/SlotList.cs @@ -8,8 +8,20 @@ namespace PKHeX.WinForms.Controls; public partial class SlotList : UserControl, ISlotViewer { - private static readonly string[] names = Enum.GetNames(); - private readonly Label[] Labels = new Label[names.Length]; + private static readonly string[] names = GetEnumNames(); + + public static string[] GetEnumNames() + { + var list = Enum.GetNames(); + foreach (ref var item in list.AsSpan()) + { + if (item.StartsWith("Fused")) + item = "Fused"; + } + return list; + } + + public readonly Label[] Labels = new Label[names.Length]; private readonly List slots = []; private List SlotOffsets = []; public int SlotCount { get; private set; } @@ -99,16 +111,16 @@ private void LoadSlots(int count, Action enableDragDropContext) private void AddControls(int countTotal) { - var type = (StorageSlotType)(-1); + var type = string.Empty; int added = -1; for (int i = 0; i < countTotal; i++) { var info = SlotOffsets[i]; - if (type != info.Type) + var label = Labels[(int)info.Type]; + if (label.Text != type) { added++; - type = info.Type; - var label = Labels[(int)type]; + type = label.Text; FLP_Slots.Controls.Add(label, 0, added++); } diff --git a/PKHeX.WinForms/Controls/Slots/SlotUtil.cs b/PKHeX.WinForms/Controls/Slots/SlotUtil.cs index 81a33bdbd..efa507571 100644 --- a/PKHeX.WinForms/Controls/Slots/SlotUtil.cs +++ b/PKHeX.WinForms/Controls/Slots/SlotUtil.cs @@ -59,9 +59,9 @@ public static void UpdateSlot(PictureBox pb, ISlotInfo c, PKM p, SaveFile s, boo 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), + SlotInfoBox b => p.Sprite(s, b.Box, b.Slot, flagIllegal, b.Type), + SlotInfoParty ps => p.Sprite(s, -1, ps.Slot, flagIllegal, ps.Type), + _ => p.Sprite(s, -1, -1, flagIllegal, c.Type), }; pb.BackColor = Color.Transparent; diff --git a/PKHeX.WinForms/MainWindow/Main.cs b/PKHeX.WinForms/MainWindow/Main.cs index 317e07f6e..6bd88921e 100644 --- a/PKHeX.WinForms/MainWindow/Main.cs +++ b/PKHeX.WinForms/MainWindow/Main.cs @@ -1162,7 +1162,7 @@ private void GetPreview(PictureBox pb, PKM? pk = null) if (menu != null) menu.Enabled = pk.Species != 0 || HaX; // Species - pb.Image = pk.Sprite(C_SAV.SAV, -1, -1, flagIllegal: false); + pb.Image = pk.Sprite(C_SAV.SAV); if (pb.BackColor == SlotUtil.BadDataColor) pb.BackColor = SlotUtil.GoodDataColor; } diff --git a/PKHeX.WinForms/Subforms/SAV_Database.cs b/PKHeX.WinForms/Subforms/SAV_Database.cs index fba48cfe5..fa87a253d 100644 --- a/PKHeX.WinForms/Subforms/SAV_Database.cs +++ b/PKHeX.WinForms/Subforms/SAV_Database.cs @@ -652,7 +652,7 @@ private void FillPKXBoxes(int start) 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); + PKXBOXES[i].Image = Results[i + begin].Entity.Sprite(SAV, flagIllegal: true, storage: Results[i + begin].Source.Type); for (int i = end; i < RES_MAX; i++) PKXBOXES[i].Image = null; diff --git a/PKHeX.WinForms/Subforms/Save Editors/Gen7/SAV_FestivalPlaza.cs b/PKHeX.WinForms/Subforms/Save Editors/Gen7/SAV_FestivalPlaza.cs index df9836c86..55be933f9 100644 --- a/PKHeX.WinForms/Subforms/Save Editors/Gen7/SAV_FestivalPlaza.cs +++ b/PKHeX.WinForms/Subforms/Save Editors/Gen7/SAV_FestivalPlaza.cs @@ -319,7 +319,7 @@ private void LoadBattleAgency() private void LoadPictureBox() { for (int i = 0; i < 3; i++) - PBs[i].Image = p[i].Sprite(SAV, -1, -1, flagIllegal: true); + PBs[i].Image = p[i].Sprite(SAV, flagIllegal: true); } private readonly NumericUpDown[] NUD_Trainers = new NumericUpDown[3]; diff --git a/PKHeX.WinForms/Subforms/Save Editors/SAV_GroupViewer.cs b/PKHeX.WinForms/Subforms/Save Editors/SAV_GroupViewer.cs index 6e1140e8d..7460212bf 100644 --- a/PKHeX.WinForms/Subforms/Save Editors/SAV_GroupViewer.cs +++ b/PKHeX.WinForms/Subforms/Save Editors/SAV_GroupViewer.cs @@ -117,7 +117,7 @@ private void LoadGroup(int index) var sav = SAV; for (int i = 0; i < slots.Length; i++) - Box.Entries[i].Image = slots[i].Sprite(sav, -1, -1, true); + Box.Entries[i].Image = slots[i].Sprite(sav, flagIllegal: true); if (slotSelected != -1 && (uint)slotSelected < Box.Entries.Count) Box.Entries[slotSelected].BackgroundImage = groupSelected != index ? null : SpriteUtil.Spriter.View; diff --git a/PKHeX.WinForms/Util/DevUtil.cs b/PKHeX.WinForms/Util/DevUtil.cs index 008cd38eb..7e3e95cf9 100644 --- a/PKHeX.WinForms/Util/DevUtil.cs +++ b/PKHeX.WinForms/Util/DevUtil.cs @@ -101,8 +101,7 @@ private static void UpdateTranslations() private static IEnumerable GetExtraControls() { - var slotGroupLabels = Enum.GetNames(); - foreach (var name in slotGroupLabels) + foreach (var name in SlotList.GetEnumNames().Distinct()) yield return new Label { Name = $"{nameof(Main)}.L_{name}", Text = name }; }