diff --git a/src/main/java/com/Acrobot/Breeze/Utils/InventoryUtil.java b/src/main/java/com/Acrobot/Breeze/Utils/InventoryUtil.java index 33e242e..0ef7715 100644 --- a/src/main/java/com/Acrobot/Breeze/Utils/InventoryUtil.java +++ b/src/main/java/com/Acrobot/Breeze/Utils/InventoryUtil.java @@ -74,6 +74,23 @@ public class InventoryUtil { return true; } + + /** + * Count amount of empty slots in an inventory + * + * @param inventory the inventory + * @return The amount of empty slots + */ + public static int countEmpty(Inventory inventory) { + int emptyAmount = 0; + for (ItemStack stack : getStorageContents(inventory)) { + if (MaterialUtil.isEmpty(stack)) { + emptyAmount++; + } + } + + return emptyAmount; + } /** * Checks if the inventory has stock of this type @@ -92,6 +109,24 @@ public class InventoryUtil { return true; } + + /** + * Checks if items fit in the inventory + * + * @param items Items to check + * @param inventory inventory + * @return Do the items fit inside the inventory? + */ + public static boolean fits(ItemStack[] items, Inventory inventory) { + ItemStack[] mergedItems = InventoryUtil.mergeSimilarStacks(items); + for (ItemStack item : mergedItems) { + if (!InventoryUtil.fits(item, inventory)) { + return false; + } + } + + return true; + } /** * Checks if the item fits the inventory diff --git a/src/main/java/com/Acrobot/ChestShop/Configuration/Properties.java b/src/main/java/com/Acrobot/ChestShop/Configuration/Properties.java index 4417715..6480d9b 100644 --- a/src/main/java/com/Acrobot/ChestShop/Configuration/Properties.java +++ b/src/main/java/com/Acrobot/ChestShop/Configuration/Properties.java @@ -70,7 +70,7 @@ public class Properties { @ConfigurationComment("Do you want to allow other players to build a shop on a block where there's one already?") public static boolean ALLOW_MULTIPLE_SHOPS_AT_ONE_BLOCK = false; - @ConfigurationComment("Can shops be used even when the seller doesn't have enough items? (The price will be scaled adequately to the item amount)") + @ConfigurationComment("Can shops be used even when the buyer/seller doesn't have enough items, space or money? (The price will be scaled adequately to the item amount)") public static boolean ALLOW_PARTIAL_TRANSACTIONS = true; @ConfigurationComment("Can '?' be put in place of item name in order for the sign to be auto-filled?") diff --git a/src/main/java/com/Acrobot/ChestShop/Listeners/PreTransaction/PartialTransactionModule.java b/src/main/java/com/Acrobot/ChestShop/Listeners/PreTransaction/PartialTransactionModule.java index 73e1ff1..d6e6423 100644 --- a/src/main/java/com/Acrobot/ChestShop/Listeners/PreTransaction/PartialTransactionModule.java +++ b/src/main/java/com/Acrobot/ChestShop/Listeners/PreTransaction/PartialTransactionModule.java @@ -16,6 +16,7 @@ import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.UUID; @@ -29,24 +30,22 @@ import static com.Acrobot.ChestShop.Events.TransactionEvent.TransactionType.SELL */ public class PartialTransactionModule implements Listener { - @EventHandler(priority = EventPriority.LOW) + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public static void onPreBuyTransaction(PreTransactionEvent event) { - if (event.isCancelled() || event.getTransactionType() != BUY) { + if (event.getTransactionType() != BUY) { return; } Player client = event.getClient(); - ItemStack[] stock = event.getStock(); - double price = event.getPrice(); - double pricePerItem = event.getPrice() / InventoryUtil.countItems(stock); + double pricePerItem = event.getPrice() / InventoryUtil.countItems(event.getStock()); CurrencyAmountEvent currencyAmountEvent = new CurrencyAmountEvent(client); ChestShop.callEvent(currencyAmountEvent); BigDecimal walletMoney = currencyAmountEvent.getAmount(); - CurrencyCheckEvent currencyCheckEvent = new CurrencyCheckEvent(BigDecimal.valueOf(price), client); + CurrencyCheckEvent currencyCheckEvent = new CurrencyCheckEvent(BigDecimal.valueOf(event.getPrice()), client); ChestShop.callEvent(currencyCheckEvent); if (!currencyCheckEvent.hasEnough()) { @@ -58,47 +57,54 @@ public class PartialTransactionModule implements Listener { } event.setPrice(amountAffordable * pricePerItem); - event.setStock(getCountedItemStack(stock, amountAffordable)); + event.setStock(getCountedItemStack(event.getStock(), amountAffordable)); } + + if (!InventoryUtil.hasItems(event.getStock(), event.getOwnerInventory())) { + ItemStack[] itemsHad = getItems(event.getStock(), event.getOwnerInventory()); + int possessedItemCount = InventoryUtil.countItems(itemsHad); + + if (possessedItemCount <= 0) { + event.setCancelled(NOT_ENOUGH_STOCK_IN_CHEST); + return; + } + + event.setPrice(pricePerItem * possessedItemCount); + event.setStock(itemsHad); + } + + if (!InventoryUtil.fits(event.getStock(), event.getClientInventory())) { + ItemStack[] itemsFit = getItemsThatFit(event.getStock(), event.getClientInventory()); + int possessedItemCount = InventoryUtil.countItems(itemsFit); + if (possessedItemCount <= 0) { + event.setCancelled(NOT_ENOUGH_SPACE_IN_INVENTORY); + return; + } + + event.setStock(itemsFit); + event.setPrice(pricePerItem * possessedItemCount); + } + + UUID seller = event.getOwnerAccount().getUuid(); - UUID seller = event.getOwner().getUniqueId(); - - CurrencyHoldEvent currencyHoldEvent = new CurrencyHoldEvent(BigDecimal.valueOf(price), seller, client.getWorld()); + CurrencyHoldEvent currencyHoldEvent = new CurrencyHoldEvent(BigDecimal.valueOf(event.getPrice()), seller, client.getWorld()); ChestShop.callEvent(currencyHoldEvent); if (!currencyHoldEvent.canHold()) { event.setCancelled(SHOP_DEPOSIT_FAILED); - return; - } - - stock = event.getStock(); - - if (!InventoryUtil.hasItems(stock, event.getOwnerInventory())) { - ItemStack[] itemsHad = getItems(stock, event.getOwnerInventory()); - int posessedItemCount = InventoryUtil.countItems(itemsHad); - - if (posessedItemCount <= 0) { - event.setCancelled(NOT_ENOUGH_STOCK_IN_CHEST); - return; - } - - event.setPrice(pricePerItem * posessedItemCount); - event.setStock(itemsHad); } } - - @EventHandler(priority = EventPriority.LOW) + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public static void onPreSellTransaction(PreTransactionEvent event) { - if (event.isCancelled() || event.getTransactionType() != SELL) { + if (event.getTransactionType() != SELL) { return; } Player client = event.getClient(); - UUID owner = event.getOwner().getUniqueId(); - ItemStack[] stock = event.getStock(); + UUID owner = event.getOwnerAccount().getUuid(); - double price = event.getPrice(); - double pricePerItem = event.getPrice() / InventoryUtil.countItems(stock); + double pricePerItem = event.getPrice() / InventoryUtil.countItems(event.getStock()); CurrencyAmountEvent currencyAmountEvent = new CurrencyAmountEvent(owner, client.getWorld()); ChestShop.callEvent(currencyAmountEvent); @@ -106,7 +112,7 @@ public class PartialTransactionModule implements Listener { BigDecimal walletMoney = currencyAmountEvent.getAmount(); if (Economy.isOwnerEconomicallyActive(event.getOwnerInventory())) { - CurrencyCheckEvent currencyCheckEvent = new CurrencyCheckEvent(BigDecimal.valueOf(price), owner, client.getWorld()); + CurrencyCheckEvent currencyCheckEvent = new CurrencyCheckEvent(BigDecimal.valueOf(event.getPrice()), owner, client.getWorld()); ChestShop.callEvent(currencyCheckEvent); if (!currencyCheckEvent.hasEnough()) { @@ -118,31 +124,40 @@ public class PartialTransactionModule implements Listener { } event.setPrice(amountAffordable * pricePerItem); - event.setStock(getCountedItemStack(stock, amountAffordable)); + event.setStock(getCountedItemStack(event.getStock(), amountAffordable)); } } + + if (!InventoryUtil.hasItems(event.getStock(), event.getClientInventory())) { + ItemStack[] itemsHad = getItems(event.getStock(), event.getClientInventory()); + int possessedItemCount = InventoryUtil.countItems(itemsHad); + + if (possessedItemCount <= 0) { + event.setCancelled(NOT_ENOUGH_STOCK_IN_INVENTORY); + return; + } + + event.setPrice(pricePerItem * possessedItemCount); + event.setStock(itemsHad); + } + + if (!InventoryUtil.fits(event.getStock(), event.getOwnerInventory())) { + ItemStack[] itemsFit = getItemsThatFit(event.getStock(), event.getOwnerInventory()); + int possessedItemCount = InventoryUtil.countItems(itemsFit); + if (possessedItemCount <= 0) { + event.setCancelled(NOT_ENOUGH_STOCK_IN_CHEST); + return; + } + + event.setStock(itemsFit); + event.setPrice(pricePerItem * possessedItemCount); + } - stock = event.getStock(); - - CurrencyHoldEvent currencyHoldEvent = new CurrencyHoldEvent(BigDecimal.valueOf(price), client); + CurrencyHoldEvent currencyHoldEvent = new CurrencyHoldEvent(BigDecimal.valueOf(event.getPrice()), client); ChestShop.callEvent(currencyHoldEvent); if (!currencyHoldEvent.canHold()) { event.setCancelled(CLIENT_DEPOSIT_FAILED); - return; - } - - if (!InventoryUtil.hasItems(stock, event.getClientInventory())) { - ItemStack[] itemsHad = getItems(stock, event.getClientInventory()); - int posessedItemCount = InventoryUtil.countItems(itemsHad); - - if (posessedItemCount <= 0) { - event.setCancelled(NOT_ENOUGH_STOCK_IN_INVENTORY); - return; - } - - event.setPrice(pricePerItem * posessedItemCount); - event.setStock(itemsHad); } } @@ -207,4 +222,51 @@ public class PartialTransactionModule implements Listener { return stacks.toArray(new ItemStack[stacks.size()]); } + + /** + * Make an array of items fit into an inventory. + * + * @param stock The items to fit in the inventory + * @param inventory The inventory to fit it in + * @return Whether or not the items fit into the inventory + */ + private static ItemStack[] getItemsThatFit(ItemStack[] stock, Inventory inventory) { + List resultStock = new ArrayList<>(); + + int emptySlots = InventoryUtil.countEmpty(inventory); + ItemStack[] itemsInInventory = getItems(stock, inventory); + + for (ItemStack item : stock) { + int maxStackSize = InventoryUtil.getMaxStackSize(item); + int free = 0; + for (ItemStack itemInInventory : itemsInInventory) { + if (MaterialUtil.equals(item, itemInInventory)) { + free = (maxStackSize - itemInInventory.getAmount()) % maxStackSize; + break; + } + } + + if (free == 0 && emptySlots == 0) { + continue; + } + + ItemStack clone = item.clone(); + if (item.getAmount() > free) { + if (emptySlots > 0) { + int requiredSlots = (int) Math.ceil((item.getAmount() - free) / maxStackSize); + if (requiredSlots <= emptySlots) { + emptySlots = emptySlots - requiredSlots; + } else { + emptySlots = 0; + clone.setAmount(free + maxStackSize * emptySlots); + } + } else { + clone.setAmount(free); + } + } + resultStock.add(clone); + } + + return (ItemStack[]) resultStock.toArray(); + } } diff --git a/src/main/java/com/Acrobot/ChestShop/Listeners/PreTransaction/StockFittingChecker.java b/src/main/java/com/Acrobot/ChestShop/Listeners/PreTransaction/StockFittingChecker.java index 3424467..53675f4 100644 --- a/src/main/java/com/Acrobot/ChestShop/Listeners/PreTransaction/StockFittingChecker.java +++ b/src/main/java/com/Acrobot/ChestShop/Listeners/PreTransaction/StockFittingChecker.java @@ -26,7 +26,7 @@ public class StockFittingChecker implements Listener { Inventory shopInventory = event.getOwnerInventory(); ItemStack[] stock = event.getStock(); - if (!itemsFitInInventory(stock, shopInventory)) { + if (!InventoryUtil.fits(stock, shopInventory)) { event.setCancelled(NOT_ENOUGH_SPACE_IN_CHEST); } } @@ -40,19 +40,8 @@ public class StockFittingChecker implements Listener { Inventory clientInventory = event.getClientInventory(); ItemStack[] stock = event.getStock(); - if (!itemsFitInInventory(stock, clientInventory)) { + if (!InventoryUtil.fits(stock, clientInventory)) { event.setCancelled(NOT_ENOUGH_SPACE_IN_INVENTORY); } } - - private static boolean itemsFitInInventory(ItemStack[] items, Inventory inventory) { - ItemStack[] mergedItems = InventoryUtil.mergeSimilarStacks(items); - for (ItemStack item : mergedItems) { - if (!InventoryUtil.fits(item, inventory)) { - return false; - } - } - - return true; - } }