Dupe prevention update

This commit is contained in:
NotAlexNoyle committed 2026-06-01 14:41:34 -04:00
1 parent 92709bf88d
commit b5e589ba41
14 files changed
+415 -508

No files matched your search

+1 -1
View File
@@ -25,7 +25,7 @@ kotlin { jvmToolchain(17) }
/* ----------------------------- Metadata ------------------------------ */
group = "net.trueog.shulkerstacking-og" // Declare bundle identifier.
version = "1.1" // Declare plugin version (will be in .jar).
version = "1.2" // Declare plugin version (will be in .jar).
val apiVersion = "1.19" // Declare minecraft server target version.
@@ -1,6 +1,7 @@
package plugin.Listeners;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryMoveItemEvent;
import org.bukkit.inventory.Inventory;
@@ -10,7 +11,7 @@ import plugin.ShulkerBoxHelpers.ShulkerBoxUtils;
public class ShulkerBoxHopperHandler implements Listener {
@EventHandler
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void shulkerBoxHopperHandler(InventoryMoveItemEvent event) {
ItemStack eventItem = event.getItem();
@@ -22,9 +23,15 @@ public class ShulkerBoxHopperHandler implements Listener {
}
int toMove = eventItem.getAmount();
if (toMove <= 0) {
return;
}
event.setCancelled(true);
int toMove = eventItem.getAmount();
for (int i = 0; i < eventSource.getSize() && toMove > 0; i++) {
ItemStack sourceItem = eventSource.getItem(i);
@@ -48,12 +55,8 @@ public class ShulkerBoxHopperHandler implements Listener {
if (moved > 0) {
sourceItem.setAmount(sourceItem.getAmount() - moved);
eventSource.setItem(i, sourceItem.getAmount() <= 0 ? null : sourceItem);
toMove -= moved;
if (sourceItem.getAmount() <= 0) {
eventSource.setItem(i, null);
}
}
@@ -1,6 +1,7 @@
package plugin.Listeners;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.ItemMergeEvent;
import org.bukkit.inventory.ItemStack;
@@ -9,7 +10,7 @@ import plugin.ShulkerBoxHelpers.ShulkerBoxUtils;
public class ShulkerBoxMergeHandler implements Listener {
@EventHandler
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onItemMerge(ItemMergeEvent event) {
ItemStack target = event.getTarget().getItemStack();
@@ -38,6 +39,8 @@ public class ShulkerBoxMergeHandler implements Listener {
int moved = Math.min(space, source.getAmount());
target.setAmount(target.getAmount() + moved);
// Write back because Item#getItemStack may return a defensive copy.
event.getTarget().setItemStack(target);
int remaining = source.getAmount() - moved;
if (remaining <= 0) {
@@ -3,6 +3,7 @@ package plugin.Listeners;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityPickupItemEvent;
import org.bukkit.inventory.Inventory;
@@ -12,7 +13,7 @@ import plugin.ShulkerBoxHelpers.ShulkerBoxUtils;
public class ShulkerBoxPickupHandler implements Listener {
@EventHandler
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void shulkerBoxStacking(EntityPickupItemEvent event) {
ItemStack item = event.getItem().getItemStack();
@@ -33,8 +34,9 @@ public class ShulkerBoxPickupHandler implements Listener {
Player player = (Player) event.getEntity();
Inventory playerInventory = player.getInventory();
int original = item.getAmount();
int remaining = item.getAmount();
int remaining = original;
remaining = ShulkerBoxUtils.mergeIntoExistingStacks(playerInventory, item, remaining);
remaining = ShulkerBoxUtils.fillEmptySlots(playerInventory, item, remaining);
@@ -46,8 +48,15 @@ public class ShulkerBoxPickupHandler implements Listener {
}
ItemStack remainingItem = new ItemStack(item.getType(), remaining);
event.getItem().setItemStack(remainingItem);
// Preserve entity metadata when the inventory could not accept any items.
if (remaining < original) {
ItemStack remainingItem = item.clone();
remainingItem.setAmount(remaining);
event.getItem().setItemStack(remainingItem);
}
player.updateInventory();
}
@@ -1,6 +1,11 @@
package plugin.Listeners;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Item;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
@@ -10,126 +15,142 @@ import plugin.ShulkerBoxHelpers.DoubleClick;
import plugin.ShulkerBoxHelpers.NormalLeftClick;
import plugin.ShulkerBoxHelpers.NormalRightClick;
import plugin.ShulkerBoxHelpers.ShiftRightLeftClick;
import plugin.ShulkerBoxHelpers.ShulkerBoxUtils;
public class ShulkerBoxStackingController implements Listener {
@EventHandler
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void shulkerBoxStacking(InventoryClickEvent event) {
if (event.getCurrentItem() == null) {
return;
}
if (event.getCursor() == null) {
return;
}
ItemStack currentItem = event.getCurrentItem();
ItemStack cursorItem = event.getCursor();
ClickType click = event.getClick();
// If the player combines two shulker stacks...
if (event.getClick().equals(ClickType.LEFT)) {
boolean currentIsShulker = ShulkerBoxUtils.isShulkerBoxMaterial(currentItem);
boolean cursorIsShulker = ShulkerBoxUtils.isShulkerBoxMaterial(cursorItem);
if (currentItem.getType().toString().contains("SHULKER_BOX")) {
if (click.equals(ClickType.LEFT) && currentIsShulker && cursorIsShulker
&& currentItem.getType().equals(cursorItem.getType()))
{
if (cursorItem.getType().equals(currentItem.getType())) {
if (ShulkerBoxUtils.isEmptyShulkerBox(currentItem) && ShulkerBoxUtils.isEmptyShulkerBox(cursorItem)) {
NormalLeftClick.NormalLeftClickShulkerBox(currentItem, cursorItem, event);
return;
NormalLeftClick.NormalLeftClickShulkerBox(currentItem, cursorItem, event);
}
} else {
// Filled shulkers cannot use vanilla NBT stacking.
event.setCancelled(true);
}
return;
}
// if the player sets down just one shulker box from a stack
if (event.getClick().equals(ClickType.RIGHT)) {
if (click.equals(ClickType.RIGHT) && currentIsShulker && cursorIsShulker
&& currentItem.getType().equals(cursorItem.getType()))
{
if (currentItem.getType().toString().contains("SHULKER_BOX")) {
if (ShulkerBoxUtils.isEmptyShulkerBox(currentItem) && ShulkerBoxUtils.isEmptyShulkerBox(cursorItem)) {
if (cursorItem.getType().equals(currentItem.getType())) {
NormalRightClick.NormalRightClickShulkerBox(currentItem, cursorItem, event);
NormalRightClick.NormalRightClickShulkerBox(currentItem, cursorItem, event);
} else {
}
event.setCancelled(true);
}
}
// if the player puts down a shulker box stack
if (event.getClick().equals(ClickType.LEFT)) {
if (cursorItem.getType().toString().contains("SHULKER_BOX")) {
// check if the shulker box stack only contains one shulker box
if (cursorItem.getAmount() != 1) {
// remove the stack from their cursor
// and replace it with whatever was in the slot
event.getWhoClicked().setItemOnCursor(currentItem);
// place down the whole stack
event.setCurrentItem(cursorItem);
// stop default behaviors
event.setCancelled(true);
return;
}
}
return;
}
// if the player double clicked in the inventory on a shulker box
// gather all of the shulker boxes in the inventory to the player's cursor
// including in their inventory
// Place the whole empty cursor stack into air or a non-shulker slot.
if (click.equals(ClickType.LEFT) && cursorIsShulker && cursorItem.getAmount() > 1) {
if (event.getClick().equals(ClickType.DOUBLE_CLICK)) {
if (!ShulkerBoxUtils.isEmptyShulkerBox(cursorItem)) {
if (cursorItem.getType().toString().contains("SHULKER_BOX")) {
DoubleClick.DoubleClickShulkerBox(cursorItem, event);
return;
}
if (currentItem == null || currentItem.getType() == Material.AIR) {
event.setCurrentItem(cursorItem);
event.getWhoClicked().setItemOnCursor(new ItemStack(Material.AIR));
event.setCancelled(true);
} else if (!currentIsShulker) {
event.getWhoClicked().setItemOnCursor(currentItem);
event.setCurrentItem(cursorItem);
event.setCancelled(true);
}
return;
}
// if the player shift clicks on a shukler box stack
// we need to handle the stacking logic
if (event.getClick().equals(ClickType.SHIFT_LEFT) || event.getClick().equals(ClickType.SHIFT_RIGHT)) {
if (click.equals(ClickType.DOUBLE_CLICK) && cursorIsShulker) {
if (currentItem.getType().toString().contains("SHULKER_BOX")) {
if (ShulkerBoxUtils.isEmptyShulkerBox(cursorItem)) {
DoubleClick.DoubleClickShulkerBox(cursorItem, event);
}
return;
}
if ((click.equals(ClickType.SHIFT_LEFT) || click.equals(ClickType.SHIFT_RIGHT)) && currentIsShulker) {
if (ShulkerBoxUtils.isEmptyShulkerBox(currentItem)) {
ShiftRightLeftClick.ShiftRightLeftClickShulkerBox(event);
}
return;
}
// if the player users CTRL+<drop> on the shulkers
// drop the whole stack, instead of just one
if (event.getClick().equals(ClickType.CONTROL_DROP)) {
// Drop the whole empty stack; vanilla correctly drops filled single boxes.
if (click.equals(ClickType.CONTROL_DROP) && currentIsShulker && currentItem.getAmount() > 1) {
if (currentItem.getType().toString().contains("SHULKER_BOX")) {
if (!ShulkerBoxUtils.isEmptyShulkerBox(currentItem)) {
event.getWhoClicked().getWorld().dropItem(event.getWhoClicked().getLocation(), event.getCurrentItem(),
(Item) ->
{
Item.setVelocity(
event.getWhoClicked().getLocation().getDirection().multiply(0.35).setY(0.25));
});
event.getCurrentItem().setAmount(0);
return;
}
event.setCancelled(true);
HumanEntity who = event.getWhoClicked();
Location dropLoc = who.getLocation();
ItemStack dropStack = currentItem.clone();
Item dropped = who.getWorld().dropItem(dropLoc, dropStack, (Item entity) -> {
entity.setVelocity(dropLoc.getDirection().multiply(0.35).setY(0.25));
});
// Clear the slot only if ItemSpawnEvent allowed the drop entity.
if (dropped != null && dropped.isValid() && !dropped.isDead()) {
event.setCurrentItem(new ItemStack(Material.AIR));
}
return;
}
// Block creative middle-clone because empty shulkers would be synthesized.
if (click.equals(ClickType.MIDDLE) && currentIsShulker) {
event.setCancelled(true);
}
}
@@ -0,0 +1,20 @@
package plugin.Listeners;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import plugin.ShulkerStackingOG;
public class ShulkerCleanupHandler implements Listener {
@EventHandler(priority = EventPriority.MONITOR)
public void onQuit(PlayerQuitEvent event) {
// Remove the close flag when the player quits to prevent stale entries.
ShulkerStackingOG.isInventoryClosed.remove(event.getPlayer().getUniqueId());
}
}
@@ -13,10 +13,12 @@ public class ShulkerDragDupePrevention implements Listener {
@EventHandler(priority = EventPriority.LOWEST)
public void shulkerDragDupePrevent(InventoryCloseEvent event) {
ShulkerStackingOG.isInventoryClosed.put(event.getPlayer().getUniqueId(), true);
java.util.UUID uuid = event.getPlayer().getUniqueId();
ShulkerStackingOG.isInventoryClosed.put(uuid, true);
// Reset flags only if present so delayed tasks cannot recreate stale state.
Bukkit.getScheduler().runTaskLater(ShulkerStackingOG.getPlugin(),
() -> ShulkerStackingOG.isInventoryClosed.put(event.getPlayer().getUniqueId(), false), 2);
() -> ShulkerStackingOG.isInventoryClosed.computeIfPresent(uuid, (k, v) -> false), 2);
}
}
}
@@ -1,6 +1,7 @@
package plugin.Listeners;
import java.util.ArrayList;
import java.util.Set;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.Material;
@@ -11,131 +12,204 @@ import org.bukkit.event.Listener;
import org.bukkit.event.inventory.DragType;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.ItemStack;
import plugin.ShulkerBoxHelpers.ShulkerBoxUtils;
import plugin.ShulkerStackingOG;
public class ShulkerDragHandler implements Listener {
@EventHandler(priority = EventPriority.MONITOR)
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void shulkerBoxDrag(InventoryDragEvent event) {
if (!event.getOldCursor().getType().toString().contains("SHULKER_BOX")) {
ItemStack cursor = event.getOldCursor();
if (!ShulkerBoxUtils.isShulkerBoxMaterial(cursor)) {
return;
}
// Cancel leaked filled stacks before vanilla can split duplicate NBT.
if (!ShulkerBoxUtils.isEmptyShulkerBox(cursor)) {
if (cursor.getAmount() > 1) {
event.setCancelled(true);
}
return;
}
// Disable default behavior.
event.setCancelled(true);
final int total = cursor.getAmount();
if (total < 1) {
return;
}
final Material shulkerType = cursor.getType();
final DragType dragType = event.getType();
final Set<Integer> rawSlots = event.getRawSlots();
final int slotCount = rawSlots.size();
if (slotCount == 0) {
return;
}
final int perSlot;
if (dragType.equals(DragType.SINGLE)) {
perSlot = 1;
} else if (dragType.equals(DragType.EVEN)) {
perSlot = total / slotCount;
} else {
return;
}
if (perSlot <= 0) {
return;
}
final InventoryView view = event.getView();
Bukkit.getScheduler().runTask(ShulkerStackingOG.getPlugin(), () -> {
// Check if the inventory was closed in the last tick.
// This prevents a duplication glitch from occurring.
if (ShulkerStackingOG.isInventoryClosed.get(event.getWhoClicked().getUniqueId())) {
// Skip if inventory just closed: vanilla already ejected cursor contents.
Boolean closed = ShulkerStackingOG.isInventoryClosed.get(event.getWhoClicked().getUniqueId());
if (closed != null && closed) {
return;
}
ItemStack shulkerBoxes = event.getOldCursor();
int numberOfSlotsDragged = event.getInventorySlots().size();
ArrayList<Integer> slotsDragged = new ArrayList<>(event.getInventorySlots().stream().toList());
Inventory dragInventory = event.getView().getBottomInventory();
// Right click drag.
if (event.getType().equals(DragType.SINGLE)) {
// Abort if vanilla dropped the cursor while the player went offline.
if (!(event.getWhoClicked() instanceof Player player) || !player.isOnline()) {
// If the player drags a stack of shulker boxes.
if (shulkerBoxes.getAmount() <= 1) {
return;
}
// Set each slot to the appropriate amount of shulkers.
for (int i = 0; i < numberOfSlotsDragged; i++) {
if (dragInventory.getItem(slotsDragged.get(i)) == null
|| dragInventory.getItem(slotsDragged.get(i)).getType() == Material.AIR)
{
dragInventory.setItem(slotsDragged.get(i), new ItemStack(shulkerBoxes.getType(), 1));
} else if (dragInventory.getItem(slotsDragged.get(i)) != null
&& dragInventory.getItem(slotsDragged.get(i)).getType().equals(shulkerBoxes.getType()))
{
dragInventory.getItem(slotsDragged.get(i))
.setAmount(dragInventory.getItem(slotsDragged.get(i)).getAmount() + 1);
}
}
// remove the shulker boxes from the player's cursor
event.getWhoClicked().setItemOnCursor(
new ItemStack(shulkerBoxes.getType(), shulkerBoxes.getAmount() - numberOfSlotsDragged));
return;
}
// left click drag
else if (event.getType().equals(DragType.EVEN)) {
// if the player drags a stack of shulker boxes
if (shulkerBoxes.getAmount() <= 1) {
// Abort if a Q-drop, hotbar swap, or outside-click changed the cursor.
ItemStack live = player.getItemOnCursor();
if (!ShulkerBoxUtils.isEmptyShulkerBox(live) || !live.getType().equals(shulkerType)
|| live.getAmount() < total)
{
return;
return;
}
}
// set each slot to the appropriate amount of shulkers
for (int i = 0; i < numberOfSlotsDragged; i++) {
// Increment before slot writes so exceptions bias toward loss, not duplication.
int[] placedRef = new int[] { 0 };
int[] pendingRef = new int[] { 0 };
try {
if (dragInventory.getItem(slotsDragged.get(i)) == null
|| dragInventory.getItem(slotsDragged.get(i)).getType() == Material.AIR)
{
for (Integer rawSlot : rawSlots) {
ItemStack newShulkerBoxes = new ItemStack(shulkerBoxes.getType(),
shulkerBoxes.getAmount() / numberOfSlotsDragged);
dragInventory.setItem(slotsDragged.get(i), newShulkerBoxes);
if (newShulkerBoxes.getAmount() > 64) {
if (placedRef[0] >= total) {
shulkerBoxes.setAmount(shulkerBoxes.getAmount() - (64 - shulkerBoxes.getAmount()));
newShulkerBoxes.setAmount(64);
slotsDragged.remove(i);
break;
}
dragInventory.setItem(slotsDragged.get(i), newShulkerBoxes);
} else if (dragInventory.getItem(slotsDragged.get(i)) != null
&& dragInventory.getItem(slotsDragged.get(i)).getType().equals(shulkerBoxes.getType()))
{
ItemStack newShulkerBoxes = new ItemStack(shulkerBoxes.getType(),
shulkerBoxes.getAmount() / numberOfSlotsDragged);
dragInventory.getItem(slotsDragged.get(i))
.setAmount(dragInventory.getItem(slotsDragged.get(i)).getAmount()
+ shulkerBoxes.getAmount() / numberOfSlotsDragged);
if (newShulkerBoxes.getAmount() > 64) {
shulkerBoxes.setAmount(shulkerBoxes.getAmount() - (64 - shulkerBoxes.getAmount()));
newShulkerBoxes.setAmount(64);
slotsDragged.remove(i);
break;
}
dragInventory.setItem(slotsDragged.get(i), newShulkerBoxes);
break;
}
Inventory targetInv = view.getInventory(rawSlot);
if (targetInv == null) {
continue;
}
int slotIdx = view.convertSlot(rawSlot);
if (slotIdx < 0 || slotIdx >= targetInv.getSize()) {
continue;
}
ItemStack existing = targetInv.getItem(slotIdx);
int desired = Math.min(perSlot, total - placedRef[0]);
if (existing == null || existing.getType() == Material.AIR) {
int toPlace = Math.min(desired, 64);
pendingRef[0] = toPlace;
placedRef[0] += toPlace;
targetInv.setItem(slotIdx, new ItemStack(shulkerType, toPlace));
pendingRef[0] = 0;
continue;
}
if (!ShulkerBoxUtils.isEmptyShulkerBox(existing)) {
continue;
}
if (!existing.getType().equals(shulkerType)) {
continue;
}
int space = 64 - existing.getAmount();
if (space <= 0) {
continue;
}
int toAdd = Math.min(desired, space);
pendingRef[0] = toAdd;
placedRef[0] += toAdd;
existing.setAmount(existing.getAmount() + toAdd);
targetInv.setItem(slotIdx, existing);
pendingRef[0] = 0;
}
event.getWhoClicked().setItemOnCursor(
new ItemStack(shulkerBoxes.getType(), shulkerBoxes.getAmount() % numberOfSlotsDragged));
((Player) event.getWhoClicked()).updateInventory();
} catch (RuntimeException exception) {
if (pendingRef[0] > 0) {
ShulkerStackingOG.getPlugin().getLogger().log(Level.SEVERE,
"Suspected loss of up to " + pendingRef[0] + " " + shulkerType
+ " shulker boxes during an inventory drag for " + player.getName() + " ("
+ player.getUniqueId() + ").",
exception);
}
throw exception;
} finally {
int leftover = total - placedRef[0];
if (leftover <= 0) {
player.setItemOnCursor(new ItemStack(Material.AIR));
} else {
player.setItemOnCursor(new ItemStack(shulkerType, leftover));
}
player.updateInventory();
}
@@ -14,49 +14,33 @@ public class DoubleClick {
public static void DoubleClickShulkerBox(ItemStack cursorItem, InventoryClickEvent event) {
// check that the held shulker box is empty
// Only empty shulkers can be collected.
Inventory heldShulkerInventory = ((ShulkerBox) ((BlockStateMeta) cursorItem.getItemMeta()).getBlockState())
.getInventory();
if (heldShulkerInventory.isEmpty()) {
// loop through the inventory, and the player's inventory
// and count up the shulker boxes
// ignore boxes in result slots
// delete the shulker boxes, up to 64, and set the cursor stack
// to the number of boxes, up to 64
// Collect up to 64 matching empty shulkers from both inventories.
InventoryView eventView = event.getView();
Inventory playerInventory = eventView.getBottomInventory();
Inventory containerInventory = eventView.getTopInventory();
Material shulkerType = cursorItem.getType();
int containerSize = containerInventory.getSize();
// store a temporary inventory so that we can handle
// full shulker boxes
// Use temporary inventories to skip filled shulkers without removing them.
Inventory tempPlayerInventory = Bukkit.createInventory(null, 36);
tempPlayerInventory.setContents(playerInventory.getContents());
// make the inventory size the max of 54 so that it will hold any size inventory
// Use the maximum container size so every view fits.
Inventory tempContainerInventory = Bukkit.createInventory(null, 54);
tempContainerInventory.setContents(containerInventory.getContents());
// use these two inventories to loop through the inventories
// we can delete full shulkers from these temporary inventories
// without deleting them from the main inventory
// and then just copy over the empty shulker deletes to the main
// inventories
// loop through the container inventory until there are no
// more shulker boxes, counting them up, and removing them
// up to 64 boxes
// Collect empty shulkers from the container first.
int shulkerBoxCount = cursorItem.getAmount();
while (tempContainerInventory.first(shulkerType) != -1) {
int firstShulker = tempContainerInventory.first(shulkerType);
// grab the inventory of the shulker box in that slot
Inventory shulkerInventory = ((ShulkerBox) ((BlockStateMeta) tempContainerInventory
.getItem(firstShulker).getItemMeta()).getBlockState()).getInventory();
// if that inventory is not empty, delete it from the temporary inventory, and
// continue on
// otherwise, do the rest of the logic
// Skip filled shulkers in the temporary inventory.
if (!shulkerInventory.isEmpty()) {
tempContainerInventory.setItem(firstShulker, new ItemStack(Material.AIR));
@@ -64,8 +48,7 @@ public class DoubleClick {
}
// if the shulker box is in a RESULT slot, then break from
// the loop, since that is always the last slot
// Result slots terminate the top-inventory scan.
InventoryType.SlotType slotType = eventView.getSlotType(firstShulker);
if (slotType.equals(InventoryType.SlotType.RESULT)) {
@@ -73,24 +56,18 @@ public class DoubleClick {
}
// if the shulker box count would have gone above 64
ItemStack shulkerStackItem = containerInventory.getItem(firstShulker);
int shulkerStackAmount = shulkerStackItem.getAmount();
if (shulkerBoxCount + shulkerStackAmount > 64) {
// get how many shulker boxes we need to get to 64
int shulkersRequired = 64 - shulkerBoxCount;
// remove that many boxes from the stack
shulkerStackItem.setAmount(shulkerStackAmount - shulkersRequired);
shulkerBoxCount += shulkersRequired;
// break from the loop, since we have a stack of shulkers collected
break;
} else {
// add the shulkerStackAmount to shulkerBoxCount
shulkerBoxCount += shulkerStackAmount;
// remove the shulkers from that slot
containerInventory.setItem(firstShulker, new ItemStack(Material.AIR));
tempContainerInventory.setItem(firstShulker, new ItemStack(Material.AIR));
@@ -98,28 +75,22 @@ public class DoubleClick {
}
// loop through the player's inventory
// Collect empty shulkers from the player's inventory next.
while (tempPlayerInventory.first(shulkerType) != -1) {
int firstShulker = tempPlayerInventory.first(shulkerType);
// grab the inventory of the shulker box in that slot
Inventory shulkerInventory = ((ShulkerBox) ((BlockStateMeta) tempPlayerInventory.getItem(firstShulker)
.getItemMeta()).getBlockState()).getInventory();
// if that inventory is not empty, delete it from the temporary inventory, and
// continue on
// otherwise, do the rest of the logic
// Skip filled shulkers in the temporary inventory.
if (!shulkerInventory.isEmpty()) {
// remove this non-empty shulker from the temporary inventory
tempPlayerInventory.setItem(firstShulker, new ItemStack(Material.AIR));
continue;
}
// if the shulker box is in a RESULT slot, then break from
// the loop, since that is always the last slot
// add the container's size to the here to offset it to the player's inventory
// Offset by the container size before checking the raw slot type.
InventoryType.SlotType slotType = eventView.getSlotType(firstShulker + containerSize);
if (slotType.equals(InventoryType.SlotType.RESULT)) {
@@ -127,24 +98,18 @@ public class DoubleClick {
}
// if the shulker box count would have gone above 64
ItemStack shulkerStackItem = playerInventory.getItem(firstShulker);
int shulkerStackAmount = shulkerStackItem.getAmount();
if (shulkerBoxCount + shulkerStackAmount > 64) {
// get how many shulker boxes we need to get to 64
int shulkersRequired = 64 - shulkerBoxCount;
// remove that many boxes from the stack
shulkerStackItem.setAmount(shulkerStackAmount - shulkersRequired);
shulkerBoxCount += shulkersRequired;
// break from the loop, since we have a stack of shulkers collected
break;
} else {
// add the shulkerStackAmount to shulkerBoxCount
shulkerBoxCount += shulkerStackAmount;
// remove the shulkers from that slot
playerInventory.setItem(firstShulker, new ItemStack(Material.AIR));
tempPlayerInventory.setItem(firstShulker, new ItemStack(Material.AIR));
@@ -152,14 +117,12 @@ public class DoubleClick {
}
// now that we have the number of shulkers to grab, put them into the cursor
// if the shulkerBoxCount was 1, then don't do anything
// Update the cursor only after collecting more than one shulker.
if (shulkerBoxCount != 1) {
ItemStack newShulkers = new ItemStack(shulkerType);
newShulkers.setAmount(shulkerBoxCount);
event.getWhoClicked().setItemOnCursor(newShulkers);
// cancel default behavior
event.setCancelled(true);
}
@@ -12,8 +12,7 @@ public class NormalLeftClick {
InventoryClickEvent event)
{
// check that both shulker boxes are empty
// get the inventories of the shulker boxes
// Read both inventories before stacking empty shulkers.
BlockStateMeta shulker1Meta = (BlockStateMeta) currentItem.getItemMeta();
BlockStateMeta shulker2Meta = (BlockStateMeta) cursorItem.getItemMeta();
ShulkerBox shulker1 = (ShulkerBox) shulker1Meta.getBlockState();
@@ -32,7 +31,7 @@ public class NormalLeftClick {
} else {
// add the shulkers until we are at 64
// Add shulkers until the target reaches 64.
event.getWhoClicked().getItemOnCursor()
.setAmount((currentItem.getAmount() + cursorItem.getAmount()) - 64);
event.getCurrentItem().setAmount(64);
@@ -12,8 +12,7 @@ public class NormalRightClick {
InventoryClickEvent event)
{
// check that both shulker boxes are empty
// get the inventories of the shulker boxes
// Read both inventories before stacking empty shulkers.
BlockStateMeta shulker1Meta = (BlockStateMeta) currentItem.getItemMeta();
BlockStateMeta shulker2Meta = (BlockStateMeta) cursorItem.getItemMeta();
ShulkerBox shulker1 = (ShulkerBox) shulker1Meta.getBlockState();
@@ -1,6 +1,5 @@
package plugin.ShulkerBoxHelpers;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryType;
@@ -12,299 +11,106 @@ public class ShiftRightLeftClick {
public static void ShiftRightLeftClickShulkerBox(InventoryClickEvent event) {
// check what kind of inventory was shift-clicked in
// regardless, loop through the inventory(ies) and add the
// shulker box stack to the
InventoryView eventInventory = event.getView();
InventoryType inventoryType = eventInventory.getType();
Inventory containerInventory = eventInventory.getTopInventory();
Inventory playerInventory = eventInventory.getBottomInventory();
// store a temporary inventory so that we can handle
// full shulker boxes
Inventory tempPlayerInventory = Bukkit.createInventory(null, 36);
tempPlayerInventory.setContents(playerInventory.getContents());
// make the inventory size the max of 54 so that it will hold any size inventory
Inventory tempContainerInventory = Bukkit.createInventory(null, 54);
tempContainerInventory.setContents(containerInventory.getContents());
Material shulkerBoxType = event.getCurrentItem().getType();
int shulkerStackAmount = event.getCurrentItem().getAmount();
ItemStack source = event.getCurrentItem();
if (!ShulkerBoxUtils.isEmptyShulkerBox(source)) {
// if it was the player's inventory
if (inventoryType.equals(InventoryType.CRAFTING)) {
// check if the slot was a hotbar slot or not
if (event.getSlotType().equals(InventoryType.SlotType.QUICKBAR)) {
// remove the hotbar of the tempInventory
for (int i = 0; i < 9; i++) {
tempPlayerInventory.setItem(i, new ItemStack(Material.AIR));
}
// check the top part of the inventory for any shulker stacks that are not 64
// yet
while (tempPlayerInventory.first(shulkerBoxType) != -1) {
int firstShulkerSlot = tempPlayerInventory.first(shulkerBoxType);
ItemStack shulkerStack = tempPlayerInventory.getItem(firstShulkerSlot);
// if the shulker stack has 64 shulker boxes in it
// remove it from the temporary inventory and continue on
if (shulkerStack.getAmount() >= 64) {
tempPlayerInventory.setItem(firstShulkerSlot, new ItemStack(Material.AIR));
continue;
}
// cancel default behavior since shulker boxes are being moved
event.setCancelled(true);
// stack up to 64 shulkers into the stack, and then continue on
if (shulkerStack.getAmount() + shulkerStackAmount <= 64) {
playerInventory.getItem(firstShulkerSlot)
.setAmount(shulkerStack.getAmount() + shulkerStackAmount);
shulkerStackAmount = 0;
break;
} else {
// remove shulker boxes until the shulker stack has 64 boxes
shulkerStackAmount -= 64 - shulkerStack.getAmount();
playerInventory.getItem(firstShulkerSlot).setAmount(64);
tempPlayerInventory.setItem(firstShulkerSlot, new ItemStack(Material.AIR));
}
}
// change the shulker stack amount to what is left
// or remove it if nothing is left
if (shulkerStackAmount == 0) {
event.setCurrentItem(new ItemStack(Material.AIR));
} else {
event.getCurrentItem().setAmount(shulkerStackAmount);
// look for an air gap to put the rest of the shulker boxes
for (int i = 9; i < playerInventory.getSize(); i++) {
if (playerInventory.getItem(i) == null) {
playerInventory.setItem(i, event.getCurrentItem());
event.setCurrentItem(new ItemStack(Material.AIR));
}
}
}
}
// the slot was an inventory slot, so check the hotbar for boxes
else {
// remove everything from the upper inventory to prevent it from causing
// problems
for (int i = 35; i > 8; i--) {
tempPlayerInventory.setItem(i, new ItemStack(Material.AIR));
}
while (tempPlayerInventory.first(shulkerBoxType) != -1) {
int firstShulkerSlot = tempPlayerInventory.first(shulkerBoxType);
ItemStack shulkerStack = tempPlayerInventory.getItem(firstShulkerSlot);
// if the shulker stack has 64 shulker boxes in it
// remove it from the temporary inventory and continue on
if (shulkerStack.getAmount() >= 64) {
tempPlayerInventory.setItem(firstShulkerSlot, new ItemStack(Material.AIR));
continue;
}
// cancel default behavior since shulker boxes are being moved
event.setCancelled(true);
// stack up to 64 shulkers into the stack, and then continue on
if (shulkerStack.getAmount() + shulkerStackAmount <= 64) {
playerInventory.getItem(firstShulkerSlot)
.setAmount(shulkerStack.getAmount() + shulkerStackAmount);
shulkerStackAmount = 0;
break;
} else {
// remove shulker boxes until the shulker stack has 64 boxes
shulkerStackAmount -= 64 - shulkerStack.getAmount();
playerInventory.getItem(firstShulkerSlot).setAmount(64);
tempPlayerInventory.setItem(firstShulkerSlot, new ItemStack(Material.AIR));
}
}
// change the shulker stack amount to what is left
// or remove it if nothing is left
if (shulkerStackAmount == 0) {
event.setCurrentItem(new ItemStack(Material.AIR));
} else {
event.getCurrentItem().setAmount(shulkerStackAmount);
// look for an air gap to put the rest of the shulker boxes
for (int i = 0; i < 9; i++) {
if (playerInventory.getItem(i) == null) {
playerInventory.setItem(i, event.getCurrentItem());
event.setCurrentItem(new ItemStack(Material.AIR));
}
}
}
}
return;
}
// if it was anything else
else {
// check if the click was in the bottom
if (event.getRawSlot() > containerInventory.getSize()) {
InventoryView view = event.getView();
Inventory top = view.getTopInventory();
Inventory bottom = view.getBottomInventory();
InventoryType viewType = view.getType();
Material shulkerType = source.getType();
int rawSlot = event.getRawSlot();
int topSize = top.getSize();
boolean clickedTop = rawSlot < topSize;
while (tempContainerInventory.first(shulkerBoxType) != -1) {
// Cancel default handling before redistributing the stack.
event.setCancelled(true);
int firstShulkerSlot = tempContainerInventory.first(shulkerBoxType);
ItemStack shulkerStack = tempContainerInventory.getItem(firstShulkerSlot);
// if the shulker stack has 64 shulker boxes in it
// remove it from the temporary inventory and continue on
if (shulkerStack.getAmount() >= 64) {
int remaining = source.getAmount();
tempContainerInventory.setItem(firstShulkerSlot, new ItemStack(Material.AIR));
continue;
if (viewType.equals(InventoryType.CRAFTING)) {
}
// Without a container open, shift within storage slots 0-35 only.
boolean fromHotbar = event.getSlotType().equals(InventoryType.SlotType.QUICKBAR);
int destStart = fromHotbar ? 9 : 0;
int destEnd = fromHotbar ? Math.min(bottom.getSize(), 36) : 9;
remaining = redistribute(bottom, destStart, destEnd, shulkerType, remaining);
// cancel default behavior since shulker boxes are being moved
event.setCancelled(true);
} else {
// stack up to 64 shulkers into the stack, and then continue on
if (shulkerStack.getAmount() + shulkerStackAmount <= 64) {
Inventory destination = clickedTop ? bottom : top;
int destStart = 0;
// Limit player destinations to storage slots 0-35, including non-vanilla views.
int destEnd = (destination == bottom) ? Math.min(destination.getSize(), 36) : destination.getSize();
remaining = redistribute(destination, destStart, destEnd, shulkerType, remaining);
containerInventory.getItem(firstShulkerSlot)
.setAmount(shulkerStack.getAmount() + shulkerStackAmount);
shulkerStackAmount = 0;
break;
}
} else {
if (remaining <= 0) {
// remove shulker boxes until the shulker stack has 64 boxes
shulkerStackAmount -= 64 - shulkerStack.getAmount();
containerInventory.getItem(firstShulkerSlot).setAmount(64);
tempContainerInventory.setItem(firstShulkerSlot, new ItemStack(Material.AIR));
event.setCurrentItem(new ItemStack(Material.AIR));
}
} else {
}
// change the shulker stack amount to what is left
// or remove it if nothing is left
if (shulkerStackAmount == 0) {
event.setCurrentItem(new ItemStack(Material.AIR));
} else {
event.getCurrentItem().setAmount(shulkerStackAmount);
// look for an air gap to put the rest of the shulker boxes
for (int i = 0; i < containerInventory.getSize(); i++) {
if (containerInventory.getItem(i) == null) {
containerInventory.setItem(i, event.getCurrentItem());
event.setCurrentItem(new ItemStack(Material.AIR));
}
}
}
}
// the click was in the top inventory
else {
while (tempPlayerInventory.first(shulkerBoxType) != -1) {
int firstShulkerSlot = tempPlayerInventory.first(shulkerBoxType);
ItemStack shulkerStack = tempPlayerInventory.getItem(firstShulkerSlot);
// if the shulker stack has 64 shulker boxes in it
// remove it from the temporary inventory and continue on
if (shulkerStack.getAmount() >= 64) {
tempPlayerInventory.setItem(firstShulkerSlot, new ItemStack(Material.AIR));
continue;
}
// cancel default behavior since shulker boxes are being moved
event.setCancelled(true);
// stack up to 64 shulkers into the stack, and then continue on
if (shulkerStack.getAmount() + shulkerStackAmount <= 64) {
playerInventory.getItem(firstShulkerSlot)
.setAmount(shulkerStack.getAmount() + shulkerStackAmount);
shulkerStackAmount = 0;
break;
} else {
// remove shulker boxes until the shulker stack has 64 boxes
shulkerStackAmount -= 64 - shulkerStack.getAmount();
playerInventory.getItem(firstShulkerSlot).setAmount(64);
tempPlayerInventory.setItem(firstShulkerSlot, new ItemStack(Material.AIR));
}
}
// change the shulker stack amount to what is left
// or remove it if nothing is left
if (shulkerStackAmount == 0) {
event.setCurrentItem(new ItemStack(Material.AIR));
} else {
event.getCurrentItem().setAmount(shulkerStackAmount);
// look for an air gap to put the rest of the shulker boxes
for (int i = 0; i < playerInventory.getSize(); i++) {
if (playerInventory.getItem(i) == null) {
playerInventory.setItem(i, event.getCurrentItem());
event.setCurrentItem(new ItemStack(Material.AIR));
}
}
}
}
event.setCurrentItem(new ItemStack(shulkerType, remaining));
}
}
private static int redistribute(Inventory inv, int start, int end, Material shulkerType, int amount) {
// Phase 1: top off existing empty stacks of the same material.
for (int i = start; i < end && amount > 0; i++) {
ItemStack slot = inv.getItem(i);
if (!ShulkerBoxUtils.isEmptyShulkerBox(slot)) {
continue;
}
if (!slot.getType().equals(shulkerType)) {
continue;
}
int space = 64 - slot.getAmount();
if (space <= 0) {
continue;
}
int moved = Math.min(space, amount);
slot.setAmount(slot.getAmount() + moved);
amount -= moved;
}
// Phase 2: drop leftovers into AIR slots, one stack at a time.
for (int i = start; i < end && amount > 0; i++) {
ItemStack slot = inv.getItem(i);
if (slot != null && slot.getType() != Material.AIR) {
continue;
}
int placed = Math.min(64, amount);
inv.setItem(i, new ItemStack(shulkerType, placed));
amount -= placed;
}
return amount;
}
}
@@ -12,7 +12,7 @@ public final class ShulkerBoxUtils {
}
public static boolean isEmptyShulkerBox(ItemStack itemStack) {
public static boolean isShulkerBoxMaterial(ItemStack itemStack) {
if (itemStack == null || itemStack.getType() == Material.AIR) {
@@ -20,7 +20,13 @@ public final class ShulkerBoxUtils {
}
if (!itemStack.getType().toString().contains("SHULKER_BOX")) {
return itemStack.getType().toString().contains("SHULKER_BOX");
}
public static boolean isEmptyShulkerBox(ItemStack itemStack) {
if (!isShulkerBoxMaterial(itemStack)) {
return false;
@@ -9,6 +9,7 @@ import plugin.Listeners.ShulkerBoxHopperHandler;
import plugin.Listeners.ShulkerBoxMergeHandler;
import plugin.Listeners.ShulkerBoxPickupHandler;
import plugin.Listeners.ShulkerBoxStackingController;
import plugin.Listeners.ShulkerCleanupHandler;
import plugin.Listeners.ShulkerDragDupePrevention;
import plugin.Listeners.ShulkerDragHandler;
@@ -31,6 +32,7 @@ public final class ShulkerStackingOG extends JavaPlugin {
getServer().getPluginManager().registerEvents(new ShulkerDragDupePrevention(), this);
getServer().getPluginManager().registerEvents(new ShulkerBoxHopperHandler(), this);
getServer().getPluginManager().registerEvents(new ShulkerBoxMergeHandler(), this);
getServer().getPluginManager().registerEvents(new ShulkerCleanupHandler(), this);
}