Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target/
17 changes: 17 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Contributing

## Building

Install JDK and Maven

Run:

```
mvn clean package
```

# Creative Tab List

Creative tab ordering is generated via a Fabric mod.

Example Mod: https://github.com/itzTheMeow/creative-tab-dumper
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ Modular Storage System is a comprehensive Minecraft storage plugin. It provides
### Sorting Options
- **Alphabetical Sort**: Default sorting mode, organizes items A-Z
- **Quantity Sort**: Click the name tag button to sort by item count (highest first)
- **Creative Menu Sort**: Click the button again to sort the same way the creative menu is organized
- **Persistent Settings**: Sort preference is saved per terminal location
![TerminalSortingFeature.png](media%2FTerminalSortingFeature.png)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.bukkit.block.Container;
import org.bukkit.scheduler.BukkitRunnable;
import org.jamesphbennett.modularstoragesystem.ModularStorageSystem;
import org.jamesphbennett.modularstoragesystem.gui.TerminalGUI.SortModes;

import java.util.Map;
import java.util.Set;
Expand All @@ -30,7 +31,7 @@ public class GUIManager {
private final Map<UUID, BukkitRunnable> searchTimeoutTasks = new ConcurrentHashMap<>();
private static final int SEARCH_TIMEOUT_SECONDS = 10;
private final Map<String, String> terminalSearchTerms = new ConcurrentHashMap<>();
private final Map<String, Boolean> terminalQuantitySort = new ConcurrentHashMap<>();
private final Map<String, SortModes> terminalSortMode = new ConcurrentHashMap<>();

private final Map<UUID, SecurityTerminalGUI> playersAwaitingPlayerInput = new ConcurrentHashMap<>();
private final Map<UUID, BukkitRunnable> playerInputTimeoutTasks = new ConcurrentHashMap<>();
Expand Down Expand Up @@ -364,20 +365,20 @@ public void setTerminalSearchTerm(Location terminalLocation, String searchTerm)
/**
* Get saved quantity sort setting for a terminal location
*/
public boolean getTerminalQuantitySort(Location terminalLocation) {
return terminalQuantitySort.getOrDefault(getTerminalKey(terminalLocation), false);
public SortModes getTerminalSortMode(Location terminalLocation) {
return terminalSortMode.getOrDefault(getTerminalKey(terminalLocation), SortModes.ALPHABETICAL);
}

/**
* Save quantity sort setting for a terminal location
* Save sort mode setting for a terminal location
*/
public void setTerminalQuantitySort(Location terminalLocation, boolean quantitySort) {
public void setTerminalSortMode(Location terminalLocation, SortModes sortMode) {
String key = getTerminalKey(terminalLocation);
if (quantitySort) {
terminalQuantitySort.put(key, true);
if (sortMode != SortModes.ALPHABETICAL) {
terminalSortMode.put(key, sortMode);
plugin.debugLog("Saved quantity sort setting for terminal at " + key);
} else {
terminalQuantitySort.remove(key);
terminalSortMode.remove(key);
plugin.debugLog("Cleared quantity sort setting for terminal at " + key + " (using default alphabetical)");
}
}
Expand Down Expand Up @@ -934,7 +935,7 @@ public void closeAllGUIs() {
playersAwaitingPlayerInput.clear();
playerInputTimeoutTasks.clear();
terminalSearchTerms.clear();
terminalQuantitySort.clear();
terminalSortMode.clear();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@

public class TerminalGUI implements Listener {

public enum SortModes {
ALPHABETICAL,
QUANTITY,
CREATIVE,
}

private final ModularStorageSystem plugin;
private final Location terminalLocation;
private final String networkId;
Expand All @@ -41,7 +47,7 @@ public class TerminalGUI implements Listener {
private boolean isSearchActive = false;

// Sorting functionality
private boolean isQuantitySortActive; // false = alphabetical, true = quantity
private SortModes sortMode;

// Click rate limiting to prevent DB spam - 200ms cooldown
private final Map<UUID, Long> clickCooldowns = new ConcurrentHashMap<>();
Expand All @@ -64,8 +70,8 @@ public TerminalGUI(ModularStorageSystem plugin, Location terminalLocation, Strin
}

// Check for saved sorting preference for this terminal location
this.isQuantitySortActive = plugin.getGUIManager().getTerminalQuantitySort(terminalLocation);
if (isQuantitySortActive) {
this.sortMode = plugin.getGUIManager().getTerminalSortMode(terminalLocation);
if (sortMode != SortModes.ALPHABETICAL) {
plugin.debugLog("debug.gui.sort-saved", "key", terminalLocation.toString());
}

Expand Down Expand Up @@ -127,28 +133,27 @@ private void updateSearchButton() {
}

private void updateSortingButton() {
ItemStack sortButton = new ItemStack(Material.NAME_TAG);
ItemMeta sortMeta = sortButton.getItemMeta();

if (isQuantitySortActive) {
// Quantity sorting is active
sortMeta.displayName(plugin.getMessageManager().getMessageComponent(null, "gui.terminal.sorting.quantity"));
List<Component> sortLore = new ArrayList<>();
sortLore.add(Component.empty());
sortLore.add(plugin.getMessageManager().getMessageComponent(null, "gui.terminal.sorting.to-alphabetical"));
sortMeta.lore(sortLore);
ItemStack sortButton = new ItemStack(switch (sortMode) {
case ALPHABETICAL -> Material.NAME_TAG;
case QUANTITY -> Material.HOPPER;
case CREATIVE -> Material.STRUCTURE_BLOCK;
});

// Add glowing effect
sortMeta.addEnchant(Enchantment.UNBREAKING, 1, true);
sortMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
} else {
// Alphabetical sorting is active (default)
sortMeta.displayName(plugin.getMessageManager().getMessageComponent(null, "gui.terminal.sorting.alphabetical"));
List<Component> sortLore = new ArrayList<>();
sortLore.add(Component.empty());
sortLore.add(plugin.getMessageManager().getMessageComponent(null, "gui.terminal.sorting.to-quantity"));
sortMeta.lore(sortLore);
}
ItemMeta sortMeta = sortButton.getItemMeta();
sortMeta.displayName(plugin.getMessageManager().getMessageComponent(null, switch (sortMode) {
case ALPHABETICAL -> "gui.terminal.sorting.alphabetical";
case QUANTITY -> "gui.terminal.sorting.quantity";
case CREATIVE -> "gui.terminal.sorting.creative";
}));

List<Component> sortLore = new ArrayList<>();
sortLore.add(Component.empty());
sortLore.add(plugin.getMessageManager().getMessageComponent(null, switch (sortMode) {
case ALPHABETICAL -> "gui.terminal.sorting.to-quantity";
case QUANTITY -> "gui.terminal.sorting.to-creative";
case CREATIVE -> "gui.terminal.sorting.to-alphabetical";
}));
sortMeta.lore(sortLore);

sortButton.setItemMeta(sortMeta);
inventory.setItem(50, sortButton); // Next to search button
Expand Down Expand Up @@ -211,8 +216,11 @@ private void updateNavigationItems() {
}

infoLore.add(plugin.getMessageManager().getMessageComponent(null, "gui.terminal.pagination.page-info", "current", (currentPage + 1), "total", maxPages));
String sortModeKey = isQuantitySortActive ? "gui.terminal.info.sort-mode-quantity" : "gui.terminal.info.sort-mode-alphabetical";
infoLore.add(plugin.getMessageManager().getMessageComponent(null, sortModeKey));
infoLore.add(plugin.getMessageManager().getMessageComponent(null, switch (sortMode) {
case ALPHABETICAL -> "gui.terminal.info.sort-mode-alphabetical";
case QUANTITY -> "gui.terminal.info.sort-mode-quantity";
case CREATIVE -> "gui.terminal.info.sort-mode-creative";
}));

// Show items on current page
int startIndex = currentPage * itemsPerPage;
Expand Down Expand Up @@ -255,7 +263,7 @@ private void loadItems() {
updateDisplayedItems();

String searchInfo = isSearchActive ? ", filtered to " + filteredItems.size() + " results" : "";
plugin.debugLog("debug.gui.items-loaded", "total", allItems.size(), "search", searchInfo, "sorting", (isQuantitySortActive ? "quantity" : "alphabetical"));
plugin.debugLog("debug.gui.items-loaded", "total", allItems.size(), "search", searchInfo, "sorting", sortMode.name());
} catch (Exception e) {
plugin.getLogger().severe("Error loading terminal items: " + e.getMessage());
}
Expand All @@ -265,22 +273,30 @@ private void loadItems() {
* Apply sorting to the items list based on current sort mode
*/
private void applySorting() {
if (isQuantitySortActive) {
// Sort by quantity (descending) - most items first
allItems.sort((a, b) -> {
int quantityCompare = Integer.compare(b.quantity(), a.quantity());
if (quantityCompare != 0) {
return quantityCompare;
}
// If quantities are equal, fall back to alphabetical
return a.itemStack().getType().name().compareTo(b.itemStack().getType().name());
});
plugin.debugLog("debug.gui.sorting-applied", "type", "quantity (most items first)");
} else {
// Sort alphabetically by item type name (default)
allItems.sort(Comparator.comparing(item -> item.itemStack().getType().name()));
plugin.debugLog("debug.gui.sorting-applied", "type", "alphabetical");
switch (sortMode) {
case CREATIVE:
// Sort using the order set in creative_menu.txt
allItems.sort(Comparator.comparingInt(item ->
plugin.getConfigManager().getCreativeMenuOrder(item.itemStack().getType())));
break;
case QUANTITY:
// Sort by quantity (descending) - most items first
allItems.sort((a, b) -> {
int quantityCompare = Integer.compare(b.quantity(), a.quantity());
if (quantityCompare != 0) {
return quantityCompare;
}
// If quantities are equal, fall back to alphabetical
return a.itemStack().getType().name().compareTo(b.itemStack().getType().name());
});
break;
case ALPHABETICAL:
default:
// Sort alphabetically by item type name (default)
allItems.sort(Comparator.comparing(item -> item.itemStack().getType().name()));
break;
}
plugin.debugLog("debug.gui.sorting-applied", "type", sortMode.name());
}

private void applySearchFilter() {
Expand Down Expand Up @@ -323,7 +339,7 @@ private void applySearchFilter() {
return scoreCompare;
}
// If scores are equal, use current sort mode as tiebreaker
if (isQuantitySortActive) {
if (sortMode == SortModes.QUANTITY) {
int quantityCompare = Integer.compare(b.item.quantity(), a.item.quantity());
if (quantityCompare != 0) {
return quantityCompare;
Expand Down Expand Up @@ -446,16 +462,21 @@ private ItemStack createDisplayItem(StoredItem storedItem) {
}

/**
* Toggle sorting mode between alphabetical and quantity-based
* Cycle sorting mode between the options
*/
public void toggleSorting() {
isQuantitySortActive = !isQuantitySortActive;
public void cycleSorting() {
// Cycle through modes
sortMode = switch (sortMode) {
case ALPHABETICAL -> SortModes.QUANTITY;
case QUANTITY -> SortModes.CREATIVE;
case CREATIVE -> SortModes.ALPHABETICAL;
};
this.currentPage = 0; // Reset to first page

// Save the sorting preference for this terminal
plugin.getGUIManager().setTerminalQuantitySort(terminalLocation, isQuantitySortActive);
plugin.getGUIManager().setTerminalSortMode(terminalLocation, sortMode);

plugin.debugLog("debug.gui.sorting-applied", "type", (isQuantitySortActive ? "quantity" : "alphabetical"));
plugin.debugLog("debug.gui.sorting-applied", "type", sortMode.name());

// Re-apply sorting to all items
applySorting();
Expand Down Expand Up @@ -537,7 +558,7 @@ public void refresh() {
// Store current search state and sorting mode
String savedSearchTerm = currentSearchTerm;
boolean wasSearchActive = isSearchActive;
boolean wasSortingByQuantity = isQuantitySortActive;
SortModes savedSortMode = sortMode;

loadItems();

Expand All @@ -547,8 +568,8 @@ public void refresh() {
}

// Restore sorting mode
if (wasSortingByQuantity != isQuantitySortActive) {
isQuantitySortActive = wasSortingByQuantity;
if (!savedSortMode.equals(sortMode)) {
sortMode = savedSortMode;
applySorting();
if (isSearchActive) {
applySearchFilter();
Expand All @@ -559,7 +580,7 @@ public void refresh() {
int itemCountAfter = allItems.size();
plugin.debugLog("Terminal refresh complete: " + itemCountBefore + " -> " + itemCountAfter + " item types" +
(wasSearchActive ? " (search preserved: '" + savedSearchTerm + "')" : "") +
" (sorting: " + (isQuantitySortActive ? "quantity" : "alphabetical") + ")");
" (sorting: " + sortMode.name() + ")");
}

@EventHandler
Expand Down Expand Up @@ -603,9 +624,12 @@ public void onInventoryClick(InventoryClickEvent event) {
if (slot == 50) {
event.setCancelled(true);

toggleSorting();
String messageKey = isQuantitySortActive ? "gui.terminal.sorting.changed-to-quantity" : "gui.terminal.sorting.changed-to-alphabetical";
player.sendMessage(plugin.getMessageManager().getMessageComponent(player, messageKey));
cycleSorting();
player.sendMessage(plugin.getMessageManager().getMessageComponent(player, switch (sortMode) {
case ALPHABETICAL -> "gui.terminal.sorting.changed-to-alphabetical";
case QUANTITY -> "gui.terminal.sorting.changed-to-quantity";
case CREATIVE -> "gui.terminal.sorting.changed-to-creative";
}));
return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
package org.jamesphbennett.modularstoragesystem.managers;

import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jamesphbennett.modularstoragesystem.ModularStorageSystem;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jamesphbennett.modularstoragesystem.ModularStorageSystem;

public class ConfigManager {

private final ModularStorageSystem plugin;
Expand All @@ -26,6 +31,7 @@ public class ConfigManager {
private int maxImporters;
private int exportTickInterval;
private Set<Material> blacklistedItems;
private Map<Material, Integer> creativeSortOrder;
private boolean requireUsePermission;
private boolean requireCraftPermission;

Expand Down Expand Up @@ -81,6 +87,7 @@ public void loadConfig() {
loadNetworkSettings();
loadStorageSettings();
loadBlacklistedItems();
loadCreativeMenuOrder();
loadPermissionSettings();
loadLoggingSettings();
loadDebugSettings();
Expand Down Expand Up @@ -122,6 +129,35 @@ private void loadRecipeSettings() {
showUnlockMessages = recipesConfig.getBoolean("settings.show_unlock_messages", false);
}

private void loadCreativeMenuOrder() {
creativeSortOrder = new HashMap<>();
File file = new File(plugin.getDataFolder(), "creative_menu.txt");

if (!file.exists()) {
plugin.saveResource("creative_menu.txt", false);
}

try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String line;
int index = 0;
while ((line = reader.readLine()) != null) {
String materialName = line.trim();
if (materialName.isEmpty()) continue;

Material material = Material.matchMaterial(materialName.replace("minecraft:", ""));
if (material != null && !creativeSortOrder.containsKey(material)) {
creativeSortOrder.put(material, index++);
}
}
} catch (IOException e) {
plugin.getLogger().warning("Failed to load creative_menu.txt: " + e.getMessage());
}
}

public int getCreativeMenuOrder(Material material) {
return creativeSortOrder.getOrDefault(material, Integer.MAX_VALUE);
}

private void loadNetworkSettings() {
maxNetworkBlocks = config.getInt("network.max_blocks", 128);
maxNetworkCables = config.getInt("network.max_cables", 800);
Expand Down
Loading