From 8b82d2994d636e55d3825aa607c2f5ab7af2f45e Mon Sep 17 00:00:00 2001 From: xGinko Date: Sat, 9 Sep 2023 01:52:04 +0200 Subject: [PATCH] unchain VillagerWrapper methods rewrite some VillagerWrapper methods for better cooldown handling enable per method cooldown add javadoc for WrappedVillager rename namespaced keys --- .../xginko/villageroptimizer/enums/Keys.java | 8 +- .../models/WrappedVillager.java | 133 ++++++++++++----- .../modules/BlockOptimization.java | 136 ++++++++++-------- .../modules/LevelVillagers.java | 6 +- .../modules/NametagOptimization.java | 46 +++--- .../modules/RestockTrades.java | 1 + .../modules/VillagerOptimizerModule.java | 2 + .../modules/WorkstationOptimization.java | 11 +- 8 files changed, 221 insertions(+), 122 deletions(-) diff --git a/src/main/java/me/xginko/villageroptimizer/enums/Keys.java b/src/main/java/me/xginko/villageroptimizer/enums/Keys.java index f55af9b..6eb7cf3 100644 --- a/src/main/java/me/xginko/villageroptimizer/enums/Keys.java +++ b/src/main/java/me/xginko/villageroptimizer/enums/Keys.java @@ -5,10 +5,10 @@ import org.bukkit.NamespacedKey; public enum Keys { - OPTIMIZED(VillagerOptimizer.getKey("optimized")), - COOLDOWN_OPTIMIZE(VillagerOptimizer.getKey("optimize-cooldown")), - COOLDOWN_EXPERIENCE(VillagerOptimizer.getKey("experience-cooldown")), - WORLDTIME(VillagerOptimizer.getKey("last-restock-time")); + OPTIMIZATION(VillagerOptimizer.getKey("optimization")), + LAST_OPTIMIZE(VillagerOptimizer.getKey("last-optimize")), + LAST_LEVELUP(VillagerOptimizer.getKey("last-levelup")), + LAST_RESTOCK(VillagerOptimizer.getKey("last-restock")); private final NamespacedKey key; diff --git a/src/main/java/me/xginko/villageroptimizer/models/WrappedVillager.java b/src/main/java/me/xginko/villageroptimizer/models/WrappedVillager.java index fac6695..29e26a2 100644 --- a/src/main/java/me/xginko/villageroptimizer/models/WrappedVillager.java +++ b/src/main/java/me/xginko/villageroptimizer/models/WrappedVillager.java @@ -1,6 +1,5 @@ package me.xginko.villageroptimizer.models; -import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.enums.Keys; import me.xginko.villageroptimizer.enums.OptimizationType; import org.bukkit.entity.Villager; @@ -9,9 +8,7 @@ import org.bukkit.persistence.PersistentDataType; import org.jetbrains.annotations.NotNull; public final class WrappedVillager { - /* - * TODO: Refresh cache when information is read or written (but efficiently) - * */ + private final @NotNull Villager villager; private final @NotNull PersistentDataContainer dataContainer; @@ -20,72 +17,132 @@ public final class WrappedVillager { this.dataContainer = this.villager.getPersistentDataContainer(); } + /** + * @return The villager inside the wrapper. + */ public @NotNull Villager villager() { return villager; } - public static @NotNull WrappedVillager fromCache(Villager villager) { - return VillagerOptimizer.getVillagerManager().getOrAdd(villager); - } - + /** + * @return True if the villager is optimized by the plugin, otherwise false. + */ public boolean isOptimized() { - return dataContainer.has(Keys.OPTIMIZED.key()); + return dataContainer.has(Keys.OPTIMIZATION.key()); } - public boolean setOptimization(OptimizationType type) { + /** + * @param cooldown_millis The configured cooldown in millis until the next optimization is allowed to occur. + * @return True if villager can be optimized again, otherwise false. + */ + public boolean canOptimize(final long cooldown_millis) { + return getLastOptimize() + cooldown_millis <= System.currentTimeMillis(); + } + + /** + * @param type OptimizationType the villager should be set to. + */ + public void setOptimization(OptimizationType type) { if (type.equals(OptimizationType.OFF) && isOptimized()) { - dataContainer.remove(Keys.OPTIMIZED.key()); + dataContainer.remove(Keys.OPTIMIZATION.key()); villager.setAware(true); villager.setAI(true); } else { - if (isOnOptimizeCooldown()) return false; - dataContainer.set(Keys.OPTIMIZED.key(), PersistentDataType.STRING, type.name()); + dataContainer.set(Keys.OPTIMIZATION.key(), PersistentDataType.STRING, type.name()); villager.setAware(false); - setOptimizeCooldown(VillagerOptimizer.getConfiguration().optimize_cooldown_millis); } - return true; } + /** + * @return The current OptimizationType of the villager. + */ public @NotNull OptimizationType getOptimizationType() { - return isOptimized() ? OptimizationType.valueOf(dataContainer.get(Keys.OPTIMIZED.key(), PersistentDataType.STRING)) : OptimizationType.OFF; + return isOptimized() ? OptimizationType.valueOf(dataContainer.get(Keys.OPTIMIZATION.key(), PersistentDataType.STRING)) : OptimizationType.OFF; } - public void setOptimizeCooldown(long milliseconds) { - dataContainer.set(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG, System.currentTimeMillis() + milliseconds); + /** + * Saves the system time in millis when the villager was last optimized. + */ + public void saveOptimizeTime() { + dataContainer.set(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG, System.currentTimeMillis()); } - public long getOptimizeCooldown() { - return dataContainer.has(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG) ? System.currentTimeMillis() - dataContainer.get(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG) : 0L; + /** + * @return The system time in millis when the villager was last optimized, 0L if the villager was never optimized. + */ + public long getLastOptimize() { + return dataContainer.has(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG) ? dataContainer.get(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG) : 0L; } - public boolean isOnOptimizeCooldown() { - return dataContainer.has(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG) && dataContainer.get(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG) <= System.currentTimeMillis(); - } - - public void setExpCooldown(long milliseconds) { - dataContainer.set(Keys.COOLDOWN_EXPERIENCE.key(), PersistentDataType.LONG, System.currentTimeMillis() + milliseconds); - } - - public boolean isOnExpCooldown() { - return dataContainer.has(Keys.COOLDOWN_EXPERIENCE.key(), PersistentDataType.LONG) && dataContainer.get(Keys.COOLDOWN_EXPERIENCE.key(), PersistentDataType.LONG) <= System.currentTimeMillis(); + /** + * @param cooldown_millis The configured cooldown in milliseconds you want to check against. + * @return The time left in millis until the villager can be optimized again. + */ + public long getOptimizeCooldownMillis(final long cooldown_millis) { + return dataContainer.has(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG) ? (System.currentTimeMillis() - (dataContainer.get(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG) + cooldown_millis)) : cooldown_millis; } + /** + * @param cooldown_millis The configured cooldown in milliseconds you want to check against. + * @return True if the villager has been loaded long enough. + */ public boolean canRestock(final long cooldown_millis) { - final long lastRestock = getRestockTimestamp(); - if (lastRestock == 0L) return true; - return lastRestock + cooldown_millis <= villager.getWorld().getFullTime(); + return getLastRestock() + cooldown_millis <= villager.getWorld().getFullTime(); } + /** + * Restock all trading recipes. + */ public void restock() { villager.getRecipes().forEach(recipe -> recipe.setUses(0)); - saveRestockTimestamp(); } - public void saveRestockTimestamp() { - dataContainer.set(Keys.WORLDTIME.key(), PersistentDataType.LONG, villager.getWorld().getFullTime()); + /** + * Saves the time of the in-game world when the entity was last restocked. + */ + public void saveRestockTime() { + dataContainer.set(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG, villager.getWorld().getFullTime()); } - public long getRestockTimestamp() { - return dataContainer.has(Keys.WORLDTIME.key(), PersistentDataType.LONG) ? dataContainer.get(Keys.WORLDTIME.key(), PersistentDataType.LONG) : 0L; + /** + * @return The time of the in-game world when the entity was last restocked. + */ + public long getLastRestock() { + return dataContainer.has(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG) ? dataContainer.get(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG) : 0L; + } + + /** + * @return The level between 1-5 calculated from the villagers experience. + */ + public int calculateLevel() { + // https://minecraft.fandom.com/wiki/Trading#Mechanics + int vilEXP = villager.getVillagerExperience(); + if (vilEXP >= 250) return 5; + if (vilEXP >= 150) return 4; + if (vilEXP >= 70) return 3; + if (vilEXP >= 10) return 2; + return 1; + } + + /** + * @param cooldown_millis The configured cooldown in milliseconds you want to check against. + * @return The system time in millis when the villager was last optimized, 0L if the villager was never optimized. + */ + public boolean canLevelUp(final long cooldown_millis) { + return dataContainer.has(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG) && dataContainer.get(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG) + cooldown_millis <= villager.getWorld().getFullTime(); + } + + /** + * Saves the time of the in-game world when the entity was last leveled up. + */ + public void saveLastLevelUp() { + dataContainer.set(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG, villager.getWorld().getFullTime()); + } + + /** + * @return The time of the in-game world when the entity was last leveled up. + */ + public long getLastLevelUpTime() { + return dataContainer.has(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG) ? dataContainer.get(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG) : 0L; } } diff --git a/src/main/java/me/xginko/villageroptimizer/modules/BlockOptimization.java b/src/main/java/me/xginko/villageroptimizer/modules/BlockOptimization.java index e980597..4bc1baa 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/BlockOptimization.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/BlockOptimization.java @@ -27,6 +27,8 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener { private final VillagerManager villagerManager; private final Config config; private final boolean shouldLog, shouldNotifyPlayer; + private final int maxVillagers; + private final long cooldown; protected BlockOptimization() { this.villagerManager = VillagerOptimizer.getVillagerManager(); @@ -36,6 +38,12 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener { player interacts with them. If the block is broken or moved, the villager will become unoptimized\s again once a player interacts with the villager afterwards. """); + this.cooldown = config.getInt("optimization.methods.by-specific-block.optimize-cooldown-seconds", 600, """ + Cooldown in seconds until a villager can be optimized again by using this method. \s + Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior. + """) * 1000L; + this.maxVillagers = config.getInt("optimization.methods.by-specific-block.max-villagers-per-block", 3, + "How many villagers can be optimized at once by placing a block under them."); this.shouldLog = config.getBoolean("optimization.methods.by-specific-block.log", false); this.shouldNotifyPlayer = config.getBoolean("optimization.methods.by-specific-block.notify-player", true); } @@ -61,30 +69,36 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener { Block placed = event.getBlock(); if (!config.blocks_that_disable.contains(placed.getType())) return; - placed.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,1,0.5).forEach(entity -> { - if (entity.getType().equals(EntityType.VILLAGER)) { - WrappedVillager wVillager = villagerManager.getOrAdd((Villager) entity); - if (!wVillager.isOptimized()) { - if (wVillager.setOptimization(OptimizationType.BLOCK)) { - if (shouldNotifyPlayer) { - Player player = event.getPlayer(); - VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(line -> player.sendMessage(line - .replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(wVillager.villager().getProfession().toString().toLowerCase()).build()) - .replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(placed.getType().toString().toLowerCase()).build()) - )); - } - if (shouldLog) - VillagerOptimizer.getLog().info("Villager was optimized by block at "+wVillager.villager().getLocation()); - } else { - if (shouldNotifyPlayer) { - Player player = event.getPlayer(); - VillagerOptimizer.getLang(player.locale()).block_on_optimize_cooldown.forEach(line -> player.sendMessage(line - .replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(wVillager.getOptimizeCooldown())).build()))); - } - } + int counter = 0; + for (Entity entity : placed.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,1,0.5)) { + if (!entity.getType().equals(EntityType.VILLAGER)) continue; + + WrappedVillager wVillager = villagerManager.getOrAdd((Villager) entity); + if (wVillager.isOptimized()) continue; + if (counter >= maxVillagers) return; + + if (wVillager.canOptimize(cooldown)) { + wVillager.setOptimization(OptimizationType.BLOCK); + wVillager.saveOptimizeTime(); + counter++; + if (shouldNotifyPlayer) { + Player player = event.getPlayer(); + VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(line -> player.sendMessage(line + .replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(wVillager.villager().getProfession().toString().toLowerCase()).build()) + .replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(placed.getType().toString().toLowerCase()).build()) + )); + } + if (shouldLog) + VillagerOptimizer.getLog().info("Villager was optimized by block at "+wVillager.villager().getLocation()); + } else { + if (shouldNotifyPlayer) { + Player player = event.getPlayer(); + final long optimizeCoolDown = wVillager.getOptimizeCooldownMillis(cooldown); + VillagerOptimizer.getLang(player.locale()).block_on_optimize_cooldown.forEach(line -> player.sendMessage(line + .replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(optimizeCoolDown)).build()))); } } - }); + } } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @@ -92,23 +106,28 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener { Block broken = event.getBlock(); if (!config.blocks_that_disable.contains(broken.getType())) return; - broken.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,1,0.5).forEach(entity -> { - if (entity.getType().equals(EntityType.VILLAGER)) { - WrappedVillager wVillager = villagerManager.getOrAdd((Villager) entity); - if (wVillager.getOptimizationType().equals(OptimizationType.BLOCK)) { - wVillager.setOptimization(OptimizationType.OFF); - if (shouldNotifyPlayer) { - Player player = event.getPlayer(); - VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(line -> player.sendMessage(line - .replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(wVillager.villager().getProfession().toString().toLowerCase()).build()) - .replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(broken.getType().toString().toLowerCase()).build()) - )); - } - if (shouldLog) - VillagerOptimizer.getLog().info("Villager unoptimized because no longer standing on optimization block at "+wVillager.villager().getLocation()); + int counter = 0; + for (Entity entity : broken.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,1,0.5)) { + if (!entity.getType().equals(EntityType.VILLAGER)) continue; + + WrappedVillager wVillager = villagerManager.getOrAdd((Villager) entity); + + if (wVillager.getOptimizationType().equals(OptimizationType.BLOCK)) { + if (counter >= maxVillagers) return; + + wVillager.setOptimization(OptimizationType.OFF); + + if (shouldNotifyPlayer) { + Player player = event.getPlayer(); + VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(line -> player.sendMessage(line + .replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(wVillager.villager().getProfession().toString().toLowerCase()).build()) + .replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(broken.getType().toString().toLowerCase()).build()) + )); } + if (shouldLog) + VillagerOptimizer.getLog().info("Villager unoptimized because no longer standing on optimization block at "+wVillager.villager().getLocation()); } - }); + } } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @@ -123,27 +142,28 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener { config.blocks_that_disable.contains(entityLegs.getBlock().getType()) // check for blocks inside the entity's legs because of slabs and sink-in blocks || config.blocks_that_disable.contains(entityLegs.clone().subtract(0,1,0).getBlock().getType()) ) { - if (!wVillager.isOptimized()) { - if (wVillager.setOptimization(OptimizationType.BLOCK)) { - if (shouldNotifyPlayer) { - Player player = event.getPlayer(); - final String vilType = wVillager.villager().getProfession().toString().toLowerCase(); - final String blockType = entityLegs.getBlock().getType().toString().toLowerCase(); - VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(line -> player.sendMessage(line - .replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(vilType).build()) - .replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(blockType).build()) - )); - } - if (shouldLog) - VillagerOptimizer.getLog().info("Villager was optimized by block at "+wVillager.villager().getLocation()); - } else { - if (shouldNotifyPlayer) { - Player player = event.getPlayer(); - final long optimizeCoolDown = wVillager.getOptimizeCooldown(); - VillagerOptimizer.getLang(player.locale()).block_on_optimize_cooldown.forEach(line -> player.sendMessage(line - .replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(optimizeCoolDown)).build())) - ); - } + if (wVillager.isOptimized()) return; + if (wVillager.canOptimize(cooldown)) { + wVillager.setOptimization(OptimizationType.BLOCK); + wVillager.saveOptimizeTime(); + if (shouldNotifyPlayer) { + Player player = event.getPlayer(); + final String vilType = wVillager.villager().getProfession().toString().toLowerCase(); + final String blockType = entityLegs.getBlock().getType().toString().toLowerCase(); + VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(line -> player.sendMessage(line + .replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(vilType).build()) + .replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(blockType).build()) + )); + } + if (shouldLog) + VillagerOptimizer.getLog().info("Villager was optimized by block at "+wVillager.villager().getLocation()); + } else { + if (shouldNotifyPlayer) { + Player player = event.getPlayer(); + final long optimizeCoolDown = wVillager.getOptimizeCooldownMillis(cooldown); + VillagerOptimizer.getLang(player.locale()).block_on_optimize_cooldown.forEach(line -> player.sendMessage(line + .replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(optimizeCoolDown)).build())) + ); } } } else { diff --git a/src/main/java/me/xginko/villageroptimizer/modules/LevelVillagers.java b/src/main/java/me/xginko/villageroptimizer/modules/LevelVillagers.java index db293eb..c4c8dff 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/LevelVillagers.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/LevelVillagers.java @@ -2,10 +2,12 @@ package me.xginko.villageroptimizer.modules; import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.cache.VillagerManager; +import org.bukkit.entity.Villager; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryCloseEvent; public class LevelVillagers implements VillagerOptimizerModule, Listener { @@ -35,7 +37,9 @@ public class LevelVillagers implements VillagerOptimizerModule, Listener { } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - private void onSomething() { + private void onTradeInventoryClose(InventoryCloseEvent event) { + if (event.getInventory().getHolder() instanceof Villager villager) { + } } } diff --git a/src/main/java/me/xginko/villageroptimizer/modules/NametagOptimization.java b/src/main/java/me/xginko/villageroptimizer/modules/NametagOptimization.java index 8666e7a..1bf50d8 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/NametagOptimization.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/NametagOptimization.java @@ -25,6 +25,7 @@ public class NametagOptimization implements VillagerOptimizerModule, Listener { private final VillagerManager villagerManager; private final Config config; private final boolean shouldLog, shouldNotifyPlayer, consumeNametag; + private final long cooldown; protected NametagOptimization() { this.villagerManager = VillagerOptimizer.getVillagerManager(); @@ -33,7 +34,12 @@ public class NametagOptimization implements VillagerOptimizerModule, Listener { Enable optimization by naming villagers to one of the names configured below.\s Nametag optimized villagers will be unoptimized again when they are renamed to something else. """); - this.consumeNametag = config.getBoolean("optimization.methods.by-nametag.nametags-get-consumed", true); + this.consumeNametag = config.getBoolean("optimization.methods.by-nametag.nametags-get-consumed", true, + "Enable or disable consumption of the used nametag item."); + this.cooldown = config.getInt("optimization.methods.by-workstation.optimize-cooldown-seconds", 600, """ + Cooldown in seconds until a villager can be optimized again using a nametag. \s + Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior. + """) * 1000L; this.shouldLog = config.getBoolean("optimization.methods.by-nametag.log", false); this.shouldNotifyPlayer = config.getBoolean("optimization.methods.by-nametag.notify-player", true); } @@ -65,24 +71,26 @@ public class NametagOptimization implements VillagerOptimizerModule, Listener { Player player = event.getPlayer(); if (config.nametags.contains(nameTag.toLowerCase())) { - if (!wVillager.isOptimized()) { - if (wVillager.setOptimization(OptimizationType.NAMETAG)) { - if (!consumeNametag) { - ItemStack mainHand = player.getInventory().getItemInMainHand(); - ItemStack offHand = player.getInventory().getItemInOffHand(); - if (mainHand.getType().equals(Material.NAME_TAG)) mainHand.add(); - else if (offHand.getType().equals(Material.NAME_TAG)) offHand.add(); - } - if (shouldNotifyPlayer) - VillagerOptimizer.getLang(player.locale()).nametag_optimize_success.forEach(player::sendMessage); - if (shouldLog) - VillagerOptimizer.getLog().info(player.getName() + " optimized a villager using nametag: '" + nameTag + "'"); - } else { - event.setCancelled(true); - if (shouldNotifyPlayer) { - VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> player.sendMessage(line - .replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(wVillager.getOptimizeCooldown())).build()))); - } + if (wVillager.isOptimized()) return; + if (wVillager.canOptimize(cooldown)) { + wVillager.setOptimization(OptimizationType.NAMETAG); + wVillager.saveOptimizeTime(); + if (!consumeNametag) { + ItemStack mainHand = player.getInventory().getItemInMainHand(); + ItemStack offHand = player.getInventory().getItemInOffHand(); + if (mainHand.getType().equals(Material.NAME_TAG)) mainHand.add(); + else if (offHand.getType().equals(Material.NAME_TAG)) offHand.add(); + } + if (shouldNotifyPlayer) + VillagerOptimizer.getLang(player.locale()).nametag_optimize_success.forEach(player::sendMessage); + if (shouldLog) + VillagerOptimizer.getLog().info(player.getName() + " optimized a villager using nametag: '" + nameTag + "'"); + } else { + event.setCancelled(true); + if (shouldNotifyPlayer) { + final long optimizeCoolDown = wVillager.getOptimizeCooldownMillis(cooldown); + VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> player.sendMessage(line + .replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(optimizeCoolDown)).build()))); } } } else { diff --git a/src/main/java/me/xginko/villageroptimizer/modules/RestockTrades.java b/src/main/java/me/xginko/villageroptimizer/modules/RestockTrades.java index 4fa775c..be3d15a 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/RestockTrades.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/RestockTrades.java @@ -58,6 +58,7 @@ public class RestockTrades implements VillagerOptimizerModule, Listener { if (wVillager.canRestock(restock_delay)) { wVillager.restock(); + wVillager.saveRestockTime(); if (notifyPlayer) { Player player = event.getPlayer(); VillagerOptimizer.getLang(player.locale()).trades_restocked.forEach(line -> player.sendMessage(line diff --git a/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java b/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java index f568e2e..6b945f0 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java @@ -20,6 +20,8 @@ public interface VillagerOptimizerModule { modules.add(new WorkstationOptimization()); modules.add(new PreventVillagerDamage()); modules.add(new PreventVillagerTargetting()); + modules.add(new RestockTrades()); + modules.add(new LevelVillagers()); for (VillagerOptimizerModule module : modules) { if (module.shouldEnable()) module.enable(); diff --git a/src/main/java/me/xginko/villageroptimizer/modules/WorkstationOptimization.java b/src/main/java/me/xginko/villageroptimizer/modules/WorkstationOptimization.java index 7f6cbe1..3c1e058 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/WorkstationOptimization.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/WorkstationOptimization.java @@ -25,6 +25,7 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene private final VillagerManager villagerManager; private final Config config; private final boolean shouldLog, shouldNotifyPlayer; + private final long cooldown; private final double search_radius; protected WorkstationOptimization() { @@ -38,6 +39,10 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene The radius in blocks a villager can be away from the player when he places a workstation.\s The closest unoptimized villager to the player will be optimized. """); + this.cooldown = config.getInt("optimization.methods.by-workstation.optimize-cooldown-seconds", 600, """ + Cooldown in seconds until a villager can be optimized again using this method. \s + Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior. + """) * 1000L; this.shouldLog = config.getBoolean("optimization.methods.by-workstation.log", false); this.shouldNotifyPlayer = config.getBoolean("optimization.methods.by-workstation.notify-player", true); } @@ -81,7 +86,9 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene if (closest == null) return; - if (closest.setOptimization(OptimizationType.WORKSTATION)) { + if (closest.canOptimize(cooldown)) { + closest.setOptimization(OptimizationType.WORKSTATION); + closest.saveOptimizeTime(); if (shouldNotifyPlayer) { Player player = event.getPlayer(); final String vilType = closest.villager().getProfession().toString().toLowerCase(); @@ -96,7 +103,7 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene } else { if (shouldNotifyPlayer) { Player player = event.getPlayer(); - final long optimizeCoolDown = closest.getOptimizeCooldown(); + final long optimizeCoolDown = closest.getOptimizeCooldownMillis(cooldown); VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> player.sendMessage(line .replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(optimizeCoolDown)).build()) ));