From 31fd6c33ffa19b2ffac68c19a6453378917522c7 Mon Sep 17 00:00:00 2001 From: trunksbomb Date: Tue, 24 Mar 2026 21:27:32 -0400 Subject: [PATCH] UI work - trade is now a snapshot of the players' inventories at the time the trade was initiated and everything that happens on the trade menu is interacting with references to that snapshot. Inventory is validated at the end to make sure it still has the items that were in there when the trade started, and then the trade completes. --- .../trunksbomb/trade/client/TradeScreen.java | 66 +++++-- .../com/trunksbomb/trade/mod/TradeMod.java | 8 + .../trade/trade/DebugTradeSession.java | 154 ++++++++++----- .../trunksbomb/trade/trade/TradeSession.java | 178 +++++++++++++----- 4 files changed, 294 insertions(+), 112 deletions(-) diff --git a/src/main/java/com/trunksbomb/trade/client/TradeScreen.java b/src/main/java/com/trunksbomb/trade/client/TradeScreen.java index fd0b69f..af315d9 100644 --- a/src/main/java/com/trunksbomb/trade/client/TradeScreen.java +++ b/src/main/java/com/trunksbomb/trade/client/TradeScreen.java @@ -36,6 +36,7 @@ public class TradeScreen extends AbstractContainerScreen private static final int BANNER_Y = 6; private static final int CENTER_COLUMN_X = 115; private static final int STATUS_LABEL_Y = 54; + private static final int CONFIRM_LABEL_Y = 34; private static final int ACCEPT_BUTTON_Y = 74; private static final int CANCEL_BUTTON_Y = 98; private static final int ACTION_BUTTON_WIDTH = 48; @@ -99,6 +100,9 @@ public class TradeScreen extends AbstractContainerScreen onClose(); return true; } + if (hoveredSlot != null) { + return true; + } return super.keyPressed(keyCode, scanCode, modifiers); } @@ -120,6 +124,9 @@ public class TradeScreen extends AbstractContainerScreen } if (menu.view().stage() == TradeStage.CONFIRMING) { + if (hoveredSlot != null) { + return true; + } return super.mouseClicked(mouseX, mouseY, button); } @@ -136,15 +143,19 @@ public class TradeScreen extends AbstractContainerScreen return true; } - if (hoveredSlot != null && hoveredSlot.container == menu.playerInventory()) { + if (hoveredSlot instanceof GhostInventorySlot ghostInventorySlot) { if (button == 1) { - openContextMenu(mouseX, mouseY, TradeAction.ADD_ITEM, hoveredSlot.getSlotIndex(), hoveredSlot.getItem(), "Trade"); + openContextMenu(mouseX, mouseY, TradeAction.ADD_ITEM, ghostInventorySlot.inventoryIndex(), hoveredSlot.getItem(), "Trade"); } else { - sendAction(TradeAction.ADD_ITEM, hoveredSlot.getSlotIndex(), 1); + sendAction(TradeAction.ADD_ITEM, ghostInventorySlot.inventoryIndex(), 1); } return true; } + if (hoveredSlot != null) { + return true; + } + return super.mouseClicked(mouseX, mouseY, button); } @@ -158,10 +169,11 @@ public class TradeScreen extends AbstractContainerScreen guiGraphics.drawString(font, "Trading with " + menu.view().otherName(), titleLabelX, titleLabelY, 0x404040, false); guiGraphics.drawString(font, playerInventoryTitle, inventoryLabelX, inventoryLabelY, 0x404040, false); if (menu.view().stage() == TradeStage.CONFIRMING) { - drawScaledCenteredColumnText(guiGraphics, "Are you", STATUS_LABEL_Y, 0xB02020, 0.7F); - drawScaledCenteredColumnText(guiGraphics, "sure you want", STATUS_LABEL_Y + 7, 0xB02020, 0.7F); - drawScaledCenteredColumnText(guiGraphics, "to trade?", STATUS_LABEL_Y + 14, 0xB02020, 0.7F); - } else if (menu.view().otherAccepted()) { + drawScaledCenteredColumnText(guiGraphics, "Are you", CONFIRM_LABEL_Y, 0xB02020, 0.7F); + drawScaledCenteredColumnText(guiGraphics, "sure you want", CONFIRM_LABEL_Y + 7, 0xB02020, 0.7F); + drawScaledCenteredColumnText(guiGraphics, "to trade?", CONFIRM_LABEL_Y + 14, 0xB02020, 0.7F); + } + if (menu.view().otherAccepted()) { drawScaledCenteredColumnText(guiGraphics, "Other player", STATUS_LABEL_Y, 0x2E7D32, 0.7F); drawScaledCenteredColumnText(guiGraphics, "has accepted", STATUS_LABEL_Y + 7, 0x2E7D32, 0.7F); } @@ -317,15 +329,14 @@ public class TradeScreen extends AbstractContainerScreen private static final int OFFER_COLUMNS = 6; private static final int OFFER_ROWS = 6; private TradeView view; - private final Inventory playerInventory; + private final SimpleContainer inventoryDisplay = new SimpleContainer(TradeView.INVENTORY_SLOT_COUNT); private final SimpleContainer selfOffer = new SimpleContainer(TradeView.OFFER_SLOT_COUNT); private final SimpleContainer otherOffer = new SimpleContainer(TradeView.OFFER_SLOT_COUNT); public TradeMenu(int containerId, Inventory inventory, TradeView view) { super(MenuType.GENERIC_9x6, containerId); - this.playerInventory = inventory; this.view = view; - syncOfferContainers(); + syncContainers(); for (int row = 0; row < OFFER_ROWS; row++) { for (int col = 0; col < OFFER_COLUMNS; col++) { @@ -344,12 +355,12 @@ public class TradeScreen extends AbstractContainerScreen for (int row = 0; row < 3; row++) { for (int col = 0; col < 9; col++) { int slot = col + row * 9 + 9; - addSlot(new Slot(inventory, slot, INVENTORY_START_X + col * 18, INVENTORY_START_Y + row * 18)); + addSlot(new GhostInventorySlot(inventoryDisplay, slot, INVENTORY_START_X + col * 18, INVENTORY_START_Y + row * 18)); } } for (int col = 0; col < 9; col++) { - addSlot(new Slot(inventory, col, INVENTORY_START_X + col * 18, INVENTORY_START_Y + 58)); + addSlot(new GhostInventorySlot(inventoryDisplay, col, INVENTORY_START_X + col * 18, INVENTORY_START_Y + 58)); } } @@ -357,16 +368,15 @@ public class TradeScreen extends AbstractContainerScreen return view; } - public Inventory playerInventory() { - return playerInventory; - } - public void updateView(TradeView view) { this.view = view; - syncOfferContainers(); + syncContainers(); } - private void syncOfferContainers() { + private void syncContainers() { + for (int i = 0; i < TradeView.INVENTORY_SLOT_COUNT; i++) { + inventoryDisplay.setItem(i, view.inventory().get(i).copy()); + } for (int i = 0; i < TradeView.OFFER_SLOT_COUNT; i++) { selfOffer.setItem(i, view.selfOffer().get(i).copy()); otherOffer.setItem(i, view.otherOffer().get(i).copy()); @@ -419,4 +429,24 @@ public class TradeScreen extends AbstractContainerScreen return false; } } + + private static class GhostInventorySlot extends Slot { + public GhostInventorySlot(Container container, int slot, int x, int y) { + super(container, slot, x, y); + } + + public int inventoryIndex() { + return getSlotIndex(); + } + + @Override + public boolean mayPickup(Player player) { + return false; + } + + @Override + public boolean mayPlace(ItemStack stack) { + return false; + } + } } diff --git a/src/main/java/com/trunksbomb/trade/mod/TradeMod.java b/src/main/java/com/trunksbomb/trade/mod/TradeMod.java index 434f10d..4060b0c 100644 --- a/src/main/java/com/trunksbomb/trade/mod/TradeMod.java +++ b/src/main/java/com/trunksbomb/trade/mod/TradeMod.java @@ -23,6 +23,7 @@ public class TradeMod { modEventBus.addListener(this::registerPayloads); NeoForge.EVENT_BUS.addListener(this::registerCommands); NeoForge.EVENT_BUS.addListener(this::onPlayerLogout); + NeoForge.EVENT_BUS.addListener(this::onItemPickup); } private void commonSetup(FMLCommonSetupEvent event) { @@ -43,4 +44,11 @@ public class TradeMod { TradeManager.get(player.server).handleDisconnect(player); } } + + public void onItemPickup(net.neoforged.neoforge.event.entity.player.ItemEntityPickupEvent.Pre event) { + if (event.getPlayer() instanceof net.minecraft.server.level.ServerPlayer player + && TradeManager.get(player.server).isTrading(player)) { + event.setCanPickup(net.neoforged.neoforge.common.util.TriState.FALSE); + } + } } diff --git a/src/main/java/com/trunksbomb/trade/trade/DebugTradeSession.java b/src/main/java/com/trunksbomb/trade/trade/DebugTradeSession.java index f16ec93..38fba30 100644 --- a/src/main/java/com/trunksbomb/trade/trade/DebugTradeSession.java +++ b/src/main/java/com/trunksbomb/trade/trade/DebugTradeSession.java @@ -22,7 +22,8 @@ public class DebugTradeSession { private final UUID id = UUID.randomUUID(); private final ServerPlayer player; - private final List selfOffer = blankOffer(); + private final List inventorySnapshot; + private final List selfOffer = blankOffer(); private final List otherOffer = blankStacks(); private boolean selfAccepted; private boolean otherAccepted; @@ -30,6 +31,7 @@ public class DebugTradeSession { public DebugTradeSession(ServerPlayer player) { this.player = player; + this.inventorySnapshot = inventorySnapshot(player); } public UUID id() { @@ -53,23 +55,34 @@ public class DebugTradeSession { return false; } - Inventory inventory = player.getInventory(); - ItemStack sourceStack = inventory.getItem(inventorySlot); + ItemStack sourceStack = inventorySnapshot.get(inventorySlot); if (sourceStack.isEmpty()) { return false; } - int moveAmount = Math.max(1, Math.min(amount, sourceStack.getCount())); - ItemStack moving = sourceStack.copyWithCount(moveAmount); - if (!insertStack(selfOffer, moving)) { + int available = sourceStack.getCount() - reservedCount(inventorySlot); + if (available <= 0) { return false; } - sourceStack.shrink(moveAmount); - if (sourceStack.isEmpty()) { - inventory.setItem(inventorySlot, ItemStack.EMPTY); + int moveAmount = Math.max(1, Math.min(amount, available)); + for (int i = 0; i < selfOffer.size(); i++) { + TradeEntry entry = selfOffer.get(i); + if (entry != null && entry.sourceSlot() == inventorySlot && ItemStack.isSameItemSameComponents(entry.stack(), sourceStack)) { + ItemStack merged = entry.stack().copy(); + merged.grow(moveAmount); + selfOffer.set(i, new TradeEntry(inventorySlot, merged)); + clearAccepts(); + return true; + } } - inventory.setChanged(); + + int freeSlot = firstFreeOfferSlot(selfOffer); + if (freeSlot == -1) { + return false; + } + + selfOffer.set(freeSlot, new TradeEntry(inventorySlot, sourceStack.copyWithCount(moveAmount))); clearAccepts(); return true; } @@ -79,30 +92,18 @@ public class DebugTradeSession { return false; } - ItemStack offered = selfOffer.get(offerSlot); - if (offered.isEmpty()) { + TradeEntry entry = selfOffer.get(offerSlot); + if (entry == null) { return false; } - List workingInventory = inventorySnapshot(); - int moveAmount = Math.max(1, Math.min(amount, offered.getCount())); - ItemStack returned = offered.copyWithCount(moveAmount); - if (!insertStack(workingInventory, returned)) { - return false; - } - - Inventory inventory = player.getInventory(); - for (int i = 0; i < workingInventory.size(); i++) { - inventory.setItem(i, workingInventory.get(i)); - } - inventory.setChanged(); - - if (moveAmount < offered.getCount()) { - ItemStack updated = offered.copy(); + int moveAmount = Math.max(1, Math.min(amount, entry.stack().getCount())); + if (moveAmount < entry.stack().getCount()) { + ItemStack updated = entry.stack().copy(); updated.shrink(moveAmount); - selfOffer.set(offerSlot, updated); + selfOffer.set(offerSlot, new TradeEntry(entry.sourceSlot(), updated)); } else { - selfOffer.set(offerSlot, ItemStack.EMPTY); + selfOffer.set(offerSlot, null); } clearAccepts(); @@ -208,7 +209,11 @@ public class DebugTradeSession { } public boolean completeTrade() { - List result = inventorySnapshot(); + List result = inventorySnapshot(player); + if (!removeOutgoing(result, selfOffer)) { + return false; + } + for (ItemStack stack : otherOffer) { if (!stack.isEmpty() && !insertStack(result, stack.copy())) { return false; @@ -232,7 +237,7 @@ public class DebugTradeSession { stage, selfAccepted, otherAccepted, - inventorySnapshot(), + inventoryDisplay(), emptyReservedSnapshot(), selfOfferSnapshot(), otherOfferSnapshot()); @@ -312,6 +317,25 @@ public class DebugTradeSession { } } + private static boolean removeOutgoing(List working, List outgoing) { + for (TradeEntry entry : outgoing) { + if (entry == null) { + continue; + } + + ItemStack current = working.get(entry.sourceSlot()); + if (!ItemStack.isSameItemSameComponents(current, entry.stack()) || current.getCount() < entry.stack().getCount()) { + return false; + } + + ItemStack updated = current.copy(); + updated.shrink(entry.stack().getCount()); + working.set(entry.sourceSlot(), updated); + } + + return true; + } + private static boolean insertStack(List working, ItemStack incoming) { if (incoming.isEmpty()) { return true; @@ -350,6 +374,49 @@ public class DebugTradeSession { return false; } + private int reservedCount(int inventorySlot) { + int count = 0; + for (TradeEntry entry : selfOffer) { + if (entry != null && entry.sourceSlot() == inventorySlot) { + count += entry.stack().getCount(); + } + } + return count; + } + + private void clearAccepts() { + selfAccepted = false; + otherAccepted = false; + } + + private List inventorySnapshotCopy() { + List result = new ArrayList<>(INVENTORY_SLOT_COUNT); + for (ItemStack stack : inventorySnapshot) { + result.add(stack.copy()); + } + return result; + } + + private List inventoryDisplay() { + List result = inventorySnapshotCopy(); + for (int i = 0; i < INVENTORY_SLOT_COUNT; i++) { + int reserved = reservedCount(i); + if (reserved <= 0) { + continue; + } + + ItemStack stack = result.get(i); + if (stack.isEmpty()) { + continue; + } + + ItemStack updated = stack.copy(); + updated.shrink(reserved); + result.set(i, updated); + } + return result; + } + private static List emptyReservedSnapshot() { List result = new ArrayList<>(INVENTORY_SLOT_COUNT); for (int i = 0; i < INVENTORY_SLOT_COUNT; i++) { @@ -358,12 +425,7 @@ public class DebugTradeSession { return result; } - private void clearAccepts() { - selfAccepted = false; - otherAccepted = false; - } - - private List inventorySnapshot() { + private static List inventorySnapshot(ServerPlayer player) { List result = new ArrayList<>(INVENTORY_SLOT_COUNT); Inventory inventory = player.getInventory(); for (int i = 0; i < INVENTORY_SLOT_COUNT; i++) { @@ -374,8 +436,8 @@ public class DebugTradeSession { private List selfOfferSnapshot() { List result = new ArrayList<>(OFFER_SLOT_COUNT); - for (ItemStack stack : selfOffer) { - result.add(stack.copy()); + for (TradeEntry entry : selfOffer) { + result.add(entry == null ? ItemStack.EMPTY : entry.stack().copy()); } return result; } @@ -388,10 +450,10 @@ public class DebugTradeSession { return result; } - private static List blankOffer() { - List result = new ArrayList<>(OFFER_SLOT_COUNT); + private static List blankOffer() { + List result = new ArrayList<>(OFFER_SLOT_COUNT); for (int i = 0; i < OFFER_SLOT_COUNT; i++) { - result.add(ItemStack.EMPTY); + result.add(null); } return result; } @@ -404,4 +466,12 @@ public class DebugTradeSession { return result; } + private static int firstFreeOfferSlot(List offer) { + for (int i = 0; i < offer.size(); i++) { + if (offer.get(i) == null) { + return i; + } + } + return -1; + } } diff --git a/src/main/java/com/trunksbomb/trade/trade/TradeSession.java b/src/main/java/com/trunksbomb/trade/trade/TradeSession.java index af77435..e5d6029 100644 --- a/src/main/java/com/trunksbomb/trade/trade/TradeSession.java +++ b/src/main/java/com/trunksbomb/trade/trade/TradeSession.java @@ -18,8 +18,10 @@ public class TradeSession { private final UUID id = UUID.randomUUID(); private final ServerPlayer first; private final ServerPlayer second; - private final List firstOffer = blankOffer(); - private final List secondOffer = blankOffer(); + private final List firstInventory; + private final List secondInventory; + private final List firstOffer = blankOffer(); + private final List secondOffer = blankOffer(); private boolean firstAccepted; private boolean secondAccepted; private TradeStage stage = TradeStage.OFFERING; @@ -27,6 +29,8 @@ public class TradeSession { public TradeSession(ServerPlayer first, ServerPlayer second) { this.first = first; this.second = second; + this.firstInventory = inventorySnapshot(first); + this.secondInventory = inventorySnapshot(second); } public UUID id() { @@ -64,24 +68,35 @@ public class TradeSession { return false; } - List offer = offerFor(player); - Inventory inventory = player.getInventory(); - ItemStack sourceStack = inventory.getItem(inventorySlot); + ItemStack sourceStack = inventoryFor(player).get(inventorySlot); if (sourceStack.isEmpty()) { return false; } - int moveAmount = Math.max(1, Math.min(amount, sourceStack.getCount())); - ItemStack moving = sourceStack.copyWithCount(moveAmount); - if (!insertStack(offer, moving)) { + int available = sourceStack.getCount() - reservedCount(player, inventorySlot); + if (available <= 0) { return false; } - sourceStack.shrink(moveAmount); - if (sourceStack.isEmpty()) { - inventory.setItem(inventorySlot, ItemStack.EMPTY); + int moveAmount = Math.max(1, Math.min(amount, available)); + List offer = offerFor(player); + for (int i = 0; i < offer.size(); i++) { + TradeEntry entry = offer.get(i); + if (entry != null && entry.sourceSlot() == inventorySlot && ItemStack.isSameItemSameComponents(entry.stack(), sourceStack)) { + ItemStack merged = entry.stack().copy(); + merged.grow(moveAmount); + offer.set(i, new TradeEntry(inventorySlot, merged)); + clearAccepts(); + return true; + } } - inventory.setChanged(); + + int freeSlot = firstFreeOfferSlot(offer); + if (freeSlot == -1) { + return false; + } + + offer.set(freeSlot, new TradeEntry(inventorySlot, sourceStack.copyWithCount(moveAmount))); clearAccepts(); return true; } @@ -91,28 +106,21 @@ public class TradeSession { return false; } - List offer = offerFor(player); - ItemStack offered = offer.get(offerSlot); - if (offered.isEmpty()) { + List offer = offerFor(player); + TradeEntry entry = offer.get(offerSlot); + if (entry == null) { return false; } - Inventory inventory = player.getInventory(); - List workingInventory = inventorySnapshot(player); - int moveAmount = Math.max(1, Math.min(amount, offered.getCount())); - ItemStack returned = offered.copyWithCount(moveAmount); - if (!insertStack(workingInventory, returned)) { - return false; - } - - applyInventory(player, workingInventory); - if (moveAmount < offered.getCount()) { - ItemStack updated = offered.copy(); + int moveAmount = Math.max(1, Math.min(amount, entry.stack().getCount())); + if (moveAmount < entry.stack().getCount()) { + ItemStack updated = entry.stack().copy(); updated.shrink(moveAmount); - offer.set(offerSlot, updated); + offer.set(offerSlot, new TradeEntry(entry.sourceSlot(), updated)); } else { - offer.set(offerSlot, ItemStack.EMPTY); + offer.set(offerSlot, null); } + clearAccepts(); return true; } @@ -142,16 +150,14 @@ public class TradeSession { } public boolean completeTrade() { - List firstResult = simulateResult(first, secondOffer); - List secondResult = simulateResult(second, firstOffer); + List firstResult = simulateResult(first, firstOffer, secondOffer); + List secondResult = simulateResult(second, secondOffer, firstOffer); if (firstResult == null || secondResult == null) { return false; } applyInventory(first, firstResult); applyInventory(second, secondResult); - clearOffer(firstOffer); - clearOffer(secondOffer); return true; } @@ -166,7 +172,7 @@ public class TradeSession { stage, isFirst ? firstAccepted : secondAccepted, isFirst ? secondAccepted : firstAccepted, - inventorySnapshot(player), + inventoryDisplayFor(player), emptyReservedSnapshot(), offerSnapshot(isFirst ? firstOffer : secondOffer), offerSnapshot(isFirst ? secondOffer : firstOffer)); @@ -176,10 +182,14 @@ public class TradeSession { PacketDistributor.sendToPlayer(player, new TradeStatePayload(viewFor(player))); } - private List simulateResult(ServerPlayer player, List incoming) { + private List simulateResult(ServerPlayer player, List outgoing, List incoming) { List working = inventorySnapshot(player); - for (ItemStack stack : incoming) { - if (!stack.isEmpty() && !insertStack(working, stack.copy())) { + if (!removeOutgoing(working, outgoing)) { + return null; + } + + for (TradeEntry entry : incoming) { + if (entry != null && !insertStack(working, entry.stack().copy())) { return null; } } @@ -187,6 +197,25 @@ public class TradeSession { return working; } + private static boolean removeOutgoing(List working, List outgoing) { + for (TradeEntry entry : outgoing) { + if (entry == null) { + continue; + } + + ItemStack current = working.get(entry.sourceSlot()); + if (!ItemStack.isSameItemSameComponents(current, entry.stack()) || current.getCount() < entry.stack().getCount()) { + return false; + } + + ItemStack updated = current.copy(); + updated.shrink(entry.stack().getCount()); + working.set(entry.sourceSlot(), updated); + } + + return true; + } + private static boolean insertStack(List working, ItemStack incoming) { if (incoming.isEmpty()) { return true; @@ -225,12 +254,6 @@ public class TradeSession { return false; } - private static void clearOffer(List offer) { - for (int i = 0; i < offer.size(); i++) { - offer.set(i, ItemStack.EMPTY); - } - } - private static void applyInventory(ServerPlayer player, List result) { Inventory inventory = player.getInventory(); for (int i = 0; i < result.size(); i++) { @@ -239,19 +262,74 @@ public class TradeSession { inventory.setChanged(); } - private static List blankOffer() { - List result = new ArrayList<>(OFFER_SLOT_COUNT); + private static List blankOffer() { + List result = new ArrayList<>(OFFER_SLOT_COUNT); for (int i = 0; i < OFFER_SLOT_COUNT; i++) { - result.add(ItemStack.EMPTY); + result.add(null); } return result; } + private static int firstFreeOfferSlot(List offer) { + for (int i = 0; i < offer.size(); i++) { + if (offer.get(i) == null) { + return i; + } + } + return -1; + } + private void clearAccepts() { firstAccepted = false; secondAccepted = false; } + private int reservedCount(ServerPlayer player, int inventorySlot) { + int count = 0; + for (TradeEntry entry : offerFor(player)) { + if (entry != null && entry.sourceSlot() == inventorySlot) { + count += entry.stack().getCount(); + } + } + return count; + } + + private List offerFor(ServerPlayer player) { + return player == first ? firstOffer : secondOffer; + } + + private List inventoryFor(ServerPlayer player) { + return player == first ? firstInventory : secondInventory; + } + + private List inventorySnapshotFor(ServerPlayer player) { + List result = new ArrayList<>(INVENTORY_SLOT_COUNT); + for (ItemStack stack : inventoryFor(player)) { + result.add(stack.copy()); + } + return result; + } + + private List inventoryDisplayFor(ServerPlayer player) { + List result = inventorySnapshotFor(player); + for (int i = 0; i < INVENTORY_SLOT_COUNT; i++) { + int reserved = reservedCount(player, i); + if (reserved <= 0) { + continue; + } + + ItemStack stack = result.get(i); + if (stack.isEmpty()) { + continue; + } + + ItemStack updated = stack.copy(); + updated.shrink(reserved); + result.set(i, updated); + } + return result; + } + private static List emptyReservedSnapshot() { List result = new ArrayList<>(INVENTORY_SLOT_COUNT); for (int i = 0; i < INVENTORY_SLOT_COUNT; i++) { @@ -260,10 +338,6 @@ public class TradeSession { return result; } - private List offerFor(ServerPlayer player) { - return player == first ? firstOffer : secondOffer; - } - private static List inventorySnapshot(ServerPlayer player) { List result = new ArrayList<>(INVENTORY_SLOT_COUNT); Inventory inventory = player.getInventory(); @@ -273,10 +347,10 @@ public class TradeSession { return result; } - private static List offerSnapshot(List offer) { + private static List offerSnapshot(List offer) { List result = new ArrayList<>(OFFER_SLOT_COUNT); - for (ItemStack stack : offer) { - result.add(stack.copy()); + for (TradeEntry entry : offer) { + result.add(entry == null ? ItemStack.EMPTY : entry.stack().copy()); } return result; }