diff --git a/gradle.properties b/gradle.properties index d1d39a0..efc5129 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ yarn_mappings=1.20.6+build.3 loader_version=0.15.11 # Mod Properties -mod_version=1.5 +mod_version=1.6 maven_group=systems.brn archives_base_name=Server_storage diff --git a/src/main/java/systems/brn/server_storage/blocks/StorageBlock.java b/src/main/java/systems/brn/server_storage/blocks/StorageBlock.java index a3fc91a..5773dcd 100644 --- a/src/main/java/systems/brn/server_storage/blocks/StorageBlock.java +++ b/src/main/java/systems/brn/server_storage/blocks/StorageBlock.java @@ -27,12 +27,14 @@ import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import systems.brn.server_storage.ServerStorage; +import systems.brn.server_storage.lib.ConnectedChests; import systems.brn.server_storage.screens.StorageScreen; import java.util.List; import static systems.brn.server_storage.ServerStorage.STORAGE_BLOCK; import static systems.brn.server_storage.lib.StorageOperations.getCombinedInventoryFromChests; +import static systems.brn.server_storage.lib.StorageOperations.getConnectedChests; import static systems.brn.server_storage.lib.Util.generateBookContent; public class StorageBlock extends Block implements PolymerTexturedBlock { @@ -65,7 +67,9 @@ public class StorageBlock extends Block implements PolymerTexturedBlock { if (!world.isClient && !player.isSpectator()) { if (player.isSneaking() && player.getStackInHand(hand).getItem() == Items.WRITTEN_BOOK) { ItemStack book = player.getStackInHand(hand); - Inventory inventory = getCombinedInventoryFromChests(world, pos); + ConnectedChests chests = getConnectedChests(world, pos); + List inventories = chests.getInventories(); + Inventory inventory = getCombinedInventoryFromChests(inventories, true); List> generatedContent = generateBookContent(inventory); book.set(DataComponentTypes.WRITTEN_BOOK_CONTENT, new WrittenBookContentComponent( RawFilteredPair.of("Item Listing"), diff --git a/src/main/java/systems/brn/server_storage/lib/ConnectedChests.java b/src/main/java/systems/brn/server_storage/lib/ConnectedChests.java new file mode 100644 index 0000000..b2e8797 --- /dev/null +++ b/src/main/java/systems/brn/server_storage/lib/ConnectedChests.java @@ -0,0 +1,48 @@ +package systems.brn.server_storage.lib; + +import net.minecraft.inventory.Inventory; + +import java.util.List; + +public class ConnectedChests { + private final List inventories; + + private final int containerCount; + private int containerSlots = 0; + private int containerUsedSlots = 0; + private int containerFreeSlots = 0; + + ConnectedChests(List inventories){ + this.inventories = inventories; + this.containerCount = inventories.size(); + for (Inventory inventory : inventories) { + containerSlots += inventory.size(); + for (int slot = 0; slot < inventory.size(); slot++) { + if (inventory.getStack(slot).isEmpty()) { + containerFreeSlots++; + } + else { + containerUsedSlots++; + } + } + assert containerSlots == containerUsedSlots + containerFreeSlots; + } + } + + public int getContainerCount() { + return containerCount; + } + public int getContainerSlots() { + return containerSlots; + } + public int getContainerUsedSlots() { + return containerUsedSlots; + } + public int getContainerFreeSlots() { + return containerFreeSlots; + } + + public List getInventories() { + return inventories; + } +} diff --git a/src/main/java/systems/brn/server_storage/lib/PagedGui.java b/src/main/java/systems/brn/server_storage/lib/PagedGui.java index 5cbced8..8e78ab3 100644 --- a/src/main/java/systems/brn/server_storage/lib/PagedGui.java +++ b/src/main/java/systems/brn/server_storage/lib/PagedGui.java @@ -27,6 +27,10 @@ public abstract class PagedGui extends SimpleGui { public static final String GUI_NEXT_PAGE = "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzg2MTg1YjFkNTE5YWRlNTg1ZjE4NGMzNGYzZjNlMjBiYjY0MWRlYjg3OWU4MTM3OGU0ZWFmMjA5Mjg3In19fQ"; public static final String GUI_NEXT_PAGE_BLOCKED = "ewogICJ0aW1lc3RhbXAiIDogMTY0MDYxNjExMDQ4OCwKICAicHJvZmlsZUlkIiA6ICIxZjEyNTNhYTVkYTQ0ZjU5YWU1YWI1NmFhZjRlNTYxNyIsCiAgInByb2ZpbGVOYW1lIiA6ICJOb3RNaUt5IiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzdlNTc3MjBhNDg3OGM4YmNhYjBlOWM5YzQ3ZDllNTUxMjhjY2Q3N2JhMzQ0NWE1NGE5MWUzZTFlMWEyNzM1NmUiLAogICAgICAibWV0YWRhdGEiIDogewogICAgICAgICJtb2RlbCIgOiAic2xpbSIKICAgICAgfQogICAgfQogIH0KfQ=="; public static final String GUI_QUESTION_MARK = "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYmM4ZWExZjUxZjI1M2ZmNTE0MmNhMTFhZTQ1MTkzYTRhZDhjM2FiNWU5YzZlZWM4YmE3YTRmY2I3YmFjNDAifX19"; + public static final String GUI_REFRESH = "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDc1ZDNkYjAzZGMyMWU1NjNiMDM0MTk3ZGE0MzViNzllY2ZlZjRiOGUyZWNkYjczMGUzNzBjMzE2NjI5ZDM2ZiJ9fX0="; + public static final String GUI_A = "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNGU0MTc0ODEyMTYyNmYyMmFlMTZhNGM2NjRjNzMwMWE5ZjhlYTU5MWJmNGQyOTg4ODk1NzY4MmE5ZmRhZiJ9fX0="; + public static final String GUI_1 = "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2E1MTZmYmFlMTYwNThmMjUxYWVmOWE2OGQzMDc4NTQ5ZjQ4ZjZkNWI2ODNmMTljZjVhMTc0NTIxN2Q3MmNjIn19fQ=="; + public static final int PAGE_SIZE = 9 * 5; protected final Runnable closeCallback; @@ -111,10 +115,21 @@ public abstract class PagedGui extends SimpleGui { protected DisplayElement getNavElement(int id) { return switch (id) { - case 1 -> DisplayElement.previousPage(this); - case 3 -> DisplayElement.nextPage(this); - case 5 -> this.search(); - case 7 -> DisplayElement.of( + case 0 -> DisplayElement.previousPage(this); + case 2 -> this.search(); + case 3 -> this.sorting(); + case 4 -> DisplayElement.of( + new GuiElementBuilder(Items.PLAYER_HEAD) + .setSkullOwner(GUI_REFRESH) + .setName(Text.translatable("selectServer.refresh").formatted(Formatting.WHITE)) + .hideDefaultTooltip().noDefaults() + .setCallback((x, y, z) -> { + playClickSound(this.player); + this.updateDisplay(); + }) + ); + case 6 -> DisplayElement.nextPage(this); + case 8 -> DisplayElement.of( new GuiElementBuilder(Items.STRUCTURE_VOID) .setName(Text.translatable(this.closeCallback != null ? "gui.back" : "mco.selectServer.close").formatted(Formatting.RED)) .hideDefaultTooltip().noDefaults() @@ -129,6 +144,8 @@ public abstract class PagedGui extends SimpleGui { protected abstract DisplayElement search(); + protected abstract DisplayElement sorting(); + public record DisplayElement(@Nullable GuiElementInterface element, @Nullable Slot slot) { private static final DisplayElement EMPTY = DisplayElement.of(new GuiElement(ItemStack.EMPTY, GuiElementInterface.EMPTY_CALLBACK)); diff --git a/src/main/java/systems/brn/server_storage/lib/StorageOperations.java b/src/main/java/systems/brn/server_storage/lib/StorageOperations.java index 94f10db..bb38952 100644 --- a/src/main/java/systems/brn/server_storage/lib/StorageOperations.java +++ b/src/main/java/systems/brn/server_storage/lib/StorageOperations.java @@ -1,6 +1,8 @@ package systems.brn.server_storage.lib; import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; import net.minecraft.inventory.Inventory; import net.minecraft.inventory.SimpleInventory; import net.minecraft.item.Item; @@ -14,13 +16,13 @@ import systems.brn.server_storage.blocks.StorageBlock; import java.util.*; public class StorageOperations { - public static List getConnectedChests(World world, BlockPos startPos) { + public static ConnectedChests getConnectedChests(World world, BlockPos startPos) { List inventories = new ArrayList<>(); Set visited = new HashSet<>(); getConnectedChestsHelper(world, startPos, inventories, visited); - return inventories; + return new ConnectedChests(inventories); } private static void getConnectedChestsHelper(World world, BlockPos pos, List inventories, Set visited) { @@ -42,11 +44,10 @@ public class StorageOperations { } } - public static boolean tryPutItemStackIntoChests(World world, BlockPos startPos, ItemStack stack) { - List chests = getConnectedChests(world, startPos); + public static boolean tryPutItemStackIntoChests(List inventories, ItemStack stack) { // Iterate over each chest to try and insert the ItemStack - for (Inventory chest : chests) { + for (Inventory chest : inventories) { stack = insertStackIntoInventory(chest, stack); if (stack.isEmpty()) { return true; @@ -88,19 +89,37 @@ public class StorageOperations { return stack; } - public static boolean tryRemoveItemStackFromChests(World world, BlockPos startPos, ItemStack stackToRemove) { - List chests = getConnectedChests(world, startPos); + public static boolean canRemoveFromInventory(Inventory inventory, ItemStack stackToRemove) { + int remainingToRemove = stackToRemove.getCount(); + + for (int i = 0; i < inventory.size(); i++) { + ItemStack slotStack = inventory.getStack(i); + if (canCombine(slotStack, stackToRemove)) { + // If the slot contains the same item type + if (slotStack.getCount() >= remainingToRemove) { + // If the count in the slot is sufficient to remove the requested amount + return true; + } else { + // If the count in the slot is not sufficient, update remainingToRemove + remainingToRemove -= slotStack.getCount(); + } + } + } + // If no matching stack with sufficient count is found, return false + return false; + } + + public static void removeItemStackFromChests(List inventories, ItemStack stackToRemove) { int remainingToRemove = stackToRemove.getCount(); - for (Inventory chest : chests) { + for (Inventory chest : inventories) { remainingToRemove = removeFromInventory(chest, stackToRemove, remainingToRemove); if (remainingToRemove <= 0) { - return true; + return; } } - return remainingToRemove <= 0; } public static int removeFromInventory(Inventory inventory, ItemStack stackToRemove, int remainingToRemove) { @@ -124,71 +143,92 @@ public class StorageOperations { return remainingToRemove; } - public static SimpleInventory getCombinedInventoryFromChests(World world, BlockPos startPos) { - List inventories = getConnectedChests(world, startPos); - int size = 0; - - // Create a map to keep track of item stacks and their counts - Map itemStackMap = new HashMap<>(); - - // Loop through inventories to add items to the inventory + // Modify getCombinedInventoryFromChests to include item metadata and combine stacks + public static SimpleInventory getCombinedInventoryFromChests(Iterable inventories, boolean sortAlphabetically) { + Map itemStackMap = new HashMap<>(); for (Inventory inventory : inventories) { - int inventorySize = inventory.size(); - for (int i = 0; i < inventorySize; i++) { + for (int i = 0; i < inventory.size(); i++) { ItemStack stack = inventory.getStack(i); if (!stack.isEmpty()) { - Item item = stack.getItem(); - // Check if the item type already exists in the map - int count = stack.getCount(); - if (itemStackMap.containsKey(item)) { - // If yes, increment the count - count += itemStackMap.get(item); - itemStackMap.put(item, count); - } else { - // If not, add the item type to the map with count 1 - itemStackMap.put(item, count); + // Check if there's an existing ItemStack with the same item and metadata + boolean found = false; + for (Map.Entry entry : itemStackMap.entrySet()) { + ItemStack existingStack = entry.getKey(); + if (ItemStack.areItemsAndComponentsEqual(stack, existingStack)) { + int count = entry.getValue() + stack.getCount(); + itemStackMap.put(existingStack, count); + found = true; + break; + } + } + // If no existing stack with the same item and metadata, add a new entry + if (!found) { + ItemStack copiedStack = stack.copy(); + itemStackMap.put(copiedStack, stack.getCount()); } } } - size += inventorySize; } - - // Populate the inventory with item stacks from the map - - return getSimpleInventory(size, itemStackMap); + return getSimpleInventory(itemStackMap.size(), itemStackMap, sortAlphabetically); } - public static @NotNull SimpleInventory getSimpleInventory(int size, Map itemStackMap) { + // Modify getSimpleInventory to include item metadata and sort conditionally + public static @NotNull SimpleInventory getSimpleInventory(int size, Map itemStackMap, boolean sortAlphabetically) { + TreeMap sortedMap; + + if (sortAlphabetically) { + // Sort alphabetically by item name + sortedMap = new TreeMap<>(new Comparator() { + @Override + public int compare(ItemStack o1, ItemStack o2) { + String name1 = String.valueOf(o1.getItem()); + String name2 = String.valueOf(o2.getItem()); + return name1.compareToIgnoreCase(name2); + } + }); + } else { + // Sort by count in descending order + sortedMap = new TreeMap<>(new Comparator() { + @Override + public int compare(ItemStack o1, ItemStack o2) { + int countCompare = Integer.compare(o2.getCount(), o1.getCount()); + // If counts are equal, compare items alphabetically by name + return countCompare == 0 ? String.valueOf(o1.getItem()).compareToIgnoreCase(String.valueOf(o2.getItem())) : countCompare; + } + }); + } + + sortedMap.putAll(itemStackMap); + SimpleInventory inv = new SimpleInventory(size); int invPointer = 0; - for (Map.Entry entry : itemStackMap.entrySet()) { - Item item = entry.getKey(); + for (Map.Entry entry : sortedMap.entrySet()) { + ItemStack stack = entry.getKey(); int count = entry.getValue(); - // Create a new ItemStack with the item type and the correct count - ItemStack stack = new ItemStack(item, count); - // Populate the inventory with the item stack - inv.heldStacks.set(invPointer, stack); + stack.setCount(count); + inv.heldStacks.set(invPointer, stack); //bypass stack maximum invPointer++; } return inv; } - public static SimpleInventory filterInventory(Inventory inventory, String query) { - Map itemStackMap = new HashMap<>(); + // Modify filterInventory to include item metadata + public static SimpleInventory filterInventory(Inventory inventory, String query, boolean sortAlphabetically) { + Map itemStackMap = new HashMap<>(); int itemCount = 0; for (int slot = 0; slot < inventory.size(); slot++) { ItemStack stack = inventory.getStack(slot); ItemStack filteredStack = filterStack(stack, query); if (!filteredStack.isEmpty()) { itemCount++; - // Count the occurrences of each item in the filtered inventory - Item item = filteredStack.getItem(); - itemStackMap.put(item, itemStackMap.getOrDefault(item, 0) + filteredStack.getCount()); + int count = itemStackMap.getOrDefault(filteredStack, 0) + filteredStack.getCount(); + itemStackMap.put(filteredStack, count); } } - return getSimpleInventory(itemCount, itemStackMap); + return getSimpleInventory(itemCount, itemStackMap, sortAlphabetically); } + // Modify filterStack to include item metadata public static ItemStack filterStack(ItemStack stack, String query) { Item item = stack.getItem(); if (item != null) { @@ -200,6 +240,26 @@ public class StorageOperations { return stack; } + // Method to check if an item can be inserted into a player's inventory + public static boolean canInsertItemIntoPlayerInventory(PlayerEntity player, ItemStack itemStack) { + // Get the player's inventory + PlayerInventory playerInventory = player.getInventory(); + + // Iterate through the slots in the player's inventory + for (int i = 0; i < playerInventory.main.size(); i++) { + ItemStack slotStack = playerInventory.main.get(i); + + // Check if the slot is empty or if there's space for the item + if ( + slotStack.isEmpty() || + (ItemStack.areItemsEqual(slotStack, itemStack) && slotStack.getCount() + itemStack.getCount() <= slotStack.getMaxCount())) { + return true; // Space available + } + } + + return false; // No space available + } + private static boolean canCombine(ItemStack stack1, ItemStack stack2) { return !stack1.isEmpty() && stack1.getItem() == stack2.getItem() && ItemStack.areItemsAndComponentsEqual(stack1, stack2); } diff --git a/src/main/java/systems/brn/server_storage/lib/Util.java b/src/main/java/systems/brn/server_storage/lib/Util.java index 05371ba..789c2a2 100644 --- a/src/main/java/systems/brn/server_storage/lib/Util.java +++ b/src/main/java/systems/brn/server_storage/lib/Util.java @@ -68,7 +68,7 @@ public class Util { LoreComponent newLore; if (lore == null) { List loreList = new ArrayList<>(); - loreList.add(countLine); + loreList.addFirst(countLine); newLore = new LoreComponent(loreList); } else { List newLines = new ArrayList<>(lore.lines()); diff --git a/src/main/java/systems/brn/server_storage/screens/StorageScreen.java b/src/main/java/systems/brn/server_storage/screens/StorageScreen.java index 83164a5..0a92ba6 100644 --- a/src/main/java/systems/brn/server_storage/screens/StorageScreen.java +++ b/src/main/java/systems/brn/server_storage/screens/StorageScreen.java @@ -12,32 +12,50 @@ import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import systems.brn.server_storage.lib.ConnectedChests; import systems.brn.server_storage.lib.PagedGui; +import java.util.List; + import static systems.brn.server_storage.lib.StorageOperations.*; -import static systems.brn.server_storage.lib.Util.*; +import static systems.brn.server_storage.lib.Util.addCountToLore; +import static systems.brn.server_storage.lib.Util.removeCountFromLore; public class StorageScreen extends PagedGui { private Inventory inventory; + List inventories; private final BlockPos pos; private final ServerPlayerEntity player; private final World world; private String query; + private boolean sortAlphabetically; public StorageScreen(ServerPlayerEntity player, BlockPos pos) { super(player, null); this.player = player; this.pos = pos; this.world = player.getWorld(); - - this.setTitle(Text.literal("Networked storage system")); this.setLockPlayerInventory(false); this.updateDisplay(); } @Override protected void updateDisplay() { - this.inventory = getCombinedInventoryFromChests(world, pos); + ConnectedChests chests = getConnectedChests(world, this.pos); + this.inventories = chests.getInventories(); + this.inventory = getCombinedInventoryFromChests(inventories, sortAlphabetically); + String title = "Storage: " + + chests.getContainerUsedSlots() + + "/" + + chests.getContainerSlots() + + "(" + + chests.getContainerFreeSlots() + + " free)" + + "[" + + chests.getContainerCount() + + "]"; + + this.setTitle(Text.of(title)); super.updateDisplay(); } @@ -45,13 +63,13 @@ public class StorageScreen extends PagedGui { protected int getPageAmount() { int sizeX = 9; int sizeY = 6; - return Math.ceilDivExact(this.inventory.size(), sizeX * sizeY); + return Math.ceilDivExact(inventory.size(), sizeX * sizeY); } @Override protected DisplayElement getElement(int id) { if (this.inventory.size() > id) { - Inventory filteredInventory = filterInventory(this.inventory, query); + Inventory filteredInventory = filterInventory(this.inventory, query, this.sortAlphabetically); ItemStack itemStack = filteredInventory.getStack(id); ItemStack newStack = addCountToLore(itemStack.getCount(), itemStack); GuiElementBuilder guiElement = new GuiElementBuilder(newStack); @@ -66,8 +84,12 @@ public class StorageScreen extends PagedGui { if (clickedElement != null) { ItemStack clickedItem = clickedElement.getItemStack(); ItemStack noLoreStack = removeCountFromLore(clickedItem); - if(this.player.getInventory().insertStack(noLoreStack)){ - tryRemoveItemStackFromChests(world, pos, noLoreStack); + if (noLoreStack.getCount() > noLoreStack.getMaxCount()) { + noLoreStack.setCount(noLoreStack.getMaxCount()); + } + if (canRemoveFromInventory(inventory, noLoreStack) && canInsertItemIntoPlayerInventory(player, noLoreStack)) { + player.getInventory().insertStack(noLoreStack.copy()); + removeItemStackFromChests(inventories, noLoreStack); } } this.updateDisplay(); @@ -76,7 +98,7 @@ public class StorageScreen extends PagedGui { @Override public boolean insertItem(ItemStack stack, int startIndex, int endIndex, boolean fromLast) { - if (tryPutItemStackIntoChests(world, pos, stack)) { + if (tryPutItemStackIntoChests(inventories, stack)) { removeFromInventory(player.getInventory(), stack, stack.getCount()); } this.updateDisplay(); @@ -97,6 +119,22 @@ public class StorageScreen extends PagedGui { }) ); } + + @Override + protected DisplayElement sorting() { + return DisplayElement.of( + new GuiElementBuilder(Items.PLAYER_HEAD) + .setName(Text.translatable(this.sortAlphabetically ? "A->Z" : "9->1").formatted(Formatting.WHITE)) + .hideDefaultTooltip().noDefaults() + .setSkullOwner(this.sortAlphabetically ? GUI_A : GUI_1) + .setCallback((x, y, z) -> { + this.sortAlphabetically ^= true; + playClickSound(this.getPlayer()); + updateDisplay(); + }) + ); + } + public void doSearch(String query) { this.query = query; this.updateDisplay();