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