diff --git a/src/main/java/com/griefcraft/lwc/LWC.java b/src/main/java/com/griefcraft/lwc/LWC.java index f6f43c3d..1901e7be 100644 --- a/src/main/java/com/griefcraft/lwc/LWC.java +++ b/src/main/java/com/griefcraft/lwc/LWC.java @@ -92,11 +92,14 @@ import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; +import org.bukkit.block.Furnace; +import org.bukkit.block.ShulkerBox; import org.bukkit.block.data.type.Chest; import org.bukkit.command.CommandSender; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; +import org.bukkit.inventory.FurnaceInventory; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; @@ -442,65 +445,204 @@ public boolean canAdminProtection(Player player, Protection protection) { return event.getAccess() == Permission.Access.ADMIN; } + /** + * Calculate The number of items that can be deposit in a furnace-like. + * + * Fuel slot allows only fuel items. + * Result slot does not allow deposits. + * + * @param furnaceLike (Not Null) Furnace-like block. + * @param itemStack (Not Null) Item stack for deposit. + * @return Number of items that can be deposit. + */ + public int getMaxDepositAmount(Furnace furnaceLike, ItemStack itemStack) { + FurnaceInventory inventory = furnaceLike.getInventory(); + ItemStack sourceSlot = inventory.getSmelting(); + ItemStack fuelSlot = inventory.getFuel(); + + // source slot is empty, can deposit + if (sourceSlot == null) { + return itemStack.getAmount(); + } + + boolean isItemStackFuel = itemStack.getType().isFuel(); + + // source slot not empty, but can stack deposit + if (sourceSlot.getAmount() < sourceSlot.getMaxStackSize()) { + if (itemStack.isSimilar(sourceSlot)) { + return Math.min( + sourceSlot.getMaxStackSize() - sourceSlot.getAmount(), + itemStack.getAmount() + ); + } + } + + // can't stack deposit to source slot or source slot is full + // mean the same thing + if (fuelSlot == null) { + if (isItemStackFuel) { + return itemStack.getAmount(); + } + return 0; + } + if (fuelSlot.getAmount() < fuelSlot.getMaxStackSize()) { + if (itemStack.isSimilar(fuelSlot)) { + return Math.min( + fuelSlot.getMaxStackSize() - fuelSlot.getAmount(), + itemStack.getAmount() + ); + } + } + + return 0; + } + + /** + * Calculate The number of items that can be deposit in a container. + * + * @param block (Not Null) Container block has inventory. + * @param itemStack (Not Null) Item stack for container deposit. + * @return Number of items that can be deposit. + */ + public int getMaxDepositAmount(Block block, ItemStack itemStack) { + BlockState blockState = block.getState(); + + // this block has neither block status nor inventory + if (blockState == null || !(blockState instanceof InventoryHolder)) { + return 0; + } + + // nesting is forbidden on shulker boxes + if (blockState instanceof ShulkerBox && itemStack.getType().toString().endsWith("SHULKER_BOX")) { + return 0; + } + + Inventory inventory = ((InventoryHolder) blockState).getInventory(); + + // for Furnace-like blocks + if (blockState instanceof Furnace) { + return this.getMaxDepositAmount((Furnace) blockState, itemStack); + } + + // has empty slot, can deposit? + if (inventory.firstEmpty() != -1) { + return itemStack.getAmount(); + } + + // has no empty slot, but can stack deposit? + if (itemStack.getMaxStackSize() == 1) { + // non-stackable items + return 0; + } + Map sameTypeItemStacks = inventory.all(itemStack.getType()); + for (ItemStack sameItemStack : sameTypeItemStacks.values()) { + if (sameItemStack.getAmount() >= sameItemStack.getMaxStackSize()) { + continue; + } + if (itemStack.isSimilar(sameItemStack)) { + return Math.min( + sameItemStack.getMaxStackSize() - sameItemStack.getAmount(), + itemStack.getAmount() + ); + } + } + + return 0; + } + /** * Deposit items into an inventory chest Works with double chests. - * + * + * NOTE: Returns empty map if the entire item is deposit, + * non-empty map if a partial amount of items is deposit, + * null if item can't be deposit. + * * @param block * @param itemStack - * @return remaining items (if any) + * @return (Nullable) remaining items. */ public Map depositItems(Block block, ItemStack itemStack) { - BlockState blockState; + BlockState blockState = block.getState(); + // not a container, leave the item intact + if (blockState == null || !(blockState instanceof InventoryHolder)) { + return null; + } - if ((blockState = block.getState()) != null && (blockState instanceof InventoryHolder)) { - Block doubleChestBlock = null; - InventoryHolder holder = (InventoryHolder) blockState; + Block doubleChestBlock = null; + if (DoubleChestMatcher.PROTECTABLES_CHESTS.contains(block.getType())) { + doubleChestBlock = findAdjacentDoubleChest(block); + } - if (DoubleChestMatcher.PROTECTABLES_CHESTS.contains(block.getType())) { - doubleChestBlock = findAdjacentDoubleChest(block); - } else if (block.getType() == Material.FURNACE) { - Inventory inventory = holder.getInventory(); + Map empty = new HashMap<>(); + int itemStackAmount = itemStack.getAmount(); - if (inventory.getItem(0) != null && inventory.getItem(1) != null) { - if (inventory.getItem(0).getType() == itemStack.getType() - && inventory.getItem(0) - .getMaxStackSize() >= (inventory.getItem(0).getAmount() + itemStack.getAmount())) { - // ItemStack fits on Slot 0 - } else if (inventory.getItem(1).getType() == itemStack.getType() - && inventory.getItem(1) - .getMaxStackSize() >= (inventory.getItem(1).getAmount() + itemStack.getAmount())) { - // ItemStack fits on Slot 1 - } else { - return null; - } - } - } + // remove unreasonable item + if (itemStackAmount <= 0) { + return empty; + } - if (itemStack.getAmount() <= 0) { - return new HashMap(); - } + // reached deposit limit, leave the item intact + int canDepositAmount = getMaxDepositAmount(block, itemStack); + if (canDepositAmount == 0) { + return null; + } - Map remaining = holder.getInventory().addItem(itemStack); + Inventory inventory = ((InventoryHolder) blockState).getInventory(); + Map remaining; + + // furnace-like is special, we don't want to handle it to addItem method directly + if (blockState instanceof Furnace) { + // for furnace-like blocks + Furnace furnaceLike = (Furnace) blockState; + FurnaceInventory snapshotInventory = furnaceLike.getSnapshotInventory(); + + ItemStack resultSlotBackup = snapshotInventory.getResult(); + // avoid addItem method stack deposit into result slot + snapshotInventory.setResult(null); + + // can't deposit them all + if (canDepositAmount < itemStackAmount) { + // split item stack into 2 parts + int restAmount = itemStackAmount - canDepositAmount; + ItemStack canDepositPart = itemStack.clone(); + ItemStack theRestPart = itemStack.clone(); + + canDepositPart.setAmount(canDepositAmount); + theRestPart.setAmount(restAmount); + // deposit can deposit part, drop the rest part into item + remaining = snapshotInventory.addItem(canDepositPart); + remaining.put(-1, theRestPart); + itemStack.setAmount(restAmount); + } else { + remaining = snapshotInventory.addItem(itemStack); + } - // we have remainders, deal with it + // restore backup and commit update + snapshotInventory.setResult(resultSlotBackup); + furnaceLike.update(true, false); + } else { + // other container block + remaining = inventory.addItem(itemStack); + } + + // we have remainders, deal with it + if (remaining.size() > 0) { + int key = remaining.keySet().iterator().next(); + ItemStack remainingItemStack = remaining.get(key); + + // is it a double chest ????? + if (doubleChestBlock != null) { + InventoryHolder holder2 = (InventoryHolder) doubleChestBlock.getState(); + remaining = holder2.getInventory().addItem(remainingItemStack); + } + + // recheck remaining in the event of double chest being used if (remaining.size() > 0) { - int key = remaining.keySet().iterator().next(); - ItemStack remainingItemStack = remaining.get(key); - - // is it a double chest ????? - if (doubleChestBlock != null) { - InventoryHolder holder2 = (InventoryHolder) doubleChestBlock.getState(); - remaining = holder2.getInventory().addItem(remainingItemStack); - } - - // recheck remaining in the event of double chest being used - if (remaining.size() > 0) { - return remaining; - } + return remaining; } } - return new HashMap(); + return empty; } /** diff --git a/src/main/java/com/griefcraft/modules/flag/BaseFlagModule.java b/src/main/java/com/griefcraft/modules/flag/BaseFlagModule.java index 3310d8de..fec12ae6 100644 --- a/src/main/java/com/griefcraft/modules/flag/BaseFlagModule.java +++ b/src/main/java/com/griefcraft/modules/flag/BaseFlagModule.java @@ -38,6 +38,9 @@ import com.griefcraft.scripting.event.LWCProtectionInteractEvent; import com.griefcraft.util.Colors; import com.griefcraft.util.StringUtil; + +import org.bukkit.block.BlockState; +import org.bukkit.block.Container; import org.bukkit.command.CommandSender; public class BaseFlagModule extends JavaModule { @@ -85,6 +88,16 @@ public void onProtectionInteract(LWCProtectionInteractEvent event) { flag = new Flag(type); } + // magnet flag can't use on non-container block. + if (flag.getType() == Flag.Type.MAGNET) { + BlockState targetBlockState = protection.getBlock().getState(); + if (!(targetBlockState instanceof Container)) { + lwc.sendLocale(player, "protection.interact.flag.magnet.notcontainer", "flag", StringUtil.capitalizeFirstLetter(flagName)); + lwc.removeModes(player); + return; + } + } + if (shouldAdd) { protection.addFlag(flag); lwc.sendLocale(player, "protection.interact.flag.add", "flag", StringUtil.capitalizeFirstLetter(flagName)); diff --git a/src/main/java/com/griefcraft/modules/flag/MagnetModule.java b/src/main/java/com/griefcraft/modules/flag/MagnetModule.java index 419f7993..060ca3dc 100644 --- a/src/main/java/com/griefcraft/modules/flag/MagnetModule.java +++ b/src/main/java/com/griefcraft/modules/flag/MagnetModule.java @@ -41,6 +41,7 @@ import org.bukkit.Server; import org.bukkit.World; import org.bukkit.block.Block; +import org.bukkit.block.Container; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Item; @@ -154,27 +155,34 @@ public void run() { List protections = lwc.getPhysicalDatabase().loadProtections(world.getName(), x, y, z, radius); for (Protection protection : protections) { - if (protection.hasFlag(Flag.Type.MAGNET)) { - - if (protection.getBukkitWorld().getName() != item.getWorld().getName()) - continue; - - // we only want inventory blocks - if (!(protection.getBlock().getState() instanceof InventoryHolder)) { - continue; - } - - // never allow a shulker box to enter another shulker box - if (item.getItemStack().getType().toString().contains("SHULKER_BOX") && protection.getBlock().getType().toString().contains("SHULKER_BOX")) { - continue; - } - - MagnetNode node = new MagnetNode(); - node.item = item; - node.protection = protection; - items.offer(node); - break; + if (!protection.hasFlag(Flag.Type.MAGNET)) { + continue; } + + if (protection.getBukkitWorld().getName() != item.getWorld().getName()) + continue; + + // we only want container blocks + if (!(protection.getBlock().getState() instanceof Container)) { + continue; + } + + // never allow a shulker box to enter another shulker box + if (item.getItemStack().getType().toString().contains("SHULKER_BOX") && protection.getBlock().getType().toString().contains("SHULKER_BOX")) { + continue; + } + + // don't try to enter a full container + boolean isFull = lwc.getMaxDepositAmount(protection.getBlock(), stack) == 0; + if (isFull) { + continue; + } + + MagnetNode node = new MagnetNode(); + node.item = item; + node.protection = protection; + items.offer(node); + break; } } } @@ -206,7 +214,7 @@ public void run() { return; } - // we cancelled the item drop for some reason + // reached deposit limit, leave the item intact if (remaining == null) { continue; } diff --git a/src/main/java/com/griefcraft/modules/modes/DropTransferModule.java b/src/main/java/com/griefcraft/modules/modes/DropTransferModule.java index 2a8133d3..9112895d 100644 --- a/src/main/java/com/griefcraft/modules/modes/DropTransferModule.java +++ b/src/main/java/com/griefcraft/modules/modes/DropTransferModule.java @@ -40,6 +40,7 @@ import com.griefcraft.scripting.event.LWCProtectionInteractEvent; import org.bukkit.World; import org.bukkit.block.Block; +import org.bukkit.block.BlockState; import org.bukkit.block.Container; import org.bukkit.command.CommandSender; import org.bukkit.entity.Item; @@ -161,11 +162,12 @@ public void onProtectionInteract(LWCProtectionInteractEvent event) { if (!canAccess) { lwc.sendLocale(player, "protection.interact.dropxfer.noaccess"); } else { - if (event.getEvent().getClickedBlock() instanceof Container) { + Block clickedBlock = event.getEvent().getClickedBlock(); + BlockState blockState = clickedBlock.getState(); + if (!(blockState instanceof Container)) { lwc.sendLocale(player, "protection.interact.dropxfer.notchest"); player.removeAllActions(); event.setResult(Result.CANCEL); - return; } diff --git a/src/main/resources/lang/lwc_cn.properties b/src/main/resources/lang/lwc_cn.properties index c98dbc57..acfc49e8 100644 --- a/src/main/resources/lang/lwc_cn.properties +++ b/src/main/resources/lang/lwc_cn.properties @@ -223,6 +223,7 @@ protection.onplace.create.finalize=%dark_green%成功创建了%type% %block%的 # Flag protection.interact.flag.add=%dark_green%成功打开%dark_aqua% %flag%%dark_green%保护 protection.interact.flag.remove=%dark_red%成功移除%dark_aqua% %flag%%dark_red%保护 +protection.interact.flag.magnet.notcontainer=%dark_aqua%%flag% %dark_red%功能只能应用在容器上 # Creation protection.interact.create.password=%dark_aqua%为了方便起见,你在下次登陆前都不用输入密码了。 diff --git a/src/main/resources/lang/lwc_en.properties b/src/main/resources/lang/lwc_en.properties index 9110c623..e5803a65 100644 --- a/src/main/resources/lang/lwc_en.properties +++ b/src/main/resources/lang/lwc_en.properties @@ -271,6 +271,7 @@ protection.onplace.create.finalize=%dark_green%Created a %type% %block% successf # Flag protection.interact.flag.add=%dark_green%Turned on the flag%dark_aqua% %flag%%dark_green% for the protection successfully protection.interact.flag.remove=%dark_red%Removed the flag%dark_aqua% %flag%%dark_red% for the protection successfully +protection.interact.flag.magnet.notcontainer=%dark_red%The %dark_aqua%%flag%%dark_red% flag applies only to containers # Creation protection.interact.create.password=%dark_aqua%For convenience, you don't have to enter your password until you next log in.