Database improvements

- Support table prefixes (Fixes #138)
- Support amounts above 127 in Paper (Fixes #149)
- Split UUID, name and shop type in economy log
- Split product and amount in economy log (Fixes #143)
- Added product data (Base64) to economy log
This commit is contained in:
Eric 2018-11-10 20:34:11 +01:00
parent 4c6c87dc08
commit 6ae12f65c0
9 changed files with 342 additions and 215 deletions

View File

@ -13,6 +13,7 @@ import de.epiceric.shopchest.language.LanguageUtils;
import de.epiceric.shopchest.language.Message;
import de.epiceric.shopchest.language.Replacement;
import de.epiceric.shopchest.shop.Shop;
import de.epiceric.shopchest.shop.ShopProduct;
import de.epiceric.shopchest.utils.Callback;
import de.epiceric.shopchest.utils.ClickType;
import de.epiceric.shopchest.utils.ItemUtils;
@ -354,11 +355,8 @@ class ShopCommandExecutor implements CommandExecutor {
}
}
ItemStack product = new ItemStack(inHand.getType(), amount, inHand.getDurability());
product.setItemMeta(inHand.getItemMeta());
if (Enchantment.DURABILITY.canEnchantItem(product)) {
if (product.getDurability() > 0 && !Config.allowBrokenItems) {
if (Enchantment.DURABILITY.canEnchantItem(inHand)) {
if (inHand.getDurability() > 0 && !Config.allowBrokenItems) {
p.sendMessage(LanguageUtils.getMessage(Message.CANNOT_SELL_BROKEN_ITEM));
plugin.debug(p.getName() + "'s item is broken");
return;
@ -374,6 +372,7 @@ class ShopCommandExecutor implements CommandExecutor {
}
}
ShopProduct product = new ShopProduct(inHand, amount);
ShopPreCreateEvent event = new ShopPreCreateEvent(p, new Shop(plugin, p, product, null, buyPrice, sellPrice, shopType));
Bukkit.getPluginManager().callEvent(event);

View File

@ -87,7 +87,12 @@ public class Config {
public static String databaseMySqlPassword;
/**
* The database type used for ShopChest.
* The prefix to be used for database tables
*/
public static String databaseTablePrefix;
/**
* The database type used for ShopChest
**/
public static Database.DatabaseType databaseType;
@ -474,6 +479,7 @@ public class Config {
databaseMySqlDatabase = plugin.getConfig().getString("database.mysql.database");
databaseMySqlUsername = plugin.getConfig().getString("database.mysql.username");
databaseMySqlPassword = plugin.getConfig().getString("database.mysql.password");
databaseTablePrefix = plugin.getConfig().getString("database.table-prefix");
databaseType = Database.DatabaseType.valueOf(plugin.getConfig().getString("database.type"));
minimumPrices = (plugin.getConfig().getConfigurationSection("minimum-prices") == null) ? new HashSet<String>() : plugin.getConfig().getConfigurationSection("minimum-prices").getKeys(true);
maximumPrices = (plugin.getConfig().getConfigurationSection("maximum-prices") == null) ? new HashSet<String>() : plugin.getConfig().getConfigurationSection("maximum-prices").getKeys(true);

View File

@ -57,8 +57,7 @@ public class NotifyPlayerOnJoinListener implements Listener {
@EventHandler
public void onPlayerQuit(PlayerQuitEvent e) {
long time = System.currentTimeMillis();
plugin.getShopDatabase().logLogout(e.getPlayer(), time, null);
plugin.getShopDatabase().logLogout(e.getPlayer(), null);
}
}

View File

@ -24,6 +24,7 @@ import de.epiceric.shopchest.language.Replacement;
import de.epiceric.shopchest.nms.Hologram;
import de.epiceric.shopchest.nms.JsonBuilder;
import de.epiceric.shopchest.shop.Shop;
import de.epiceric.shopchest.shop.ShopProduct;
import de.epiceric.shopchest.shop.Shop.ShopType;
import de.epiceric.shopchest.sql.Database;
import de.epiceric.shopchest.utils.ClickType;
@ -301,7 +302,7 @@ public class ShopInteractListener implements Listener {
if (b.getRelative(BlockFace.UP).getType() == Material.AIR) {
ClickType clickType = ClickType.getPlayerClickType(p);
ItemStack product = clickType.getProduct();
ShopProduct product = clickType.getProduct();
double buyPrice = clickType.getBuyPrice();
double sellPrice = clickType.getSellPrice();
ShopType shopType = clickType.getShopType();
@ -484,9 +485,10 @@ public class ShopInteractListener implements Listener {
} else {
if (externalPluginsAllowed || p.hasPermission(Permissions.BYPASS_EXTERNAL_PLUGIN)) {
Chest c = (Chest) b.getState();
int amount = (p.isSneaking() ? shop.getProduct().getMaxStackSize() : shop.getProduct().getAmount());
ItemStack itemStack = shop.getProduct().getItemStack();
int amount = (p.isSneaking() ? itemStack.getMaxStackSize() : shop.getProduct().getAmount());
if (Utils.getAmount(c.getInventory(), shop.getProduct()) >= amount) {
if (Utils.getAmount(c.getInventory(), itemStack) >= amount) {
if (confirmed || !Config.confirmShopping) {
buy(p, shop, p.isSneaking());
if (Config.confirmShopping) {
@ -503,7 +505,7 @@ public class ShopInteractListener implements Listener {
needsConfirmation.put(p.getUniqueId(), ids);
}
} else {
if (Config.autoCalculateItemAmount && Utils.getAmount(c.getInventory(), shop.getProduct()) > 0) {
if (Config.autoCalculateItemAmount && Utils.getAmount(c.getInventory(), itemStack) > 0) {
if (confirmed || !Config.confirmShopping) {
buy(p, shop, p.isSneaking());
if (Config.confirmShopping) {
@ -524,7 +526,7 @@ public class ShopInteractListener implements Listener {
if (shop.getVendor().isOnline() && Config.enableVendorMessages) {
shop.getVendor().getPlayer().sendMessage(LanguageUtils.getMessage(Message.VENDOR_OUT_OF_STOCK,
new Replacement(Placeholder.AMOUNT, String.valueOf(shop.getProduct().getAmount())),
new Replacement(Placeholder.ITEM_NAME, LanguageUtils.getItemName(shop.getProduct()))));
new Replacement(Placeholder.ITEM_NAME, LanguageUtils.getItemName(itemStack))));
}
plugin.debug("Shop is out of stock");
}
@ -569,11 +571,13 @@ public class ShopInteractListener implements Listener {
externalPluginsAllowed = WorldGuardWrapper.getInstance().queryStateFlag(p, b.getLocation(), flagName).orElse(false);
}
ItemStack itemStack = shop.getProduct().getItemStack();
if (externalPluginsAllowed || p.hasPermission(Permissions.BYPASS_EXTERNAL_PLUGIN)) {
boolean stack = p.isSneaking() && !Utils.hasAxeInHand(p);
int amount = stack ? shop.getProduct().getMaxStackSize() : shop.getProduct().getAmount();
int amount = stack ? itemStack.getMaxStackSize() : shop.getProduct().getAmount();
if (Utils.getAmount(p.getInventory(), shop.getProduct()) >= amount) {
if (Utils.getAmount(p.getInventory(), itemStack) >= amount) {
if (confirmed || !Config.confirmShopping) {
sell(p, shop, stack);
if (Config.confirmShopping) {
@ -590,7 +594,7 @@ public class ShopInteractListener implements Listener {
needsConfirmation.put(p.getUniqueId(), ids);
}
} else {
if (Config.autoCalculateItemAmount && Utils.getAmount(p.getInventory(), shop.getProduct()) > 0) {
if (Config.autoCalculateItemAmount && Utils.getAmount(p.getInventory(), itemStack) > 0) {
if (confirmed || !Config.confirmShopping) {
sell(p, shop, stack);
if (Config.confirmShopping) {
@ -716,7 +720,7 @@ public class ShopInteractListener implements Listener {
* @param sellPrice Sell price
* @param shopType Type of the shop
*/
private void create(final Player executor, final Location location, final ItemStack product, final double buyPrice, final double sellPrice, final ShopType shopType) {
private void create(final Player executor, final Location location, final ShopProduct product, final double buyPrice, final double sellPrice, final ShopType shopType) {
plugin.debug(executor.getName() + " is creating new shop...");
if (!executor.hasPermission(Permissions.CREATE)) {
@ -825,7 +829,8 @@ public class ShopInteractListener implements Listener {
}
Chest c = (Chest) shop.getLocation().getBlock().getState();
int amount = Utils.getAmount(c.getInventory(), shop.getProduct());
ItemStack itemStack = shop.getProduct().getItemStack();
int amount = Utils.getAmount(c.getInventory(), itemStack);
String vendorName = (shop.getVendor().getName() == null ?
shop.getVendor().getUniqueId().toString() : shop.getVendor().getName());
@ -863,21 +868,21 @@ public class ShopInteractListener implements Listener {
* @param product The product of the shop
* @return A {@link JsonBuilder} that can send the message via {@link JsonBuilder#sendJson(Player)}
*/
private JsonBuilder getProductJson(ItemStack product) {
private JsonBuilder getProductJson(ShopProduct product) {
// Add spaces at start and end, so there will always be a part before and after
// the item name after splitting at Placeholder.ITEM_NAME
String productString = " " + LanguageUtils.getMessage(Message.SHOP_INFO_PRODUCT,
new Replacement(Placeholder.AMOUNT, String.valueOf(product.getAmount()))) + " ";
String[] parts = productString.split(Placeholder.ITEM_NAME.toString());
String productName = LanguageUtils.getItemName(product);
String productName = LanguageUtils.getItemName(product.getItemStack());
String jsonItem = "";
JsonBuilder jb = new JsonBuilder(plugin);
JsonBuilder.PartArray rootArray = new JsonBuilder.PartArray();
try {
Class<?> craftItemStackClass = Utils.getCraftClass("inventory.CraftItemStack");
Object nmsStack = craftItemStackClass.getMethod("asNMSCopy", ItemStack.class).invoke(null, product);
Object nmsStack = craftItemStackClass.getMethod("asNMSCopy", ItemStack.class).invoke(null, product.getItemStack());
Class<?> nbtTagCompoundClass = Utils.getNMSClass("NBTTagCompound");
Object nbtTagCompound = nbtTagCompoundClass.getConstructor().newInstance();
nmsStack.getClass().getMethod("save", nbtTagCompoundClass).invoke(nmsStack, nbtTagCompound);
@ -944,8 +949,9 @@ public class ShopInteractListener implements Listener {
private void buy(Player executor, final Shop shop, boolean stack) {
plugin.debug(executor.getName() + " is buying (#" + shop.getID() + ")");
ItemStack itemStack = shop.getProduct().getItemStack();
int amount = shop.getProduct().getAmount();
if (stack) amount = shop.getProduct().getMaxStackSize();
if (stack) amount = itemStack.getMaxStackSize();
String worldName = shop.getLocation().getWorld().getName();
@ -966,14 +972,14 @@ public class ShopInteractListener implements Listener {
Block b = shop.getLocation().getBlock();
Chest c = (Chest) b.getState();
int amountForChestItems = Utils.getAmount(c.getInventory(), shop.getProduct());
int amountForChestItems = Utils.getAmount(c.getInventory(), itemStack);
if (amountForChestItems == 0 && shop.getShopType() != ShopType.ADMIN) {
executor.sendMessage(LanguageUtils.getMessage(Message.OUT_OF_STOCK));
return;
}
ItemStack product = new ItemStack(shop.getProduct());
ItemStack product = new ItemStack(itemStack);
if (stack) product.setAmount(amount);
Inventory inventory = executor.getInventory();
@ -996,14 +1002,12 @@ public class ShopInteractListener implements Listener {
if (newAmount > amount) newAmount = amount;
ShopProduct newProduct = new ShopProduct(product, newAmount);
double newPrice = (price / amount) * newAmount;
if (freeSpace >= newAmount) {
plugin.debug(executor.getName() + " has enough inventory space for " + freeSpace + " items (#" + shop.getID() + ")");
ItemStack newProduct = new ItemStack(product);
newProduct.setAmount(newAmount);
EconomyResponse r = econ.withdrawPlayer(executor, worldName, newPrice);
if (r.transactionSuccess()) {
@ -1105,8 +1109,9 @@ public class ShopInteractListener implements Listener {
private void sell(Player executor, final Shop shop, boolean stack) {
plugin.debug(executor.getName() + " is selling (#" + shop.getID() + ")");
ItemStack itemStack = shop.getProduct().getItemStack();
int amount = shop.getProduct().getAmount();
if (stack) amount = shop.getProduct().getMaxStackSize();
if (stack) amount = itemStack.getMaxStackSize();
double price = shop.getSellPrice();
if (stack) price = (price / shop.getProduct().getAmount()) * amount;
@ -1130,14 +1135,14 @@ public class ShopInteractListener implements Listener {
Block block = shop.getLocation().getBlock();
Chest chest = (Chest) block.getState();
int amountForItemCount = Utils.getAmount(executor.getInventory(), shop.getProduct());
int amountForItemCount = Utils.getAmount(executor.getInventory(), itemStack);
if (amountForItemCount == 0) {
executor.sendMessage(LanguageUtils.getMessage(Message.NOT_ENOUGH_ITEMS));
return;
}
ItemStack product = new ItemStack(shop.getProduct());
ItemStack product = new ItemStack(itemStack);
if (stack) product.setAmount(amount);
Inventory inventory = chest.getInventory();
@ -1160,14 +1165,12 @@ public class ShopInteractListener implements Listener {
if (newAmount > amount) newAmount = amount;
ShopProduct newProduct = new ShopProduct(product, newAmount);
double newPrice = (price / amount) * newAmount;
if (freeSpace >= newAmount || shop.getShopType() == ShopType.ADMIN) {
plugin.debug("Chest has enough inventory space for " + freeSpace + " items (#" + shop.getID() + ")");
ItemStack newProduct = new ItemStack(product);
newProduct.setAmount(newAmount);
EconomyResponse r = econ.depositPlayer(executor, worldName, newPrice);
if (r.transactionSuccess()) {
@ -1271,11 +1274,12 @@ public class ShopInteractListener implements Listener {
* @param itemStack Items to add
* @return Whether all items were added to the inventory
*/
private boolean addToInventory(Inventory inventory, ItemStack itemStack) {
private boolean addToInventory(Inventory inventory, ShopProduct product) {
plugin.debug("Adding items to inventory...");
HashMap<Integer, ItemStack> inventoryItems = new HashMap<>();
int amount = itemStack.getAmount();
ItemStack itemStack = product.getItemStack();
int amount = product.getAmount();
int added = 0;
if (inventory instanceof PlayerInventory) {
@ -1329,11 +1333,12 @@ public class ShopInteractListener implements Listener {
* @param itemStack Items to remove
* @return Whether all items were removed from the inventory
*/
private boolean removeFromInventory(Inventory inventory, ItemStack itemStack) {
private boolean removeFromInventory(Inventory inventory, ShopProduct product) {
plugin.debug("Removing items from inventory...");
HashMap<Integer, ItemStack> inventoryItems = new HashMap<>();
int amount = itemStack.getAmount();
ItemStack itemStack = product.getItemStack();
int amount = product.getAmount();
int removed = 0;
if (inventory instanceof PlayerInventory) {

View File

@ -48,7 +48,7 @@ public class Shop {
private final ShopChest plugin;
private final OfflinePlayer vendor;
private final ItemStack product;
private final ShopProduct product;
private final Location location;
private final double buyPrice;
private final double sellPrice;
@ -60,7 +60,7 @@ public class Shop {
private Location holoLocation;
private ShopItem item;
public Shop(int id, ShopChest plugin, OfflinePlayer vendor, ItemStack product, Location location, double buyPrice, double sellPrice, ShopType shopType) {
public Shop(int id, ShopChest plugin, OfflinePlayer vendor, ShopProduct product, Location location, double buyPrice, double sellPrice, ShopType shopType) {
this.id = id;
this.plugin = plugin;
this.vendor = vendor;
@ -71,7 +71,7 @@ public class Shop {
this.shopType = shopType;
}
public Shop(ShopChest plugin, OfflinePlayer vendor, ItemStack product, Location location, double buyPrice, double sellPrice, ShopType shopType) {
public Shop(ShopChest plugin, OfflinePlayer vendor, ShopProduct product, Location location, double buyPrice, double sellPrice, ShopType shopType) {
this(-1, plugin, vendor, product, location, buyPrice, sellPrice, shopType);
}
@ -179,13 +179,9 @@ public class Shop {
plugin.debug("Creating item (#" + id + ")");
Location itemLocation;
ItemStack itemStack;
itemLocation = new Location(location.getWorld(), holoLocation.getX(), location.getY() + 0.9, holoLocation.getZ());
itemStack = product.clone();
itemStack.setAmount(1);
item = new ShopItem(plugin, itemStack, itemLocation);
item = new ShopItem(plugin, product.getItemStack(), itemLocation);
}
}
@ -260,39 +256,41 @@ public class Shop {
private String[] getHologramText(Inventory inventory) {
List<String> lines = new ArrayList<>();
ItemStack itemStack = getProduct().getItemStack();
Map<HologramFormat.Requirement, Object> requirements = new EnumMap<>(HologramFormat.Requirement.class);
requirements.put(HologramFormat.Requirement.VENDOR, getVendor().getName());
requirements.put(HologramFormat.Requirement.AMOUNT, getProduct().getAmount());
requirements.put(HologramFormat.Requirement.ITEM_TYPE, getProduct().getType() + (getProduct().getDurability() > 0 ? ":" + getProduct().getDurability() : ""));
requirements.put(HologramFormat.Requirement.ITEM_NAME, getProduct().hasItemMeta() ? getProduct().getItemMeta().getDisplayName() : null);
requirements.put(HologramFormat.Requirement.HAS_ENCHANTMENT, !LanguageUtils.getEnchantmentString(ItemUtils.getEnchantments(getProduct())).isEmpty());
requirements.put(HologramFormat.Requirement.ITEM_TYPE, itemStack.getType() + (itemStack.getDurability() > 0 ? ":" + itemStack.getDurability() : ""));
requirements.put(HologramFormat.Requirement.ITEM_NAME, itemStack.hasItemMeta() ? itemStack.getItemMeta().getDisplayName() : null);
requirements.put(HologramFormat.Requirement.HAS_ENCHANTMENT, !LanguageUtils.getEnchantmentString(ItemUtils.getEnchantments(itemStack)).isEmpty());
requirements.put(HologramFormat.Requirement.BUY_PRICE, getBuyPrice());
requirements.put(HologramFormat.Requirement.SELL_PRICE, getSellPrice());
requirements.put(HologramFormat.Requirement.HAS_POTION_EFFECT, ItemUtils.getPotionEffect(getProduct()) != null);
requirements.put(HologramFormat.Requirement.IS_MUSIC_DISC, getProduct().getType().isRecord());
requirements.put(HologramFormat.Requirement.IS_POTION_EXTENDED, ItemUtils.isExtendedPotion(getProduct()));
requirements.put(HologramFormat.Requirement.IS_WRITTEN_BOOK, getProduct().getType() == Material.WRITTEN_BOOK);
requirements.put(HologramFormat.Requirement.HAS_POTION_EFFECT, ItemUtils.getPotionEffect(itemStack) != null);
requirements.put(HologramFormat.Requirement.IS_MUSIC_DISC, itemStack.getType().isRecord());
requirements.put(HologramFormat.Requirement.IS_POTION_EXTENDED, ItemUtils.isExtendedPotion(itemStack));
requirements.put(HologramFormat.Requirement.IS_WRITTEN_BOOK, itemStack.getType() == Material.WRITTEN_BOOK);
requirements.put(HologramFormat.Requirement.ADMIN_SHOP, getShopType() == ShopType.ADMIN);
requirements.put(HologramFormat.Requirement.NORMAL_SHOP, getShopType() == ShopType.NORMAL);
requirements.put(HologramFormat.Requirement.IN_STOCK, Utils.getAmount(inventory, getProduct()));
requirements.put(HologramFormat.Requirement.MAX_STACK, getProduct().getMaxStackSize());
requirements.put(HologramFormat.Requirement.CHEST_SPACE, Utils.getFreeSpaceForItem(inventory, getProduct()));
requirements.put(HologramFormat.Requirement.DURABILITY, getProduct().getDurability());
requirements.put(HologramFormat.Requirement.IN_STOCK, Utils.getAmount(inventory, itemStack));
requirements.put(HologramFormat.Requirement.MAX_STACK, itemStack.getMaxStackSize());
requirements.put(HologramFormat.Requirement.CHEST_SPACE, Utils.getFreeSpaceForItem(inventory, itemStack));
requirements.put(HologramFormat.Requirement.DURABILITY, itemStack.getDurability());
Map<Placeholder, Object> placeholders = new EnumMap<>(Placeholder.class);
placeholders.put(Placeholder.VENDOR, getVendor().getName());
placeholders.put(Placeholder.AMOUNT, getProduct().getAmount());
placeholders.put(Placeholder.ITEM_NAME, LanguageUtils.getItemName(getProduct()));
placeholders.put(Placeholder.ENCHANTMENT, LanguageUtils.getEnchantmentString(ItemUtils.getEnchantments(getProduct())));
placeholders.put(Placeholder.ITEM_NAME, LanguageUtils.getItemName(itemStack));
placeholders.put(Placeholder.ENCHANTMENT, LanguageUtils.getEnchantmentString(ItemUtils.getEnchantments(itemStack)));
placeholders.put(Placeholder.BUY_PRICE, getBuyPrice());
placeholders.put(Placeholder.SELL_PRICE, getSellPrice());
placeholders.put(Placeholder.POTION_EFFECT, LanguageUtils.getPotionEffectName(getProduct()));
placeholders.put(Placeholder.MUSIC_TITLE, LanguageUtils.getMusicDiscName(getProduct().getType()));
placeholders.put(Placeholder.GENERATION, LanguageUtils.getBookGenerationName(getProduct()));
placeholders.put(Placeholder.STOCK, Utils.getAmount(inventory, getProduct()));
placeholders.put(Placeholder.MAX_STACK, getProduct().getMaxStackSize());
placeholders.put(Placeholder.CHEST_SPACE, Utils.getFreeSpaceForItem(inventory, getProduct()));
placeholders.put(Placeholder.DURABILITY, getProduct().getDurability());
placeholders.put(Placeholder.POTION_EFFECT, LanguageUtils.getPotionEffectName(itemStack));
placeholders.put(Placeholder.MUSIC_TITLE, LanguageUtils.getMusicDiscName(itemStack.getType()));
placeholders.put(Placeholder.GENERATION, LanguageUtils.getBookGenerationName(itemStack));
placeholders.put(Placeholder.STOCK, Utils.getAmount(inventory, itemStack));
placeholders.put(Placeholder.MAX_STACK, itemStack.getMaxStackSize());
placeholders.put(Placeholder.CHEST_SPACE, Utils.getFreeSpaceForItem(inventory, itemStack));
placeholders.put(Placeholder.DURABILITY, itemStack.getDurability());
int lineCount = plugin.getHologramFormat().getLineCount();
@ -407,7 +405,7 @@ public class Shop {
/**
* @return Product the shop sells (or buys)
*/
public ItemStack getProduct() {
public ShopProduct getProduct() {
return product;
}

View File

@ -0,0 +1,34 @@
package de.epiceric.shopchest.shop;
import org.bukkit.inventory.ItemStack;
public class ShopProduct {
private final ItemStack itemStack;
private final int amount;
public ShopProduct(ItemStack itemStack, int amount) {
this.itemStack = new ItemStack(itemStack);
this.itemStack.setAmount(1);
this.amount = amount;
}
public ShopProduct(ItemStack itemStack) {
this(itemStack, itemStack.getAmount());
}
/**
* @return The {@link ItemStack} with an amount of {@code 1}.
*/
public ItemStack getItemStack() {
return itemStack;
}
/**
* @return The amount
*/
public int getAmount() {
return amount;
}
}

View File

@ -7,6 +7,7 @@ import de.epiceric.shopchest.event.ShopBuySellEvent.Type;
import de.epiceric.shopchest.exceptions.WorldNotFoundException;
import de.epiceric.shopchest.language.LanguageUtils;
import de.epiceric.shopchest.shop.Shop;
import de.epiceric.shopchest.shop.ShopProduct;
import de.epiceric.shopchest.shop.Shop.ShopType;
import de.epiceric.shopchest.utils.Callback;
import de.epiceric.shopchest.utils.Utils;
@ -18,11 +19,14 @@ import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.scheduler.BukkitRunnable;
import java.sql.*;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@ -35,6 +39,10 @@ public abstract class Database {
private static Set<String> notFoundWorlds = new HashSet<>();
private String tableShops;
private String tableLogs;
private String tableLogouts;
ShopChest plugin;
HikariDataSource dataSource;
@ -52,6 +60,16 @@ public abstract class Database {
* that were found (as {@code int})
*/
public void connect(final Callback<Integer> callback) {
if (!Config.databaseTablePrefix.matches("^([a-zA-Z0-9\\-\\_]+)?$")) {
// Only letters, numbers dashes and underscores are allowed
plugin.getLogger().severe("Database table prefix contains illegal letters, using 'shopchest_' prefix.");
Config.databaseTablePrefix = "shopchest_";
}
this.tableShops = Config.databaseTablePrefix + "shops";
this.tableLogs = Config.databaseTablePrefix + "economy_logs";
this.tableLogouts = Config.databaseTablePrefix + "player_logouts";
new BukkitRunnable() {
@Override
public void run() {
@ -59,11 +77,14 @@ public abstract class Database {
dataSource = getDataSource();
String autoIncrement = Database.this instanceof SQLite ? "AUTOINCREMENT" : "AUTO_INCREMENT";
String queryCreateTableShopList =
"CREATE TABLE IF NOT EXISTS shops ("
+ "id INTEGER PRIMARY KEY " + (Database.this instanceof SQLite ? "AUTOINCREMENT" : "AUTO_INCREMENT") + ","
"CREATE TABLE IF NOT EXISTS " + tableShops + " ("
+ "id INTEGER PRIMARY KEY " + autoIncrement + ","
+ "vendor TINYTEXT NOT NULL,"
+ "product TEXT NOT NULL,"
+ "amount INTEGER NOT NULL,"
+ "world TINYTEXT NOT NULL,"
+ "x INTEGER NOT NULL,"
+ "y INTEGER NOT NULL,"
@ -73,12 +94,19 @@ public abstract class Database {
+ "shoptype TINYTEXT NOT NULL)";
String queryCreateTableShopLog =
"CREATE TABLE IF NOT EXISTS `shop_log` ("
+ "id INTEGER PRIMARY KEY " + (Database.this instanceof SQLite ? "AUTOINCREMENT" : "AUTO_INCREMENT") + ","
"CREATE TABLE IF NOT EXISTS " + tableLogs + " ("
+ "id INTEGER PRIMARY KEY " + autoIncrement + ","
+ "shop_id INTEGER NOT NULL,"
+ "timestamp TINYTEXT NOT NULL,"
+ "executor TINYTEXT NOT NULL,"
+ "product TINYTEXT NOT NULL,"
+ "vendor TINYTEXT NOT NULL,"
+ "time LONG NOT NULL,"
+ "player_name TINYTEXT NOT NULL,"
+ "player_uuid TINYTEXT NOT NULL,"
+ "product_name TINYTEXT NOT NULL,"
+ "product TEXT NOT NULL,"
+ "amount INTEGER NOT NULL,"
+ "vendor_name TINYTEXT NOT NULL,"
+ "vendor_uuid TINYTEXT NOT NULL,"
+ "admin BIT NOT NULL,"
+ "world TINYTEXT NOT NULL,"
+ "x INTEGER NOT NULL,"
+ "y INTEGER NOT NULL,"
@ -87,23 +115,127 @@ public abstract class Database {
+ "type TINYTEXT NOT NULL)";
String queryCreateTablePlayerLogout =
"CREATE TABLE IF NOT EXISTS player_logout ("
"CREATE TABLE IF NOT EXISTS " + tableLogouts + " ("
+ "player VARCHAR(36) PRIMARY KEY NOT NULL,"
+ "time LONG NOT NULL)";
String queryCheckIfOldFormat =
Database.this instanceof SQLite ?
"SELECT name FROM sqlite_master WHERE type='table' AND name='shop_log'" :
"SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='shop_log'";
String queryRenameTableLogouts = "ALTER TABLE player_logout RENAME TO " + tableLogouts;
String queryRenameTableLogs = "ALTER TABLE shop_log RENAME TO backup_shop_log"; // for backup
String queryRenameTableShops = "ALTER TABLE shops RENAME TO backup_shops"; // for backup
try (Connection con = dataSource.getConnection()) {
// Check if database is in old format
try (Statement s = con.createStatement()) {
ResultSet rs = s.executeQuery(queryCheckIfOldFormat);
if (rs.next()) {
plugin.getLogger().warning("Database is using old format and will be converted.");
try (Statement s2 = con.createStatement()) {
s.executeUpdate(queryRenameTableShops);
// Create new shops table
try (Statement s3 = con.createStatement()) {
s3.executeUpdate(queryCreateTableShopList);
}
// Create new log table
try (Statement s4 = con.createStatement()) {
s4.executeUpdate(queryCreateTableShopLog);
}
// Convert shop table
try (Statement s3 = con.createStatement()) {
ResultSet rs2 = s3.executeQuery("SELECT id,product FROM backup_shops");
while (rs2.next()) {
ItemStack is = Utils.decode(rs2.getString("product"));
int amount = is.getAmount();
is.setAmount(1);
String product = Utils.encode(is);
String insertQuery = "INSERT INTO " + tableShops + " SELECT id,vendor,?,?,world,x,y,z,buyprice,sellprice,shoptype FROM backup_shops WHERE id = ?";
try (PreparedStatement ps = con.prepareStatement(insertQuery)) {
ps.setString(1, product);
ps.setInt(2, amount);
ps.setInt(3, rs2.getInt("id"));
ps.executeUpdate();
}
}
}
// Convert log table
try (Statement s3 = con.createStatement()) {
ResultSet rs2 = s3.executeQuery("SELECT id,timestamp,executor,product,vendor FROM shop_log");
while (rs2.next()) {
String timestamp = rs2.getString("timestamp");
long time = 0L;
try {
// Create table "shops"
try (Connection con = dataSource.getConnection(); Statement s = con.createStatement()) {
time = new SimpleDateFormat("yyyy-MM-dd HH:mm").parse(timestamp).getTime();
} catch (ParseException e) {
plugin.debug("Failed to parse timestamp '" + timestamp + "': Time is set to 0");
plugin.debug(e);
}
String player = rs2.getString("executor");
String playerUuid = player.substring(0, 36);
String playerName = player.substring(38, player.length() - 1);
String oldProduct = rs2.getString("product");
String product = oldProduct.split(" x ")[1];
int amount = Integer.valueOf(oldProduct.split(" x ")[0]);
String vendor = rs2.getString("vendor");
String vendorUuid = vendor.substring(0, 36);
String vendorName = vendor.substring(38).replaceAll("\\)( \\(ADMIN\\))?", "");
boolean admin = vendor.endsWith("(ADMIN)");
String insertQuery = "INSERT INTO " + tableLogs + " SELECT id,-1,timestamp,?,?,?,?,'Unknown',?,?,?,?,world,x,y,z,price,type FROM shop_log WHERE id = ?";
try (PreparedStatement ps = con.prepareStatement(insertQuery)) {
ps.setLong(1, time);
ps.setString(2, playerName);
ps.setString(3, playerUuid);
ps.setString(4, product);
ps.setInt(5, amount);
ps.setString(6, vendorName);
ps.setString(7, vendorUuid);
ps.setBoolean(8, admin);
ps.setInt(9, rs2.getInt("id"));
ps.executeUpdate();
}
}
}
// Rename log table
try (Statement s3 = con.createStatement()) {
s3.executeUpdate(queryRenameTableLogs);
}
// Rename logout table
try (Statement s3 = con.createStatement()) {
s3.executeUpdate(queryRenameTableLogouts);
}
}
}
}
// Create shop table
try (Statement s = con.createStatement()) {
s.executeUpdate(queryCreateTableShopList);
}
// Create table "shop_log"
try (Connection con = dataSource.getConnection(); Statement s = con.createStatement()) {
// Create log table
try (Statement s = con.createStatement()) {
s.executeUpdate(queryCreateTableShopLog);
}
// Create table "player_logout"
try (Connection con = dataSource.getConnection(); Statement s = con.createStatement()) {
// Create logout table
try (Statement s = con.createStatement()) {
s.executeUpdate(queryCreateTablePlayerLogout);
}
@ -112,20 +244,20 @@ public abstract class Database {
cleanUpEconomy(false);
}
// Count entries in table "shops"
try (Connection con = dataSource.getConnection(); Statement s = con.createStatement();) {
ResultSet rs = s.executeQuery("SELECT id FROM shops");
int count = 0;
while (rs.next()) {
count++;
}
// Count shops entries in database
try (Statement s = con.createStatement();) {
ResultSet rs = s.executeQuery("SELECT COUNT(id) FROM " + tableShops);
if (rs.next()) {
int count = rs.getInt(1);
plugin.debug("Initialized database with " + count + " entries");
if (callback != null) {
callback.callSyncResult(count);
}
} else {
throw new SQLException("Count result set has no entries");
}
}
} catch (SQLException e) {
if (callback != null) {
@ -151,7 +283,7 @@ public abstract class Database {
@Override
public void run() {
try (Connection con = dataSource.getConnection();
PreparedStatement ps = con.prepareStatement("DELETE FROM shops WHERE id = ?")) {
PreparedStatement ps = con.prepareStatement("DELETE FROM " + tableShops + " WHERE id = ?")) {
ps.setInt(1, shop.getID());
ps.executeUpdate();
@ -173,53 +305,14 @@ public abstract class Database {
}.runTaskAsynchronously(plugin);
}
/**
* @param id ID of the shop
* @param callback Callback that - if succeeded - returns whether a shop with
* the given ID exists (as {@code boolean})
*/
public void isShop(final int id, final Callback<Boolean> callback) {
new BukkitRunnable() {
@Override
public void run() {
try (Connection con = dataSource.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT * FROM shops WHERE id = ?")) {
ps.setInt(1, id);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
if (rs.getInt("id") == id) {
if (callback != null) {
callback.callSyncResult(true);
}
return;
}
}
if (callback != null) {
callback.callSyncResult(false);
}
} catch (SQLException ex) {
if (callback != null) {
callback.callSyncError(ex);
}
plugin.getLogger().severe("Failed to check if shop exists in the database");
plugin.debug("Failed to check if shop with ID exists (#" + id + ")");
plugin.debug(ex);
}
}
}.runTaskAsynchronously(plugin);
}
/**
* Get all shops from the database
*
* @param showConsoleMessages Whether console messages (errors or warnings)
* should be shown
* @param callback Callback that - if succeeded - returns a read-only
* collection of all shops (as
* {@code Collection<Shop>})
* @param showConsoleMessages Whether console messages (errors or warnings)
* should be shown
*/
public void getShops(final boolean showConsoleMessages, final Callback<Collection<Shop>> callback) {
new BukkitRunnable() {
@ -228,7 +321,7 @@ public abstract class Database {
ArrayList<Shop> shops = new ArrayList<>();
try (Connection con = dataSource.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT * FROM shops")) {
PreparedStatement ps = con.prepareStatement("SELECT * FROM " + tableShops + "")) {
ResultSet rs = ps.executeQuery();
while (rs.next()) {
@ -238,9 +331,6 @@ public abstract class Database {
String worldName = rs.getString("world");
World world = Bukkit.getWorld(worldName);
int x = rs.getInt("x");
int y = rs.getInt("y");
int z = rs.getInt("z");
if (world == null) {
WorldNotFoundException ex = new WorldNotFoundException(worldName);
@ -253,16 +343,20 @@ public abstract class Database {
continue;
}
int x = rs.getInt("x");
int y = rs.getInt("y");
int z = rs.getInt("z");
Location location = new Location(world, x, y, z);
plugin.debug("Initializing new shop... (#" + id + ")");
OfflinePlayer vendor = Bukkit.getOfflinePlayer(UUID.fromString(rs.getString("vendor")));
ItemStack product = Utils.decode(rs.getString("product"));
ItemStack itemStack = Utils.decode(rs.getString("product"));
int amount = rs.getInt("amount");
ShopProduct product = new ShopProduct(itemStack, amount);
double buyPrice = rs.getDouble("buyprice");
double sellPrice = rs.getDouble("sellprice");
ShopType shopType = ShopType.valueOf(rs.getString("shoptype"));
plugin.debug("Initializing new shop... (#" + id + ")");
shops.add(new Shop(id, plugin, vendor, product, location, buyPrice, sellPrice, shopType));
}
@ -291,8 +385,8 @@ public abstract class Database {
* given (as {@code int})
*/
public void addShop(final Shop shop, final Callback<Integer> callback) {
final String queryNoId = "REPLACE INTO shops (vendor,product,world,x,y,z,buyprice,sellprice,shoptype) VALUES(?,?,?,?,?,?,?,?,?)";
final String queryWithId = "REPLACE INTO shops (id,vendor,product,world,x,y,z,buyprice,sellprice,shoptype) VALUES(?,?,?,?,?,?,?,?,?,?)";
final String queryNoId = "REPLACE INTO " + tableShops + " (vendor,product,amount,world,x,y,z,buyprice,sellprice,shoptype) VALUES(?,?,?,?,?,?,?,?,?,?)";
final String queryWithId = "REPLACE INTO " + tableShops + " (id,vendor,product,amount,world,x,y,z,buyprice,sellprice,shoptype) VALUES(?,?,?,?,?,?,?,?,?,?,?)";
new BukkitRunnable() {
@Override
@ -308,14 +402,15 @@ public abstract class Database {
}
ps.setString(i+1, shop.getVendor().getUniqueId().toString());
ps.setString(i+2, Utils.encode(shop.getProduct()));
ps.setString(i+3, shop.getLocation().getWorld().getName());
ps.setInt(i+4, shop.getLocation().getBlockX());
ps.setInt(i+5, shop.getLocation().getBlockY());
ps.setInt(i+6, shop.getLocation().getBlockZ());
ps.setDouble(i+7, shop.getBuyPrice());
ps.setDouble(i+8, shop.getSellPrice());
ps.setString(i+9, shop.getShopType().toString());
ps.setString(i+2, Utils.encode(shop.getProduct().getItemStack()));
ps.setInt(i+3, shop.getProduct().getAmount());
ps.setString(i+4, shop.getLocation().getWorld().getName());
ps.setInt(i+5, shop.getLocation().getBlockX());
ps.setInt(i+6, shop.getLocation().getBlockY());
ps.setInt(i+7, shop.getLocation().getBlockZ());
ps.setDouble(i+8, shop.getBuyPrice());
ps.setDouble(i+9, shop.getSellPrice());
ps.setString(i+10, shop.getShopType().toString());
ps.executeUpdate();
if (!shop.hasId()) {
@ -356,8 +451,10 @@ public abstract class Database {
* @param type Whether the executor bought or sold
* @param callback Callback that - if succeeded - returns {@code null}
*/
public void logEconomy(final Player executor, Shop shop, ItemStack product, double price, Type type, final Callback<Void> callback) {
final String query = "INSERT INTO shop_log (timestamp,executor,product,vendor,world,x,y,z,price,type) VALUES(?,?,?,?,?,?,?,?,?,?)";
public void logEconomy(final Player executor, Shop shop, ShopProduct product, double price, Type type, final Callback<Void> callback) {
final String query = "INSERT INTO " + tableLogs + " (shop_id,timestamp,time,player_name,player_uuid,product_name,product,amount,"
+ "vendor_name,vendor_uuid,admin,world,x,y,z,price,type) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
if (Config.enableEconomyLog) {
new BukkitRunnable() {
@Override
@ -365,16 +462,25 @@ public abstract class Database {
try (Connection con = dataSource.getConnection();
PreparedStatement ps = con.prepareStatement(query)) {
ps.setString(1, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance().getTime()));
ps.setString(2, executor.getUniqueId().toString() + " (" + executor.getName() + ")");
ps.setString(3, product.getAmount() + " x " + LanguageUtils.getItemName(product));
ps.setString(4, shop.getVendor().getUniqueId().toString() + " (" + shop.getVendor().getName() + ")" + (shop.getShopType() == ShopType.ADMIN ? " (ADMIN)" : ""));
ps.setString(5, shop.getLocation().getWorld().getName());
ps.setInt(6, shop.getLocation().getBlockX());
ps.setInt(7, shop.getLocation().getBlockY());
ps.setInt(8, shop.getLocation().getBlockZ());
ps.setDouble(9, price);
ps.setString(10, type.toString());
long millis = System.currentTimeMillis();
ps.setInt(1, shop.getID());
ps.setString(2, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(millis));
ps.setLong(3, millis);
ps.setString(4, executor.getName());
ps.setString(5, executor.getUniqueId().toString());
ps.setString(6, LanguageUtils.getItemName(product.getItemStack()));
ps.setString(7, Utils.encode(product.getItemStack()));
ps.setInt(8, product.getAmount());
ps.setString(9, shop.getVendor().getName());
ps.setString(10, shop.getVendor().getUniqueId().toString());
ps.setBoolean(11, shop.getShopType() == ShopType.ADMIN);
ps.setString(12, shop.getLocation().getWorld().getName());
ps.setInt(13, shop.getLocation().getBlockX());
ps.setInt(14, shop.getLocation().getBlockY());
ps.setInt(15, shop.getLocation().getBlockZ());
ps.setDouble(16, price);
ps.setString(17, type.toString());
ps.executeUpdate();
if (callback != null) {
@ -409,13 +515,9 @@ public abstract class Database {
BukkitRunnable runnable = new BukkitRunnable() {
@Override
public void run() {
Calendar cal = Calendar.getInstance();
long time = System.currentTimeMillis();
cal.add(Calendar.DATE, -Config.cleanupEconomyLogDays);
time -= Config.cleanupEconomyLogDays * 86400000L;
String logPurgeLimit = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(cal.getTime());
String queryCleanUpLog = "DELETE FROM shop_log WHERE timestamp < '" + logPurgeLimit + "'";
String queryCleanUpPlayers = "DELETE FROM player_logout WHERE time < " + String.valueOf(time);
long time = System.currentTimeMillis() - Config.cleanupEconomyLogDays * 86400000L;
String queryCleanUpLog = "DELETE FROM " + tableLogs + " WHERE time < " + time;
String queryCleanUpPlayers = "DELETE FROM " + tableLogouts + " WHERE time < " + time;
try (Connection con = dataSource.getConnection();
Statement s = con.createStatement();
@ -452,16 +554,15 @@ public abstract class Database {
new BukkitRunnable() {
@Override
public void run() {
String vendor = String.format("%s (%s)", player.getUniqueId().toString(), player.getName());
double revenue = 0;
try (Connection con = dataSource.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT * FROM shop_log WHERE vendor = ?")) {
ps.setString(1, vendor);
PreparedStatement ps = con.prepareStatement("SELECT * FROM " + tableLogs + " WHERE vendor_uuid = ?")) {
ps.setString(1, player.getUniqueId().toString());
ResultSet rs = ps.executeQuery();
while (rs.next()) {
if (rs.getString("vendor").equals(vendor)) {
long timestamp = rs.getLong("time");
double singleRevenue = rs.getDouble("price");
ShopBuySellEvent.Type type = ShopBuySellEvent.Type.valueOf(rs.getString("type"));
@ -469,21 +570,10 @@ public abstract class Database {
singleRevenue = -singleRevenue;
}
long timestamp;
try {
timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(rs.getString("timestamp")).getTime();
} catch (ParseException ex) {
plugin.debug("Failed to get revenue from player \"" + player.getUniqueId().toString() + "\"");
plugin.debug(ex);
continue;
}
if (timestamp > logoutTime) {
revenue += singleRevenue;
}
}
}
if (callback != null) {
callback.callSyncResult(revenue);
@ -505,18 +595,16 @@ public abstract class Database {
* Log a logout to the database
*
* @param player Player who logged out
* @param timestamp Time in milliseconds when the player logged out
* @param callback Callback that - if succeeded - returns {@code null}
*/
public void logLogout(final Player player, final long timestamp, final Callback<Void> callback) {
public void logLogout(final Player player, final Callback<Void> callback) {
new BukkitRunnable() {
@Override
public void run() {
try (Connection con = dataSource.getConnection();
PreparedStatement ps = con.prepareStatement("REPLACE INTO player_logout (player,time) VALUES(?,?)")) {
PreparedStatement ps = con.prepareStatement("REPLACE INTO " + tableLogouts + " (player,time) VALUES(?,?)")) {
ps.setString(1, player.getUniqueId().toString());
ps.setLong(2, timestamp);
ps.setLong(2, System.currentTimeMillis());
ps.executeUpdate();
if (callback != null) {
@ -549,20 +637,15 @@ public abstract class Database {
new BukkitRunnable() {
@Override
public void run() {
String uuid = player.getUniqueId().toString();
try (Connection con = dataSource.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT * FROM player_logout WHERE player = ?")) {
ps.setString(1, uuid);
PreparedStatement ps = con.prepareStatement("SELECT * FROM " + tableLogouts + " WHERE player = ?")) {
ps.setString(1, player.getUniqueId().toString());
ResultSet rs = ps.executeQuery();
while (rs.next()) {
if (rs.getString("player").equals(uuid)) {
if (rs.next()) {
if (callback != null) {
callback.callSyncResult(rs.getLong("time"));
}
return;
}
}
if (callback != null) {

View File

@ -1,9 +1,9 @@
package de.epiceric.shopchest.utils;
import de.epiceric.shopchest.ShopChest;
import de.epiceric.shopchest.shop.ShopProduct;
import de.epiceric.shopchest.shop.Shop.ShopType;
import org.bukkit.OfflinePlayer;
import org.bukkit.inventory.ItemStack;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitTask;
@ -18,7 +18,7 @@ public class ClickType {
private static Map<UUID, BukkitTask> playerTimers = new HashMap<>();
private EnumClickType enumClickType;
private ItemStack product;
private ShopProduct product;
private double buyPrice;
private double sellPrice;
private ShopType shopType;
@ -27,7 +27,7 @@ public class ClickType {
this.enumClickType = enumClickType;
}
public ClickType(EnumClickType enumClickType, ItemStack product, double buyPrice, double sellPrice, ShopType shopType) {
public ClickType(EnumClickType enumClickType, ShopProduct product, double buyPrice, double sellPrice, ShopType shopType) {
this.enumClickType = enumClickType;
this.product = product;
this.sellPrice = sellPrice;
@ -90,7 +90,7 @@ public class ClickType {
/**
* @return If {@link #getClickType()} returns {@link EnumClickType#CREATE}, this returns the item, the player has hold in his hands, else <b>null</b>.
*/
public ItemStack getProduct() {
public ShopProduct getProduct() {
return product;
}

View File

@ -251,6 +251,9 @@ database:
# Either use 'SQLite' or 'MySQL'. Otherwise you will break the plugin!
type: "SQLite"
# Set the prefix of all table names related to this plugin.
table-prefix: "shopchest_"
# If the specified type is 'MySQL', here you configure the...
# (You can leave this empty if you're using SQLite)
mysql: