diff --git a/AllChestsMenu/AllChestsMenu.cs b/AllChestsMenu/AllChestsMenu.cs
index b0aa7da9..2afb4b4a 100644
--- a/AllChestsMenu/AllChestsMenu.cs
+++ b/AllChestsMenu/AllChestsMenu.cs
@@ -18,6 +18,17 @@
namespace AllChestsMenu
{
+ ///
+ /// Represents an item that can be consolidated from multiple chests
+ ///
+ public class ConsolidationItem
+ {
+ public Item SampleItem { get; set; }
+ public int TotalStack { get; set; }
+ public List ChestsContaining { get; set; }
+ public List ChestsWithSpace { get; set; }
+ }
+
public class AllChestsMenu : IClickableMenu
{
public const string chestsAnywhereNameKey = "Pathoschild.ChestsAnywhere/Name";
@@ -42,6 +53,27 @@ public enum Sort
CS
}
public Sort currentSort = Sort.LA;
+
+ private void CalculateResponsiveLayout()
+ {
+ int viewportWidth = Game1.graphics?.GraphicsDevice?.Viewport.Width ?? Game1.uiViewport.Width;
+ int viewportHeight = Game1.graphics?.GraphicsDevice?.Viewport.Height ?? Game1.uiViewport.Height;
+ effectiveViewportWidth = Math.Min(Game1.uiViewport.Width, viewportWidth);
+ effectiveViewportHeight = Math.Min(Game1.uiViewport.Height, viewportHeight);
+ compactLayout = effectiveViewportWidth < 1250 || effectiveViewportHeight < 820;
+
+ int availableWidth = effectiveViewportWidth - borderWidth * 4;
+ int minChestWidth = 64 * 12 + 32; // Largura mínima para um baú
+
+ // Determinar número de colunas baseado no espaço disponível
+ numberOfChestColumns = (availableWidth >= minChestWidth * 2 + 64) ? 2 : 1;
+
+ // Recalcular dimensões (Adiciona 64px de largura extra para as bordas laterais)
+ int chestAreaWidth = numberOfChestColumns * (64 * 12 + 64) + borderWidth * 2 + 64;
+ width = Math.Min(chestAreaWidth, effectiveViewportWidth - borderWidth * 2);
+ xPositionOnScreen = (effectiveViewportWidth - width) / 2;
+ }
+
public List allChestDataList = new();
public List chestDataList = new();
public InventoryMenu playerInventoryMenu;
@@ -62,6 +94,7 @@ public enum Sort
};
public ClickableTextureComponent trashCan;
public ClickableTextureComponent organizeButton;
+ public ClickableTextureComponent clearFiltersButton;
public TextBox locationText;
public ClickableComponent lastTopSnappedCC;
public ClickableComponent locationTextCC;
@@ -84,21 +117,127 @@ public enum Sort
public TextBox renameBox;
public ClickableTextureComponent okButton;
public ClickableTextureComponent storeAlikeButton;
+ public ClickableTextureComponent consolidateButton;
+ public ClickableTextureComponent sortAllButton;
public string filterString;
public string nameString;
+
+ // New filter fields
+ public TextBox itemNameText;
+ public TextBox chestLabelText;
+ public TextBox itemDescriptionText;
+ public ClickableComponent chestLabelCC;
+ public ClickableComponent itemNameCC;
+ public ClickableComponent itemDescCC;
+ public ClickableComponent locationDropdownCC;
+ public List locationOptionsCCList = new();
+ private bool locationDropdownOpen = false;
+ public List filterCheckboxCCList = new();
+ public List chestHeaders = new();
+
+ // Filter tracking for real-time updates
+ private string lastItemNameFilter = "";
+ private string lastItemDescriptionFilter = "";
+
+ // Responsive layout fields
+ internal int numberOfChestColumns = 2;
+ private readonly Color[] chestBgColors = new Color[]
+ {
+ new Color(0, 0, 0, 30), // Semi-transparent gray
+ new Color(0, 0, 0, 15) // Lighter semi-transparent gray
+ };
+
+ public int filterAreaHeight; // Altura da área de filtros
+ public int filtersY; // Posição Y dos filtros
+ public int buttonsY; // Posição Y dos botões
+ public int buttonsHeight; // Altura da área de botões
+ public int inventoryHeight; // Altura do inventário
public string fridgeString;
public string sortString;
+
+ private bool gamepadShift = false;
- public int clickpos;
+ public int clickpos;
public bool draggingScrollbar;
public int scrollbarWidth;
private bool scrolling;
private int totalHeight;
-
- public AllChestsMenu() : base(Game1.uiViewport.Width / 2 - (windowWidth + borderWidth * 2) / 2, -borderWidth - 64, windowWidth + borderWidth * 2, Game1.uiViewport.Height + borderWidth * 2 + 64, false)
+ public int bottomAreaHeight; // To store for drawing
+ private int locationOptionBaseId => 6 * ccMagnitude;
+ private int effectiveViewportWidth;
+ private int effectiveViewportHeight;
+ private bool compactLayout;
+ private bool stackedBottomControls;
+ private int playerInventoryColumns = 12;
+ private int playerInventoryRows = 3;
+ private float trashLidScale = 4f;
+
+ public AllChestsMenu() : base(0, -borderWidth - 64, Game1.uiViewport.Width, Game1.uiViewport.Height + borderWidth * 2 + 64, false)
{
+ // Calcular layout responsivo
+ CalculateResponsiveLayout();
+ stackedBottomControls = numberOfChestColumns == 1;
+
currentSort = ModEntry.Config.CurrentSort;
- cutoff = Game1.uiViewport.Height - 64 * 3 - 8 - borderWidth;
+ int actionButtonSize = stackedBottomControls ? (compactLayout ? 40 : 52) : (compactLayout ? 48 : 64);
+ int actionButtonGap = stackedBottomControls ? (compactLayout ? 4 : 6) : (compactLayout ? 6 : 8);
+ float actionButtonScale = stackedBottomControls ? (compactLayout ? 2.5f : 3.3f) : (compactLayout ? 3f : 4f);
+ float compactFilterScale = stackedBottomControls ? 0.8f : 1f;
+ int stackedControlGap = compactLayout ? 8 : 10;
+ int sortOptionCount = Enum.GetNames(typeof(Sort)).Length - 1;
+ int sortLabelPixelWidth = stackedBottomControls ? (compactLayout ? 54 : 66) : 0;
+ int sortInlineGap = stackedBottomControls ? 6 : 0;
+ int sortXStep = stackedBottomControls ? (compactLayout ? 25 : 30) : (compactLayout ? 48 : 64);
+ int sortYStep = stackedBottomControls ? 0 : (compactLayout ? 32 : 40);
+ int sortSize = stackedBottomControls ? (compactLayout ? 18 : 22) : (compactLayout ? 26 : 32);
+ int sortRowWidth = stackedBottomControls ? sortLabelPixelWidth + sortInlineGap + ((sortOptionCount - 1) * sortXStep) + sortSize : 0;
+ int trashWidth = stackedBottomControls ? (compactLayout ? 30 : 40) : (compactLayout ? 48 : 64);
+ int trashHeight = stackedBottomControls ? (compactLayout ? 46 : 60) : (compactLayout ? 78 : 104);
+ int topControlsHeight = Math.Max(actionButtonSize, Math.Max(trashHeight, sortSize));
+ int topControlsOffset = stackedBottomControls ? actionButtonSize / 2 : 0;
+ int sortYStart = stackedBottomControls ? (topControlsHeight - sortSize) / 2 : (compactLayout ? 84 : 104);
+ int sortBlockBottom = stackedBottomControls ? topControlsOffset + topControlsHeight : sortYStart + sortYStep + sortSize;
+ int rightControlBlockHeight = stackedBottomControls ? sortBlockBottom : Math.Max(sortBlockBottom, actionButtonSize + stackedControlGap + trashHeight);
+ trashLidScale = actionButtonScale;
+ int totalPlayerSlots = Math.Max(12, Game1.player.Items.Count);
+ int smallFilterWidth = (int)Math.Round((compactLayout ? 104 : 120) * compactFilterScale);
+ int filterXStart = xPositionOnScreen + borderWidth + (stackedBottomControls ? 12 : 24);
+ int filtersTotalWidth = filterXStart + smallFilterWidth + 16;
+ if (stackedBottomControls)
+ {
+ int availableInventoryWidth = width - borderWidth * 2 - 12 - smallFilterWidth - 24 - 12;
+ playerInventoryColumns = Math.Max(6, Math.Min(12, availableInventoryWidth / 64));
+ playerInventoryRows = Math.Max(3, (int)Math.Ceiling(totalPlayerSlots / (float)playerInventoryColumns));
+ }
+ else
+ {
+ playerInventoryColumns = 12;
+ playerInventoryRows = Math.Min(3, (int)Math.Ceiling(totalPlayerSlots / 12f));
+ }
+
+ inventoryHeight = 64 * playerInventoryRows;
+
+ // Layout claro: área de baús (topo) e área de inventário/filtros (fundo)
+ inventoryHeight = 64 * 3; // Altura do inventário
+
+ // Filters stack height: 4 filtros + labels + espaçamento
+ inventoryHeight = 64 * playerInventoryRows;
+ int filterStackHeight = compactLayout ? 320 : 380;
+ if (stackedBottomControls)
+ {
+ filterStackHeight = Math.Max(filterStackHeight, rightControlBlockHeight + stackedControlGap + inventoryHeight);
+ }
+ int verticalPadding = compactLayout ? 40 : 64;
+ bottomAreaHeight = Math.Max(inventoryHeight + verticalPadding, filterStackHeight) + borderWidth + verticalPadding;
+ cutoff = effectiveViewportHeight - bottomAreaHeight; // Y onde a área de baús termina
+
+ // Centraliza o conjunto (inventário + botões) empurrado pra direita se necessário
+
+
+ int availableVerticalSpace = effectiveViewportHeight - cutoff;
+ int inventoryY = stackedBottomControls
+ ? cutoff + borderWidth + (compactLayout ? 14 : 20) + rightControlBlockHeight + stackedControlGap
+ : cutoff + (availableVerticalSpace - inventoryHeight) / 2;
widgetText = new string[]{
ModEntry.SHelper.Translation.Get("open"),
Game1.content.LoadString("Strings\\UI:ItemGrab_Organize"),
@@ -107,68 +246,126 @@ public AllChestsMenu() : base(Game1.uiViewport.Width / 2 - (windowWidth + border
ModEntry.SHelper.Translation.Get("rename"),
ModEntry.SHelper.Translation.Get("target")
};
- filterString = ModEntry.SHelper.Translation.Get("filter");
+ filterString = ModEntry.SHelper.Translation.Get("name"); // "Name" or "Chest" depending on translation
nameString = ModEntry.SHelper.Translation.Get("name");
fridgeString = ModEntry.SHelper.Translation.Get("fridge");
sortString = ModEntry.SHelper.Translation.Get("sort");
- int columns = 12;
- int rows = Math.Min(3, (int)Math.Ceiling((double)Game1.player.Items.Count / columns));
+ int columns = playerInventoryColumns;
+ int rows = playerInventoryRows;
int capacity = rows * columns;
- playerInventoryMenu = new InventoryMenu((Game1.uiViewport.Width - 64 * columns) / 2, Game1.uiViewport.Height - 64 * 3 - borderWidth / 2, false, Game1.player.Items, null, capacity, rows);
- SetPlayerInventoryNeighbours();
- trashCan = new ClickableTextureComponent(new Rectangle(playerInventoryMenu.xPositionOnScreen + playerInventoryMenu.width + 64 + 32 + 8, playerInventoryMenu.yPositionOnScreen + 64 + 16, 64, 104), Game1.mouseCursors, new Rectangle(564 + Game1.player.trashCanLevel * 18, 102, 18, 26), 4f, false)
+ // Centraliza o inventário e evita sobreposição com os filtros no layout de 1 coluna
+ int inventoryPixelWidth = 64 * columns;
+ int startX;
+ if (stackedBottomControls)
+ {
+ int centeredInventoryX = xPositionOnScreen + (width - inventoryPixelWidth) / 2;
+ int maxInventoryX = xPositionOnScreen + width - borderWidth - inventoryPixelWidth - 12;
+ int maxFilterWidth = maxInventoryX - filterXStart - 24;
+ int minFilterWidth = compactLayout ? 44 : 56;
+ smallFilterWidth = Math.Max(minFilterWidth, Math.Min(smallFilterWidth, maxFilterWidth));
+ int minInventoryX = filterXStart + smallFilterWidth + 24;
+ startX = Math.Max(minInventoryX, centeredInventoryX);
+ startX = Math.Min(startX, maxInventoryX);
+ }
+ else
{
- myID = 4 * ccMagnitude + 2,
- leftNeighborID = 11,
- upNeighborID = 4 * ccMagnitude,
- rightNeighborID = 5 * ccMagnitude
- };
- organizeButton = new ClickableTextureComponent("", new Rectangle(playerInventoryMenu.xPositionOnScreen + playerInventoryMenu.width + 64, playerInventoryMenu.yPositionOnScreen, 64, 64), "", Game1.content.LoadString("Strings\\UI:ItemGrab_Organize"), Game1.mouseCursors, new Rectangle(162, 440, 16, 16), 4f, false)
+ int totalBottomWidth = inventoryPixelWidth + 32 + (actionButtonSize * 5) + (actionButtonSize + 16); // inventario + espaco + botoes e lixeira
+ startX = (effectiveViewportWidth - totalBottomWidth) / 2;
+ startX += 40; // Deslocar para a direita para balancear com os filtros na esqueda
+ if (startX < filtersTotalWidth) startX = filtersTotalWidth;
+ }
+
+ playerInventoryMenu = new InventoryMenu(
+ startX,
+ inventoryY,
+ false,
+ Game1.player.Items,
+ null,
+ capacity,
+ rows);
+ SetPlayerInventoryNeighbours();
+ int playerTopRightSlot = GetPlayerInventoryRowEndIndex(0);
+ int topButtonBlockWidth = actionButtonSize * 4 + actionButtonGap * 4 + trashWidth;
+ int sortLabelX = stackedBottomControls
+ ? playerInventoryMenu.xPositionOnScreen + playerInventoryMenu.width - (sortRowWidth + actionButtonGap + topButtonBlockWidth)
+ : 0;
+ int sortOptionsX = stackedBottomControls ? sortLabelX + sortLabelPixelWidth + sortInlineGap : 0;
+ int actionButtonsX = stackedBottomControls
+ ? sortOptionsX + ((sortOptionCount - 1) * sortXStep) + sortSize + actionButtonGap
+ : playerInventoryMenu.xPositionOnScreen + playerInventoryMenu.width + 32;
+ int actionButtonsY = stackedBottomControls
+ ? cutoff + borderWidth + (compactLayout ? 14 : 20) + topControlsOffset
+ : playerInventoryMenu.yPositionOnScreen - (compactLayout ? 8 : 16);
+ organizeButton = new ClickableTextureComponent("", new Rectangle(actionButtonsX, actionButtonsY, actionButtonSize, actionButtonSize), "", Game1.content.LoadString("Strings\\UI:ItemGrab_Organize"), Game1.mouseCursors, new Rectangle(162, 440, 16, 16), actionButtonScale, false)
{
myID = 4 * ccMagnitude,
- downNeighborID = 4 * ccMagnitude + 2,
- leftNeighborID = 11,
+ downNeighborID = 4 * ccMagnitude + 1,
+ leftNeighborID = playerTopRightSlot,
rightNeighborID = 4 * ccMagnitude + 1
};
- storeAlikeButton = new ClickableTextureComponent("", new Rectangle(playerInventoryMenu.xPositionOnScreen + playerInventoryMenu.width + 64 + 64 + 16, playerInventoryMenu.yPositionOnScreen, 64, 64), "", Game1.content.LoadString("Strings\\UI:ItemGrab_FillStacks"), Game1.mouseCursors, new Rectangle(103, 469, 16, 16), 4f, false)
+ storeAlikeButton = new ClickableTextureComponent("", new Rectangle(organizeButton.bounds.X + actionButtonSize + actionButtonGap, organizeButton.bounds.Y, actionButtonSize, actionButtonSize), "", Game1.content.LoadString("Strings\\UI:ItemGrab_FillStacks"), Game1.mouseCursors, new Rectangle(103, 469, 16, 16), actionButtonScale, false)
{
myID = 4 * ccMagnitude + 1,
downNeighborID = 4 * ccMagnitude + 2,
leftNeighborID = 4 * ccMagnitude,
- rightNeighborID = 5 * ccMagnitude,
+ rightNeighborID = 4 * ccMagnitude + 2,
upNeighborID = 4 * ccMagnitude
};
- locationText = new TextBox(Game1.content.Load("LooseSprites\\textBox"), null, Game1.smallFont, Game1.textColor)
+
+ // Consolidate button - merges duplicate items across chests
+ string consolidateText = ModEntry.SHelper.Translation.Get("consolidate");
+ string consolidateTooltip = ModEntry.SHelper.Translation.Get("consolidate-tooltip");
+ consolidateButton = new ClickableTextureComponent("", new Rectangle(storeAlikeButton.bounds.X + actionButtonSize + actionButtonGap, organizeButton.bounds.Y, actionButtonSize, actionButtonSize), "", consolidateTooltip, Game1.mouseCursors, new Rectangle(257, 284, 16, 16), actionButtonScale, false)
{
- X = xPositionOnScreen + borderWidth,
- Width = (width - playerInventoryMenu.width) / 2 - borderWidth * 2 - 32,
- Y = cutoff + borderWidth + 32,
- Text = whichLocation
+ myID = 4 * ccMagnitude + 2,
+ downNeighborID = 4 * ccMagnitude + 3,
+ leftNeighborID = 4 * ccMagnitude + 1,
+ rightNeighborID = 4 * ccMagnitude + 3,
+ upNeighborID = 4 * ccMagnitude
+ };
+
+ // Sort all button - sorts all items in all chests
+ string sortAllText = ModEntry.SHelper.Translation.Get("sort-all");
+ string sortAllTooltip = ModEntry.SHelper.Translation.Get("sort-all-tooltip");
+ sortAllButton = new ClickableTextureComponent("", new Rectangle(consolidateButton.bounds.X + actionButtonSize + actionButtonGap, organizeButton.bounds.Y, actionButtonSize, actionButtonSize), "", sortAllTooltip, Game1.mouseCursors, new Rectangle(162, 440, 16, 16), actionButtonScale, false)
+ {
+ myID = 4 * ccMagnitude + 3,
+ downNeighborID = 4 * ccMagnitude + 4,
+ leftNeighborID = 4 * ccMagnitude + 2,
+ rightNeighborID = 5 * ccMagnitude,
+ upNeighborID = 4 * ccMagnitude
};
- locationTextCC = new ClickableComponent(new Rectangle(locationText.X, locationText.Y, locationText.Width, locationText.Height), "")
+
+ // Update trashCan position
+ int trashX = stackedBottomControls
+ ? sortAllButton.bounds.Right + actionButtonGap
+ : sortAllButton.bounds.X + actionButtonSize + 16;
+ int trashY = stackedBottomControls
+ ? organizeButton.bounds.Y + (actionButtonSize - trashHeight) / 2
+ : playerInventoryMenu.yPositionOnScreen + (compactLayout ? 18 : 28);
+ trashCan = new ClickableTextureComponent(new Rectangle(trashX, trashY, trashWidth, trashHeight), Game1.mouseCursors, new Rectangle(564 + Game1.player.trashCanLevel * 18, 102, 18, 26), actionButtonScale, false)
+ {
+ myID = 4 * ccMagnitude + 4,
+ leftNeighborID = 4 * ccMagnitude + 3,
+ upNeighborID = 4 * ccMagnitude + 3,
+ rightNeighborID = 5 * ccMagnitude
+ };
+ locationTextCC = new ClickableComponent(new Rectangle(0, 0, 0, 0), "") // Placeholder, será definido depois
{
myID = 2 * ccMagnitude,
upNeighborID = 1 * ccMagnitude,
rightNeighborID = 0,
downNeighborID = 2 * ccMagnitude + 1
};
- renameBox = new TextBox(Game1.content.Load("LooseSprites\\textBox"), null, Game1.smallFont, Game1.textColor)
- {
- X = locationText.X,
- Width = locationText.Width,
- Y = locationText.Y + locationText.Height + 48
- };
- renameBoxCC = new ClickableComponent(new Rectangle(renameBox.X, renameBox.Y, renameBox.Width, renameBox.Height), "")
+ renameBoxCC = new ClickableComponent(new Rectangle(0, 0, 0, 0), "") // Placeholder, será definido depois
{
myID = 2 * ccMagnitude + 1,
upNeighborID = 2 * ccMagnitude,
rightNeighborID = 2 * ccMagnitude + 2
};
- locationText.Selected = false;
- renameBox.Selected = false;
- okButton = new ClickableTextureComponent(new Rectangle(renameBox.X + renameBox.Width + 4, renameBox.Y, 48, 48), Game1.mouseCursors, Game1.getSourceRectForStandardTileSheet(Game1.mouseCursors, 46, -1, -1), 0.75f, false)
+ okButton = new ClickableTextureComponent(new Rectangle(0, 0, 48, 48), Game1.mouseCursors, Game1.getSourceRectForStandardTileSheet(Game1.mouseCursors, 46, -1, -1), 0.75f, false)
{
myID = 2 * ccMagnitude + 2,
leftNeighborID = 2 * ccMagnitude + 1,
@@ -179,20 +376,133 @@ public AllChestsMenu() : base(Game1.uiViewport.Width / 2 - (windowWidth + border
for (int i = 0; i < s.Length - 1; i++)
{
- int row = i % 2;
+ int row = stackedBottomControls ? 0 : i % 2;
string name = s[i];
int idx = 5 * ccMagnitude;
sortNames[name] = ModEntry.SHelper.Translation.Get("sort-" + name);
- sortCCList.Add(new ClickableComponent(new Rectangle(organizeButton.bounds.X + 156 + i / 2 * 48 + 32, organizeButton.bounds.Y + 64 + row * 48 + 16, 32, 32), name, name)
+ int sortGridX = stackedBottomControls ? sortOptionsX : organizeButton.bounds.X;
+ int sortColumn = stackedBottomControls ? i : (i / 2);
+ sortCCList.Add(new ClickableComponent(new Rectangle(sortGridX + sortColumn * sortXStep, organizeButton.bounds.Y + sortYStart + row * sortYStep, sortSize, sortSize), name, name)
{
myID = idx + i,
- leftNeighborID = i > 2 ? idx + i - 2: 4 * ccMagnitude + 1,
- rightNeighborID = i < s.Length - 2 ? idx + i + 2 : -1,
- downNeighborID = row == 0 ? idx + i + 1 : -1,
- upNeighborID = row == 1 ? idx + i - 1 : -1
+ leftNeighborID = stackedBottomControls ? (i > 0 ? idx + i - 1 : 4 * ccMagnitude + 1) : (i > 2 ? idx + i - 2 : 4 * ccMagnitude + 1),
+ rightNeighborID = stackedBottomControls ? (i < s.Length - 2 ? idx + i + 1 : -1) : (i < s.Length - 2 ? idx + i + 2 : -1),
+ downNeighborID = stackedBottomControls ? -1 : (row == 0 ? idx + i + 1 : -1),
+ upNeighborID = stackedBottomControls ? -1 : (row == 1 ? idx + i - 1 : -1)
});
}
+
+ // Initialize new filter fields - layout vertical na esquerda
+ int filterYStart = effectiveViewportHeight - bottomAreaHeight + borderWidth + (compactLayout ? 52 : 64) + 16;
+ int filterSpacing = compactLayout ? 72 : 84;
+ int locationDropdownHeight = stackedBottomControls ? 32 : 40;
+
+ // Chest Label filter
+ chestLabelText = new TextBox(Game1.content.Load("LooseSprites\\textBox"), null, Game1.smallFont, Game1.textColor)
+ {
+ X = filterXStart,
+ Y = filterYStart,
+ Width = smallFilterWidth
+ };
+ chestLabelCC = new ClickableComponent(new Rectangle(chestLabelText.X, chestLabelText.Y, chestLabelText.Width, chestLabelText.Height), "")
+ {
+ myID = 2 * ccMagnitude + 3,
+ upNeighborID = 3 * ccMagnitude + 1,
+ downNeighborID = 2 * ccMagnitude + 4,
+ rightNeighborID = playerInventoryMenu.inventory[0].myID
+ };
+
+ // Item Name filter
+ itemNameText = new TextBox(Game1.content.Load("LooseSprites\\textBox"), null, Game1.smallFont, Game1.textColor)
+ {
+ X = filterXStart,
+ Y = filterYStart + filterSpacing,
+ Width = smallFilterWidth
+ };
+ itemNameCC = new ClickableComponent(new Rectangle(itemNameText.X, itemNameText.Y, itemNameText.Width, itemNameText.Height), "")
+ {
+ myID = 2 * ccMagnitude + 4,
+ upNeighborID = 2 * ccMagnitude + 3,
+ downNeighborID = 2 * ccMagnitude + 6,
+ rightNeighborID = GetPlayerInventoryRowStartIndex(1) >= 0 ? playerInventoryMenu.inventory[GetPlayerInventoryRowStartIndex(1)].myID : playerInventoryMenu.inventory[0].myID
+ };
+
+ // Item Description filter
+ itemDescriptionText = new TextBox(Game1.content.Load("LooseSprites\\textBox"), null, Game1.smallFont, Game1.textColor)
+ {
+ X = filterXStart,
+ Y = filterYStart + filterSpacing * 2,
+ Width = smallFilterWidth
+ };
+ itemDescCC = new ClickableComponent(new Rectangle(itemDescriptionText.X, itemDescriptionText.Y, itemDescriptionText.Width, itemDescriptionText.Height), "")
+ {
+ myID = 2 * ccMagnitude + 6,
+ upNeighborID = 2 * ccMagnitude + 4,
+ downNeighborID = 2 * ccMagnitude + 7,
+ rightNeighborID = GetPlayerInventoryRowStartIndex(2) >= 0 ? playerInventoryMenu.inventory[GetPlayerInventoryRowStartIndex(2)].myID : playerInventoryMenu.inventory[0].myID
+ };
+
+ // Location dropdown (vertical na esquerda)
+ locationDropdownCC = new ClickableComponent(new Rectangle(filterXStart, filterYStart + filterSpacing * 3, smallFilterWidth, locationDropdownHeight), "location", "Location")
+ {
+ myID = 2 * ccMagnitude + 7,
+ upNeighborID = 2 * ccMagnitude + 6,
+ downNeighborID = 5 * ccMagnitude,
+ rightNeighborID = GetPlayerInventoryRowStartIndex(3) >= 0 ? playerInventoryMenu.inventory[GetPlayerInventoryRowStartIndex(3)].myID : playerInventoryMenu.inventory[GetPlayerInventoryRowStartIndex(2)].myID
+ };
+
+ // Keep original locationText for backward compatibility
+ locationText = chestLabelText;
+
+ // Rename box (for renaming chests) - posicionado fora da tela (escondido)
+ renameBox = new TextBox(Game1.content.Load("LooseSprites\\textBox"), null, Game1.smallFont, Game1.textColor)
+ {
+ X = xPositionOnScreen + borderWidth,
+ Width = 200,
+ Y = -1000 // Escondido fora da tela
+ };
+ renameBoxCC = new ClickableComponent(new Rectangle(renameBox.X, renameBox.Y, renameBox.Width, renameBox.Height), "")
+ {
+ myID = 2 * ccMagnitude + 8,
+ upNeighborID = 2 * ccMagnitude + 7,
+ downNeighborID = 5 * ccMagnitude
+ };
+
+ // Filter checkboxes with controller navigation
+ int checkboxX = xPositionOnScreen + borderWidth;
+ int checkboxSize = stackedBottomControls ? 19 : 24;
+
+ filterCheckboxCCList.Add(new ClickableComponent(
+ new Rectangle(chestLabelText.X - checkboxSize - 4, chestLabelText.Y + 4, checkboxSize, checkboxSize), "chestLabelChk"));
+ filterCheckboxCCList.Add(new ClickableComponent(
+ new Rectangle(itemNameText.X - checkboxSize - 4, itemNameText.Y + 4, checkboxSize, checkboxSize), "itemNameChk"));
+ filterCheckboxCCList.Add(new ClickableComponent(
+ new Rectangle(itemDescriptionText.X - checkboxSize - 4, itemDescriptionText.Y + 4, checkboxSize, checkboxSize), "itemDescChk"));
+ filterCheckboxCCList.Add(new ClickableComponent(
+ new Rectangle(locationDropdownCC.bounds.X - checkboxSize - 4, locationDropdownCC.bounds.Y + 8, checkboxSize, checkboxSize), "locationChk"));
+
+ // Clear filters button - 20px to the right of the top filter, vertically centered with text box
+ int clearButtonSize = stackedBottomControls ? (compactLayout ? 32 : 38) : (compactLayout ? 40 : 48);
+ float clearButtonScale = stackedBottomControls ? (compactLayout ? 2.6f : 3.1f) : (compactLayout ? 3.3f : 4f);
+ int clearBtnY = filterYStart - (stackedBottomControls ? 4 : 10);
+ int clearBtnX = filterXStart + smallFilterWidth + (stackedBottomControls ? 14 : 20);
+ clearFiltersButton = new ClickableTextureComponent(
+ "",
+ new Rectangle(clearBtnX, clearBtnY, clearButtonSize, clearButtonSize),
+ "",
+ ModEntry.SHelper.Translation.Get("clear-filters"),
+ Game1.mouseCursors,
+ new Rectangle(337, 494, 12, 12), // X icon
+ clearButtonScale,
+ false
+ )
+ {
+ myID = 4 * ccMagnitude + 5,
+ leftNeighborID = 2 * ccMagnitude + 7,
+ rightNeighborID = playerInventoryMenu.inventory.Count > 0 ? playerInventoryMenu.inventory[0].myID : -1
+ };
+
exitFunction = emergencyShutDown;
PopulateMenus(true);
snapToDefaultClickableComponent();
@@ -204,6 +514,23 @@ private void PopulateMenus(bool resetAllChestDataList = false)
{
ResetAllChestList();
}
+
+ // Rebuild location options list whenever menus are populated
+ // This ensures the dropdown options are always up-to-date and clickable
+ locationOptionsCCList.Clear();
+ var uniqueLocations = allChestDataList.GroupBy(c => c.locationDisplayName).Select(g => g.First()).ToList();
+ for (int i = 0; i < uniqueLocations.Count; i++)
+ {
+ string loc = uniqueLocations[i].locationDisplayName;
+ string displayLoc = loc;
+ locationOptionsCCList.Add(new ClickableComponent(new Rectangle(locationDropdownCC.bounds.X, locationDropdownCC.bounds.Bottom + i * 36, Math.Max(locationDropdownCC.bounds.Width, 300), 36), loc, displayLoc)
+ {
+ myID = locationOptionBaseId + i
+ });
+ }
+ UpdateLocationOptionBounds();
+ RebuildControllerNavigation();
+
ResetChestList();
}
@@ -239,6 +566,19 @@ private void ResetAllChestList()
}
}
+ // Método auxiliar para obter a cor do baú real em vez de transparente
+ Color GetChestColor(Chest chest)
+ {
+ if (chest == null)
+ return new Color(139, 69, 19, 255); // Marrom padrão
+
+ // Usar a cor escolhida pelo jogador ou cor padrão
+ Color baseColor = chest.playerChoiceColor.Value;
+ if (baseColor == Color.Black)
+ return new Color(139, 69, 19, 255); // Prevent black chests from being pure black boxes, fallback to standard wood color unless it's a specific black dye (standard chest defaults to black color value initially though default is no dye). Wait, actually default chest color isn't Color.Black, but playerChoiceColor.Value defaults to Color.Black if uncolored. Let's just use it but wait, in SDV uncolored chest has playerChoiceColor.Value == Color.Black. We should check if playerChoiceColor.Value.Equals(Color.Black). Note: default uncolored chest has playerChoiceColor.Value == Color.Black.
+ return new Color(baseColor.R, baseColor.G, baseColor.B, (byte)255);
+ }
+
void AddChestsFromLocation(GameLocation location)
{
GameLocation parentLocation = location.GetParentLocation();
@@ -272,7 +612,7 @@ void AddChestsFromLocation(GameLocation location)
{
label = $"{fridgeName} ({fridgeString})";
}
- allChestDataList.Add(new ChestData() { chest = fridge, name = fridgeName, location = farmhouse.NameOrUniqueName, tile = new Vector2(-1, -1), label = label, originalIndex = allChestDataList.Count, index = allChestDataList.Count });
+ allChestDataList.Add(new ChestData() { chest = fridge, name = fridgeName, location = farmhouse.NameOrUniqueName, locationDisplayName = locationDisplayName, tile = new Vector2(-1, -1), label = label, originalIndex = allChestDataList.Count, index = allChestDataList.Count, chestColor = GetChestColor(fridge) });
}
}
if (!shippingBinAlreadyAdded && ModEntry.Config.IncludeShippingBin && location.IsBuildableLocation())
@@ -286,7 +626,7 @@ void AddChestsFromLocation(GameLocation location)
shippingBinAlreadyAdded = true;
RestoreNulls(shippingBin);
- allChestDataList.Add(new ChestData() { chest = shippingBin, name = "", location = location.NameOrUniqueName, tile = new Vector2(building.tileX.Value, building.tileY.Value), label = label, originalIndex = allChestDataList.Count, index = allChestDataList.Count });
+ allChestDataList.Add(new ChestData() { chest = shippingBin, name = "", location = location.NameOrUniqueName, locationDisplayName = locationDisplayName, tile = new Vector2(building.tileX.Value, building.tileY.Value), label = label, originalIndex = allChestDataList.Count, index = allChestDataList.Count, chestColor = GetChestColor(shippingBin) });
break;
}
}
@@ -298,7 +638,7 @@ void AddChestsFromLocation(GameLocation location)
shippingBinAlreadyAdded = true;
RestoreNulls(shippingBin);
- allChestDataList.Add(new ChestData() { chest = shippingBin, name = "", location = location.NameOrUniqueName, tile = new Vector2(90, 39), label = label, originalIndex = allChestDataList.Count, index = allChestDataList.Count });
+ allChestDataList.Add(new ChestData() { chest = shippingBin, name = "", location = location.NameOrUniqueName, locationDisplayName = locationDisplayName, tile = new Vector2(90, 39), label = label, originalIndex = allChestDataList.Count, index = allChestDataList.Count, chestColor = GetChestColor(shippingBin) });
}
foreach (KeyValuePair kvp in location.objects.Pairs)
{
@@ -343,7 +683,7 @@ void AddChestsFromLocation(GameLocation location)
{
chestName = "";
}
- allChestDataList.Add(new ChestData() { chest = chest, name = chestName, location = location.NameOrUniqueName, tile = new Vector2(kvp.Key.X, kvp.Key.Y), label = label, originalIndex = allChestDataList.Count, index = allChestDataList.Count });
+ allChestDataList.Add(new ChestData() { chest = chest, name = chestName, location = location.NameOrUniqueName, locationDisplayName = locationDisplayName, tile = new Vector2(kvp.Key.X, kvp.Key.Y), label = label, originalIndex = allChestDataList.Count, index = allChestDataList.Count, chestColor = GetChestColor(chest) });
}
}
@@ -353,9 +693,11 @@ void AddChestsFromLocation(GameLocation location)
private void ResetChestList()
{
int menusAlready = 0;
+ int locationGaps = 0;
int rowsAlready = 0;
bool even = false;
int oddRows = 0;
+ string lastLocation = null;
chestDataList.Clear();
inventoryButtons.Clear();
@@ -373,55 +715,86 @@ private void ResetChestList()
if (!string.IsNullOrEmpty(whichLocation))
{
- var searchTerm = whichLocation.ToLower().Trim();
- var chestNameMatches = chestData.label.ToLower().Contains(searchTerm);
- var anyItemMatches = false;
- if (ModEntry.Config.FilterItems || ModEntry.Config.FilterItemsCategory || ModEntry.Config.FilterItemsDescription)
- {
- anyItemMatches = chestData.chest.Items.Any(item =>
- {
- if (item == null)
- return false;
-
- bool nameMatch = ModEntry.Config.FilterItems && item.DisplayName.ToLower().Trim().Contains(searchTerm);
- if (nameMatch) return true;
+ if (!chestData.label.ToLower().Contains(whichLocation.ToLower().Trim()))
+ continue;
+ }
- bool categoryMatch = ModEntry.Config.FilterItemsCategory && item.getCategoryName().ToLower().Trim().Contains(searchTerm);
- if (categoryMatch) return true;
+ bool hasItemFilter = !string.IsNullOrEmpty(itemNameText.Text) ||
+ !string.IsNullOrEmpty(itemDescriptionText.Text);
- bool descriptionMatch = ModEntry.Config.FilterItemsDescription && item.getDescription().ToLower().Trim().Contains(searchTerm);
- if (descriptionMatch) return true;
-
- return false;
- });
- }
+ if (hasItemFilter)
+ {
+ string searchName = itemNameText.Text.ToLower().Trim();
+ string searchDesc = itemDescriptionText.Text.ToLower().Trim();
- if (!chestNameMatches && !anyItemMatches)
+ bool anyItemMatches = chestData.chest.Items.Any(item =>
{
+ if (item == null) return false;
+
+ if (!string.IsNullOrEmpty(searchName) && !item.DisplayName.ToLower().Trim().Contains(searchName)) return false;
+ if (!string.IsNullOrEmpty(searchDesc) && !item.getDescription().ToLower().Trim().Contains(searchDesc)) return false;
+
+ return true;
+ });
+
+ if (!anyItemMatches)
continue;
- }
}
+ if (ModEntry.Config.SelectedLocations.Count > 0 && !ModEntry.Config.SelectedLocations.Contains(chestData.locationDisplayName))
+ {
+ continue;
+ }
+
+ // Location grouping check
+ if (lastLocation != null && chestData.locationDisplayName != lastLocation)
+ {
+ if (numberOfChestColumns > 1 && even) // In 2-column mode, finish the partial row before starting a new location group
+ {
+ var prevChest = chestDataList[chestDataList.Count - 1];
+ rowsAlready += Math.Max(!prevChest.collapsed ? Math.Max(prevChest.menu.rows, 3) : 0, oddRows);
+ menusAlready++;
+ even = false;
+ }
+ menusAlready++; // Add extra header space for the new location
+ locationGaps++;
+ }
+ else if (lastLocation == null)
+ {
+ menusAlready++; // Extra header space for the very first location
+ }
+ lastLocation = chestData.locationDisplayName;
+ chestData.isFirstInLocation = (lastLocation != null && chestDataList.Count > 0 && chestData.locationDisplayName != chestDataList[chestDataList.Count - 1].locationDisplayName) || chestDataList.Count == 0;
+
int columns = 12;
int rows = (int)Math.Ceiling(chestData.chest.GetActualCapacity() / (float)columns);
- chestData.menu = new ChestMenu(xPositionOnScreen + borderWidth + (even ? (64 * 13) : 0), yPositionOnScreen - scrolled * scrollInterval + borderWidth + 64 + 64 * rowsAlready + xSpace * (1 + menusAlready), false, chestData.chest.Items, null, chestData.chest.GetActualCapacity(), rows);
+ int columnIndex = (numberOfChestColumns > 1 && even) ? 1 : 0;
+ chestData.menu = new ChestMenu(xPositionOnScreen + borderWidth + 64 + columnIndex * (64 * 13), yPositionOnScreen - scrolled * scrollInterval + borderWidth + 64 + 64 * rowsAlready + 96 * (1 + menusAlready) + 48 * locationGaps, false, chestData.chest.Items, null, chestData.chest.GetActualCapacity(), rows);
if (chestData.chest is ShippingBinChest && !ModEntry.Config.UnrestrictedShippingBin)
{
chestData.menu.highlightMethod = (Item i) => {
return i == Game1.getFarm().lastItemShipped;
};
}
- if (!even)
+ if (numberOfChestColumns == 1)
{
- oddRows = !chestData.collapsed ? Math.Max(chestData.menu.rows, 3) : 0;
+ rowsAlready += !chestData.collapsed ? Math.Max(chestData.menu.rows, 3) : 0;
+ menusAlready++;
}
else
{
- rowsAlready += Math.Max(!chestData.collapsed ? Math.Max(chestData.menu.rows, 3) : 0, oddRows);
- menusAlready++;
+ if (!even)
+ {
+ oddRows = !chestData.collapsed ? Math.Max(chestData.menu.rows, 3) : 0;
+ }
+ else
+ {
+ rowsAlready += Math.Max(!chestData.collapsed ? Math.Max(chestData.menu.rows, 3) : 0, oddRows);
+ menusAlready++;
+ }
+ even = !even;
}
- even = !even;
if (chestDataList.Count >= 1000)
{
ModEntry.SMonitor.Log("More than 1000 chests. Giving up while we're ahead.", LogLevel.Warn);
@@ -452,13 +825,14 @@ private void ResetChestList()
}
totalHeight += lastHeight;
+ chestHeaders.Clear();
for (int i = 0; i < chestDataList.Count; i++)
{
const int columns = 12;
ChestData chestData = chestDataList[i];
int count = chestData.menu.inventory.Count;
int lastCount = i > 0 ? chestDataList[i - 1].menu.inventory.Count : 0;
- // int nextCount = i < chestDataList.Count - 1 ? chestDataList[i + 1].menu.inventory.Count : 0;
+ int nextCount = i < chestDataList.Count - 1 ? chestDataList[i + 1].menu.inventory.Count : 0;
int lastLastCount = i > 1 ? chestDataList[i - 2].menu.inventory.Count : 0;
int nextNextCount = i < chestDataList.Count - 2 ? chestDataList[i + 2].menu.inventory.Count : 0;
int index = ccMagnitude + i * ccMagnitude / 1000;
@@ -467,6 +841,17 @@ private void ResetChestList()
int lastLastIndex = ccMagnitude + (i - 2) * ccMagnitude / 1000;
int nextNextIndex = ccMagnitude + (i + 2) * ccMagnitude / 1000;
+ int headerWidth = numberOfChestColumns == 1 ? width - borderWidth * 2 - 128 : (width - borderWidth * 2 - 128) / 2;
+ ClickableComponent headerCC = new ClickableComponent(new Rectangle(chestData.menu.xPositionOnScreen, chestData.menu.yPositionOnScreen - 48, headerWidth, 48), chestData.index.ToString(), chestData.label)
+ {
+ myID = index + 5000,
+ downNeighborID = index + 0,
+ rightNeighborID = numberOfChestColumns == 1 ? -1 : ((i % 2 == 0 && i + 1 < chestDataList.Count) ? ccMagnitude + (i + 1) * ccMagnitude / 1000 + 5000 : -1),
+ leftNeighborID = numberOfChestColumns == 1 ? -1 : ((i % 2 == 1) ? ccMagnitude + (i - 1) * ccMagnitude / 1000 + 5000 : -1),
+ upNeighborID = numberOfChestColumns == 1 ? (i > 0 ? ccMagnitude + (i - 1) * ccMagnitude / 1000 + 5000 : -1) : ((i > 1) ? ccMagnitude + (i - 2) * ccMagnitude / 1000 + 5000 : -1)
+ };
+ chestHeaders.Add(headerCC);
+
for (int j = 0; j < count; j++)
{
int rowIndex = j / columns;
@@ -538,9 +923,13 @@ private void ResetChestList()
}
if (j >= count - columns)
{
- if (i < chestDataList.Count - 2)
+ if (numberOfChestColumns == 1)
{
- chestData.menu.inventory[j].downNeighborID = nextNextIndex + columnIndex;
+ chestData.menu.inventory[j].downNeighborID = i < chestDataList.Count - 1 ? nextIndex + 5000 : -1;
+ }
+ else if (i < chestDataList.Count - 2)
+ {
+ chestData.menu.inventory[j].downNeighborID = nextNextIndex + 5000;
}
else
{
@@ -553,34 +942,7 @@ private void ResetChestList()
}
if (j < columns)
{
- int lastLastLastRowColumns = lastLastCount % 12;
-
- if (i > 1)
- {
- if (columnIndex < lastLastLastRowColumns)
- {
- int lastLastLastRowIndex = lastLastCount - lastLastLastRowColumns;
-
- chestData.menu.inventory[j].upNeighborID = lastLastIndex + lastLastLastRowIndex + columnIndex;
- }
- else
- {
- int lastLastSecondLastRowIndex = lastLastCount - lastLastLastRowColumns - columns;
-
- if (lastLastSecondLastRowIndex >= 0)
- {
- chestData.menu.inventory[j].upNeighborID = lastLastIndex + lastLastSecondLastRowIndex + columnIndex;
- }
- else
- {
- chestData.menu.inventory[j].upNeighborID = lastLastIndex + lastLastCount - 1;
- }
- }
- }
- else
- {
- chestData.menu.inventory[j].upNeighborID = -1;
- }
+ chestData.menu.inventory[j].upNeighborID = index + 5000;
}
else
{
@@ -600,10 +962,10 @@ private void ResetChestList()
ClickableTextureComponent cc = new("", GetWidgetRectangle(chestData, j), "", widgetText[k], Game1.mouseCursors, widgetSources[k], 32f / widgetSources[k].Width, false)
{
myID = index + count + k,
- downNeighborID = k < widgetTextLength - 1 ? index + count + k + 5 : (i < chestDataList.Count - 2 ? nextNextIndex + nextNextCount : -1),
+ downNeighborID = k < widgetTextLength - 1 ? index + count + k + 5 : (numberOfChestColumns == 1 ? (i < chestDataList.Count - 1 ? nextIndex + nextCount : -1) : (i < chestDataList.Count - 2 ? nextNextIndex + nextNextCount : -1)),
leftNeighborID = index + 11 + LeftRowIndex * columns - ((LeftRowIndex == (int)Math.Ceiling((double)count / columns) - 1) ? columns - (count % columns) : 0),
- rightNeighborID = i < chestDataList.Count - 1 ? nextIndex + RightRowIndex * columns : -1,
- upNeighborID = k > 0 ? index + count + k - 5: (i > 1 ? lastLastIndex + lastLastCount + widgetTextLength - 1: -1)
+ rightNeighborID = numberOfChestColumns == 1 ? -1 : (i < chestDataList.Count - 1 ? nextIndex + RightRowIndex * columns : -1),
+ upNeighborID = k > 0 ? index + count + k - 5: (numberOfChestColumns == 1 ? (i > 0 ? lastIndex + lastCount + widgetTextLength - 1 : -1) : (i > 1 ? lastLastIndex + lastLastCount + widgetTextLength - 1: -1))
};
chestData.inventoryButtons.Add(cc);
@@ -619,10 +981,10 @@ private void ResetChestList()
ClickableTextureComponent cc = new("", GetWidgetRectangle(chestData, j), "", widgetText[j], Game1.mouseCursors, widgetSources[j], 32f / widgetSources[j].Width, false)
{
myID = index + count + j,
- downNeighborID = j < widgetText.Length - 1 ? index + count + j + 1 : (i < chestDataList.Count - 2 ? nextNextIndex + nextNextCount : -1),
+ downNeighborID = j < widgetText.Length - 1 ? index + count + j + 1 : (numberOfChestColumns == 1 ? (i < chestDataList.Count - 1 ? nextIndex + nextCount : -1) : (i < chestDataList.Count - 2 ? nextNextIndex + nextNextCount : -1)),
leftNeighborID = index + 11 + LeftRowIndex * columns - ((LeftRowIndex == (int)Math.Ceiling((double)count / columns) - 1) ? columns - (count % columns) : 0),
- rightNeighborID = i < chestDataList.Count - 1 ? nextIndex + RightRowIndex * columns : -1,
- upNeighborID = j > 0 ? index + count + j - 1: (i > 1 ? lastLastIndex + lastLastCount + widgetText.Length - 1: -1)
+ rightNeighborID = numberOfChestColumns == 1 ? -1 : (i < chestDataList.Count - 1 ? nextIndex + RightRowIndex * columns : -1),
+ upNeighborID = j > 0 ? index + count + j - 1: (numberOfChestColumns == 1 ? (i > 0 ? lastIndex + lastCount + widgetText.Length - 1 : -1) : (i > 1 ? lastLastIndex + lastLastCount + widgetText.Length - 1: -1))
};
chestData.inventoryButtons.Add(cc);
@@ -676,10 +1038,20 @@ public static void RestoreNulls(Chest chest)
public override void draw(SpriteBatch b)
{
Game1.drawDialogueBox(xPositionOnScreen, yPositionOnScreen, width, height, false, true, null, false, true);
+
+ b.End();
+ Rectangle prevScissor = b.GraphicsDevice.ScissorRectangle;
+ b.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, new RasterizerState { ScissorTestEnable = true });
+
+ Rectangle scissor = new Rectangle(xPositionOnScreen, yPositionOnScreen, width, cutoff - yPositionOnScreen);
+ b.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(scissor, b.GraphicsDevice.Viewport.Bounds);
+
+ int locationZebraIndex = 0;
canScroll = (chestDataList.Count > 0 && chestDataList[^1].menu.yPositionOnScreen + Math.Max(chestDataList[^1].menu.rows, 3) * 64 + borderWidth > cutoff) || (chestDataList.Count > 1 && chestDataList[^2].menu.yPositionOnScreen + Math.Max(chestDataList[^2].menu.rows, 3) * 64 + borderWidth > cutoff);
for (int i = 0; i < chestDataList.Count; i++)
{
ChestData chestData = chestDataList[i];
+ if (chestData.isFirstInLocation) locationZebraIndex++;
if (canScroll && chestData.menu.yPositionOnScreen - 48 > cutoff + 64 * 4)
{
@@ -689,16 +1061,53 @@ public override void draw(SpriteBatch b)
{
continue;
}
- SpriteText.drawString(b, chestData.label, chestData.menu.xPositionOnScreen, chestData.menu.yPositionOnScreen - 48);
+
if (!chestData.collapsed)
{
+ // Desenhar apenas os botões/slots (Game1.menuTexture já tem a borda e background)
+ Color boxColor = (locationZebraIndex % 2 == 0) ? Color.White : new Color(210, 210, 210);
+ IClickableMenu.drawTextureBox(b, Game1.menuTexture, new Rectangle(0, 256, 60, 60), chestData.menu.xPositionOnScreen - 24, chestData.menu.yPositionOnScreen - 20, chestData.menu.width + 48, chestData.menu.height + 40, boxColor, 1f, false);
+ // Desenhar Overlay de cor do baú sobre os quadrados
+ Rectangle bgRect = new Rectangle(
+ chestData.menu.xPositionOnScreen,
+ chestData.menu.yPositionOnScreen,
+ chestData.menu.width,
+ chestData.menu.height
+ );
+ b.Draw(Game1.staminaRect, bgRect, chestData.chestColor * 0.45f);
+
chestData.menu.draw(b);
for (int j = 0; j < chestData.inventoryButtons.Count; j++)
{
chestData.inventoryButtons[j].draw(b, targetChest?.index != chestData.index && j == chestData.inventoryButtons.Count - 1 ? Color.Purple : Color.White, 1);
}
}
+ if (chestData.isFirstInLocation)
+ {
+ if (i > 0)
+ {
+ // Draw separator line centered in the new 48px gap
+ b.Draw(
+ Game1.staminaRect,
+ new Rectangle(
+ xPositionOnScreen + borderWidth + 24,
+ chestData.menu.yPositionOnScreen - 64 * 2 - 20,
+ width - borderWidth * 2 - 48,
+ 4
+ ),
+ new Color(50, 30, 0) * 0.5f
+ );
+ }
+
+ // Draw location header
+ SpriteText.drawString(b, chestData.locationDisplayName, chestData.menu.xPositionOnScreen, chestData.menu.yPositionOnScreen - 64 * 2 - 4);
+ }
+ SpriteText.drawString(b, chestData.label, chestData.menu.xPositionOnScreen, chestData.menu.yPositionOnScreen - 64);
}
+
+ b.End();
+ b.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
+ b.GraphicsDevice.ScissorRectangle = prevScissor;
if(chestDataList.Count > 0)
{
//scrollbar
@@ -721,26 +1130,62 @@ public override void draw(SpriteBatch b)
SpriteText.drawString(b, scrollbarSize+"", 16, 64);
}
- Game1.drawDialogueBox(xPositionOnScreen, cutoff - borderWidth * 2, width, 64 * 4 + borderWidth * 2, false, true, null, false, true);
+ Game1.drawDialogueBox(xPositionOnScreen, cutoff - (borderWidth / 2), width, bottomAreaHeight + borderWidth + (borderWidth / 2), false, true, null, false, true);
playerInventoryMenu.draw(b);
- SpriteText.drawString(b, filterString, locationText.X + 16, locationText.Y - 48);
+ float filterLabelScale = stackedBottomControls ? 0.8f : 1f;
+ b.DrawString(Game1.smallFont, filterString, new Vector2(locationText.X, locationText.Y - 29), Game1.textColor, 0f, Vector2.Zero, filterLabelScale, SpriteEffects.None, 0.88f);
locationText.Draw(b);
+
+ // Draw additional filter fields
+ // Item Name filter
+ b.DrawString(Game1.smallFont, ModEntry.SHelper.Translation.Get("filter-item-name"), new Vector2(itemNameText.X, itemNameText.Y - 29), Game1.textColor, 0f, Vector2.Zero, filterLabelScale, SpriteEffects.None, 0.88f);
+ itemNameText.Draw(b);
+
+ // Item Description filter
+ b.DrawString(Game1.smallFont, ModEntry.SHelper.Translation.Get("filter-item-description"), new Vector2(itemDescriptionText.X, itemDescriptionText.Y - 29), Game1.textColor, 0f, Vector2.Zero, filterLabelScale, SpriteEffects.None, 0.88f);
+ itemDescriptionText.Draw(b);
+
+ // Location dropdown
+ string locationLabelText = ModEntry.Config.SelectedLocations.Count > 0
+ ? $"{ModEntry.Config.SelectedLocations.Count} {ModEntry.SHelper.Translation.Get("filter-location-select")}"
+ : ModEntry.SHelper.Translation.Get("category-all");
+ Game1.spriteBatch.Draw(Game1.menuTexture, locationDropdownCC.bounds, Game1.getSourceRectForStandardTileSheet(Game1.menuTexture, 10), Color.White);
+ b.DrawString(Game1.smallFont, "Loc", new Vector2(locationDropdownCC.bounds.X, locationDropdownCC.bounds.Y - 29), Game1.textColor, 0f, Vector2.Zero, filterLabelScale, SpriteEffects.None, 0.88f);
+ b.DrawString(Game1.smallFont, locationLabelText, new Vector2(locationDropdownCC.bounds.X + 8, locationDropdownCC.bounds.Y + 8), Game1.textColor, 0f, Vector2.Zero, filterLabelScale, SpriteEffects.None, 0.88f);
+ // Dropdown arrow
+ b.Draw(Game1.mouseCursors, new Vector2(locationDropdownCC.bounds.Right - 20, locationDropdownCC.bounds.Y + locationDropdownCC.bounds.Height / 2 - 4),
+ new Rectangle(345, 494, 10, 6), Color.White, 0f, Vector2.Zero, 2f, SpriteEffects.None, 0.87f);
+
if (renamingChest is not null)
{
SpriteText.drawString(b, nameString, renameBox.X + 16, renameBox.Y - 48);
renameBox.Draw(b);
okButton.draw(b);
}
- SpriteText.drawStringHorizontallyCenteredAt(b, sortString, organizeButton.bounds.X + 156 + 32 * 2 + 24 + 32, organizeButton.bounds.Y + 16);
+ int sortLabelX = stackedBottomControls && sortCCList.Count > 0 ? sortCCList[0].bounds.X - (compactLayout ? 60 : 72) : organizeButton.bounds.X;
+ int sortLabelY = stackedBottomControls
+ ? organizeButton.bounds.Y + (compactLayout ? 10 : 14)
+ : organizeButton.bounds.Y + 62;
+ if (stackedBottomControls)
+ b.DrawString(Game1.smallFont, sortString, new Vector2(sortLabelX, sortLabelY), Game1.textColor, 0f, Vector2.Zero, 0.9f, SpriteEffects.None, 0.88f);
+ else
+ SpriteText.drawString(b, sortString, sortLabelX, sortLabelY);
foreach (ClickableComponent cc in sortCCList)
{
- b.DrawString(Game1.smallFont, cc.label, cc.bounds.Location.ToVector2() + new Vector2(-1, 1), currentSort.ToString() == cc.label ? Color.Green : Color.Black);
- b.DrawString(Game1.smallFont, cc.label, cc.bounds.Location.ToVector2(), currentSort.ToString() == cc.label ? Color.LightGreen : Color.White);
+ Vector2 sortLabelPosition = cc.bounds.Location.ToVector2();
+ float sortScale = stackedBottomControls ? 0.82f : 1f;
+ b.DrawString(Game1.smallFont, cc.label, sortLabelPosition + new Vector2(-1, 1), currentSort.ToString() == cc.label ? Color.Green : Color.Black, 0f, Vector2.Zero, sortScale, SpriteEffects.None, 0.88f);
+ b.DrawString(Game1.smallFont, cc.label, sortLabelPosition, currentSort.ToString() == cc.label ? Color.LightGreen : Color.White, 0f, Vector2.Zero, sortScale, SpriteEffects.None, 0.89f);
}
trashCan.draw(b);
organizeButton.draw(b);
storeAlikeButton.draw(b);
- b.Draw(Game1.mouseCursors, new Vector2(trashCan.bounds.X + 60, trashCan.bounds.Y + 40), new Rectangle?(new Rectangle(564 + Game1.player.trashCanLevel * 18, 129, 18, 10)), Color.White, trashCanLidRotation, new Vector2(16f, 10f), 4f, SpriteEffects.None, 0.86f);
+ consolidateButton.draw(b);
+ sortAllButton.draw(b);
+ clearFiltersButton.draw(b);
+ float lidOffsetX = stackedBottomControls ? (compactLayout ? 24f : 34f) : (compactLayout ? 44f : 60f);
+ float lidOffsetY = stackedBottomControls ? (compactLayout ? 16f : 24f) : (compactLayout ? 30f : 40f);
+ b.Draw(Game1.mouseCursors, new Vector2(trashCan.bounds.X + lidOffsetX, trashCan.bounds.Y + lidOffsetY), new Rectangle?(new Rectangle(564 + Game1.player.trashCanLevel * 18, 129, 18, 10)), Color.White, trashCanLidRotation, new Vector2(16f, 10f), trashLidScale, SpriteEffects.None, 0.86f);
Game1.spriteBatch.Draw(Game1.menuTexture, new Rectangle(xPositionOnScreen + 16, -4, 24, 16), new Rectangle(16, 16, 24, 16), Color.White);
Game1.spriteBatch.Draw(Game1.menuTexture, new Rectangle(xPositionOnScreen + width - 32, -4, 16, 16), new Rectangle(225, 16, 16, 16), Color.White);
Game1.spriteBatch.Draw(Game1.menuTexture, new Rectangle(xPositionOnScreen + 40, -4, width - 72, 16), new Rectangle(40, 16, 1, 16), Color.White);
@@ -765,6 +1210,32 @@ public override void draw(SpriteBatch b)
SpriteText.drawString(b, allChestDataList[heldMenu].label, Game1.getOldMouseX(), Game1.getOldMouseY() - 48);
b.Draw(Game1.staminaRect, new Rectangle(Game1.getOldMouseX(), Game1.getOldMouseY(), 64 * 12, allChestDataList[heldMenu].menu.rows * 64), Color.LightGray * 0.5f);
}
+
+ if (locationDropdownOpen)
+ {
+ int listHeight = locationOptionsCCList.Count * 36;
+ if (listHeight > 0)
+ {
+ UpdateLocationOptionBounds();
+ int startY = locationOptionsCCList[0].bounds.Y - 8;
+ int dropdownWidth = Math.Max(locationDropdownCC.bounds.Width, 300);
+ IClickableMenu.drawTextureBox(b, Game1.menuTexture, new Rectangle(0, 256, 60, 60), locationDropdownCC.bounds.X, startY, dropdownWidth, listHeight + 16, Color.White * 0.95f, 1f, false);
+ for (int i = 0; i < locationOptionsCCList.Count; i++)
+ {
+ var cc = locationOptionsCCList[i];
+ if (cc.containsPoint(Game1.getMouseX(), Game1.getMouseY()))
+ {
+ b.Draw(Game1.staminaRect, cc.bounds, Color.Wheat);
+ }
+
+ bool isSelected = ModEntry.Config.SelectedLocations.Contains(cc.name);
+ Color textColor = isSelected ? Color.Green : Game1.textColor;
+ string displayTxt = (isSelected ? "[X] " : "[ ] ") + cc.label;
+ b.DrawString(Game1.smallFont, displayTxt, new Vector2(cc.bounds.X + 8, cc.bounds.Y + 4), textColor);
+ }
+ }
+ }
+
Game1.mouseCursorTransparency = 1f;
drawMouse(b);
}
@@ -797,13 +1268,59 @@ public override void releaseLeftClick(int x, int y)
base.releaseLeftClick(x, y);
scrolling = false;
}
- public override void receiveLeftClick(int x, int y, bool playSound = true)
+ public override void receiveLeftClick(int x, int y, bool playSound = true)
{
+ if (locationDropdownOpen)
+ {
+ foreach (var cc in locationOptionsCCList)
+ {
+ if (cc.containsPoint(x, y))
+ {
+ if (ModEntry.Config.SelectedLocations.Contains(cc.name))
+ ModEntry.Config.SelectedLocations.Remove(cc.name);
+ else
+ ModEntry.Config.SelectedLocations.Add(cc.name);
+
+ ModEntry.SHelper.WriteConfig(ModEntry.Config);
+ Game1.playSound("drumkit6");
+ PopulateMenus(false);
+ RebuildControllerNavigation();
+ populateClickableComponentList();
+ return;
+ }
+ }
+ locationDropdownOpen = false;
+ RebuildControllerNavigation();
+ populateClickableComponentList();
+ if (locationDropdownCC.bounds.Contains(x, y))
+ {
+ Game1.playSound("drumkit6");
+ return;
+ }
+ }
+
+ if (locationDropdownCC.bounds.Contains(x, y))
+ {
+ locationDropdownOpen = true;
+ UpdateLocationOptionBounds();
+ RebuildControllerNavigation();
+ populateClickableComponentList();
+ if (Game1.options.snappyMenus && Game1.options.gamepadControls && locationOptionsCCList.Count > 0)
+ {
+ currentlySnappedComponent = locationOptionsCCList[0];
+ snapCursorToCurrentSnappedComponent();
+ }
+ Game1.playSound("shwip");
+ return;
+ }
+
Item held = heldItem;
Rectangle rect;
- renameBox.Selected = false;
- locationText.Selected = false;
+ renameBox.Selected = new Rectangle(renameBox.X, renameBox.Y, renameBox.Width, renameBox.Height).Contains(x,y);
+ locationText.Selected = new Rectangle(locationText.X, locationText.Y, locationText.Width, locationText.Height).Contains(x,y);
+ itemNameText.Selected = new Rectangle(itemNameText.X, itemNameText.Y, itemNameText.Width, itemNameText.Height).Contains(x,y);
+ itemDescriptionText.Selected = new Rectangle(itemDescriptionText.X, itemDescriptionText.Y, itemDescriptionText.Width, itemDescriptionText.Height).Contains(x,y);
if (y >= cutoff)
{
if (heldMenu > -1)
@@ -843,6 +1360,21 @@ public override void receiveLeftClick(int x, int y, bool playSound = true)
}
}
locationText.Update();
+ itemNameText.Update();
+ itemDescriptionText.Update();
+
+ if (ModEntry.Config.EnableControllerKeyboard && Game1.options.gamepadControls)
+ {
+ if (renamingChest is not null && renameBox.Selected)
+ Game1.showTextEntry(renameBox);
+ if (locationText.Selected)
+ Game1.showTextEntry(locationText);
+ if (itemNameText.Selected)
+ Game1.showTextEntry(itemNameText);
+ if (itemDescriptionText.Selected)
+ Game1.showTextEntry(itemDescriptionText);
+ }
+
if (trashCan != null && trashCan.containsPoint(x, y) && heldItem != null && heldItem.canBeTrashed())
{
Utility.trashItem(heldItem);
@@ -864,6 +1396,21 @@ public override void receiveLeftClick(int x, int y, bool playSound = true)
}
return;
}
+ if (consolidateButton.containsPoint(x, y))
+ {
+ OpenConsolidationMenu();
+ return;
+ }
+ if (sortAllButton.containsPoint(x, y))
+ {
+ SortAllChests();
+ return;
+ }
+ if (clearFiltersButton.containsPoint(x, y))
+ {
+ ClearAllFilters();
+ return;
+ }
foreach (ClickableComponent cc in sortCCList)
{
if (cc.containsPoint(x, y))
@@ -932,7 +1479,7 @@ static bool isWithinBounds(int x, int y, ChestMenu chestMenu, int otherChestMenu
if (heldItem != null)
{
Game1.playSound("bigSelect");
- if (ModEntry.SHelper.Input.IsDown(ModEntry.Config.ModKey))
+ if (ModEntry.SHelper.Input.IsDown(ModEntry.Config.ModKey) || gamepadShift)
{
heldItem = AddItemToInventory(Game1.player.Items, heldItem);
}
@@ -1020,7 +1567,9 @@ public override void receiveKeyPress(Keys key)
{
applyMovementKey(key);
}
- if (Game1.options.doesInputListContain(Game1.options.menuButton, key) && readyToClose())
+ // Don't close menu if typing in a text box
+ bool isTypingInFilter = locationText.Selected || itemNameText.Selected || itemDescriptionText.Selected || (renamingChest is not null && renameBox.Selected);
+ if (Game1.options.doesInputListContain(Game1.options.menuButton, key) && readyToClose() && !isTypingInFilter)
{
exitThisMenu(true);
}
@@ -1047,6 +1596,97 @@ public override void receiveKeyPress(Keys key)
}
}
+ public override void receiveGamePadButton(Buttons b)
+ {
+ if (b == Buttons.B || b == Buttons.Start)
+ {
+ if (renameBox.Selected || locationText.Selected || itemNameText.Selected || itemDescriptionText.Selected)
+ {
+ renameBox.Selected = false;
+ locationText.Selected = false;
+ itemNameText.Selected = false;
+ itemDescriptionText.Selected = false;
+ Game1.keyboardDispatcher.Subscriber = null;
+ return;
+ }
+ }
+
+ if (b == Buttons.X)
+ {
+ if (currentlySnappedComponent != null && currentlySnappedComponent.myID >= ccMagnitude)
+ {
+ int id = currentlySnappedComponent.myID;
+ int chestIndex = (id - ccMagnitude) / (ccMagnitude / 1000);
+ int localId = id - ccMagnitude - (chestIndex * (ccMagnitude / 1000));
+
+ if (localId == 5000)
+ {
+ if (heldMenu == -1 && chestIndex >= 0 && chestIndex < chestDataList.Count)
+ {
+ heldMenu = chestDataList[chestIndex].index;
+ Game1.playSound("bigSelect");
+ return;
+ }
+ }
+ }
+
+ // Simulate a Left Click on the item while injecting a faux-shift
+ // We use getMouseX and getMouseY so it works even if snapping is broken (virtual mouse)
+ gamepadShift = true;
+ receiveLeftClick(Game1.getMouseX(), Game1.getMouseY());
+ gamepadShift = false;
+ return;
+ }
+
+ if (b == Buttons.A && currentlySnappedComponent is not null)
+ {
+ receiveLeftClick(currentlySnappedComponent.bounds.Center.X, currentlySnappedComponent.bounds.Center.Y);
+ return;
+ }
+ base.receiveGamePadButton(b);
+ }
+
+ public override void populateClickableComponentList()
+ {
+ base.populateClickableComponentList();
+ allClickableComponents = new List();
+
+ allClickableComponents.AddRange(inventoryCells);
+ allClickableComponents.AddRange(inventoryButtons);
+ allClickableComponents.AddRange(chestHeaders);
+ allClickableComponents.AddRange(playerInventoryMenu.inventory);
+ allClickableComponents.AddRange(sortCCList);
+ allClickableComponents.Add(organizeButton);
+ allClickableComponents.Add(storeAlikeButton);
+ allClickableComponents.Add(consolidateButton);
+ allClickableComponents.Add(sortAllButton);
+ allClickableComponents.Add(trashCan);
+ allClickableComponents.Add(clearFiltersButton);
+ allClickableComponents.Add(chestLabelCC);
+ allClickableComponents.Add(itemNameCC);
+ allClickableComponents.Add(itemDescCC);
+ allClickableComponents.Add(locationDropdownCC);
+
+ if (locationDropdownOpen)
+ allClickableComponents.AddRange(locationOptionsCCList);
+
+ if (renamingChest is not null)
+ {
+ allClickableComponents.Add(renameBoxCC);
+ allClickableComponents.Add(okButton);
+ }
+ }
+
+ public override void setUpForGamePadMode()
+ {
+ base.setUpForGamePadMode();
+ if (this.allClickableComponents == null)
+ {
+ this.populateClickableComponentList();
+ }
+ this.snapToDefaultClickableComponent();
+ }
+
public override void snapToDefaultClickableComponent()
{
if (!Game1.options.snappyMenus || !Game1.options.gamepadControls)
@@ -1169,15 +1809,34 @@ public override void update(GameTime time)
{
return;
}
-
- locationText.Selected = true;
- if (whichLocation?.ToLower() != locationText.Text.ToLower())
+
+ // Only update location filter if it's currently selected
+ if (locationText.Selected && whichLocation?.ToLower() != locationText.Text.ToLower())
{
whichLocation = locationText.Text;
scrolled = 0;
PopulateMenus(false);
lastTopSnappedCC = getComponentWithID(ccMagnitude);
}
+
+ // Update item name filter when changed
+ string currentItemNameFilter = itemNameText.Text?.ToLower().Trim() ?? "";
+ if (currentItemNameFilter != lastItemNameFilter)
+ {
+ lastItemNameFilter = currentItemNameFilter;
+ scrolled = 0;
+ PopulateMenus(false);
+ }
+
+ // Update item description filter when changed
+ string currentItemDescFilter = itemDescriptionText.Text?.ToLower().Trim() ?? "";
+ if (currentItemDescFilter != lastItemDescriptionFilter)
+ {
+ lastItemDescriptionFilter = currentItemDescFilter;
+ scrolled = 0;
+ PopulateMenus(false);
+ }
+
if (poof != null && poof.update(time))
{
poof = null;
@@ -1215,6 +1874,24 @@ public override void performHoverAction(int x, int y)
hoverText = storeAlikeButton.hoverText;
return;
}
+ consolidateButton.tryHover(x, y, 0.1f);
+ if (consolidateButton.containsPoint(x, y))
+ {
+ hoverText = consolidateButton.hoverText;
+ return;
+ }
+ sortAllButton.tryHover(x, y, 0.1f);
+ if (sortAllButton.containsPoint(x, y))
+ {
+ hoverText = sortAllButton.hoverText;
+ return;
+ }
+ clearFiltersButton.tryHover(x, y, 0.1f);
+ if (clearFiltersButton.containsPoint(x, y))
+ {
+ hoverText = clearFiltersButton.hoverText;
+ return;
+ }
hoverAmount = 0;
if (trashCan.containsPoint(x, y))
{
@@ -1685,6 +2362,15 @@ static int CompareLabels(string labelA, string labelB)
case Sort.LA:
result = a.location.CompareTo(b.location);
if (result == 0)
+ {
+ result = b.chest.GetActualCapacity().CompareTo(a.chest.GetActualCapacity()); // Sort by capacity descending
+ }
+ // Tie-breaker: Group by color as well
+ if (result == 0)
+ {
+ result = a.chestColor.PackedValue.CompareTo(b.chestColor.PackedValue);
+ }
+ if (result == 0)
{
result = CompareLabels(a.label, b.label);
}
@@ -1692,6 +2378,15 @@ static int CompareLabels(string labelA, string labelB)
case Sort.LD:
result = b.location.CompareTo(a.location);
if (result == 0)
+ {
+ result = b.chest.GetActualCapacity().CompareTo(a.chest.GetActualCapacity()); // Sort by capacity descending
+ }
+ // Tie-breaker: Group by color as well
+ if (result == 0)
+ {
+ result = a.chestColor.PackedValue.CompareTo(b.chestColor.PackedValue); // Still sort color similarly
+ }
+ if (result == 0)
{
result = CompareLabels(b.label, a.label);
}
@@ -1739,22 +2434,110 @@ static int CompareLabels(string labelA, string labelB)
});
}
+ private int GetPlayerInventoryRowStartIndex(int row)
+ {
+ if (playerInventoryMenu?.inventory == null || playerInventoryMenu.inventory.Count == 0)
+ return -1;
+
+ int clampedRow = Math.Min(row, Math.Max(0, playerInventoryRows - 1));
+ int index = clampedRow * playerInventoryColumns;
+ return Math.Min(index, playerInventoryMenu.inventory.Count - 1);
+ }
+
+ private int GetPlayerInventoryRowEndIndex(int row)
+ {
+ int start = GetPlayerInventoryRowStartIndex(row);
+ if (start < 0)
+ return -1;
+
+ int nextStart = GetPlayerInventoryRowStartIndex(row + 1);
+ if (nextStart > start)
+ return nextStart - 1;
+
+ return playerInventoryMenu.inventory.Count - 1;
+ }
+
private void SetPlayerInventoryNeighbours()
{
- if (playerInventoryMenu.inventory.Count >= 12)
+ if (playerInventoryMenu?.inventory == null || playerInventoryMenu.inventory.Count == 0)
+ return;
+
+ int leftTop = chestLabelCC?.myID ?? 2 * ccMagnitude;
+ int leftMiddle = itemNameCC?.myID ?? 2 * ccMagnitude;
+ int leftBottom = itemDescCC?.myID ?? 2 * ccMagnitude;
+
+ int topStart = GetPlayerInventoryRowStartIndex(0);
+ int middleStart = GetPlayerInventoryRowStartIndex(1);
+ int bottomStart = GetPlayerInventoryRowStartIndex(2);
+ int topEnd = GetPlayerInventoryRowEndIndex(0);
+ int middleEnd = GetPlayerInventoryRowEndIndex(1);
+ int bottomEnd = GetPlayerInventoryRowEndIndex(2);
+
+ playerInventoryMenu.inventory[topStart].leftNeighborID = leftTop;
+ playerInventoryMenu.inventory[topEnd].rightNeighborID = 4 * ccMagnitude;
+
+ if (middleStart >= 0)
{
- playerInventoryMenu.inventory[0].leftNeighborID = 2 * ccMagnitude;
- playerInventoryMenu.inventory[11].rightNeighborID = 4 * ccMagnitude;
- if (playerInventoryMenu.inventory.Count >= 24)
- {
- playerInventoryMenu.inventory[12].leftNeighborID = 2 * ccMagnitude;
- playerInventoryMenu.inventory[23].rightNeighborID = 4 * ccMagnitude + 1;
- if (playerInventoryMenu.inventory.Count >= 36)
- {
- playerInventoryMenu.inventory[24].leftNeighborID = 2 * ccMagnitude;
- playerInventoryMenu.inventory[35].rightNeighborID = 4 * ccMagnitude + 1;
- }
- }
+ playerInventoryMenu.inventory[middleStart].leftNeighborID = leftMiddle;
+ playerInventoryMenu.inventory[middleEnd].rightNeighborID = 4 * ccMagnitude + 1;
+ }
+
+ if (bottomStart >= 0)
+ {
+ playerInventoryMenu.inventory[bottomStart].leftNeighborID = leftBottom;
+ playerInventoryMenu.inventory[bottomEnd].rightNeighborID = 4 * ccMagnitude + 1;
+ }
+ }
+
+ private void RebuildControllerNavigation()
+ {
+ SetPlayerInventoryNeighbours();
+
+ chestLabelCC.upNeighborID = -1;
+ chestLabelCC.downNeighborID = itemNameCC.myID;
+ chestLabelCC.rightNeighborID = playerInventoryMenu.inventory.Count > 0 ? playerInventoryMenu.inventory[0].myID : -1;
+
+ itemNameCC.upNeighborID = chestLabelCC.myID;
+ itemNameCC.downNeighborID = itemDescCC.myID;
+ itemNameCC.rightNeighborID = GetPlayerInventoryRowStartIndex(1) >= 0 ? playerInventoryMenu.inventory[GetPlayerInventoryRowStartIndex(1)].myID : chestLabelCC.rightNeighborID;
+
+ itemDescCC.upNeighborID = itemNameCC.myID;
+ itemDescCC.downNeighborID = locationDropdownCC.myID;
+ itemDescCC.rightNeighborID = GetPlayerInventoryRowStartIndex(2) >= 0 ? playerInventoryMenu.inventory[GetPlayerInventoryRowStartIndex(2)].myID : chestLabelCC.rightNeighborID;
+
+ locationDropdownCC.upNeighborID = itemDescCC.myID;
+ locationDropdownCC.downNeighborID = (locationDropdownOpen && locationOptionsCCList.Count > 0) ? locationOptionsCCList[0].myID : clearFiltersButton.myID;
+ locationDropdownCC.rightNeighborID = itemDescCC.rightNeighborID;
+
+ clearFiltersButton.leftNeighborID = locationDropdownCC.myID;
+ clearFiltersButton.rightNeighborID = chestLabelCC.rightNeighborID;
+ clearFiltersButton.upNeighborID = locationDropdownCC.myID;
+
+ for (int i = 0; i < locationOptionsCCList.Count; i++)
+ {
+ ClickableComponent cc = locationOptionsCCList[i];
+ cc.upNeighborID = i == 0 ? locationDropdownCC.myID : locationOptionsCCList[i - 1].myID;
+ cc.downNeighborID = i == locationOptionsCCList.Count - 1 ? clearFiltersButton.myID : locationOptionsCCList[i + 1].myID;
+ cc.leftNeighborID = locationDropdownCC.myID;
+ cc.rightNeighborID = locationDropdownCC.rightNeighborID;
+ }
+ }
+
+ private void UpdateLocationOptionBounds()
+ {
+ int listHeight = locationOptionsCCList.Count * 36;
+ if (listHeight <= 0)
+ return;
+
+ int startY = locationDropdownCC.bounds.Bottom;
+ if (startY + listHeight > Game1.uiViewport.Height)
+ {
+ startY = locationDropdownCC.bounds.Y - listHeight;
+ }
+
+ for (int i = 0; i < locationOptionsCCList.Count; i++)
+ {
+ locationOptionsCCList[i].bounds.Y = startY + 8 + i * 36;
}
}
@@ -1781,5 +2564,216 @@ public override void emergencyShutDown()
}
Game1.getFarm().getShippingBin(Game1.player).RemoveEmptySlots();
}
+
+ ///
+ /// Creates a unique key for an item considering type and quality
+ ///
+ private string GetItemKey(Item item)
+ {
+ string key = item.ItemId;
+ if (item is StardewValley.Object obj && obj.Quality > 0)
+ key += $"_q{obj.Quality}";
+ return key;
+ }
+
+ ///
+ /// Counts free slots in a chest
+ ///
+ private int CountFreeSlots(ChestData chestData)
+ {
+ if (chestData?.chest == null) return 0;
+ return chestData.chest.Items.Count(i => i == null);
+ }
+
+ ///
+ /// Finds items that exist in multiple chests
+ ///
+ private List FindDuplicateItems()
+ {
+ var duplicates = new List();
+ var itemGroups = new Dictionary>();
+
+ // Group items by type across all chests
+ foreach (var chestData in allChestDataList)
+ {
+ if (chestData?.chest == null) continue;
+
+ foreach (var item in chestData.chest.Items)
+ {
+ if (item == null) continue;
+
+ string key = GetItemKey(item);
+ if (!itemGroups.ContainsKey(key))
+ itemGroups[key] = new List();
+
+ if (!itemGroups[key].Contains(chestData))
+ itemGroups[key].Add(chestData);
+ }
+ }
+
+ // Find items that exist in multiple chests
+ foreach (var kvp in itemGroups)
+ {
+ var chestsContainingItem = kvp.Value;
+ if (chestsContainingItem.Count < 2) continue; // Skip if only in one chest
+
+ // Get a sample item and calculate total stack
+ Item sampleItem = null;
+ int totalStack = 0;
+
+ foreach (var chestData in chestsContainingItem)
+ {
+ foreach (var item in chestData.chest.Items)
+ {
+ if (item != null && GetItemKey(item) == kvp.Key)
+ {
+ if (sampleItem == null)
+ sampleItem = item;
+ totalStack += item.Stack;
+ }
+ }
+ }
+
+ if (sampleItem == null) continue;
+
+ // Calculate if consolidation is possible (enough space in one chest)
+ int maxStackSize = sampleItem.maximumStackSize();
+ int minSlotsNeeded = (int)Math.Ceiling((double)totalStack / maxStackSize);
+
+ var chestsWithSpace = chestsContainingItem
+ .Where(c => CountFreeSlots(c) >= minSlotsNeeded)
+ .ToList();
+
+ if (chestsWithSpace.Any())
+ {
+ duplicates.Add(new ConsolidationItem
+ {
+ SampleItem = sampleItem,
+ TotalStack = totalStack,
+ ChestsContaining = chestsContainingItem,
+ ChestsWithSpace = chestsWithSpace
+ });
+ }
+ }
+
+ return duplicates;
+ }
+
+ ///
+ /// Opens the consolidation menu to merge duplicate items
+ ///
+ private void OpenConsolidationMenu()
+ {
+ var duplicates = FindDuplicateItems();
+ if (duplicates.Count == 0)
+ {
+ Game1.showRedMessage(ModEntry.SHelper.Translation.Get("no-duplicates"));
+ return;
+ }
+ Game1.activeClickableMenu = new ConsolidationMenu(duplicates, this);
+ }
+
+ ///
+ /// Consolidates an item from multiple chests into a destination chest
+ ///
+ internal void ConsolidateItem(ConsolidationItem item, ChestData destination)
+ {
+ if (item?.SampleItem == null || destination?.chest == null) return;
+
+ // Check space
+ int totalStack = item.TotalStack;
+ int maxStackSize = item.SampleItem.maximumStackSize();
+ int slotsNeeded = (int)Math.Ceiling((double)totalStack / maxStackSize);
+
+ if (CountFreeSlots(destination) < slotsNeeded)
+ {
+ Game1.showRedMessage(ModEntry.SHelper.Translation.Get("not-enough-space"));
+ return;
+ }
+
+ // Collect all items from source chests
+ var allItems = new List- ();
+ foreach (var chest in item.ChestsContaining)
+ {
+ if (chest?.chest == null) continue;
+ for (int i = chest.chest.Items.Count - 1; i >= 0; i--)
+ {
+ var itemInChest = chest.chest.Items[i];
+ if (itemInChest != null && itemInChest.canStackWith(item.SampleItem))
+ {
+ allItems.Add(itemInChest);
+ chest.chest.Items[i] = null;
+ }
+ }
+ }
+
+ // Move to destination chest
+ foreach (var itemToMove in allItems)
+ {
+ AddItemToInventory(destination.chest.Items, itemToMove);
+ }
+
+ Game1.playSound("bigSelect");
+ PopulateMenus(false);
+ }
+
+ ///
+ /// Sorts all items in all chests using the same algorithm as the individual organize button
+ ///
+ private void SortAllChests()
+ {
+ foreach (var chestData in allChestDataList)
+ {
+ if (chestData?.chest == null) continue;
+ ItemGrabMenu.organizeItemsInList(chestData.chest.Items);
+ }
+ Game1.playSound("Ship");
+ }
+
+ ///
+ /// Clears all filters and resets the chest list
+ ///
+ private void ClearAllFilters()
+ {
+ Game1.playSound("bigDeSelect");
+ itemNameText.Text = "";
+ itemDescriptionText.Text = "";
+ locationText.Text = "";
+ whichLocation = "";
+ ModEntry.Config.SelectedLocations.Clear();
+ ModEntry.SHelper.WriteConfig(ModEntry.Config);
+ scrolled = 0;
+ PopulateMenus(false);
+ }
+
+ ///
+ /// Helper method to add item to inventory with stacking
+ ///
+ private void AddItemToInventory(IList
- inventory, Item item)
+ {
+ // Try to stack with existing items
+ foreach (var invItem in inventory)
+ {
+ if (invItem != null && invItem.canStackWith(item))
+ {
+ int spaceLeft = invItem.maximumStackSize() - invItem.Stack;
+ int toAdd = Math.Min(spaceLeft, item.Stack);
+ invItem.Stack += toAdd;
+ item.Stack -= toAdd;
+ if (item.Stack <= 0)
+ return;
+ }
+ }
+
+ // Find empty slot
+ for (int i = 0; i < inventory.Count; i++)
+ {
+ if (inventory[i] == null)
+ {
+ inventory[i] = item;
+ return;
+ }
+ }
+ }
}
}
diff --git a/AllChestsMenu/ChestData.cs b/AllChestsMenu/ChestData.cs
index a9e7bd24..9a8ce6e9 100644
--- a/AllChestsMenu/ChestData.cs
+++ b/AllChestsMenu/ChestData.cs
@@ -10,6 +10,7 @@ public class ChestData
public int originalIndex;
public int index;
public string location;
+ public string locationDisplayName;
public string name;
public string label;
public bool collapsed;
@@ -17,5 +18,7 @@ public class ChestData
public ChestMenu menu;
public List inventoryButtons = new();
public Vector2 tile;
+ public Color chestColor; // Cor do baú para uso no fundo
+ public bool isFirstInLocation;
}
}
diff --git a/AllChestsMenu/ChestMenu.cs b/AllChestsMenu/ChestMenu.cs
index f50b3be1..bf278c9b 100644
--- a/AllChestsMenu/ChestMenu.cs
+++ b/AllChestsMenu/ChestMenu.cs
@@ -9,10 +9,11 @@ namespace AllChestsMenu
{
public class ChestMenu : InventoryMenu
{
- const int columns = 12;
+ public int columns { get; private set; } = 12;
- public ChestMenu(int xPosition, int yPosition, bool playerInventory, IList
- actualInventory = null, highlightThisItem highlightMethod = null, int capacity = -1, int rows = 3, int horizontalGap = 0, int verticalGap = 0, bool drawSlots = true) : base(xPosition, yPosition, playerInventory, actualInventory, highlightMethod, capacity, rows, horizontalGap, verticalGap, drawSlots)
+ public ChestMenu(int xPosition, int yPosition, bool playerInventory, IList
- actualInventory = null, highlightThisItem highlightMethod = null, int capacity = -1, int rows = 3, int horizontalGap = 0, int verticalGap = 0, bool drawSlots = true, int chestColumns = 12) : base(xPosition, yPosition, playerInventory, actualInventory, highlightMethod, capacity, rows, horizontalGap, verticalGap, drawSlots)
{
+ this.columns = chestColumns;
initialize(xPositionOnScreen, yPositionOnScreen, 64 * columns, 64 * rows + 16);
inventory.Clear();
for (int j = 0; j < capacity; j++)
diff --git a/AllChestsMenu/ConsolidationMenu.cs b/AllChestsMenu/ConsolidationMenu.cs
new file mode 100644
index 00000000..ced1524d
--- /dev/null
+++ b/AllChestsMenu/ConsolidationMenu.cs
@@ -0,0 +1,385 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using StardewValley;
+using StardewValley.Menus;
+using StardewValley.Objects;
+
+namespace AllChestsMenu
+{
+ ///
+ /// Menu for consolidating duplicate items from multiple chests into one
+ ///
+ public class ConsolidationMenu : IClickableMenu
+ {
+ private readonly List items;
+ private readonly AllChestsMenu parentMenu;
+ private int selectedItemIndex = 0;
+
+ private int itemScroll = 0;
+ private int destScroll = 0;
+
+ private ClickableTextureComponent closeButton;
+ private ClickableTextureComponent itemScrollUpButton;
+ private ClickableTextureComponent itemScrollDownButton;
+ private ClickableTextureComponent destScrollUpButton;
+ private ClickableTextureComponent destScrollDownButton;
+
+ private const int SlotHeight = 64;
+ private const int BorderWidth = 32;
+
+ public ConsolidationMenu(List consolidationItems, AllChestsMenu parent)
+ {
+ items = consolidationItems;
+ parentMenu = parent;
+
+ int menuWidth = Math.Min(1000, Game1.uiViewport.Width - BorderWidth * 2);
+ int menuHeight = Math.Min(600, Game1.uiViewport.Height - BorderWidth * 2);
+ xPositionOnScreen = (Game1.uiViewport.Width - menuWidth) / 2;
+ yPositionOnScreen = (Game1.uiViewport.Height - menuHeight) / 2;
+ width = menuWidth;
+ height = menuHeight;
+
+ // Close button
+ closeButton = new ClickableTextureComponent(
+ "close",
+ new Rectangle(xPositionOnScreen + width - 48, yPositionOnScreen + 8, 32, 32),
+ "",
+ "",
+ Game1.mouseCursors,
+ new Rectangle(341, 494, 14, 14),
+ 3f,
+ false
+ );
+
+ // Up/down buttons for left pane
+ itemScrollUpButton = new ClickableTextureComponent(new Rectangle(xPositionOnScreen + BorderWidth + 4, yPositionOnScreen + BorderWidth + 64, 44, 48), Game1.mouseCursors, new Rectangle(421, 459, 11, 12), 4f);
+ itemScrollDownButton = new ClickableTextureComponent(new Rectangle(xPositionOnScreen + BorderWidth + 4, yPositionOnScreen + height - BorderWidth - 52, 44, 48), Game1.mouseCursors, new Rectangle(421, 472, 11, 12), 4f);
+
+ // Up/down buttons for right pane
+ destScrollUpButton = new ClickableTextureComponent(new Rectangle(xPositionOnScreen + width / 2 + 16, yPositionOnScreen + BorderWidth + 64, 44, 48), Game1.mouseCursors, new Rectangle(421, 459, 11, 12), 4f);
+ destScrollDownButton = new ClickableTextureComponent(new Rectangle(xPositionOnScreen + width / 2 + 16, yPositionOnScreen + height - BorderWidth - 52, 44, 48), Game1.mouseCursors, new Rectangle(421, 472, 11, 12), 4f);
+ }
+
+ private int GetMaxItemsVisible()
+ {
+ // Subtract title heights and buttons offsets
+ return (height - BorderWidth * 2 - 80) / (SlotHeight + 8);
+ }
+
+ public override void draw(SpriteBatch b)
+ {
+ // Draw background
+ Game1.drawDialogueBox(xPositionOnScreen - BorderWidth, yPositionOnScreen - BorderWidth, width + BorderWidth * 2, height + BorderWidth * 2, false, true);
+
+ // Draw title
+ string title = ModEntry.SHelper.Translation.Get("consolidate-menu-title");
+ var titleSize = Game1.smallFont.MeasureString(title);
+ b.DrawString(Game1.smallFont, title, new Vector2(xPositionOnScreen + width / 2 - titleSize.X / 2, yPositionOnScreen + 8), Game1.textColor);
+
+ int maxVisible = GetMaxItemsVisible();
+
+ // == LEFT PANE: ITEMS ==
+ string itemsLabel = ModEntry.SHelper.Translation.Get("item-found-in");
+ try { itemsLabel = string.Format(itemsLabel, items.Count); } catch { }
+ b.DrawString(Game1.smallFont, itemsLabel, new Vector2(xPositionOnScreen + BorderWidth + 8, yPositionOnScreen + BorderWidth + 24), Game1.textColor);
+
+ int paneWidth = width / 2 - BorderWidth - 24;
+ int startY = yPositionOnScreen + BorderWidth + 64;
+
+ for (int i = 0; i < maxVisible; i++)
+ {
+ int itemIndex = itemScroll + i;
+ if (itemIndex >= items.Count) break;
+
+ var item = items[itemIndex];
+ int drawY = startY + i * (SlotHeight + 8);
+ Rectangle slotRect = new Rectangle(xPositionOnScreen + BorderWidth + 56, drawY, paneWidth - 64, SlotHeight);
+ Rectangle skipRect = new Rectangle(slotRect.Right - 36, slotRect.Y + 16, 32, 32);
+
+ // Draw slot background
+ Color bgColor = itemIndex == selectedItemIndex ? Color.Wheat * 0.8f : Color.White * 0.3f;
+ bool hovered = slotRect.Contains(Game1.getMouseX(), Game1.getMouseY());
+ if (hovered) bgColor = Color.Wheat * 1.0f;
+ b.Draw(Game1.staminaRect, slotRect, bgColor);
+
+ // Draw skip button if hovered
+ if (hovered || itemIndex == selectedItemIndex)
+ {
+ bool skipHovered = skipRect.Contains(Game1.getMouseX(), Game1.getMouseY());
+ b.Draw(Game1.mouseCursors, skipRect, new Rectangle(341, 494, 14, 14), skipHovered ? Color.White : Color.Gray, 0f, Vector2.Zero, SpriteEffects.None, 1f);
+ }
+
+ // Draw item
+ if (item.SampleItem != null)
+ {
+ var itemPos = new Vector2(slotRect.X + 8, slotRect.Y + 8);
+ item.SampleItem.drawInMenu(b, itemPos, 1);
+
+ // Draw stack count inside the slot, closer to the item icon
+ string countText = $"x{item.TotalStack}";
+ var countSize = Game1.smallFont.MeasureString(countText);
+ b.DrawString(Game1.smallFont, countText, new Vector2(itemPos.X + 64 + 8, slotRect.Y + 16), Game1.textColor);
+
+ // Draw chest count
+ string chestsText = $"{item.ChestsContaining.Count} chests";
+ b.DrawString(Game1.smallFont, chestsText, new Vector2(itemPos.X + 64 + 8, slotRect.Y + 36), Game1.textColor * 0.8f);
+ }
+ }
+
+ if (items.Count > maxVisible)
+ {
+ if (itemScroll > 0) itemScrollUpButton.draw(b);
+ if (itemScroll + maxVisible < items.Count) itemScrollDownButton.draw(b);
+ }
+
+ // == RIGHT PANE: DESTINATIONS ==
+ if (selectedItemIndex < items.Count)
+ {
+ var selectedItem = items[selectedItemIndex];
+ string destLabel = ModEntry.SHelper.Translation.Get("select-destination");
+ b.DrawString(Game1.smallFont, destLabel, new Vector2(xPositionOnScreen + width / 2 + 16 + 56, yPositionOnScreen + BorderWidth + 24), Game1.textColor);
+
+ int maxDestVisible = GetMaxItemsVisible();
+ for (int i = 0; i < maxDestVisible; i++)
+ {
+ int destIndex = destScroll + i;
+ if (destIndex >= selectedItem.ChestsContaining.Count) break;
+
+ var chest = selectedItem.ChestsContaining[destIndex];
+ bool hasSpace = selectedItem.ChestsWithSpace.Contains(chest);
+
+ int drawY = startY + i * (SlotHeight + 8);
+ Rectangle destRect = new Rectangle(xPositionOnScreen + width / 2 + 16 + 56, drawY, paneWidth - 64, SlotHeight);
+
+ Color destBgColor = Color.White * 0.3f;
+ if (hasSpace && destRect.Contains(Game1.getMouseX(), Game1.getMouseY())) destBgColor = Color.Wheat * 0.8f;
+ if (!hasSpace) destBgColor = Color.Black * 0.3f;
+
+ b.Draw(Game1.staminaRect, destRect, destBgColor);
+
+ // Draw chest name
+ string chestLabel = !string.IsNullOrEmpty(chest.label)
+ ? chest.label
+ : $"{chest.location} ({chest.tile.X},{chest.tile.Y})";
+
+ // Calculate item count in this chest
+ int chestItemCount = 0;
+ if (chest.chest != null && selectedItem.SampleItem != null)
+ {
+ foreach (var item in chest.chest.Items)
+ {
+ if (item != null && item.canStackWith(selectedItem.SampleItem))
+ {
+ chestItemCount += item.Stack;
+ }
+ }
+ }
+
+ chestLabel += $" (x{chestItemCount})";
+
+ Color textColor = hasSpace ? Game1.textColor : Game1.textColor * 0.5f;
+ b.DrawString(Game1.smallFont, chestLabel, new Vector2(destRect.X + 8, destRect.Y + 16), textColor);
+
+ if (!hasSpace)
+ {
+ string fullText = ModEntry.SHelper.Translation.Get("not-enough-space");
+ var fullSize = Game1.smallFont.MeasureString(fullText);
+ // Ensure text doesn't overflow to the right
+ float textX = destRect.Right - fullSize.X - 8;
+ if (textX < destRect.X + 8) textX = destRect.X + 8; // clamp
+ b.DrawString(Game1.smallFont, fullText, new Vector2(textX, destRect.Y + 32), Color.Red * 0.8f);
+ }
+ }
+
+ if (selectedItem.ChestsContaining.Count > maxDestVisible)
+ {
+ if (destScroll > 0) destScrollUpButton.draw(b);
+ if (destScroll + maxDestVisible < selectedItem.ChestsContaining.Count) destScrollDownButton.draw(b);
+ }
+ }
+
+ // Draw close button
+ closeButton.draw(b);
+
+ // Draw mouse cursor
+ drawMouse(b);
+ }
+
+ public override void receiveLeftClick(int x, int y, bool playSound = true)
+ {
+ if (closeButton.containsPoint(x, y))
+ {
+ Game1.playSound("bigDeSelect");
+ Game1.activeClickableMenu = parentMenu;
+ return;
+ }
+
+ int maxVisible = GetMaxItemsVisible();
+
+ // Scroll buttons items
+ if (items.Count > maxVisible)
+ {
+ if (itemScroll > 0 && itemScrollUpButton.containsPoint(x, y)) { itemScroll--; Game1.playSound("shwip"); return; }
+ if (itemScroll + maxVisible < items.Count && itemScrollDownButton.containsPoint(x, y)) { itemScroll++; Game1.playSound("shwip"); return; }
+ }
+
+ // Scroll buttons dest
+ if (selectedItemIndex < items.Count)
+ {
+ var selectedItem = items[selectedItemIndex];
+ if (selectedItem.ChestsContaining.Count > maxVisible)
+ {
+ if (destScroll > 0 && destScrollUpButton.containsPoint(x, y)) { destScroll--; Game1.playSound("shwip"); return; }
+ if (destScroll + maxVisible < selectedItem.ChestsContaining.Count && destScrollDownButton.containsPoint(x, y)) { destScroll++; Game1.playSound("shwip"); return; }
+ }
+ }
+
+ int paneWidth = width / 2 - BorderWidth - 24;
+ int startY = yPositionOnScreen + BorderWidth + 64;
+
+ // Check item slots
+ for (int i = 0; i < maxVisible; i++)
+ {
+ int itemIndex = itemScroll + i;
+ if (itemIndex >= items.Count) break;
+
+ int drawY = startY + i * (SlotHeight + 8);
+ Rectangle slotRect = new Rectangle(xPositionOnScreen + BorderWidth + 56, drawY, paneWidth - 64, SlotHeight);
+ Rectangle skipRect = new Rectangle(slotRect.Right - 36, slotRect.Y + 16, 32, 32);
+
+ if (skipRect.Contains(x, y))
+ {
+ // Skip this item
+ items.RemoveAt(itemIndex);
+ selectedItemIndex = Math.Min(selectedItemIndex, Math.Max(0, items.Count - 1));
+ destScroll = 0;
+
+ if (itemScroll > Math.Max(0, items.Count - GetMaxItemsVisible()))
+ itemScroll = Math.Max(0, items.Count - GetMaxItemsVisible());
+
+ Game1.playSound("trashcan");
+
+ if (items.Count == 0)
+ {
+ Game1.activeClickableMenu = parentMenu;
+ }
+ return;
+ }
+ else if (slotRect.Contains(x, y))
+ {
+ selectedItemIndex = itemIndex;
+ destScroll = 0;
+ Game1.playSound("tinyWhip");
+ return;
+ }
+ }
+
+ // Check destination slots for selected item
+ if (selectedItemIndex < items.Count)
+ {
+ var selectedItem = items[selectedItemIndex];
+
+ for (int i = 0; i < maxVisible; i++)
+ {
+ int destIndex = destScroll + i;
+ if (destIndex >= selectedItem.ChestsContaining.Count) break;
+
+ int drawY = startY + i * (SlotHeight + 8);
+ Rectangle destRect = new Rectangle(xPositionOnScreen + width / 2 + 16 + 56, drawY, paneWidth - 64, SlotHeight);
+
+ if (destRect.Contains(x, y))
+ {
+ var chest = selectedItem.ChestsContaining[destIndex];
+ if (selectedItem.ChestsWithSpace.Contains(chest))
+ {
+ PerformConsolidation(chest);
+ Game1.playSound("coin");
+ return;
+ }
+ else
+ {
+ Game1.playSound("cancel");
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ private void PerformConsolidation(ChestData destination)
+ {
+ if (selectedItemIndex >= items.Count)
+ return;
+
+ var selectedItem = items[selectedItemIndex];
+ parentMenu.ConsolidateItem(selectedItem, destination);
+
+ // Remove consolidated item and refresh
+ if (selectedItemIndex < items.Count)
+ {
+ items.RemoveAt(selectedItemIndex);
+ selectedItemIndex = Math.Min(selectedItemIndex, Math.Max(0, items.Count - 1));
+ destScroll = 0;
+
+ if (itemScroll > Math.Max(0, items.Count - GetMaxItemsVisible()))
+ itemScroll = Math.Max(0, items.Count - GetMaxItemsVisible());
+
+ if (items.Count == 0)
+ {
+ Game1.activeClickableMenu = parentMenu;
+ }
+ }
+ }
+
+ public override void receiveRightClick(int x, int y, bool playSound = true) { }
+
+ public override void receiveScrollWheelAction(int direction)
+ {
+ if (items.Count == 0) return;
+
+ int maxVisible = GetMaxItemsVisible();
+
+ if (Game1.getMouseX() < xPositionOnScreen + width / 2)
+ {
+ // Scroll left pane
+ if (direction > 0 && itemScroll > 0)
+ {
+ itemScroll--;
+ Game1.playSound("shiny4");
+ }
+ else if (direction < 0 && itemScroll + maxVisible < items.Count)
+ {
+ itemScroll++;
+ Game1.playSound("shiny4");
+ }
+ }
+ else
+ {
+ // Scroll right pane
+ var selectedItem = items[selectedItemIndex];
+ if (direction > 0 && destScroll > 0)
+ {
+ destScroll--;
+ Game1.playSound("shiny4");
+ }
+ else if (direction < 0 && destScroll + maxVisible < selectedItem.ChestsContaining.Count)
+ {
+ destScroll++;
+ Game1.playSound("shiny4");
+ }
+ }
+ }
+
+ public override void receiveKeyPress(Keys key)
+ {
+ if (key == Keys.Escape || Game1.options.doesInputListContain(Game1.options.menuButton, key))
+ {
+ Game1.activeClickableMenu = parentMenu;
+ }
+ }
+ }
+}
diff --git a/AllChestsMenu/ModConfig.cs b/AllChestsMenu/ModConfig.cs
index a435bc9d..b5c24031 100644
--- a/AllChestsMenu/ModConfig.cs
+++ b/AllChestsMenu/ModConfig.cs
@@ -1,4 +1,5 @@
-using StardewModdingAPI;
+using System.Collections.Generic;
+using StardewModdingAPI;
namespace AllChestsMenu
{
@@ -7,9 +8,15 @@ public class ModConfig
public bool ModEnabled { get; set; } = true;
public bool ModToOpen { get; set; } = false;
public bool LimitToCurrentLocation { get; set; } = false;
- public bool FilterItems { get; set; } = false;
- public bool FilterItemsCategory { get; set; } = false;
- public bool FilterItemsDescription { get; set; } = false;
+
+
+ // New independent filter options
+ public bool FilterChestLabel { get; set; } = true;
+ public bool FilterItemName { get; set; } = false;
+ public bool FilterItemCategory { get; set; } = false;
+ public bool FilterItemDescription { get; set; } = false;
+ public bool EnableControllerKeyboard { get; set; } = true;
+ public List SelectedLocations { get; set; } = new();
public bool IncludeFridge { get; set; } = true;
public bool IncludeMiniFridges { get; set; } = true;
public bool IncludeShippingBin { get; set; } = true;
@@ -19,9 +26,15 @@ public class ModConfig
public bool IncludeAutoGrabbers { get; set; } = true;
public AllChestsMenu.Sort CurrentSort { get; set; } = AllChestsMenu.Sort.NA;
public string SecondarySortingPriority { get; set; } = "Y";
+ public bool KeyboardRequireModifierToOpen { get; set; } = false;
+ public bool ControllerRequireModifierToOpen { get; set; } = false;
+ public SButton KeyboardOpenModifierKey { get; set; } = SButton.LeftShift;
+ public SButton ControllerOpenModifierButton { get; set; } = SButton.LeftTrigger;
public SButton ModKey { get; set; } = SButton.LeftShift;
public SButton ModKey2 { get; set; } = SButton.LeftControl;
public SButton SwitchButton { get; set; } = SButton.ControllerBack;
- public SButton MenuKey { get; set; } = SButton.F2;
+ public SButton KeyboardMenuKey { get; set; } = SButton.F2;
+ public SButton ControllerMenuButton { get; set; } = SButton.None;
+ public SButton MenuKey { get; set; } = SButton.None;
}
}
diff --git a/AllChestsMenu/ModEntry.cs b/AllChestsMenu/ModEntry.cs
index 15c8c9ad..f164ed69 100644
--- a/AllChestsMenu/ModEntry.cs
+++ b/AllChestsMenu/ModEntry.cs
@@ -1,4 +1,4 @@
-using System.IO;
+using System.IO;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI;
using StardewModdingAPI.Events;
@@ -20,6 +20,7 @@ public partial class ModEntry : Mod
public override void Entry(IModHelper helper)
{
Config = Helper.ReadConfig();
+ MigrateLegacyConfig();
context = this;
SMonitor = Monitor;
@@ -53,12 +54,70 @@ public void Input_ButtonPressed(object sender, ButtonPressedEventArgs e)
SHelper.Input.Suppress(e.Button);
}
}
- if (e.Button == Config.MenuKey && (Config.ModKey == SButton.None || !Config.ModToOpen || Helper.Input.IsDown(Config.ModKey)))
+ bool keyboardOpen = e.Button == Config.KeyboardMenuKey
+ && (!Config.KeyboardRequireModifierToOpen || Config.KeyboardOpenModifierKey == SButton.None || Helper.Input.IsDown(Config.KeyboardOpenModifierKey));
+ bool controllerOpen = e.Button == Config.ControllerMenuButton
+ && (!Config.ControllerRequireModifierToOpen || Config.ControllerOpenModifierButton == SButton.None || Helper.Input.IsDown(Config.ControllerOpenModifierButton));
+ if (keyboardOpen || controllerOpen)
{
OpenMenu();
}
}
+ private void MigrateLegacyConfig()
+ {
+ bool changed = false;
+
+ if (Config.MenuKey != SButton.None)
+ {
+ if (Config.MenuKey.ToString().StartsWith("Controller"))
+ {
+ if (Config.ControllerMenuButton == SButton.None)
+ {
+ Config.ControllerMenuButton = Config.MenuKey;
+ changed = true;
+ }
+ }
+ else
+ {
+ if (Config.KeyboardMenuKey == SButton.None || Config.KeyboardMenuKey == SButton.F2)
+ {
+ Config.KeyboardMenuKey = Config.MenuKey;
+ changed = true;
+ }
+ }
+ }
+
+ if (Config.ModToOpen)
+ {
+ if (!Config.KeyboardRequireModifierToOpen)
+ {
+ Config.KeyboardRequireModifierToOpen = true;
+ changed = true;
+ }
+ if (!Config.ControllerRequireModifierToOpen)
+ {
+ Config.ControllerRequireModifierToOpen = true;
+ changed = true;
+ }
+ }
+
+ if (Config.KeyboardOpenModifierKey == SButton.LeftShift && Config.ModKey != SButton.LeftShift)
+ {
+ Config.KeyboardOpenModifierKey = Config.ModKey;
+ changed = true;
+ }
+
+ if (Config.MenuKey != SButton.None)
+ {
+ Config.MenuKey = SButton.None;
+ changed = true;
+ }
+
+ if (changed)
+ Helper.WriteConfig(Config);
+ }
+
public void GameLoop_GameLaunched(object sender, GameLaunchedEventArgs e)
{
// Get Mobile Phone's API
@@ -78,37 +137,65 @@ public void GameLoop_GameLaunched(object sender, GameLaunchedEventArgs e)
save: () => Helper.WriteConfig(Config)
);
- // Main section
+ // ==================== GENERAL SECTION ====================
+ gmcm.AddSectionTitle(
+ mod: ModManifest,
+ text: () => SHelper.Translation.Get("GMCM.Section.General.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.Section.General.Desc")
+ );
+
gmcm.AddBoolOption(
mod: ModManifest,
name: () => SHelper.Translation.Get("GMCM.ModEnabled.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.ModEnabled.Tooltip"),
getValue: () => Config.ModEnabled,
setValue: value => Config.ModEnabled = value
);
+
+ // ==================== CONTAINERS SECTION ====================
+ gmcm.AddSectionTitle(
+ mod: ModManifest,
+ text: () => SHelper.Translation.Get("GMCM.Section.Containers.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.Section.Containers.Desc")
+ );
+
gmcm.AddBoolOption(
mod: ModManifest,
name: () => SHelper.Translation.Get("GMCM.LimitToCurrentLocation.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.LimitToCurrentLocation.Tooltip"),
getValue: () => Config.LimitToCurrentLocation,
setValue: value => Config.LimitToCurrentLocation = value
);
+
+ gmcm.AddParagraph(
+ mod: ModManifest,
+ text: () => ""
+ ); // spacer
+
gmcm.AddBoolOption(
mod: ModManifest,
name: () => SHelper.Translation.Get("GMCM.IncludeFridge.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.IncludeFridge.Tooltip"),
getValue: () => Config.IncludeFridge,
setValue: value => Config.IncludeFridge = value
);
+
gmcm.AddBoolOption(
mod: ModManifest,
name: () => SHelper.Translation.Get("GMCM.IncludeMiniFridges.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.IncludeMiniFridges.Tooltip"),
getValue: () => Config.IncludeMiniFridges,
setValue: value => Config.IncludeMiniFridges = value
);
+
gmcm.AddBoolOption(
mod: ModManifest,
name: () => SHelper.Translation.Get("GMCM.IncludeShippingBin.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.IncludeShippingBin.Tooltip"),
getValue: () => Config.IncludeShippingBin,
setValue: value => Config.IncludeShippingBin = value
);
+
gmcm.AddBoolOption(
mod: ModManifest,
name: () => SHelper.Translation.Get("GMCM.UnrestrictedShippingBin.Name"),
@@ -116,68 +203,95 @@ public void GameLoop_GameLaunched(object sender, GameLaunchedEventArgs e)
getValue: () => Config.UnrestrictedShippingBin,
setValue: value => Config.UnrestrictedShippingBin = value
);
- gmcm.AddBoolOption(
- mod: ModManifest,
- name: () => SHelper.Translation.Get("GMCM.FilterItems.Name"),
- getValue: () => Config.FilterItems,
- setValue: value => Config.FilterItems = value
- );
- gmcm.AddBoolOption(
- mod: ModManifest,
- name: () => SHelper.Translation.Get("GMCM.FilterItemsCategory.Name"),
- getValue: () => Config.FilterItemsCategory,
- setValue: value => Config.FilterItemsCategory = value
- );
- gmcm.AddBoolOption(
- mod: ModManifest,
- name: () => SHelper.Translation.Get("GMCM.FilterItemsDescription.Name"),
- getValue: () => Config.FilterItemsDescription,
- setValue: value => Config.FilterItemsDescription = value
- );
+
gmcm.AddBoolOption(
mod: ModManifest,
name: () => SHelper.Translation.Get("GMCM.IncludeMiniShippingBins.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.IncludeMiniShippingBins.Tooltip"),
getValue: () => Config.IncludeMiniShippingBins,
setValue: value => Config.IncludeMiniShippingBins = value
);
+
gmcm.AddBoolOption(
mod: ModManifest,
name: () => SHelper.Translation.Get("GMCM.IncludeJunimoChests.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.IncludeJunimoChests.Tooltip"),
getValue: () => Config.IncludeJunimoChests,
setValue: value => Config.IncludeJunimoChests = value
);
+
gmcm.AddBoolOption(
mod: ModManifest,
name: () => SHelper.Translation.Get("GMCM.IncludeAutoGrabbers.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.IncludeAutoGrabbers.Tooltip"),
getValue: () => Config.IncludeAutoGrabbers,
setValue: value => Config.IncludeAutoGrabbers = value
);
+
+
+
+ // ==================== SORTING SECTION ====================
+ gmcm.AddSectionTitle(
+ mod: ModManifest,
+ text: () => SHelper.Translation.Get("GMCM.Section.Sorting.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.Section.Sorting.Desc")
+ );
+
gmcm.AddTextOption(
mod: ModManifest,
name: () => SHelper.Translation.Get("GMCM.SecondarySortingPriority.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.SecondarySortingPriority.Tooltip"),
getValue: () => Config.SecondarySortingPriority,
setValue: value => Config.SecondarySortingPriority = value,
- allowedValues: new string[] { "X", "Y" }
+ allowedValues: new string[] { "X", "Y" },
+ formatAllowedValue: value => SHelper.Translation.Get($"GMCM.SecondarySortingPriority.{value}")
);
- gmcm.AddKeybind(
+
+ // ==================== CONTROLS SECTION ====================
+ gmcm.AddSectionTitle(
+ mod: ModManifest,
+ text: () => SHelper.Translation.Get("GMCM.Section.Controls.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.Section.Controls.Desc")
+ );
+
+ gmcm.AddSectionTitle(
mod: ModManifest,
- name: () => SHelper.Translation.Get("GMCM.MenuKey.Name"),
- getValue: () => Config.MenuKey,
- setValue: value => Config.MenuKey = value
+ text: () => SHelper.Translation.Get("GMCM.Section.KeyboardControls.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.Section.KeyboardControls.Desc")
);
+
gmcm.AddBoolOption(
mod: ModManifest,
- name: () => SHelper.Translation.Get("GMCM.ModToOpen.Name"),
- getValue: () => Config.ModToOpen,
- setValue: value => Config.ModToOpen = value
+ name: () => SHelper.Translation.Get("GMCM.KeyboardRequireModifierToOpen.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.KeyboardRequireModifierToOpen.Tooltip"),
+ getValue: () => Config.KeyboardRequireModifierToOpen,
+ setValue: value => Config.KeyboardRequireModifierToOpen = value
+ );
+
+ gmcm.AddKeybind(
+ mod: ModManifest,
+ name: () => SHelper.Translation.Get("GMCM.KeyboardOpenModifierKey.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.KeyboardOpenModifierKey.Tooltip"),
+ getValue: () => Config.KeyboardOpenModifierKey,
+ setValue: value => Config.KeyboardOpenModifierKey = value
);
+
+ gmcm.AddKeybind(
+ mod: ModManifest,
+ name: () => SHelper.Translation.Get("GMCM.KeyboardMenuKey.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.KeyboardMenuKey.Tooltip"),
+ getValue: () => Config.KeyboardMenuKey,
+ setValue: value => Config.KeyboardMenuKey = value
+ );
+
gmcm.AddKeybind(
mod: ModManifest,
- name: () => SHelper.Translation.Get("GMCM.ModKey.Name"),
- tooltip: () => SHelper.Translation.Get("GMCM.ModKey.Tooltip"),
+ name: () => SHelper.Translation.Get("GMCM.TransferModifierKey.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.TransferModifierKey.Tooltip"),
getValue: () => Config.ModKey,
setValue: value => Config.ModKey = value
);
+
gmcm.AddKeybind(
mod: ModManifest,
name: () => SHelper.Translation.Get("GMCM.ModKey2.Name"),
@@ -185,6 +299,37 @@ public void GameLoop_GameLaunched(object sender, GameLaunchedEventArgs e)
getValue: () => Config.ModKey2,
setValue: value => Config.ModKey2 = value
);
+
+ gmcm.AddSectionTitle(
+ mod: ModManifest,
+ text: () => SHelper.Translation.Get("GMCM.Section.ControllerControls.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.Section.ControllerControls.Desc")
+ );
+
+ gmcm.AddBoolOption(
+ mod: ModManifest,
+ name: () => SHelper.Translation.Get("GMCM.ControllerRequireModifierToOpen.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.ControllerRequireModifierToOpen.Tooltip"),
+ getValue: () => Config.ControllerRequireModifierToOpen,
+ setValue: value => Config.ControllerRequireModifierToOpen = value
+ );
+
+ gmcm.AddKeybind(
+ mod: ModManifest,
+ name: () => SHelper.Translation.Get("GMCM.ControllerOpenModifierButton.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.ControllerOpenModifierButton.Tooltip"),
+ getValue: () => Config.ControllerOpenModifierButton,
+ setValue: value => Config.ControllerOpenModifierButton = value
+ );
+
+ gmcm.AddKeybind(
+ mod: ModManifest,
+ name: () => SHelper.Translation.Get("GMCM.ControllerMenuButton.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.ControllerMenuButton.Tooltip"),
+ getValue: () => Config.ControllerMenuButton,
+ setValue: value => Config.ControllerMenuButton = value
+ );
+
gmcm.AddKeybind(
mod: ModManifest,
name: () => SHelper.Translation.Get("GMCM.SwitchButton.Name"),
@@ -192,6 +337,14 @@ public void GameLoop_GameLaunched(object sender, GameLaunchedEventArgs e)
getValue: () => Config.SwitchButton,
setValue: value => Config.SwitchButton = value
);
+
+ gmcm.AddBoolOption(
+ mod: ModManifest,
+ name: () => SHelper.Translation.Get("GMCM.EnableControllerKeyboard.Name"),
+ tooltip: () => SHelper.Translation.Get("GMCM.EnableControllerKeyboard.Tooltip"),
+ getValue: () => Config.EnableControllerKeyboard,
+ setValue: value => Config.EnableControllerKeyboard = value
+ );
}
}
}
diff --git a/AllChestsMenu/NuGet.config b/AllChestsMenu/NuGet.config
new file mode 100644
index 00000000..765346e5
--- /dev/null
+++ b/AllChestsMenu/NuGet.config
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/AllChestsMenu/README.md b/AllChestsMenu/README.md
new file mode 100644
index 00000000..de9e7931
--- /dev/null
+++ b/AllChestsMenu/README.md
@@ -0,0 +1,80 @@
+# All Chests Menu
+
+A Stardew Valley mod that allows you to access and manage all your chests, fridges, shipping bins, and more from a single, unified menu.
+
+## 🌟 Key Features
+
+- **Global Chest Access:** View and interact with all your storage containers from anywhere in the world.
+- **Extensive Container Support:** Access not just standard Chests, but also:
+ - Farmhouse Fridges & Mini-Fridges
+ - Shipping Bins & Mini-Shipping Bins
+ - Junimo Chests
+ - Auto-Grabbers
+- **Advanced Filtering & Search:** Quickly find exactly what you need with dedicated text filters:
+ - **Location Dropdown:** Filter chests by specific map locations (e.g., Farm, House, Greenhouse) via an intuitive dropdown menu.
+ - **Chest Name:** Search for custom-named chests.
+ - **Item Name:** Search for specific items inside the chests.
+ - **Item Description / Category:** Search based on item descriptions or their category.
+- **Smart Organization & Quality of Life:**
+ - **Consolidate Items:** A powerful button that merges and stacks duplicate items scattered across different chests, saving space automatically.
+ - **Sort All (Global):** Sort items across *all* your chests with a single click.
+ - **Target System:** Select a specific chest as your "Target" to quickly send items from other chests directly into it.
+ - **Custom Sorting:** Sort your list of chests dynamically by Location, Name, Capacity, or Item Count (Ascending/Descending).
+ - **Store Alike (Put All / Take All):** Quickly deposit or extract items. Use the **Same-Item Transfer Key** (default: *Left Control*) while clicking to only transfer items that match what's already in the destination.
+- **Polished UI:**
+ - **Responsive Grid:** The menu adapts dynamically based on your screen resolution, expanding columns when space is available.
+ - **Zebra Striping:** Chests are visually grouped by location with alternating background colors and separator lines, making browsing much easier on the eyes.
+ - **Clear Filters:** A dedicated "X" button to instantly wipe all active text and location filters.
+- **Gamepad Support:** Full controller support with virtual keyboard integration for text filtering. Use the **Switch Button** (default: *Back/Select*) to easily toggle focus between the chest list and your player inventory.
+- **Localization (i18n):** Supports multiple languages, including English, French, Russian, and Portuguese (pt-BR).
+
+---
+
+## 🎮 How to Use the Menu
+
+Once the mod is installed, press the configured hotkey (default: **`F2`** or **`I`** depending on config) to open the All Chests Menu.
+
+### Individual Chest Action Buttons
+Every chest in the list has a set of buttons on its right side:
+- **Location:** The location of the chest in the world.
+- **Open:** Opens the standard game menu for that specific chest.
+- **Put:** Moves items from your player inventory into this chest. *(Hold `Left Control` to only deposit items the chest already has).*
+- **Take:** Takes items from this chest into your player inventory. *(Hold `Left Control` to only take items you already have).*
+- **Rename:** Allows you to change the custom name of the chest.
+- **Target:** Sets this specific chest as your current "Target". A purple border will appear around it. When you click items in other chests, they will be sent directly to this Target chest instead of your inventory!
+
+### Main Global Action Buttons
+Located just above your player inventory, you will find the main action buttons:
+1. **Organize (Chest Icon):** Sorts the items within the currently selected chest.
+2. **Store Alike (Stack Icon):** Scans your inventory and automatically moves items into chests that already contain items of the same type.
+3. **Consolidate (Merge Icon):** Looks through all your chests and merges matching item stacks together to free up slots.
+4. **Sort All (Global Organize Icon):** Sorts the items individually inside *every single chest* you own.
+5. **Trash Can:** Drag and drop items here to delete them permanently.
+
+### Filtering Menu
+On the left side of the screen, you have multiple fields to filter the displayed chests in real-time:
+- **Loc (Location):** Click to select one or multiple specific locations from a Dropdown list.
+- **Chest Label:** Show only chests that contain this word in their name.
+- **Item Name:** Show only chests that contain an item with this exact name.
+- **Item Description:** Show only chests that contain an item with this word in its description.
+- **Clear Filters (X Button):** Clears all text fields and resets the location dropdown instantly.
+
+---
+
+## ⚙️ Configuration
+
+You can configure this mod by editing the `config.json` file generated in the mod's folder after running the game once, or simply by using the **[Generic Mod Config Menu (GMCM)](https://www.nexusmods.com/stardewvalley/mods/5098)** in-game (Highly Recommended).
+
+### Key Settings:
+- **Menu Key:** The default key to open the menu.
+- **Limit To Current Location:** If enabled, the menu will only show chests in your current map area instead of globally.
+- **Container Toggles:** Individually toggle whether Fridges, Shipping Bins, Junimo Chests, and Auto-Grabbers should appear in the menu.
+- **Shipping Bin Access:** Determine if you have unrestricted access to take any item out of the Shipping Bin, or just the last shipped item.
+- **Coordinate Sort Order:** Choose how chests in the same location are sorted (Left-to-Right `X` vs Top-to-Bottom `Y`).
+- **Modifier Keys:** Customize shortcuts for Same-Item Transfers (`ModKey2`, default: *Left Control*) and standard transfers (`ModKey`, default: *Left Shift*).
+- **Controller Switch Button:** Set the gamepad button to jump between your inventory and the chest menu.
+
+---
+
+## 🤝 Compatibility
+Works with Stardew Valley 1.6+ and requires **[SMAPI](https://smapi.io/)**.
diff --git a/AllChestsMenu/build.txt b/AllChestsMenu/build.txt
new file mode 100644
index 00000000..62954995
--- /dev/null
+++ b/AllChestsMenu/build.txt
@@ -0,0 +1,26 @@
+ Determining projects to restore...
+ All projects are up-to-date for restore.
+ AllChestsMenu -> C:\Users\rodol\Desktop\mods\StardewValleyMods\AllChestsMenu\bin\Debug\net6.0\AllChestsMenu.dll
+ [mod build package] Handling build with options BundleExtraAssemblies: null, EnableModDeploy: true, EnableModZip: true, GameModsDir: 'E:\SteamLibrary\steamapps\common\Stardew Valley\Mods', IgnoreModFilePaths: null, ModDllName: 'AllChestsMenu', ModFolderName: 'AllChestsMenu', ModZipPath: 'C:\Users\rodol\Desktop\mods\StardewValleyMods\AllChestsMenu\bin\Debug\net6.0\', ProjectDir: 'C:\Users\rodol\Desktop\mods\StardewValleyMods\AllChestsMenu\', ProjectVersion: '0.3.2', TargetDir: 'C:\Users\rodol\Desktop\mods\StardewValleyMods\AllChestsMenu\bin\Debug\net6.0\'
+ [mod build package] Copying the mod files to E:\SteamLibrary\steamapps\common\Stardew Valley\Mods\AllChestsMenu...
+C:\Users\rodol\.nuget\packages\pathoschild.stardew.modbuildconfig\4.4.0\build\Pathoschild.Stardew.ModBuildConfig.targets(87,5): error : [mod build package] Failed trying to deploy the mod. [C:\Users\rodol\Desktop\mods\StardewValleyMods\AllChestsMenu\AllChestsMenu.csproj]
+C:\Users\rodol\.nuget\packages\pathoschild.stardew.modbuildconfig\4.4.0\build\Pathoschild.Stardew.ModBuildConfig.targets(87,5): error : System.IO.IOException: The process cannot access the file 'E:\SteamLibrary\steamapps\common\Stardew Valley\Mods\AllChestsMenu\AllChestsMenu.dll' because it is being used by another process. [C:\Users\rodol\Desktop\mods\StardewValleyMods\AllChestsMenu\AllChestsMenu.csproj]
+C:\Users\rodol\.nuget\packages\pathoschild.stardew.modbuildconfig\4.4.0\build\Pathoschild.Stardew.ModBuildConfig.targets(87,5): error : at System.IO.FileSystem.CopyFile(String sourceFullPath, String destFullPath, Boolean overwrite) [C:\Users\rodol\Desktop\mods\StardewValleyMods\AllChestsMenu\AllChestsMenu.csproj]
+C:\Users\rodol\.nuget\packages\pathoschild.stardew.modbuildconfig\4.4.0\build\Pathoschild.Stardew.ModBuildConfig.targets(87,5): error : at System.IO.FileInfo.CopyTo(String destFileName, Boolean overwrite) [C:\Users\rodol\Desktop\mods\StardewValleyMods\AllChestsMenu\AllChestsMenu.csproj]
+C:\Users\rodol\.nuget\packages\pathoschild.stardew.modbuildconfig\4.4.0\build\Pathoschild.Stardew.ModBuildConfig.targets(87,5): error : at StardewModdingAPI.ModBuildConfig.Framework.BundleFile.CopyToFolder(String folderPath) [C:\Users\rodol\Desktop\mods\StardewValleyMods\AllChestsMenu\AllChestsMenu.csproj]
+C:\Users\rodol\.nuget\packages\pathoschild.stardew.modbuildconfig\4.4.0\build\Pathoschild.Stardew.ModBuildConfig.targets(87,5): error : at StardewModdingAPI.ModBuildConfig.DeployModTask.CreateModFolder(IDictionary`2 modPackages, String outputPath) [C:\Users\rodol\Desktop\mods\StardewValleyMods\AllChestsMenu\AllChestsMenu.csproj]
+C:\Users\rodol\.nuget\packages\pathoschild.stardew.modbuildconfig\4.4.0\build\Pathoschild.Stardew.ModBuildConfig.targets(87,5): error : at StardewModdingAPI.ModBuildConfig.DeployModTask.Execute() [C:\Users\rodol\Desktop\mods\StardewValleyMods\AllChestsMenu\AllChestsMenu.csproj]
+
+Build FAILED.
+
+C:\Users\rodol\.nuget\packages\pathoschild.stardew.modbuildconfig\4.4.0\build\Pathoschild.Stardew.ModBuildConfig.targets(87,5): error : [mod build package] Failed trying to deploy the mod. [C:\Users\rodol\Desktop\mods\StardewValleyMods\AllChestsMenu\AllChestsMenu.csproj]
+C:\Users\rodol\.nuget\packages\pathoschild.stardew.modbuildconfig\4.4.0\build\Pathoschild.Stardew.ModBuildConfig.targets(87,5): error : System.IO.IOException: The process cannot access the file 'E:\SteamLibrary\steamapps\common\Stardew Valley\Mods\AllChestsMenu\AllChestsMenu.dll' because it is being used by another process. [C:\Users\rodol\Desktop\mods\StardewValleyMods\AllChestsMenu\AllChestsMenu.csproj]
+C:\Users\rodol\.nuget\packages\pathoschild.stardew.modbuildconfig\4.4.0\build\Pathoschild.Stardew.ModBuildConfig.targets(87,5): error : at System.IO.FileSystem.CopyFile(String sourceFullPath, String destFullPath, Boolean overwrite) [C:\Users\rodol\Desktop\mods\StardewValleyMods\AllChestsMenu\AllChestsMenu.csproj]
+C:\Users\rodol\.nuget\packages\pathoschild.stardew.modbuildconfig\4.4.0\build\Pathoschild.Stardew.ModBuildConfig.targets(87,5): error : at System.IO.FileInfo.CopyTo(String destFileName, Boolean overwrite) [C:\Users\rodol\Desktop\mods\StardewValleyMods\AllChestsMenu\AllChestsMenu.csproj]
+C:\Users\rodol\.nuget\packages\pathoschild.stardew.modbuildconfig\4.4.0\build\Pathoschild.Stardew.ModBuildConfig.targets(87,5): error : at StardewModdingAPI.ModBuildConfig.Framework.BundleFile.CopyToFolder(String folderPath) [C:\Users\rodol\Desktop\mods\StardewValleyMods\AllChestsMenu\AllChestsMenu.csproj]
+C:\Users\rodol\.nuget\packages\pathoschild.stardew.modbuildconfig\4.4.0\build\Pathoschild.Stardew.ModBuildConfig.targets(87,5): error : at StardewModdingAPI.ModBuildConfig.DeployModTask.CreateModFolder(IDictionary`2 modPackages, String outputPath) [C:\Users\rodol\Desktop\mods\StardewValleyMods\AllChestsMenu\AllChestsMenu.csproj]
+C:\Users\rodol\.nuget\packages\pathoschild.stardew.modbuildconfig\4.4.0\build\Pathoschild.Stardew.ModBuildConfig.targets(87,5): error : at StardewModdingAPI.ModBuildConfig.DeployModTask.Execute() [C:\Users\rodol\Desktop\mods\StardewValleyMods\AllChestsMenu\AllChestsMenu.csproj]
+ 0 Warning(s)
+ 1 Error(s)
+
+Time Elapsed 00:00:01.12
diff --git a/AllChestsMenu/build_output.txt b/AllChestsMenu/build_output.txt
new file mode 100644
index 00000000..b76b62c7
Binary files /dev/null and b/AllChestsMenu/build_output.txt differ
diff --git a/AllChestsMenu/errors.txt b/AllChestsMenu/errors.txt
new file mode 100644
index 00000000..3f55251f
--- /dev/null
+++ b/AllChestsMenu/errors.txt
@@ -0,0 +1,6 @@
+
+Build succeeded.
+ 0 Warning(s)
+ 0 Error(s)
+
+Time Elapsed 00:00:01.04
diff --git a/AllChestsMenu/i18n/default.json b/AllChestsMenu/i18n/default.json
index 00bd5aa9..681bf6d2 100644
--- a/AllChestsMenu/i18n/default.json
+++ b/AllChestsMenu/i18n/default.json
@@ -1,4 +1,4 @@
-{
+{
"filter": "Filter",
"name": "Name",
"fridge": "Fridge",
@@ -8,6 +8,31 @@
"rename": "Rename",
"target": "Target",
"sort": "Sort By",
+ "consolidate": "Consolidate",
+ "consolidate-tooltip": "Merge duplicate items across chests",
+ "sort-all": "Sort All",
+ "sort-all-tooltip": "Sort all items in all chests",
+ "consolidate-menu-title": "Consolidate Items",
+ "item-found-in": "Found {0} items to consolidate:",
+ "select-destination": "Select destination:",
+ "not-enough-space": "Not enough space in selected chest!",
+ "consolidation-complete": "Items consolidated!",
+ "no-duplicates": "No duplicate items found to consolidate.",
+ "filter-item-name": "Item Name",
+ "filter-chest-label": "Chest Label",
+ "filter-item-category": "Item Category",
+ "filter-item-description": "Item Description",
+ "filter-location": "Location",
+ "filter-location-select": "Select Locations",
+ "category-all": "All",
+ "category-none": "None",
+ "clear-filters": "Clear Filters",
+ "filter-active": "Filters Active",
+ "filter-enable-chest-label": "Chest Name",
+ "filter-enable-item-name": "Item Name",
+ "filter-enable-category": "Category",
+ "filter-enable-description": "Description",
+ "filter-enable-location": "Location",
"sort-LA": "Location (Asc)",
"sort-LD": "Location (Desc)",
"sort-NA": "Name (Asc)",
@@ -17,27 +42,77 @@
"sort-IA": "Items (Asc)",
"sort-ID": "Items (Desc)",
- // GMCM
- "GMCM.ModEnabled.Name": "Mod Enabled",
- "GMCM.LimitToCurrentLocation.Name": "Current Loc Only",
- "GMCM.IncludeFridge.Name": "Include Fridge",
- "GMCM.IncludeMiniFridges.Name": "Include Mini-Fridges",
- "GMCM.IncludeShippingBin.Name": "Include Shipping Bin",
- "GMCM.UnrestrictedShippingBin.Name": "Unrestricted Shipping Bin",
- "GMCM.UnrestrictedShippingBin.Tooltip": "When enabled, items beyond the last one added can be retrieved.",
- "GMCM.IncludeMiniShippingBins.Name": "Include Mini-Shipping Bins",
- "GMCM.IncludeJunimoChests.Name": "Include Junimo Chests",
- "GMCM.IncludeAutoGrabbers.Name": "Include Auto-Grabbers",
- "GMCM.SecondarySortingPriority.Name": "Secondary Sorting Priority",
- "GMCM.MenuKey.Name": "Menu Key",
- "GMCM.FilterItems.Name": "Filter by item names",
- "GMCM.FilterItemsCategory.Name": "Filter by item category",
- "GMCM.FilterItemsDescription.Name": "Filter by item description",
- "GMCM.ModToOpen.Name": "Req. Mod Key To Open",
- "GMCM.ModKey.Name": "Mod Key",
- "GMCM.ModKey.Tooltip": "Hold down to open menu and to transfer contents instead of swapping menus.",
- "GMCM.ModKey2.Name": "Mod Key 2",
- "GMCM.ModKey2.Tooltip": "Hold down to transfer only same items when transfering all.",
- "GMCM.SwitchButton.Name": "Switch Button",
- "GMCM.SwitchButton.Tooltip": "For controllers to switch between upper and lower interfaces."
+ // GMCM Section Titles
+ "GMCM.Section.General.Name": "General",
+ "GMCM.Section.General.Desc": "Basic mod settings and keybinds",
+ "GMCM.Section.Containers.Name": "Containers",
+ "GMCM.Section.Containers.Desc": "Choose which storage types appear in the menu",
+ "GMCM.Section.Filter.Name": "Search & Filter",
+ "GMCM.Section.Filter.Desc": "Configure how the search box filters items",
+ "GMCM.Section.Sorting.Name": "Sorting",
+ "GMCM.Section.Sorting.Desc": "Change how chests are sorted in the list",
+ "GMCM.Section.Controls.Name": "Controls",
+ "GMCM.Section.Controls.Desc": "Keyboard and controller bindings",
+ "GMCM.Section.KeyboardControls.Name": "Keyboard",
+ "GMCM.Section.KeyboardControls.Desc": "Keyboard settings for opening the menu and item transfer shortcuts",
+ "GMCM.Section.ControllerControls.Name": "Controller",
+ "GMCM.Section.ControllerControls.Desc": "Controller settings for opening the menu and navigation",
+
+ // General
+ "GMCM.ModEnabled.Name": "Enable Mod",
+ "GMCM.ModEnabled.Tooltip": "Turn the All Chests Menu mod on or off",
+
+ // Containers
+ "GMCM.LimitToCurrentLocation.Name": "Current Location Only",
+ "GMCM.LimitToCurrentLocation.Tooltip": "Only show chests in the current location instead of all locations",
+ "GMCM.IncludeFridge.Name": "Kitchen Fridge",
+ "GMCM.IncludeFridge.Tooltip": "Include the fridge in the farmhouse kitchen",
+ "GMCM.IncludeMiniFridges.Name": "Mini Fridges",
+ "GMCM.IncludeMiniFridges.Tooltip": "Include mini-fridge objects placed anywhere",
+ "GMCM.IncludeShippingBin.Name": "Shipping Bin",
+ "GMCM.IncludeShippingBin.Tooltip": "Include the farm's shipping bin as a storage container",
+ "GMCM.UnrestrictedShippingBin.Name": "Full Shipping Bin Access",
+ "GMCM.UnrestrictedShippingBin.Tooltip": "Allow retrieving ANY item from the shipping bin, not just the most recently shipped one. Disable to only retrieve the last item.",
+ "GMCM.IncludeMiniShippingBins.Name": "Mini Shipping Bins",
+ "GMCM.IncludeMiniShippingBins.Tooltip": "Include mini-shipping bin objects placed anywhere",
+ "GMCM.IncludeJunimoChests.Name": "Junimo Chests",
+ "GMCM.IncludeJunimoChests.Tooltip": "Include Junimo chests from the Junimo Hut building",
+ "GMCM.IncludeAutoGrabbers.Name": "Auto-Grabbers",
+ "GMCM.IncludeAutoGrabbers.Tooltip": "Include items inside auto-grabber machines",
+
+ // Search & Filter
+ "GMCM.FilterItems.Name": "Search by Item Name",
+ "GMCM.FilterItems.Tooltip": "Filter chests based on item names when searching",
+ "GMCM.FilterItemsCategory.Name": "Search by Category",
+ "GMCM.FilterItemsCategory.Tooltip": "Filter chests based on item categories when searching",
+ "GMCM.FilterItemsDescription.Name": "Search by Description",
+ "GMCM.FilterItemsDescription.Tooltip": "Filter chests based on item descriptions when searching",
+
+ // Sorting
+ "GMCM.SecondarySortingPriority.Name": "Coordinate Sort Order",
+ "GMCM.SecondarySortingPriority.Tooltip": "When sorting by location/name, how should chests at the same place be ordered? 'Y' sorts top-to-bottom first, 'X' sorts left-to-right first.",
+ "GMCM.SecondarySortingPriority.X": "X (Left to Right)",
+ "GMCM.SecondarySortingPriority.Y": "Y (Top to Bottom)",
+
+ // Controls
+ "GMCM.KeyboardMenuKey.Name": "Open Menu Key (Keyboard)",
+ "GMCM.KeyboardMenuKey.Tooltip": "Press this keyboard key to open the All Chests Menu",
+ "GMCM.KeyboardRequireModifierToOpen.Name": "Require Modifier (Keyboard)",
+ "GMCM.KeyboardRequireModifierToOpen.Tooltip": "If enabled, opening with keyboard requires the keyboard modifier key below",
+ "GMCM.KeyboardOpenModifierKey.Name": "Open Modifier Key (Keyboard)",
+ "GMCM.KeyboardOpenModifierKey.Tooltip": "Keyboard key used as modifier when 'Require Modifier To Open (Keyboard)' is enabled",
+ "GMCM.ControllerMenuButton.Name": "Open Menu Button (Controller)",
+ "GMCM.ControllerMenuButton.Tooltip": "Press this controller button to open the All Chests Menu",
+ "GMCM.ControllerRequireModifierToOpen.Name": "Require Modifier (Controller)",
+ "GMCM.ControllerRequireModifierToOpen.Tooltip": "If enabled, opening with controller requires the controller modifier button below",
+ "GMCM.ControllerOpenModifierButton.Name": "Open Modifier Button (Controller)",
+ "GMCM.ControllerOpenModifierButton.Tooltip": "Controller button used as modifier when 'Require Modifier To Open (Controller)' is enabled",
+ "GMCM.TransferModifierKey.Name": "Transfer Modifier Key",
+ "GMCM.TransferModifierKey.Tooltip": "Hold this key while clicking to transfer items to your inventory instead of picking them up.",
+ "GMCM.ModKey2.Name": "Same-Item Transfer Key",
+ "GMCM.ModKey2.Tooltip": "Hold this key while using the 'Put All' or 'Take All' buttons to only transfer items that match what's already in the target inventory.",
+ "GMCM.SwitchButton.Name": "Controller Switch Button",
+ "GMCM.SwitchButton.Tooltip": "Button for gamepad users to switch focus between the chest list (top) and player inventory (bottom)",
+ "GMCM.EnableControllerKeyboard.Name": "Enable Controller Keyboard",
+ "GMCM.EnableControllerKeyboard.Tooltip": "Opens an on-screen keyboard when selecting a text input using a controller."
}
diff --git a/AllChestsMenu/i18n/fr.json b/AllChestsMenu/i18n/fr.json
index 4f387000..497928f2 100644
--- a/AllChestsMenu/i18n/fr.json
+++ b/AllChestsMenu/i18n/fr.json
@@ -1,4 +1,4 @@
-{
+{
"filter": "Filtre",
"name": "Nom",
"fridge": "Réfrigérateur",
@@ -8,6 +8,31 @@
"rename": "Renommer",
"target": "Cibler",
"sort": "Trier par",
+ "consolidate": "Consolider",
+ "consolidate-tooltip": "Fusionner les objets en double dans les coffres",
+ "sort-all": "Tout Trier",
+ "sort-all-tooltip": "Trier tous les objets dans tous les coffres",
+ "consolidate-menu-title": "Consolider les Objets",
+ "item-found-in": "Trouvé dans {0} coffres:",
+ "select-destination": "Sélectionner la destination:",
+ "not-enough-space": "Pas assez d'espace dans le coffre sélectionné!",
+ "consolidation-complete": "Objets consolidés!",
+ "no-duplicates": "Aucun objet en double trouvé à consolider.",
+ "filter-item-name": "Nom de l'objet",
+ "filter-chest-label": "Label du coffre",
+ "filter-item-category": "Catégorie de l'objet",
+ "filter-item-description": "Description de l'objet",
+ "filter-location": "Emplacement",
+ "filter-location-select": "Sélectionner emplacements",
+ "category-all": "Tout",
+ "category-none": "Aucun",
+ "clear-filters": "Effacer les filtres",
+ "filter-active": "Filtres actifs",
+ "filter-enable-chest-label": "Nom du coffre",
+ "filter-enable-item-name": "Nom de l'objet",
+ "filter-enable-category": "Catégorie",
+ "filter-enable-description": "Description",
+ "filter-enable-location": "Emplacement",
"sort-LA": "Emplacement (Asc)",
"sort-LD": "Emplacement (Desc)",
"sort-NA": "Nom (Asc)",
@@ -17,24 +42,65 @@
"sort-IA": "Objets (Asc)",
"sort-ID": "Objets (Desc)",
- // GMCM
+ // GMCM Section Titles
+ "GMCM.Section.General.Name": "Général",
+ "GMCM.Section.General.Desc": "Paramètres de base du mod",
+ "GMCM.Section.Containers.Name": "Contenants",
+ "GMCM.Section.Containers.Desc": "Choisir quels types de stockage apparaissent dans le menu",
+ "GMCM.Section.Filter.Name": "Recherche & Filtres",
+ "GMCM.Section.Filter.Desc": "Configurer comment la barre de recherche filtre les items",
+ "GMCM.Section.Sorting.Name": "Tri",
+ "GMCM.Section.Sorting.Desc": "Changer comment les coffres sont triés dans la liste",
+ "GMCM.Section.Controls.Name": "Contrôles",
+ "GMCM.Section.Controls.Desc": "Paramètres clavier et manette",
+
+ // General
"GMCM.ModEnabled.Name": "Activer le Mod",
+ "GMCM.ModEnabled.Tooltip": "Active ou désactive le mod All Chests Menu",
+
+ // Containers
"GMCM.LimitToCurrentLocation.Name": "Emplacement actuel uniquement",
- "GMCM.IncludeFridge.Name": "Inclure le réfrigérateur",
- "GMCM.IncludeMiniFridges.Name": "Inclure les mini-réfrigérateurs",
- "GMCM.IncludeShippingBin.Name": "Inclure le bac d'expédition",
- "GMCM.UnrestrictedShippingBin.Name": "Bac d'expédition non restreint",
- "GMCM.UnrestrictedShippingBin.Tooltip": "Lorsque activé, les objets au-delà du dernier ajouté peuvent être récupérés.",
- "GMCM.IncludeMiniShippingBins.Name": "Inclure les mini-bacs d'expédition",
- "GMCM.IncludeJunimoChests.Name": "Inclure les coffres Junimo",
- "GMCM.IncludeAutoGrabbers.Name": "Inclure les récolteurs automatiques",
- "GMCM.SecondarySortingPriority.Name": "Priorité du tri secondaire",
- "GMCM.MenuKey.Name": "Touche pour ouvrir le menu",
- "GMCM.ModToOpen.Name": "Nécessite la touche secondaire",
- "GMCM.ModKey.Name": "Touche secondaire",
- "GMCM.ModKey.Tooltip": "Maintenez enfoncé pour ouvrir le menu et pour transférer le contenu au lieu d'intervertir les menus.",
- "GMCM.ModKey2.Name": "Touche tertiaire",
- "GMCM.ModKey2.Tooltip": "Maintenez enfoncé pour transférer uniquement les objets similaires lorsque les objets sont tous pris ou déposés.",
- "GMCM.SwitchButton.Name": "Touche de commutation",
- "GMCM.SwitchButton.Tooltip": "Touche permettant aux manettes de basculer entre les interfaces supérieure et inférieure."
+ "GMCM.LimitToCurrentLocation.Tooltip": "Affiche uniquement les coffres dans l'emplacement actuel au lieu de tous les emplacements",
+ "GMCM.IncludeFridge.Name": "Réfrigérateur de la cuisine",
+ "GMCM.IncludeFridge.Tooltip": "Inclut le réfrigérateur de la cuisine de la ferme",
+ "GMCM.IncludeMiniFridges.Name": "Mini-réfrigérateurs",
+ "GMCM.IncludeMiniFridges.Tooltip": "Inclut les objets mini-réfrigérateurs placés n'importe où",
+ "GMCM.IncludeShippingBin.Name": "Bac d'expédition",
+ "GMCM.IncludeShippingBin.Tooltip": "Inclut le bac d'expédition de la ferme comme contenant de stockage",
+ "GMCM.UnrestrictedShippingBin.Name": "Accès complet au bac d'expédition",
+ "GMCM.UnrestrictedShippingBin.Tooltip": "Permet de récupérer N'IMPORTE QUEL objet du bac d'expédition, pas seulement le dernier expédié. Désactiver pour ne récupérer que le dernier objet.",
+ "GMCM.IncludeMiniShippingBins.Name": "Mini-bacs d'expédition",
+ "GMCM.IncludeMiniShippingBins.Tooltip": "Inclut les objets mini-bacs d'expédition placés n'importe où",
+ "GMCM.IncludeJunimoChests.Name": "Coffres Junimo",
+ "GMCM.IncludeJunimoChests.Tooltip": "Inclut les coffres Junimo des bâtiments Junimo",
+ "GMCM.IncludeAutoGrabbers.Name": "Récolteurs automatiques",
+ "GMCM.IncludeAutoGrabbers.Tooltip": "Inclut les objets à l'intérieur des machines récolteurs automatiques",
+
+ // Search & Filter
+ "GMCM.FilterItems.Name": "Rechercher par nom d'objet",
+ "GMCM.FilterItems.Tooltip": "Filtre les coffres basés sur les noms des objets lors de la recherche",
+ "GMCM.FilterItemsCategory.Name": "Rechercher par catégorie",
+ "GMCM.FilterItemsCategory.Tooltip": "Filtre les coffres basés sur les catégories d'objets lors de la recherche",
+ "GMCM.FilterItemsDescription.Name": "Rechercher par description",
+ "GMCM.FilterItemsDescription.Tooltip": "Filtre les coffres basés sur les descriptions d'objets lors de la recherche",
+
+ // Sorting
+ "GMCM.SecondarySortingPriority.Name": "Ordre de tri par coordonnées",
+ "GMCM.SecondarySortingPriority.Tooltip": "Lors du tri par emplacement/nom, comment les coffres au même endroit sont-ils ordonnés ? 'Y' trie de haut en bas d'abord, 'X' trie de gauche à droite d'abord.",
+ "GMCM.SecondarySortingPriority.X": "X (Gauche à Droite)",
+ "GMCM.SecondarySortingPriority.Y": "Y (Haut en Bas)",
+
+ // Controls
+ "GMCM.KeyboardMenuKey.Name": "Touche d'ouverture du menu (Clavier)",
+ "GMCM.KeyboardMenuKey.Tooltip": "Appuyez sur cette touche du clavier pour ouvrir le menu All Chests Menu",
+ "GMCM.ControllerMenuButton.Name": "Bouton d'ouverture du menu (Manette)",
+ "GMCM.ControllerMenuButton.Tooltip": "Appuyez sur ce bouton de la manette pour ouvrir le menu All Chests Menu",
+ "GMCM.ModToOpen.Name": "Exiger la touche modificatrice",
+ "GMCM.ModToOpen.Tooltip": "Si activé, vous devez maintenir la touche modificatrice tout en appuyant sur la touche/bouton d'ouverture du menu",
+ "GMCM.ModKey.Name": "Touche modificatrice",
+ "GMCM.ModKey.Tooltip": "Maintenez cette touche en cliquant pour transférer des objets vers votre inventaire au lieu de les ramasser. Également utilisée pour ouvrir le menu si 'Exiger la touche modificatrice' est activé.",
+ "GMCM.ModKey2.Name": "Touche de transfert d'objets identiques",
+ "GMCM.ModKey2.Tooltip": "Maintenez cette touche en utilisant les boutons 'Tout déposer' ou 'Tout prendre' pour ne transférer que les objets correspondant à ce qui est déjà dans l'inventaire cible.",
+ "GMCM.SwitchButton.Name": "Touche de commutation du contrôleur",
+ "GMCM.SwitchButton.Tooltip": "Bouton pour les utilisateurs de manette pour basculer le focus entre la liste des coffres (haut) et l'inventaire du joueur (bas)"
}
diff --git a/AllChestsMenu/i18n/pt-BR.json b/AllChestsMenu/i18n/pt-BR.json
new file mode 100644
index 00000000..3fd3d132
--- /dev/null
+++ b/AllChestsMenu/i18n/pt-BR.json
@@ -0,0 +1,112 @@
+{
+ "filter": "Filtro",
+ "name": "Nome",
+ "fridge": "Geladeira",
+ "open": "Abrir",
+ "put": "Colocar",
+ "take": "Pegar",
+ "rename": "Renomear",
+ "target": "Alvo",
+ "sort": "Ordenar por",
+ "consolidate": "Consolidar",
+ "consolidate-tooltip": "Mesclar itens duplicados entre os baús",
+ "sort-all": "Ordenar Todos",
+ "sort-all-tooltip": "Ordenar todos os itens em todos os baús",
+ "consolidate-menu-title": "Consolidar Itens",
+ "item-found-in": "Itens para consolidar: {0}",
+ "select-destination": "Selecione o destino:",
+ "not-enough-space": "Espaço insuficiente no baú selecionado!",
+ "consolidation-complete": "Itens consolidados!",
+ "no-duplicates": "Nenhum item duplicado encontrado para consolidar.",
+ "filter-item-name": "Nome do Item",
+ "filter-chest-label": "Nome do Baú",
+ "filter-item-category": "Categoria do Item",
+ "filter-item-description": "Descrição do Item",
+ "filter-location": "Localização",
+ "filter-location-select": "Selecionar Localizações",
+ "category-all": "Todos",
+ "category-none": "Nenhum",
+ "clear-filters": "Limpar Filtros",
+ "filter-active": "Filtros Ativos",
+ "filter-enable-chest-label": "Nome do Baú",
+ "filter-enable-item-name": "Nome do Item",
+ "filter-enable-category": "Categoria",
+ "filter-enable-description": "Descrição",
+ "filter-enable-location": "Localização",
+ "sort-LA": "Localização (Asc)",
+ "sort-LD": "Localização (Desc)",
+ "sort-NA": "Nome (Asc)",
+ "sort-ND": "Nome (Desc)",
+ "sort-CA": "Capacidade (Asc)",
+ "sort-CD": "Capacidade (Desc)",
+ "sort-IA": "Itens (Asc)",
+ "sort-ID": "Itens (Desc)",
+ // GMCM Section Titles
+ "GMCM.Section.General.Name": "Geral",
+ "GMCM.Section.General.Desc": "Configurações básicas do mod e atalhos de teclado",
+ "GMCM.Section.Containers.Name": "Contêineres",
+ "GMCM.Section.Containers.Desc": "Escolha quais tipos de armazenamento aparecem no menu",
+ "GMCM.Section.Filter.Name": "Busca e Filtro",
+ "GMCM.Section.Filter.Desc": "Configure como a caixa de busca filtra os itens",
+ "GMCM.Section.Sorting.Name": "Ordenação",
+ "GMCM.Section.Sorting.Desc": "Alterar como os baús são ordenados na lista",
+ "GMCM.Section.Controls.Name": "Controles",
+ "GMCM.Section.Controls.Desc": "Binds de teclado e controle",
+ "GMCM.Section.KeyboardControls.Name": "Teclado",
+ "GMCM.Section.KeyboardControls.Desc": "Configurações de teclado para abrir o menu e atalhos de transferência",
+ "GMCM.Section.ControllerControls.Name": "Controle",
+ "GMCM.Section.ControllerControls.Desc": "Configurações de controle para abrir o menu e navegação",
+ // General
+ "GMCM.ModEnabled.Name": "Ativar Mod",
+ "GMCM.ModEnabled.Tooltip": "Ligar ou desligar o mod All Chests Menu",
+ // Containers
+ "GMCM.LimitToCurrentLocation.Name": "Apenas Localização Atual",
+ "GMCM.LimitToCurrentLocation.Tooltip": "Mostrar apenas baús na localização atual em vez de todas as localizações",
+ "GMCM.IncludeFridge.Name": "Incluir Geladeira",
+ "GMCM.IncludeFridge.Tooltip": "Incluir a geladeira na cozinha da casa",
+ "GMCM.IncludeMiniFridges.Name": "Incluir Mini-Geladeiras",
+ "GMCM.IncludeMiniFridges.Tooltip": "Incluir objetos de mini-geladeira colocados em qualquer lugar",
+ "GMCM.IncludeShippingBin.Name": "Incluir Caixa de Envio",
+ "GMCM.IncludeShippingBin.Tooltip": "Incluir a caixa de envio da fazenda como um contêiner de armazenamento",
+ "GMCM.UnrestrictedShippingBin.Name": "Acesso Completo à Caixa de Envio",
+ "GMCM.UnrestrictedShippingBin.Tooltip": "Permitir recuperar QUALQUER item da caixa de envio, não apenas o enviado mais recentemente. Desative para recuperar apenas o último item.",
+ "GMCM.IncludeMiniShippingBins.Name": "Incluir Mini-Caixas de Envio",
+ "GMCM.IncludeMiniShippingBins.Tooltip": "Incluir objetos de mini-caixa de envio colocados em qualquer lugar",
+ "GMCM.IncludeJunimoChests.Name": "Incluir Baús Junimo",
+ "GMCM.IncludeJunimoChests.Tooltip": "Incluir baús Junimo do edifício Junimo Hut",
+ "GMCM.IncludeAutoGrabbers.Name": "Incluir Auto-Coletores",
+ "GMCM.IncludeAutoGrabbers.Tooltip": "Incluir itens dentro das máquinas auto-coletoras",
+ // Search & Filter
+ "GMCM.FilterItems.Name": "Buscar por Nome do Item",
+ "GMCM.FilterItems.Tooltip": "Filtrar baús com base nos nomes dos itens ao buscar",
+ "GMCM.FilterItemsCategory.Name": "Buscar por Categoria",
+ "GMCM.FilterItemsCategory.Tooltip": "Filtrar baús com base nas categorias dos itens ao buscar",
+ "GMCM.FilterItemsDescription.Name": "Buscar por Descrição",
+ "GMCM.FilterItemsDescription.Tooltip": "Filtrar baús com base nas descrições dos itens ao buscar",
+ // Sorting
+ "GMCM.SecondarySortingPriority.Name": "Ordem de Coordenadas na Ordenação",
+ "GMCM.SecondarySortingPriority.Tooltip": "Ao ordenar por localização/nome, como os baús no mesmo lugar devem ser ordenados? 'Y' ordena de cima para baixo primeiro, 'X' ordena da esquerda para a direita primeiro.",
+ "GMCM.SecondarySortingPriority.X": "X (Esquerda para Direita)",
+ "GMCM.SecondarySortingPriority.Y": "Y (Cima para Baixo)",
+ // Controls
+ "GMCM.KeyboardRequireModifierToOpen.Name": "Exigir Mod. (Teclado)",
+ "GMCM.KeyboardRequireModifierToOpen.Tooltip": "Se ativado, abrir com teclado exige a tecla modificadora do teclado abaixo",
+ "GMCM.KeyboardOpenModifierKey.Name": "Tecla Modificadora para Abrir (Teclado)",
+ "GMCM.KeyboardOpenModifierKey.Tooltip": "Tecla do teclado usada como modificador quando 'Exigir Modificador para Abrir (Teclado)' estiver ativo",
+ "GMCM.KeyboardMenuKey.Name": "Tecla de Abrir Menu (Teclado)",
+ "GMCM.KeyboardMenuKey.Tooltip": "Pressione esta tecla do teclado para abrir o All Chests Menu",
+ "GMCM.TransferModifierKey.Name": "Tecla Modificadora de Transferência",
+ "GMCM.TransferModifierKey.Tooltip": "Segure esta tecla ao clicar para transferir itens para o seu inventário em vez de pegá-los.",
+ "GMCM.ModKey2.Name": "Tecla de Transferência de Mesmos Itens",
+ "GMCM.ModKey2.Tooltip": "Segure esta tecla ao usar os botões 'Colocar Tudo' ou 'Pegar Tudo' para transferir apenas itens que correspondem ao que já está no inventário alvo.",
+ "GMCM.ControllerRequireModifierToOpen.Name": "Exigir Mod. (Controle)",
+ "GMCM.ControllerRequireModifierToOpen.Tooltip": "Se ativado, abrir com controle exige o botão modificador do controle abaixo",
+ "GMCM.ControllerOpenModifierButton.Name": "Botão Mod. de Abertura (Controle)",
+ "GMCM.ControllerOpenModifierButton.Tooltip": "Botão do controle usado como modificador quando 'Exigir Modificador para Abrir (Controle)' estiver ativo",
+ "GMCM.ControllerMenuButton.Name": "Botão de Abrir Menu (Controle)",
+ "GMCM.ControllerMenuButton.Tooltip": "Pressione este botão do controle para abrir o All Chests Menu",
+ "GMCM.SwitchButton.Name": "Botão de Troca do Controle",
+ "GMCM.SwitchButton.Tooltip": "Botão para usuários de controle trocarem o foco entre a lista de baús (cima) e o inventário do jogador (baixo)",
+ "GMCM.EnableControllerKeyboard.Name": "Ativar Teclado no Controle",
+ "GMCM.EnableControllerKeyboard.Tooltip": "Abre um teclado virtual ao selecionar um campo de texto usando controle."
+}
diff --git a/AllChestsMenu/i18n/ru.json b/AllChestsMenu/i18n/ru.json
index fa9ac33e..ff3a9fff 100644
--- a/AllChestsMenu/i18n/ru.json
+++ b/AllChestsMenu/i18n/ru.json
@@ -1,4 +1,4 @@
-{
+{
"filter": "Фильтр",
"name": "Название",
"fridge": "Холодильник",
@@ -8,6 +8,31 @@
"rename": "Переименовать",
"target": "Цель",
"sort": "Сортировать по",
+ "consolidate": "Консолидировать",
+ "consolidate-tooltip": "Объединить дубликаты предметов в сундуках",
+ "sort-all": "Сортировать Всё",
+ "sort-all-tooltip": "Сортировать все предметы во всех сундуках",
+ "consolidate-menu-title": "Консолидация Предметов",
+ "item-found-in": "Найдено в {0} сундуках:",
+ "select-destination": "Выберите назначение:",
+ "not-enough-space": "Недостаточно места в выбранном сундуке!",
+ "consolidation-complete": "Предметы консолидированы!",
+ "no-duplicates": "Не найдено дубликатов для консолидации.",
+ "filter-item-name": "Название предмета",
+ "filter-chest-label": "Метка сундука",
+ "filter-item-category": "Категория предмета",
+ "filter-item-description": "Описание предмета",
+ "filter-location": "Местоположение",
+ "filter-location-select": "Выбрать местоположения",
+ "category-all": "Все",
+ "category-none": "Нет",
+ "clear-filters": "Очистить фильтры",
+ "filter-active": "Фильтры активны",
+ "filter-enable-chest-label": "Название сундука",
+ "filter-enable-item-name": "Название предмета",
+ "filter-enable-category": "Категория",
+ "filter-enable-description": "Описание",
+ "filter-enable-location": "Местоположение",
"sort-LA": "Местоположение (Возр.)",
"sort-LD": "Местоположение (Убыв.)",
"sort-NA": "Название (Возр.)",
@@ -17,15 +42,65 @@
"sort-IA": "Предметы (Возр.)",
"sort-ID": "Предметы (Убыв.)",
- // GMCM
- "GMCM.ModEnabled.Name": "Мод включён?",
+ // GMCM Section Titles
+ "GMCM.Section.General.Name": "Основное",
+ "GMCM.Section.General.Desc": "Базовые настройки мода",
+ "GMCM.Section.Containers.Name": "Контейнеры",
+ "GMCM.Section.Containers.Desc": "Выберите, какие типы хранилищ отображать в меню",
+ "GMCM.Section.Filter.Name": "Поиск и Фильтры",
+ "GMCM.Section.Filter.Desc": "Настройка фильтрации предметов в строке поиска",
+ "GMCM.Section.Sorting.Name": "Сортировка",
+ "GMCM.Section.Sorting.Desc": "Изменение способа сортировки сундуков в списке",
+ "GMCM.Section.Controls.Name": "Управление",
+ "GMCM.Section.Controls.Desc": "Настройки клавиатуры и контроллера",
+
+ // General
+ "GMCM.ModEnabled.Name": "Включить мод",
+ "GMCM.ModEnabled.Tooltip": "Включить или выключить мод All Chests Menu",
+
+ // Containers
"GMCM.LimitToCurrentLocation.Name": "Только текущая локация",
- "GMCM.MenuKey.Name": "Клавиша меню",
- "GMCM.ModToOpen.Name": "Требование клавиши для открытия",
- "GMCM.ModKey.Name": "Клавиша мода",
- "GMCM.ModKey.Tooltip": "Удерживайте, чтобы начать работу.",
- "GMCM.ModKey2.Name": "Клавиша мода 2",
- "GMCM.ModKey2.Tooltip": "Удерживайте, чтобы переместить только схожие предметы при обмене.",
- "GMCM.SwitchButton.Name": "Кнопка переключения",
- "GMCM.SwitchButton.Tooltip": "Для контроллеров. Для переключения между верхним и нижним интерфейсом."
+ "GMCM.LimitToCurrentLocation.Tooltip": "Показывать сундуки только в текущей локации вместо всех локаций",
+ "GMCM.IncludeFridge.Name": "Кухонный холодильник",
+ "GMCM.IncludeFridge.Tooltip": "Включать холодильник на кухне фермы",
+ "GMCM.IncludeMiniFridges.Name": "Мини-холодильники",
+ "GMCM.IncludeMiniFridges.Tooltip": "Включать объекты мини-холодильников, установленные где угодно",
+ "GMCM.IncludeShippingBin.Name": "Грузовой ящик",
+ "GMCM.IncludeShippingBin.Tooltip": "Включать грузовой ящик фермы как контейнер для хранения",
+ "GMCM.UnrestrictedShippingBin.Name": "Полный доступ к грузовому ящику",
+ "GMCM.UnrestrictedShippingBin.Tooltip": "Разрешить извлекать ЛЮБЫЕ предметы из грузового ящика, а не только последний отправленный. Отключите, чтобы извлекать только последний предмет.",
+ "GMCM.IncludeMiniShippingBins.Name": "Мини-грузовые ящики",
+ "GMCM.IncludeMiniShippingBins.Tooltip": "Включать объекты мини-грузовых ящиков, установленные где угодно",
+ "GMCM.IncludeJunimoChests.Name": "Сундуки Джунимо",
+ "GMCM.IncludeJunimoChests.Tooltip": "Включать сундуки Джунимо из зданий Джунимо",
+ "GMCM.IncludeAutoGrabbers.Name": "Авто-сборщики",
+ "GMCM.IncludeAutoGrabbers.Tooltip": "Включать предметы внутри машин авто-сборщиков",
+
+ // Search & Filter
+ "GMCM.FilterItems.Name": "Искать по названию предмета",
+ "GMCM.FilterItems.Tooltip": "Фильтровать сундуки по названиям предметов при поиске",
+ "GMCM.FilterItemsCategory.Name": "Искать по категории",
+ "GMCM.FilterItemsCategory.Tooltip": "Фильтровать сундуки по категориям предметов при поиске",
+ "GMCM.FilterItemsDescription.Name": "Искать по описанию",
+ "GMCM.FilterItemsDescription.Tooltip": "Фильтровать сундуки по описаниям предметов при поиске",
+
+ // Sorting
+ "GMCM.SecondarySortingPriority.Name": "Порядок сортировки по координатам",
+ "GMCM.SecondarySortingPriority.Tooltip": "При сортировке по местоположению/названию, как упорядочивать сундуки в одном месте? 'Y' сортирует сверху вниз сначала, 'X' сортирует слева направо сначала.",
+ "GMCM.SecondarySortingPriority.X": "X (Слева направо)",
+ "GMCM.SecondarySortingPriority.Y": "Y (Сверху вниз)",
+
+ // Controls
+ "GMCM.KeyboardMenuKey.Name": "Клавиша открытия меню (Клавиатура)",
+ "GMCM.KeyboardMenuKey.Tooltip": "Нажмите эту клавишу на клавиатуре, чтобы открыть меню All Chests Menu",
+ "GMCM.ControllerMenuButton.Name": "Кнопка открытия меню (Контроллер)",
+ "GMCM.ControllerMenuButton.Tooltip": "Нажмите эту кнопку на контроллере, чтобы открыть меню All Chests Menu",
+ "GMCM.ModToOpen.Name": "Требовать модификатор",
+ "GMCM.ModToOpen.Tooltip": "Если включено, нужно удерживать модификатор при нажатии клавиши/кнопки открытия меню",
+ "GMCM.ModKey.Name": "Модификатор",
+ "GMCM.ModKey.Tooltip": "Удерживайте при клике для передачи предметов в инвентарь вместо поднятия. Также используется для открытия меню, если включено 'Требовать модификатор'.",
+ "GMCM.ModKey2.Name": "Клавиша передачи одинаковых",
+ "GMCM.ModKey2.Tooltip": "Удерживайте при использовании кнопок 'Всё поместить' или 'Всё взять' для передачи только предметов, соответствующих содержимому целевого инвентаря.",
+ "GMCM.SwitchButton.Name": "Кнопка переключения контроллера",
+ "GMCM.SwitchButton.Tooltip": "Кнопка для пользователей геймпада для переключения фокуса между списком сундуков (сверху) и инвентарём игрока (снизу)"
}
diff --git a/AllChestsMenu/out.txt b/AllChestsMenu/out.txt
new file mode 100644
index 00000000..4c376eb3
--- /dev/null
+++ b/AllChestsMenu/out.txt
@@ -0,0 +1,11 @@
+ Determinando os projetos a serem restaurados...
+ Todos os projetos estão atualizados para restauração.
+C:\Users\rodol\Desktop\mods\StardewValleyMods\AllChestsMenu\AllChestsMenu.cs(564,4): error CS1513: } esperada [C:\Users\rodol\Desktop\mods\StardewValleyMods\AllChestsMenu\AllChestsMenu.csproj]
+
+FALHA da compilação.
+
+C:\Users\rodol\Desktop\mods\StardewValleyMods\AllChestsMenu\AllChestsMenu.cs(564,4): error CS1513: } esperada [C:\Users\rodol\Desktop\mods\StardewValleyMods\AllChestsMenu\AllChestsMenu.csproj]
+ 0 Aviso(s)
+ 1 Erro(s)
+
+Tempo Decorrido 00:00:00.75