/* * This file is part of LanternServer, licensed under the MIT License (MIT). * * Copyright (c) LanternPowered <https://www.lanternpowered.org> * Copyright (c) SpongePowered <https://www.spongepowered.org> * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the Software), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.lanternpowered.server.inventory; import org.lanternpowered.server.entity.living.player.LanternPlayer; import org.lanternpowered.server.game.Lantern; import org.lanternpowered.server.inventory.entity.HumanInventoryView; import org.lanternpowered.server.inventory.entity.HumanMainInventory; import org.lanternpowered.server.inventory.entity.LanternHotbar; import org.lanternpowered.server.inventory.slot.LanternSlot; import org.lanternpowered.server.network.vanilla.message.type.play.MessagePlayInClickWindow; import org.lanternpowered.server.network.vanilla.message.type.play.MessagePlayInCreativeWindowAction; import org.lanternpowered.server.network.vanilla.message.type.play.MessagePlayInDropHeldItem; import org.lanternpowered.server.network.vanilla.message.type.play.MessagePlayInOutCloseWindow; import org.lanternpowered.server.network.vanilla.message.type.play.MessagePlayOutSetWindowSlot; import org.spongepowered.api.Sponge; import org.spongepowered.api.data.Transaction; import org.spongepowered.api.data.key.Keys; import org.spongepowered.api.entity.Entity; import org.spongepowered.api.entity.EntityTypes; import org.spongepowered.api.entity.living.player.gamemode.GameMode; import org.spongepowered.api.entity.living.player.gamemode.GameModes; import org.spongepowered.api.event.SpongeEventFactory; import org.spongepowered.api.event.cause.Cause; import org.spongepowered.api.event.cause.NamedCause; import org.spongepowered.api.event.cause.entity.spawn.SpawnCause; import org.spongepowered.api.event.cause.entity.spawn.SpawnTypes; import org.spongepowered.api.event.entity.SpawnEntityEvent; import org.spongepowered.api.event.item.inventory.ClickInventoryEvent; import org.spongepowered.api.event.item.inventory.InteractInventoryEvent; import org.spongepowered.api.item.inventory.Inventory; import org.spongepowered.api.item.inventory.ItemStack; import org.spongepowered.api.item.inventory.ItemStackSnapshot; import org.spongepowered.api.item.inventory.Slot; import org.spongepowered.api.item.inventory.slot.OutputSlot; import org.spongepowered.api.item.inventory.transaction.InventoryTransactionResult; import org.spongepowered.api.item.inventory.transaction.SlotTransaction; import org.spongepowered.api.world.World; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.function.Predicate; import javax.annotation.Nullable; /** * Represents a session of a player interacting with a * {@link LanternContainer}. It is possible to switch * between {@link LanternContainer}s without canceling * the session. * * This will for example keep the cursor item until it * is placed or the session is finished. */ public class PlayerContainerSession { private final LanternPlayer player; /** * The container that is currently open. */ @Nullable private LanternContainer openContainer; /** * The item stack currently on the cursor */ @Nullable private ItemStack cursorItem; /** * All the slots currently in a drag session. */ private final List<LanternSlot> dragSlots = new ArrayList<>(); /** * Whether the dragging was started with the left mouse button. * * -1 means not started * 0 means left drag * 1 means right drag */ private int dragState = -1; public PlayerContainerSession(LanternPlayer player) { this.player = player; } /** * Gets the open container. * * @return The container */ @Nullable public LanternContainer getOpenContainer() { return this.openContainer; } /** * Sets the open container. * * @param container The container */ public boolean setOpenContainer(@Nullable LanternContainer container, @Nullable Cause cause) { return this.setRawOpenContainer(container, cause, true); } public boolean setRawOpenContainer(@Nullable LanternContainer container, @Nullable Cause cause) { return this.setRawOpenContainer(container, cause, false); } /** * Sets the open container. * * @param container The container */ private boolean setRawOpenContainer(@Nullable LanternContainer container, @Nullable Cause cause, boolean sendClose) { if (this.openContainer != container) { final ItemStackSnapshot oldCursorItemSnapshot = LanternItemStack.toSnapshot(this.cursorItem); ItemStackSnapshot cursorItemSnapshot = oldCursorItemSnapshot; if (this.openContainer != null) { if (cause != null) { final InteractInventoryEvent.Close event = SpongeEventFactory.createInteractInventoryEventClose( cause, new Transaction<>(cursorItemSnapshot, ItemStackSnapshot.NONE), this.openContainer); Sponge.getEventManager().post(event); if (event.isCancelled()) { return false; } final Transaction<ItemStackSnapshot> transaction = event.getCursorTransaction(); if (transaction.isValid()) { cursorItemSnapshot = transaction.getFinal(); } } else { this.cursorItem = null; } if (LanternItemStack.toNullable(oldCursorItemSnapshot) != null) { final List<Entity> entities = new ArrayList<>(); entities.add(this.createDroppedItem(oldCursorItemSnapshot)); final SpawnEntityEvent event1 = SpongeEventFactory.createDropItemEventDispense(cause, entities, this.player.getWorld()); Sponge.getEventManager().post(event1); if (!event1.isCancelled()) { this.finishSpawnEntityEvent(event1); } } } else { sendClose = false; } if (container != null) { if (cause != null) { final InteractInventoryEvent.Open event = SpongeEventFactory.createInteractInventoryEventOpen( cause, new Transaction<>(cursorItemSnapshot, cursorItemSnapshot), container); Sponge.getEventManager().post(event); if (event.isCancelled()) { this.cursorItem = LanternItemStack.toNullable(cursorItemSnapshot); container.removeViewer(this.player, container); return false; } final Transaction<ItemStackSnapshot> transaction = event.getCursorTransaction(); if (transaction.isValid()) { cursorItemSnapshot = transaction.getFinal(); this.cursorItem = LanternItemStack.toNullable(cursorItemSnapshot); } } // The container is being used for the first time if (container.getRawViewers().isEmpty()) { container.addSlotTrackers(); } sendClose = false; container.addViewer(this.player, container); container.viewers.add(this.player); container.openInventoryForAndInitialize(this.player); this.updateCursorItem(); } else { this.cursorItem = LanternItemStack.toNullable(cursorItemSnapshot); } if (sendClose && this.openContainer.windowId != -1) { this.player.getConnection().send( new MessagePlayInOutCloseWindow(this.openContainer.windowId)); } if (this.openContainer != null) { this.openContainer.viewers.remove(this.player); this.openContainer.removeViewer(this.player, this.openContainer); if (this.openContainer.getRawViewers().isEmpty()) { this.openContainer.removeSlotTrackers(); } } } this.openContainer = container; return true; } /** * Sets the cursor item. * * @param cursorItem The cursor item */ public void setCursorItem(@Nullable ItemStack cursorItem) { this.cursorItem = LanternItemStack.toNullable(cursorItem); this.updateCursorItem(); } private void updateCursorItem() { this.player.getConnection().send( new MessagePlayOutSetWindowSlot(-1, -1, this.cursorItem)); } @Nullable public ItemStack getCursorItem() { return this.cursorItem; } public void handleWindowCreativeClick(MessagePlayInCreativeWindowAction message) { if (this.openContainer == null) { return; } ItemStack itemStack = LanternItemStack.toNullable(message.getItemStack()); int slotIndex = message.getSlot(); if (slotIndex < 0) { if (itemStack != null) { final Cause cause = Cause.builder().named("SpawnCause", SpawnCause.builder() .type(SpawnTypes.DROPPED_ITEM).build()).named(NamedCause.SOURCE, this.player).build(); final List<Entity> entities = new ArrayList<>(); entities.add(this.createDroppedItem(itemStack.createSnapshot())); final World world = this.player.getWorld(); final SpawnEntityEvent event = SpongeEventFactory.createDropItemEventDispense(cause, entities, world); Sponge.getEventManager().post(event); if (!event.isCancelled()) { this.finishSpawnEntityEvent(event); } } } else { Optional<LanternSlot> optSlot = this.openContainer.playerInventory.getSlotAt(slotIndex); if (optSlot.isPresent()) { final Cause cause = Cause.builder().named(NamedCause.SOURCE, this.player).build(); final LanternSlot slot = optSlot.get(); PeekSetTransactionsResult result = slot.peekSetTransactions(itemStack); // We do not know the remaining stack in the cursor, // so just use none as new item Transaction<ItemStackSnapshot> cursorTransaction = new Transaction<>( LanternItemStack.toSnapshot(itemStack), ItemStackSnapshot.NONE); ClickInventoryEvent.Creative event = SpongeEventFactory.createClickInventoryEventCreative( cause, cursorTransaction, this.openContainer, result.getTransactions()); this.finishInventoryEvent(event); } else { Lantern.getLogger().warn("Unknown slot index {} in container {}", slotIndex, this.openContainer); } } } public void handleItemDrop(MessagePlayInDropHeldItem message) { final LanternSlot slot = this.player.getInventory().getHotbar().getSelectedSlot(); final Optional<ItemStack> itemStack = message.isFullStack() ? slot.peek() : slot.peek(1); if (itemStack.isPresent()) { final Cause cause = Cause.builder().named("SpawnCause", SpawnCause.builder() .type(SpawnTypes.DROPPED_ITEM).build()) .named(NamedCause.SOURCE, this.player) .named("Slot", slot) .build(); final List<Entity> entities = new ArrayList<>(); entities.add(this.createDroppedItem(itemStack.get().createSnapshot())); final SpawnEntityEvent event = SpongeEventFactory.createDropItemEventDispense(cause, entities, this.player.getWorld()); Sponge.getEventManager().post(event); if (!event.isCancelled()) { if (message.isFullStack()) { slot.poll(); } else { slot.poll(1); } this.finishSpawnEntityEvent(event); } } } private void resetDrag() { this.dragState = -1; this.dragSlots.clear(); } public void handleWindowClick(MessagePlayInClickWindow message) { if (this.openContainer == null) { return; } final int windowId = message.getWindowId(); if (windowId != this.openContainer.windowId) { return; } final int button = message.getButton(); final int mode = message.getMode(); final int slotIndex = message.getSlot(); // Drag mode if (mode == 5) { if (this.cursorItem == null) { this.resetDrag(); } else if (this.dragState != -1) { if ((this.dragState == 0 && (button == 1 || button == 2)) || (this.dragState == 1 && (button == 5 || button == 6))) { if (button == 2 || button == 6) { final Cause cause = Cause.builder().named(NamedCause.SOURCE, this.player).build(); if (button == 2) { int quantity = this.cursorItem.getQuantity(); int slots = this.dragSlots.size(); int itemsPerSlot = quantity / slots; int rest = quantity - itemsPerSlot * slots; final List<SlotTransaction> transactions = new ArrayList<>(); for (LanternSlot slot : this.dragSlots) { final ItemStack itemStack = this.cursorItem.copy(); itemStack.setQuantity(itemsPerSlot); transactions.addAll(slot.peekOfferFastTransactions(itemStack).getTransactions()); } ItemStackSnapshot newCursorItem = ItemStackSnapshot.NONE; if (rest > 0) { ItemStack itemStack = this.cursorItem.copy(); itemStack.setQuantity(rest); newCursorItem = itemStack.createSnapshot(); } final ItemStackSnapshot oldCursorItem = this.cursorItem.createSnapshot(); final Transaction<ItemStackSnapshot> cursorTransaction = new Transaction<>(oldCursorItem, newCursorItem); final ClickInventoryEvent.Drag.Primary event = SpongeEventFactory.createClickInventoryEventDragPrimary( cause, cursorTransaction, this.openContainer, transactions); finishInventoryEvent(event); resetDrag(); } else { int quantity = this.cursorItem.getQuantity(); int size = Math.min(this.dragSlots.size(), quantity); final List<SlotTransaction> transactions = new ArrayList<>(); for (LanternSlot slot : this.dragSlots) { final ItemStack itemStack = this.cursorItem.copy(); itemStack.setQuantity(1); transactions.addAll(slot.peekOfferFastTransactions(itemStack).getTransactions()); } quantity -= size; ItemStackSnapshot newCursorItem = ItemStackSnapshot.NONE; if (quantity > 0) { ItemStack itemStack = this.cursorItem.copy(); itemStack.setQuantity(quantity); newCursorItem = itemStack.createSnapshot(); } final ItemStackSnapshot oldCursorItem = this.cursorItem.createSnapshot(); final Transaction<ItemStackSnapshot> cursorTransaction = new Transaction<>(oldCursorItem, newCursorItem); final ClickInventoryEvent.Drag.Secondary event = SpongeEventFactory.createClickInventoryEventDragSecondary( cause, cursorTransaction, this.openContainer, transactions); this.finishInventoryEvent(event); this.resetDrag(); } } else { // Add slot final Optional<LanternSlot> optSlot = this.openContainer.getSlotAt(slotIndex); if (optSlot.isPresent()) { final LanternSlot slot = optSlot.get(); if (!(slot instanceof OutputSlot) && slot.isValidItem(this.cursorItem) && (slot.getRawItemStack() == null || ((LanternItemStack) this.cursorItem).isSimilar(slot.getRawItemStack())) && !this.dragSlots.contains(slot)) { this.dragSlots.add(slot); } } } } else { resetDrag(); } } else if (button == 0) { this.dragState = 0; } else if (button == 4) { this.dragState = 1; } } else if (this.dragState != -1) { resetDrag(); // Left/right click inside the inventory } else if (mode == 0 && (button == 0 || button == 1) && slotIndex != -999) { final Optional<LanternSlot> optSlot = this.openContainer.getSlotAt(slotIndex); if (optSlot.isPresent()) { final LanternSlot slot = optSlot.get(); final Cause cause = Cause.builder().named(NamedCause.SOURCE, this.player).build(); ClickInventoryEvent event; // Left click if (button == 0) { final List<SlotTransaction> transactions = new ArrayList<>(); Transaction<ItemStackSnapshot> cursorTransaction = null; if (this.cursorItem != null && !(slot instanceof OutputSlot)) { final PeekOfferTransactionsResult result = slot.peekOfferFastTransactions(this.cursorItem); if (result.getOfferResult().isSuccess()) { transactions.addAll(result.getTransactions()); cursorTransaction = new Transaction<>(this.cursorItem.createSnapshot(), LanternItemStack.toSnapshot(result.getOfferResult().getRest())); } else { final PeekSetTransactionsResult result1 = slot.peekSetTransactions(this.cursorItem); if (result1.getTransactionResult().getType().equals(InventoryTransactionResult.Type.SUCCESS)) { final Collection<ItemStackSnapshot> replaceItems = result1.getTransactionResult().getReplacedItems(); if (!replaceItems.isEmpty()) { cursorTransaction = new Transaction<>(this.cursorItem.createSnapshot(), replaceItems.iterator().next()); } else { cursorTransaction = new Transaction<>(this.cursorItem.createSnapshot(), ItemStackSnapshot.NONE); } transactions.addAll(result1.getTransactions()); } } } else if (this.cursorItem == null) { final PeekPollTransactionsResult result = slot.peekPollTransactions(stack -> true).orElse(null); if (result != null) { cursorTransaction = new Transaction<>(ItemStackSnapshot.NONE, LanternItemStack.toSnapshot(result.getPeekedItem())); transactions.addAll(result.getTransactions()); } else { cursorTransaction = new Transaction<>(ItemStackSnapshot.NONE, ItemStackSnapshot.NONE); } } if (cursorTransaction == null) { final ItemStackSnapshot cursorItem = LanternItemStack.toSnapshot(this.cursorItem); cursorTransaction = new Transaction<>(cursorItem, cursorItem); } event = SpongeEventFactory.createClickInventoryEventPrimary(cause, cursorTransaction, this.openContainer, transactions); // Right click } else { final List<SlotTransaction> transactions = new ArrayList<>(); Transaction<ItemStackSnapshot> cursorTransaction = null; if (this.cursorItem == null) { int stackSize = slot.getStackSize(); if (stackSize != 0) { stackSize = stackSize - (stackSize / 2); final PeekPollTransactionsResult result = slot.peekPollTransactions(stackSize, stack -> true).get(); transactions.addAll(result.getTransactions()); cursorTransaction = new Transaction<>(ItemStackSnapshot.NONE, result.getPeekedItem().createSnapshot()); } } else { final ItemStack itemStack = this.cursorItem.copy(); itemStack.setQuantity(1); final PeekOfferTransactionsResult result = slot.peekOfferFastTransactions(itemStack); if (result.getOfferResult().isSuccess()) { final ItemStackSnapshot oldCursor = this.cursorItem.createSnapshot(); int quantity = this.cursorItem.getQuantity() - 1; if (quantity <= 0) { cursorTransaction = new Transaction<>(oldCursor, ItemStackSnapshot.NONE); } else { final ItemStack newCursorItem = this.cursorItem.copy(); newCursorItem.setQuantity(quantity); cursorTransaction = new Transaction<>(oldCursor, newCursorItem.createSnapshot()); } transactions.addAll(result.getTransactions()); } else { final PeekSetTransactionsResult result1 = slot.peekSetTransactions(this.cursorItem); if (result1.getTransactionResult().getType().equals(InventoryTransactionResult.Type.SUCCESS) && result1.getTransactionResult().getRejectedItems().isEmpty()) { final Collection<ItemStackSnapshot> replaceItems = result1.getTransactionResult().getReplacedItems(); if (!replaceItems.isEmpty()) { setCursorItem(replaceItems.iterator().next().createStack()); cursorTransaction = new Transaction<>(this.cursorItem.createSnapshot(), replaceItems.iterator().next()); } else { cursorTransaction = new Transaction<>(this.cursorItem.createSnapshot(), ItemStackSnapshot.NONE); } transactions.addAll(result1.getTransactions()); } } } if (cursorTransaction == null) { final ItemStackSnapshot cursorItem = LanternItemStack.toSnapshot(this.cursorItem); cursorTransaction = new Transaction<>(cursorItem, cursorItem); } event = SpongeEventFactory.createClickInventoryEventSecondary(cause, cursorTransaction, this.openContainer, transactions); } finishInventoryEvent(event); } else { Lantern.getLogger().warn("Unknown slot index {} in container {}", slotIndex, this.openContainer); } // Shift + left/right click } else if (mode == 1 && (button == 0 || button == 1)) { Optional<LanternSlot> optSlot = this.openContainer.getSlotAt(slotIndex); if (optSlot.isPresent()) { final LanternSlot slot = optSlot.get(); final ItemStack itemStack = slot.peek().orElse(null); final Cause cause = Cause.builder().named(NamedCause.SOURCE, this.player).build(); final List<SlotTransaction> transactions = new ArrayList<>(); final ItemStackSnapshot cursorItem = LanternItemStack.toSnapshot(this.cursorItem); final Transaction<ItemStackSnapshot> cursorTransaction = new Transaction<>(cursorItem, cursorItem); if (itemStack != null) { AbstractMutableInventory inventory; final HumanMainInventory mainInventory = this.openContainer.playerInventory.getMain(); final boolean offhand = slot == this.openContainer.playerInventory.getOffhand(); PeekOfferTransactionsResult result; if ((windowId != 0 && this.openContainer.openInventory.getSlotIndex(slot) != -1) || (windowId == 0 && !mainInventory.isChild(slot) && !offhand)) { if (slot.isReverseShiftClickOfferOrder()) { inventory = this.openContainer.playerInventory.getInventoryView(HumanInventoryView.REVERSE_MAIN_AND_HOTBAR); } else { inventory = this.openContainer.playerInventory.getInventoryView(HumanInventoryView.PRIORITY_MAIN_AND_HOTBAR); } result = inventory.peekOfferFastTransactions(itemStack.copy()); } else { inventory = this.openContainer.openInventory.query(inv -> !mainInventory.isChild(inv) && inv instanceof Slot && ((LanternSlot) inv).doesAllowShiftClickOffer() && !(inv instanceof OutputSlot), false); result = inventory.peekOfferFastTransactions(itemStack.copy()); if (result.getOfferResult().getRest() != null) { if (slot.parent() instanceof LanternHotbar || offhand) { inventory = this.openContainer.playerInventory.getInventoryView(HumanInventoryView.MAIN); } else { inventory = this.openContainer.playerInventory.getHotbar(); } PeekOfferTransactionsResult result1 = inventory.peekOfferFastTransactions(result.getOfferResult().getRest()); if (result1.getOfferResult().isSuccess()) { result1.getTransactions().addAll(result.getTransactions()); result = result1; } } } if (result.getOfferResult().isSuccess()) { transactions.addAll(result.getTransactions()); final ItemStack rest = result.getOfferResult().getRest(); if (rest != null) { transactions.addAll(slot.peekPollTransactions( itemStack.getQuantity() - rest.getQuantity(), stack -> true).get().getTransactions()); } else { transactions.addAll(slot.peekPollTransactions( stack -> true).get().getTransactions()); } } } final ClickInventoryEvent.Shift event; if (button == 0) { event = SpongeEventFactory.createClickInventoryEventShiftPrimary( cause, cursorTransaction, this.openContainer, transactions); } else { event = SpongeEventFactory.createClickInventoryEventShiftSecondary( cause, cursorTransaction, this.openContainer, transactions); } finishInventoryEvent(event); } else { Lantern.getLogger().warn("Unknown slot index {} in container {}", slotIndex, this.openContainer); } // Double click } else if (mode == 6 && button == 0) { final Optional<LanternSlot> optSlot = this.openContainer.getSlotAt(slotIndex); if (optSlot.isPresent()) { final Cause cause = Cause.builder().named(NamedCause.SOURCE, this.player).build(); final ItemStackSnapshot oldItem = LanternItemStack.toSnapshot(this.cursorItem); ItemStackSnapshot newItem = oldItem; final List<SlotTransaction> transactions = new ArrayList<>(); if (this.cursorItem != null) { final ItemStack cursorItem = this.cursorItem.copy(); int quantity = cursorItem.getQuantity(); final int maxQuantity = cursorItem.getMaxStackQuantity(); if (quantity < maxQuantity) { final AbstractMutableInventory inventory; if (windowId != 0) { inventory = new AbstractChildrenInventory(null, null, Arrays.asList( this.openContainer.openInventory, this.openContainer.playerInventory .getInventoryView(HumanInventoryView.PRIORITY_MAIN_AND_HOTBAR))); } else { inventory = this.openContainer.playerInventory .getInventoryView(HumanInventoryView.ALL_PRIORITY_MAIN); } // Try first to get enough unfinished stacks PeekPollTransactionsResult peekResult = inventory.peekPollTransactions(maxQuantity - quantity, stack -> stack.getQuantity() < stack.getMaxStackQuantity() && ((LanternItemStack) cursorItem).isSimilar(stack)).orElse(null); if (peekResult != null) { quantity += peekResult.getPeekedItem().getQuantity(); transactions.addAll(peekResult.getTransactions()); } // Get the last items for the stack from a full stack if (quantity <= maxQuantity) { peekResult = this.openContainer.peekPollTransactions(maxQuantity - quantity, stack -> stack.getQuantity() >= stack.getMaxStackQuantity() && ((LanternItemStack) cursorItem).isSimilar(stack)).orElse(null); if (peekResult != null) { quantity += peekResult.getPeekedItem().getQuantity(); transactions.addAll(peekResult.getTransactions()); } } cursorItem.setQuantity(quantity); newItem = cursorItem.createSnapshot(); } } final Transaction<ItemStackSnapshot> cursorTransaction = new Transaction<>(oldItem, newItem); final ClickInventoryEvent.Double event = SpongeEventFactory.createClickInventoryEventDouble( cause, cursorTransaction, this.openContainer, transactions); finishInventoryEvent(event); } else { Lantern.getLogger().warn("Unknown slot index {} in container {}", slotIndex, this.openContainer); } // Number keys } else if (mode == 2) { final Optional<LanternSlot> optSlot = this.openContainer.getSlotAt(slotIndex); if (optSlot.isPresent()) { final LanternSlot slot = optSlot.get(); final LanternHotbar hotbar = this.openContainer.playerInventory.getHotbar(); final Optional<LanternSlot> optHotbarSlot = hotbar.getSlotAt(button); if (optHotbarSlot.isPresent()) { final LanternSlot hotbarSlot = optHotbarSlot.get(); final Cause cause = Cause.builder().named(NamedCause.SOURCE, this.player).build(); final List<SlotTransaction> transactions = new ArrayList<>(); final Transaction<ItemStackSnapshot> cursorTransaction; if (this.cursorItem == null) { cursorTransaction = new Transaction<>(ItemStackSnapshot.NONE, ItemStackSnapshot.NONE); ItemStack otherItemStack = slot.getRawItemStack(); ItemStack hotbarItemStack = hotbarSlot.getRawItemStack(); ItemStackSnapshot otherItem = LanternItemStack.toSnapshot(otherItemStack); ItemStackSnapshot hotbarItem = LanternItemStack.toSnapshot(hotbarItemStack); if (!(otherItem != ItemStackSnapshot.NONE && (!hotbarSlot.isValidItem(otherItemStack) || otherItemStack.getQuantity() > hotbarSlot.getMaxStackSize())) && !(hotbarItem != ItemStackSnapshot.NONE && (!slot.isValidItem(hotbarItemStack) || hotbarItemStack.getQuantity() > slot.getMaxStackSize()))) { transactions.add(new SlotTransaction(slot, otherItem, hotbarItem)); transactions.add(new SlotTransaction(hotbarSlot, hotbarItem, otherItem)); } } else { final ItemStackSnapshot cursorItem = this.cursorItem.createSnapshot(); cursorTransaction = new Transaction<>(cursorItem, cursorItem); } final ClickInventoryEvent.NumberPress event = SpongeEventFactory.createClickInventoryEventNumberPress( cause, cursorTransaction, this.openContainer, transactions, button); finishInventoryEvent(event); } else { Lantern.getLogger().warn("Unknown hotbar slot index {}", mode); } } else { Lantern.getLogger().warn("Unknown slot index {} in container {}", slotIndex, this.openContainer); } // Left/right click outside inventory // Drop or control drop click a slot } else if ((mode == 4 || mode == 0) && (button == 0 || button == 1)) { ClickInventoryEvent.Drop event = null; final Cause cause = Cause.builder().named("SpawnCause", SpawnCause.builder() .type(SpawnTypes.DROPPED_ITEM).build()).named(NamedCause.SOURCE, this.player).build(); final List<Entity> entities = new ArrayList<>(); final World world = this.player.getWorld(); final Transaction<ItemStackSnapshot> cursorTransaction; final List<SlotTransaction> slotTransactions = new ArrayList<>(); if (slotIndex == -999) { ItemStackSnapshot oldItem = ItemStackSnapshot.NONE; ItemStackSnapshot newItem = ItemStackSnapshot.NONE; if (this.cursorItem != null) { oldItem = this.cursorItem.createSnapshot(); if (button != 0) { final ItemStack stack = this.cursorItem.copy(); stack.setQuantity(stack.getQuantity() - 1); newItem = LanternItemStack.toSnapshot(stack); stack.setQuantity(1); entities.add(createDroppedItem(LanternItemStack.toSnapshot(stack))); } else { entities.add(createDroppedItem(oldItem)); } } cursorTransaction = new Transaction<>(oldItem, newItem); if (button == 0) { event = SpongeEventFactory.createClickInventoryEventDropOutsidePrimary(cause, cursorTransaction, entities, this.openContainer, world, slotTransactions); } else { event = SpongeEventFactory.createClickInventoryEventDropOutsideSecondary(cause, cursorTransaction, entities, this.openContainer, world, slotTransactions); } } else { final ItemStackSnapshot item = LanternItemStack.toSnapshot(this.cursorItem); cursorTransaction = new Transaction<>(item, item); final Optional<LanternSlot> optSlot = this.openContainer.getSlotAt(slotIndex); if (optSlot.isPresent()) { final LanternSlot slot = optSlot.get(); final Optional<PeekPollTransactionsResult> result = button == 0 ? slot.peekPollTransactions(1, itemStack -> true) : slot.peekPollTransactions(itemStack -> true); if (result.isPresent()) { final List<SlotTransaction> transactions = result.get().getTransactions(); slotTransactions.addAll(transactions); final ItemStack itemStack = transactions.get(0).getOriginal().createStack(); itemStack.setQuantity(itemStack.getQuantity() - transactions.get(0).getFinal().getCount()); entities.add(createDroppedItem(itemStack.createSnapshot())); } if (button == 0) { event = SpongeEventFactory.createClickInventoryEventDropSingle(cause, cursorTransaction, entities, this.openContainer, world, slotTransactions); } else { event = SpongeEventFactory.createClickInventoryEventDropFull(cause, cursorTransaction, entities, this.openContainer, world, slotTransactions); } } else { Lantern.getLogger().warn("Unknown slot index {} in container {}", slotIndex, this.openContainer); } } if (event != null) { finishInventoryEvent(event); } // Middle lock a slot } else if (mode == 3) { final Cause cause = Cause.builder().named(NamedCause.SOURCE, this.player).build(); final ItemStackSnapshot oldItem = LanternItemStack.toSnapshot(this.cursorItem); Transaction<ItemStackSnapshot> cursorTransaction = null; final Optional<GameMode> gameMode = this.player.get(Keys.GAME_MODE); if (gameMode.isPresent() && gameMode.get().equals(GameModes.CREATIVE) && this.cursorItem == null) { final Optional<LanternSlot> optSlot = this.openContainer.getSlotAt(slotIndex); if (optSlot.isPresent()) { final LanternSlot slot = optSlot.get(); final ItemStack stack = slot.peek().orElse(null); if (stack != null) { stack.setQuantity(stack.getMaxStackQuantity()); cursorTransaction = new Transaction<>(oldItem, stack.createSnapshot()); } } else { Lantern.getLogger().warn("Unknown slot index {} in container {}", slotIndex, this.openContainer); } } if (cursorTransaction == null) { cursorTransaction = new Transaction<>(oldItem, oldItem); } final ClickInventoryEvent.Middle event = SpongeEventFactory.createClickInventoryEventMiddle( cause, cursorTransaction, this.openContainer, new ArrayList<>()); finishInventoryEvent(event); } } private Entity createDroppedItem(ItemStackSnapshot snapshot) { final Entity entity = this.player.getWorld().createEntity(EntityTypes.ITEM, this.player.getPosition()); entity.offer(Keys.REPRESENTED_ITEM, snapshot); return entity; } private void finishSpawnEntityEvent(SpawnEntityEvent event) { if (event.isCancelled()) { return; } final Cause cause = Cause.source(event).build(); for (Entity entity : event.getEntities()) { entity.getWorld().spawnEntity(entity, cause); } } private void finishInventoryEvent(ClickInventoryEvent event) { final List<SlotTransaction> slotTransactions = event.getTransactions(); Sponge.getEventManager().post(event); if (!event.isCancelled()) { if (!(event instanceof ClickInventoryEvent.Creative)) { final Transaction<ItemStackSnapshot> cursorTransaction = event.getCursorTransaction(); if (!cursorTransaction.isValid()) { updateCursorItem(); } else { setCursorItem(cursorTransaction.getFinal().createStack()); } } for (SlotTransaction slotTransaction : slotTransactions) { if (slotTransaction.isValid()) { slotTransaction.getSlot().set(slotTransaction.getFinal().createStack()); } else { // Force the slot to update this.openContainer.queueSlotChange(slotTransaction.getSlot()); } } if (event instanceof SpawnEntityEvent) { finishSpawnEntityEvent((SpawnEntityEvent) event); } } else { updateCursorItem(); for (SlotTransaction slotTransaction : slotTransactions) { // Force the slot to update this.openContainer.queueSlotChange(slotTransaction.getSlot()); } } } }