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
This commit is contained in:
PretzelJohn 2022-01-15 09:46:56 -05:00
parent 0e63df608f
commit 3aba0ff1c7
7 changed files with 93 additions and 25 deletions

View File

@ -2,13 +2,20 @@ package com.pretzel.dev.villagertradelimiter.data;
import com.pretzel.dev.villagertradelimiter.lib.Util; 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 { public class Cooldown {
private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH);
private enum Interval { private enum Interval {
s(1000L), s(1L),
m(60000L), m(60L),
h(3600000L), h(3600L),
d(86400000L), d(86400L),
w(604800000L); w(604800L);
final long factor; final long factor;
Interval(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) * @param cooldownStr The cooldown time as written in config.yml (7d, 30s, 5m, etc)
* @return The cooldown time in milliseconds * @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 { try {
long time = Long.parseLong(timeStr.substring(0, timeStr.length()-1)); long time = Long.parseLong(cooldownStr.substring(0, cooldownStr.length()-1));
String interval = timeStr.substring(timeStr.length()-1).toLowerCase(); String interval = cooldownStr.substring(cooldownStr.length()-1).toLowerCase();
return time * Interval.valueOf(interval).factor; return time * Interval.valueOf(interval).factor;
} catch (Exception e) { } catch (Exception e) {
Util.errorMsg(e); Util.errorMsg(e);
} }
return 0; 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;
}
} }

View File

@ -5,7 +5,7 @@ import com.pretzel.dev.villagertradelimiter.wrappers.VillagerWrapper;
import java.util.HashMap; import java.util.HashMap;
public class PlayerData { public class PlayerData {
private final HashMap<String, Long> tradingCooldowns; private final HashMap<String, String> tradingCooldowns;
private VillagerWrapper tradingVillager; private VillagerWrapper tradingVillager;
public PlayerData() { public PlayerData() {
@ -14,7 +14,7 @@ public class PlayerData {
} }
/** @return The map of items to timestamps for the player's trading history */ /** @return The map of items to timestamps for the player's trading history */
public HashMap<String, Long> getTradingCooldowns() { return this.tradingCooldowns; } public HashMap<String, String> getTradingCooldowns() { return this.tradingCooldowns; }
/** @param tradingVillager The villager that this player is currently trading with */ /** @param tradingVillager The villager that this player is currently trading with */
public void setTradingVillager(final VillagerWrapper tradingVillager) { this.tradingVillager = tradingVillager; } public void setTradingVillager(final VillagerWrapper tradingVillager) { this.tradingVillager = tradingVillager; }

View File

@ -6,6 +6,8 @@ import com.pretzel.dev.villagertradelimiter.data.PlayerData;
import com.pretzel.dev.villagertradelimiter.lib.Util; import com.pretzel.dev.villagertradelimiter.lib.Util;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import java.time.Instant;
import java.util.Date;
import java.util.UUID; import java.util.UUID;
public class DatabaseManager { public class DatabaseManager {
@ -13,7 +15,7 @@ public class DatabaseManager {
"CREATE TABLE IF NOT EXISTS vtl_cooldown("+ "CREATE TABLE IF NOT EXISTS vtl_cooldown("+
"uuid CHAR(36) NOT NULL,"+ "uuid CHAR(36) NOT NULL,"+
"item VARCHAR(255) NOT NULL,"+ "item VARCHAR(255) NOT NULL,"+
"time BIGINT NOT NULL,"+ "time TEXT NOT NULL,"+
"PRIMARY KEY(uuid, item));"; "PRIMARY KEY(uuid, item));";
private static final String SELECT_ITEMS = "SELECT * FROM vtl_cooldown;"; 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 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]); UUID uuid = UUID.fromString(tokens[0]);
String item = tokens[1]; 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); PlayerData playerData = instance.getPlayerData().get(uuid);
if(playerData == null) { if(playerData == null) {
@ -54,9 +58,12 @@ public class DatabaseManager {
instance.getPlayerData().put(uuid, playerData); instance.getPlayerData().put(uuid, playerData);
} }
String cooldown = instance.getCfg().getString("Overrides."+item+".Cooldown", null); final Date now = Date.from(Instant.now());
if(cooldown != null && System.currentTimeMillis() < time + Cooldown.parseTime(cooldown)) { final String global = instance.getCfg().getString("Cooldown", "0");
playerData.getTradingCooldowns().put(item, time); 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 = ""; String values = "";
for(String item : playerData.getTradingCooldowns().keySet()) { for(String item : playerData.getTradingCooldowns().keySet()) {
long time = playerData.getTradingCooldowns().get(item); String time = playerData.getTradingCooldowns().get(item);
if(!values.isEmpty()) values += ","; if(!values.isEmpty()) values += ",";
values += "('"+uuid+"','"+item+"','"+time+"')"; values += "('"+uuid+"','"+item+"','"+time+"')";

View File

@ -1,6 +1,7 @@
package com.pretzel.dev.villagertradelimiter.listeners; package com.pretzel.dev.villagertradelimiter.listeners;
import com.pretzel.dev.villagertradelimiter.VillagerTradeLimiter; 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.data.PlayerData;
import com.pretzel.dev.villagertradelimiter.lib.Util; import com.pretzel.dev.villagertradelimiter.lib.Util;
import com.pretzel.dev.villagertradelimiter.settings.Settings; 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.ItemStack;
import org.bukkit.inventory.MerchantRecipe; import org.bukkit.inventory.MerchantRecipe;
import java.time.Instant;
import java.util.Date;
public class InventoryListener implements Listener { public class InventoryListener implements Listener {
private final VillagerTradeLimiter instance; private final VillagerTradeLimiter instance;
private final Settings settings; private final Settings settings;
@ -74,7 +79,13 @@ public class InventoryListener implements Listener {
if(overrides == null) return; if(overrides == null) return;
final String type = settings.getType(result, ingredient1, ingredient2); 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 //Get the selected recipe by the items in the slots
final MerchantRecipe selectedRecipe = getSelectedRecipe((Villager)event.getInventory().getHolder(), ingredient1, ingredient2, result); final MerchantRecipe selectedRecipe = getSelectedRecipe((Villager)event.getInventory().getHolder(), ingredient1, ingredient2, result);
@ -89,7 +100,7 @@ public class InventoryListener implements Listener {
Bukkit.getScheduler().runTaskLater(instance, () -> { Bukkit.getScheduler().runTaskLater(instance, () -> {
int uses = selectedRecipe.getUses(); int uses = selectedRecipe.getUses();
if(!playerData.getTradingCooldowns().containsKey(type) && uses >= selectedRecipe.getMaxUses()) { 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); }, 1);
} }

View File

@ -16,6 +16,8 @@ import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectType;
import java.time.Instant;
import java.util.Date;
import java.util.List; import java.util.List;
public class PlayerListener implements Listener { public class PlayerListener implements Listener {
@ -172,9 +174,14 @@ public class PlayerListener implements Listener {
final ConfigurationSection overrides = instance.getCfg().getConfigurationSection("Overrides"); final ConfigurationSection overrides = instance.getCfg().getConfigurationSection("Overrides");
if(overrides != null) { if(overrides != null) {
final String type = settings.getType(recipe.getItemStack("sell"), recipe.getItemStack("buy"), recipe.getItemStack("buyB")); 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(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); playerData.getTradingCooldowns().remove(type);
} else { } else {
maxUses = 0; maxUses = 0;

View File

@ -66,24 +66,34 @@ public class Settings {
final String resultType = result.getType().name().toLowerCase(); final String resultType = result.getType().name().toLowerCase();
final String ingredient1Type = ingredient1.getType().name().toLowerCase(); final String ingredient1Type = ingredient1.getType().name().toLowerCase();
final String ingredient2Type = ingredient2.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) { if(result.getType() == Material.ENCHANTED_BOOK) {
final EnchantmentStorageMeta meta = (EnchantmentStorageMeta) result.getItemMeta(); final EnchantmentStorageMeta meta = (EnchantmentStorageMeta) result.getItemMeta();
if(meta == null) return null; if(meta == null) return defaultType;
for(Enchantment key : meta.getStoredEnchants().keySet()) { for(Enchantment key : meta.getStoredEnchants().keySet()) {
if (key != null) { if (key != null) {
final String itemType = key.getKey().getKey() +"_"+meta.getStoredEnchantLevel(key); final String itemType = key.getKey().getKey() +"_"+meta.getStoredEnchantLevel(key);
if(getItem(ingredient1, result, itemType) != null) return itemType; if(getItem(ingredient1, result, itemType) != null) return itemType;
} }
} }
return null; return defaultType;
} }
final ItemStack ingredient = (ingredient1.getType() == Material.AIR ? ingredient2 : ingredient1); final ItemStack ingredient = (ingredient1.getType() == Material.AIR ? ingredient2 : ingredient1);
if(getItem(ingredient, result, resultType) != null) return resultType; if(getItem(ingredient, result, resultType) != null) return resultType;
if(getItem(ingredient, result, ingredient1Type) != null) return ingredient1Type; if(getItem(ingredient, result, ingredient1Type) != null) return ingredient1Type;
if(getItem(ingredient, result, ingredient2Type) != null) return ingredient2Type; if(getItem(ingredient, result, ingredient2Type) != null) return ingredient2Type;
return null; return defaultType;
} }
/** /**

View File

@ -49,6 +49,18 @@ MaxDemand: -1
# For more information, see https://minecraft.fandom.com/el/wiki/Trading#Java_Edition # For more information, see https://minecraft.fandom.com/el/wiki/Trading#Java_Edition
MaxUses: -1 MaxUses: -1
# The per-player, per-trade cooldown in real-world time.
# After a player makes a trade <MaxUses> 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 <Number><Interval> 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 --------------------------------# #-------------------------------- PER-ITEM SETTINGS --------------------------------#
# Override the global settings for individual items. To disable, set like this --> Overrides: none # Override the global settings for individual items. To disable, set like this --> Overrides: none