diff --git a/com/Acrobot/ChestShop/ChestShop.java b/com/Acrobot/ChestShop/ChestShop.java index 46aa1bb..8f0598c 100644 --- a/com/Acrobot/ChestShop/ChestShop.java +++ b/com/Acrobot/ChestShop/ChestShop.java @@ -15,9 +15,12 @@ import com.Acrobot.ChestShop.Listeners.ItemInfoListener; import com.Acrobot.ChestShop.Listeners.Player.PlayerConnect; import com.Acrobot.ChestShop.Listeners.Player.PlayerInteract; import com.Acrobot.ChestShop.Listeners.Player.ShortNameSaver; -import com.Acrobot.ChestShop.Listeners.Transaction.EmptyShopDeleter; -import com.Acrobot.ChestShop.Listeners.Transaction.TransactionLogger; -import com.Acrobot.ChestShop.Listeners.Transaction.TransactionMessageSender; +import com.Acrobot.ChestShop.Listeners.Shop.PostTransaction.EconomicModule; +import com.Acrobot.ChestShop.Listeners.Shop.PostTransaction.ItemManager; +import com.Acrobot.ChestShop.Listeners.Shop.PreTransaction.*; +import com.Acrobot.ChestShop.Listeners.Shop.PostTransaction.EmptyShopDeleter; +import com.Acrobot.ChestShop.Listeners.Shop.PostTransaction.TransactionLogger; +import com.Acrobot.ChestShop.Listeners.Shop.PostTransaction.TransactionMessageSender; import com.Acrobot.ChestShop.Logging.FileFormatter; import com.avaje.ebean.EbeanServer; import com.lennardf1989.bukkitex.Database; @@ -37,6 +40,9 @@ import java.util.List; import java.util.logging.FileHandler; import java.util.logging.Logger; +import static com.Acrobot.ChestShop.Config.Property.ALLOW_PARTIAL_TRANSACTIONS; +import static com.Acrobot.ChestShop.Config.Property.SHOP_INTERACTION_INTERVAL; + /** * Main file of the plugin * @@ -137,6 +143,7 @@ public class ChestShop extends JavaPlugin { registerEvent(new SignChange()); registerEvent(new EntityExplode()); registerEvent(new PlayerConnect()); + registerEvent(new PlayerInteract()); registerEvent(new ItemInfoListener()); @@ -144,9 +151,24 @@ public class ChestShop extends JavaPlugin { registerEvent(new TransactionLogger()); registerEvent(new TransactionMessageSender()); - registerEvent(new ShortNameSaver()); + registerEvent(new EconomicModule()); + registerEvent(new ItemManager()); - registerEvent(new PlayerInteract(Config.getInteger(Property.SHOP_INTERACTION_INTERVAL))); + registerEvent(new CreativeModeIgnorer()); + registerEvent(new PermissionChecker()); + registerEvent(new PriceValidator()); + registerEvent(new ShopValidator()); + registerEvent(new StockFittingChecker()); + registerEvent(new ErrorMessageSender()); + registerEvent(new SpamClickProtector(Config.getInteger(SHOP_INTERACTION_INTERVAL))); + + if (Config.getBoolean(ALLOW_PARTIAL_TRANSACTIONS)) { + registerEvent(new PartialTransactionModule()); + } else { + registerEvent(new AmountAndPriceChecker()); + } + + registerEvent(new ShortNameSaver()); } public void registerEvent(Listener listener) { diff --git a/com/Acrobot/ChestShop/Events/PreTransactionEvent.java b/com/Acrobot/ChestShop/Events/PreTransactionEvent.java index 7f69730..416493d 100644 --- a/com/Acrobot/ChestShop/Events/PreTransactionEvent.java +++ b/com/Acrobot/ChestShop/Events/PreTransactionEvent.java @@ -1,47 +1,112 @@ package com.Acrobot.ChestShop.Events; -import com.Acrobot.ChestShop.Shop.Shop; +import org.bukkit.OfflinePlayer; +import org.bukkit.block.Sign; import org.bukkit.entity.Player; -import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import static com.Acrobot.ChestShop.Events.PreTransactionEvent.TransactionOutcome.TRANSACTION_SUCCESFUL; +import static com.Acrobot.ChestShop.Events.TransactionEvent.TransactionType; /** * @author Acrobot */ -public class PreTransactionEvent extends Event implements Cancellable { +public class PreTransactionEvent extends Event { private static final HandlerList handlers = new HandlerList(); - private final Shop shop; - private final Player player; - private final TransactionEvent.Type transactionType; + private final Player client; + private OfflinePlayer owner; - private boolean isCancelled = false; + private final TransactionType transactionType; + private final Sign sign; - public PreTransactionEvent(Shop shop, Player player, TransactionEvent.Type type) { - this.shop = shop; - this.player = player; + private Inventory ownerInventory; + private Inventory clientInventory; + + private ItemStack[] items; + private double price; + + private TransactionOutcome transactionOutcome = TRANSACTION_SUCCESFUL; + + public PreTransactionEvent(Inventory ownerInventory, Inventory clientInventory, ItemStack[] items, double price, Player client, OfflinePlayer owner, Sign sign, TransactionType type) { + this.ownerInventory = ownerInventory; + this.clientInventory = (clientInventory == null ? client.getInventory() : clientInventory); + + this.items = items; + this.price = price; + + this.client = client; + this.owner = owner; + + this.sign = sign; this.transactionType = type; } - public Shop getShop() { - return shop; + public Sign getSign() { + return sign; } - public Player getPlayer() { - return player; + public double getPrice() { + return price; } - public TransactionEvent.Type getTransactionType() { + public void setPrice(double price) { + this.price = price; + } + + public void setStock(ItemStack... stock) { + items = stock; + } + + public ItemStack[] getStock() { + return items; + } + + public Player getClient() { + return client; + } + + public OfflinePlayer getOwner() { + return owner; + } + + public void setOwner(OfflinePlayer owner) { + this.owner = owner; + } + + public Inventory getOwnerInventory() { + return ownerInventory; + } + + public void setOwnerInventory(Inventory ownerInventory) { + this.ownerInventory = ownerInventory; + } + + public void setClientInventory(Inventory clientInventory) { + this.clientInventory = clientInventory; + } + + public Inventory getClientInventory() { + return clientInventory; + } + + public TransactionType getTransactionType() { return transactionType; } public boolean isCancelled() { - return isCancelled; + return transactionOutcome != TRANSACTION_SUCCESFUL; } - public void setCancelled(boolean b) { - this.isCancelled = b; + public TransactionOutcome getTransactionOutcome() { + return transactionOutcome; + } + + public void setCancelled(TransactionOutcome reason) { + transactionOutcome = reason; } public HandlerList getHandlers() { @@ -51,4 +116,30 @@ public class PreTransactionEvent extends Event implements Cancellable { public static HandlerList getHandlerList() { return handlers; } + + public enum TransactionOutcome { + SHOP_DOES_NOT_BUY_THIS_ITEM, + SHOP_DOES_NOT_SELL_THIS_ITEM, + + CLIENT_DOES_NOT_HAVE_PERMISSION, + + CLIENT_DOES_NOT_HAVE_ENOUGH_MONEY, + SHOP_DOES_NOT_HAVE_ENOUGH_MONEY, + + NOT_ENOUGH_SPACE_IN_CHEST, + NOT_ENOUGH_SPACE_IN_INVENTORY, + + NOT_ENOUGH_STOCK_IN_CHEST, + NOT_ENOUGH_STOCK_IN_INVENTORY, + + INVALID_SHOP, + + SPAM_CLICKING_PROTECTION, + CREATIVE_MODE_PROTECTION, + SHOP_IS_RESTRICTED, + + OTHER, //For plugin use! + + TRANSACTION_SUCCESFUL + } } diff --git a/com/Acrobot/ChestShop/Events/TransactionEvent.java b/com/Acrobot/ChestShop/Events/TransactionEvent.java index ff0f0f0..26ea4c4 100644 --- a/com/Acrobot/ChestShop/Events/TransactionEvent.java +++ b/com/Acrobot/ChestShop/Events/TransactionEvent.java @@ -1,10 +1,11 @@ package com.Acrobot.ChestShop.Events; -import com.Acrobot.ChestShop.Containers.Container; +import org.bukkit.OfflinePlayer; import org.bukkit.block.Sign; import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; +import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; /** @@ -12,65 +13,81 @@ import org.bukkit.inventory.ItemStack; */ public class TransactionEvent extends Event { private static final HandlerList handlers = new HandlerList(); + private final TransactionType type; - private Container container; - private Sign sign; + private final Inventory ownerInventory; + private final Inventory clientInventory; - private Player client; - private String owner; + private final Player client; + private final OfflinePlayer owner; - private ItemStack item; - private int itemAmount; - private double price; + private final ItemStack[] stock; + private final double price; - private Type transactionType; + private final Sign sign; + + public TransactionEvent(PreTransactionEvent event, Sign sign) { + this.type = event.getTransactionType(); + + this.ownerInventory = event.getOwnerInventory(); + this.clientInventory = event.getClientInventory(); + + this.client = event.getClient(); + this.owner = event.getOwner(); + + this.stock = event.getStock(); + this.price = event.getPrice(); - public TransactionEvent(Type transactionType, Container container, Sign sign, Player client, String owner, ItemStack item, double price) { - this.container = container; this.sign = sign; + } + + public TransactionEvent(TransactionType type, Inventory ownerInventory, Inventory clientInventory, Player client, OfflinePlayer owner, ItemStack[] stock, double price, Sign sign){ + this.type = type; + + this.ownerInventory = ownerInventory; + this.clientInventory = clientInventory; this.client = client; this.owner = owner; - this.item = item; - this.itemAmount = item.getAmount(); - - this.transactionType = transactionType; + this.stock = stock; this.price = price; + + this.sign = sign; } - public Type getTransactionType() { - return transactionType; + public TransactionType getTransactionType() { + return type; } - public Container getContainer() { - return container; + public Inventory getOwnerInventory() { + return ownerInventory; } - public Sign getSign() { - return sign; + public Inventory getClientInventory() { + return clientInventory; } public Player getClient() { return client; } - public String getOwner() { + public OfflinePlayer getOwner() { return owner; } - public ItemStack getItem() { - return item; - } - - public int getItemAmount() { - return itemAmount; + public ItemStack[] getStock() { + return stock; } public double getPrice() { return price; } + public Sign getSign() { + return sign; + } + public HandlerList getHandlers() { return handlers; } @@ -79,7 +96,7 @@ public class TransactionEvent extends Event { return handlers; } - public enum Type { + public enum TransactionType { BUY, SELL } diff --git a/com/Acrobot/ChestShop/Listeners/Shop/PostTransaction/EconomicModule.java b/com/Acrobot/ChestShop/Listeners/Shop/PostTransaction/EconomicModule.java new file mode 100644 index 0000000..69c348f --- /dev/null +++ b/com/Acrobot/ChestShop/Listeners/Shop/PostTransaction/EconomicModule.java @@ -0,0 +1,34 @@ +package com.Acrobot.ChestShop.Listeners.Shop.PostTransaction; + +import com.Acrobot.ChestShop.Economy.Economy; +import com.Acrobot.ChestShop.Events.TransactionEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +import static com.Acrobot.ChestShop.Events.TransactionEvent.TransactionType.BUY; +import static com.Acrobot.ChestShop.Events.TransactionEvent.TransactionType.SELL; + +/** + * @author Acrobot + */ +public class EconomicModule implements Listener { + @EventHandler + public static void onBuyTransaction(TransactionEvent event) { + if (event.getTransactionType() != BUY) { + return; + } + + Economy.subtract(event.getClient().getName(), event.getPrice()); + Economy.add(event.getOwner().getName(), event.getPrice()); + } + + @EventHandler + public static void onSellTransaction(TransactionEvent event) { + if (event.getTransactionType() != SELL) { + return; + } + + Economy.subtract(event.getOwner().getName(), event.getPrice()); + Economy.add(event.getClient().getName(), event.getPrice()); + } +} diff --git a/com/Acrobot/ChestShop/Listeners/Shop/PostTransaction/EmptyShopDeleter.java b/com/Acrobot/ChestShop/Listeners/Shop/PostTransaction/EmptyShopDeleter.java new file mode 100644 index 0000000..363d819 --- /dev/null +++ b/com/Acrobot/ChestShop/Listeners/Shop/PostTransaction/EmptyShopDeleter.java @@ -0,0 +1,41 @@ +package com.Acrobot.ChestShop.Listeners.Shop.PostTransaction; + +import com.Acrobot.ChestShop.Config.Config; +import com.Acrobot.ChestShop.Config.Property; +import com.Acrobot.ChestShop.Events.TransactionEvent; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +/** + * @author Acrobot + */ +public class EmptyShopDeleter implements Listener { + @EventHandler + public static void onTransaction(TransactionEvent event) { + if (event.getTransactionType() != TransactionEvent.TransactionType.BUY) { + return; + } + + if (shopShouldBeRemoved(event.getOwnerInventory())) { + event.getSign().getBlock().setType(Material.AIR); + event.getOwnerInventory().addItem(new ItemStack(Material.SIGN, 1)); + } + } + + private static boolean shopShouldBeRemoved(Inventory inventory) { + return Config.getBoolean(Property.REMOVE_EMPTY_SHOPS) && isEmpty(inventory); + } + + public static boolean isEmpty(Inventory inventory) { + for (ItemStack item : inventory.getContents()) { + if (item != null) { + return false; + } + } + + return true; + } +} diff --git a/com/Acrobot/ChestShop/Listeners/Shop/PostTransaction/ItemManager.java b/com/Acrobot/ChestShop/Listeners/Shop/PostTransaction/ItemManager.java new file mode 100644 index 0000000..b4631b4 --- /dev/null +++ b/com/Acrobot/ChestShop/Listeners/Shop/PostTransaction/ItemManager.java @@ -0,0 +1,50 @@ +package com.Acrobot.ChestShop.Listeners.Shop.PostTransaction; + +import com.Acrobot.Breeze.Utils.InventoryUtil; +import com.Acrobot.ChestShop.Events.TransactionEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import static com.Acrobot.ChestShop.Events.TransactionEvent.TransactionType.BUY; +import static com.Acrobot.ChestShop.Events.TransactionEvent.TransactionType.SELL; + +/** + * @author Acrobot + */ +public class ItemManager implements Listener { + @EventHandler + public static void shopItemRemover(TransactionEvent event) { + if (event.getTransactionType() != BUY) { + return; + } + + removeItems(event.getOwnerInventory(), event.getStock()); + addItems(event.getClientInventory(), event.getStock()); + + event.getClient().updateInventory(); + } + + @EventHandler + public static void inventoryItemRemover(TransactionEvent event) { + if (event.getTransactionType() != SELL) { + return; + } + + removeItems(event.getClientInventory(), event.getStock()); + addItems(event.getOwnerInventory(), event.getStock()); + + event.getClient().updateInventory(); + } + + private static void removeItems(Inventory inventory, ItemStack[] items) { + for (ItemStack item : items) { + InventoryUtil.remove(item, inventory); + } + } + + private static void addItems(Inventory inventory, ItemStack[] items) { + inventory.addItem(items); + } +} diff --git a/com/Acrobot/ChestShop/Listeners/Transaction/TransactionLogger.java b/com/Acrobot/ChestShop/Listeners/Shop/PostTransaction/TransactionLogger.java similarity index 61% rename from com/Acrobot/ChestShop/Listeners/Transaction/TransactionLogger.java rename to com/Acrobot/ChestShop/Listeners/Shop/PostTransaction/TransactionLogger.java index b06c177..04dec7e 100644 --- a/com/Acrobot/ChestShop/Listeners/Transaction/TransactionLogger.java +++ b/com/Acrobot/ChestShop/Listeners/Shop/PostTransaction/TransactionLogger.java @@ -1,4 +1,4 @@ -package com.Acrobot.ChestShop.Listeners.Transaction; +package com.Acrobot.ChestShop.Listeners.Shop.PostTransaction; import com.Acrobot.ChestShop.ChestShop; import com.Acrobot.ChestShop.Config.Config; @@ -13,7 +13,7 @@ import org.bukkit.inventory.ItemStack; import static com.Acrobot.Breeze.Utils.MaterialUtil.getSignName; import static com.Acrobot.ChestShop.Config.Property.GENERATE_STATISTICS_PAGE; import static com.Acrobot.ChestShop.Config.Property.LOG_TO_DATABASE; -import static com.Acrobot.ChestShop.Events.TransactionEvent.Type.BUY; +import static com.Acrobot.ChestShop.Events.TransactionEvent.TransactionType.BUY; /** * @author Acrobot @@ -31,9 +31,11 @@ public class TransactionLogger implements Listener { message.append(" sold "); } - message.append(event.getItemAmount()).append(' '); - message.append(getSignName(event.getItem())).append(" for "); - message.append(event.getPrice()); + for (ItemStack item : event.getStock()) { + message.append(item.getAmount()).append(' ').append(getSignName(item)); + } + + message.append(" for ").append(event.getPrice()); if (event.getTransactionType() == BUY) { message.append(" from "); @@ -42,7 +44,6 @@ public class TransactionLogger implements Listener { } message.append(event.getOwner()).append(' '); - message.append(locationToString(event.getSign().getLocation())); ChestShop.getBukkitLogger().info(message.toString()); @@ -54,23 +55,26 @@ public class TransactionLogger implements Listener { return; } - Transaction transaction = new Transaction(); - ItemStack item = event.getItem(); + double pricePerStack = event.getPrice() / event.getStock().length; - transaction.setAmount(event.getItemAmount()); + for (ItemStack item : event.getStock()) { + Transaction transaction = new Transaction(); - transaction.setItemID(item.getTypeId()); - transaction.setItemDurability(item.getDurability()); + transaction.setAmount(event.getStock()[0].getAmount()); - transaction.setPrice((float) event.getPrice()); + transaction.setItemID(item.getTypeId()); + transaction.setItemDurability(item.getDurability()); - transaction.setShopOwner(event.getOwner()); - transaction.setShopUser(event.getClient().getName()); + transaction.setPrice((float) pricePerStack); - transaction.setSec(System.currentTimeMillis() / 1000); - transaction.setBuy(event.getTransactionType() == BUY); + transaction.setShopOwner(event.getOwner().getName()); + transaction.setShopUser(event.getClient().getName()); - Queue.addToQueue(transaction); + transaction.setSec(System.currentTimeMillis() / 1000); + transaction.setBuy(event.getTransactionType() == BUY); + + Queue.addToQueue(transaction); + } } private static String locationToString(Location loc) { diff --git a/com/Acrobot/ChestShop/Listeners/Transaction/TransactionMessageSender.java b/com/Acrobot/ChestShop/Listeners/Shop/PostTransaction/TransactionMessageSender.java similarity index 65% rename from com/Acrobot/ChestShop/Listeners/Transaction/TransactionMessageSender.java rename to com/Acrobot/ChestShop/Listeners/Shop/PostTransaction/TransactionMessageSender.java index b790e48..5cb118c 100644 --- a/com/Acrobot/ChestShop/Listeners/Transaction/TransactionMessageSender.java +++ b/com/Acrobot/ChestShop/Listeners/Shop/PostTransaction/TransactionMessageSender.java @@ -1,6 +1,5 @@ -package com.Acrobot.ChestShop.Listeners.Transaction; +package com.Acrobot.ChestShop.Listeners.Shop.PostTransaction; -import com.Acrobot.Breeze.Utils.StringUtil; import com.Acrobot.ChestShop.Config.Config; import com.Acrobot.ChestShop.Config.Language; import com.Acrobot.ChestShop.Economy.Economy; @@ -10,6 +9,10 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; +import org.bukkit.inventory.ItemStack; + +import java.util.HashMap; +import java.util.Map; import static com.Acrobot.ChestShop.Config.Language.*; import static com.Acrobot.ChestShop.Config.Property.SHOW_TRANSACTION_INFORMATION_CLIENT; @@ -21,7 +24,7 @@ import static com.Acrobot.ChestShop.Config.Property.SHOW_TRANSACTION_INFORMATION public class TransactionMessageSender implements Listener { @EventHandler(priority = EventPriority.MONITOR) public static void onTransaction(TransactionEvent event) { - if (event.getTransactionType() == TransactionEvent.Type.BUY) { + if (event.getTransactionType() == TransactionEvent.TransactionType.BUY) { sendBuyMessage(event); } else { sendSellMessage(event); @@ -29,23 +32,22 @@ public class TransactionMessageSender implements Listener { } protected static void sendBuyMessage(TransactionEvent event) { - String itemName = StringUtil.capitalizeFirstLetter(event.getItem().getType().name()); - String owner = event.getOwner(); + String itemName = parseItemInformation(event.getStock()); + String owner = event.getOwner().getName(); Player player = event.getClient(); - int amount = event.getItemAmount(); String price = Economy.formatBalance(event.getPrice()); if (Config.getBoolean(SHOW_TRANSACTION_INFORMATION_CLIENT)) { - String message = formatMessage(YOU_BOUGHT_FROM_SHOP, itemName, price, amount); + String message = formatMessage(YOU_BOUGHT_FROM_SHOP, itemName, price); message = message.replace("%owner", owner); player.sendMessage(message); } if (Config.getBoolean(SHOW_TRANSACTION_INFORMATION_OWNER)) { - String message = formatMessage(SOMEBODY_BOUGHT_FROM_YOUR_SHOP, itemName, price, amount); + String message = formatMessage(SOMEBODY_BOUGHT_FROM_YOUR_SHOP, itemName, price); message = message.replace("%buyer", player.getName()); sendMessageToOwner(message, event); @@ -53,31 +55,54 @@ public class TransactionMessageSender implements Listener { } protected static void sendSellMessage(TransactionEvent event) { - String itemName = StringUtil.capitalizeFirstLetter(event.getItem().getType().name()); - String owner = event.getOwner(); + String itemName = parseItemInformation(event.getStock()); + String owner = event.getOwner().getName(); Player player = event.getClient(); - int amount = event.getItemAmount(); String price = Economy.formatBalance(event.getPrice()); if (Config.getBoolean(SHOW_TRANSACTION_INFORMATION_CLIENT)) { - String message = formatMessage(YOU_SOLD_TO_SHOP, itemName, price, amount); + String message = formatMessage(YOU_SOLD_TO_SHOP, itemName, price); message = message.replace("%buyer", owner); player.sendMessage(message); } if (Config.getBoolean(SHOW_TRANSACTION_INFORMATION_OWNER)) { - String message = formatMessage(SOMEBODY_SOLD_TO_YOUR_SHOP, itemName, price, amount); + String message = formatMessage(SOMEBODY_SOLD_TO_YOUR_SHOP, itemName, price); message = message.replace("%seller", player.getName()); sendMessageToOwner(message, event); } } + private static String parseItemInformation(ItemStack[] items) { + Map itemMap = new HashMap(); + + for (ItemStack item : items) { + String matName = item.getType().name(); + + if (itemMap.containsKey(matName)) { + int curAmount = itemMap.get(matName); + + itemMap.put(matName, curAmount + item.getAmount()); + } else { + itemMap.put(matName, item.getAmount()); + } + } + + StringBuilder message = new StringBuilder(15); + + for (Map.Entry item : itemMap.entrySet()) { + message.append(item.getValue()).append(' ').append(item.getKey()); + } + + return message.toString(); + } + private static void sendMessageToOwner(String message, TransactionEvent event) { - String owner = event.getOwner(); + String owner = event.getOwner().getName(); Player player = Bukkit.getPlayer(owner); @@ -86,9 +111,8 @@ public class TransactionMessageSender implements Listener { } } - private static String formatMessage(Language message, String item, String price, int amount) { + private static String formatMessage(Language message, String item, String price) { return Config.getLocal(message) - .replace("%amount", String.valueOf(amount)) .replace("%item", item) .replace("%price", price); } diff --git a/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/AmountAndPriceChecker.java b/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/AmountAndPriceChecker.java new file mode 100644 index 0000000..19f08e1 --- /dev/null +++ b/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/AmountAndPriceChecker.java @@ -0,0 +1,66 @@ +package com.Acrobot.ChestShop.Listeners.Shop.PreTransaction; + +import com.Acrobot.Breeze.Utils.InventoryUtil; +import com.Acrobot.ChestShop.Economy.Economy; +import com.Acrobot.ChestShop.Events.PreTransactionEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import static com.Acrobot.ChestShop.Events.PreTransactionEvent.TransactionOutcome.*; +import static com.Acrobot.ChestShop.Events.TransactionEvent.TransactionType.BUY; +import static com.Acrobot.ChestShop.Events.TransactionEvent.TransactionType.SELL; + +/** + * @author Acrobot + */ +public class AmountAndPriceChecker implements Listener { + @EventHandler + public static void onItemCheck(PreTransactionEvent event) { + if (event.isCancelled() || event.getTransactionType() != BUY) { + return; + } + + ItemStack[] stock = event.getStock(); + Inventory ownerInventory = event.getOwnerInventory(); + + if (!Economy.hasEnough(event.getClient().getName(), event.getPrice())) { + event.setCancelled(CLIENT_DOES_NOT_HAVE_ENOUGH_MONEY); + return; + } + + if (!hasItems(ownerInventory, stock)) { + event.setCancelled(NOT_ENOUGH_STOCK_IN_CHEST); + } + } + + @EventHandler + public static void onSellItemCheck(PreTransactionEvent event) { + if (event.isCancelled() || event.getTransactionType() != SELL) { + return; + } + + ItemStack[] stock = event.getStock(); + Inventory clientInventory = event.getClientInventory(); + + if (!Economy.hasEnough(event.getOwner().getName(), event.getPrice())) { + event.setCancelled(SHOP_DOES_NOT_HAVE_ENOUGH_MONEY); + return; + } + + if (!hasItems(clientInventory, stock)) { + event.setCancelled(NOT_ENOUGH_STOCK_IN_INVENTORY); + } + } + + private static boolean hasItems(Inventory inventory, ItemStack[] items) { + for (ItemStack item : items) { + if (InventoryUtil.getAmount(item, inventory) < item.getAmount()) { + return false; + } + } + + return true; + } +} diff --git a/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/CreativeModeIgnorer.java b/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/CreativeModeIgnorer.java new file mode 100644 index 0000000..e8fb896 --- /dev/null +++ b/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/CreativeModeIgnorer.java @@ -0,0 +1,27 @@ +package com.Acrobot.ChestShop.Listeners.Shop.PreTransaction; + +import com.Acrobot.ChestShop.Config.Config; +import com.Acrobot.ChestShop.Events.PreTransactionEvent; +import org.bukkit.GameMode; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; + +import static com.Acrobot.ChestShop.Config.Property.IGNORE_CREATIVE_MODE; +import static com.Acrobot.ChestShop.Events.PreTransactionEvent.TransactionOutcome.CREATIVE_MODE_PROTECTION; + +/** + * @author Acrobot + */ +public class CreativeModeIgnorer implements Listener { + @EventHandler(priority = EventPriority.LOWEST) + public static void onPreTransaction(PreTransactionEvent event) { + if (event.isCancelled()) { + return; + } + + if (Config.getBoolean(IGNORE_CREATIVE_MODE) && event.getClient().getGameMode() == GameMode.CREATIVE) { + event.setCancelled(CREATIVE_MODE_PROTECTION); + } + } +} diff --git a/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/ErrorMessageSender.java b/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/ErrorMessageSender.java new file mode 100644 index 0000000..b0a54e7 --- /dev/null +++ b/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/ErrorMessageSender.java @@ -0,0 +1,73 @@ +package com.Acrobot.ChestShop.Listeners.Shop.PreTransaction; + +import com.Acrobot.ChestShop.Config.Config; +import com.Acrobot.ChestShop.Config.Language; +import com.Acrobot.ChestShop.Config.Property; +import com.Acrobot.ChestShop.Events.PreTransactionEvent; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; + +/** + * @author Acrobot + */ +public class ErrorMessageSender implements Listener { + @EventHandler(priority = EventPriority.MONITOR) + public static void onMessage(PreTransactionEvent event) { + if (!event.isCancelled()) { + return; + } + + Language message = null; + + switch(event.getTransactionOutcome()) { + case SHOP_DOES_NOT_BUY_THIS_ITEM: + message = Language.NO_BUYING_HERE; + break; + case SHOP_DOES_NOT_SELL_THIS_ITEM: + message = Language.NO_SELLING_HERE; + break; + case CLIENT_DOES_NOT_HAVE_PERMISSION: + message = Language.NO_PERMISSION; + break; + case CLIENT_DOES_NOT_HAVE_ENOUGH_MONEY: + message = Language.NOT_ENOUGH_MONEY; + break; + case SHOP_DOES_NOT_HAVE_ENOUGH_MONEY: + message = Language.NOT_ENOUGH_MONEY_SHOP; + break; + case NOT_ENOUGH_SPACE_IN_CHEST: + message = Language.NOT_ENOUGH_SPACE_IN_CHEST; + break; + case NOT_ENOUGH_SPACE_IN_INVENTORY: + message = Language.NOT_ENOUGH_SPACE_IN_INVENTORY; + break; + case NOT_ENOUGH_STOCK_IN_INVENTORY: + message = Language.NOT_ENOUGH_ITEMS_TO_SELL; + break; + case NOT_ENOUGH_STOCK_IN_CHEST: + sendMessageToOwner(event.getOwner(), Language.NOT_ENOUGH_STOCK_IN_YOUR_SHOP); + message = Language.NOT_ENOUGH_STOCK; + break; + case SHOP_IS_RESTRICTED: + message = Language.ACCESS_DENIED; + break; + case INVALID_SHOP: + message = Language.INVALID_SHOP_DETECTED; + break; + } + + if (message != null) { + event.getClient().sendMessage(Config.getLocal(message)); + } + } + + private static void sendMessageToOwner(OfflinePlayer owner, Language message) { + if (owner.isOnline() && Config.getBoolean(Property.SHOW_MESSAGE_OUT_OF_STOCK)) { + Player player = (Player) owner; + player.sendMessage(Config.getLocal(message)); + } + } +} diff --git a/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/PartialTransactionModule.java b/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/PartialTransactionModule.java new file mode 100644 index 0000000..b284977 --- /dev/null +++ b/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/PartialTransactionModule.java @@ -0,0 +1,189 @@ +package com.Acrobot.ChestShop.Listeners.Shop.PreTransaction; + +import com.Acrobot.Breeze.Utils.InventoryUtil; +import com.Acrobot.Breeze.Utils.MaterialUtil; +import com.Acrobot.ChestShop.Economy.Economy; +import com.Acrobot.ChestShop.Events.PreTransactionEvent; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import java.util.LinkedList; +import java.util.List; + +import static com.Acrobot.ChestShop.Events.PreTransactionEvent.TransactionOutcome.*; +import static com.Acrobot.ChestShop.Events.TransactionEvent.TransactionType.BUY; +import static com.Acrobot.ChestShop.Events.TransactionEvent.TransactionType.SELL; + +/** + * @author Acrobot + */ +public class PartialTransactionModule implements Listener { + @EventHandler(priority = EventPriority.LOW) + public static void onPreBuyTransaction(PreTransactionEvent event) { + if (event.isCancelled() || event.getTransactionType() != BUY) { + return; + } + + Player client = event.getClient(); + String clientName = client.getName(); + ItemStack[] stock = event.getStock(); + + double price = event.getPrice(); + double pricePerItem = event.getPrice() / getItemCount(stock); + double walletMoney = Economy.getBalance(clientName); + + if (!Economy.hasEnough(clientName, price)) { + int amountAffordable = getAmountOfAffordableItems(walletMoney, pricePerItem); + + if (amountAffordable < 1) { + event.setCancelled(CLIENT_DOES_NOT_HAVE_ENOUGH_MONEY); + return; + } + + event.setPrice(amountAffordable * pricePerItem); + event.setStock(getCountedItemStack(stock, amountAffordable)); + } + + stock = event.getStock(); + + if (!hasItems(event.getOwnerInventory(), stock)) { + ItemStack[] itemsHad = getItems(stock, event.getOwnerInventory()); + int posessedItemCount = getItemCount(itemsHad); + + if (posessedItemCount <= 0) { + event.setCancelled(NOT_ENOUGH_STOCK_IN_CHEST); + return; + } + + event.setPrice(pricePerItem * posessedItemCount); + event.setStock(itemsHad); + } + } + + @EventHandler(priority = EventPriority.LOW) + public static void onPreSellTransaction(PreTransactionEvent event) { + if (event.isCancelled() || event.getTransactionType() != SELL) { + return; + } + + String ownerName = event.getOwner().getName(); + ItemStack[] stock = event.getStock(); + + double price = event.getPrice(); + double pricePerItem = event.getPrice() / getItemCount(stock); + double walletMoney = Economy.getBalance(ownerName); + + if (!Economy.hasEnough(ownerName, price)) { + int amountAffordable = getAmountOfAffordableItems(walletMoney, pricePerItem); + + if (amountAffordable < 1) { + event.setCancelled(SHOP_DOES_NOT_HAVE_ENOUGH_MONEY); + return; + } + + event.setPrice(amountAffordable * pricePerItem); + event.setStock(getCountedItemStack(stock, amountAffordable)); + } + + stock = event.getStock(); + + if (!hasItems(event.getClientInventory(), stock)) { + ItemStack[] itemsHad = getItems(stock, event.getClientInventory()); + int posessedItemCount = getItemCount(itemsHad); + + if (posessedItemCount <= 0) { + event.setCancelled(NOT_ENOUGH_STOCK_IN_INVENTORY); + return; + } + + event.setPrice(pricePerItem * posessedItemCount); + event.setStock(itemsHad); + } + } + + private static boolean hasItems(Inventory inventory, ItemStack[] items) { + for (ItemStack item : items) { + if (!inventory.contains(item, item.getAmount())) { + return false; + } + } + + return true; + } + + private static int getAmountOfAffordableItems(double walletMoney, double pricePerItem) { + return (int) Math.floor(walletMoney / pricePerItem); + } + + private static int getItemCount(ItemStack[] items) { + int count = 0; + + for (ItemStack item : items) { + count += item.getAmount(); + } + + return count; + } + + private static ItemStack[] getItems(ItemStack[] stock, Inventory inventory) { + List neededItems = new LinkedList(); + + boolean itemAdded = false; + + for (ItemStack item : stock) { + for (ItemStack needed : neededItems) { + if (MaterialUtil.equals(item, needed)) { + needed.setAmount(needed.getAmount() + item.getAmount()); + itemAdded = true; + } + } + + if (!itemAdded) { + neededItems.add(item); + itemAdded = false; + } + } + + List toReturn = new LinkedList(); + + for (ItemStack item : neededItems) { + int amount = InventoryUtil.getAmount(item, inventory); + ItemStack clone = item.clone(); + + clone.setAmount(amount > item.getAmount() ? item.getAmount() : amount); + + toReturn.add(clone); + } + + return toReturn.toArray(stock); + } + + private static ItemStack[] getCountedItemStack(ItemStack[] stock, int numberOfItems) { + int left = numberOfItems; + LinkedList stacks = new LinkedList(); + + for (ItemStack stack : stock) { + int count = stack.getAmount(); + if (left > count) { + stacks.add(stack); + left -= count; + } else { + ItemStack clone = stack.clone(); + + clone.setAmount(left); + stacks.add(clone); + left = 0; + } + + if (left <= 0) { + break; + } + } + + return stacks.toArray(stock); + } +} diff --git a/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/PermissionChecker.java b/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/PermissionChecker.java new file mode 100644 index 0000000..ca878da --- /dev/null +++ b/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/PermissionChecker.java @@ -0,0 +1,44 @@ +package com.Acrobot.ChestShop.Listeners.Shop.PreTransaction; + +import com.Acrobot.ChestShop.Events.PreTransactionEvent; +import com.Acrobot.ChestShop.Events.TransactionEvent; +import com.Acrobot.ChestShop.Permission; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.inventory.ItemStack; + +import static com.Acrobot.ChestShop.Events.PreTransactionEvent.TransactionOutcome.CLIENT_DOES_NOT_HAVE_PERMISSION; +import static com.Acrobot.ChestShop.Events.TransactionEvent.TransactionType.BUY; + +/** + * @author Acrobot + */ +public class PermissionChecker implements Listener { + @EventHandler + public static void onPermissionCheck(PreTransactionEvent event) { + if (event.isCancelled()) { + return; + } + + Player client = event.getClient(); + TransactionEvent.TransactionType transactionType = event.getTransactionType(); + + for (ItemStack stock : event.getStock()) { + String matID = Integer.toString(stock.getTypeId()); + + boolean hasPerm; + + if (transactionType == BUY) { + hasPerm = Permission.has(client, Permission.BUY) || Permission.has(client, Permission.BUY_ID + matID); + } else { + hasPerm = Permission.has(client, Permission.SELL) || Permission.has(client, Permission.SELL_ID + matID); + } + + if (!hasPerm) { + event.setCancelled(CLIENT_DOES_NOT_HAVE_PERMISSION); + return; + } + } + } +} diff --git a/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/PriceValidator.java b/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/PriceValidator.java new file mode 100644 index 0000000..4706248 --- /dev/null +++ b/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/PriceValidator.java @@ -0,0 +1,34 @@ +package com.Acrobot.ChestShop.Listeners.Shop.PreTransaction; + +import com.Acrobot.Breeze.Utils.PriceUtil; +import com.Acrobot.ChestShop.Events.PreTransactionEvent; +import com.Acrobot.ChestShop.Events.TransactionEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +import static com.Acrobot.ChestShop.Events.PreTransactionEvent.TransactionOutcome.SHOP_DOES_NOT_BUY_THIS_ITEM; +import static com.Acrobot.ChestShop.Events.PreTransactionEvent.TransactionOutcome.SHOP_DOES_NOT_SELL_THIS_ITEM; +import static com.Acrobot.ChestShop.Events.TransactionEvent.TransactionType.BUY; + +/** + * @author Acrobot + */ +public class PriceValidator implements Listener { + @EventHandler + public static void onPriceCheck(PreTransactionEvent event) { + if (event.isCancelled()) { + return; + } + + TransactionEvent.TransactionType transactionType = event.getTransactionType(); + double price = event.getPrice(); + + if (price == PriceUtil.NO_PRICE) { + if (transactionType == BUY) { + event.setCancelled(SHOP_DOES_NOT_BUY_THIS_ITEM); + } else { + event.setCancelled(SHOP_DOES_NOT_SELL_THIS_ITEM); + } + } + } +} diff --git a/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/ShopValidator.java b/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/ShopValidator.java new file mode 100644 index 0000000..8c1678a --- /dev/null +++ b/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/ShopValidator.java @@ -0,0 +1,25 @@ +package com.Acrobot.ChestShop.Listeners.Shop.PreTransaction; + +import com.Acrobot.ChestShop.Events.PreTransactionEvent; +import com.Acrobot.ChestShop.Signs.ChestShopSign; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; + +import static com.Acrobot.ChestShop.Events.PreTransactionEvent.TransactionOutcome.INVALID_SHOP; + +/** + * @author Acrobot + */ +public class ShopValidator implements Listener { + @EventHandler(priority = EventPriority.LOW) + public static void onCheck(PreTransactionEvent event) { + if (event.isCancelled()) { + return; + } + + if (!ChestShopSign.isAdminShop(event.getSign()) && event.getOwnerInventory() == null) { + event.setCancelled(INVALID_SHOP); + } + } +} diff --git a/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/SpamClickProtector.java b/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/SpamClickProtector.java new file mode 100644 index 0000000..2d3ce4f --- /dev/null +++ b/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/SpamClickProtector.java @@ -0,0 +1,40 @@ +package com.Acrobot.ChestShop.Listeners.Shop.PreTransaction; + +import com.Acrobot.ChestShop.Events.PreTransactionEvent; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; + +import java.util.Map; +import java.util.WeakHashMap; + +import static com.Acrobot.ChestShop.Events.PreTransactionEvent.TransactionOutcome.SPAM_CLICKING_PROTECTION; + +/** + * @author Acrobot + */ +public class SpamClickProtector implements Listener { + private final Map TIME_OF_LATEST_CLICK = new WeakHashMap(); + private final int interval; + + public SpamClickProtector(int interval) { + this.interval = interval; + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onClick(PreTransactionEvent event) { + if (event.isCancelled()) { + return; + } + + Player clicker = event.getClient(); + + if (TIME_OF_LATEST_CLICK.containsKey(clicker) && (System.currentTimeMillis() - TIME_OF_LATEST_CLICK.get(clicker)) < interval) { + event.setCancelled(SPAM_CLICKING_PROTECTION); + return; + } + + TIME_OF_LATEST_CLICK.put(clicker, System.currentTimeMillis()); + } +} diff --git a/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/StockFittingChecker.java b/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/StockFittingChecker.java new file mode 100644 index 0000000..8705970 --- /dev/null +++ b/com/Acrobot/ChestShop/Listeners/Shop/PreTransaction/StockFittingChecker.java @@ -0,0 +1,56 @@ +package com.Acrobot.ChestShop.Listeners.Shop.PreTransaction; + +import com.Acrobot.Breeze.Utils.InventoryUtil; +import com.Acrobot.ChestShop.Events.PreTransactionEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import static com.Acrobot.ChestShop.Events.PreTransactionEvent.TransactionOutcome.NOT_ENOUGH_SPACE_IN_CHEST; +import static com.Acrobot.ChestShop.Events.PreTransactionEvent.TransactionOutcome.NOT_ENOUGH_SPACE_IN_INVENTORY; +import static com.Acrobot.ChestShop.Events.TransactionEvent.TransactionType.BUY; +import static com.Acrobot.ChestShop.Events.TransactionEvent.TransactionType.SELL; + +/** + * @author Acrobot + */ +public class StockFittingChecker implements Listener { + @EventHandler + public static void onSellCheck(PreTransactionEvent event) { + if (event.isCancelled() || event.getTransactionType() != SELL) { + return; + } + + Inventory shopInventory = event.getOwnerInventory(); + ItemStack[] stock = event.getStock(); + + if (!itemsFitInInventory(stock, shopInventory)) { + event.setCancelled(NOT_ENOUGH_SPACE_IN_CHEST); + } + } + + @EventHandler + public static void onBuyCheck(PreTransactionEvent event) { + if (event.isCancelled() || event.getTransactionType() != BUY) { + return; + } + + Inventory clientInventory = event.getClientInventory(); + ItemStack[] stock = event.getStock(); + + if (!itemsFitInInventory(stock, clientInventory)) { + event.setCancelled(NOT_ENOUGH_SPACE_IN_INVENTORY); + } + } + + private static boolean itemsFitInInventory(ItemStack[] items, Inventory inventory) { + for (ItemStack item : items) { + if (!InventoryUtil.fits(item, inventory)) { + return false; + } + } + + return true; + } +} diff --git a/com/Acrobot/ChestShop/Listeners/Transaction/EmptyShopDeleter.java b/com/Acrobot/ChestShop/Listeners/Transaction/EmptyShopDeleter.java deleted file mode 100644 index c15306d..0000000 --- a/com/Acrobot/ChestShop/Listeners/Transaction/EmptyShopDeleter.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.Acrobot.ChestShop.Listeners.Transaction; - -import com.Acrobot.ChestShop.Config.Config; -import com.Acrobot.ChestShop.Config.Property; -import com.Acrobot.ChestShop.Containers.Container; -import com.Acrobot.ChestShop.Events.TransactionEvent; -import org.bukkit.Material; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.inventory.ItemStack; - -/** - * @author Acrobot - */ -public class EmptyShopDeleter implements Listener { - @EventHandler - public static void onTransaction(TransactionEvent event) { - if (event.getTransactionType() != TransactionEvent.Type.BUY) { - return; - } - - if (shopShouldBeRemoved(event.getContainer())) { - event.getSign().getBlock().setType(Material.AIR); - event.getContainer().addItem(new ItemStack(Material.SIGN, 1)); - } - } - - private static boolean shopShouldBeRemoved(Container container) { - return Config.getBoolean(Property.REMOVE_EMPTY_SHOPS) && shopIsEmpty(container); - } - - private static boolean shopIsEmpty(Container container) { - return container.isEmpty(); - } -}