a days progress

This commit is contained in:
xGinko 2023-09-10 00:44:03 +02:00
parent 8b82d2994d
commit 5d106640a3
13 changed files with 256 additions and 135 deletions

View File

@ -9,8 +9,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.time.Duration; import java.time.Duration;
import java.util.Collection;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
public class VillagerManager { public class VillagerManager {
@ -20,8 +20,8 @@ public class VillagerManager {
this.villagerCache = Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(expireAfterWriteSeconds)).build(); this.villagerCache = Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(expireAfterWriteSeconds)).build();
} }
public @NotNull Collection<WrappedVillager> getAll() { public @NotNull ConcurrentMap<UUID, WrappedVillager> cacheMap() {
return this.villagerCache.asMap().values(); return this.villagerCache.asMap();
} }
public @Nullable WrappedVillager get(@NotNull UUID uuid) { public @Nullable WrappedVillager get(@NotNull UUID uuid) {

View File

@ -3,11 +3,8 @@ package me.xginko.villageroptimizer.config;
import io.github.thatsmusic99.configurationmaster.api.ConfigFile; import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
import io.github.thatsmusic99.configurationmaster.api.ConfigSection; import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.utils.LogUtils;
import org.bukkit.Material;
import java.io.File; import java.io.File;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
@ -15,66 +12,21 @@ import java.util.Map;
public class Config { public class Config {
private final ConfigFile config; private final ConfigFile config;
public final Locale default_lang; public final Locale default_lang;
public final boolean auto_lang, enable_nametag_optimization, enable_workstation_optimization, enable_block_optimization; public final boolean auto_lang;
public final long cache_keep_time_seconds, optimize_cooldown_millis; public final long cache_keep_time_seconds;
public final HashSet<String> nametags = new HashSet<>(4);
public final HashSet<Material> blocks_that_disable = new HashSet<>(4);
public final HashSet<Material> workstations_that_disable = new HashSet<>(14);
public Config() throws Exception { public Config() throws Exception {
this.config = loadConfig(new File(VillagerOptimizer.getInstance().getDataFolder(), "config.yml")); this.config = loadConfig(new File(VillagerOptimizer.getInstance().getDataFolder(), "config.yml"));
structureConfig(); structureConfig();
/**
* General
*/
this.default_lang = Locale.forLanguageTag( this.default_lang = Locale.forLanguageTag(
getString("general.default-language", "en_us", getString("general.default-language", "en_us",
"The default language that will be used if auto-language is false or no matching language file was found.") "The default language that will be used if auto-language is false or no matching language file was found.")
.replace("_", "-")); .replace("_", "-"));
this.auto_lang = getBoolean("general.auto-language", true, "If set to true, will display messages based on client language"); this.auto_lang = getBoolean("general.auto-language", true,
this.cache_keep_time_seconds = getInt("general.cache-keep-time-seconds", 30, "The amount of time in seconds a villager will be kept in the plugin's cache."); "If set to true, will display messages based on client language");
/** this.cache_keep_time_seconds = getInt("general.cache-keep-time-seconds", 30,
* Optimization "The amount of time in seconds a villager will be kept in the plugin's cache.");
*/
this.optimize_cooldown_millis = getInt("optimization.state-change-cooldown-in-seconds", 600) * 1000L;
// Nametags
this.enable_nametag_optimization = getBoolean("optimization.methods.by-nametag.enable", true);
this.nametags.addAll(getList("optimization.methods.by-nametag.names", List.of("Optimize", "DisableAI"), "Names are case insensitive")
.stream().map(String::toLowerCase).toList());
// Workstations
this.enable_workstation_optimization = getBoolean("optimization.methods.by-workstation.enable", true, """
Optimize villagers that are standing near their acquired workstations /s
Values here need to be valid bukkit Material enums for your server version.
""");
this.getList("optimization.methods.by-workstation.workstation-materials", List.of(
"COMPOSTER", "SMOKER", "BARREL", "LOOM", "BLAST_FURNACE", "BREWING_STAND", "CAULDRON",
"FLETCHING_TABLE", "CARTOGRAPHY_TABLE", "LECTERN", "SMITHING_TABLE", "STONECUTTER", "GRINDSTONE"
)).forEach(configuredMaterial -> {
try {
Material disableBlock = Material.valueOf(configuredMaterial);
this.blocks_that_disable.add(disableBlock);
} catch (IllegalArgumentException e) {
LogUtils.materialNotRecognized("optimization.methods.by-workstation", configuredMaterial);
}
});
// Blocks
this.enable_block_optimization = getBoolean("optimization.methods.by-specific-block.enable", true, """
Optimize villagers that are standing on these specific block materials /s
Values here need to be valid bukkit Material enums for your server version.
""");
this.getList("optimization.methods.by-specific-block.materials", List.of(
"LAPIS_BLOCK", "GLOWSTONE", "IRON_BLOCK"
)).forEach(configuredMaterial -> {
try {
Material disableBlock = Material.valueOf(configuredMaterial);
this.blocks_that_disable.add(disableBlock);
} catch (IllegalArgumentException e) {
LogUtils.materialNotRecognized("optimization.methods.by-specific-block", configuredMaterial);
}
});
} }
private ConfigFile loadConfig(File ymlFile) throws Exception { private ConfigFile loadConfig(File ymlFile) throws Exception {

View File

@ -17,23 +17,52 @@ public class LanguageCache {
public final List<Component> nametag_optimize_success, nametag_on_optimize_cooldown, nametag_unoptimize_success, public final List<Component> nametag_optimize_success, nametag_on_optimize_cooldown, nametag_unoptimize_success,
block_optimize_success, block_on_optimize_cooldown, block_unoptimize_success, block_optimize_success, block_on_optimize_cooldown, block_unoptimize_success,
workstation_optimize_success, workstation_on_optimize_cooldown, workstation_unoptimize_success, workstation_optimize_success, workstation_on_optimize_cooldown, workstation_unoptimize_success,
trades_restocked; command_optimize_success, command_radius_limit_exceed, command_optimize_fail, command_unoptimize_success,
trades_restocked, optimize_for_trading, villager_leveling_up;
public LanguageCache(String lang) throws Exception { public LanguageCache(String lang) throws Exception {
this.lang = loadLang(new File(VillagerOptimizer.getInstance().getDataFolder() + File.separator + "lang", lang + ".yml")); this.lang = loadLang(new File(VillagerOptimizer.getInstance().getDataFolder() + File.separator + "lang", lang + ".yml"));
this.miniMessage = MiniMessage.miniMessage(); this.miniMessage = MiniMessage.miniMessage();
this.no_permission = getTranslation("messages.no-permission", "<red>You don't have permission to use this command."); // General
this.trades_restocked = getListTranslation("messages.trade-restock.success", List.of("<green>All trades restocked!")); this.no_permission = getTranslation("messages.no-permission",
this.nametag_optimize_success = getListTranslation("messages.nametag.optimize-success", List.of("<green>Successfully optimized villager by using a nametag.")); "<red>You don't have permission to use this command.");
this.nametag_on_optimize_cooldown = getListTranslation("messages.nametag.optimize-on-cooldown", List.of("<gray>You need to wait %time% until you can optimize this villager again.")); this.trades_restocked = getListTranslation("messages.trades-restocked",
this.nametag_unoptimize_success = getListTranslation("messages.nametag.unoptimize-success", List.of("<green>Successfully unoptimized villager by using a nametag.")); List.of("<green>All trades have been restocked! Next restock in %time%"));
this.block_optimize_success = getListTranslation("messages.block.optimize-success", List.of("<green>%villagertype% villager successfully optimized using block %blocktype%.")); this.optimize_for_trading = getListTranslation("messages.optimize-to-trade",
this.block_on_optimize_cooldown = getListTranslation("messages.block.optimize-on-cooldown", List.of("<gray>You need to wait %time% until you can optimize this villager again.")); List.of("<red>You need to optimize this villager before you can trade with it."));
this.block_unoptimize_success = getListTranslation("messages.block.unoptimize-success", List.of("<green>Successfully unoptimized %villagertype% villager by removing %blocktype%.")); this.villager_leveling_up = getListTranslation("messages.villager-leveling-up",
this.workstation_optimize_success = getListTranslation("messages.workstation.optimize-success", List.of("<green>%villagertype% villager successfully optimized using workstation %workstation%.")); List.of("<yellow>Villager is currently leveling up! You can use the villager again in %time%."));
this.workstation_on_optimize_cooldown = getListTranslation("messages.workstation.optimize-on-cooldown", List.of("<gray>You need to wait %time% until you can optimize this villager again.")); // Nametag
this.workstation_unoptimize_success = getListTranslation("messages.workstation.unoptimize-success", List.of("<green>Successfully unoptimized %villagertype% villager by removing workstation block %workstation%.")); this.nametag_optimize_success = getListTranslation("messages.nametag.optimize-success",
List.of("<green>Successfully optimized villager by using a nametag."));
this.nametag_on_optimize_cooldown = getListTranslation("messages.nametag.optimize-on-cooldown",
List.of("<gray>You need to wait %time% until you can optimize this villager again."));
this.nametag_unoptimize_success = getListTranslation("messages.nametag.unoptimize-success",
List.of("<green>Successfully unoptimized villager by using a nametag."));
// Block
this.block_optimize_success = getListTranslation("messages.block.optimize-success",
List.of("<green>%villagertype% villager successfully optimized using block %blocktype%."));
this.block_on_optimize_cooldown = getListTranslation("messages.block.optimize-on-cooldown",
List.of("<gray>You need to wait %time% until you can optimize this villager again."));
this.block_unoptimize_success = getListTranslation("messages.block.unoptimize-success",
List.of("<green>Successfully unoptimized %villagertype% villager by removing %blocktype%."));
// Workstation
this.workstation_optimize_success = getListTranslation("messages.workstation.optimize-success",
List.of("<green>%villagertype% villager successfully optimized using workstation %workstation%."));
this.workstation_on_optimize_cooldown = getListTranslation("messages.workstation.optimize-on-cooldown",
List.of("<gray>You need to wait %time% until you can optimize this villager again."));
this.workstation_unoptimize_success = getListTranslation("messages.workstation.unoptimize-success",
List.of("<green>Successfully unoptimized %villagertype% villager by removing workstation block %workstation%."));
// Command
this.command_optimize_success = getListTranslation("messages.workstation.optimize-success",
List.of("<green>Successfully optimized %amount% villager(s) in a radius of %radius% blocks."));
this.command_radius_limit_exceed = getListTranslation("messages.workstation.radius-limit-exceed",
List.of("<red>The radius you entered exceeds the limit of %distance% blocks."));
this.command_optimize_fail = getListTranslation("messages.workstation.optimize-fail",
List.of("<gray>%amount% villagers couldn't be optimized because they have recently been optimized."));
this.command_unoptimize_success = getListTranslation("messages.workstation.unoptimize-success",
List.of("<green>Successfully unoptimized %amount% villager(s) in a radius of %radius% blocks."));
saveLang(); saveLang();
} }

View File

@ -5,7 +5,7 @@ import org.bukkit.NamespacedKey;
public enum Keys { public enum Keys {
OPTIMIZATION(VillagerOptimizer.getKey("optimization")), OPTIMIZATION_TYPE(VillagerOptimizer.getKey("optimization-type")),
LAST_OPTIMIZE(VillagerOptimizer.getKey("last-optimize")), LAST_OPTIMIZE(VillagerOptimizer.getKey("last-optimize")),
LAST_LEVELUP(VillagerOptimizer.getKey("last-levelup")), LAST_LEVELUP(VillagerOptimizer.getKey("last-levelup")),
LAST_RESTOCK(VillagerOptimizer.getKey("last-restock")); LAST_RESTOCK(VillagerOptimizer.getKey("last-restock"));

View File

@ -28,7 +28,7 @@ public final class WrappedVillager {
* @return True if the villager is optimized by the plugin, otherwise false. * @return True if the villager is optimized by the plugin, otherwise false.
*/ */
public boolean isOptimized() { public boolean isOptimized() {
return dataContainer.has(Keys.OPTIMIZATION.key()); return dataContainer.has(Keys.OPTIMIZATION_TYPE.key());
} }
/** /**
@ -44,11 +44,11 @@ public final class WrappedVillager {
*/ */
public void setOptimization(OptimizationType type) { public void setOptimization(OptimizationType type) {
if (type.equals(OptimizationType.OFF) && isOptimized()) { if (type.equals(OptimizationType.OFF) && isOptimized()) {
dataContainer.remove(Keys.OPTIMIZATION.key()); dataContainer.remove(Keys.OPTIMIZATION_TYPE.key());
villager.setAware(true); villager.setAware(true);
villager.setAI(true); villager.setAI(true);
} else { } else {
dataContainer.set(Keys.OPTIMIZATION.key(), PersistentDataType.STRING, type.name()); dataContainer.set(Keys.OPTIMIZATION_TYPE.key(), PersistentDataType.STRING, type.name());
villager.setAware(false); villager.setAware(false);
} }
} }
@ -57,7 +57,7 @@ public final class WrappedVillager {
* @return The current OptimizationType of the villager. * @return The current OptimizationType of the villager.
*/ */
public @NotNull OptimizationType getOptimizationType() { public @NotNull OptimizationType getOptimizationType() {
return isOptimized() ? OptimizationType.valueOf(dataContainer.get(Keys.OPTIMIZATION.key(), PersistentDataType.STRING)) : OptimizationType.OFF; return isOptimized() ? OptimizationType.valueOf(dataContainer.get(Keys.OPTIMIZATION_TYPE.key(), PersistentDataType.STRING)) : OptimizationType.OFF;
} }
/** /**
@ -75,6 +75,10 @@ public final class WrappedVillager {
} }
/** /**
* Here for convenience so the remaining millis since the last stored optimize time
* can be easily calculated.
* This enables new configured cooldowns to instantly apply instead of them being persistent.
*
* @param cooldown_millis The configured cooldown in milliseconds you want to check against. * @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. * @return The time left in millis until the villager can be optimized again.
*/ */
@ -83,6 +87,9 @@ public final class WrappedVillager {
} }
/** /**
* Here for convenience so the remaining millis since the last stored restock time
* can be easily calculated.
*
* @param cooldown_millis The configured cooldown in milliseconds you want to check against. * @param cooldown_millis The configured cooldown in milliseconds you want to check against.
* @return True if the villager has been loaded long enough. * @return True if the villager has been loaded long enough.
*/ */
@ -140,6 +147,9 @@ public final class WrappedVillager {
} }
/** /**
* Here for convenience so the remaining millis since the last stored level-up time
* can be easily calculated.
*
* @return The time of the in-game world when the entity was last leveled up. * @return The time of the in-game world when the entity was last leveled up.
*/ */
public long getLastLevelUpTime() { public long getLastLevelUpTime() {

View File

@ -6,8 +6,10 @@ import me.xginko.villageroptimizer.enums.OptimizationType;
import me.xginko.villageroptimizer.cache.VillagerManager; import me.xginko.villageroptimizer.cache.VillagerManager;
import me.xginko.villageroptimizer.models.WrappedVillager; import me.xginko.villageroptimizer.models.WrappedVillager;
import me.xginko.villageroptimizer.utils.CommonUtils; import me.xginko.villageroptimizer.utils.CommonUtils;
import me.xginko.villageroptimizer.utils.LogUtils;
import net.kyori.adventure.text.TextReplacementConfig; import net.kyori.adventure.text.TextReplacementConfig;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.BlockFace; import org.bukkit.block.BlockFace;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
@ -22,30 +24,44 @@ import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.event.player.PlayerInteractEntityEvent;
import java.util.HashSet;
import java.util.List;
public class BlockOptimization implements VillagerOptimizerModule, Listener { public class BlockOptimization implements VillagerOptimizerModule, Listener {
private final VillagerManager villagerManager; private final VillagerManager villagerManager;
private final Config config; private final HashSet<Material> blocks_that_disable = new HashSet<>(4);
private final boolean shouldLog, shouldNotifyPlayer; private final boolean shouldLog, shouldNotifyPlayer;
private final int maxVillagers; private final int maxVillagers;
private final long cooldown; private final long cooldown;
protected BlockOptimization() { protected BlockOptimization() {
this.villagerManager = VillagerOptimizer.getVillagerManager(); this.villagerManager = VillagerOptimizer.getVillagerManager();
this.config = VillagerOptimizer.getConfiguration(); Config config = VillagerOptimizer.getConfiguration();
this.config.addComment("optimization.methods.by-specific-block.enable", """ config.addComment("optimization.methods.by-specific-block.enable", """
When enabled, villagers standing on the configured specific blocks will become optimized once a\s When enabled, villagers standing on the configured specific blocks will become optimized once a\s
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.
"""); """);
config.getList("optimization.methods.by-specific-block.materials", List.of(
"LAPIS_BLOCK", "GLOWSTONE", "IRON_BLOCK"
), "Values here need to be valid bukkit Material enums for your server version."
).forEach(configuredMaterial -> {
try {
Material disableBlock = Material.valueOf(configuredMaterial);
this.blocks_that_disable.add(disableBlock);
} catch (IllegalArgumentException e) {
LogUtils.materialNotRecognized("optimization.methods.by-specific-block", configuredMaterial);
}
});
this.cooldown = config.getInt("optimization.methods.by-specific-block.optimize-cooldown-seconds", 600, """ 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 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. Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.
""") * 1000L; """) * 1000L;
this.maxVillagers = config.getInt("optimization.methods.by-specific-block.max-villagers-per-block", 3, 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."); "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); this.shouldNotifyPlayer = config.getBoolean("optimization.methods.by-specific-block.notify-player", true);
this.shouldLog = config.getBoolean("optimization.methods.by-specific-block.log", false);
} }
@Override @Override
@ -61,13 +77,13 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener {
@Override @Override
public boolean shouldEnable() { public boolean shouldEnable() {
return config.enable_block_optimization; return VillagerOptimizer.getConfiguration().getBoolean("optimization.methods.by-specific-block.enable", true);
} }
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
private void onBlockPlace(BlockPlaceEvent event) { private void onBlockPlace(BlockPlaceEvent event) {
Block placed = event.getBlock(); Block placed = event.getBlock();
if (!config.blocks_that_disable.contains(placed.getType())) return; if (!blocks_that_disable.contains(placed.getType())) return;
int counter = 0; int counter = 0;
for (Entity entity : placed.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,1,0.5)) { for (Entity entity : placed.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,1,0.5)) {
@ -91,6 +107,7 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener {
if (shouldLog) if (shouldLog)
VillagerOptimizer.getLog().info("Villager was optimized by block at "+wVillager.villager().getLocation()); VillagerOptimizer.getLog().info("Villager was optimized by block at "+wVillager.villager().getLocation());
} else { } else {
wVillager.villager().shakeHead();
if (shouldNotifyPlayer) { if (shouldNotifyPlayer) {
Player player = event.getPlayer(); Player player = event.getPlayer();
final long optimizeCoolDown = wVillager.getOptimizeCooldownMillis(cooldown); final long optimizeCoolDown = wVillager.getOptimizeCooldownMillis(cooldown);
@ -104,7 +121,7 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener {
@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 (!blocks_that_disable.contains(broken.getType())) return;
int counter = 0; int counter = 0;
for (Entity entity : broken.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,1,0.5)) { for (Entity entity : broken.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,1,0.5)) {
@ -139,8 +156,8 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener {
final Location entityLegs = interacted.getLocation(); final Location entityLegs = interacted.getLocation();
if ( if (
config.blocks_that_disable.contains(entityLegs.getBlock().getType()) // check for blocks inside the entity's legs because of slabs and sink-in blocks 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()) || blocks_that_disable.contains(entityLegs.clone().subtract(0,1,0).getBlock().getType())
) { ) {
if (wVillager.isOptimized()) return; if (wVillager.isOptimized()) return;
if (wVillager.canOptimize(cooldown)) { if (wVillager.canOptimize(cooldown)) {
@ -158,6 +175,7 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener {
if (shouldLog) if (shouldLog)
VillagerOptimizer.getLog().info("Villager was optimized by block at "+wVillager.villager().getLocation()); VillagerOptimizer.getLog().info("Villager was optimized by block at "+wVillager.villager().getLocation());
} else { } else {
wVillager.villager().shakeHead();
if (shouldNotifyPlayer) { if (shouldNotifyPlayer) {
Player player = event.getPlayer(); Player player = event.getPlayer();
final long optimizeCoolDown = wVillager.getOptimizeCooldownMillis(cooldown); final long optimizeCoolDown = wVillager.getOptimizeCooldownMillis(cooldown);

View File

@ -2,22 +2,34 @@ 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 me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.models.WrappedVillager;
import org.bukkit.entity.Villager; 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; import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryType;
public class LevelVillagers implements VillagerOptimizerModule, Listener { public class LevelVillagers implements VillagerOptimizerModule, Listener {
private final VillagerOptimizer plugin; private final VillagerOptimizer plugin;
private final VillagerManager villagerManager; private final VillagerManager villagerManager;
private final long cooldown;
public LevelVillagers() { public LevelVillagers() {
this.plugin = VillagerOptimizer.getInstance(); this.plugin = VillagerOptimizer.getInstance();
this.villagerManager = VillagerOptimizer.getVillagerManager(); this.villagerManager = VillagerOptimizer.getVillagerManager();
Config config = VillagerOptimizer.getConfiguration();
config.addComment("optimization.villager-leveling.enable", """
This is needed to allow optimized villagers to level up. s\
Temporarily enables the villagers AI to allow it to level up and then disables it again.
""");
this.cooldown = config.getInt("optimization.villager-leveling.level-check-cooldown-seconds", 5, """
Cooldown in seconds the level of a villager will be checked and updated again. \s
Recommended to leave as is.
""") * 1000L;
} }
@Override @Override
@ -33,13 +45,19 @@ public class LevelVillagers implements VillagerOptimizerModule, Listener {
@Override @Override
public boolean shouldEnable() { public boolean shouldEnable() {
return false; return VillagerOptimizer.getConfiguration().getBoolean("optimization.villager-leveling.enable", true);
} }
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
private void onTradeInventoryClose(InventoryCloseEvent event) { private void onTradeScreenClose(InventoryCloseEvent event) {
if (event.getInventory().getHolder() instanceof Villager villager) { if (
event.getInventory().getType().equals(InventoryType.MERCHANT)
&& event.getInventory().getHolder() instanceof Villager villager
) {
WrappedVillager wVillager = villagerManager.getOrAdd(villager);
if (!wVillager.isOptimized()) return;
// logic missing
} }
} }
} }

View File

@ -20,28 +20,33 @@ import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import java.util.HashSet;
import java.util.List;
public class NametagOptimization implements VillagerOptimizerModule, Listener { public class NametagOptimization implements VillagerOptimizerModule, Listener {
private final VillagerManager villagerManager; private final VillagerManager villagerManager;
private final Config config; private final HashSet<String> nametags = new HashSet<>(4);
private final boolean shouldLog, shouldNotifyPlayer, consumeNametag; private final boolean shouldLog, shouldNotifyPlayer, consumeNametag;
private final long cooldown; private final long cooldown;
protected NametagOptimization() { protected NametagOptimization() {
this.villagerManager = VillagerOptimizer.getVillagerManager(); this.villagerManager = VillagerOptimizer.getVillagerManager();
this.config = VillagerOptimizer.getConfiguration(); Config config = VillagerOptimizer.getConfiguration();
this.config.addComment("optimization.methods.by-nametag.enable", """ config.addComment("optimization.methods.by-nametag.enable", """
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.nametags.addAll(config.getList("optimization.methods.by-nametag.names", List.of("Optimize", "DisableAI"),
"Names are case insensitive, capital letters won't matter.").stream().map(String::toLowerCase).toList());
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."); "Enable or disable consumption of the used nametag item.");
this.cooldown = config.getInt("optimization.methods.by-workstation.optimize-cooldown-seconds", 600, """ 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 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. Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.
""") * 1000L; """) * 1000L;
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);
this.shouldLog = config.getBoolean("optimization.methods.by-nametag.log", false);
} }
@Override @Override
@ -57,7 +62,7 @@ public class NametagOptimization implements VillagerOptimizerModule, Listener {
@Override @Override
public boolean shouldEnable() { public boolean shouldEnable() {
return config.enable_nametag_optimization; return VillagerOptimizer.getConfiguration().getBoolean("optimization.methods.by-nametag.enable", true);
} }
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
@ -70,7 +75,7 @@ public class NametagOptimization implements VillagerOptimizerModule, Listener {
WrappedVillager wVillager = villagerManager.getOrAdd((Villager) event.getEntity()); WrappedVillager wVillager = villagerManager.getOrAdd((Villager) event.getEntity());
Player player = event.getPlayer(); Player player = event.getPlayer();
if (config.nametags.contains(nameTag.toLowerCase())) { if (nametags.contains(nameTag.toLowerCase())) {
if (wVillager.isOptimized()) return; if (wVillager.isOptimized()) return;
if (wVillager.canOptimize(cooldown)) { if (wVillager.canOptimize(cooldown)) {
wVillager.setOptimization(OptimizationType.NAMETAG); wVillager.setOptimization(OptimizationType.NAMETAG);
@ -87,6 +92,7 @@ public class NametagOptimization implements VillagerOptimizerModule, Listener {
VillagerOptimizer.getLog().info(player.getName() + " optimized a villager using nametag: '" + nameTag + "'"); VillagerOptimizer.getLog().info(player.getName() + " optimized a villager using nametag: '" + nameTag + "'");
} else { } else {
event.setCancelled(true); event.setCancelled(true);
wVillager.villager().shakeHead();
if (shouldNotifyPlayer) { if (shouldNotifyPlayer) {
final long optimizeCoolDown = wVillager.getOptimizeCooldownMillis(cooldown); 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

View File

@ -0,0 +1,60 @@
package me.xginko.villageroptimizer.modules;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.cache.VillagerManager;
import me.xginko.villageroptimizer.config.Config;
import org.bukkit.entity.Player;
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.InventoryType;
import org.bukkit.event.inventory.TradeSelectEvent;
public class PreventUnoptimizedTrading implements VillagerOptimizerModule, Listener {
private final VillagerManager villagerManager;
private final boolean notifyPlayer;
protected PreventUnoptimizedTrading() {
this.villagerManager = VillagerOptimizer.getVillagerManager();
Config config = VillagerOptimizer.getConfiguration();
config.addComment("optimization.prevent-trading-with-unoptimized-villagers.enable", """
Will prevent players from selecting and using trades of unoptimized villagers. s\
Use this if you have a lot of villagers and therefore want to force your players to optimize them. s\
Inventories can still be opened so players can move villagers around.
""");
this.notifyPlayer = config.getBoolean("optimization.prevent-trading-with-unoptimized-villagers.notify-player", true);
}
@Override
public void enable() {
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
@Override
public void disable() {
HandlerList.unregisterAll(this);
}
@Override
public boolean shouldEnable() {
return VillagerOptimizer.getConfiguration().getBoolean("optimization.prevent-trading-with-unoptimized-villagers.enable", false);
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
private void onTradeOpen(TradeSelectEvent event) {
if (
event.getInventory().getType().equals(InventoryType.MERCHANT)
&& event.getInventory().getHolder() instanceof Villager villager
&& !villagerManager.getOrAdd(villager).isOptimized()
) {
event.setCancelled(true);
if (!notifyPlayer) return;
Player player = (Player) event.getWhoClicked();
VillagerOptimizer.getLang(player.locale()).optimize_for_trading.forEach(player::sendMessage);
}
}
}

View File

@ -18,7 +18,7 @@ import org.bukkit.event.player.PlayerInteractEntityEvent;
public class RestockTrades implements VillagerOptimizerModule, Listener { public class RestockTrades implements VillagerOptimizerModule, Listener {
private final VillagerManager villagerManager; private final VillagerManager villagerManager;
private final long restock_delay; private final long restock_delay_millis;
private final boolean shouldLog, notifyPlayer; private final boolean shouldLog, notifyPlayer;
protected RestockTrades() { protected RestockTrades() {
@ -28,7 +28,8 @@ public class RestockTrades implements VillagerOptimizerModule, Listener {
This is for automatic restocking of trades for optimized villagers. Optimized Villagers\s This is for automatic restocking of trades for optimized villagers. Optimized Villagers\s
Don't have enough AI to do trade restocks themselves, so this needs to always be enabled. Don't have enough AI to do trade restocks themselves, so this needs to always be enabled.
"""); """);
this.restock_delay = config.getInt("optimization.trade-restocking.delay-in-ticks", 1200) * 50L; this.restock_delay_millis = config.getInt("optimization.trade-restocking.delay-in-ticks", 1000,
"1 second = 20 ticks. There are 24.000 ticks in a single minecraft day.") * 50L;
this.shouldLog = config.getBoolean("optimization.trade-restocking.log", false); this.shouldLog = config.getBoolean("optimization.trade-restocking.log", false);
this.notifyPlayer = config.getBoolean("optimization.trade-restocking.notify-player", true, this.notifyPlayer = config.getBoolean("optimization.trade-restocking.notify-player", true,
"Sends the player a message when the trades were restocked on a clicked villager."); "Sends the player a message when the trades were restocked on a clicked villager.");
@ -53,16 +54,16 @@ public class RestockTrades implements VillagerOptimizerModule, Listener {
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
private void onInteract(PlayerInteractEntityEvent event) { private void onInteract(PlayerInteractEntityEvent event) {
if (!event.getRightClicked().getType().equals(EntityType.VILLAGER)) return; if (!event.getRightClicked().getType().equals(EntityType.VILLAGER)) return;
WrappedVillager wVillager = villagerManager.getOrAdd((Villager) event.getRightClicked());
if (!wVillager.isOptimized()) return;
if (wVillager.canRestock(restock_delay)) { WrappedVillager wVillager = villagerManager.getOrAdd((Villager) event.getRightClicked());
if (wVillager.isOptimized() && wVillager.canRestock(restock_delay_millis)) {
wVillager.restock(); wVillager.restock();
wVillager.saveRestockTime(); 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
.replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(restock_delay)).build())) .replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(restock_delay_millis)).build()))
); );
} }
if (shouldLog) if (shouldLog)

View File

@ -14,17 +14,18 @@ public interface VillagerOptimizerModule {
modules.forEach(VillagerOptimizerModule::disable); modules.forEach(VillagerOptimizerModule::disable);
modules.clear(); modules.clear();
modules.add(new VillagerChunkLimit());
modules.add(new NametagOptimization());
modules.add(new BlockOptimization()); modules.add(new BlockOptimization());
modules.add(new WorkstationOptimization()); modules.add(new LevelVillagers());
modules.add(new NametagOptimization());
modules.add(new PreventUnoptimizedTrading());
modules.add(new PreventVillagerDamage()); modules.add(new PreventVillagerDamage());
modules.add(new PreventVillagerTargetting()); modules.add(new PreventVillagerTargetting());
modules.add(new RestockTrades()); modules.add(new RestockTrades());
modules.add(new LevelVillagers()); modules.add(new VillagerChunkLimit());
modules.add(new WorkstationOptimization());
for (VillagerOptimizerModule module : modules) { modules.forEach(module -> {
if (module.shouldEnable()) module.enable(); if (module.shouldEnable()) module.enable();
} });
} }
} }

View File

@ -6,8 +6,10 @@ import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.enums.OptimizationType; import me.xginko.villageroptimizer.enums.OptimizationType;
import me.xginko.villageroptimizer.models.WrappedVillager; import me.xginko.villageroptimizer.models.WrappedVillager;
import me.xginko.villageroptimizer.utils.CommonUtils; import me.xginko.villageroptimizer.utils.CommonUtils;
import me.xginko.villageroptimizer.utils.LogUtils;
import net.kyori.adventure.text.TextReplacementConfig; import net.kyori.adventure.text.TextReplacementConfig;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
@ -20,21 +22,36 @@ import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.block.BlockPlaceEvent;
import java.util.HashSet;
import java.util.List;
public class WorkstationOptimization implements VillagerOptimizerModule, Listener { public class WorkstationOptimization implements VillagerOptimizerModule, Listener {
private final VillagerManager villagerManager; private final VillagerManager villagerManager;
private final Config config; private final HashSet<Material> workstations_that_disable = new HashSet<>(14);
private final boolean shouldLog, shouldNotifyPlayer; private final boolean shouldLog, shouldNotifyPlayer;
private final long cooldown; private final long cooldown;
private final double search_radius; private final double search_radius;
protected WorkstationOptimization() { protected WorkstationOptimization() {
this.villagerManager = VillagerOptimizer.getVillagerManager(); this.villagerManager = VillagerOptimizer.getVillagerManager();
this.config = VillagerOptimizer.getConfiguration(); Config config = VillagerOptimizer.getConfiguration();
this.config.addComment("optimization.methods.by-workstation.enable", """ config.addComment("optimization.methods.by-workstation.enable", """
When enabled, villagers near a configured radius to a workstation specific to your config\s When enabled, villagers near a configured radius to a workstation specific to your config\s
will be optimized. will be optimized.
"""); """);
config.getList("optimization.methods.by-workstation.workstation-materials", List.of(
"COMPOSTER", "SMOKER", "BARREL", "LOOM", "BLAST_FURNACE", "BREWING_STAND", "CAULDRON",
"FLETCHING_TABLE", "CARTOGRAPHY_TABLE", "LECTERN", "SMITHING_TABLE", "STONECUTTER", "GRINDSTONE"
), "Values here need to be valid bukkit Material enums for your server version."
).forEach(configuredMaterial -> {
try {
Material disableBlock = Material.valueOf(configuredMaterial);
this.workstations_that_disable.add(disableBlock);
} catch (IllegalArgumentException e) {
LogUtils.materialNotRecognized("optimization.methods.by-workstation", configuredMaterial);
}
});
this.search_radius = config.getDouble("optimization.methods.by-workstation.search-radius-in-blocks", 4.0, """ this.search_radius = config.getDouble("optimization.methods.by-workstation.search-radius-in-blocks", 4.0, """
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.
@ -43,8 +60,8 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene
Cooldown in seconds until a villager can be optimized again using this method. \s 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. Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.
""") * 1000L; """) * 1000L;
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);
this.shouldLog = config.getBoolean("optimization.methods.by-workstation.log", false);
} }
@Override @Override
@ -60,16 +77,16 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene
@Override @Override
public boolean shouldEnable() { public boolean shouldEnable() {
return config.enable_workstation_optimization; return VillagerOptimizer.getConfiguration().getBoolean("optimization.methods.by-workstation.enable", true);
} }
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onBlockPlace(BlockPlaceEvent event) { private void onBlockPlace(BlockPlaceEvent event) {
Block placed = event.getBlock(); Block placed = event.getBlock();
if (!config.workstations_that_disable.contains(placed.getType())) return; if (!workstations_that_disable.contains(placed.getType())) return;
final Location workstationLoc = placed.getLocation(); final Location workstationLoc = placed.getLocation();
WrappedVillager closest = null; WrappedVillager closestOptimizableVillager = null;
double closestDistance = Double.MAX_VALUE; double closestDistance = Double.MAX_VALUE;
for (Entity entity : workstationLoc.getNearbyEntities(search_radius, search_radius, search_radius)) { for (Entity entity : workstationLoc.getNearbyEntities(search_radius, search_radius, search_radius)) {
@ -80,18 +97,18 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene
WrappedVillager wVillager = villagerManager.getOrAdd(villager); WrappedVillager wVillager = villagerManager.getOrAdd(villager);
if (!wVillager.isOptimized() && entity.getLocation().distance(workstationLoc) < closestDistance) { if (!wVillager.isOptimized() && entity.getLocation().distance(workstationLoc) < closestDistance) {
closest = wVillager; closestOptimizableVillager = wVillager;
} }
} }
if (closest == null) return; if (closestOptimizableVillager == null) return;
if (closest.canOptimize(cooldown)) { if (closestOptimizableVillager.canOptimize(cooldown)) {
closest.setOptimization(OptimizationType.WORKSTATION); closestOptimizableVillager.setOptimization(OptimizationType.WORKSTATION);
closest.saveOptimizeTime(); closestOptimizableVillager.saveOptimizeTime();
if (shouldNotifyPlayer) { if (shouldNotifyPlayer) {
Player player = event.getPlayer(); Player player = event.getPlayer();
final String vilType = closest.villager().getProfession().toString().toLowerCase(); final String vilType = closestOptimizableVillager.villager().getProfession().toString().toLowerCase();
final String workstation = placed.getType().toString().toLowerCase(); final String workstation = placed.getType().toString().toLowerCase();
VillagerOptimizer.getLang(player.locale()).workstation_unoptimize_success.forEach(line -> player.sendMessage(line VillagerOptimizer.getLang(player.locale()).workstation_unoptimize_success.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(vilType).build()) .replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(vilType).build())
@ -101,9 +118,10 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene
if (shouldLog) if (shouldLog)
VillagerOptimizer.getLog().info(event.getPlayer().getName() + " optimized a villager using workstation: '" + placed.getType().toString().toLowerCase() + "'"); VillagerOptimizer.getLog().info(event.getPlayer().getName() + " optimized a villager using workstation: '" + placed.getType().toString().toLowerCase() + "'");
} else { } else {
closestOptimizableVillager.villager().shakeHead();
if (shouldNotifyPlayer) { if (shouldNotifyPlayer) {
Player player = event.getPlayer(); Player player = event.getPlayer();
final long optimizeCoolDown = closest.getOptimizeCooldownMillis(cooldown); final long optimizeCoolDown = closestOptimizableVillager.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())
)); ));
@ -114,11 +132,11 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onBlockBreak(BlockBreakEvent event) { private void onBlockBreak(BlockBreakEvent event) {
Block placed = event.getBlock(); Block placed = event.getBlock();
if (!config.workstations_that_disable.contains(placed.getType())) return; if (!workstations_that_disable.contains(placed.getType())) return;
final Location workstationLoc = placed.getLocation(); final Location workstationLoc = placed.getLocation();
WrappedVillager closest = null; WrappedVillager closestOptimizedVillager = null;
double closestDistance = Double.MAX_VALUE; double closestDistance = search_radius;
for (Entity entity : workstationLoc.getNearbyEntities(search_radius, search_radius, search_radius)) { for (Entity entity : workstationLoc.getNearbyEntities(search_radius, search_radius, search_radius)) {
if (!entity.getType().equals(EntityType.VILLAGER)) continue; if (!entity.getType().equals(EntityType.VILLAGER)) continue;
@ -127,15 +145,18 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene
if (profession.equals(Villager.Profession.NONE) || profession.equals(Villager.Profession.NITWIT)) continue; if (profession.equals(Villager.Profession.NONE) || profession.equals(Villager.Profession.NITWIT)) continue;
WrappedVillager wVillager = villagerManager.getOrAdd(villager); WrappedVillager wVillager = villagerManager.getOrAdd(villager);
if (wVillager.isOptimized() && entity.getLocation().distance(workstationLoc) < closestDistance) { final double distance = entity.getLocation().distance(workstationLoc);
closest = wVillager;
if (wVillager.isOptimized() && distance < closestDistance) {
closestOptimizedVillager = wVillager;
closestDistance = distance;
} }
} }
if (closest != null && closest.getOptimizationType().equals(OptimizationType.WORKSTATION)) { if (closestOptimizedVillager != null && closestOptimizedVillager.getOptimizationType().equals(OptimizationType.WORKSTATION)) {
if (shouldNotifyPlayer) { if (shouldNotifyPlayer) {
Player player = event.getPlayer(); Player player = event.getPlayer();
final String vilType = closest.villager().getProfession().toString().toLowerCase(); final String vilType = closestOptimizedVillager.villager().getProfession().toString().toLowerCase();
final String workstation = placed.getType().toString().toLowerCase(); final String workstation = placed.getType().toString().toLowerCase();
VillagerOptimizer.getLang(player.locale()).workstation_unoptimize_success.forEach(line -> player.sendMessage(line VillagerOptimizer.getLang(player.locale()).workstation_unoptimize_success.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(vilType).build()) .replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(vilType).build())

View File

@ -1,8 +1,11 @@
messages: messages:
no-permission: "<red>You don't have permission to use this command." no-permission: "<red>You don't have permission to use this command."
trade-restock: optimize-to-trade:
success: - "<red>You need to optimize this villager before you can trade with it."
- "<green>All trades restocked! Next restock in %time%" trades-restocked:
- "<green>All trades have been restocked! Next restock in %time%."
villager-leveling-up:
- "<yellow>Villager is currently leveling up! You can use the villager again in %time%"
nametag: nametag:
optimize-success: optimize-success:
- "<green>Successfully optimized villager by using a nametag." - "<green>Successfully optimized villager by using a nametag."
@ -27,7 +30,9 @@ messages:
command: command:
optimize-success: optimize-success:
- "<green>Successfully optimized %amount% villager(s) in a radius of %radius% blocks." - "<green>Successfully optimized %amount% villager(s) in a radius of %radius% blocks."
command-on-cooldown: optimize-fail:
- "<gray>You need to wait %time% until you can use this command again." - "<gray>%amount% villagers couldn't be optimized because they have recently been optimized."
radius-limit-exceed:
- "<red>The radius you entered exceeds the limit of %distance% blocks."
unoptimize-success: unoptimize-success:
- "<green>Successfully unoptimized %amount% villager(s) in a radius of %radius% blocks." - "<green>Successfully unoptimized %amount% villager(s) in a radius of %radius% blocks."