compat for Traveller's Backpacks and Sophisticated Storage.

Drag-and-drop item stacks into tabs for supported mods
This commit is contained in:
trunksbomb
2026-03-22 18:42:46 -04:00
parent 3e4fa38528
commit 52a303784d
9 changed files with 617 additions and 2 deletions

View File

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

View File

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

View File

@@ -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<ItemStack> copyItems() {
NonNullList<ItemStack> 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;
}
}

View File

@@ -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<Integer> 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<Integer> insertableSlots) {
if (requestId != pendingInsertRequestId) {
return;
}
INSERTABLE_SLOTS.clear();
INSERTABLE_SLOTS.addAll(insertableSlots);
pendingInsertRequestId = -1;
}
private static List<RenderedTab> getRenderedTabs(AbstractContainerScreen<?> screen, Player player) {
List<BagEntry> bags = BagAccess.findBags(player);
List<RenderedTab> 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;

View File

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

View File

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

View File

@@ -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<InsertIntoBagPayload> TYPE = new Type<>(BagTabs.id("insert_into_bag"));
public static final StreamCodec<RegistryFriendlyByteBuf, InsertIntoBagPayload> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.VAR_INT,
InsertIntoBagPayload::slot,
InsertIntoBagPayload::new
);
@Override
public Type<InsertIntoBagPayload> 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();
}
}
}

View File

@@ -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<Integer> insertableSlots) implements CustomPacketPayload {
public static final Type<InsertTargetsPayload> TYPE = new Type<>(BagTabs.id("insert_targets"));
public static final StreamCodec<RegistryFriendlyByteBuf, InsertTargetsPayload> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.VAR_INT,
InsertTargetsPayload::requestId,
ByteBufCodecs.collection(ArrayList::new, ByteBufCodecs.VAR_INT),
InsertTargetsPayload::insertableSlots,
InsertTargetsPayload::new
);
@Override
public Type<InsertTargetsPayload> type() {
return TYPE;
}
public static void handle(InsertTargetsPayload payload, IPayloadContext context) {
BagTabOverlay.updateInsertTargets(payload.requestId(), payload.insertableSlots());
}
}

View File

@@ -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<QueryInsertTargetsPayload> TYPE = new Type<>(BagTabs.id("query_insert_targets"));
public static final StreamCodec<RegistryFriendlyByteBuf, QueryInsertTargetsPayload> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.VAR_INT,
QueryInsertTargetsPayload::requestId,
QueryInsertTargetsPayload::new
);
@Override
public Type<QueryInsertTargetsPayload> type() {
return TYPE;
}
public static void handle(QueryInsertTargetsPayload payload, IPayloadContext context) {
if (!(context.player() instanceof ServerPlayer serverPlayer)) {
return;
}
ItemStack carriedStack = serverPlayer.containerMenu.getCarried();
List<Integer> 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));
}
}