unchain VillagerWrapper methods

rewrite some VillagerWrapper methods for better cooldown handling
enable per method cooldown
add javadoc for WrappedVillager
rename namespaced keys
This commit is contained in:
xGinko 2023-09-09 01:52:04 +02:00
parent 5610b5b929
commit 8b82d2994d
8 changed files with 221 additions and 122 deletions

View File

@ -5,10 +5,10 @@ import org.bukkit.NamespacedKey;
public enum Keys { public enum Keys {
OPTIMIZED(VillagerOptimizer.getKey("optimized")), OPTIMIZATION(VillagerOptimizer.getKey("optimization")),
COOLDOWN_OPTIMIZE(VillagerOptimizer.getKey("optimize-cooldown")), LAST_OPTIMIZE(VillagerOptimizer.getKey("last-optimize")),
COOLDOWN_EXPERIENCE(VillagerOptimizer.getKey("experience-cooldown")), LAST_LEVELUP(VillagerOptimizer.getKey("last-levelup")),
WORLDTIME(VillagerOptimizer.getKey("last-restock-time")); LAST_RESTOCK(VillagerOptimizer.getKey("last-restock"));
private final NamespacedKey key; private final NamespacedKey key;

View File

@ -1,6 +1,5 @@
package me.xginko.villageroptimizer.models; package me.xginko.villageroptimizer.models;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.enums.Keys; import me.xginko.villageroptimizer.enums.Keys;
import me.xginko.villageroptimizer.enums.OptimizationType; import me.xginko.villageroptimizer.enums.OptimizationType;
import org.bukkit.entity.Villager; import org.bukkit.entity.Villager;
@ -9,9 +8,7 @@ import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
public final class WrappedVillager { public final class WrappedVillager {
/*
* TODO: Refresh cache when information is read or written (but efficiently)
* */
private final @NotNull Villager villager; private final @NotNull Villager villager;
private final @NotNull PersistentDataContainer dataContainer; private final @NotNull PersistentDataContainer dataContainer;
@ -20,72 +17,132 @@ public final class WrappedVillager {
this.dataContainer = this.villager.getPersistentDataContainer(); this.dataContainer = this.villager.getPersistentDataContainer();
} }
/**
* @return The villager inside the wrapper.
*/
public @NotNull Villager villager() { public @NotNull Villager villager() {
return 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() { 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()) { if (type.equals(OptimizationType.OFF) && isOptimized()) {
dataContainer.remove(Keys.OPTIMIZED.key()); dataContainer.remove(Keys.OPTIMIZATION.key());
villager.setAware(true); villager.setAware(true);
villager.setAI(true); villager.setAI(true);
} else { } else {
if (isOnOptimizeCooldown()) return false; dataContainer.set(Keys.OPTIMIZATION.key(), PersistentDataType.STRING, type.name());
dataContainer.set(Keys.OPTIMIZED.key(), PersistentDataType.STRING, type.name());
villager.setAware(false); villager.setAware(false);
setOptimizeCooldown(VillagerOptimizer.getConfiguration().optimize_cooldown_millis);
} }
return true;
} }
/**
* @return The current OptimizationType of the villager.
*/
public @NotNull OptimizationType getOptimizationType() { 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(); * @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 void setExpCooldown(long milliseconds) { public long getOptimizeCooldownMillis(final long cooldown_millis) {
dataContainer.set(Keys.COOLDOWN_EXPERIENCE.key(), PersistentDataType.LONG, System.currentTimeMillis() + milliseconds); return dataContainer.has(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG) ? (System.currentTimeMillis() - (dataContainer.get(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG) + cooldown_millis)) : cooldown_millis;
}
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 True if the villager has been loaded long enough.
*/
public boolean canRestock(final long cooldown_millis) { public boolean canRestock(final long cooldown_millis) {
final long lastRestock = getRestockTimestamp(); return getLastRestock() + cooldown_millis <= villager.getWorld().getFullTime();
if (lastRestock == 0L) return true;
return lastRestock + cooldown_millis <= villager.getWorld().getFullTime();
} }
/**
* Restock all trading recipes.
*/
public void restock() { public void restock() {
villager.getRecipes().forEach(recipe -> recipe.setUses(0)); 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;
} }
} }

View File

@ -27,6 +27,8 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener {
private final VillagerManager villagerManager; private final VillagerManager villagerManager;
private final Config config; private final Config config;
private final boolean shouldLog, shouldNotifyPlayer; private final boolean shouldLog, shouldNotifyPlayer;
private final int maxVillagers;
private final long cooldown;
protected BlockOptimization() { protected BlockOptimization() {
this.villagerManager = VillagerOptimizer.getVillagerManager(); 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 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. 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.shouldLog = config.getBoolean("optimization.methods.by-specific-block.log", false);
this.shouldNotifyPlayer = config.getBoolean("optimization.methods.by-specific-block.notify-player", true); this.shouldNotifyPlayer = config.getBoolean("optimization.methods.by-specific-block.notify-player", true);
} }
@ -61,11 +69,18 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener {
Block placed = event.getBlock(); Block placed = event.getBlock();
if (!config.blocks_that_disable.contains(placed.getType())) return; if (!config.blocks_that_disable.contains(placed.getType())) return;
placed.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,1,0.5).forEach(entity -> { int counter = 0;
if (entity.getType().equals(EntityType.VILLAGER)) { 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); WrappedVillager wVillager = villagerManager.getOrAdd((Villager) entity);
if (!wVillager.isOptimized()) { if (wVillager.isOptimized()) continue;
if (wVillager.setOptimization(OptimizationType.BLOCK)) { if (counter >= maxVillagers) return;
if (wVillager.canOptimize(cooldown)) {
wVillager.setOptimization(OptimizationType.BLOCK);
wVillager.saveOptimizeTime();
counter++;
if (shouldNotifyPlayer) { if (shouldNotifyPlayer) {
Player player = event.getPlayer(); Player player = event.getPlayer();
VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(line -> player.sendMessage(line VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(line -> player.sendMessage(line
@ -78,25 +93,30 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener {
} else { } else {
if (shouldNotifyPlayer) { if (shouldNotifyPlayer) {
Player player = event.getPlayer(); Player player = event.getPlayer();
final long optimizeCoolDown = wVillager.getOptimizeCooldownMillis(cooldown);
VillagerOptimizer.getLang(player.locale()).block_on_optimize_cooldown.forEach(line -> player.sendMessage(line VillagerOptimizer.getLang(player.locale()).block_on_optimize_cooldown.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(wVillager.getOptimizeCooldown())).build()))); .replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(optimizeCoolDown)).build())));
} }
} }
} }
} }
});
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
private void onBlockBreak(BlockBreakEvent event) { private void onBlockBreak(BlockBreakEvent event) {
Block broken = event.getBlock(); Block broken = event.getBlock();
if (!config.blocks_that_disable.contains(broken.getType())) return; if (!config.blocks_that_disable.contains(broken.getType())) return;
broken.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,1,0.5).forEach(entity -> { int counter = 0;
if (entity.getType().equals(EntityType.VILLAGER)) { 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); WrappedVillager wVillager = villagerManager.getOrAdd((Villager) entity);
if (wVillager.getOptimizationType().equals(OptimizationType.BLOCK)) { if (wVillager.getOptimizationType().equals(OptimizationType.BLOCK)) {
if (counter >= maxVillagers) return;
wVillager.setOptimization(OptimizationType.OFF); wVillager.setOptimization(OptimizationType.OFF);
if (shouldNotifyPlayer) { if (shouldNotifyPlayer) {
Player player = event.getPlayer(); Player player = event.getPlayer();
VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(line -> player.sendMessage(line VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(line -> player.sendMessage(line
@ -108,7 +128,6 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener {
VillagerOptimizer.getLog().info("Villager unoptimized because no longer standing on optimization block at "+wVillager.villager().getLocation()); VillagerOptimizer.getLog().info("Villager unoptimized because no longer standing on optimization block at "+wVillager.villager().getLocation());
} }
} }
});
} }
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
@ -123,8 +142,10 @@ 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.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()) || config.blocks_that_disable.contains(entityLegs.clone().subtract(0,1,0).getBlock().getType())
) { ) {
if (!wVillager.isOptimized()) { if (wVillager.isOptimized()) return;
if (wVillager.setOptimization(OptimizationType.BLOCK)) { if (wVillager.canOptimize(cooldown)) {
wVillager.setOptimization(OptimizationType.BLOCK);
wVillager.saveOptimizeTime();
if (shouldNotifyPlayer) { if (shouldNotifyPlayer) {
Player player = event.getPlayer(); Player player = event.getPlayer();
final String vilType = wVillager.villager().getProfession().toString().toLowerCase(); final String vilType = wVillager.villager().getProfession().toString().toLowerCase();
@ -139,13 +160,12 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener {
} else { } else {
if (shouldNotifyPlayer) { if (shouldNotifyPlayer) {
Player player = event.getPlayer(); Player player = event.getPlayer();
final long optimizeCoolDown = wVillager.getOptimizeCooldown(); final long optimizeCoolDown = wVillager.getOptimizeCooldownMillis(cooldown);
VillagerOptimizer.getLang(player.locale()).block_on_optimize_cooldown.forEach(line -> player.sendMessage(line VillagerOptimizer.getLang(player.locale()).block_on_optimize_cooldown.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(optimizeCoolDown)).build())) .replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(optimizeCoolDown)).build()))
); );
} }
} }
}
} else { } else {
if (wVillager.getOptimizationType().equals(OptimizationType.BLOCK)) { if (wVillager.getOptimizationType().equals(OptimizationType.BLOCK)) {
wVillager.setOptimization(OptimizationType.OFF); wVillager.setOptimization(OptimizationType.OFF);

View File

@ -2,10 +2,12 @@ package me.xginko.villageroptimizer.modules;
import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.cache.VillagerManager; import me.xginko.villageroptimizer.cache.VillagerManager;
import org.bukkit.entity.Villager;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList; import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryCloseEvent;
public class LevelVillagers implements VillagerOptimizerModule, Listener { public class LevelVillagers implements VillagerOptimizerModule, Listener {
@ -35,7 +37,9 @@ public class LevelVillagers implements VillagerOptimizerModule, Listener {
} }
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
private void onSomething() { private void onTradeInventoryClose(InventoryCloseEvent event) {
if (event.getInventory().getHolder() instanceof Villager villager) {
} }
} }
}

View File

@ -25,6 +25,7 @@ public class NametagOptimization implements VillagerOptimizerModule, Listener {
private final VillagerManager villagerManager; private final VillagerManager villagerManager;
private final Config config; private final Config config;
private final boolean shouldLog, shouldNotifyPlayer, consumeNametag; private final boolean shouldLog, shouldNotifyPlayer, consumeNametag;
private final long cooldown;
protected NametagOptimization() { protected NametagOptimization() {
this.villagerManager = VillagerOptimizer.getVillagerManager(); 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 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. 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.shouldLog = config.getBoolean("optimization.methods.by-nametag.log", false);
this.shouldNotifyPlayer = config.getBoolean("optimization.methods.by-nametag.notify-player", true); this.shouldNotifyPlayer = config.getBoolean("optimization.methods.by-nametag.notify-player", true);
} }
@ -65,8 +71,10 @@ public class NametagOptimization implements VillagerOptimizerModule, Listener {
Player player = event.getPlayer(); Player player = event.getPlayer();
if (config.nametags.contains(nameTag.toLowerCase())) { if (config.nametags.contains(nameTag.toLowerCase())) {
if (!wVillager.isOptimized()) { if (wVillager.isOptimized()) return;
if (wVillager.setOptimization(OptimizationType.NAMETAG)) { if (wVillager.canOptimize(cooldown)) {
wVillager.setOptimization(OptimizationType.NAMETAG);
wVillager.saveOptimizeTime();
if (!consumeNametag) { if (!consumeNametag) {
ItemStack mainHand = player.getInventory().getItemInMainHand(); ItemStack mainHand = player.getInventory().getItemInMainHand();
ItemStack offHand = player.getInventory().getItemInOffHand(); ItemStack offHand = player.getInventory().getItemInOffHand();
@ -80,9 +88,9 @@ public class NametagOptimization implements VillagerOptimizerModule, Listener {
} else { } else {
event.setCancelled(true); event.setCancelled(true);
if (shouldNotifyPlayer) { if (shouldNotifyPlayer) {
final long optimizeCoolDown = wVillager.getOptimizeCooldownMillis(cooldown);
VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> player.sendMessage(line VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(wVillager.getOptimizeCooldown())).build()))); .replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(optimizeCoolDown)).build())));
}
} }
} }
} else { } else {

View File

@ -58,6 +58,7 @@ public class RestockTrades implements VillagerOptimizerModule, Listener {
if (wVillager.canRestock(restock_delay)) { if (wVillager.canRestock(restock_delay)) {
wVillager.restock(); wVillager.restock();
wVillager.saveRestockTime();
if (notifyPlayer) { if (notifyPlayer) {
Player player = event.getPlayer(); Player player = event.getPlayer();
VillagerOptimizer.getLang(player.locale()).trades_restocked.forEach(line -> player.sendMessage(line VillagerOptimizer.getLang(player.locale()).trades_restocked.forEach(line -> player.sendMessage(line

View File

@ -20,6 +20,8 @@ public interface VillagerOptimizerModule {
modules.add(new WorkstationOptimization()); modules.add(new WorkstationOptimization());
modules.add(new PreventVillagerDamage()); modules.add(new PreventVillagerDamage());
modules.add(new PreventVillagerTargetting()); modules.add(new PreventVillagerTargetting());
modules.add(new RestockTrades());
modules.add(new LevelVillagers());
for (VillagerOptimizerModule module : modules) { for (VillagerOptimizerModule module : modules) {
if (module.shouldEnable()) module.enable(); if (module.shouldEnable()) module.enable();

View File

@ -25,6 +25,7 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene
private final VillagerManager villagerManager; private final VillagerManager villagerManager;
private final Config config; private final Config config;
private final boolean shouldLog, shouldNotifyPlayer; private final boolean shouldLog, shouldNotifyPlayer;
private final long cooldown;
private final double search_radius; private final double search_radius;
protected WorkstationOptimization() { 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 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. 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.shouldLog = config.getBoolean("optimization.methods.by-workstation.log", false);
this.shouldNotifyPlayer = config.getBoolean("optimization.methods.by-workstation.notify-player", true); 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 == null) return;
if (closest.setOptimization(OptimizationType.WORKSTATION)) { if (closest.canOptimize(cooldown)) {
closest.setOptimization(OptimizationType.WORKSTATION);
closest.saveOptimizeTime();
if (shouldNotifyPlayer) { if (shouldNotifyPlayer) {
Player player = event.getPlayer(); Player player = event.getPlayer();
final String vilType = closest.villager().getProfession().toString().toLowerCase(); final String vilType = closest.villager().getProfession().toString().toLowerCase();
@ -96,7 +103,7 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene
} else { } else {
if (shouldNotifyPlayer) { if (shouldNotifyPlayer) {
Player player = event.getPlayer(); 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 VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(optimizeCoolDown)).build()) .replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(optimizeCoolDown)).build())
)); ));