Version 1.5.0-pre4:

* Add per-villager restock cooldowns

To-do:
* Add enchantments, data, etc. to ingredients and result
* Add config editor GUI in-game
This commit is contained in:
PretzelJohn 2022-01-15 12:10:28 -05:00
parent 3aba0ff1c7
commit 8720e840e2
7 changed files with 124 additions and 19 deletions

View File

@ -5,6 +5,7 @@ import com.pretzel.dev.villagertradelimiter.commands.CommandBase;
import com.pretzel.dev.villagertradelimiter.data.PlayerData;
import com.pretzel.dev.villagertradelimiter.database.DatabaseManager;
import com.pretzel.dev.villagertradelimiter.listeners.InventoryListener;
import com.pretzel.dev.villagertradelimiter.listeners.VillagerListener;
import com.pretzel.dev.villagertradelimiter.settings.ConfigUpdater;
import com.pretzel.dev.villagertradelimiter.lib.Metrics;
import com.pretzel.dev.villagertradelimiter.lib.Util;
@ -76,6 +77,7 @@ public class VillagerTradeLimiter extends JavaPlugin {
//Load/reload database manager
if(this.databaseManager == null) this.databaseManager = new DatabaseManager(this);
else onDisable();
this.databaseManager.load();
}
@ -99,6 +101,7 @@ public class VillagerTradeLimiter extends JavaPlugin {
this.playerListener = new PlayerListener(this, settings);
this.getServer().getPluginManager().registerEvents(this.playerListener, this);
this.getServer().getPluginManager().registerEvents(new InventoryListener(this, settings), this);
this.getServer().getPluginManager().registerEvents(new VillagerListener(this, settings), this);
}

View File

@ -39,10 +39,18 @@ public class Cooldown {
return 0;
}
/**
* @param date The date to format
* @return The date as a 'yyyy-MM-dd HH:mm:ss' formatted string
*/
public static String formatTime(Date date) {
return FORMAT.format(date);
}
/**
* @param timeStr The string to format, in 'yyyy-MM-dd HH:mm:ss' format
* @return The date that the string represents
*/
public static Date parseTime(final String timeStr) {
try {
return FORMAT.parse(timeStr);

View File

@ -4,7 +4,9 @@ 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 org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Villager;
import java.time.Instant;
import java.util.Date;
@ -52,18 +54,20 @@ public class DatabaseManager {
if(date == null) continue;
long time = date.getTime();
PlayerData playerData = instance.getPlayerData().get(uuid);
if(playerData == null) {
playerData = new PlayerData();
instance.getPlayerData().put(uuid, playerData);
PlayerData data = instance.getPlayerData().get(uuid);
if(data == null) {
data = new PlayerData();
instance.getPlayerData().put(uuid, data);
}
String key = (Bukkit.getEntity(uuid) instanceof Villager ? "Restock" : "Cooldown");
String cooldownStr = instance.getCfg().getString(key, "0");
cooldownStr = instance.getCfg().getString("Overrides."+item+"."+key, cooldownStr);
long cooldown = Cooldown.parseCooldown(cooldownStr);
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));
data.getTradingCooldowns().put(item, Cooldown.formatTime(date));
}
}
}
@ -88,7 +92,7 @@ public class DatabaseManager {
String values = "";
for(String item : playerData.getTradingCooldowns().keySet()) {
String time = playerData.getTradingCooldowns().get(item);
final String time = playerData.getTradingCooldowns().get(item);
if(!values.isEmpty()) values += ",";
values += "('"+uuid+"','"+item+"','"+time+"')";

View File

@ -79,13 +79,12 @@ public class InventoryListener implements Listener {
if(overrides == null) return;
final String type = settings.getType(result, ingredient1, ingredient2);
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;
}
}
String cooldownStr = instance.getCfg().getString("Cooldown", "0");
cooldownStr = overrides.getString(type+".Cooldown", cooldownStr);
String restockStr = instance.getCfg().getString("Restock", "0");
restockStr = overrides.getString(type+".Restock", restockStr);
if(cooldownStr.equals("0") && restockStr.equals("0")) return;
//Get the selected recipe by the items in the slots
final MerchantRecipe selectedRecipe = getSelectedRecipe((Villager)event.getInventory().getHolder(), ingredient1, ingredient2, result);
@ -96,11 +95,18 @@ public class InventoryListener implements Listener {
//Add a cooldown to the trade if the player has reached the max uses
final PlayerData playerData = instance.getPlayerData().get(player.getUniqueId());
final PlayerData villagerData = instance.getPlayerData().get(((Villager)event.getInventory().getHolder()).getUniqueId());
if(playerData == null || playerData.getTradingVillager() == null) return;
Bukkit.getScheduler().runTaskLater(instance, () -> {
int uses = selectedRecipe.getUses();
if(!playerData.getTradingCooldowns().containsKey(type) && uses >= selectedRecipe.getMaxUses()) {
playerData.getTradingCooldowns().put(type, Cooldown.formatTime(Date.from(Instant.now())));
final String time = Cooldown.formatTime(Date.from(Instant.now()));
if(uses >= selectedRecipe.getMaxUses()) {
if(!playerData.getTradingCooldowns().containsKey(type)) {
playerData.getTradingCooldowns().put(type, time);
}
if(villagerData != null && !villagerData.getTradingCooldowns().containsKey(type)) {
villagerData.getTradingCooldowns().put(type, time);
}
}
}, 1);
}

View File

@ -67,6 +67,9 @@ public class PlayerListener implements Listener {
if(!instance.getPlayerData().containsKey(player.getUniqueId())) {
instance.getPlayerData().put(player.getUniqueId(), new PlayerData());
}
if(!instance.getPlayerData().containsKey(villager.getUniqueId())) {
instance.getPlayerData().put(villager.getUniqueId(), new PlayerData());
}
this.see(villager, player, player);
}
@ -143,6 +146,7 @@ public class PlayerListener implements Listener {
* @return The total discount for the recipe, which is added to the base price to get the final price
*/
private int getDiscount(final RecipeWrapper recipe, int totalReputation, double hotvDiscount) {
//Calculates the total discount
int basePrice = getBasePrice(recipe);
int demand = getDemand(recipe);
float priceMultiplier = recipe.getPriceMultiplier();
@ -150,10 +154,12 @@ public class PlayerListener implements Listener {
double maxDiscount = settings.fetchDouble(recipe, "MaxDiscount", 0.3);
if(maxDiscount >= 0.0 && maxDiscount <= 1.0) {
//Change the discount to the smaller MaxDiscount
if(basePrice + discount < basePrice * (1.0 - maxDiscount)) {
discount = -(int)(basePrice * maxDiscount);
}
} else if(maxDiscount > 1.0) {
//Change the discount to the larger MaxDiscount
//TODO: Allow for better fine-tuning
discount = (int)(discount * maxDiscount);
}
@ -169,6 +175,7 @@ public class PlayerListener implements Listener {
int maxUses = settings.fetchInt(recipe, "MaxUses", -1);
boolean disabled = settings.fetchBoolean(recipe, "Disabled", false);
//Disables the trade if the player has an active cooldown for the trade
final PlayerData playerData = instance.getPlayerData().get(player.getUniqueId());
if(playerData != null && playerData.getTradingVillager() != null) {
final ConfigurationSection overrides = instance.getCfg().getConfigurationSection("Overrides");
@ -210,6 +217,7 @@ public class PlayerListener implements Listener {
final PotionEffect effect = player.getPotionEffect(effectType);
if(effect == null) return 0.0;
//Calculates the discount factor from the player's current effect level or the defined maximum
int heroLevel = effect.getAmplifier()+1;
final int maxHeroLevel = instance.getCfg().getInt("MaxHeroLevel", -1);
if(maxHeroLevel == 0 || heroLevel == 0) return 0.0;

View File

@ -0,0 +1,69 @@
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;
import org.bukkit.entity.Villager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.VillagerReplenishTradeEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.MerchantRecipe;
import java.time.Instant;
import java.util.Date;
import java.util.UUID;
public class VillagerListener implements Listener {
private final VillagerTradeLimiter instance;
private final Settings settings;
/**
* @param instance The instance of VillagerTradeLimiter.java
* @param settings The settings instance
*/
public VillagerListener(final VillagerTradeLimiter instance, final Settings settings) {
this.instance = instance;
this.settings = settings;
}
/** Handles villager restocks */
@EventHandler
public void onVillagerRestock(final VillagerReplenishTradeEvent event) {
if(!(event.getEntity() instanceof Villager)) return;
if(Util.isNPC((Villager) event.getEntity())) return;
//Get the items involved in the restock
final MerchantRecipe recipe = event.getRecipe();
final ItemStack result = recipe.getResult();
ItemStack ingredient1 = recipe.getIngredients().get(0);
ItemStack ingredient2 = recipe.getIngredients().get(1);
final String type = settings.getType(result, ingredient1, ingredient2);
//Get the villager's data container
final UUID uuid = event.getEntity().getUniqueId();
final PlayerData villagerData = instance.getPlayerData().get(uuid);
if(villagerData == null) return;
//Get the time of the last trade, restock cooldown setting, and now
final String lastTradeStr = villagerData.getTradingCooldowns().get(type);
if(lastTradeStr == null) return;
String cooldownStr = instance.getCfg().getString("Restock", "0");
cooldownStr = instance.getCfg().getString("Overrides."+type+".Restock", cooldownStr);
final Date now = Date.from(Instant.now());
final Date lastTrade = Cooldown.parseTime(lastTradeStr);
if(lastTrade == null) return;
final long cooldown = Cooldown.parseCooldown(cooldownStr);
//Cancel the event if there is an active restock cooldown, otherwise remove the restock cooldown
if(now.getTime()/1000L >= lastTrade.getTime()/1000L + cooldown) {
villagerData.getTradingCooldowns().remove(type);
} else {
event.setCancelled(true);
}
}
}

View File

@ -61,6 +61,12 @@ MaxUses: -1
# * w = weeks (e.g. 2w)
Cooldown: 0
# The per-villager, per-trade cooldown in real-world time.
# This is the same as Cooldown, but applies to a villager's restocking function
# * Set to 0 to disable this feature and keep vanilla behavior
# * Set to a number and interval to add a per-villager, per-trade cooldown for all trades (see below)
Restock: 0
#-------------------------------- PER-ITEM SETTINGS --------------------------------#
# Override the global settings for individual items. To disable, set like this --> Overrides: none
@ -91,4 +97,5 @@ Overrides:
clock:
MaxDemand: 12
paper:
Disabled: true
MaxUses: 1
Restock: 1h