mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-03-21 17:48:28 -05:00
Changed: Inventory editor no longer needs to clone the save file on GUI open Changed: some method signatures have moved from SAV3* to the specific block Allows the block structures to be used without a SAV3 object Allows the Inventory editor to open from a blank save file.
494 lines
18 KiB
C#
494 lines
18 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using System.Windows.Forms;
|
|
using PKHeX.Core;
|
|
using PKHeX.WinForms.Controls;
|
|
using static PKHeX.Core.MessageStrings;
|
|
|
|
namespace PKHeX.WinForms;
|
|
|
|
public sealed partial class SAV_Inventory : Form
|
|
{
|
|
private readonly SaveFile Origin;
|
|
|
|
private static readonly ImageList IL_Pouch = InventoryTypeImageUtil.GetImageList();
|
|
|
|
public SAV_Inventory(SaveFile sav)
|
|
{
|
|
InitializeComponent();
|
|
tabControl1.ImageList = IL_Pouch;
|
|
WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage);
|
|
Origin = sav;
|
|
itemlist = [.. GameInfo.Strings.GetItemStrings(sav.Context, sav.Version)]; // copy
|
|
|
|
for (int i = 0; i < itemlist.Length; i++)
|
|
{
|
|
if (string.IsNullOrEmpty(itemlist[i]))
|
|
itemlist[i] = $"(Item #{i:000})";
|
|
}
|
|
|
|
Bag = sav.Inventory;
|
|
ItemColumnReadOnly = sav is SAV9ZA or SAV9SV;
|
|
var item0 = Bag.Pouches[0].Items[0];
|
|
HasFreeSpace = item0 is IItemFreeSpace;
|
|
HasFreeSpaceIndex = item0 is IItemFreeSpaceIndex;
|
|
HasFavorite = item0 is IItemFavorite;
|
|
HasNew = item0 is IItemNewFlag;
|
|
HasNewShop = item0 is IItemNewShopFlag;
|
|
HasHeld = item0 is IItemHeldFlag;
|
|
|
|
CreateBagViews();
|
|
LoadAllBags();
|
|
ChangeViewedPouch(0);
|
|
|
|
if (Application.IsDarkModeEnabled)
|
|
{
|
|
WinFormsUtil.InvertToolStripIcons(giveMenu.Items);
|
|
WinFormsUtil.InvertToolStripIcons(sortMenu.Items);
|
|
}
|
|
|
|
// simple tweak to widen the GUI for optional columns making it wider than the narrow default
|
|
var widen = 0;
|
|
if (HasNewShop)
|
|
widen += ControlGrids.First().Value.Columns[ColumnNEWShop].Width;
|
|
if (HasHeld)
|
|
widen += ControlGrids.First().Value.Columns[ColumnHeld].Width;
|
|
if (widen != 0)
|
|
Width += widen;
|
|
MinimumSize = Size;
|
|
}
|
|
|
|
private readonly PlayerBag Bag;
|
|
private readonly bool ItemColumnReadOnly;
|
|
private readonly bool HasFreeSpace;
|
|
private readonly bool HasFreeSpaceIndex;
|
|
private readonly bool HasFavorite;
|
|
private readonly bool HasNew;
|
|
private readonly bool HasNewShop;
|
|
private readonly bool HasHeld;
|
|
private bool IsCountValidationSuppressed;
|
|
|
|
// assume that all pouches have the same amount of columns
|
|
private int ColumnItem;
|
|
private int ColumnCount;
|
|
private int ColumnFreeSpace;
|
|
private int ColumnFreeSpaceIndex;
|
|
private int ColumnFavorite;
|
|
private int ColumnNEW;
|
|
private int ColumnNEWShop;
|
|
private int ColumnHeld;
|
|
|
|
private readonly Dictionary<InventoryType, DataGridView> ControlGrids = [];
|
|
private DataGridView GetGrid(InventoryType type) => ControlGrids[type];
|
|
private DataGridView GetGrid(int pouch) => ControlGrids[Bag.Pouches[pouch].Type];
|
|
|
|
private void B_Cancel_Click(object sender, EventArgs e) => Close();
|
|
|
|
private void B_Save_Click(object sender, EventArgs e)
|
|
{
|
|
SetBags();
|
|
Bag.CopyTo(Origin);
|
|
Close();
|
|
}
|
|
|
|
private void CreateBagViews()
|
|
{
|
|
tabControl1.SizeMode = TabSizeMode.Fixed;
|
|
tabControl1.ItemSize = new Size(IL_Pouch.Images[0].Width + 4, IL_Pouch.Images[0].Height + 4);
|
|
foreach (var pouch in Bag.Pouches)
|
|
{
|
|
var tab = new TabPage { ImageIndex = InventoryTypeImageUtil.GetImageIndex(pouch.Type) };
|
|
var dgv = GetDGV(pouch);
|
|
ControlGrids.Add(pouch.Type, dgv);
|
|
tab.Controls.Add(dgv);
|
|
tabControl1.TabPages.Add(tab);
|
|
tabControl1.ShowToolTips = true;
|
|
tab.ToolTipText = pouch.Type.ToString();
|
|
}
|
|
}
|
|
|
|
private DataGridView GetDGV(InventoryPouch pouch)
|
|
{
|
|
// Add DataGrid
|
|
var dgv = GetBaseDataGrid(pouch);
|
|
dgv.CellValueChanged += Dgv_CellValueChanged;
|
|
|
|
// Get Columns
|
|
var item = GetItemColumn(ColumnItem = dgv.Columns.Count);
|
|
dgv.Columns.Add(item);
|
|
dgv.Columns.Add(GetCountColumn(ColumnCount = dgv.Columns.Count));
|
|
if (HasFavorite)
|
|
dgv.Columns.Add(GetCheckColumn(ColumnFavorite = dgv.Columns.Count, "Fav"));
|
|
if (HasNew)
|
|
dgv.Columns.Add(GetCheckColumn(ColumnNEW = dgv.Columns.Count, "New"));
|
|
|
|
if (HasFreeSpace)
|
|
dgv.Columns.Add(GetCheckColumn(ColumnFreeSpace = dgv.Columns.Count, "Free"));
|
|
if (HasFreeSpaceIndex)
|
|
dgv.Columns.Add(GetCountColumn(ColumnFreeSpaceIndex = dgv.Columns.Count, "Free"));
|
|
if (HasNewShop)
|
|
dgv.Columns.Add(GetCheckColumn(ColumnNEWShop = dgv.Columns.Count, "Shop"));
|
|
if (HasHeld)
|
|
dgv.Columns.Add(GetCheckColumn(ColumnHeld = dgv.Columns.Count, "Held"));
|
|
|
|
// Populate with rows
|
|
var itemarr = Main.HaX ? itemlist : GetStringsForPouch(pouch.GetAllItems());
|
|
item.Items.AddRange(itemarr);
|
|
|
|
var items = pouch.Items;
|
|
if (items.Length != 0)
|
|
dgv.Rows.Add(items.Length);
|
|
dgv.CancelEdit();
|
|
|
|
return dgv;
|
|
}
|
|
|
|
private static DoubleBufferedDataGridView GetBaseDataGrid(InventoryPouch pouch) => new()
|
|
{
|
|
Dock = DockStyle.Fill,
|
|
Text = $"{pouch.Type}",
|
|
Name = $"DGV_{pouch.Type}",
|
|
|
|
AllowUserToAddRows = false,
|
|
AllowUserToDeleteRows = false,
|
|
AllowUserToResizeRows = false,
|
|
AllowUserToResizeColumns = false,
|
|
RowHeadersVisible = false,
|
|
MultiSelect = false,
|
|
ShowEditingIcon = false,
|
|
|
|
EditMode = DataGridViewEditMode.EditOnEnter,
|
|
ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.Single,
|
|
ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize,
|
|
SelectionMode = DataGridViewSelectionMode.CellSelect,
|
|
CellBorderStyle = DataGridViewCellBorderStyle.None,
|
|
|
|
Tag = pouch,
|
|
};
|
|
|
|
private DataGridViewComboBoxColumn GetItemColumn(int c, string name = "Item") => new()
|
|
{
|
|
HeaderText = name,
|
|
DisplayStyle = DataGridViewComboBoxDisplayStyle.Nothing,
|
|
DisplayIndex = c,
|
|
Width = 135,
|
|
FlatStyle = FlatStyle.Flat,
|
|
ReadOnly = ItemColumnReadOnly,
|
|
};
|
|
|
|
private static DataGridViewCheckBoxColumn GetCheckColumn(int c, string name) => new()
|
|
{
|
|
HeaderText = name,
|
|
DisplayIndex = c,
|
|
Width = 40,
|
|
FlatStyle = Application.IsDarkModeEnabled ? FlatStyle.System : FlatStyle.Flat,
|
|
};
|
|
|
|
private static DataGridViewTextBoxColumn GetCountColumn(int c, string name = "Count") => new()
|
|
{
|
|
HeaderText = name,
|
|
DisplayIndex = c,
|
|
Width = 45,
|
|
DefaultCellStyle = { Alignment = DataGridViewContentAlignment.MiddleCenter },
|
|
MaxInputLength = 5 // enough to cover ushort.MaxValue (absolute maximum of any quantity ever allowed)
|
|
};
|
|
|
|
private void LoadAllBags()
|
|
{
|
|
foreach (var pouch in Bag.Pouches)
|
|
{
|
|
var dgv = GetGrid(pouch.Type);
|
|
|
|
// Sanity Screen
|
|
var invalid = Array.FindAll(pouch.Items, item => item.Index != 0 && !pouch.CanContain((ushort)item.Index));
|
|
var outOfBounds = Array.FindAll(invalid, item => item.Index >= itemlist.Length);
|
|
var incorrectPouch = Array.FindAll(invalid, item => item.Index < itemlist.Length);
|
|
|
|
if (outOfBounds.Length != 0)
|
|
WinFormsUtil.Error(MsgItemPouchUnknown, $"Item ID(s): {string.Join(", ", outOfBounds.Select(item => item.Index))}");
|
|
if (!Main.HaX && incorrectPouch.Length != 0)
|
|
WinFormsUtil.Alert(string.Format(MsgItemPouchRemoved, pouch.Type), string.Join(", ", incorrectPouch.Select(item => itemlist[item.Index])), MsgItemPouchWarning);
|
|
|
|
pouch.Sanitize(itemlist.Length - 1, Main.HaX);
|
|
GetBag(dgv, pouch);
|
|
}
|
|
}
|
|
|
|
private void SetBags()
|
|
{
|
|
foreach (var pouch in Bag.Pouches)
|
|
{
|
|
var dgv = GetGrid(pouch.Type);
|
|
SetBag(dgv, pouch);
|
|
}
|
|
}
|
|
|
|
private void GetBag(DataGridView dgv, InventoryPouch pouch)
|
|
{
|
|
IsCountValidationSuppressed = true;
|
|
var valid = pouch.GetAllItems();
|
|
for (int i = 0; i < dgv.Rows.Count; i++)
|
|
{
|
|
var item = pouch.Items[i];
|
|
if (item.Index != 0 && !valid.Contains((ushort)item.Index) && !Main.HaX)
|
|
item = pouch.Items[i] = pouch.GetEmpty();
|
|
|
|
var cells = dgv.Rows[i].Cells;
|
|
cells[ColumnItem].Value = itemlist[item.Index];
|
|
cells[ColumnCount].Value = item.Count;
|
|
|
|
if (item is IItemFreeSpace f)
|
|
cells[ColumnFreeSpace].Value = f.IsFreeSpace;
|
|
if (item is IItemFreeSpaceIndex fi)
|
|
cells[ColumnFreeSpaceIndex].Value = fi.FreeSpaceIndex;
|
|
if (item is IItemFavorite v)
|
|
cells[ColumnFavorite].Value = v.IsFavorite;
|
|
if (item is IItemNewFlag n)
|
|
cells[ColumnNEW].Value = n.IsNew;
|
|
if (item is IItemNewShopFlag ns)
|
|
cells[ColumnNEWShop].Value = ns.IsNewShop;
|
|
if (item is IItemHeldFlag g)
|
|
cells[ColumnHeld].Value = g.IsHeld;
|
|
}
|
|
|
|
if (ItemColumnReadOnly) // Sort the column alphabetically.
|
|
{
|
|
dgv.Sort(dgv.Columns[ColumnItem], System.ComponentModel.ListSortDirection.Ascending);
|
|
dgv.ClearSelection();
|
|
}
|
|
IsCountValidationSuppressed = false;
|
|
}
|
|
|
|
private void Dgv_CellValueChanged(object? sender, DataGridViewCellEventArgs e)
|
|
{
|
|
if (IsCountValidationSuppressed)
|
|
return;
|
|
if (e.RowIndex < 0 || (e.ColumnIndex != ColumnCount && e.ColumnIndex != ColumnItem))
|
|
return;
|
|
if (sender is not DataGridView { Tag: InventoryPouch pouch } dgv)
|
|
return;
|
|
|
|
// Sanity check the item count against its maximum
|
|
var cells = dgv.Rows[e.RowIndex].Cells;
|
|
var itemName = cells[ColumnItem].Value?.ToString();
|
|
if (string.IsNullOrEmpty(itemName))
|
|
return;
|
|
|
|
var itemID = itemlist.IndexOf(itemName);
|
|
var cell = cells[ColumnCount];
|
|
var text = cell.Value?.ToString();
|
|
var count = Util.ToInt32(text);
|
|
var original = count;
|
|
if (Bag.IsQuantitySane(pouch.Type, itemID, ref count, HasNew, Main.HaX) && count == original && text == count.ToString())
|
|
return;
|
|
|
|
IsCountValidationSuppressed = true;
|
|
cell.Value = count;
|
|
IsCountValidationSuppressed = false;
|
|
}
|
|
|
|
private void SetBag(DataGridView dgv, InventoryPouch pouch)
|
|
{
|
|
int ctr = 0;
|
|
for (int i = 0; i < dgv.Rows.Count; i++)
|
|
{
|
|
var cells = dgv.Rows[i].Cells;
|
|
var str = cells[ColumnItem].Value!.ToString();
|
|
var itemID = itemlist.IndexOf(str);
|
|
|
|
if (itemID <= 0 && !HasNew) // Compression of Empty Slots
|
|
continue;
|
|
|
|
bool result = int.TryParse(cells[ColumnCount].Value?.ToString(), out var count);
|
|
if (!result)
|
|
continue;
|
|
if (!Bag.IsQuantitySane(pouch.Type, itemID, ref count, HasNew, Main.HaX))
|
|
continue; // ignore item
|
|
|
|
// create clean item data when saving
|
|
var item = pouch.GetEmpty(itemID, count);
|
|
if (item is IItemFreeSpace f)
|
|
f.IsFreeSpace = (bool)cells[ColumnFreeSpace].Value!;
|
|
if (item is IItemFreeSpaceIndex fi)
|
|
fi.FreeSpaceIndex = uint.TryParse(cells[ColumnFreeSpaceIndex].Value?.ToString(), out var fsi) ? fsi : 0;
|
|
if (item is IItemFavorite v)
|
|
v.IsFavorite = (bool)cells[ColumnFavorite].Value!;
|
|
if (item is IItemNewFlag n)
|
|
n.IsNew = (bool)cells[ColumnNEW].Value!;
|
|
if (item is IItemNewShopFlag ns)
|
|
ns.IsNewShop = (bool)cells[ColumnNEWShop].Value!;
|
|
if (item is IItemHeldFlag g)
|
|
g.IsHeld = (bool)cells[ColumnHeld].Value!;
|
|
|
|
pouch.Items[ctr] = item;
|
|
ctr++;
|
|
}
|
|
for (int i = ctr; i < pouch.Items.Length; i++)
|
|
pouch.Items[i] = pouch.GetEmpty(); // Empty Slots at the end
|
|
}
|
|
|
|
private void ChangeViewedPouch(int index)
|
|
{
|
|
var pouch = Bag.Pouches[index];
|
|
NUD_Count.Maximum = pouch.MaxCount;
|
|
|
|
bool disable = pouch.Type is InventoryType.PCItems or InventoryType.FreeSpace && Origin is not SAV8LA;
|
|
NUD_Count.Visible = L_Count.Visible = B_GiveAll.Visible = !disable;
|
|
if (disable && !Main.HaX)
|
|
{
|
|
giveMenu.Items.Remove(giveAll);
|
|
giveMenu.Items.Remove(giveModify);
|
|
}
|
|
else if (!giveMenu.Items.Contains(giveAll))
|
|
{
|
|
giveMenu.Items.Insert(0, giveAll);
|
|
giveMenu.Items.Add(giveModify);
|
|
}
|
|
NUD_Count.Value = Math.Max(1, pouch.MaxCount - 4);
|
|
}
|
|
|
|
// Initialize String Tables
|
|
private readonly string[] itemlist;
|
|
|
|
private string[] GetStringsForPouch(ReadOnlySpan<ushort> items, bool sort = true)
|
|
{
|
|
var result = new string[items.Length + 1];
|
|
for (int i = 0; i < result.Length - 1; i++)
|
|
result[i] = itemlist[items[i]];
|
|
result[items.Length] = itemlist[0];
|
|
if (sort)
|
|
Array.Sort(result);
|
|
return result;
|
|
}
|
|
|
|
// User Cheats
|
|
private int CurrentPouch => tabControl1.SelectedIndex;
|
|
private void SwitchBag(object sender, EventArgs e) => ChangeViewedPouch(CurrentPouch);
|
|
private void B_GiveAll_Click(object sender, EventArgs e) => ShowContextMenuBelow(giveMenu, (Control)sender);
|
|
private void B_Sort_Click(object sender, EventArgs e) => ShowContextMenuBelow(sortMenu, (Control)sender);
|
|
|
|
private void SortByName(object sender, EventArgs e) => ModifyPouch(CurrentPouch, p => p.SortByName(itemlist, reverse: sender == mnuSortNameReverse));
|
|
private void SortByCount(object sender, EventArgs e) => ModifyPouch(CurrentPouch, p => p.SortByCount(reverse: sender == mnuSortCountReverse));
|
|
private void SortByIndex(object sender, EventArgs e) => ModifyPouch(CurrentPouch, p => p.SortByIndex(reverse: sender == mnuSortIndexReverse));
|
|
|
|
private static void ShowContextMenuBelow(ToolStripDropDown c, Control n) => c.Show(n.PointToScreen(new Point(0, n.Height)));
|
|
|
|
private void GiveAllItems(object sender, EventArgs e)
|
|
{
|
|
var pouch = Bag.Pouches[CurrentPouch];
|
|
if (!GetModifySettings(pouch, out var truncate, out var shuffle))
|
|
return;
|
|
|
|
var items = pouch.GetAllItems().ToArray();
|
|
// No need to trim the list on truncation; we filter by IsLegal.
|
|
// GiveItem reaching a full pouch will fail silently (no exception thrown).
|
|
// This is equivalent to filtering and truncating eagerly.
|
|
// if (truncate)
|
|
{
|
|
if (shuffle)
|
|
Util.Rand.Shuffle(items);
|
|
}
|
|
|
|
ModifyPouch(CurrentPouch, p => p.GiveAllItems(Bag, items, (int)NUD_Count.Value));
|
|
System.Media.SystemSounds.Asterisk.Play();
|
|
}
|
|
|
|
private static bool GetModifySettings(InventoryPouch pouch, out bool truncate, out bool shuffle)
|
|
{
|
|
truncate = false;
|
|
shuffle = false;
|
|
if (!pouch.IsCramped)
|
|
return true;
|
|
|
|
var dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgItemPouchSizeSmall,
|
|
string.Format(MsgItemPouchRandom, Environment.NewLine));
|
|
if (dr == DialogResult.Cancel)
|
|
return false;
|
|
truncate = true;
|
|
if (dr == DialogResult.No)
|
|
shuffle = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
private void RemoveAllItems(object sender, EventArgs e)
|
|
{
|
|
ModifyPouch(CurrentPouch, p => p.RemoveAll());
|
|
WinFormsUtil.Alert(MsgItemCleared);
|
|
}
|
|
|
|
private void ModifyAllItems(object sender, EventArgs e)
|
|
{
|
|
ModifyPouch(CurrentPouch, p => p.ModifyAllCount(Bag, (int)NUD_Count.Value));
|
|
WinFormsUtil.Alert(MsgItemPouchCountUpdated);
|
|
}
|
|
|
|
private void ModifyPouch(int pouch, Action<InventoryPouch> func)
|
|
{
|
|
var dgv = GetGrid(pouch);
|
|
var p = Bag.Pouches[pouch];
|
|
SetBag(dgv, p); // save current
|
|
func(p); // update
|
|
GetBag(dgv, p); // load current
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// File specific utility class for creating a <see cref="ImageList"/> for displaying an icon in each of the tabs.
|
|
/// </summary>
|
|
file static class InventoryTypeImageUtil
|
|
{
|
|
/// <summary>
|
|
/// Gets the index within the <see cref="ImageList"/> for the given <see cref="InventoryType"/>.
|
|
/// </summary>
|
|
/// <remarks><see cref="InventoryType.None"/> is skipped.</remarks>
|
|
public static int GetImageIndex(InventoryType type) => (int)type - 1;
|
|
|
|
/// <summary>
|
|
/// Creates a <see cref="ImageList"/> for displaying an icon in each of the tabs.
|
|
/// </summary>
|
|
public static ImageList GetImageList()
|
|
{
|
|
var result = new ImageList
|
|
{
|
|
ImageSize = Properties.Resources.bag_items.Size, // Match the size of the resources.
|
|
};
|
|
var images = result.Images;
|
|
var types = Enum.GetValues<InventoryType>();
|
|
foreach (var type in types)
|
|
{
|
|
if (type is InventoryType.None)
|
|
continue;
|
|
var img = GetImage(type);
|
|
|
|
int index = GetImageIndex(type);
|
|
var name = type.ToString();
|
|
images.Add(name, img);
|
|
images.SetKeyName(index, name);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private static Bitmap GetImage(InventoryType type) => type switch
|
|
{
|
|
InventoryType.Items => Properties.Resources.bag_items,
|
|
InventoryType.KeyItems => Properties.Resources.bag_key,
|
|
InventoryType.TMHMs => Properties.Resources.bag_tech,
|
|
InventoryType.Medicine => Properties.Resources.bag_medicine,
|
|
InventoryType.Berries => Properties.Resources.bag_berries,
|
|
InventoryType.Balls => Properties.Resources.bag_balls,
|
|
InventoryType.BattleItems => Properties.Resources.bag_battle,
|
|
InventoryType.MailItems => Properties.Resources.bag_mail,
|
|
InventoryType.PCItems => Properties.Resources.bag_pcitems,
|
|
InventoryType.FreeSpace => Properties.Resources.bag_free,
|
|
InventoryType.ZCrystals => Properties.Resources.bag_z,
|
|
InventoryType.Candy => Properties.Resources.bag_candy,
|
|
InventoryType.Treasure => Properties.Resources.bag_treasure,
|
|
InventoryType.Ingredients => Properties.Resources.bag_ingredient,
|
|
InventoryType.MegaStones => Properties.Resources.bag_mega,
|
|
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
|
|
};
|
|
}
|