Update project

This commit is contained in:
Ilya 2024-12-05 00:41:41 +03:00
parent 9b9eb2b39b
commit d723697be8
11 changed files with 897 additions and 499 deletions

View File

@ -1,5 +1,7 @@
plugins { plugins {
id 'java' id 'java'
id 'com.github.johnrengelman.shadow' version '7.1.2'
id 'xyz.jpenilla.run-paper' version '2.2.0'
} }
group = 'ru.redguy' group = 'ru.redguy'
@ -23,9 +25,8 @@ repositories {
dependencies { dependencies {
compileOnly 'com.destroystokyo.paper:paper-api:1.16.5-R0.1-SNAPSHOT' compileOnly 'com.destroystokyo.paper:paper-api:1.16.5-R0.1-SNAPSHOT'
compileOnly 'me.filoghost.chestcommands:chestcommands-api:4.0.3' compileOnly 'me.filoghost.chestcommands:chestcommands-api:4.0.4'
compileOnly 'net.milkbowl.vault:VaultAPI:1.7' compileOnly 'net.milkbowl.vault:VaultAPI:1.7'
implementation 'org.jetbrains:annotations:23.0.0' implementation 'org.jetbrains:annotations:23.0.0'
} }
@ -47,6 +48,23 @@ tasks.withType(JavaCompile).configureEach {
compileJava.options.encoding = 'UTF-8' 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 { processResources {
def props = [version: version] def props = [version: version]
inputs.properties props inputs.properties props
@ -58,7 +76,4 @@ processResources {
jar { jar {
configurations.implementation.canBeResolved = true; configurations.implementation.canBeResolved = true;
from {
configurations.implementation.collect { it.isDirectory() ? it : zipTree(it) }
}
} }

View File

@ -7,6 +7,7 @@ import org.bukkit.plugin.java.JavaPlugin;
import ru.redguy.dynamicshop.api.ShopItem; import ru.redguy.dynamicshop.api.ShopItem;
import ru.redguy.dynamicshop.commands.AddShopItemCommand; import ru.redguy.dynamicshop.commands.AddShopItemCommand;
import ru.redguy.dynamicshop.commands.ShopCommand; import ru.redguy.dynamicshop.commands.ShopCommand;
import org.jetbrains.annotations.NotNull;
public final class DynamicShop extends JavaPlugin { public final class DynamicShop extends JavaPlugin {
@ -15,29 +16,43 @@ public final class DynamicShop extends JavaPlugin {
} }
private static DynamicShop instance; private static DynamicShop instance;
private Economy economy;
@NotNull
public static DynamicShop getInstance() { public static DynamicShop getInstance() {
if (instance == null) {
throw new IllegalStateException("Plugin instance not initialized!");
}
return instance; return instance;
} }
public Economy econ; public Economy getEconomy() {
return economy;
}
@Override @Override
public void onEnable() { public void onEnable() {
instance = this; instance = this;
getConfig().set("init",true);
saveConfig(); // Save default config if it doesn't exist
if(!setupEconomy()) { saveDefaultConfig();
getLogger().warning("Cannot find economy!");
// 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")); // Register commands
getServer().getCommandMap().register("reloadshop","shop",new AddShopItemCommand("reloadshop")); registerCommands();
getLogger().info("DynamicShop has been enabled!");
} }
@Override @Override
public void onDisable() { public void onDisable() {
getLogger().info("DynamicShop has been disabled!");
} }
private boolean setupEconomy() { private boolean setupEconomy() {
@ -48,7 +63,18 @@ public final class DynamicShop extends JavaPlugin {
if (rsp == null) { if (rsp == null) {
return false; return false;
} }
econ = rsp.getProvider(); economy = rsp.getProvider();
return true; 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());
}
} }
} }

View File

@ -2,90 +2,129 @@ package ru.redguy.dynamicshop.api;
import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import ru.redguy.dynamicshop.DynamicShop; import ru.redguy.dynamicshop.DynamicShop;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.logging.Level;
public class Database { public class Database {
private static final Database instance = new 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() { public static Database getInstance() {
return instance; return instance;
} }
private final File file = new File(DynamicShop.getInstance().getDataFolder(), "items.yml"); private synchronized void loadDatabase() {
private final YamlConfiguration conf = new YamlConfiguration();
public Database() {
load();
save();
}
public void load() {
try {
conf.load(file);
} catch (FileNotFoundException e) {
try { try {
if (!file.exists()) {
DynamicShop.getInstance().getDataFolder().mkdirs();
file.createNewFile(); file.createNewFile();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
try {
conf.load(file);
} catch (IOException | InvalidConfigurationException ex) {
throw new RuntimeException(ex);
} }
config.load(file);
} catch (IOException | InvalidConfigurationException e) { } catch (IOException | InvalidConfigurationException e) {
throw new RuntimeException(e); DynamicShop.getInstance().getLogger().log(Level.SEVERE, "Failed to load database", e);
throw new RuntimeException("Failed to load database", e);
} }
} }
private void save() { private synchronized void saveDatabase() {
try { try {
conf.save(file); config.save(file);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); DynamicShop.getInstance().getLogger().log(Level.SEVERE, "Failed to save database", e);
throw new RuntimeException("Failed to save database", e);
} }
} }
public List<ShopItem> getItems() { @NotNull
List<?> list = conf.getList("items"); public synchronized List<ShopItem> getItems() {
if(list == null) list = new ArrayList<>(); List<?> list = config.getList(ITEMS_KEY);
if (list == null) {
return new ArrayList<>();
}
List<ShopItem> shopItems = new ArrayList<>(); List<ShopItem> shopItems = new ArrayList<>();
for (Object o : list) { for (Object obj : list) {
if(o instanceof ShopItem) { if (obj instanceof ShopItem) {
shopItems.add((ShopItem) o); shopItems.add((ShopItem) obj);
} else {
DynamicShop.getInstance().getLogger().warning("Found invalid item in database: " + obj);
} }
} }
return shopItems; return Collections.unmodifiableList(shopItems);
} }
public void setItems(List<ShopItem> items) { public synchronized void setItems(@NotNull List<ShopItem> items) {
conf.set("items",items); if (items == null) {
save(); throw new IllegalArgumentException("Items list cannot be null");
}
config.set(ITEMS_KEY, new ArrayList<>(items)); // Create a copy to prevent external modification
saveDatabase();
} }
@Nullable @Nullable
public ShopItem getItem(int index) { public synchronized ShopItem getItem(int index) {
List<ShopItem> items = getItems(); List<ShopItem> items = getItems();
if(items.size()<=index) return null; if (index < 0 || index >= items.size()) {
return items.get(index); return null;
}
return items.get(index).clone(); // Return a clone to prevent modification
} }
public void setItem(int index, ShopItem item) { public synchronized void setItem(int index, @NotNull ShopItem item) {
List<ShopItem> items = getItems(); if (item == null) {
items.set(index,item); throw new IllegalArgumentException("Item cannot be null");
}
List<ShopItem> 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); setItems(items);
} }
public void addItem(ShopItem item) { public synchronized void addItem(@NotNull ShopItem item) {
List<ShopItem> items = getItems(); if (item == null) {
throw new IllegalArgumentException("Item cannot be null");
}
List<ShopItem> items = new ArrayList<>(getItems());
items.add(item); items.add(item);
setItems(items); setItems(items);
} }
public synchronized void removeItem(int index) {
List<ShopItem> 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();
}
} }

View File

@ -9,65 +9,94 @@ import org.jetbrains.annotations.NotNull;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects;
@SerializableAs("ShopItem") @SerializableAs("ShopItem")
public class ShopItem implements Cloneable, ConfigurationSerializable { public class ShopItem implements Cloneable, ConfigurationSerializable {
private ItemStack item; private final ItemStack item;
private double price; private final double price;
public ShopItem(ItemStack item, double price) { /**
this.item = item; * 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; this.price = price;
} }
/**
* Gets the item being sold
*
* @return A copy of the item
*/
@NotNull
public ItemStack getItem() { public ItemStack getItem() {
return item; return item.clone(); // Return defensive copy
}
public void setItem(ItemStack item) {
this.item = item;
} }
/**
* Gets the price of the item
*
* @return The price
*/
public double getPrice() { public double getPrice() {
return price; return price;
} }
public void setPrice(double price) {
this.price = price;
}
@Contract("_ -> new") @Contract("_ -> new")
public static @NotNull ShopItem deserialize(@NotNull Map<String, Object> args) { public static @NotNull ShopItem deserialize(@NotNull Map<String, Object> args) {
ItemStack item = new ItemStack(Material.AIR); ItemStack item = args.containsKey("item") ? (ItemStack) args.get("item") : new ItemStack(Material.AIR);
double price = 0; double price = args.containsKey("price") ? (Double) args.get("price") : 0.0;
if(args.containsKey("item")) {
item = (ItemStack) args.get("item");
}
if(args.containsKey("price")) { return new ShopItem(item, price);
price = (Double) args.get("price");
}
return new ShopItem(item,price);
} }
@Override @Override
public @NotNull Map<String, Object> serialize() { public @NotNull Map<String, Object> serialize() {
HashMap<String, Object> map = new HashMap<>(); Map<String, Object> map = new HashMap<>();
map.put("item",item); map.put("item", item);
map.put("price",price); map.put("price", price);
return map; return map;
} }
@Override @Override
public ShopItem clone() { public ShopItem clone() {
try { try {
ShopItem clone = (ShopItem) super.clone(); return new ShopItem(this.item.clone(), this.price);
clone.item = item; } catch (Exception e) {
clone.price = price; throw new AssertionError("Failed to clone ShopItem: " + e.getMessage());
return clone;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
} }
} }
@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 +
'}';
}
} }

View File

@ -1,29 +1,81 @@
package ru.redguy.dynamicshop.commands; package ru.redguy.dynamicshop.commands;
import org.bukkit.ChatColor;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import ru.redguy.dynamicshop.api.Database; import ru.redguy.dynamicshop.api.Database;
import ru.redguy.dynamicshop.api.ShopItem; import ru.redguy.dynamicshop.api.ShopItem;
import ru.redguy.dynamicshop.gui.ShopGuiSell; import ru.redguy.dynamicshop.gui.ShopGuiSell;
import java.util.Collections;
import java.util.List;
public class AddShopItemCommand extends Command { public class AddShopItemCommand extends Command {
public AddShopItemCommand(@NotNull String name) { public AddShopItemCommand(@NotNull String name) {
super(name); super(name);
this.setDescription("Add an item to the shop");
this.setUsage("§c/additem <price>");
this.setPermission("dynshop.add");
} }
@Override @Override
public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) { public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
if(!(sender instanceof Player)) return true; if (!(sender instanceof Player)) {
if(!sender.hasPermission("dynshop.add")) return true; sender.sendMessage(ChatColor.RED + "This command can only be used by players!");
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; 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<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) {
return Collections.emptyList(); // No tab completion for price
}
@Override @Override
public @Nullable String getPermission() { public @Nullable String getPermission() {
return "dynshop.add"; return "dynshop.add";

View File

@ -7,34 +7,65 @@ import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import ru.redguy.dynamicshop.gui.ShopGuiBuy; import ru.redguy.dynamicshop.gui.ShopGuiBuy;
import ru.redguy.dynamicshop.gui.ShopGuiSell; import ru.redguy.dynamicshop.gui.ShopGuiSell;
import org.bukkit.ChatColor;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class ShopCommand extends Command { public class ShopCommand extends Command {
private static final String[] SUBCOMMANDS = {"sell", "buy"};
public ShopCommand(@NotNull String name) { public ShopCommand(@NotNull String name) {
super(name); super(name);
this.setDescription("Main shop command");
this.setUsage("§c/shop <sell|buy>");
this.setPermission("dynshop.use");
} }
@Override @Override
public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) { public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
if(args.length!=1) return true; if (!(sender instanceof Player)) {
if(sender instanceof Player) { sender.sendMessage(ChatColor.RED + "This command can only be used by players!");
if(args[0].equals("sell")) { return true;
ShopGuiSell.open((Player) sender);
} }
if(args[0].equals("buy")) {
ShopGuiBuy.open((Player) sender); 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; return true;
} }
@Override @Override
public @NotNull List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException { public @NotNull List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException {
String[] subCommands = {"sell","buy"}; if (!(sender instanceof Player) || !sender.hasPermission("dynshop.use") || args.length != 1) {
if(args.length!=1) return Lists.newArrayList(); return Lists.newArrayList();
return Arrays.stream(subCommands).filter(s -> s.startsWith(args[0])).collect(Collectors.toList()); }
return Arrays.stream(SUBCOMMANDS)
.filter(s -> s.toLowerCase().startsWith(args[0].toLowerCase()))
.collect(Collectors.toList());
} }
} }

View File

@ -16,7 +16,6 @@ public class ShopReloadCommand extends Command {
@Override @Override
public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) { public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
if(!sender.hasPermission("dynshop.reload")) return true; if(!sender.hasPermission("dynshop.reload")) return true;
Database.getInstance().load();
ShopGuiBuy.update(); ShopGuiBuy.update();
ShopGuiSell.update(); ShopGuiSell.update();
return true; return true;

View File

@ -1,30 +1,64 @@
package ru.redguy.dynamicshop.gui; package ru.redguy.dynamicshop.gui;
import me.filoghost.chestcommands.api.Menu; import me.filoghost.chestcommands.api.Menu;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import ru.redguy.dynamicshop.DynamicShop; import ru.redguy.dynamicshop.DynamicShop;
import ru.redguy.dynamicshop.gui.icons.ItemIconBuy; import ru.redguy.dynamicshop.gui.icons.ItemIconBuy;
public class ShopGuiBuy { public class ShopGuiBuy {
private static final Menu menu = Menu.create(DynamicShop.getInstance(),"Магазин | Покупка",6); 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 { static {
try {
menu = Menu.create(DynamicShop.getInstance(), ChatColor.DARK_GREEN + "Shop | Buy", ROWS);
populateIcons(); populateIcons();
} catch (Exception e) {
DynamicShop.getInstance().getLogger().severe("Failed to initialize shop buy menu: " + e.getMessage());
throw new ExceptionInInitializerError(e);
}
} }
public static void open(Player player) { 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); 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() { public static void update() {
try {
menu.refreshOpenViews(); menu.refreshOpenViews();
} catch (Exception e) {
DynamicShop.getInstance().getLogger().severe("Failed to update shop buy menu: " + e.getMessage());
}
} }
private static void populateIcons() { private static void populateIcons() {
for (int row = 0; row < 3; row++) { try {
for (int id = 0; id < 9; id++) { for (int row = 0; row < ITEM_ROWS; row++) {
menu.setIcon(row,id,new ItemIconBuy(row*9+id)); 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);
}
} }
} }

View File

@ -1,30 +1,64 @@
package ru.redguy.dynamicshop.gui; package ru.redguy.dynamicshop.gui;
import me.filoghost.chestcommands.api.Menu; import me.filoghost.chestcommands.api.Menu;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import ru.redguy.dynamicshop.DynamicShop; import ru.redguy.dynamicshop.DynamicShop;
import ru.redguy.dynamicshop.gui.icons.ItemIconSell; import ru.redguy.dynamicshop.gui.icons.ItemIconSell;
public class ShopGuiSell { public class ShopGuiSell {
private static final Menu menu = Menu.create(DynamicShop.getInstance(),"Магазин | Продажа",6); 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 { static {
try {
menu = Menu.create(DynamicShop.getInstance(), ChatColor.DARK_GREEN + "Shop | Sell", ROWS);
populateIcons(); populateIcons();
} catch (Exception e) {
DynamicShop.getInstance().getLogger().severe("Failed to initialize shop sell menu: " + e.getMessage());
throw new ExceptionInInitializerError(e);
}
} }
public static void open(Player player) { 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); 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() { public static void update() {
try {
menu.refreshOpenViews(); menu.refreshOpenViews();
} catch (Exception e) {
DynamicShop.getInstance().getLogger().severe("Failed to update shop sell menu: " + e.getMessage());
}
} }
private static void populateIcons() { private static void populateIcons() {
for (int row = 0; row < 3; row++) { try {
for (int id = 0; id < 9; id++) { for (int row = 0; row < ITEM_ROWS; row++) {
menu.setIcon(row,id,new ItemIconSell(row*9+id)); 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);
}
} }
} }

View File

@ -4,6 +4,7 @@ import me.filoghost.chestcommands.api.Icon;
import me.filoghost.chestcommands.api.MenuView; import me.filoghost.chestcommands.api.MenuView;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextColor;
import org.bukkit.ChatColor;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@ -19,46 +20,114 @@ import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Level;
public class ItemIconBuy implements Icon { 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; private final int index;
public ItemIconBuy(int index) { public ItemIconBuy(int index) {
if (index < 0) {
throw new IllegalArgumentException("Index cannot be negative");
}
this.index = index; this.index = index;
} }
@Override @Override
public @Nullable ItemStack render(@NotNull Player player) { public @Nullable ItemStack render(@NotNull Player player) {
try {
ShopItem item = Database.getInstance().getItem(index); ShopItem item = Database.getInstance().getItem(index);
if (item == null) return null; if (item == null || item.getItem().getType() == Material.AIR) {
return null;
}
ItemStack stack = item.getItem().clone(); ItemStack stack = item.getItem().clone();
if (stack.getType() == Material.AIR) return null;
List<Component> lore = new ArrayList<>(); List<Component> 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)))); 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); stack.lore(lore);
return stack; return stack;
} catch (Exception e) {
DynamicShop.getInstance().getLogger().log(Level.SEVERE,
"Failed to render buy icon for index " + index, e);
return null;
}
} }
@Override @Override
public void onClick(@NotNull MenuView menuView, @NotNull Player player) { public void onClick(@NotNull MenuView menuView, @NotNull Player player) {
try {
ShopItem item = Database.getInstance().getItem(index); ShopItem item = Database.getInstance().getItem(index);
if (item == null) return; if (!validateTransaction(player, item)) {
if (item.getItem().getType() == Material.AIR) return; return;
if (!DynamicShop.getInstance().econ.has(player, item.getPrice() + 1)) 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()); player.getInventory().addItem(item.getItem().clone());
DynamicShop.getInstance().econ.withdrawPlayer(player, item.getPrice() + 1); DynamicShop.getInstance().getEconomy().withdrawPlayer(player, buyPrice);
item.setPrice(round(item.getPrice() + 0.1d, 2)); player.sendMessage(ChatColor.GREEN + "Successfully bought item for " +
Database.getInstance().setItem(index, item); 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(); ShopGuiSell.update();
ShopGuiBuy.update(); ShopGuiBuy.update();
} }
public static double round(double value, int places) { private static double round(double value, int places) {
if (places < 0) throw new IllegalArgumentException(); if (places < 0) {
throw new IllegalArgumentException("Decimal places cannot be negative");
}
try {
BigDecimal bd = BigDecimal.valueOf(value); BigDecimal bd = BigDecimal.valueOf(value);
bd = bd.setScale(places, RoundingMode.HALF_DOWN); bd = bd.setScale(places, RoundingMode.HALF_DOWN);
return bd.doubleValue(); return bd.doubleValue();
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Failed to round value: " + value, e);
}
} }
} }

View File

@ -4,6 +4,7 @@ import me.filoghost.chestcommands.api.Icon;
import me.filoghost.chestcommands.api.MenuView; import me.filoghost.chestcommands.api.MenuView;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextColor;
import org.bukkit.ChatColor;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@ -19,47 +20,116 @@ import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Level;
public class ItemIconSell implements Icon { public class ItemIconSell implements Icon {
private int index; 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) { public ItemIconSell(int index) {
if (index < 0) {
throw new IllegalArgumentException("Index cannot be negative");
}
this.index = index; this.index = index;
} }
@Override @Override
public @Nullable ItemStack render(@NotNull Player player) { public @Nullable ItemStack render(@NotNull Player player) {
try {
ShopItem item = Database.getInstance().getItem(index); ShopItem item = Database.getInstance().getItem(index);
if(item == null) return null; if (item == null || item.getItem().getType() == Material.AIR) {
return null;
}
ItemStack stack = item.getItem().clone(); ItemStack stack = item.getItem().clone();
if (stack.getType() == Material.AIR) return null;
List<Component> lore = new ArrayList<>(); List<Component> lore = new ArrayList<>();
lore.add(Component.text("Цена: ").color(TextColor.color(255,255,255)).append(Component.text(item.getPrice()).color(TextColor.color(255,100,0)))); lore.add(Component.text("Price: ")
.color(PRICE_LABEL_COLOR)
.append(Component.text(String.format("%.2f", item.getPrice()))
.color(PRICE_VALUE_COLOR)));
stack.lore(lore); stack.lore(lore);
return stack; return stack;
} catch (Exception e) {
DynamicShop.getInstance().getLogger().log(Level.SEVERE,
"Failed to render sell icon for index " + index, e);
return null;
}
} }
@Override @Override
public void onClick(@NotNull MenuView menuView, @NotNull Player player) { public void onClick(@NotNull MenuView menuView, @NotNull Player player) {
try {
ShopItem item = Database.getInstance().getItem(index); ShopItem item = Database.getInstance().getItem(index);
if(item == null) return; if (!validateTransaction(player, item)) {
if (item.getItem().getType() == Material.AIR) return; return;
if(!player.getInventory().containsAtLeast(item.getItem().clone(),item.getItem().clone().getAmount())) return; }
if(item.getPrice()<=1) 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()); player.getInventory().removeItem(item.getItem().clone());
DynamicShop.getInstance().econ.depositPlayer(player,item.getPrice()); DynamicShop.getInstance().getEconomy().depositPlayer(player, item.getPrice());
item.setPrice(round(item.getPrice()-0.1d,2)); player.sendMessage(ChatColor.GREEN + "Successfully sold item for " +
Database.getInstance().setItem(index,item); 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(); ShopGuiSell.update();
ShopGuiBuy.update(); ShopGuiBuy.update();
} }
public static double round(double value, int places) { private static double round(double value, int places) {
if (places < 0) throw new IllegalArgumentException(); if (places < 0) {
throw new IllegalArgumentException("Decimal places cannot be negative");
}
try {
BigDecimal bd = BigDecimal.valueOf(value); BigDecimal bd = BigDecimal.valueOf(value);
bd = bd.setScale(places, RoundingMode.HALF_DOWN); bd = bd.setScale(places, RoundingMode.HALF_DOWN);
return bd.doubleValue(); return bd.doubleValue();
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Failed to round value: " + value, e);
}
} }
} }