From 3aba0ff1c7232ccd40e0561afb4deb38978dc556 Mon Sep 17 00:00:00 2001 From: PretzelJohn <58197328+PretzelJohn@users.noreply.github.com> Date: Sat, 15 Jan 2022 09:46:56 -0500 Subject: [PATCH] Version 1.5.0-pre3: * Added global setting for per-player cooldowns To-do: * Add per-villager restock cooldowns * Add enchantments, data, etc. to ingredients and result * Add config editor GUI in-game --- .../villagertradelimiter/data/Cooldown.java | 41 ++++++++++++++----- .../villagertradelimiter/data/PlayerData.java | 4 +- .../database/DatabaseManager.java | 19 ++++++--- .../listeners/InventoryListener.java | 15 ++++++- .../listeners/PlayerListener.java | 11 ++++- .../settings/Settings.java | 16 ++++++-- src/main/resources/config.yml | 12 ++++++ 7 files changed, 93 insertions(+), 25 deletions(-) diff --git a/src/com/pretzel/dev/villagertradelimiter/data/Cooldown.java b/src/com/pretzel/dev/villagertradelimiter/data/Cooldown.java index c876d03..4416f95 100644 --- a/src/com/pretzel/dev/villagertradelimiter/data/Cooldown.java +++ b/src/com/pretzel/dev/villagertradelimiter/data/Cooldown.java @@ -2,13 +2,20 @@ package com.pretzel.dev.villagertradelimiter.data; import com.pretzel.dev.villagertradelimiter.lib.Util; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + public class Cooldown { + private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH); + private enum Interval { - s(1000L), - m(60000L), - h(3600000L), - d(86400000L), - w(604800000L); + s(1L), + m(60L), + h(3600L), + d(86400L), + w(604800L); final long factor; Interval(long factor) { @@ -17,17 +24,31 @@ public class Cooldown { } /** - * @param timeStr The cooldown time as written in config.yml (7d, 30s, 5m, etc) - * @return The cooldown time in milliseconds + * @param cooldownStr The cooldown time as written in config.yml (7d, 30s, 5m, etc) + * @return The cooldown time in seconds */ - public static long parseTime(final String timeStr) { + public static long parseCooldown(final String cooldownStr) { + if(cooldownStr.equals("0")) return 0; try { - long time = Long.parseLong(timeStr.substring(0, timeStr.length()-1)); - String interval = timeStr.substring(timeStr.length()-1).toLowerCase(); + long time = Long.parseLong(cooldownStr.substring(0, cooldownStr.length()-1)); + String interval = cooldownStr.substring(cooldownStr.length()-1).toLowerCase(); return time * Interval.valueOf(interval).factor; } catch (Exception e) { Util.errorMsg(e); } return 0; } + + public static String formatTime(Date date) { + return FORMAT.format(date); + } + + public static Date parseTime(final String timeStr) { + try { + return FORMAT.parse(timeStr); + } catch (ParseException e) { + Util.errorMsg(e); + } + return null; + } } diff --git a/src/com/pretzel/dev/villagertradelimiter/data/PlayerData.java b/src/com/pretzel/dev/villagertradelimiter/data/PlayerData.java index 937f852..fc787df 100644 --- a/src/com/pretzel/dev/villagertradelimiter/data/PlayerData.java +++ b/src/com/pretzel/dev/villagertradelimiter/data/PlayerData.java @@ -5,7 +5,7 @@ import com.pretzel.dev.villagertradelimiter.wrappers.VillagerWrapper; import java.util.HashMap; public class PlayerData { - private final HashMap tradingCooldowns; + private final HashMap tradingCooldowns; private VillagerWrapper tradingVillager; public PlayerData() { @@ -14,7 +14,7 @@ public class PlayerData { } /** @return The map of items to timestamps for the player's trading history */ - public HashMap getTradingCooldowns() { return this.tradingCooldowns; } + public HashMap getTradingCooldowns() { return this.tradingCooldowns; } /** @param tradingVillager The villager that this player is currently trading with */ public void setTradingVillager(final VillagerWrapper tradingVillager) { this.tradingVillager = tradingVillager; } diff --git a/src/com/pretzel/dev/villagertradelimiter/database/DatabaseManager.java b/src/com/pretzel/dev/villagertradelimiter/database/DatabaseManager.java index f0e11b9..ad85e51 100644 --- a/src/com/pretzel/dev/villagertradelimiter/database/DatabaseManager.java +++ b/src/com/pretzel/dev/villagertradelimiter/database/DatabaseManager.java @@ -6,6 +6,8 @@ import com.pretzel.dev.villagertradelimiter.data.PlayerData; import com.pretzel.dev.villagertradelimiter.lib.Util; import org.bukkit.configuration.ConfigurationSection; +import java.time.Instant; +import java.util.Date; import java.util.UUID; public class DatabaseManager { @@ -13,7 +15,7 @@ public class DatabaseManager { "CREATE TABLE IF NOT EXISTS vtl_cooldown("+ "uuid CHAR(36) NOT NULL,"+ "item VARCHAR(255) NOT NULL,"+ - "time BIGINT NOT NULL,"+ + "time TEXT NOT NULL,"+ "PRIMARY KEY(uuid, item));"; private static final String SELECT_ITEMS = "SELECT * FROM vtl_cooldown;"; private static final String INSERT_ITEM = "INSERT OR IGNORE INTO vtl_cooldown(uuid,item,time) VALUES?;"; //INSERT IGNORE INTO for MySQL @@ -46,7 +48,9 @@ public class DatabaseManager { UUID uuid = UUID.fromString(tokens[0]); String item = tokens[1]; - long time = Long.parseLong(tokens[2]); + final Date date = Cooldown.parseTime(tokens[2]); + if(date == null) continue; + long time = date.getTime(); PlayerData playerData = instance.getPlayerData().get(uuid); if(playerData == null) { @@ -54,9 +58,12 @@ public class DatabaseManager { instance.getPlayerData().put(uuid, playerData); } - String cooldown = instance.getCfg().getString("Overrides."+item+".Cooldown", null); - if(cooldown != null && System.currentTimeMillis() < time + Cooldown.parseTime(cooldown)) { - playerData.getTradingCooldowns().put(item, time); + final Date now = Date.from(Instant.now()); + final String global = instance.getCfg().getString("Cooldown", "0"); + final String local = instance.getCfg().getString("Overrides."+item+".Cooldown", global); + long cooldown = Cooldown.parseCooldown(local); + if(cooldown != 0 && now.getTime()/1000L < time/1000L + cooldown) { + playerData.getTradingCooldowns().put(item, Cooldown.formatTime(date)); } } } @@ -81,7 +88,7 @@ public class DatabaseManager { String values = ""; for(String item : playerData.getTradingCooldowns().keySet()) { - long time = playerData.getTradingCooldowns().get(item); + String time = playerData.getTradingCooldowns().get(item); if(!values.isEmpty()) values += ","; values += "('"+uuid+"','"+item+"','"+time+"')"; diff --git a/src/com/pretzel/dev/villagertradelimiter/listeners/InventoryListener.java b/src/com/pretzel/dev/villagertradelimiter/listeners/InventoryListener.java index e3c2614..49b74fa 100644 --- a/src/com/pretzel/dev/villagertradelimiter/listeners/InventoryListener.java +++ b/src/com/pretzel/dev/villagertradelimiter/listeners/InventoryListener.java @@ -1,6 +1,7 @@ package com.pretzel.dev.villagertradelimiter.listeners; import com.pretzel.dev.villagertradelimiter.VillagerTradeLimiter; +import com.pretzel.dev.villagertradelimiter.data.Cooldown; import com.pretzel.dev.villagertradelimiter.data.PlayerData; import com.pretzel.dev.villagertradelimiter.lib.Util; import com.pretzel.dev.villagertradelimiter.settings.Settings; @@ -18,7 +19,11 @@ import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.MerchantRecipe; +import java.time.Instant; +import java.util.Date; + public class InventoryListener implements Listener { + private final VillagerTradeLimiter instance; private final Settings settings; @@ -74,7 +79,13 @@ public class InventoryListener implements Listener { if(overrides == null) return; final String type = settings.getType(result, ingredient1, ingredient2); - if(type == null || !overrides.contains(type+".Cooldown")) return; + if(instance.getCfg().getString("Cooldown", "0").equals("0")) { + Util.consoleMsg("global cooldown is 0"); + if(overrides.getString(type+".Cooldown", "0").equals("0")) { + Util.consoleMsg("local cooldown is 0"); + return; + } + } //Get the selected recipe by the items in the slots final MerchantRecipe selectedRecipe = getSelectedRecipe((Villager)event.getInventory().getHolder(), ingredient1, ingredient2, result); @@ -89,7 +100,7 @@ public class InventoryListener implements Listener { Bukkit.getScheduler().runTaskLater(instance, () -> { int uses = selectedRecipe.getUses(); if(!playerData.getTradingCooldowns().containsKey(type) && uses >= selectedRecipe.getMaxUses()) { - playerData.getTradingCooldowns().put(type, System.currentTimeMillis()); + playerData.getTradingCooldowns().put(type, Cooldown.formatTime(Date.from(Instant.now()))); } }, 1); } diff --git a/src/com/pretzel/dev/villagertradelimiter/listeners/PlayerListener.java b/src/com/pretzel/dev/villagertradelimiter/listeners/PlayerListener.java index f1dcac1..447b87f 100644 --- a/src/com/pretzel/dev/villagertradelimiter/listeners/PlayerListener.java +++ b/src/com/pretzel/dev/villagertradelimiter/listeners/PlayerListener.java @@ -16,6 +16,8 @@ import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; +import java.time.Instant; +import java.util.Date; import java.util.List; public class PlayerListener implements Listener { @@ -172,9 +174,14 @@ public class PlayerListener implements Listener { final ConfigurationSection overrides = instance.getCfg().getConfigurationSection("Overrides"); if(overrides != null) { final String type = settings.getType(recipe.getItemStack("sell"), recipe.getItemStack("buy"), recipe.getItemStack("buyB")); - if(type != null && overrides.contains(type+".Cooldown")) { + final String global = instance.getCfg().getString("Cooldown", "0"); + final String local = overrides.getString(type+".Cooldown", global); + if(type != null && !local.equals("0")) { if(playerData.getTradingCooldowns().containsKey(type)) { - if(System.currentTimeMillis() >= playerData.getTradingCooldowns().get(type) + Cooldown.parseTime(overrides.getString(type+".Cooldown"))) { + final Date now = Date.from(Instant.now()); + final Date lastTrade = Cooldown.parseTime(playerData.getTradingCooldowns().get(type)); + long cooldown = Cooldown.parseCooldown(local); + if(lastTrade != null && (now.getTime()/1000L >= lastTrade.getTime()/1000L + cooldown)) { playerData.getTradingCooldowns().remove(type); } else { maxUses = 0; diff --git a/src/com/pretzel/dev/villagertradelimiter/settings/Settings.java b/src/com/pretzel/dev/villagertradelimiter/settings/Settings.java index b462f8c..b3a27f2 100644 --- a/src/com/pretzel/dev/villagertradelimiter/settings/Settings.java +++ b/src/com/pretzel/dev/villagertradelimiter/settings/Settings.java @@ -66,24 +66,34 @@ public class Settings { final String resultType = result.getType().name().toLowerCase(); final String ingredient1Type = ingredient1.getType().name().toLowerCase(); final String ingredient2Type = ingredient2.getType().name().toLowerCase(); + final String defaultType; + if(result.getType() == Material.EMERALD) { + if(ingredient1.getType() == Material.BOOK || ingredient1.getType() == Material.AIR) { + defaultType = ingredient2Type; + } else { + defaultType = ingredient1Type; + } + } else { + defaultType = resultType; + } if(result.getType() == Material.ENCHANTED_BOOK) { final EnchantmentStorageMeta meta = (EnchantmentStorageMeta) result.getItemMeta(); - if(meta == null) return null; + if(meta == null) return defaultType; for(Enchantment key : meta.getStoredEnchants().keySet()) { if (key != null) { final String itemType = key.getKey().getKey() +"_"+meta.getStoredEnchantLevel(key); if(getItem(ingredient1, result, itemType) != null) return itemType; } } - return null; + return defaultType; } final ItemStack ingredient = (ingredient1.getType() == Material.AIR ? ingredient2 : ingredient1); if(getItem(ingredient, result, resultType) != null) return resultType; if(getItem(ingredient, result, ingredient1Type) != null) return ingredient1Type; if(getItem(ingredient, result, ingredient2Type) != null) return ingredient2Type; - return null; + return defaultType; } /** diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 41acb3e..bf89877 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -49,6 +49,18 @@ MaxDemand: -1 # For more information, see https://minecraft.fandom.com/el/wiki/Trading#Java_Edition MaxUses: -1 +# The per-player, per-trade cooldown in real-world time. +# After a player makes a trade times, the trade will be disabled for the player until the cooldown is over. +# * Set to 0 to disable this feature and keep vanilla behavior +# * Set to a number and interval to add a per-player, per-trade cooldown for all trades (see below) +# A valid cooldown follows the format, such as 7d or 30s. The valid intervals are: +# * s = seconds (e.g. 30s) +# * m = minutes (e.g. 10m) +# * h = hours (e.g. 1h) +# * d = days (e.g. 3d) +# * w = weeks (e.g. 2w) +Cooldown: 0 + #-------------------------------- PER-ITEM SETTINGS --------------------------------# # Override the global settings for individual items. To disable, set like this --> Overrides: none