diff --git a/src/main/java/de/epiceric/shopchest/ShopChest.java b/src/main/java/de/epiceric/shopchest/ShopChest.java index 979b9f7..fc93a02 100644 --- a/src/main/java/de/epiceric/shopchest/ShopChest.java +++ b/src/main/java/de/epiceric/shopchest/ShopChest.java @@ -63,6 +63,7 @@ import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.HashMap; import java.util.Map; +import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; @@ -446,6 +447,20 @@ public class ShopChest extends JavaPlugin { Chunk[] loadedChunks = getServer().getWorlds().stream().map(World::getLoadedChunks) .flatMap(Stream::of).toArray(Chunk[]::new); + shopUtils.loadShopAmounts(new Callback>(ShopChest.this) { + @Override + public void onResult(Map result) { + getLogger().info("Loaded shop amounts"); + debug("Loaded shop amounts"); + } + + @Override + public void onError(Throwable throwable) { + getLogger().severe("Failed to load shop amounts. Shop limits will not be working correctly!"); + if (throwable != null) getLogger().severe(throwable.getMessage()); + } + }); + shopUtils.loadShops(loadedChunks, new Callback(ShopChest.this) { @Override public void onResult(Integer result) { diff --git a/src/main/java/de/epiceric/shopchest/sql/Database.java b/src/main/java/de/epiceric/shopchest/sql/Database.java index 7fbe9c5..64630dc 100644 --- a/src/main/java/de/epiceric/shopchest/sql/Database.java +++ b/src/main/java/de/epiceric/shopchest/sql/Database.java @@ -366,6 +366,43 @@ public abstract class Database { }.runTaskAsynchronously(plugin); } + /** + * Get shop amounts for each player + * + * @param callback Callback that returns a map of each player's shop amount + */ + public void getShopAmounts(final Callback> callback) { + new BukkitRunnable(){ + @Override + public void run() { + try (Connection con = dataSource.getConnection(); + Statement s = con.createStatement()) { + ResultSet rs = s.executeQuery("SELECT vendor, COUNT(*) AS count FROM " + tableShops + " WHERE shoptype = 'NORMAL' GROUP BY vendor"); + + plugin.debug("Getting shop amounts from database"); + + Map result = new HashMap<>(); + while (rs.next()) { + UUID uuid = UUID.fromString(rs.getString("vendor")); + result.put(uuid, rs.getInt("count")); + } + + if (callback != null) { + callback.callSyncResult(result); + } + } catch (SQLException ex) { + if (callback != null) { + callback.callSyncError(ex); + } + + plugin.getLogger().severe("Failed to get shop amounts from database"); + plugin.debug("Failed to get shop amounts from database"); + plugin.debug(ex); + } + } + }.runTaskAsynchronously(plugin); + } + /** * Get all shops from the database that are located in the given chunks * diff --git a/src/main/java/de/epiceric/shopchest/utils/Counter.java b/src/main/java/de/epiceric/shopchest/utils/Counter.java new file mode 100644 index 0000000..c466c93 --- /dev/null +++ b/src/main/java/de/epiceric/shopchest/utils/Counter.java @@ -0,0 +1,55 @@ +package de.epiceric.shopchest.utils; + +/** + * Represents a counter for integers greather than or equal to zero. + */ +public final class Counter { + private int value; + + /** + * Creates a counter with a starting value of zero + */ + public Counter() { + this(0); + } + + /** + * Creates a counter with the given starting value + * @param value the starting value of this counter + */ + public Counter(int value) { + set(value); + } + + /** + * Increments the counter by one and returns itself + */ + public final Counter increment() { + this.value++; + return this; + } + + /** + * Decrements the counter by one if its value is greater than zero and returns itself + */ + public final Counter decrement() { + this.value = Math.max(0, this.value - 1); + return this; + } + + /** + * Sets the counter's value to the given value or zero if the given value is negative + * @param value the value to set the counter to + */ + public final Counter set(int value) { + this.value = Math.max(0, value); + return this; + } + + /** + * Returns the current value + */ + public final int get() { + return value; + } +} \ No newline at end of file diff --git a/src/main/java/de/epiceric/shopchest/utils/ShopUtils.java b/src/main/java/de/epiceric/shopchest/utils/ShopUtils.java index 17ddbb0..e72ce4d 100644 --- a/src/main/java/de/epiceric/shopchest/utils/ShopUtils.java +++ b/src/main/java/de/epiceric/shopchest/utils/ShopUtils.java @@ -5,6 +5,7 @@ import de.epiceric.shopchest.config.Config; import de.epiceric.shopchest.event.ShopsLoadedEvent; import de.epiceric.shopchest.event.ShopsUnloadedEvent; import de.epiceric.shopchest.shop.Shop; +import de.epiceric.shopchest.shop.Shop.ShopType; import org.bukkit.Bukkit; import org.bukkit.Chunk; @@ -23,6 +24,8 @@ import java.util.stream.Collectors; public class ShopUtils { + private final Map playerShopAmount = new HashMap<>(); + // concurrent since it is updated in async task private final Map playerLocation = new ConcurrentHashMap<>(); private final Map shopLocation = new ConcurrentHashMap<>(); @@ -103,6 +106,9 @@ public class ShopUtils { } if (addToDatabase) { + if (shop.getShopType() != ShopType.ADMIN) { + playerShopAmount.compute(shop.getVendor().getUniqueId(), (uuid, amount) -> amount == null ? new Counter(1) : amount.increment()); + } plugin.getShopDatabase().addShop(shop, callback); } else { if (callback != null) callback.callSyncResult(shop.getID()); @@ -145,6 +151,9 @@ public class ShopUtils { shop.removeHologram(); if (removeFromDatabase) { + if (shop.getShopType() != ShopType.ADMIN) { + playerShopAmount.compute(shop.getVendor().getUniqueId(), (uuid, amount) -> amount == null ? new Counter() : amount.decrement()); + } plugin.getShopDatabase().removeShop(shop, callback); } else { if (callback != null) callback.callSyncResult(null); @@ -186,8 +195,15 @@ public class ShopUtils { shop.removeHologram(); }); + Shop first = toRemove.values().iterator().next(); + boolean isAdmin = first.getShopType() == ShopType.ADMIN; + UUID vendorUuid = first.getVendor().getUniqueId(); + // Database#removeShop removes shop by ID so this only needs to be called once if (removeFromDatabase) { + if (!isAdmin) { + playerShopAmount.compute(vendorUuid, (uuid, amount) -> amount == null ? new Counter() : amount.decrement()); + } plugin.getShopDatabase().removeShop(toRemove.values().iterator().next(), callback); } else { if (callback != null) callback.callSyncResult(null); @@ -250,23 +266,27 @@ public class ShopUtils { * @return The amount of a shops a player has (if {@link Config#excludeAdminShops} is true, admin shops won't be counted) */ public int getShopAmount(OfflinePlayer p) { - // FIXME: currently only showing loaded shops - float shopCount = 0; + return playerShopAmount.getOrDefault(p.getUniqueId(), new Counter()).get(); + } - for (Shop shop : getShops()) { - if (shop.getVendor().equals(p)) { - if (shop.getShopType() != Shop.ShopType.ADMIN) { - shopCount++; - - InventoryHolder ih = shop.getInventoryHolder(); - - if (ih instanceof DoubleChest) - shopCount -= 0.5; - } + /** + * Loads the amount of shops for each player + * @param callback Callback that returns the amount of shops for each player + */ + public void loadShopAmounts(final Callback> callback) { + plugin.getShopDatabase().getShopAmounts(new Callback>(plugin) { + @Override + public void onResult(Map result) { + playerShopAmount.clear(); + result.forEach((uuid, amount) -> playerShopAmount.put(uuid, new Counter(amount))); + if (callback != null) callback.onResult(result); } - } - return Math.round(shopCount); + @Override + public void onError(Throwable throwable) { + if (callback != null) callback.onError(throwable); + } + }); } /**