From 52a303784dd04ecd43fa3c116ad223b0401225ed Mon Sep 17 00:00:00 2001 From: trunksbomb Date: Sun, 22 Mar 2026 18:42:46 -0400 Subject: [PATCH] compat for Traveller's Backpacks and Sophisticated Storage. Drag-and-drop item stacks into tabs for supported mods --- .../com/trunksbomb/bagtabs/BagTabsClient.java | 5 + .../com/trunksbomb/bagtabs/bag/BagCompat.java | 272 ++++++++++++++++++ .../trunksbomb/bagtabs/bag/BagContainer.java | 89 ++++++ .../bagtabs/client/BagTabOverlay.java | 112 +++++++- .../com/trunksbomb/bagtabs/menu/BagMenu.java | 8 + .../bagtabs/network/BagTabsNetwork.java | 3 + .../bagtabs/network/InsertIntoBagPayload.java | 50 ++++ .../bagtabs/network/InsertTargetsPayload.java | 31 ++ .../network/QueryInsertTargetsPayload.java | 49 ++++ 9 files changed, 617 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/trunksbomb/bagtabs/network/InsertIntoBagPayload.java create mode 100644 src/main/java/com/trunksbomb/bagtabs/network/InsertTargetsPayload.java create mode 100644 src/main/java/com/trunksbomb/bagtabs/network/QueryInsertTargetsPayload.java diff --git a/src/main/java/com/trunksbomb/bagtabs/BagTabsClient.java b/src/main/java/com/trunksbomb/bagtabs/BagTabsClient.java index 2e8dc92..6e9cfad 100644 --- a/src/main/java/com/trunksbomb/bagtabs/BagTabsClient.java +++ b/src/main/java/com/trunksbomb/bagtabs/BagTabsClient.java @@ -20,6 +20,7 @@ public class BagTabsClient { modEventBus.addListener(BagTabsClient::registerItemColors); NeoForge.EVENT_BUS.addListener(BagTabsClient::renderTabs); NeoForge.EVENT_BUS.addListener(BagTabsClient::clickTabs); + NeoForge.EVENT_BUS.addListener(BagTabsClient::releaseTabs); } private static void registerScreens(RegisterMenuScreensEvent event) { @@ -39,6 +40,10 @@ public class BagTabsClient { BagTabOverlay.mouseClicked(event); } + private static void releaseTabs(ScreenEvent.MouseButtonReleased.Pre event) { + BagTabOverlay.mouseReleased(event); + } + private static final class BagItemColor { private static final int DEFAULT_TINT = 0x9E7B4F; diff --git a/src/main/java/com/trunksbomb/bagtabs/bag/BagCompat.java b/src/main/java/com/trunksbomb/bagtabs/bag/BagCompat.java index 29d7bc3..47ff238 100644 --- a/src/main/java/com/trunksbomb/bagtabs/bag/BagCompat.java +++ b/src/main/java/com/trunksbomb/bagtabs/bag/BagCompat.java @@ -61,6 +61,14 @@ public final class BagCompat { void open(ServerPlayer player, int slot, ItemStack stack); + default boolean canInsertFromCarried(ServerPlayer player, int slot, ItemStack bagStack, ItemStack carriedStack) { + return false; + } + + default boolean insertFromCarried(ServerPlayer player, int slot, ItemStack bagStack, ItemStack carriedStack) { + return false; + } + default int getActiveBagSlot(AbstractContainerMenu menu) { return -1; } @@ -78,6 +86,32 @@ public final class BagCompat { inventoryBag.openFromInventory(player, slot); } } + + @Override + public boolean insertFromCarried(ServerPlayer player, int slot, ItemStack bagStack, ItemStack carriedStack) { + if (carriedStack.isEmpty() || carriedStack.getItem() instanceof InventoryBag) { + return false; + } + + if (player.containerMenu instanceof BagMenu bagMenu && bagMenu.getBagSlot() == slot) { + return bagMenu.insertCarriedStack(carriedStack); + } + + return BagContainer.insertInto(new BagContainer(player, slot), carriedStack); + } + + @Override + public boolean canInsertFromCarried(ServerPlayer player, int slot, ItemStack bagStack, ItemStack carriedStack) { + if (carriedStack.isEmpty() || carriedStack.getItem() instanceof InventoryBag) { + return false; + } + + if (player.containerMenu instanceof BagMenu bagMenu && bagMenu.getBagSlot() == slot) { + return bagMenu.canInsertCarriedStack(carriedStack); + } + + return BagContainer.canInsertInto(new BagContainer(player, slot), carriedStack); + } } private static final class TravelersBackpackHandler implements BagHandler { @@ -118,6 +152,41 @@ public final class BagCompat { Object slot = invoke(wrapper, "getBackpackSlotIndex"); return slot instanceof Integer integer ? integer : -1; } + + @Override + public boolean canInsertFromCarried(ServerPlayer player, int slot, ItemStack bagStack, ItemStack carriedStack) { + if (carriedStack.isEmpty()) { + return false; + } + + Object storage = getTravelersStorage(player, slot, bagStack); + return storage != null && canInsertIntoItemHandler(storage, carriedStack); + } + + @Override + public boolean insertFromCarried(ServerPlayer player, int slot, ItemStack bagStack, ItemStack carriedStack) { + if (carriedStack.isEmpty()) { + return false; + } + + Object storage = getTravelersStorage(player, slot, bagStack); + return storage != null && insertIntoItemHandler(storage, carriedStack); + } + + private Object getTravelersStorage(ServerPlayer player, int slot, ItemStack bagStack) { + if (isInstance(MENU_CLASS, player.containerMenu) && getActiveBagSlot(player.containerMenu) == slot) { + Object wrapper = invoke(player.containerMenu, "getWrapper"); + return wrapper == null ? null : invoke(wrapper, "getStorage"); + } + + try { + Class wrapperClass = Class.forName(WRAPPER_CLASS); + Object wrapper = wrapperClass.getMethod("fromStack", ItemStack.class).invoke(null, bagStack); + return wrapper == null ? null : invoke(wrapper, "getStorage"); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException exception) { + throw new RuntimeException("Failed to access Traveler's Backpack storage", exception); + } + } } private static final class DankStorageHandler implements BagHandler { @@ -158,6 +227,85 @@ public final class BagCompat { return findMatchingPlayerSlot(menu, bagStack); } + + @Override + public boolean canInsertFromCarried(ServerPlayer player, int slot, ItemStack bagStack, ItemStack carriedStack) { + if (carriedStack.isEmpty() || ItemStack.isSameItemSameComponents(carriedStack, bagStack)) { + return false; + } + + Object dankInventory = getDankInventory(player, slot, bagStack); + return dankInventory != null && canInsertIntoDank(dankInventory, carriedStack); + } + + @Override + public boolean insertFromCarried(ServerPlayer player, int slot, ItemStack bagStack, ItemStack carriedStack) { + if (carriedStack.isEmpty() || ItemStack.isSameItemSameComponents(carriedStack, bagStack)) { + return false; + } + + Object dankInventory = getDankInventory(player, slot, bagStack); + return dankInventory != null && insertIntoDank(dankInventory, carriedStack); + } + + private Object getDankInventory(ServerPlayer player, int slot, ItemStack bagStack) { + if (isInstance(MENU_CLASS, player.containerMenu) && getActiveBagSlot(player.containerMenu) == slot) { + return getFieldValue(player.containerMenu, "dankInventory"); + } + + try { + Method getInventoryFrom = bagStack.getItem().getClass().getMethod( + "getInventoryFrom", + ItemStack.class, + Class.forName("net.minecraft.server.MinecraftServer") + ); + return getInventoryFrom.invoke(null, bagStack, player.server); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException exception) { + throw new RuntimeException("Failed to access Dank Storage inventory", exception); + } + } + + private boolean canInsertIntoDank(Object dankInventory, ItemStack stack) { + ItemStack remaining = stack.copy(); + int slotCount = (Integer) invoke(dankInventory, "slotCount"); + + for (int slot = 0; slot < slotCount && !remaining.isEmpty(); slot++) { + remaining = (ItemStack) invoke( + dankInventory, + "insertStack", + new Class[] {int.class, ItemStack.class, boolean.class}, + slot, + remaining, + true + ); + } + + return remaining.getCount() < stack.getCount(); + } + + private boolean insertIntoDank(Object dankInventory, ItemStack stack) { + ItemStack remaining = stack.copy(); + int slotCount = (Integer) invoke(dankInventory, "slotCount"); + + for (int slot = 0; slot < slotCount && !remaining.isEmpty(); slot++) { + remaining = (ItemStack) invoke( + dankInventory, + "insertStack", + new Class[] {int.class, ItemStack.class, boolean.class}, + slot, + remaining, + false + ); + } + + int inserted = stack.getCount() - remaining.getCount(); + if (inserted <= 0) { + return false; + } + + stack.shrink(inserted); + return true; + } } private static final class SophisticatedBackpacksHandler implements BagHandler { @@ -201,6 +349,42 @@ public final class BagCompat { Object slot = invoke(context, "getBackpackSlotIndex"); return slot instanceof Integer integer ? integer : -1; } + + @Override + public boolean canInsertFromCarried(ServerPlayer player, int slot, ItemStack bagStack, ItemStack carriedStack) { + if (carriedStack.isEmpty()) { + return false; + } + + Object inventoryHandler = getSophisticatedInventoryHandler(player, slot, bagStack); + return inventoryHandler != null && canInsertIntoWholeHandler(inventoryHandler, carriedStack); + } + + @Override + public boolean insertFromCarried(ServerPlayer player, int slot, ItemStack bagStack, ItemStack carriedStack) { + if (carriedStack.isEmpty()) { + return false; + } + + Object inventoryHandler = getSophisticatedInventoryHandler(player, slot, bagStack); + return inventoryHandler != null && insertIntoWholeHandler(inventoryHandler, carriedStack); + } + + private Object getSophisticatedInventoryHandler(ServerPlayer player, int slot, ItemStack bagStack) { + Object wrapper; + if (isInstance(MENU_CLASS, player.containerMenu) && getActiveBagSlot(player.containerMenu) == slot) { + wrapper = invoke(player.containerMenu, "getStorageWrapper"); + } else { + try { + Class wrapperClass = Class.forName("net.p3pp3rf1y.sophisticatedbackpacks.backpack.wrapper.BackpackWrapper"); + wrapper = wrapperClass.getMethod("fromStack", ItemStack.class).invoke(null, bagStack); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException exception) { + throw new RuntimeException("Failed to access Sophisticated Backpack wrapper", exception); + } + } + + return wrapper == null ? null : invoke(wrapper, "getInventoryHandler"); + } } private static int findMatchingPlayerSlot(AbstractContainerMenu menu, ItemStack bagStack) { @@ -267,6 +451,94 @@ public final class BagCompat { } } + private static Object invoke(Object target, String methodName, Class[] parameterTypes, Object... args) { + try { + Method method = target.getClass().getMethod(methodName, parameterTypes); + return method.invoke(target, args); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException exception) { + throw new RuntimeException("Failed to invoke " + methodName + " on " + target.getClass().getName(), exception); + } + } + + private static Object getFieldValue(Object target, String fieldName) { + try { + Field field = target.getClass().getField(fieldName); + return field.get(target); + } catch (IllegalAccessException | NoSuchFieldException exception) { + throw new RuntimeException("Failed to access field " + fieldName + " on " + target.getClass().getName(), exception); + } + } + + private static boolean canInsertIntoItemHandler(Object itemHandler, ItemStack stack) { + ItemStack remaining = stack.copy(); + int slots = (Integer) invoke(itemHandler, "getSlots"); + + for (int slot = 0; slot < slots && !remaining.isEmpty(); slot++) { + remaining = (ItemStack) invoke( + itemHandler, + "insertItem", + new Class[] {int.class, ItemStack.class, boolean.class}, + slot, + remaining, + true + ); + } + + return remaining.getCount() < stack.getCount(); + } + + private static boolean insertIntoItemHandler(Object itemHandler, ItemStack stack) { + ItemStack remaining = stack.copy(); + int slots = (Integer) invoke(itemHandler, "getSlots"); + + for (int slot = 0; slot < slots && !remaining.isEmpty(); slot++) { + remaining = (ItemStack) invoke( + itemHandler, + "insertItem", + new Class[] {int.class, ItemStack.class, boolean.class}, + slot, + remaining, + false + ); + } + + int inserted = stack.getCount() - remaining.getCount(); + if (inserted <= 0) { + return false; + } + + stack.shrink(inserted); + return true; + } + + private static boolean canInsertIntoWholeHandler(Object inventoryHandler, ItemStack stack) { + ItemStack remaining = (ItemStack) invoke( + inventoryHandler, + "insertItem", + new Class[] {ItemStack.class, boolean.class}, + stack.copy(), + true + ); + return remaining.getCount() < stack.getCount(); + } + + private static boolean insertIntoWholeHandler(Object inventoryHandler, ItemStack stack) { + ItemStack remaining = (ItemStack) invoke( + inventoryHandler, + "insertItem", + new Class[] {ItemStack.class, boolean.class}, + stack.copy(), + false + ); + int inserted = stack.getCount() - remaining.getCount(); + if (inserted <= 0) { + return false; + } + + stack.shrink(inserted); + return true; + } + private static void invokeStatic(String className, String methodName, Class[] parameterTypes, Object... args) { try { Class targetClass = Class.forName(className); diff --git a/src/main/java/com/trunksbomb/bagtabs/bag/BagContainer.java b/src/main/java/com/trunksbomb/bagtabs/bag/BagContainer.java index 5ea3aa7..17df61d 100644 --- a/src/main/java/com/trunksbomb/bagtabs/bag/BagContainer.java +++ b/src/main/java/com/trunksbomb/bagtabs/bag/BagContainer.java @@ -46,6 +46,52 @@ public class BagContainer extends SimpleContainer { return current.getItem() instanceof BagItem; } + public static boolean insertInto(net.minecraft.world.Container container, ItemStack stack) { + if (stack.isEmpty()) { + return false; + } + + boolean changed = mergeIntoExistingStacks(container, stack); + changed |= fillEmptySlots(container, stack); + + if (changed) { + container.setChanged(); + } + + return changed; + } + + public static boolean canInsertInto(net.minecraft.world.Container container, ItemStack stack) { + if (stack.isEmpty()) { + return false; + } + + int remaining = stack.getCount(); + + for (int slot = 0; slot < container.getContainerSize() && remaining > 0; slot++) { + ItemStack existing = container.getItem(slot); + if (existing.isEmpty() + || !ItemStack.isSameItemSameComponents(existing, stack) + || !existing.isStackable()) { + continue; + } + + int maxStackSize = Math.min(existing.getMaxStackSize(), container.getMaxStackSize()); + remaining -= Math.max(0, maxStackSize - existing.getCount()); + } + + for (int slot = 0; slot < container.getContainerSize() && remaining > 0; slot++) { + ItemStack existing = container.getItem(slot); + if (!existing.isEmpty() || !container.canPlaceItem(slot, stack)) { + continue; + } + + remaining -= Math.min(stack.getMaxStackSize(), container.getMaxStackSize()); + } + + return remaining < stack.getCount(); + } + private NonNullList copyItems() { NonNullList items = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); @@ -55,4 +101,47 @@ public class BagContainer extends SimpleContainer { return items; } + + private static boolean mergeIntoExistingStacks(net.minecraft.world.Container container, ItemStack stack) { + boolean changed = false; + + for (int slot = 0; slot < container.getContainerSize() && !stack.isEmpty(); slot++) { + ItemStack existing = container.getItem(slot); + if (existing.isEmpty() + || !ItemStack.isSameItemSameComponents(existing, stack) + || !existing.isStackable()) { + continue; + } + + int transferable = Math.min(stack.getCount(), existing.getMaxStackSize() - existing.getCount()); + if (transferable <= 0) { + continue; + } + + existing.grow(transferable); + stack.shrink(transferable); + changed = true; + } + + return changed; + } + + private static boolean fillEmptySlots(net.minecraft.world.Container container, ItemStack stack) { + boolean changed = false; + + for (int slot = 0; slot < container.getContainerSize() && !stack.isEmpty(); slot++) { + ItemStack existing = container.getItem(slot); + if (!existing.isEmpty() || !container.canPlaceItem(slot, stack)) { + continue; + } + + int transferable = Math.min(stack.getCount(), stack.getMaxStackSize()); + ItemStack inserted = stack.copyWithCount(transferable); + container.setItem(slot, inserted); + stack.shrink(transferable); + changed = true; + } + + return changed; + } } diff --git a/src/main/java/com/trunksbomb/bagtabs/client/BagTabOverlay.java b/src/main/java/com/trunksbomb/bagtabs/client/BagTabOverlay.java index e54e222..6a5f936 100644 --- a/src/main/java/com/trunksbomb/bagtabs/client/BagTabOverlay.java +++ b/src/main/java/com/trunksbomb/bagtabs/client/BagTabOverlay.java @@ -5,11 +5,15 @@ import com.trunksbomb.bagtabs.bag.BagAccess; import com.trunksbomb.bagtabs.bag.BagCompat; import com.trunksbomb.bagtabs.bag.BagEntry; import com.trunksbomb.bagtabs.item.BagItem; +import com.trunksbomb.bagtabs.network.InsertIntoBagPayload; +import com.trunksbomb.bagtabs.network.QueryInsertTargetsPayload; import com.trunksbomb.bagtabs.menu.BagMenu; import com.trunksbomb.bagtabs.network.OpenBagPayload; import com.mojang.blaze3d.systems.RenderSystem; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; @@ -17,6 +21,7 @@ import net.minecraft.client.resources.sounds.SimpleSoundInstance; import net.minecraft.resources.ResourceLocation; import net.minecraft.sounds.SoundEvents; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.component.DyedItemColor; import net.minecraft.world.inventory.Slot; import net.neoforged.neoforge.client.event.ScreenEvent; @@ -30,6 +35,11 @@ public final class BagTabOverlay { private static final int TAB_GAP = 0; private static final int TAB_Y_OFFSET = -3; private static final int TAB_X_OFFSET = -6; + private static final Set INSERTABLE_SLOTS = new HashSet<>(); + private static ItemStack lastCarriedStack = ItemStack.EMPTY; + private static int lastMenuContainerId = Integer.MIN_VALUE; + private static int lastInsertRequestId = 0; + private static int pendingInsertRequestId = -1; private BagTabOverlay() { } @@ -52,9 +62,12 @@ public final class BagTabOverlay { GuiGraphics guiGraphics = event.getGuiGraphics(); int mouseX = event.getMouseX(); int mouseY = event.getMouseY(); + ItemStack carriedStack = screen.getMenu().getCarried(); + + refreshInsertTargets(screen, carriedStack); for (RenderedTab tab : tabs) { - renderTab(guiGraphics, tab, mouseX, mouseY); + renderTab(guiGraphics, tab, mouseX, mouseY, carriedStack); guiGraphics.renderItem(tab.entry().stack(), tab.x() + 3, tab.y() + 3); } @@ -76,6 +89,10 @@ public final class BagTabOverlay { return; } + if (!screen.getMenu().getCarried().isEmpty()) { + return; + } + for (RenderedTab tab : getRenderedTabs(screen, player)) { if (tab.isHovered(event.getMouseX(), event.getMouseY())) { PacketDistributor.sendToServer(new OpenBagPayload(tab.entry().slot())); @@ -86,6 +103,48 @@ public final class BagTabOverlay { } } + public static void mouseReleased(ScreenEvent.MouseButtonReleased.Pre event) { + if (!(event.getScreen() instanceof AbstractContainerScreen screen) || !supportsTabs(screen) || event.getButton() != 0) { + return; + } + + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + + ItemStack carriedStack = screen.getMenu().getCarried(); + if (carriedStack.isEmpty()) { + return; + } + + for (RenderedTab tab : getRenderedTabs(screen, player)) { + if (!tab.isHovered(event.getMouseX(), event.getMouseY())) { + continue; + } + + if (!INSERTABLE_SLOTS.contains(tab.entry().slot())) { + event.setCanceled(true); + return; + } + + PacketDistributor.sendToServer(new InsertIntoBagPayload(tab.entry().slot())); + Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.ITEM_PICKUP, 0.8F)); + event.setCanceled(true); + return; + } + } + + public static void updateInsertTargets(int requestId, List insertableSlots) { + if (requestId != pendingInsertRequestId) { + return; + } + + INSERTABLE_SLOTS.clear(); + INSERTABLE_SLOTS.addAll(insertableSlots); + pendingInsertRequestId = -1; + } + private static List getRenderedTabs(AbstractContainerScreen screen, Player player) { List bags = BagAccess.findBags(player); List renderedTabs = new ArrayList<>(); @@ -129,7 +188,7 @@ public final class BagTabOverlay { return slots.isEmpty() ? screen.getMenu().slots : slots; } - private static void renderTab(GuiGraphics guiGraphics, RenderedTab tab, int mouseX, int mouseY) { + private static void renderTab(GuiGraphics guiGraphics, RenderedTab tab, int mouseX, int mouseY, ItemStack carriedStack) { boolean hovered = tab.isHovered(mouseX, mouseY); boolean selected = tab.selected(); int color = DyedItemColor.getOrDefault(tab.entry().stack(), BagItem.DEFAULT_COLOR); @@ -146,6 +205,14 @@ public final class BagTabOverlay { if (selected) { guiGraphics.fill(tab.x() + 2, tab.y() + 2, tab.x() + TAB_WIDTH - 2, tab.y() + 4, 0x90FFFFFF); } + + if (!carriedStack.isEmpty()) { + if (INSERTABLE_SLOTS.contains(tab.entry().slot())) { + renderPlusIndicator(guiGraphics, tab.x() + 15, tab.y() + 3); + } else { + renderXIndicator(guiGraphics, tab.x() + 15, tab.y() + 3); + } + } } private static boolean supportsTabs(AbstractContainerScreen screen) { @@ -156,6 +223,47 @@ public final class BagTabOverlay { return BagCompat.getActiveBagSlot(screen.getMenu()); } + private static void refreshInsertTargets(AbstractContainerScreen screen, ItemStack carriedStack) { + if (carriedStack.isEmpty()) { + INSERTABLE_SLOTS.clear(); + lastCarriedStack = ItemStack.EMPTY; + lastMenuContainerId = screen.getMenu().containerId; + pendingInsertRequestId = -1; + return; + } + + boolean menuChanged = screen.getMenu().containerId != lastMenuContainerId; + boolean carriedChanged = !ItemStack.matches(lastCarriedStack, carriedStack); + if (!menuChanged && !carriedChanged) { + return; + } + + lastCarriedStack = carriedStack.copy(); + lastMenuContainerId = screen.getMenu().containerId; + INSERTABLE_SLOTS.clear(); + pendingInsertRequestId = ++lastInsertRequestId; + PacketDistributor.sendToServer(new QueryInsertTargetsPayload(pendingInsertRequestId)); + } + + private static void renderPlusIndicator(GuiGraphics guiGraphics, int x, int y) { + guiGraphics.fill(x + 1, y, x + 4, y + 5, 0xFF000000); + guiGraphics.fill(x, y + 1, x + 5, y + 4, 0xFF000000); + guiGraphics.fill(x + 2, y + 1, x + 3, y + 4, 0xFF6CFF6C); + guiGraphics.fill(x + 1, y + 2, x + 4, y + 3, 0xFF6CFF6C); + } + + private static void renderXIndicator(GuiGraphics guiGraphics, int x, int y) { + guiGraphics.fill(x, y, x + 1, y + 1, 0xFFFF6C6C); + guiGraphics.fill(x + 4, y, x + 5, y + 1, 0xFFFF6C6C); + guiGraphics.fill(x + 1, y + 1, x + 2, y + 2, 0xFFFF6C6C); + guiGraphics.fill(x + 3, y + 1, x + 4, y + 2, 0xFFFF6C6C); + guiGraphics.fill(x + 2, y + 2, x + 3, y + 3, 0xFFFF6C6C); + guiGraphics.fill(x + 1, y + 3, x + 2, y + 4, 0xFFFF6C6C); + guiGraphics.fill(x + 3, y + 3, x + 4, y + 4, 0xFFFF6C6C); + guiGraphics.fill(x, y + 4, x + 1, y + 5, 0xFFFF6C6C); + guiGraphics.fill(x + 4, y + 4, x + 5, y + 5, 0xFFFF6C6C); + } + private record RenderedTab(BagEntry entry, int x, int y, boolean selected) { private boolean isHovered(double mouseX, double mouseY) { return mouseX >= this.x && mouseX < this.x + TAB_WIDTH && mouseY >= this.y && mouseY < this.y + TAB_HEIGHT; diff --git a/src/main/java/com/trunksbomb/bagtabs/menu/BagMenu.java b/src/main/java/com/trunksbomb/bagtabs/menu/BagMenu.java index 330da9b..b94c1b3 100644 --- a/src/main/java/com/trunksbomb/bagtabs/menu/BagMenu.java +++ b/src/main/java/com/trunksbomb/bagtabs/menu/BagMenu.java @@ -61,6 +61,14 @@ public class BagMenu extends AbstractContainerMenu { return this.bagSlot; } + public boolean insertCarriedStack(ItemStack stack) { + return BagContainer.insertInto(this.container, stack); + } + + public boolean canInsertCarriedStack(ItemStack stack) { + return BagContainer.canInsertInto(this.container, stack); + } + @Override public boolean stillValid(Player player) { return this.container.stillValid(player); diff --git a/src/main/java/com/trunksbomb/bagtabs/network/BagTabsNetwork.java b/src/main/java/com/trunksbomb/bagtabs/network/BagTabsNetwork.java index 58251e4..c7a08fe 100644 --- a/src/main/java/com/trunksbomb/bagtabs/network/BagTabsNetwork.java +++ b/src/main/java/com/trunksbomb/bagtabs/network/BagTabsNetwork.java @@ -8,5 +8,8 @@ public final class BagTabsNetwork { public static void register(PayloadRegistrar registrar) { registrar.playToServer(OpenBagPayload.TYPE, OpenBagPayload.STREAM_CODEC, OpenBagPayload::handle); + registrar.playToServer(InsertIntoBagPayload.TYPE, InsertIntoBagPayload.STREAM_CODEC, InsertIntoBagPayload::handle); + registrar.playToServer(QueryInsertTargetsPayload.TYPE, QueryInsertTargetsPayload.STREAM_CODEC, QueryInsertTargetsPayload::handle); + registrar.playToClient(InsertTargetsPayload.TYPE, InsertTargetsPayload.STREAM_CODEC, InsertTargetsPayload::handle); } } diff --git a/src/main/java/com/trunksbomb/bagtabs/network/InsertIntoBagPayload.java b/src/main/java/com/trunksbomb/bagtabs/network/InsertIntoBagPayload.java new file mode 100644 index 0000000..a359f27 --- /dev/null +++ b/src/main/java/com/trunksbomb/bagtabs/network/InsertIntoBagPayload.java @@ -0,0 +1,50 @@ +package com.trunksbomb.bagtabs.network; + +import com.trunksbomb.bagtabs.BagTabs; +import com.trunksbomb.bagtabs.bag.BagCompat; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.network.handling.IPayloadContext; + +public record InsertIntoBagPayload(int slot) implements CustomPacketPayload { + public static final Type TYPE = new Type<>(BagTabs.id("insert_into_bag")); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, + InsertIntoBagPayload::slot, + InsertIntoBagPayload::new + ); + + @Override + public Type type() { + return TYPE; + } + + public static void handle(InsertIntoBagPayload payload, IPayloadContext context) { + if (!(context.player() instanceof ServerPlayer serverPlayer)) { + return; + } + + if (payload.slot() < 0 || payload.slot() >= serverPlayer.getInventory().getContainerSize()) { + return; + } + + ItemStack bagStack = serverPlayer.getInventory().getItem(payload.slot()); + ItemStack carriedStack = serverPlayer.containerMenu.getCarried(); + if (carriedStack.isEmpty() || bagStack.isEmpty()) { + return; + } + + if (carriedStack == bagStack) { + return; + } + + BagCompat.BagHandler handler = BagCompat.findHandler(bagStack); + if (handler != null && handler.insertFromCarried(serverPlayer, payload.slot(), bagStack, carriedStack)) { + serverPlayer.containerMenu.broadcastChanges(); + } + } +} diff --git a/src/main/java/com/trunksbomb/bagtabs/network/InsertTargetsPayload.java b/src/main/java/com/trunksbomb/bagtabs/network/InsertTargetsPayload.java new file mode 100644 index 0000000..43fb34d --- /dev/null +++ b/src/main/java/com/trunksbomb/bagtabs/network/InsertTargetsPayload.java @@ -0,0 +1,31 @@ +package com.trunksbomb.bagtabs.network; + +import com.trunksbomb.bagtabs.BagTabs; +import com.trunksbomb.bagtabs.client.BagTabOverlay; +import java.util.ArrayList; +import java.util.List; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.neoforged.neoforge.network.handling.IPayloadContext; + +public record InsertTargetsPayload(int requestId, List insertableSlots) implements CustomPacketPayload { + public static final Type TYPE = new Type<>(BagTabs.id("insert_targets")); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, + InsertTargetsPayload::requestId, + ByteBufCodecs.collection(ArrayList::new, ByteBufCodecs.VAR_INT), + InsertTargetsPayload::insertableSlots, + InsertTargetsPayload::new + ); + + @Override + public Type type() { + return TYPE; + } + + public static void handle(InsertTargetsPayload payload, IPayloadContext context) { + BagTabOverlay.updateInsertTargets(payload.requestId(), payload.insertableSlots()); + } +} diff --git a/src/main/java/com/trunksbomb/bagtabs/network/QueryInsertTargetsPayload.java b/src/main/java/com/trunksbomb/bagtabs/network/QueryInsertTargetsPayload.java new file mode 100644 index 0000000..0436456 --- /dev/null +++ b/src/main/java/com/trunksbomb/bagtabs/network/QueryInsertTargetsPayload.java @@ -0,0 +1,49 @@ +package com.trunksbomb.bagtabs.network; + +import com.trunksbomb.bagtabs.BagTabs; +import com.trunksbomb.bagtabs.bag.BagAccess; +import com.trunksbomb.bagtabs.bag.BagEntry; +import java.util.ArrayList; +import java.util.List; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.network.PacketDistributor; +import net.neoforged.neoforge.network.handling.IPayloadContext; + +public record QueryInsertTargetsPayload(int requestId) implements CustomPacketPayload { + public static final Type TYPE = new Type<>(BagTabs.id("query_insert_targets")); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, + QueryInsertTargetsPayload::requestId, + QueryInsertTargetsPayload::new + ); + + @Override + public Type type() { + return TYPE; + } + + public static void handle(QueryInsertTargetsPayload payload, IPayloadContext context) { + if (!(context.player() instanceof ServerPlayer serverPlayer)) { + return; + } + + ItemStack carriedStack = serverPlayer.containerMenu.getCarried(); + List insertableSlots = new ArrayList<>(); + + if (!carriedStack.isEmpty()) { + for (BagEntry bag : BagAccess.findBags(serverPlayer)) { + ItemStack bagStack = serverPlayer.getInventory().getItem(bag.slot()); + if (bag.handler().canInsertFromCarried(serverPlayer, bag.slot(), bagStack, carriedStack)) { + insertableSlots.add(bag.slot()); + } + } + } + + PacketDistributor.sendToPlayer(serverPlayer, new InsertTargetsPayload(payload.requestId(), insertableSlots)); + } +}