Skip to content
Open
Show file tree
Hide file tree
Changes from all 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