From 8d98958c2d73c4132fe5ba389b5999c4dd7ebeb6 Mon Sep 17 00:00:00 2001 From: Brice Frisco <39070938+bricefrisco@users.noreply.github.com> Date: Fri, 30 Oct 2020 16:45:16 -0500 Subject: [PATCH] Stock Counter (Resolves #365) (#368) --- .../Acrobot/Breeze/Utils/QuantityUtil.java | 22 +++ .../java/com/Acrobot/ChestShop/ChestShop.java | 3 +- .../ChestShop/Configuration/Properties.java | 4 + .../Modules/PriceRestrictionModule.java | 3 +- .../Listeners/Modules/StockCounterModule.java | 164 ++++++++++++++++++ .../Listeners/Player/PlayerInteract.java | 2 +- .../PreShopCreation/QuantityChecker.java | 3 +- .../ChestShop/Signs/ChestShopSign.java | 2 +- .../com/Acrobot/ChestShop/Utils/uBlock.java | 63 +++++++ 9 files changed, 261 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/Acrobot/Breeze/Utils/QuantityUtil.java create mode 100644 src/main/java/com/Acrobot/ChestShop/Listeners/Modules/StockCounterModule.java diff --git a/src/main/java/com/Acrobot/Breeze/Utils/QuantityUtil.java b/src/main/java/com/Acrobot/Breeze/Utils/QuantityUtil.java new file mode 100644 index 0000000..91acf2f --- /dev/null +++ b/src/main/java/com/Acrobot/Breeze/Utils/QuantityUtil.java @@ -0,0 +1,22 @@ +package com.Acrobot.Breeze.Utils; + +import java.util.regex.Pattern; + +/** + * @author bricefrisco + */ +public class QuantityUtil { + private static final Pattern QUANTITY_LINE_WITH_COUNTER_PATTERN = Pattern.compile("^Q [1-9][0-9]{0,4} : C [0-9]{0,5}$"); + + public static int parseQuantity(String quantityLine) throws IllegalArgumentException { + if (quantityLineContainsCounter(quantityLine)) { + return Integer.parseInt(quantityLine.split(" : ")[0].replace("Q ", "")); + } + + return Integer.parseInt(quantityLine); + } + + public static boolean quantityLineContainsCounter(String quantityLine) { + return QUANTITY_LINE_WITH_COUNTER_PATTERN.matcher(quantityLine).matches(); + } +} diff --git a/src/main/java/com/Acrobot/ChestShop/ChestShop.java b/src/main/java/com/Acrobot/ChestShop/ChestShop.java index 54c7a06..8b29558 100644 --- a/src/main/java/com/Acrobot/ChestShop/ChestShop.java +++ b/src/main/java/com/Acrobot/ChestShop/ChestShop.java @@ -20,6 +20,7 @@ import com.Acrobot.ChestShop.Listeners.GarbageTextListener; import com.Acrobot.ChestShop.Listeners.Item.ItemMoveListener; import com.Acrobot.ChestShop.Listeners.ItemInfoListener; import com.Acrobot.ChestShop.Listeners.Modules.MetricsModule; +import com.Acrobot.ChestShop.Listeners.Modules.StockCounterModule; import com.Acrobot.ChestShop.Listeners.SignParseListener; import com.Acrobot.ChestShop.Listeners.Modules.DiscountModule; import com.Acrobot.ChestShop.Listeners.Modules.PriceRestrictionModule; @@ -77,7 +78,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.UUID; -import java.util.concurrent.Callable; import java.util.jar.JarFile; import java.util.logging.FileHandler; import java.util.logging.Logger; @@ -394,6 +394,7 @@ public class ChestShop extends JavaPlugin { registerEvent(new DiscountModule()); registerEvent(new MetricsModule()); registerEvent(new PriceRestrictionModule()); + registerEvent(new StockCounterModule()); registerEconomicalModules(); } diff --git a/src/main/java/com/Acrobot/ChestShop/Configuration/Properties.java b/src/main/java/com/Acrobot/ChestShop/Configuration/Properties.java index bf53ace..7066793 100644 --- a/src/main/java/com/Acrobot/ChestShop/Configuration/Properties.java +++ b/src/main/java/com/Acrobot/ChestShop/Configuration/Properties.java @@ -315,4 +315,8 @@ public class Properties { @PrecededBySpace @ConfigurationComment("Add icons and make item names hoverable in transaction messages when ShowItem is installed?") public static boolean SHOWITEM_MESSAGE = true; + + @PrecededBySpace + @ConfigurationComment("Add stock counter to quantity line?") + public static boolean USE_STOCK_COUNTER = false; } \ No newline at end of file diff --git a/src/main/java/com/Acrobot/ChestShop/Listeners/Modules/PriceRestrictionModule.java b/src/main/java/com/Acrobot/ChestShop/Listeners/Modules/PriceRestrictionModule.java index 790d165..17a7389 100644 --- a/src/main/java/com/Acrobot/ChestShop/Listeners/Modules/PriceRestrictionModule.java +++ b/src/main/java/com/Acrobot/ChestShop/Listeners/Modules/PriceRestrictionModule.java @@ -1,6 +1,7 @@ package com.Acrobot.ChestShop.Listeners.Modules; import com.Acrobot.Breeze.Utils.PriceUtil; +import com.Acrobot.Breeze.Utils.QuantityUtil; import com.Acrobot.ChestShop.ChestShop; import com.Acrobot.ChestShop.Events.ChestShopReloadEvent; import com.Acrobot.ChestShop.Events.ItemParseEvent; @@ -115,7 +116,7 @@ public class PriceRestrictionModule implements Listener { String itemType = material.getType().toString().toLowerCase(Locale.ROOT); int amount; try { - amount = Integer.parseInt(event.getSignLine(QUANTITY_LINE)); + amount = QuantityUtil.parseQuantity(event.getSignLine(QUANTITY_LINE)); } catch (IllegalArgumentException e) { return; } diff --git a/src/main/java/com/Acrobot/ChestShop/Listeners/Modules/StockCounterModule.java b/src/main/java/com/Acrobot/ChestShop/Listeners/Modules/StockCounterModule.java new file mode 100644 index 0000000..767a680 --- /dev/null +++ b/src/main/java/com/Acrobot/ChestShop/Listeners/Modules/StockCounterModule.java @@ -0,0 +1,164 @@ +package com.Acrobot.ChestShop.Listeners.Modules; + +import com.Acrobot.Breeze.Utils.InventoryUtil; +import com.Acrobot.Breeze.Utils.QuantityUtil; +import com.Acrobot.ChestShop.ChestShop; +import com.Acrobot.ChestShop.Configuration.Properties; +import com.Acrobot.ChestShop.Events.ItemParseEvent; +import com.Acrobot.ChestShop.Events.PreShopCreationEvent; +import com.Acrobot.ChestShop.Events.TransactionEvent; +import com.Acrobot.ChestShop.Signs.ChestShopSign; +import com.Acrobot.ChestShop.Utils.uBlock; +import org.bukkit.Bukkit; +import org.bukkit.block.Sign; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import java.util.IllegalFormatException; + +import static com.Acrobot.ChestShop.Signs.ChestShopSign.NAME_LINE; +import static com.Acrobot.ChestShop.Signs.ChestShopSign.ITEM_LINE; +import static com.Acrobot.ChestShop.Signs.ChestShopSign.QUANTITY_LINE; + + +/** + * @author bricefrisco + */ +public class StockCounterModule implements Listener { + private static final String PRICE_LINE_WITH_COUNT = "Q %d : C %d"; + + @EventHandler(priority = EventPriority.HIGH) + public static void onPreShopCreation(PreShopCreationEvent event) { + int quantity; + try { + quantity = QuantityUtil.parseQuantity(event.getSignLine(QUANTITY_LINE)); + } catch (IllegalArgumentException invalidQuantity) { + return; + } + + if (QuantityUtil.quantityLineContainsCounter(event.getSignLine(QUANTITY_LINE))) { + event.setSignLine(QUANTITY_LINE, Integer.toString(quantity)); + } + + if (!Properties.USE_STOCK_COUNTER || ChestShopSign.isAdminShop(event.getSignLine(NAME_LINE))) { + return; + } + + if (Properties.MAX_SHOP_AMOUNT > 99999) { + ChestShop.getBukkitLogger().warning("Stock counter cannot be used if MAX_SHOP_AMOUNT is over 5 digits"); + return; + } + + ItemStack itemTradedByShop = determineItemTradedByShop(event.getSignLine(ITEM_LINE)); + Inventory chestShopInventory = uBlock.findConnectedContainer(event.getSign()).getInventory(); + + event.setSignLine(QUANTITY_LINE, getQuantityLineWithCounter(quantity, itemTradedByShop, chestShopInventory)); + } + + @EventHandler + public static void onInventoryClose(InventoryCloseEvent event) { + if (event.getInventory().getLocation() == null || !ChestShopSign.isShopBlock(event.getInventory().getLocation().getBlock())) { + return; + } + + for (Sign shopSign : uBlock.findConnectedShopSigns(event.getInventory().getHolder())) { + if (ChestShopSign.isAdminShop(shopSign)) { + return; + } + + if (!Properties.USE_STOCK_COUNTER) { + if (QuantityUtil.quantityLineContainsCounter(shopSign.getLine(QUANTITY_LINE))) { + removeCounterFromQuantityLine(shopSign); + } + continue; + } + + if (Properties.MAX_SHOP_AMOUNT > 99999) { + ChestShop.getBukkitLogger().warning("Stock counter cannot be used if MAX_SHOP_AMOUNT is over 5 digits"); + if (QuantityUtil.quantityLineContainsCounter(shopSign.getLine(QUANTITY_LINE))) { + removeCounterFromQuantityLine(shopSign); + } + return; + } + + updateCounterOnQuantityLine(shopSign, event.getInventory()); + } + } + + @EventHandler(priority = EventPriority.HIGH) + public static void onTransaction(final TransactionEvent event) { + if (!Properties.USE_STOCK_COUNTER) { + if (QuantityUtil.quantityLineContainsCounter(event.getSign().getLine(QUANTITY_LINE))) { + removeCounterFromQuantityLine(event.getSign()); + } + return; + } + + if (Properties.MAX_SHOP_AMOUNT > 99999) { + ChestShop.getBukkitLogger().warning("Stock counter cannot be used if MAX_SHOP_AMOUNT is over 5 digits"); + if (QuantityUtil.quantityLineContainsCounter(event.getSign().getLine(QUANTITY_LINE))) { + removeCounterFromQuantityLine(event.getSign()); + } + return; + } + + if (ChestShopSign.isAdminShop(event.getSign())) { + return; + } + + for (Sign shopSign : uBlock.findConnectedShopSigns(event.getOwnerInventory().getHolder())) { + updateCounterOnQuantityLine(shopSign, event.getOwnerInventory()); + } + } + + public static void updateCounterOnQuantityLine(Sign sign, Inventory chestShopInventory) { + ItemStack itemTradedByShop = determineItemTradedByShop(sign); + if (itemTradedByShop == null) { + return; + } + + int quantity; + try { + quantity = QuantityUtil.parseQuantity(sign.getLine(QUANTITY_LINE)); + } catch (IllegalFormatException invalidQuantity) { + return; + } + + int numTradedItemsInChest = InventoryUtil.getAmount(itemTradedByShop, chestShopInventory); + + sign.setLine(QUANTITY_LINE, String.format(PRICE_LINE_WITH_COUNT, quantity, numTradedItemsInChest)); + sign.update(true); + } + + public static void removeCounterFromQuantityLine(Sign sign) { + int quantity; + try { + quantity = QuantityUtil.parseQuantity(sign.getLine(QUANTITY_LINE)); + } catch (IllegalFormatException invalidQuantity) { + return; + } + + sign.setLine(QUANTITY_LINE, Integer.toString(quantity)); + sign.update(true); + } + + public static String getQuantityLineWithCounter(int amount, ItemStack itemTransacted, Inventory chestShopInventory) { + int numTransactionItemsInChest = InventoryUtil.getAmount(itemTransacted, chestShopInventory); + + return String.format(PRICE_LINE_WITH_COUNT, amount, numTransactionItemsInChest); + } + + public static ItemStack determineItemTradedByShop(Sign sign) { + return determineItemTradedByShop(sign.getLine(ITEM_LINE)); + } + + public static ItemStack determineItemTradedByShop(String material) { + ItemParseEvent parseEvent = new ItemParseEvent(material); + Bukkit.getPluginManager().callEvent(parseEvent); + return parseEvent.getItem(); + } +} diff --git a/src/main/java/com/Acrobot/ChestShop/Listeners/Player/PlayerInteract.java b/src/main/java/com/Acrobot/ChestShop/Listeners/Player/PlayerInteract.java index 6bc7561..df5c3aa 100644 --- a/src/main/java/com/Acrobot/ChestShop/Listeners/Player/PlayerInteract.java +++ b/src/main/java/com/Acrobot/ChestShop/Listeners/Player/PlayerInteract.java @@ -196,7 +196,7 @@ public class PlayerInteract implements Listener { int amount = -1; try { - amount = Integer.parseInt(quantity); + amount = QuantityUtil.parseQuantity(quantity); } catch (NumberFormatException notANumber) {} if (amount < 1 || amount > Properties.MAX_SHOP_AMOUNT) { diff --git a/src/main/java/com/Acrobot/ChestShop/Listeners/PreShopCreation/QuantityChecker.java b/src/main/java/com/Acrobot/ChestShop/Listeners/PreShopCreation/QuantityChecker.java index 2d93841..d230443 100644 --- a/src/main/java/com/Acrobot/ChestShop/Listeners/PreShopCreation/QuantityChecker.java +++ b/src/main/java/com/Acrobot/ChestShop/Listeners/PreShopCreation/QuantityChecker.java @@ -1,5 +1,6 @@ package com.Acrobot.ChestShop.Listeners.PreShopCreation; +import com.Acrobot.Breeze.Utils.QuantityUtil; import com.Acrobot.ChestShop.Configuration.Properties; import com.Acrobot.ChestShop.Events.PreShopCreationEvent; import org.bukkit.event.EventHandler; @@ -18,7 +19,7 @@ public class QuantityChecker implements Listener { public static void onPreShopCreation(PreShopCreationEvent event) { int amount = -1; try { - amount = Integer.parseInt(event.getSignLine(QUANTITY_LINE)); + amount = QuantityUtil.parseQuantity(event.getSignLine(QUANTITY_LINE)); } catch (NumberFormatException notANumber) {} if (amount < 1 || amount > Properties.MAX_SHOP_AMOUNT) { diff --git a/src/main/java/com/Acrobot/ChestShop/Signs/ChestShopSign.java b/src/main/java/com/Acrobot/ChestShop/Signs/ChestShopSign.java index 486162f..0239ed0 100644 --- a/src/main/java/com/Acrobot/ChestShop/Signs/ChestShopSign.java +++ b/src/main/java/com/Acrobot/ChestShop/Signs/ChestShopSign.java @@ -33,7 +33,7 @@ public class ChestShopSign { public static final Pattern[] SHOP_SIGN_PATTERN = { Pattern.compile("^?[\\w -.:]*$"), - Pattern.compile("^[1-9][0-9]{0,5}$"), + Pattern.compile("^[1-9][0-9]{0,5}$|^Q [1-9][0-9]{0,4} : C [0-9]{0,5}$"), Pattern.compile("(?i)^[\\d.bs(free) :]+$"), Pattern.compile("^[\\w? #:-]+$") }; diff --git a/src/main/java/com/Acrobot/ChestShop/Utils/uBlock.java b/src/main/java/com/Acrobot/ChestShop/Utils/uBlock.java index 4b39efe..1a762ba 100644 --- a/src/main/java/com/Acrobot/ChestShop/Utils/uBlock.java +++ b/src/main/java/com/Acrobot/ChestShop/Utils/uBlock.java @@ -9,11 +9,15 @@ import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; import org.bukkit.block.Container; import org.bukkit.block.Sign; +import org.bukkit.block.DoubleChest; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.type.Chest; import org.bukkit.block.data.type.WallSign; import org.bukkit.inventory.InventoryHolder; +import java.util.ArrayList; +import java.util.List; + /** * @author Acrobot */ @@ -152,6 +156,65 @@ public class uBlock { return ownerShopSign; } + public static List findConnectedShopSigns(InventoryHolder chestShopInventoryHolder) { + List result = new ArrayList<>(); + + if (chestShopInventoryHolder instanceof DoubleChest) { + BlockState leftChestSide = (BlockState) ((DoubleChest) chestShopInventoryHolder).getLeftSide(); + BlockState rightChestSide = (BlockState) ((DoubleChest) chestShopInventoryHolder).getRightSide(); + + if (leftChestSide == null || rightChestSide == null) { + return result; + } + + Block leftChest = leftChestSide.getBlock(); + Block rightChest = rightChestSide.getBlock(); + + if (ChestShopSign.isShopBlock(leftChest)) { + result.addAll(uBlock.findConnectedShopSigns(leftChest)); + } + + if (ChestShopSign.isShopBlock(rightChest)) { + result.addAll(uBlock.findConnectedShopSigns(rightChest)); + } + } + + else if (chestShopInventoryHolder instanceof BlockState) { + Block chestBlock = ((BlockState) chestShopInventoryHolder).getBlock(); + + if (ChestShopSign.isShopBlock(chestBlock)) { + result.addAll(uBlock.findConnectedShopSigns(chestBlock)); + } + } + + return result; + } + + public static List findConnectedShopSigns(Block chestBlock) { + List result = new ArrayList<>(); + + for (BlockFace bf : SHOP_FACES) { + Block faceBlock = chestBlock.getRelative(bf); + + if (!BlockUtil.isSign(faceBlock)) { + continue; + } + + Sign sign = (Sign) faceBlock.getState(); + + Container signContainer = findConnectedContainer(sign); + if (!chestBlock.equals(signContainer.getBlock())) { + continue; + } + + if (ChestShopSign.isValid(sign)) { + result.add(sign); + } + } + + return result; + } + public static Sign findAnyNearbyShopSign(Block block) { for (BlockFace bf : SHOP_FACES) { Block faceBlock = block.getRelative(bf);