From 3aa8414c5bf79728e829c92708d1431c08fbafdc Mon Sep 17 00:00:00 2001 From: Len <40720638+destro174@users.noreply.github.com> Date: Mon, 22 Aug 2022 00:21:36 +0200 Subject: [PATCH] Refactor/rework --- .gitignore | 3 +- build.gradle.kts | 37 ++++++ .../com/alttd/playershops/PlayerShops.java | 4 + .../playershops/commands/ShopCommand.java | 47 +++++++ .../playershops/config/GuiIconConfig.java | 65 ++++++++++ .../alttd/playershops/gui/AbstractGui.java | 116 +++++++++++++++++ .../com/alttd/playershops/gui/GuiIcon.java | 76 +++++++++++ .../playershops/gui/ShopManagementGui.java | 44 +++++++ .../playershops/handler/ShopHandler.java | 19 ++- .../playershops/hook/WorldGuardHook.java | 11 ++ .../playershops/listener/PlayerListener.java | 2 +- .../listener/TransactionListener.java | 97 ++++++++++++++ .../alttd/playershops/shop/PlayerShop.java | 114 ++++++++++++++++- .../com/alttd/playershops/shop/ShopType.java | 2 +- .../alttd/playershops/utils/EconomyUtils.java | 58 +++++++++ .../playershops/utils/InventoryUtils.java | 119 ++++++++++++++++++ .../com/alttd/playershops/utils/Util.java | 83 ++++++++++++ 17 files changed, 885 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/alttd/playershops/commands/ShopCommand.java create mode 100644 src/main/java/com/alttd/playershops/config/GuiIconConfig.java create mode 100644 src/main/java/com/alttd/playershops/gui/AbstractGui.java create mode 100644 src/main/java/com/alttd/playershops/gui/GuiIcon.java create mode 100644 src/main/java/com/alttd/playershops/gui/ShopManagementGui.java create mode 100644 src/main/java/com/alttd/playershops/hook/WorldGuardHook.java create mode 100644 src/main/java/com/alttd/playershops/listener/TransactionListener.java create mode 100644 src/main/java/com/alttd/playershops/utils/EconomyUtils.java create mode 100644 src/main/java/com/alttd/playershops/utils/InventoryUtils.java create mode 100644 src/main/java/com/alttd/playershops/utils/Util.java diff --git a/.gitignore b/.gitignore index 6790c8d..cc8dd0d 100644 --- a/.gitignore +++ b/.gitignore @@ -47,5 +47,6 @@ out/ *~ !gradle/wrapper/gradle-wrapper.jar -/run/ +/run1/ /lib/ +/run/ diff --git a/build.gradle.kts b/build.gradle.kts index 3ccb8fc..e4f1ecd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -82,10 +82,23 @@ bukkit { authors = listOf("destro174") depend = listOf("Vault") + commands { + register("playershop") { + description = "This is a test command!" + aliases = listOf("shop") + permission = "playershops.command.playershop" + } + } + permissions { register("playershops.admin") { description = "Admin permission for the ${rootProject.name} plugin." default = BukkitPluginDescription.Permission.Default.FALSE + children = listOf( + "playershops.shop.create", + "playershops.shop.break.other", + "playershops.shop.use" + ) } register("playershops.shoplimit") { description = "Base permission to allow per player shop limits." @@ -102,6 +115,30 @@ bukkit { register("playershops.shop.break.other") { description = "Allows players to break other players shops." default = BukkitPluginDescription.Permission.Default.FALSE + children = listOf( + "playershops.shop.break", + ) + } + register("playershops.shop.use") { + description = "Allows players to use all playershops." + default = BukkitPluginDescription.Permission.Default.FALSE + children = listOf( + "playershops.shop.use.buy", + "playershops.shop.use.sell", + "playershops.shop.use.gamble" + ) + } + register("playershops.shop.use.buy") { + description = "Allows players to use buying playershops." + default = BukkitPluginDescription.Permission.Default.FALSE + } + register("playershops.shop.use.sell") { + description = "Allows players to use selling playershops." + default = BukkitPluginDescription.Permission.Default.FALSE + } + register("playershops.shop.use.gamble") { + description = "Allows players to use gamble playershops." + default = BukkitPluginDescription.Permission.Default.FALSE } } } \ No newline at end of file diff --git a/src/main/java/com/alttd/playershops/PlayerShops.java b/src/main/java/com/alttd/playershops/PlayerShops.java index 2da4e08..680232f 100644 --- a/src/main/java/com/alttd/playershops/PlayerShops.java +++ b/src/main/java/com/alttd/playershops/PlayerShops.java @@ -9,6 +9,7 @@ import com.alttd.playershops.handler.ShopHandler; import com.alttd.playershops.listener.BlockListener; import com.alttd.playershops.listener.PlayerListener; import com.alttd.playershops.listener.ShopListener; +import com.alttd.playershops.listener.TransactionListener; import com.alttd.playershops.shop.ShopType; import com.alttd.playershops.storage.DatabaseManager; import lombok.Getter; @@ -31,6 +32,7 @@ public class PlayerShops extends JavaPlugin { private ShopListener shopListener; private PlayerListener playerListener; private BlockListener blockListener; + private TransactionListener transactionListener; public void onEnable() { instance = this; @@ -77,12 +79,14 @@ public class PlayerShops extends JavaPlugin { shopListener = new ShopListener(this); playerListener = new PlayerListener(this); blockListener = new BlockListener(this); + transactionListener = new TransactionListener(this); } private void unRegisterListeners() { shopListener.unregister(); playerListener.unregister(); blockListener.unregister(); + transactionListener.unregister(); } private void registerCommands() { diff --git a/src/main/java/com/alttd/playershops/commands/ShopCommand.java b/src/main/java/com/alttd/playershops/commands/ShopCommand.java new file mode 100644 index 0000000..cf960c6 --- /dev/null +++ b/src/main/java/com/alttd/playershops/commands/ShopCommand.java @@ -0,0 +1,47 @@ +package com.alttd.playershops.commands; + +import com.alttd.playershops.gui.ShopManagementGui; +import com.alttd.playershops.shop.PlayerShop; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +// expose brig in Galaxy? +public class ShopCommand implements CommandExecutor, TabCompleter { + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player player)) return true; + if (args.length == 0) { + return true; + } + switch (args[0].toLowerCase()) { + case "reload": + break; + case "open": + ShopManagementGui gui = new ShopManagementGui(player.getUniqueId(), new PlayerShop(player.getLocation(), player.getLocation(), player)); + gui.open(); + break; + default: + sender.sendMessage("invalid command useage"); + break; + } + return false; + } + + @Override + public List onTabComplete(CommandSender sender, Command cmd, String label, String[] args) { + if (args.length == 1) { + return Stream.of("reload", "open") + .filter(arg -> arg.startsWith(args[0].toLowerCase())) + .collect(Collectors.toList()); + } + return null; + } +} diff --git a/src/main/java/com/alttd/playershops/config/GuiIconConfig.java b/src/main/java/com/alttd/playershops/config/GuiIconConfig.java new file mode 100644 index 0000000..592776c --- /dev/null +++ b/src/main/java/com/alttd/playershops/config/GuiIconConfig.java @@ -0,0 +1,65 @@ +package com.alttd.playershops.config; + +import io.leangen.geantyref.TypeToken; +import org.spongepowered.configurate.serialize.SerializationException; + +import java.util.ArrayList; +import java.util.List; + +@SuppressWarnings("unused") +public class GuiIconConfig { + + + private final String icon; + private final String configPath; + private final String defaultPath; + public GuiIconConfig(String icon) { + this.icon = icon; + this.configPath = "gui-icon." + this.icon + "."; + this.defaultPath = "gui-icon.divider."; + init(); + } + + static int version; + + public void init() { + Config.config.readConfig(GuiIconConfig.class, this); + } + + private static void set(String path, Object def) { + Config.config.set(path, def); + } + + private String getString(String path, String def) { + set(defaultPath + path, def); + return Config.config.getNode(configPath + path).getString( + Config.config.getNode(defaultPath + path).getString(def)); + } + + private int getInt(String path, int def) { + set(defaultPath + path, def); + return Config.config.getNode(configPath + path).getInt( + Config.config.getNode(defaultPath + path).getInt(def)); + } + + private List getStringList(String path, List def) { + try { + set(defaultPath + path, def); + return Config.config.getNode(configPath + path).getList(TypeToken.get(String.class), + Config.config.getNode(defaultPath + path).getList(TypeToken.get(String.class), def)); + } catch(SerializationException ignore) {} + return new ArrayList<>(); + } + + public int slot = 0; + public String displayName = "Default"; + public String material = "dirt"; + public List lore = new ArrayList<>(); + private void guiItems() { + slot = getInt("slot", slot); + material = getString("material", material); + displayName = getString("display-name", displayName); + lore = getStringList("lore", lore); + } + +} diff --git a/src/main/java/com/alttd/playershops/gui/AbstractGui.java b/src/main/java/com/alttd/playershops/gui/AbstractGui.java new file mode 100644 index 0000000..ffe372f --- /dev/null +++ b/src/main/java/com/alttd/playershops/gui/AbstractGui.java @@ -0,0 +1,116 @@ +package com.alttd.playershops.gui; + +import com.alttd.playershops.config.GuiIconConfig; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +public abstract class AbstractGui implements InventoryHolder { + + Inventory inventory; + protected final int INV_SIZE = 54; + int currentSlot; + UUID uuid; + int pageIndex; + + AbstractGui(UUID uuid) { + this.uuid = uuid; + this.currentSlot = 0; + } + + public void open() { + Player player = getPlayer(); + if (player != null) { + player.openInventory(inventory); + } + } + + boolean close() { + Player player = getPlayer(); + if (player != null) { + player.closeInventory(); + return true; + } + return false; + } + + void scrollPageNext() { + ItemStack nextPageIcon = inventory.getItem(GuiIcon.MENUBAR_NEXT_PAGE.getSlot()); + if (nextPageIcon != null && nextPageIcon.equals(getNextPageIcon())) { + inventory.setItem(GuiIcon.MENUBAR_NEXT_PAGE.getSlot(), getPrevPageIcon()); + ++pageIndex; + initInvContents(); + } + } + + void scrollPagePrev() { + ItemStack prevPageIcon = inventory.getItem(GuiIcon.MENUBAR_PREV_PAGE.getSlot()); + if (prevPageIcon != null && prevPageIcon.equals(getPrevPageIcon())) { + inventory.setItem(GuiIcon.MENUBAR_PREV_PAGE.getSlot(), getNextPageIcon()); + --pageIndex; + if (pageIndex == 0) { + inventory.setItem(GuiIcon.MENUBAR_PREV_PAGE.getSlot(), null); + } + initInvContents(); + } + } + + protected boolean addItem(ItemStack icon) { + if (currentSlot == inventory.getSize()) + return false; + + inventory.setItem(currentSlot, icon); + currentSlot++; + + return true; + } + + void initInvContents() { + clearInvBody(); + currentSlot = 0; + } + + protected void clearInvBody() { + for (int i = 0; i < inventory.getSize() - 9; ++i) { + inventory.setItem(i, null); + } + } + + void makeMenuBar() { + for (int i = inventory.getSize() - 9; i < inventory.getSize(); ++i) { + inventory.setItem(i, getDivider()); + } + } + + Player getPlayer() { + return Bukkit.getPlayer(uuid); + } + + @NotNull + @Override + public Inventory getInventory() { + return inventory; + } + + ItemStack getPrevPageIcon() { + return GuiIcon.MENUBAR_PREV_PAGE.getItemStack(); + } + + ItemStack getNextPageIcon() { + return GuiIcon.MENUBAR_NEXT_PAGE.getItemStack(); + } + + ItemStack getExitIcon() { + return null; + } + + ItemStack getDivider() { + return GuiIcon.DIVIDER.getItemStack(); + } + +} diff --git a/src/main/java/com/alttd/playershops/gui/GuiIcon.java b/src/main/java/com/alttd/playershops/gui/GuiIcon.java new file mode 100644 index 0000000..2a2aa8f --- /dev/null +++ b/src/main/java/com/alttd/playershops/gui/GuiIcon.java @@ -0,0 +1,76 @@ +package com.alttd.playershops.gui; + +import com.alttd.playershops.config.GuiIconConfig; +import com.alttd.playershops.config.ShopTypeConfig; +import com.alttd.playershops.utils.Util; +import lombok.Getter; +import net.kyori.adventure.text.Component; +import org.bukkit.Material; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.ArrayList; +import java.util.List; + +public enum GuiIcon { + DIVIDER, + MENUBAR_BACK, + MENUBAR_EXIT, + MENUBAR_SEARCH, + MENUBAR_PREV_PAGE, + MENUBAR_NEXT_PAGE, + + MANAGE_SHOP, + MANAGE_SHOP_BALANCE_ADD, + MANAGE_SHOP_BALANCE_REMOVE, + MANAGE_SHOP_SALES, + MANAGE_SHOP_ITEM, + MANAGE_SHOP_TYPE, + + HOME_LIST_OWN_SHOPS, + HOME_LIST_PLAYERS, + HOME_SETTINGS, + LIST_SHOP, + LIST_PLAYER, + LIST_PLAYER_ADMIN; + + @Getter + private final int slot; + @Getter + private final ItemStack itemStack; + + GuiIcon() { + GuiIconConfig config = new GuiIconConfig(this.toString()); + this.slot = config.slot; + this.itemStack = createItem(config.material, config.displayName, config.lore); + } + + private ItemStack createItem(String materialString, String displayName, List itemlore) { + Material material = Material.getMaterial(materialString); + if (material == null) + return null; + + ItemStack item = new ItemStack(material); + ItemMeta itemMeta = item.getItemMeta(); + itemMeta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES); + itemMeta.displayName(format(displayName)); + List lore = new ArrayList<>(); + for (String line : itemlore) { + lore.add(format(line)); + } + itemMeta.lore(lore); + item.setItemMeta(itemMeta); + + return item; + } + + private Component format(String s) { + return Util.parseMiniMessage(s, null); + } + + @Override + public String toString() { + return name().toLowerCase(); + } +} diff --git a/src/main/java/com/alttd/playershops/gui/ShopManagementGui.java b/src/main/java/com/alttd/playershops/gui/ShopManagementGui.java new file mode 100644 index 0000000..94ae024 --- /dev/null +++ b/src/main/java/com/alttd/playershops/gui/ShopManagementGui.java @@ -0,0 +1,44 @@ +package com.alttd.playershops.gui; + +import com.alttd.playershops.shop.PlayerShop; +import com.alttd.playershops.utils.Util; +import org.bukkit.Bukkit; + +import java.util.UUID; + +public class ShopManagementGui extends AbstractGui { + + PlayerShop shop; + + public ShopManagementGui(UUID uuid, PlayerShop shop) { + super(uuid); + this.inventory = Bukkit.createInventory(this, INV_SIZE, Util.parseMiniMessage("Config.gui.management-title", null)); + this.shop = shop; + initInvContents(); + } + + @Override + void initInvContents() { +// super.initInvContents(); + makeMenuBar(); + for (int i = 0; i < inventory.getSize() - 9; ++i) { + inventory.setItem(i, getDivider()); + } + inventory.setItem(GuiIcon.MANAGE_SHOP.getSlot(), GuiIcon.MANAGE_SHOP.getItemStack()); + inventory.setItem(GuiIcon.MANAGE_SHOP_BALANCE_ADD.getSlot(), GuiIcon.MANAGE_SHOP_BALANCE_ADD.getItemStack()); + inventory.setItem(GuiIcon.MANAGE_SHOP_BALANCE_REMOVE.getSlot(), GuiIcon.MANAGE_SHOP_BALANCE_REMOVE.getItemStack()); + inventory.setItem(GuiIcon.MANAGE_SHOP_SALES.getSlot(), GuiIcon.MANAGE_SHOP_SALES.getItemStack()); + inventory.setItem(GuiIcon.MANAGE_SHOP_ITEM.getSlot(), GuiIcon.MANAGE_SHOP_ITEM.getItemStack()); + inventory.setItem(GuiIcon.MANAGE_SHOP_TYPE.getSlot(), GuiIcon.MANAGE_SHOP_TYPE.getItemStack()); + } + + @Override + void makeMenuBar() { + super.makeMenuBar(); + inventory.setItem(GuiIcon.MENUBAR_BACK.getSlot(), GuiIcon.MENUBAR_BACK.getItemStack()); + inventory.setItem(GuiIcon.MENUBAR_PREV_PAGE.getSlot(), GuiIcon.MENUBAR_PREV_PAGE.getItemStack()); +// inventory.setItem(49, new ItemStack(Material.NETHER_STAR)); + inventory.setItem(GuiIcon.MENUBAR_NEXT_PAGE.getSlot(), GuiIcon.MENUBAR_NEXT_PAGE.getItemStack()); + } + +} diff --git a/src/main/java/com/alttd/playershops/handler/ShopHandler.java b/src/main/java/com/alttd/playershops/handler/ShopHandler.java index d4db6c6..8f08ee0 100644 --- a/src/main/java/com/alttd/playershops/handler/ShopHandler.java +++ b/src/main/java/com/alttd/playershops/handler/ShopHandler.java @@ -22,12 +22,14 @@ public class ShopHandler { private final Object2IntMap shopBuildLimits; @Getter private final Map shopLocation; + private final Map shopSignLocation; @Getter private final ArrayList shopMaterials; public ShopHandler(PlayerShops instance) { plugin = instance; shopLocation = new ConcurrentHashMap<>(); + shopSignLocation = new ConcurrentHashMap<>(); shopBuildLimits = new Object2IntOpenHashMap<>(); shopBuildLimits.defaultReturnValue(Config.shopLimit); shopMaterials = new ArrayList<>(); // TODO move into parent method where materials are loaded in. @@ -58,6 +60,7 @@ public class ShopHandler { public void removeShops() { shopLocation.clear(); + shopSignLocation.clear(); } public boolean isShopMaterial(Block block) { @@ -77,18 +80,22 @@ public class ShopHandler { } public PlayerShop getShopBySignLocation(Location signLocation) { - for (PlayerShop shop : shopLocation.values()) { - if (shop.getSignLocation().equals(signLocation)) - return shop; - } - return null; + Location newLocation = new Location(signLocation.getWorld(), signLocation.getBlockX(), signLocation.getBlockY(), signLocation.getBlockZ()); + + return shopSignLocation.get(newLocation); } public void addShop(PlayerShop shop) { shopLocation.put(shop.getShopLocation(), shop); + shopSignLocation.put(shop.getSignLocation(), shop); } - public boolean canPlayerBreakShop(Player player, PlayerShop shop) { + public void removeShop(PlayerShop shop) { + shopLocation.remove(shop.getShopLocation()); + shopSignLocation.remove(shop.getSignLocation()); + } + + public boolean canPlayerBreakShop(Player player, PlayerShop shop) { // TODO move to util? if (player.getUniqueId().equals(shop.getOwnerUUID()) && player.hasPermission("playershops.shop.break")) return true; diff --git a/src/main/java/com/alttd/playershops/hook/WorldGuardHook.java b/src/main/java/com/alttd/playershops/hook/WorldGuardHook.java new file mode 100644 index 0000000..310e1f7 --- /dev/null +++ b/src/main/java/com/alttd/playershops/hook/WorldGuardHook.java @@ -0,0 +1,11 @@ +package com.alttd.playershops.hook; + +import org.bukkit.Location; +import org.bukkit.entity.Player; + +public class WorldGuardHook { + + public static boolean canUseShopInRegion(Player player, Location location) { + return true; + } +} diff --git a/src/main/java/com/alttd/playershops/listener/PlayerListener.java b/src/main/java/com/alttd/playershops/listener/PlayerListener.java index 53e7c81..49a053f 100644 --- a/src/main/java/com/alttd/playershops/listener/PlayerListener.java +++ b/src/main/java/com/alttd/playershops/listener/PlayerListener.java @@ -97,6 +97,7 @@ public class PlayerListener extends EventListener { Component signLine = event.line(0); if (signLine == null) return; + String signLineString = PlainTextComponentSerializer.plainText().serialize(signLine); if (!signLineString.equalsIgnoreCase(Config.shopCreationWord)) return; @@ -131,5 +132,4 @@ public class PlayerListener extends EventListener { } } - } diff --git a/src/main/java/com/alttd/playershops/listener/TransactionListener.java b/src/main/java/com/alttd/playershops/listener/TransactionListener.java new file mode 100644 index 0000000..7470174 --- /dev/null +++ b/src/main/java/com/alttd/playershops/listener/TransactionListener.java @@ -0,0 +1,97 @@ +package com.alttd.playershops.listener; + +import com.alttd.playershops.PlayerShops; +import com.alttd.playershops.gui.ShopManagementGui; +import com.alttd.playershops.handler.ShopHandler; +import com.alttd.playershops.hook.WorldGuardHook; +import com.alttd.playershops.shop.PlayerShop; +import com.alttd.playershops.shop.TransactionError; +import org.bukkit.Tag; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; + +/** + * Dedicated class to listen to transactions for shops. + * + */ +public class TransactionListener extends EventListener { + + private final PlayerShops plugin; + + public TransactionListener(PlayerShops plugin) { + this.plugin = plugin; + this.register(this.plugin); + } + + @EventHandler(ignoreCancelled = true) + public void onShopSignClick(PlayerInteractEvent event) { + if (event.getHand() == EquipmentSlot.OFF_HAND) { + return; + } + Player player = event.getPlayer(); + + if (!(event.getAction() == Action.RIGHT_CLICK_BLOCK)) + return; + + Block block = event.getClickedBlock(); + if (block == null || Tag.WALL_SIGNS.isTagged(block.getType())) + return; + + PlayerShop playerShop = plugin.getShopHandler().getShopBySignLocation(block.getLocation()); + if (playerShop == null || !playerShop.isInitialized()) + return; + + // if we ever need worldguard support add it to the hook + if (!WorldGuardHook.canUseShopInRegion(player, block.getLocation())) { + event.setCancelled(true); + return; + } + + if (!player.hasPermission("playershops.shop.use." + playerShop.getType().toString())) { + player.sendMiniMessage("You do not have permission to use " + playerShop.getType().toString() + " shops.", null); // TODO config + } + + ShopHandler shopHandler = plugin.getShopHandler(); + + // Failsafe. If we have a shopsign but no block cancel the event, log error save and unload the shop + if (!shopHandler.isShopMaterial(playerShop.getShopLocation().getBlock())) { + event.setCancelled(true); + shopHandler.removeShop(playerShop); + } + + // TODO upgrade this to an util method check if owner/trusted and open management interface + if (playerShop.getOwnerUUID().equals(player.getUniqueId())) { + ShopManagementGui gui = new ShopManagementGui(player.getUniqueId(), playerShop); + gui.open(); + return; + } + executeTransaction(player, playerShop); + } + + private void executeTransaction(Player player, PlayerShop shop) { + if (shop == null || shop.getItemStack() == null) + return; + + int orders = 1; + + if (player.isSneaking()) + orders = shop.getItemStack().getMaxStackSize(); + TransactionError transactionError = shop.executeTransaction(orders, player); + + if (transactionError != TransactionError.NONE) { + switch (transactionError) { + case INVENTORY_FULL_PLAYER -> { + + } + case CANCELLED -> { + } + case INSUFFICIENT_FUNDS_SHOP -> { + } + } + } + } +} diff --git a/src/main/java/com/alttd/playershops/shop/PlayerShop.java b/src/main/java/com/alttd/playershops/shop/PlayerShop.java index c7a8ec3..09830c5 100644 --- a/src/main/java/com/alttd/playershops/shop/PlayerShop.java +++ b/src/main/java/com/alttd/playershops/shop/PlayerShop.java @@ -1,7 +1,11 @@ package com.alttd.playershops.shop; import com.alttd.playershops.PlayerShops; +import com.alttd.playershops.events.PlayerExchangeShopEvent; +import com.alttd.playershops.utils.EconomyUtils; +import com.alttd.playershops.utils.InventoryUtils; import com.alttd.playershops.utils.ShopUtil; +import com.alttd.playershops.utils.Util; import lombok.Getter; import lombok.Setter; import net.kyori.adventure.text.minimessage.MiniMessage; @@ -27,7 +31,7 @@ public class PlayerShop { @Getter private UUID ownerUUID; @Getter @Setter - private ShopType type = ShopType.DEFAULT; + private ShopType type = ShopType.NONE; @Getter private final Location signLocation; @Getter @@ -76,7 +80,7 @@ public class PlayerShop { } public int getRemainingStock() { - return ShopUtil.countItems(getInventory(), getItemStack()); + return InventoryUtils.countItems(getInventory(), getItemStack()); } public int getRemainingSpace() { @@ -121,7 +125,7 @@ public class PlayerShop { } public boolean isInitialized() { - return type != ShopType.DEFAULT; + return type != ShopType.NONE; } public void updateSign() { @@ -149,4 +153,108 @@ public class PlayerShop { } }.runTaskLater(PlayerShops.getInstance(), 2L); } + + public double getPricePerItem() { + double pricePer = this.getPrice() / this.getAmount(); + return this.getPrice() / this.getAmount(); + } + + public void removeBalance(double amount) { + this.balance -= amount; + } + + public void addBalance(double amount) { + this.balance += amount; + } + + + public TransactionError executeTransaction(int orders, Player player) { + return switch (getType()) { + case SELL -> executeSellTransaction(orders, player); + case BUY -> executeBuyTransaction(orders, player); + case GAMBLE -> executeGambleTransaction(orders, player); + default -> TransactionError.NONE; // This should not happen + }; + } + + private TransactionError executeSellTransaction(int orders, Player player) { + ItemStack itemStack = getItemStack().clone(); + double price = getPrice(); + if (orders == itemStack.getMaxStackSize()) { + itemStack.setAmount(orders); + price = getPricePerItem() * orders; + } + + int shopItems = InventoryUtils.countItems(getInventory(), itemStack); + if (shopItems < itemStack.getAmount()) + return TransactionError.INSUFFICIENT_FUNDS_SHOP; + + boolean hasFunds = EconomyUtils.hasSufficientFunds(player, price); + if (!hasFunds) + return TransactionError.INSUFFICIENT_FUNDS_PLAYER; + + boolean hasRoom = EconomyUtils.canAcceptFunds(this, price); + if (!hasRoom) + return TransactionError.INVENTORY_FULL_SHOP; + + boolean playerHasRoom = InventoryUtils.hasRoom(player.getInventory(), itemStack); + if (!playerHasRoom) + return TransactionError.INVENTORY_FULL_PLAYER; + + PlayerExchangeShopEvent playerExchangeShopEvent = new PlayerExchangeShopEvent(player, this); + if (Util.callCancellableEvent(playerExchangeShopEvent)) { + return TransactionError.CANCELLED; + } + + InventoryUtils.removeItem(getInventory(), itemStack); + EconomyUtils.removeFunds(player, price); + addBalance(price); + InventoryUtils.addItem(player.getInventory(), itemStack); + player.updateInventory(); + + return TransactionError.NONE; + } + + private TransactionError executeBuyTransaction(int orders, Player player) { + ItemStack itemStack = getItemStack().clone(); + double price = getPrice(); + if (orders == itemStack.getMaxStackSize()) { + itemStack.setAmount(orders); + price = getPricePerItem() * orders; + } + + int playerItems = InventoryUtils.countItems(player.getInventory(), itemStack); + if (playerItems < itemStack.getAmount()) + return TransactionError.INSUFFICIENT_FUNDS_PLAYER; + + boolean hasFunds = EconomyUtils.hasSufficientFunds(this, price); + if (!hasFunds) + return TransactionError.INSUFFICIENT_FUNDS_SHOP; + + boolean hasRoom = EconomyUtils.canAcceptFunds(player, price); + if (!hasRoom) + return TransactionError.INVENTORY_FULL_PLAYER; + + boolean shopHasRoom = InventoryUtils.hasRoom(getInventory(), itemStack); + if (!shopHasRoom) + return TransactionError.INVENTORY_FULL_SHOP; + + PlayerExchangeShopEvent playerExchangeShopEvent = new PlayerExchangeShopEvent(player, this); + if (Util.callCancellableEvent(playerExchangeShopEvent)) { + return TransactionError.CANCELLED; + } + + InventoryUtils.removeItem(player.getInventory(), itemStack); + removeBalance(price); + EconomyUtils.addFunds(player, price); + InventoryUtils.addItem(getInventory(), itemStack); + player.updateInventory(); + + return TransactionError.NONE; + } + + private TransactionError executeGambleTransaction(int orders, Player player) { + return TransactionError.NONE; + } + } diff --git a/src/main/java/com/alttd/playershops/shop/ShopType.java b/src/main/java/com/alttd/playershops/shop/ShopType.java index e6d378d..1b23021 100644 --- a/src/main/java/com/alttd/playershops/shop/ShopType.java +++ b/src/main/java/com/alttd/playershops/shop/ShopType.java @@ -4,7 +4,7 @@ import com.alttd.playershops.config.ShopTypeConfig; public enum ShopType { - DEFAULT(), + NONE(), SELL(), BUY(), GAMBLE(); diff --git a/src/main/java/com/alttd/playershops/utils/EconomyUtils.java b/src/main/java/com/alttd/playershops/utils/EconomyUtils.java new file mode 100644 index 0000000..93dfdeb --- /dev/null +++ b/src/main/java/com/alttd/playershops/utils/EconomyUtils.java @@ -0,0 +1,58 @@ +package com.alttd.playershops.utils; + +import com.alttd.playershops.PlayerShops; +import com.alttd.playershops.shop.PlayerShop; +import net.milkbowl.vault.economy.EconomyResponse; +import org.bukkit.entity.Player; + +// TODO document +public class EconomyUtils { + + //check to see if the player has enough funds to take out [amount] + //return false if they do not + public static boolean hasSufficientFunds(Player player, double amount) { + double balance = PlayerShops.getInstance().getEconomy().getBalance(player); + return (balance >= amount); + } + + public static boolean hasSufficientFunds(PlayerShop shop, double amount) { + double balance = shop.getBalance(); + return (balance >= amount); + } + + //gets the current funds of the player + public static double getFunds(Player player) { + return PlayerShops.getInstance().getEconomy().getBalance(player); + } + + //removes [amount] of funds from the player + //return false if the player did not have sufficient funds or if something went wrong + public static boolean removeFunds(Player player, double amount) { + EconomyResponse response = PlayerShops.getInstance().getEconomy().withdrawPlayer(player, amount); + return response.transactionSuccess(); + } + + //adds [amount] of funds to the player + //return false if the player is offline + public static boolean addFunds(Player player, double amount) { + if(player.isOnline()) { + EconomyResponse response = PlayerShops.getInstance().getEconomy().depositPlayer(player, amount); + return response.transactionSuccess(); + } else { + return false; +// int temp = PlayerShops.getInstance().Earnings.getInt(player.getUniqueId() + ".earnings"); +// PlayerShops.getInstance().Earnings.set(player.getUniqueId() + ".earnings", temp + amount); +// PlayerShops.getInstance().Earnings.save(); + } + } + + public static boolean canAcceptFunds(Player player, double price) { + // if we ever need to limit the maximum balance a player can have this is the place + return true; + } + + public static boolean canAcceptFunds(PlayerShop shop, double price) { + // if we ever need to limit the maximum balance a shop can have this is the place + return true; + } +} diff --git a/src/main/java/com/alttd/playershops/utils/InventoryUtils.java b/src/main/java/com/alttd/playershops/utils/InventoryUtils.java new file mode 100644 index 0000000..106fdcf --- /dev/null +++ b/src/main/java/com/alttd/playershops/utils/InventoryUtils.java @@ -0,0 +1,119 @@ +package com.alttd.playershops.utils; + +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.HashMap; + +public class InventoryUtils { + + /** + * Counts the number of items in the given inventory where + * Util.matches(inventory item, item) is true. + * + * @param inv + * The inventory to search + * @param item + * The ItemStack to search for + * @return The number of items that match in this inventory. + */ + public static int countItems(Inventory inv, ItemStack item) { + int items = 0; + for (ItemStack iStack : inv.getContents()) { + if (iStack == null) + continue; + if (ShopUtil.matches(item, iStack)) { + items += iStack.getAmount(); + } + } + return items; + } + + // TODO DOCS + //removes itemstack from inventory + //returns the amount of items it could not remove + public static int removeItem(Inventory inventory, ItemStack itemStack) { + if (inventory == null) + return itemStack.getAmount(); + if (itemStack == null || itemStack.getAmount() <= 0) + return 0; + ItemStack[] contents = inventory.getContents(); + int amount = itemStack.getAmount(); + for (ItemStack stack : contents) { + if (stack != null) { + if (ShopUtil.matches(stack, itemStack)) { + if (stack.getAmount() > amount) { + stack.setAmount(stack.getAmount() - amount); + inventory.setContents(contents); + return 0; + } else if (stack.getAmount() == amount) { + stack.setType(Material.AIR); + inventory.setContents(contents); + return 0; + } else { + amount -= stack.getAmount(); + stack.setType(Material.AIR); + } + } + } + } + inventory.setContents(contents); + return amount; + } + + //takes an ItemStack and splits it up into multiple ItemStacks with correct stack sizes + //then adds those items to the given inventory + public static int addItem(Inventory inventory, ItemStack itemStack) { + if (inventory == null) + return itemStack.getAmount(); + if (itemStack.getAmount() <= 0) + return 0; + ArrayList itemStacksAdding = new ArrayList<>(); + + //break up the itemstack into multiple ItemStacks with correct stack size + int fullStacks = itemStack.getAmount() / itemStack.getMaxStackSize(); + int partialStack = itemStack.getAmount() % itemStack.getMaxStackSize(); + for (int i = 0; i < fullStacks; i++) { + ItemStack is = itemStack.clone(); + is.setAmount(is.getMaxStackSize()); + itemStacksAdding.add(is); + } + ItemStack is = itemStack.clone(); + is.setAmount(partialStack); + if (partialStack > 0) + itemStacksAdding.add(is); + + //try adding all items from itemStacksAdding and return number of ones you couldn't add + int amount = 0; + for (ItemStack addItem : itemStacksAdding) { + HashMap noAdd = inventory.addItem(addItem); + for (ItemStack noAddItemstack : noAdd.values()) { + amount += noAddItemstack.getAmount(); + } + } + return amount; + } + + public static boolean hasRoom(Inventory inventory, ItemStack itemStack) { + if (inventory == null) + return false; + if (itemStack.getAmount() <= 0) + return true; + + int overflow = addItem(inventory, itemStack); + + //revert back if inventory cannot hold all of the items + if (overflow > 0) { + ItemStack revert = itemStack.clone(); + revert.setAmount(revert.getAmount() - overflow); + InventoryUtils.removeItem(inventory, revert); + return false; + } + removeItem(inventory, itemStack); + return true; + } + +} diff --git a/src/main/java/com/alttd/playershops/utils/Util.java b/src/main/java/com/alttd/playershops/utils/Util.java new file mode 100644 index 0000000..8eaf292 --- /dev/null +++ b/src/main/java/com/alttd/playershops/utils/Util.java @@ -0,0 +1,83 @@ +package com.alttd.playershops.utils; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import org.bukkit.Bukkit; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; + +import java.util.HashMap; + +public class Util { + + private static MiniMessage miniMessage = null; + + public static HashMap colors; + static { // this might be in minimessage already? + colors = new HashMap<>(); + colors.put("&0", ""); + colors.put("&1", ""); + colors.put("&2", ""); + colors.put("&3", ""); + colors.put("&4", ""); + colors.put("&5", ""); + colors.put("&6", ""); + colors.put("&7", ""); + colors.put("&8", ""); + colors.put("&9", ""); + colors.put("&a", ""); + colors.put("&b", ""); + colors.put("&c", ""); + colors.put("&d", ""); + colors.put("&e", ""); + colors.put("&f", ""); + colors.put("&l", ""); + colors.put("&o", ""); + } + + public static String parseColors(String message) { + for (String key : colors.keySet()) { + if (message.contains(key)) { + message = message.replace(key, colors.get(key)); + } + } + return message; + } + + private static MiniMessage miniMessage() { + if (miniMessage == null) + miniMessage = MiniMessage.miniMessage(); + return miniMessage; + } + + public static Component parseMiniMessage(String message, TagResolver tagResolver) { + message = parseColors(message); + if (tagResolver == null) { + return miniMessage().deserialize(message); + } else { + return miniMessage().deserialize(message, tagResolver); + } + } + + public static String capitalize(String string) { + if (string.length() <= 1) + return string.toUpperCase(); + string = string.toLowerCase(); + return string.substring(0, 1).toUpperCase() + string.toLowerCase().substring(1); + } + + /** + * Call an event and check it is cancelled. + * + * @param event The event implement the Cancellable interface. + * @return The event is cancelled. + */ + public static boolean callCancellableEvent(Cancellable event) { + if (!(event instanceof Event)) { + throw new IllegalArgumentException("Cancellable must is event implement"); + } + Bukkit.getPluginManager().callEvent((Event) event); + return event.isCancelled(); + } +}