Fix a bug that destroyed items with a full inventory

Add sorting by alphabet or by count

Change title to show clots and containers

Add refresh button
This commit is contained in:
Bruno Rybársky 2024-05-25 12:41:30 +02:00
parent 33799eebd4
commit 8c50e0fd52
7 changed files with 231 additions and 64 deletions

@ -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

@ -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<Inventory> inventories = chests.getInventories();
Inventory inventory = getCombinedInventoryFromChests(inventories, true);
List<RawFilteredPair<Text>> generatedContent = generateBookContent(inventory);
book.set(DataComponentTypes.WRITTEN_BOOK_CONTENT, new WrittenBookContentComponent(
RawFilteredPair.of("Item Listing"),

@ -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<Inventory> inventories;
private final int containerCount;
private int containerSlots = 0;
private int containerUsedSlots = 0;
private int containerFreeSlots = 0;
ConnectedChests(List<Inventory> 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<Inventory> getInventories() {
return inventories;
}
}

@ -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));

@ -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<Inventory> getConnectedChests(World world, BlockPos startPos) {
public static ConnectedChests getConnectedChests(World world, BlockPos startPos) {
List<Inventory> inventories = new ArrayList<>();
Set<BlockPos> visited = new HashSet<>();
getConnectedChestsHelper(world, startPos, inventories, visited);
return inventories;
return new ConnectedChests(inventories);
}
private static void getConnectedChestsHelper(World world, BlockPos pos, List<Inventory> inventories, Set<BlockPos> visited) {
@ -42,11 +44,10 @@ public class StorageOperations {
}
}
public static boolean tryPutItemStackIntoChests(World world, BlockPos startPos, ItemStack stack) {
List<Inventory> chests = getConnectedChests(world, startPos);
public static boolean tryPutItemStackIntoChests(List<Inventory> 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<Inventory> 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<Inventory> 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<Inventory> inventories = getConnectedChests(world, startPos);
int size = 0;
// Create a map to keep track of item stacks and their counts
Map<Item, Integer> 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<Inventory> inventories, boolean sortAlphabetically) {
Map<ItemStack, Integer> 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<ItemStack, Integer> 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<Item, Integer> itemStackMap) {
// Modify getSimpleInventory to include item metadata and sort conditionally
public static @NotNull SimpleInventory getSimpleInventory(int size, Map<ItemStack, Integer> itemStackMap, boolean sortAlphabetically) {
TreeMap<ItemStack, Integer> sortedMap;
if (sortAlphabetically) {
// Sort alphabetically by item name
sortedMap = new TreeMap<>(new Comparator<ItemStack>() {
@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<ItemStack>() {
@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<Item, Integer> entry : itemStackMap.entrySet()) {
Item item = entry.getKey();
for (Map.Entry<ItemStack, Integer> 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<Item, Integer> itemStackMap = new HashMap<>();
// Modify filterInventory to include item metadata
public static SimpleInventory filterInventory(Inventory inventory, String query, boolean sortAlphabetically) {
Map<ItemStack, Integer> 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);
}

@ -68,7 +68,7 @@ public class Util {
LoreComponent newLore;
if (lore == null) {
List<Text> loreList = new ArrayList<>();
loreList.add(countLine);
loreList.addFirst(countLine);
newLore = new LoreComponent(loreList);
} else {
List<Text> newLines = new ArrayList<>(lore.lines());

@ -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<Inventory> 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();