finish up improvement to workstation optimization

This commit is contained in:
xGinko 2024-02-08 22:09:03 +01:00
parent ed79060e7f
commit e14c212dff
9 changed files with 149 additions and 127 deletions

View File

@ -4,7 +4,9 @@ import me.xginko.villageroptimizer.enums.Keyring;
import me.xginko.villageroptimizer.enums.OptimizationType;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Location;
import org.bukkit.entity.Villager;
import org.bukkit.entity.memory.MemoryKey;
import org.bukkit.inventory.MerchantRecipe;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
@ -13,16 +15,18 @@ import org.jetbrains.annotations.Nullable;
import java.util.concurrent.TimeUnit;
@SuppressWarnings("ALL")
public final class WrappedVillager {
private final @NotNull Villager villager;
private final @NotNull PersistentDataContainer dataContainer;
private @Nullable CachedJobSite cachedJobSite;
private final boolean parseOther;
WrappedVillager(@NotNull Villager villager) {
this.villager = villager;
this.dataContainer = villager.getPersistentDataContainer();
this.parseOther = me.xginko.villageroptimizer.VillagerOptimizer.getConfiguration().support_other_plugins;
this.parseOther = VillagerOptimizer.getConfiguration().support_other_plugins;
}
/**
@ -83,20 +87,32 @@ public final class WrappedVillager {
/**
* @param type OptimizationType the villager should be set to.
*/
public void setOptimizationType(OptimizationType type) {
me.xginko.villageroptimizer.VillagerOptimizer.getFoliaLib().getImpl().runAtEntityTimer(villager, setOptimization -> {
public void setOptimizationType(final OptimizationType type) {
VillagerOptimizer.getFoliaLib().getImpl().runAtEntityTimer(villager, setOptimization -> {
// Keep repeating task until villager is no longer trading with a player
if (villager.isTrading()) return;
if (type.equals(OptimizationType.NONE) && isOptimized()) {
if (!parseOther || isOptimized(Keyring.Spaces.VillagerOptimizer))
dataContainer.remove(Keyring.VillagerOptimizer.OPTIMIZATION_TYPE.getKey());
villager.setAware(true);
villager.setAI(true); // Done for stability so villager is guaranteed to wake up
} else {
switch (type) {
case NAMETAG, COMMAND, BLOCK, WORKSTATION -> {
dataContainer.set(Keyring.VillagerOptimizer.OPTIMIZATION_TYPE.getKey(), PersistentDataType.STRING, type.name());
villager.setAware(false);
}
case NONE -> {
if (isOptimized(Keyring.Spaces.VillagerOptimizer)) {
dataContainer.remove(Keyring.VillagerOptimizer.OPTIMIZATION_TYPE.getKey());
}
if (parseOther) {
if (dataContainer.has(Keyring.AntiVillagerLag.OPTIMIZED_ANY.getKey(), PersistentDataType.STRING))
dataContainer.remove(Keyring.AntiVillagerLag.OPTIMIZED_ANY.getKey());
if (dataContainer.has(Keyring.AntiVillagerLag.OPTIMIZED_WORKSTATION.getKey(), PersistentDataType.STRING))
dataContainer.remove(Keyring.AntiVillagerLag.OPTIMIZED_WORKSTATION.getKey());
if (dataContainer.has(Keyring.AntiVillagerLag.OPTIMIZED_BLOCK.getKey(), PersistentDataType.STRING))
dataContainer.remove(Keyring.AntiVillagerLag.OPTIMIZED_BLOCK.getKey());
}
villager.setAware(true);
villager.setAI(true);
}
}
// End repeating task once logic is finished
setOptimization.cancel();
@ -230,7 +246,9 @@ public final class WrappedVillager {
}
public long getRestockCooldownMillis(final long cooldown_millis) {
return dataContainer.has(Keyring.VillagerOptimizer.LAST_RESTOCK.getKey(), PersistentDataType.LONG) ? (villager.getWorld().getFullTime() - (dataContainer.get(Keyring.VillagerOptimizer.LAST_RESTOCK.getKey(), PersistentDataType.LONG) + cooldown_millis)) : cooldown_millis;
if (dataContainer.has(Keyring.VillagerOptimizer.LAST_RESTOCK.getKey(), PersistentDataType.LONG))
return villager.getWorld().getFullTime() - (dataContainer.get(Keyring.VillagerOptimizer.LAST_RESTOCK.getKey(), PersistentDataType.LONG) + cooldown_millis);
return cooldown_millis;
}
/**
@ -277,11 +295,15 @@ public final class WrappedVillager {
* @return The time of the in-game world when the entity was last leveled up.
*/
public long getLastLevelUpTime() {
return dataContainer.has(Keyring.VillagerOptimizer.LAST_LEVELUP.getKey(), PersistentDataType.LONG) ? dataContainer.get(Keyring.VillagerOptimizer.LAST_LEVELUP.getKey(), PersistentDataType.LONG) : 0L;
if (dataContainer.has(Keyring.VillagerOptimizer.LAST_LEVELUP.getKey(), PersistentDataType.LONG))
return dataContainer.get(Keyring.VillagerOptimizer.LAST_LEVELUP.getKey(), PersistentDataType.LONG);
return 0L;
}
public long getLevelCooldownMillis(final long cooldown_millis) {
return dataContainer.has(Keyring.VillagerOptimizer.LAST_LEVELUP.getKey(), PersistentDataType.LONG) ? (villager.getWorld().getFullTime() - (dataContainer.get(Keyring.VillagerOptimizer.LAST_LEVELUP.getKey(), PersistentDataType.LONG) + cooldown_millis)) : cooldown_millis;
if (dataContainer.has(Keyring.VillagerOptimizer.LAST_LEVELUP.getKey(), PersistentDataType.LONG))
return villager.getWorld().getFullTime() - (dataContainer.get(Keyring.VillagerOptimizer.LAST_LEVELUP.getKey(), PersistentDataType.LONG) + cooldown_millis);
return cooldown_millis;
}
public void memorizeName(final Component customName) {
@ -289,10 +311,40 @@ public final class WrappedVillager {
}
public @Nullable Component getMemorizedName() {
return dataContainer.has(Keyring.VillagerOptimizer.LAST_OPTIMIZE_NAME.getKey(), PersistentDataType.STRING) ? MiniMessage.miniMessage().deserialize(dataContainer.get(Keyring.VillagerOptimizer.LAST_OPTIMIZE_NAME.getKey(), PersistentDataType.STRING)) : null;
if (dataContainer.has(Keyring.VillagerOptimizer.LAST_OPTIMIZE_NAME.getKey(), PersistentDataType.STRING))
return MiniMessage.miniMessage().deserialize(dataContainer.get(Keyring.VillagerOptimizer.LAST_OPTIMIZE_NAME.getKey(), PersistentDataType.STRING));
return null;
}
public void forgetName() {
dataContainer.remove(Keyring.VillagerOptimizer.LAST_OPTIMIZE_NAME.getKey());
}
private static class CachedJobSite {
private @Nullable Location jobSite;
private long lastRefresh;
private CachedJobSite(Villager villager) {
this.jobSite = villager.getMemory(MemoryKey.JOB_SITE);
this.lastRefresh = System.currentTimeMillis();
}
private @Nullable Location getJobSite(Villager villager) {
final long now = System.currentTimeMillis();
if (now - lastRefresh > 1000L) {
this.jobSite = villager.getMemory(MemoryKey.JOB_SITE);
this.lastRefresh = now;
}
return jobSite;
}
}
public @Nullable Location getJobSite() {
if (cachedJobSite == null)
cachedJobSite = new CachedJobSite(villager);
return cachedJobSite.getJobSite(villager);
}
public boolean canLooseProfession() {
// A villager with a level of 1 and no trading experience is liable to lose its profession.
return villager.getVillagerLevel() <= 1 && villager.getVillagerExperience() <= 0;
}
}

View File

@ -28,15 +28,16 @@ public class DisableSubCmd extends SubCommand {
@Override
public void perform(CommandSender sender, String[] args) {
if (sender.hasPermission(Commands.DISABLE.get())) {
if (!sender.hasPermission(Commands.DISABLE.get())) {
sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission);
return;
}
sender.sendMessage(Component.text("Disabling VillagerOptimizer...").color(NamedTextColor.RED));
VillagerOptimizerModule.modules.forEach(VillagerOptimizerModule::disable);
VillagerOptimizerModule.modules.clear();
VillagerOptimizer.getCache().cacheMap().clear();
sender.sendMessage(Component.text("Disabled all plugin listeners and tasks.").color(NamedTextColor.GREEN));
sender.sendMessage(Component.text("You can enable the plugin again using the reload command.").color(NamedTextColor.YELLOW));
} else {
sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission);
}
}
}

View File

@ -27,14 +27,15 @@ public class ReloadSubCmd extends SubCommand {
@Override
public void perform(CommandSender sender, String[] args) {
if (sender.hasPermission(Commands.RELOAD.get())) {
if (!sender.hasPermission(Commands.RELOAD.get())) {
sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission);
return;
}
sender.sendMessage(Component.text("Reloading VillagerOptimizer...").color(NamedTextColor.WHITE));
VillagerOptimizer.getFoliaLib().getImpl().runNextTick(reload -> { // Reload in sync with the server
VillagerOptimizer.getInstance().reloadPlugin();
sender.sendMessage(Component.text("Reload complete.").color(NamedTextColor.GREEN));
});
} else {
sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission);
}
}
}

View File

@ -5,11 +5,11 @@ import org.bukkit.permissions.PermissionDefault;
public enum Commands {
VERSION(new Permission("villageroptimizer.cmd.version",
"Permission get the plugin version", PermissionDefault.FALSE)),
"Permission get the plugin version", PermissionDefault.OP)),
RELOAD(new Permission("villageroptimizer.cmd.reload",
"Permission to reload the plugin config", PermissionDefault.FALSE)),
"Permission to reload the plugin config", PermissionDefault.OP)),
DISABLE(new Permission("villageroptimizer.cmd.disable",
"Permission to disable the plugin", PermissionDefault.FALSE)),
"Permission to disable the plugin", PermissionDefault.OP)),
OPTIMIZE_RADIUS(new Permission("villageroptimizer.cmd.optimize",
"Permission to optimize villagers in a radius", PermissionDefault.TRUE)),
UNOPTIMIZE_RADIUS(new Permission("villageroptimizer.cmd.unoptimize",

View File

@ -1,4 +1,4 @@
package me.xginko.villageroptimizer.utils;
package me.xginko.villageroptimizer.models;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;

View File

@ -4,7 +4,7 @@ import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
import me.xginko.villageroptimizer.utils.ExpiringSet;
import me.xginko.villageroptimizer.models.ExpiringSet;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
@ -58,24 +58,23 @@ public class EnableLeashingVillagers implements VillagerOptimizerModule, Listene
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onLeash(PlayerInteractEntityEvent event) {
if (!event.getRightClicked().getType().equals(EntityType.VILLAGER)) return;
Player player = event.getPlayer();
final Player player = event.getPlayer();
if (!player.getInventory().getItem(event.getHand()).getType().equals(Material.LEAD)) return;
Villager villager = (Villager) event.getRightClicked();
if (villager.isLeashed()) {
// If player clicked leashed villager, unleash.
// If leash holder clicked leashed villager, unleash.
try {
if (villager.getLeashHolder().getUniqueId().equals(player.getUniqueId())) {
villager.setLeashHolder(null);
villagersThatShouldntOpenTradeView.add(villager.getUniqueId());
}
} catch (IllegalStateException ignored) {
// This shouldn't throw because we check LivingEntity#isLeashed(),
// but if for some reason it does, we catch it.
// This shouldn't throw because we check LivingEntity#isLeashed(), but if for some reason it does, we catch it.
}
// Otherwise do not continue if already leashed
// Otherwise do nothing. There should only ever be one leash holder.
return;
}

View File

@ -33,7 +33,7 @@ public class UnoptimizeOnJobLoose implements VillagerOptimizerModule, Listener {
@Override
public boolean shouldEnable() {
return VillagerOptimizer.getConfiguration().getBoolean("gameplay.unoptimize-on-job-loose.enable", true,
"Villagers that get their jobs reset will become unoptimized again. Highly recommended to leave on.");
"Villagers that get their jobs reset will become unoptimized again.");
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)

View File

@ -3,7 +3,7 @@ package me.xginko.villageroptimizer.modules.optimization;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.tcoded.folialib.impl.ServerImplementation;
import io.papermc.paper.event.entity.EntityMoveEvent;
import com.tcoded.folialib.wrapper.task.WrappedTask;
import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.WrappedVillager;
@ -22,44 +22,48 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Villager;
import org.bukkit.entity.memory.MemoryKey;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.VillagerCareerChangeEvent;
import org.jetbrains.annotations.Nullable;
import org.bukkit.util.NumberConversions;
import java.time.Duration;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener {
private final ServerImplementation scheduler;
private final VillagerCache villagerCache;
private final Cache<UUID, Location> cachedVillagerJobSites;
private final long cooldown_millis;
private final double search_radius;
private final Cache<Location, WrappedTask> pending_optimizations;
private final long cooldown_millis, delay_millis, resettable_delay_millis;
private final double search_radius, search_radius_squared;
private final boolean only_while_sneaking, log_enabled, notify_player;
public OptimizeByWorkstation() {
shouldEnable();
this.scheduler = VillagerOptimizer.getFoliaLib().getImpl();
this.villagerCache = VillagerOptimizer.getCache();
this.cachedVillagerJobSites = Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(2)).build();
// Broken and unfinished, in progress
Config config = VillagerOptimizer.getConfiguration();
config.master().addComment("optimization-methods.workstation-optimization.enable", """
When enabled, villagers that have a job and have been traded with at least once will become optimized,\s
if near their workstation. If the workstation is broken, the villager will become unoptimized again.""");
this.delay_millis = Math.max(config.getInt("optimization-methods.workstation-optimization.delay.default-delay-in-ticks", 10, """
The delay in ticks the plugin should wait before trying to optimize the closest villager on workstation place.\s
Gives the villager time to claim the placed workstation. Minimum delay is 1 Tick (Not recommended)"""), 1) * 50L;
this.resettable_delay_millis = Math.max(config.getInt("optimization-methods.workstation-optimization.delay.resettable-delay-in-ticks", 60, """
The delay in ticks the plugin should wait before trying to optimize a villager that can loose its profession\s
by having their workstation destroyed.\s
Intended to fix issues while trade rolling."""), 1) * 50L;
this.pending_optimizations = Caffeine.newBuilder()
.expireAfterWrite(Duration.ofMillis(Math.max(resettable_delay_millis, delay_millis) + 500L))
.build();
this.search_radius = config.getDouble("optimization-methods.workstation-optimization.search-radius-in-blocks", 2.0, """
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.""") / 2;
The closest unoptimized villager to the player will be optimized.""");
this.search_radius_squared = NumberConversions.square(search_radius);
this.cooldown_millis = TimeUnit.SECONDS.toMillis(
config.getInt("optimization-methods.workstation-optimization.optimize-cooldown-seconds", 600, """
Cooldown in seconds until a villager can be optimized again using a workstation.\s
@ -87,92 +91,56 @@ public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener
return VillagerOptimizer.getConfiguration().getBoolean("optimization-methods.workstation-optimization.enable", false);
}
private @Nullable Location getJobSite(Villager villager) {
Location jobSite = cachedVillagerJobSites.getIfPresent(villager.getUniqueId());
if (jobSite == null) {
jobSite = villager.getMemory(MemoryKey.JOB_SITE);
cachedVillagerJobSites.put(villager.getUniqueId(), jobSite);
}
return jobSite;
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onVillagerMove(EntityMoveEvent event) {
if (!event.getEntityType().equals(EntityType.VILLAGER)) return;
final Villager villager = (Villager) event.getEntity();
if (villager.getProfession().equals(Villager.Profession.NONE)) return;
if (CommonUtil.canLooseProfession(villager)) return;
final Location jobSite = getJobSite(villager);
if (jobSite == null) return;
// Using distanceSquared is faster. 1*1=1 -> 1 block away from the workstation
if (!(villager.getLocation().distanceSquared(jobSite) <= 1)) return;
WrappedVillager wrappedVillager = villagerCache.getOrAdd(villager);
if (wrappedVillager.canOptimize(cooldown_millis)) {
wrappedVillager.setOptimizationType(OptimizationType.WORKSTATION);
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onCareerChange(VillagerCareerChangeEvent event) {
if (!event.getReason().equals(VillagerCareerChangeEvent.ChangeReason.EMPLOYED)) return;
if (CommonUtil.canLooseProfession(event.getEntity())) return;
WrappedVillager wrappedVillager = villagerCache.getOrAdd(event.getEntity());
if (!wrappedVillager.isOptimized() && wrappedVillager.canOptimize(cooldown_millis)) {
wrappedVillager.setOptimizationType(OptimizationType.WORKSTATION);
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onBlockPlace(BlockPlaceEvent event) {
Block placed = event.getBlock();
Villager.Profession workstationProfession = CommonUtil.getWorkstationProfession(placed.getType());
final Block placed = event.getBlock();
final Villager.Profession workstationProfession = CommonUtil.getWorkstationProfession(placed.getType());
if (workstationProfession.equals(Villager.Profession.NONE)) return;
Player player = event.getPlayer();
final Player player = event.getPlayer();
if (!player.hasPermission(Optimize.WORKSTATION.get())) return;
if (only_while_sneaking && !player.isSneaking()) return;
final Location workstationLoc = placed.getLocation().toCenterLocation();
WrappedVillager villagerThatClaimedWorkstation = null;
WrappedVillager toOptimize = null;
for (Entity entity : workstationLoc.getNearbyEntities(search_radius, search_radius, search_radius)) {
if (!entity.getType().equals(EntityType.VILLAGER)) continue;
Villager villager = (Villager) entity;
if (!villager.getProfession().equals(workstationProfession)) continue;
// Ignore villagers that haven't been locked into a profession yet, so we don't disturb trade rollers
if (CommonUtil.canLooseProfession(villager)) continue;
Location jobSite = getJobSite(villager);
if (jobSite == null) continue;
if (!workstationLoc.equals(jobSite.toBlockLocation())) continue;
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
final Location jobSite = wVillager.getJobSite();
if (jobSite == null) continue;
if (jobSite.distanceSquared(workstationLoc) > search_radius_squared) continue;
if (wVillager.canOptimize(cooldown_millis)) {
villagerThatClaimedWorkstation = wVillager;
toOptimize = wVillager;
break;
}
}
if (villagerThatClaimedWorkstation == null) return;
if (toOptimize == null) return;
WrappedVillager finalToOptimize = toOptimize;
if (villagerThatClaimedWorkstation.canOptimize(cooldown_millis) || player.hasPermission(Bypass.WORKSTATION_COOLDOWN.get())) {
VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(villagerThatClaimedWorkstation, OptimizationType.WORKSTATION, player, event.isAsynchronous());
pending_optimizations.put(placed.getLocation(), scheduler.runAtLocationLater(workstationLoc,
() -> optimize(finalToOptimize, player, placed, event.isAsynchronous()),
toOptimize.canLooseProfession() ? resettable_delay_millis : delay_millis,
TimeUnit.MILLISECONDS));
}
private void optimize(WrappedVillager toOptimize, Player player, Block placed, boolean async) {
if (toOptimize.canOptimize(cooldown_millis) || player.hasPermission(Bypass.WORKSTATION_COOLDOWN.get())) {
VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(toOptimize, OptimizationType.WORKSTATION, player, async);
if (!optimizeEvent.callEvent()) return;
villagerThatClaimedWorkstation.setOptimizationType(optimizeEvent.getOptimizationType());
villagerThatClaimedWorkstation.saveOptimizeTime();
toOptimize.setOptimizationType(optimizeEvent.getOptimizationType());
toOptimize.saveOptimizeTime();
if (notify_player) {
final TextReplacementConfig vilProfession = TextReplacementConfig.builder()
.matchLiteral("%vil_profession%")
.replacement(villagerThatClaimedWorkstation.villager().getProfession().toString().toLowerCase())
.replacement(toOptimize.villager().getProfession().toString().toLowerCase())
.build();
final TextReplacementConfig placedWorkstation = TextReplacementConfig.builder()
.matchLiteral("%workstation%")
@ -183,14 +151,16 @@ public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener
.replaceText(placedWorkstation)
));
}
if (log_enabled)
VillagerOptimizer.getLog().info(player.getName() + " optimized a villager using workstation: '" + placed.getType().toString().toLowerCase() + "'");
VillagerOptimizer.getLog().info(player.getName() + " optimized a villager using workstation: '" +
placed.getType().toString().toLowerCase() + "'");
} else {
CommonUtil.shakeHead(villagerThatClaimedWorkstation.villager());
CommonUtil.shakeHead(toOptimize.villager());
if (notify_player) {
final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
.matchLiteral("%time%")
.replacement(CommonUtil.formatTime(villagerThatClaimedWorkstation.getOptimizeCooldownMillis(cooldown_millis)))
.replacement(CommonUtil.formatTime(toOptimize.getOptimizeCooldownMillis(cooldown_millis)))
.build();
VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> player.sendMessage(line
.replaceText(timeLeft)
@ -201,10 +171,14 @@ public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onBlockBreak(BlockBreakEvent event) {
Block broken = event.getBlock();
Villager.Profession workstationProfession = CommonUtil.getWorkstationProfession(broken.getType());
final Block broken = event.getBlock();
// Cancel any pending optimization for this block
WrappedTask pendingOpt = pending_optimizations.getIfPresent(broken.getLocation());
if (pendingOpt != null) pendingOpt.cancel();
final Villager.Profession workstationProfession = CommonUtil.getWorkstationProfession(broken.getType());
if (workstationProfession.equals(Villager.Profession.NONE)) return;
Player player = event.getPlayer();
final Player player = event.getPlayer();
if (!player.hasPermission(Optimize.WORKSTATION.get())) return;
if (only_while_sneaking && !player.isSneaking()) return;
@ -218,9 +192,9 @@ public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener
if (!villager.getProfession().equals(workstationProfession)) continue;
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
final double distance = entity.getLocation().distance(workstationLoc);
final double distance = entity.getLocation().distanceSquared(workstationLoc);
if (distance < closestDistance && wVillager.canOptimize(cooldown_millis)) {
if (distance < closestDistance && wVillager.isOptimized()) {
closestOptimizedVillager = wVillager;
closestDistance = distance;
}

View File

@ -60,9 +60,4 @@ public class CommonUtil {
default -> Villager.Profession.NONE;
};
}
public static boolean canLooseProfession(@NotNull Villager villager) {
// A villager with a level of 1 and no trading experience is liable to lose its profession.
return villager.getVillagerLevel() <= 1 && villager.getVillagerExperience() <= 0;
}
}