diff --git a/build.gradle b/build.gradle index a21af12..adb3247 100755 --- a/build.gradle +++ b/build.gradle @@ -1,64 +1,79 @@ -plugins { - id 'java' -} - -group = 'ru.redguy' -version = '1.0.0' - -repositories { - maven { - name = 'redguy-repo' - url = 'https://rep.redguy.org/repo/maven-minecraft/' - } - mavenCentral() - maven { - name = 'papermc-repo' - url = 'https://papermc.io/repo/repository/maven-public/' - } - maven { - name = 'sonatype' - url = 'https://oss.sonatype.org/content/groups/public/' - } -} - -dependencies { - compileOnly 'com.destroystokyo.paper:paper-api:1.16.5-R0.1-SNAPSHOT' - compileOnly 'me.filoghost.chestcommands:chestcommands-api:4.0.3' - compileOnly 'net.milkbowl.vault:VaultAPI:1.7' - - implementation 'org.jetbrains:annotations:23.0.0' -} - -def targetJavaVersion = 8 -java { - def javaVersion = JavaVersion.toVersion(targetJavaVersion) - sourceCompatibility = javaVersion - targetCompatibility = javaVersion - if (JavaVersion.current() < javaVersion) { - toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) - } -} - -tasks.withType(JavaCompile).configureEach { - if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { - options.release = targetJavaVersion - } -} - -compileJava.options.encoding = 'UTF-8' - -processResources { - def props = [version: version] - inputs.properties props - filteringCharset 'UTF-8' - filesMatching('plugin.yml') { - expand props - } -} - -jar { - configurations.implementation.canBeResolved = true; - from { - configurations.implementation.collect { it.isDirectory() ? it : zipTree(it) } - } +plugins { + id 'java' + id 'com.github.johnrengelman.shadow' version '7.1.2' + id 'xyz.jpenilla.run-paper' version '2.2.0' +} + +group = 'ru.redguy' +version = '1.0.0' + +repositories { + maven { + name = 'redguy-repo' + url = 'https://rep.redguy.org/repo/maven-minecraft/' + } + mavenCentral() + maven { + name = 'papermc-repo' + url = 'https://papermc.io/repo/repository/maven-public/' + } + maven { + name = 'sonatype' + url = 'https://oss.sonatype.org/content/groups/public/' + } +} + +dependencies { + compileOnly 'com.destroystokyo.paper:paper-api:1.16.5-R0.1-SNAPSHOT' + compileOnly 'me.filoghost.chestcommands:chestcommands-api:4.0.4' + compileOnly 'net.milkbowl.vault:VaultAPI:1.7' + implementation 'org.jetbrains:annotations:23.0.0' +} + +def targetJavaVersion = 8 +java { + def javaVersion = JavaVersion.toVersion(targetJavaVersion) + sourceCompatibility = javaVersion + targetCompatibility = javaVersion + if (JavaVersion.current() < javaVersion) { + toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) + } +} + +tasks.withType(JavaCompile).configureEach { + if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { + options.release = targetJavaVersion + } +} + +compileJava.options.encoding = 'UTF-8' + +shadowJar { + archiveClassifier.set('') + configurations = [project.configurations.implementation] +} + +tasks { + runServer { + minecraftVersion('1.16.5') + + // Download required plugins + downloadPlugins { + url('https://github.com/MilkBowl/Vault/releases/download/1.7.3/Vault.jar') + url('https://ci.codemc.io/job/filoghost/job/ChestCommands/lastSuccessfulBuild/artifact/ChestCommands-4.0.5.jar') + } + } +} + +processResources { + def props = [version: version] + inputs.properties props + filteringCharset 'UTF-8' + filesMatching('plugin.yml') { + expand props + } +} + +jar { + configurations.implementation.canBeResolved = true; } \ No newline at end of file diff --git a/src/main/java/ru/redguy/dynamicshop/DynamicShop.java b/src/main/java/ru/redguy/dynamicshop/DynamicShop.java index 4ec55e0..712b60a 100755 --- a/src/main/java/ru/redguy/dynamicshop/DynamicShop.java +++ b/src/main/java/ru/redguy/dynamicshop/DynamicShop.java @@ -7,6 +7,7 @@ import org.bukkit.plugin.java.JavaPlugin; import ru.redguy.dynamicshop.api.ShopItem; import ru.redguy.dynamicshop.commands.AddShopItemCommand; import ru.redguy.dynamicshop.commands.ShopCommand; +import org.jetbrains.annotations.NotNull; public final class DynamicShop extends JavaPlugin { @@ -15,29 +16,43 @@ public final class DynamicShop extends JavaPlugin { } private static DynamicShop instance; + private Economy economy; + @NotNull public static DynamicShop getInstance() { + if (instance == null) { + throw new IllegalStateException("Plugin instance not initialized!"); + } return instance; } - public Economy econ; + public Economy getEconomy() { + return economy; + } @Override public void onEnable() { instance = this; - getConfig().set("init",true); - saveConfig(); - if(!setupEconomy()) { - getLogger().warning("Cannot find economy!"); + + // Save default config if it doesn't exist + saveDefaultConfig(); + + // Setup economy + if (!setupEconomy()) { + getLogger().severe("Disabled due to no Vault dependency found!"); + getServer().getPluginManager().disablePlugin(this); + return; } - getServer().getCommandMap().register("shop","shop",new ShopCommand("shop")); - getServer().getCommandMap().register("additem","shop",new AddShopItemCommand("additem")); - getServer().getCommandMap().register("reloadshop","shop",new AddShopItemCommand("reloadshop")); + + // Register commands + registerCommands(); + + getLogger().info("DynamicShop has been enabled!"); } @Override public void onDisable() { - + getLogger().info("DynamicShop has been disabled!"); } private boolean setupEconomy() { @@ -48,7 +63,18 @@ public final class DynamicShop extends JavaPlugin { if (rsp == null) { return false; } - econ = rsp.getProvider(); - return true; + economy = rsp.getProvider(); + return economy != null; + } + + private void registerCommands() { + try { + getServer().getCommandMap().register("shop", "dynamicshop", new ShopCommand("shop")); + getServer().getCommandMap().register("additem", "dynamicshop", new AddShopItemCommand("additem")); + getServer().getCommandMap().register("reloadshop", "dynamicshop", new AddShopItemCommand("reloadshop")); + getLogger().info("Commands registered successfully!"); + } catch (Exception e) { + getLogger().severe("Failed to register commands: " + e.getMessage()); + } } } diff --git a/src/main/java/ru/redguy/dynamicshop/api/Database.java b/src/main/java/ru/redguy/dynamicshop/api/Database.java index 9d2b750..c6c2dac 100755 --- a/src/main/java/ru/redguy/dynamicshop/api/Database.java +++ b/src/main/java/ru/redguy/dynamicshop/api/Database.java @@ -1,91 +1,130 @@ -package ru.redguy.dynamicshop.api; - -import org.bukkit.configuration.InvalidConfigurationException; -import org.bukkit.configuration.file.YamlConfiguration; -import org.jetbrains.annotations.Nullable; -import ru.redguy.dynamicshop.DynamicShop; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -public class Database { - private static final Database instance = new Database(); - - public static Database getInstance() { - return instance; - } - - private final File file = new File(DynamicShop.getInstance().getDataFolder(), "items.yml"); - private final YamlConfiguration conf = new YamlConfiguration(); - - public Database() { - load(); - save(); - } - - public void load() { - try { - conf.load(file); - } catch (FileNotFoundException e) { - try { - file.createNewFile(); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - try { - conf.load(file); - } catch (IOException | InvalidConfigurationException ex) { - throw new RuntimeException(ex); - } - } catch (IOException | InvalidConfigurationException e) { - throw new RuntimeException(e); - } - } - - private void save() { - try { - conf.save(file); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public List getItems() { - List list = conf.getList("items"); - if(list == null) list = new ArrayList<>(); - List shopItems = new ArrayList<>(); - for (Object o : list) { - if(o instanceof ShopItem) { - shopItems.add((ShopItem) o); - } - } - return shopItems; - } - - public void setItems(List items) { - conf.set("items",items); - save(); - } - - @Nullable - public ShopItem getItem(int index) { - List items = getItems(); - if(items.size()<=index) return null; - return items.get(index); - } - - public void setItem(int index, ShopItem item) { - List items = getItems(); - items.set(index,item); - setItems(items); - } - - public void addItem(ShopItem item) { - List items = getItems(); - items.add(item); - setItems(items); - } -} +package ru.redguy.dynamicshop.api; + +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import ru.redguy.dynamicshop.DynamicShop; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; + +public class Database { + private static final Database instance = new Database(); + private final File file; + private final YamlConfiguration config; + private static final String ITEMS_KEY = "items"; + + private Database() { + this.file = new File(DynamicShop.getInstance().getDataFolder(), "items.yml"); + this.config = new YamlConfiguration(); + loadDatabase(); + } + + @NotNull + public static Database getInstance() { + return instance; + } + + private synchronized void loadDatabase() { + try { + if (!file.exists()) { + DynamicShop.getInstance().getDataFolder().mkdirs(); + file.createNewFile(); + } + config.load(file); + } catch (IOException | InvalidConfigurationException e) { + DynamicShop.getInstance().getLogger().log(Level.SEVERE, "Failed to load database", e); + throw new RuntimeException("Failed to load database", e); + } + } + + private synchronized void saveDatabase() { + try { + config.save(file); + } catch (IOException e) { + DynamicShop.getInstance().getLogger().log(Level.SEVERE, "Failed to save database", e); + throw new RuntimeException("Failed to save database", e); + } + } + + @NotNull + public synchronized List getItems() { + List list = config.getList(ITEMS_KEY); + if (list == null) { + return new ArrayList<>(); + } + + List shopItems = new ArrayList<>(); + for (Object obj : list) { + if (obj instanceof ShopItem) { + shopItems.add((ShopItem) obj); + } else { + DynamicShop.getInstance().getLogger().warning("Found invalid item in database: " + obj); + } + } + return Collections.unmodifiableList(shopItems); + } + + public synchronized void setItems(@NotNull List items) { + if (items == null) { + throw new IllegalArgumentException("Items list cannot be null"); + } + config.set(ITEMS_KEY, new ArrayList<>(items)); // Create a copy to prevent external modification + saveDatabase(); + } + + @Nullable + public synchronized ShopItem getItem(int index) { + List items = getItems(); + if (index < 0 || index >= items.size()) { + return null; + } + return items.get(index).clone(); // Return a clone to prevent modification + } + + public synchronized void setItem(int index, @NotNull ShopItem item) { + if (item == null) { + throw new IllegalArgumentException("Item cannot be null"); + } + List items = new ArrayList<>(getItems()); + if (index < 0) { + throw new IllegalArgumentException("Index cannot be negative"); + } + if (index >= items.size()) { + throw new IllegalArgumentException("Index " + index + " is out of bounds for size " + items.size()); + } + items.set(index, item); + setItems(items); + } + + public synchronized void addItem(@NotNull ShopItem item) { + if (item == null) { + throw new IllegalArgumentException("Item cannot be null"); + } + List items = new ArrayList<>(getItems()); + items.add(item); + setItems(items); + } + + public synchronized void removeItem(int index) { + List items = new ArrayList<>(getItems()); + if (index < 0 || index >= items.size()) { + throw new IllegalArgumentException("Index " + index + " is out of bounds for size " + items.size()); + } + items.remove(index); + setItems(items); + } + + public synchronized void clear() { + setItems(new ArrayList<>()); + } + + public synchronized int size() { + return getItems().size(); + } +} diff --git a/src/main/java/ru/redguy/dynamicshop/api/ShopItem.java b/src/main/java/ru/redguy/dynamicshop/api/ShopItem.java index 4d595e3..df5ab91 100755 --- a/src/main/java/ru/redguy/dynamicshop/api/ShopItem.java +++ b/src/main/java/ru/redguy/dynamicshop/api/ShopItem.java @@ -1,73 +1,102 @@ -package ru.redguy.dynamicshop.api; - -import org.bukkit.Material; -import org.bukkit.configuration.serialization.ConfigurationSerializable; -import org.bukkit.configuration.serialization.SerializableAs; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -import java.util.HashMap; -import java.util.Map; - -@SerializableAs("ShopItem") -public class ShopItem implements Cloneable, ConfigurationSerializable { - private ItemStack item; - private double price; - - public ShopItem(ItemStack item, double price) { - this.item = item; - this.price = price; - } - - public ItemStack getItem() { - return item; - } - - public void setItem(ItemStack item) { - this.item = item; - } - - public double getPrice() { - return price; - } - - public void setPrice(double price) { - this.price = price; - } - - @Contract("_ -> new") - public static @NotNull ShopItem deserialize(@NotNull Map args) { - ItemStack item = new ItemStack(Material.AIR); - double price = 0; - if(args.containsKey("item")) { - item = (ItemStack) args.get("item"); - } - - if(args.containsKey("price")) { - price = (Double) args.get("price"); - } - - return new ShopItem(item,price); - } - - @Override - public @NotNull Map serialize() { - HashMap map = new HashMap<>(); - map.put("item",item); - map.put("price",price); - return map; - } - - @Override - public ShopItem clone() { - try { - ShopItem clone = (ShopItem) super.clone(); - clone.item = item; - clone.price = price; - return clone; - } catch (CloneNotSupportedException e) { - throw new AssertionError(); - } - } -} +package ru.redguy.dynamicshop.api; + +import org.bukkit.Material; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.SerializableAs; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +@SerializableAs("ShopItem") +public class ShopItem implements Cloneable, ConfigurationSerializable { + private final ItemStack item; + private final double price; + + /** + * Creates a new shop item + * + * @param item The item to sell + * @param price The price of the item + * @throws IllegalArgumentException if item is null or price is negative + */ + public ShopItem(@NotNull ItemStack item, double price) { + if (item == null || item.getType() == Material.AIR) { + throw new IllegalArgumentException("Item cannot be null or AIR"); + } + if (price < 0) { + throw new IllegalArgumentException("Price cannot be negative"); + } + this.item = item.clone(); // Defensive copy + this.price = price; + } + + /** + * Gets the item being sold + * + * @return A copy of the item + */ + @NotNull + public ItemStack getItem() { + return item.clone(); // Return defensive copy + } + + /** + * Gets the price of the item + * + * @return The price + */ + public double getPrice() { + return price; + } + + @Contract("_ -> new") + public static @NotNull ShopItem deserialize(@NotNull Map args) { + ItemStack item = args.containsKey("item") ? (ItemStack) args.get("item") : new ItemStack(Material.AIR); + double price = args.containsKey("price") ? (Double) args.get("price") : 0.0; + + return new ShopItem(item, price); + } + + @Override + public @NotNull Map serialize() { + Map map = new HashMap<>(); + map.put("item", item); + map.put("price", price); + return map; + } + + @Override + public ShopItem clone() { + try { + return new ShopItem(this.item.clone(), this.price); + } catch (Exception e) { + throw new AssertionError("Failed to clone ShopItem: " + e.getMessage()); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ShopItem)) return false; + ShopItem shopItem = (ShopItem) o; + return Double.compare(shopItem.price, price) == 0 && + Objects.equals(item, shopItem.item); + } + + @Override + public int hashCode() { + return Objects.hash(item, price); + } + + @Override + public String toString() { + return "ShopItem{" + + "item=" + item + + ", price=" + price + + '}'; + } +} diff --git a/src/main/java/ru/redguy/dynamicshop/commands/AddShopItemCommand.java b/src/main/java/ru/redguy/dynamicshop/commands/AddShopItemCommand.java index 7781411..f83a86f 100755 --- a/src/main/java/ru/redguy/dynamicshop/commands/AddShopItemCommand.java +++ b/src/main/java/ru/redguy/dynamicshop/commands/AddShopItemCommand.java @@ -1,31 +1,83 @@ -package ru.redguy.dynamicshop.commands; - -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import ru.redguy.dynamicshop.api.Database; -import ru.redguy.dynamicshop.api.ShopItem; -import ru.redguy.dynamicshop.gui.ShopGuiSell; - -public class AddShopItemCommand extends Command { - public AddShopItemCommand(@NotNull String name) { - super(name); - } - - @Override - public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) { - if(!(sender instanceof Player)) return true; - if(!sender.hasPermission("dynshop.add")) return true; - if(args.length!=1) return true; - Database.getInstance().addItem(new ShopItem(((Player) sender).getInventory().getItemInMainHand().clone(),Double.parseDouble(args[0].replace(',','.')))); - ShopGuiSell.update(); - return true; - } - - @Override - public @Nullable String getPermission() { - return "dynshop.add"; - } -} +package ru.redguy.dynamicshop.commands; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import ru.redguy.dynamicshop.api.Database; +import ru.redguy.dynamicshop.api.ShopItem; +import ru.redguy.dynamicshop.gui.ShopGuiSell; + +import java.util.Collections; +import java.util.List; + +public class AddShopItemCommand extends Command { + public AddShopItemCommand(@NotNull String name) { + super(name); + this.setDescription("Add an item to the shop"); + this.setUsage("§c/additem "); + this.setPermission("dynshop.add"); + } + + @Override + public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage(ChatColor.RED + "This command can only be used by players!"); + return true; + } + + Player player = (Player) sender; + + if (!player.hasPermission("dynshop.add")) { + player.sendMessage(ChatColor.RED + "You don't have permission to add items to the shop!"); + return true; + } + + if (args.length != 1) { + player.sendMessage(this.getUsage()); + return true; + } + + ItemStack itemInHand = player.getInventory().getItemInMainHand(); + if (itemInHand == null || itemInHand.getType().isAir()) { + player.sendMessage(ChatColor.RED + "You must hold an item to add it to the shop!"); + return true; + } + + double price; + try { + price = Double.parseDouble(args[0].replace(',', '.')); + if (price < 0) { + player.sendMessage(ChatColor.RED + "Price cannot be negative!"); + return true; + } + } catch (NumberFormatException e) { + player.sendMessage(ChatColor.RED + "Invalid price format! Use numbers only."); + return true; + } + + try { + ShopItem shopItem = new ShopItem(itemInHand.clone(), price); + Database.getInstance().addItem(shopItem); + ShopGuiSell.update(); + player.sendMessage(ChatColor.GREEN + "Item successfully added to the shop for " + price + "!"); + } catch (Exception e) { + player.sendMessage(ChatColor.RED + "Failed to add item to the shop: " + e.getMessage()); + } + + return true; + } + + @Override + public @NotNull List tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) { + return Collections.emptyList(); // No tab completion for price + } + + @Override + public @Nullable String getPermission() { + return "dynshop.add"; + } +} diff --git a/src/main/java/ru/redguy/dynamicshop/commands/ShopCommand.java b/src/main/java/ru/redguy/dynamicshop/commands/ShopCommand.java index f2ce283..3669717 100755 --- a/src/main/java/ru/redguy/dynamicshop/commands/ShopCommand.java +++ b/src/main/java/ru/redguy/dynamicshop/commands/ShopCommand.java @@ -1,40 +1,71 @@ -package ru.redguy.dynamicshop.commands; - -import com.google.common.collect.Lists; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import ru.redguy.dynamicshop.gui.ShopGuiBuy; -import ru.redguy.dynamicshop.gui.ShopGuiSell; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -public class ShopCommand extends Command { - public ShopCommand(@NotNull String name) { - super(name); - } - - @Override - public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) { - if(args.length!=1) return true; - if(sender instanceof Player) { - if(args[0].equals("sell")) { - ShopGuiSell.open((Player) sender); - } - if(args[0].equals("buy")) { - ShopGuiBuy.open((Player) sender); - } - } - return true; - } - - @Override - public @NotNull List tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException { - String[] subCommands = {"sell","buy"}; - if(args.length!=1) return Lists.newArrayList(); - return Arrays.stream(subCommands).filter(s -> s.startsWith(args[0])).collect(Collectors.toList()); - } -} +package ru.redguy.dynamicshop.commands; + +import com.google.common.collect.Lists; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import ru.redguy.dynamicshop.gui.ShopGuiBuy; +import ru.redguy.dynamicshop.gui.ShopGuiSell; +import org.bukkit.ChatColor; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class ShopCommand extends Command { + private static final String[] SUBCOMMANDS = {"sell", "buy"}; + + public ShopCommand(@NotNull String name) { + super(name); + this.setDescription("Main shop command"); + this.setUsage("§c/shop "); + this.setPermission("dynshop.use"); + } + + @Override + public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage(ChatColor.RED + "This command can only be used by players!"); + return true; + } + + Player player = (Player) sender; + + if (!player.hasPermission("dynshop.use")) { + player.sendMessage(ChatColor.RED + "You don't have permission to use this command!"); + return true; + } + + if (args.length != 1) { + player.sendMessage(this.getUsage()); + return true; + } + + String subCommand = args[0].toLowerCase(); + switch (subCommand) { + case "sell": + ShopGuiSell.open(player); + break; + case "buy": + ShopGuiBuy.open(player); + break; + default: + player.sendMessage(this.getUsage()); + break; + } + + return true; + } + + @Override + public @NotNull List tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException { + if (!(sender instanceof Player) || !sender.hasPermission("dynshop.use") || args.length != 1) { + return Lists.newArrayList(); + } + + return Arrays.stream(SUBCOMMANDS) + .filter(s -> s.toLowerCase().startsWith(args[0].toLowerCase())) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/ru/redguy/dynamicshop/commands/ShopReloadCommand.java b/src/main/java/ru/redguy/dynamicshop/commands/ShopReloadCommand.java index 9989ddc..5a72f29 100755 --- a/src/main/java/ru/redguy/dynamicshop/commands/ShopReloadCommand.java +++ b/src/main/java/ru/redguy/dynamicshop/commands/ShopReloadCommand.java @@ -16,7 +16,6 @@ public class ShopReloadCommand extends Command { @Override public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) { if(!sender.hasPermission("dynshop.reload")) return true; - Database.getInstance().load(); ShopGuiBuy.update(); ShopGuiSell.update(); return true; diff --git a/src/main/java/ru/redguy/dynamicshop/gui/ShopGuiBuy.java b/src/main/java/ru/redguy/dynamicshop/gui/ShopGuiBuy.java index ff9a345..8aaf764 100755 --- a/src/main/java/ru/redguy/dynamicshop/gui/ShopGuiBuy.java +++ b/src/main/java/ru/redguy/dynamicshop/gui/ShopGuiBuy.java @@ -1,30 +1,64 @@ -package ru.redguy.dynamicshop.gui; - -import me.filoghost.chestcommands.api.Menu; -import org.bukkit.entity.Player; -import ru.redguy.dynamicshop.DynamicShop; -import ru.redguy.dynamicshop.gui.icons.ItemIconBuy; - -public class ShopGuiBuy { - private static final Menu menu = Menu.create(DynamicShop.getInstance(),"Магазин | Покупка",6); - - static { - populateIcons(); - } - - public static void open(Player player) { - menu.open(player); - } - - public static void update() { - menu.refreshOpenViews(); - } - - private static void populateIcons() { - for (int row = 0; row < 3; row++) { - for (int id = 0; id < 9; id++) { - menu.setIcon(row,id,new ItemIconBuy(row*9+id)); - } - } - } -} +package ru.redguy.dynamicshop.gui; + +import me.filoghost.chestcommands.api.Menu; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import ru.redguy.dynamicshop.DynamicShop; +import ru.redguy.dynamicshop.gui.icons.ItemIconBuy; + +public class ShopGuiBuy { + private static final int ROWS = 6; + private static final int COLUMNS = 9; + private static final int ITEM_ROWS = 3; + private static final Menu menu; + + static { + try { + menu = Menu.create(DynamicShop.getInstance(), ChatColor.DARK_GREEN + "Shop | Buy", ROWS); + populateIcons(); + } catch (Exception e) { + DynamicShop.getInstance().getLogger().severe("Failed to initialize shop buy menu: " + e.getMessage()); + throw new ExceptionInInitializerError(e); + } + } + + private ShopGuiBuy() { + // Private constructor to prevent instantiation + } + + public static void open(@NotNull Player player) { + if (player == null) { + throw new IllegalArgumentException("Player cannot be null"); + } + + try { + menu.open(player); + } catch (Exception e) { + player.sendMessage(ChatColor.RED + "Failed to open shop menu!"); + DynamicShop.getInstance().getLogger().severe("Failed to open shop buy menu for " + player.getName() + ": " + e.getMessage()); + } + } + + public static void update() { + try { + menu.refreshOpenViews(); + } catch (Exception e) { + DynamicShop.getInstance().getLogger().severe("Failed to update shop buy menu: " + e.getMessage()); + } + } + + private static void populateIcons() { + try { + for (int row = 0; row < ITEM_ROWS; row++) { + for (int column = 0; column < COLUMNS; column++) { + int slot = row * COLUMNS + column; + menu.setIcon(row, column, new ItemIconBuy(slot)); + } + } + } catch (Exception e) { + DynamicShop.getInstance().getLogger().severe("Failed to populate shop buy menu icons: " + e.getMessage()); + throw new RuntimeException("Failed to populate shop buy menu icons", e); + } + } +} diff --git a/src/main/java/ru/redguy/dynamicshop/gui/ShopGuiSell.java b/src/main/java/ru/redguy/dynamicshop/gui/ShopGuiSell.java index a4a0d2b..250ed9f 100755 --- a/src/main/java/ru/redguy/dynamicshop/gui/ShopGuiSell.java +++ b/src/main/java/ru/redguy/dynamicshop/gui/ShopGuiSell.java @@ -1,30 +1,64 @@ -package ru.redguy.dynamicshop.gui; - -import me.filoghost.chestcommands.api.Menu; -import org.bukkit.entity.Player; -import ru.redguy.dynamicshop.DynamicShop; -import ru.redguy.dynamicshop.gui.icons.ItemIconSell; - -public class ShopGuiSell { - private static final Menu menu = Menu.create(DynamicShop.getInstance(),"Магазин | Продажа",6); - - static { - populateIcons(); - } - - public static void open(Player player) { - menu.open(player); - } - - public static void update() { - menu.refreshOpenViews(); - } - - private static void populateIcons() { - for (int row = 0; row < 3; row++) { - for (int id = 0; id < 9; id++) { - menu.setIcon(row,id,new ItemIconSell(row*9+id)); - } - } - } -} +package ru.redguy.dynamicshop.gui; + +import me.filoghost.chestcommands.api.Menu; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import ru.redguy.dynamicshop.DynamicShop; +import ru.redguy.dynamicshop.gui.icons.ItemIconSell; + +public class ShopGuiSell { + private static final int ROWS = 6; + private static final int COLUMNS = 9; + private static final int ITEM_ROWS = 3; + private static final Menu menu; + + static { + try { + menu = Menu.create(DynamicShop.getInstance(), ChatColor.DARK_GREEN + "Shop | Sell", ROWS); + populateIcons(); + } catch (Exception e) { + DynamicShop.getInstance().getLogger().severe("Failed to initialize shop sell menu: " + e.getMessage()); + throw new ExceptionInInitializerError(e); + } + } + + private ShopGuiSell() { + // Private constructor to prevent instantiation + } + + public static void open(@NotNull Player player) { + if (player == null) { + throw new IllegalArgumentException("Player cannot be null"); + } + + try { + menu.open(player); + } catch (Exception e) { + player.sendMessage(ChatColor.RED + "Failed to open shop menu!"); + DynamicShop.getInstance().getLogger().severe("Failed to open shop sell menu for " + player.getName() + ": " + e.getMessage()); + } + } + + public static void update() { + try { + menu.refreshOpenViews(); + } catch (Exception e) { + DynamicShop.getInstance().getLogger().severe("Failed to update shop sell menu: " + e.getMessage()); + } + } + + private static void populateIcons() { + try { + for (int row = 0; row < ITEM_ROWS; row++) { + for (int column = 0; column < COLUMNS; column++) { + int slot = row * COLUMNS + column; + menu.setIcon(row, column, new ItemIconSell(slot)); + } + } + } catch (Exception e) { + DynamicShop.getInstance().getLogger().severe("Failed to populate shop sell menu icons: " + e.getMessage()); + throw new RuntimeException("Failed to populate shop sell menu icons", e); + } + } +} diff --git a/src/main/java/ru/redguy/dynamicshop/gui/icons/ItemIconBuy.java b/src/main/java/ru/redguy/dynamicshop/gui/icons/ItemIconBuy.java index 1817446..098e3ec 100755 --- a/src/main/java/ru/redguy/dynamicshop/gui/icons/ItemIconBuy.java +++ b/src/main/java/ru/redguy/dynamicshop/gui/icons/ItemIconBuy.java @@ -1,64 +1,133 @@ -package ru.redguy.dynamicshop.gui.icons; - -import me.filoghost.chestcommands.api.Icon; -import me.filoghost.chestcommands.api.MenuView; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.TextColor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import ru.redguy.dynamicshop.DynamicShop; -import ru.redguy.dynamicshop.api.Database; -import ru.redguy.dynamicshop.api.ShopItem; -import ru.redguy.dynamicshop.gui.ShopGuiBuy; -import ru.redguy.dynamicshop.gui.ShopGuiSell; - -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.ArrayList; -import java.util.List; - -public class ItemIconBuy implements Icon { - private final int index; - - public ItemIconBuy(int index) { - this.index = index; - } - - @Override - public @Nullable ItemStack render(@NotNull Player player) { - ShopItem item = Database.getInstance().getItem(index); - if (item == null) return null; - ItemStack stack = item.getItem().clone(); - if (stack.getType() == Material.AIR) return null; - List lore = new ArrayList<>(); - lore.add(Component.text("Цена: ").color(TextColor.color(255, 255, 255)).append(Component.text(item.getPrice() + 1).color(TextColor.color(255, 100, 0)))); - stack.lore(lore); - return stack; - } - - @Override - public void onClick(@NotNull MenuView menuView, @NotNull Player player) { - ShopItem item = Database.getInstance().getItem(index); - if (item == null) return; - if (item.getItem().getType() == Material.AIR) return; - if (!DynamicShop.getInstance().econ.has(player, item.getPrice() + 1)) return; - - player.getInventory().addItem(item.getItem().clone()); - DynamicShop.getInstance().econ.withdrawPlayer(player, item.getPrice() + 1); - item.setPrice(round(item.getPrice() + 0.1d, 2)); - Database.getInstance().setItem(index, item); - ShopGuiSell.update(); - ShopGuiBuy.update(); - } - - public static double round(double value, int places) { - if (places < 0) throw new IllegalArgumentException(); - - BigDecimal bd = BigDecimal.valueOf(value); - bd = bd.setScale(places, RoundingMode.HALF_DOWN); - return bd.doubleValue(); - } -} +package ru.redguy.dynamicshop.gui.icons; + +import me.filoghost.chestcommands.api.Icon; +import me.filoghost.chestcommands.api.MenuView; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextColor; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import ru.redguy.dynamicshop.DynamicShop; +import ru.redguy.dynamicshop.api.Database; +import ru.redguy.dynamicshop.api.ShopItem; +import ru.redguy.dynamicshop.gui.ShopGuiBuy; +import ru.redguy.dynamicshop.gui.ShopGuiSell; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; + +public class ItemIconBuy implements Icon { + private static final double PRICE_INCREASE = 0.1; + private static final double PRICE_MARKUP = 1.0; + private static final int PRICE_DECIMALS = 2; + private static final TextColor PRICE_LABEL_COLOR = TextColor.color(255, 255, 255); + private static final TextColor PRICE_VALUE_COLOR = TextColor.color(255, 100, 0); + + private final int index; + + public ItemIconBuy(int index) { + if (index < 0) { + throw new IllegalArgumentException("Index cannot be negative"); + } + this.index = index; + } + + @Override + public @Nullable ItemStack render(@NotNull Player player) { + try { + ShopItem item = Database.getInstance().getItem(index); + if (item == null || item.getItem().getType() == Material.AIR) { + return null; + } + + ItemStack stack = item.getItem().clone(); + List lore = new ArrayList<>(); + double buyPrice = item.getPrice() + PRICE_MARKUP; + lore.add(Component.text("Price: ") + .color(PRICE_LABEL_COLOR) + .append(Component.text(String.format("%.2f", buyPrice)) + .color(PRICE_VALUE_COLOR))); + stack.lore(lore); + return stack; + } catch (Exception e) { + DynamicShop.getInstance().getLogger().log(Level.SEVERE, + "Failed to render buy icon for index " + index, e); + return null; + } + } + + @Override + public void onClick(@NotNull MenuView menuView, @NotNull Player player) { + try { + ShopItem item = Database.getInstance().getItem(index); + if (!validateTransaction(player, item)) { + return; + } + + // Process the transaction + processTransaction(player, item); + + // Update the item price and refresh menus + updateItemPrice(item); + refreshMenus(); + + } catch (Exception e) { + player.sendMessage(ChatColor.RED + "Failed to process purchase!"); + DynamicShop.getInstance().getLogger().log(Level.SEVERE, + "Failed to process buy transaction for player " + player.getName(), e); + } + } + + private boolean validateTransaction(@NotNull Player player, @Nullable ShopItem item) { + if (item == null || item.getItem().getType() == Material.AIR) { + return false; + } + + double buyPrice = item.getPrice() + PRICE_MARKUP; + if (!DynamicShop.getInstance().getEconomy().has(player, buyPrice)) { + player.sendMessage(ChatColor.RED + "You don't have enough money! Need: " + + ChatColor.GOLD + String.format("%.2f", buyPrice)); + return false; + } + + return true; + } + + private void processTransaction(@NotNull Player player, @NotNull ShopItem item) { + double buyPrice = item.getPrice() + PRICE_MARKUP; + player.getInventory().addItem(item.getItem().clone()); + DynamicShop.getInstance().getEconomy().withdrawPlayer(player, buyPrice); + player.sendMessage(ChatColor.GREEN + "Successfully bought item for " + + ChatColor.GOLD + String.format("%.2f", buyPrice)); + } + + private void updateItemPrice(@NotNull ShopItem item) { + double newPrice = round(item.getPrice() + PRICE_INCREASE, PRICE_DECIMALS); + Database.getInstance().setItem(index, new ShopItem(item.getItem(), newPrice)); + } + + private void refreshMenus() { + ShopGuiSell.update(); + ShopGuiBuy.update(); + } + + private static double round(double value, int places) { + if (places < 0) { + throw new IllegalArgumentException("Decimal places cannot be negative"); + } + + try { + BigDecimal bd = BigDecimal.valueOf(value); + bd = bd.setScale(places, RoundingMode.HALF_DOWN); + return bd.doubleValue(); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Failed to round value: " + value, e); + } + } +} diff --git a/src/main/java/ru/redguy/dynamicshop/gui/icons/ItemIconSell.java b/src/main/java/ru/redguy/dynamicshop/gui/icons/ItemIconSell.java index dd786f9..358fec1 100755 --- a/src/main/java/ru/redguy/dynamicshop/gui/icons/ItemIconSell.java +++ b/src/main/java/ru/redguy/dynamicshop/gui/icons/ItemIconSell.java @@ -1,65 +1,135 @@ -package ru.redguy.dynamicshop.gui.icons; - -import me.filoghost.chestcommands.api.Icon; -import me.filoghost.chestcommands.api.MenuView; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.TextColor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import ru.redguy.dynamicshop.DynamicShop; -import ru.redguy.dynamicshop.api.Database; -import ru.redguy.dynamicshop.api.ShopItem; -import ru.redguy.dynamicshop.gui.ShopGuiBuy; -import ru.redguy.dynamicshop.gui.ShopGuiSell; - -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.ArrayList; -import java.util.List; - -public class ItemIconSell implements Icon { - private int index; - - public ItemIconSell(int index) { - this.index = index; - } - - @Override - public @Nullable ItemStack render(@NotNull Player player) { - ShopItem item = Database.getInstance().getItem(index); - if(item == null) return null; - ItemStack stack = item.getItem().clone(); - if (stack.getType() == Material.AIR) return null; - List lore = new ArrayList<>(); - lore.add(Component.text("Цена: ").color(TextColor.color(255,255,255)).append(Component.text(item.getPrice()).color(TextColor.color(255,100,0)))); - stack.lore(lore); - return stack; - } - - @Override - public void onClick(@NotNull MenuView menuView, @NotNull Player player) { - ShopItem item = Database.getInstance().getItem(index); - if(item == null) return; - if (item.getItem().getType() == Material.AIR) return; - if(!player.getInventory().containsAtLeast(item.getItem().clone(),item.getItem().clone().getAmount())) return; - if(item.getPrice()<=1) return; - - player.getInventory().removeItem(item.getItem().clone()); - DynamicShop.getInstance().econ.depositPlayer(player,item.getPrice()); - item.setPrice(round(item.getPrice()-0.1d,2)); - Database.getInstance().setItem(index,item); - ShopGuiSell.update(); - ShopGuiBuy.update(); - } - - public static double round(double value, int places) { - if (places < 0) throw new IllegalArgumentException(); - - BigDecimal bd = BigDecimal.valueOf(value); - bd = bd.setScale(places, RoundingMode.HALF_DOWN); - return bd.doubleValue(); - } -} +package ru.redguy.dynamicshop.gui.icons; + +import me.filoghost.chestcommands.api.Icon; +import me.filoghost.chestcommands.api.MenuView; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextColor; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import ru.redguy.dynamicshop.DynamicShop; +import ru.redguy.dynamicshop.api.Database; +import ru.redguy.dynamicshop.api.ShopItem; +import ru.redguy.dynamicshop.gui.ShopGuiBuy; +import ru.redguy.dynamicshop.gui.ShopGuiSell; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; + +public class ItemIconSell implements Icon { + private static final double MIN_PRICE = 1.0; + private static final double PRICE_DECREASE = 0.1; + private static final int PRICE_DECIMALS = 2; + private static final TextColor PRICE_LABEL_COLOR = TextColor.color(255, 255, 255); + private static final TextColor PRICE_VALUE_COLOR = TextColor.color(255, 100, 0); + + private final int index; + + public ItemIconSell(int index) { + if (index < 0) { + throw new IllegalArgumentException("Index cannot be negative"); + } + this.index = index; + } + + @Override + public @Nullable ItemStack render(@NotNull Player player) { + try { + ShopItem item = Database.getInstance().getItem(index); + if (item == null || item.getItem().getType() == Material.AIR) { + return null; + } + + ItemStack stack = item.getItem().clone(); + List lore = new ArrayList<>(); + lore.add(Component.text("Price: ") + .color(PRICE_LABEL_COLOR) + .append(Component.text(String.format("%.2f", item.getPrice())) + .color(PRICE_VALUE_COLOR))); + stack.lore(lore); + return stack; + } catch (Exception e) { + DynamicShop.getInstance().getLogger().log(Level.SEVERE, + "Failed to render sell icon for index " + index, e); + return null; + } + } + + @Override + public void onClick(@NotNull MenuView menuView, @NotNull Player player) { + try { + ShopItem item = Database.getInstance().getItem(index); + if (!validateTransaction(player, item)) { + return; + } + + // Process the transaction + processTransaction(player, item); + + // Update the item price and refresh menus + updateItemPrice(item); + refreshMenus(); + + } catch (Exception e) { + player.sendMessage(ChatColor.RED + "Failed to process sale!"); + DynamicShop.getInstance().getLogger().log(Level.SEVERE, + "Failed to process sell transaction for player " + player.getName(), e); + } + } + + private boolean validateTransaction(@NotNull Player player, @Nullable ShopItem item) { + if (item == null || item.getItem().getType() == Material.AIR) { + return false; + } + + if (item.getPrice() <= MIN_PRICE) { + player.sendMessage(ChatColor.RED + "Item price is too low!"); + return false; + } + + ItemStack requiredItem = item.getItem().clone(); + if (!player.getInventory().containsAtLeast(requiredItem, requiredItem.getAmount())) { + player.sendMessage(ChatColor.RED + "You don't have enough items to sell!"); + return false; + } + + return true; + } + + private void processTransaction(@NotNull Player player, @NotNull ShopItem item) { + player.getInventory().removeItem(item.getItem().clone()); + DynamicShop.getInstance().getEconomy().depositPlayer(player, item.getPrice()); + player.sendMessage(ChatColor.GREEN + "Successfully sold item for " + + ChatColor.GOLD + String.format("%.2f", item.getPrice())); + } + + private void updateItemPrice(@NotNull ShopItem item) { + double newPrice = round(item.getPrice() - PRICE_DECREASE, PRICE_DECIMALS); + Database.getInstance().setItem(index, new ShopItem(item.getItem(), newPrice)); + } + + private void refreshMenus() { + ShopGuiSell.update(); + ShopGuiBuy.update(); + } + + private static double round(double value, int places) { + if (places < 0) { + throw new IllegalArgumentException("Decimal places cannot be negative"); + } + + try { + BigDecimal bd = BigDecimal.valueOf(value); + bd = bd.setScale(places, RoundingMode.HALF_DOWN); + return bd.doubleValue(); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Failed to round value: " + value, e); + } + } +}