finish up improvement to workstation optimization
This commit is contained in:
parent
ed79060e7f
commit
e14c212dff
@ -4,7 +4,9 @@ import me.xginko.villageroptimizer.enums.Keyring;
|
|||||||
import me.xginko.villageroptimizer.enums.OptimizationType;
|
import me.xginko.villageroptimizer.enums.OptimizationType;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
|
import org.bukkit.Location;
|
||||||
import org.bukkit.entity.Villager;
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.bukkit.entity.memory.MemoryKey;
|
||||||
import org.bukkit.inventory.MerchantRecipe;
|
import org.bukkit.inventory.MerchantRecipe;
|
||||||
import org.bukkit.persistence.PersistentDataContainer;
|
import org.bukkit.persistence.PersistentDataContainer;
|
||||||
import org.bukkit.persistence.PersistentDataType;
|
import org.bukkit.persistence.PersistentDataType;
|
||||||
@ -13,16 +15,18 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@SuppressWarnings("ALL")
|
||||||
public final class WrappedVillager {
|
public final class WrappedVillager {
|
||||||
|
|
||||||
private final @NotNull Villager villager;
|
private final @NotNull Villager villager;
|
||||||
private final @NotNull PersistentDataContainer dataContainer;
|
private final @NotNull PersistentDataContainer dataContainer;
|
||||||
|
private @Nullable CachedJobSite cachedJobSite;
|
||||||
private final boolean parseOther;
|
private final boolean parseOther;
|
||||||
|
|
||||||
WrappedVillager(@NotNull Villager villager) {
|
WrappedVillager(@NotNull Villager villager) {
|
||||||
this.villager = villager;
|
this.villager = villager;
|
||||||
this.dataContainer = villager.getPersistentDataContainer();
|
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.
|
* @param type OptimizationType the villager should be set to.
|
||||||
*/
|
*/
|
||||||
public void setOptimizationType(OptimizationType type) {
|
public void setOptimizationType(final OptimizationType type) {
|
||||||
me.xginko.villageroptimizer.VillagerOptimizer.getFoliaLib().getImpl().runAtEntityTimer(villager, setOptimization -> {
|
VillagerOptimizer.getFoliaLib().getImpl().runAtEntityTimer(villager, setOptimization -> {
|
||||||
// Keep repeating task until villager is no longer trading with a player
|
// Keep repeating task until villager is no longer trading with a player
|
||||||
if (villager.isTrading()) return;
|
if (villager.isTrading()) return;
|
||||||
|
|
||||||
if (type.equals(OptimizationType.NONE) && isOptimized()) {
|
switch (type) {
|
||||||
if (!parseOther || isOptimized(Keyring.Spaces.VillagerOptimizer))
|
case NAMETAG, COMMAND, BLOCK, WORKSTATION -> {
|
||||||
dataContainer.remove(Keyring.VillagerOptimizer.OPTIMIZATION_TYPE.getKey());
|
|
||||||
villager.setAware(true);
|
|
||||||
villager.setAI(true); // Done for stability so villager is guaranteed to wake up
|
|
||||||
} else {
|
|
||||||
dataContainer.set(Keyring.VillagerOptimizer.OPTIMIZATION_TYPE.getKey(), PersistentDataType.STRING, type.name());
|
dataContainer.set(Keyring.VillagerOptimizer.OPTIMIZATION_TYPE.getKey(), PersistentDataType.STRING, type.name());
|
||||||
villager.setAware(false);
|
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
|
// End repeating task once logic is finished
|
||||||
setOptimization.cancel();
|
setOptimization.cancel();
|
||||||
@ -230,7 +246,9 @@ public final class WrappedVillager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public long getRestockCooldownMillis(final long cooldown_millis) {
|
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.
|
* @return The time of the in-game world when the entity was last leveled up.
|
||||||
*/
|
*/
|
||||||
public long getLastLevelUpTime() {
|
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) {
|
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) {
|
public void memorizeName(final Component customName) {
|
||||||
@ -289,10 +311,40 @@ public final class WrappedVillager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable Component getMemorizedName() {
|
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() {
|
public void forgetName() {
|
||||||
dataContainer.remove(Keyring.VillagerOptimizer.LAST_OPTIMIZE_NAME.getKey());
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
@ -28,15 +28,16 @@ public class DisableSubCmd extends SubCommand {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void perform(CommandSender sender, String[] args) {
|
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));
|
sender.sendMessage(Component.text("Disabling VillagerOptimizer...").color(NamedTextColor.RED));
|
||||||
VillagerOptimizerModule.modules.forEach(VillagerOptimizerModule::disable);
|
VillagerOptimizerModule.modules.forEach(VillagerOptimizerModule::disable);
|
||||||
VillagerOptimizerModule.modules.clear();
|
VillagerOptimizerModule.modules.clear();
|
||||||
VillagerOptimizer.getCache().cacheMap().clear();
|
VillagerOptimizer.getCache().cacheMap().clear();
|
||||||
sender.sendMessage(Component.text("Disabled all plugin listeners and tasks.").color(NamedTextColor.GREEN));
|
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));
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -27,14 +27,15 @@ public class ReloadSubCmd extends SubCommand {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void perform(CommandSender sender, String[] args) {
|
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));
|
sender.sendMessage(Component.text("Reloading VillagerOptimizer...").color(NamedTextColor.WHITE));
|
||||||
VillagerOptimizer.getFoliaLib().getImpl().runNextTick(reload -> { // Reload in sync with the server
|
VillagerOptimizer.getFoliaLib().getImpl().runNextTick(reload -> { // Reload in sync with the server
|
||||||
VillagerOptimizer.getInstance().reloadPlugin();
|
VillagerOptimizer.getInstance().reloadPlugin();
|
||||||
sender.sendMessage(Component.text("Reload complete.").color(NamedTextColor.GREEN));
|
sender.sendMessage(Component.text("Reload complete.").color(NamedTextColor.GREEN));
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,11 +5,11 @@ import org.bukkit.permissions.PermissionDefault;
|
|||||||
|
|
||||||
public enum Commands {
|
public enum Commands {
|
||||||
VERSION(new Permission("villageroptimizer.cmd.version",
|
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",
|
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",
|
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",
|
OPTIMIZE_RADIUS(new Permission("villageroptimizer.cmd.optimize",
|
||||||
"Permission to optimize villagers in a radius", PermissionDefault.TRUE)),
|
"Permission to optimize villagers in a radius", PermissionDefault.TRUE)),
|
||||||
UNOPTIMIZE_RADIUS(new Permission("villageroptimizer.cmd.unoptimize",
|
UNOPTIMIZE_RADIUS(new Permission("villageroptimizer.cmd.unoptimize",
|
||||||
|
@ -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.Cache;
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
@ -4,7 +4,7 @@ import me.xginko.villageroptimizer.VillagerCache;
|
|||||||
import me.xginko.villageroptimizer.VillagerOptimizer;
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
import me.xginko.villageroptimizer.config.Config;
|
import me.xginko.villageroptimizer.config.Config;
|
||||||
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
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.Material;
|
||||||
import org.bukkit.entity.EntityType;
|
import org.bukkit.entity.EntityType;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
@ -58,24 +58,23 @@ public class EnableLeashingVillagers implements VillagerOptimizerModule, Listene
|
|||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
private void onLeash(PlayerInteractEntityEvent event) {
|
private void onLeash(PlayerInteractEntityEvent event) {
|
||||||
if (!event.getRightClicked().getType().equals(EntityType.VILLAGER)) return;
|
if (!event.getRightClicked().getType().equals(EntityType.VILLAGER)) return;
|
||||||
|
final Player player = event.getPlayer();
|
||||||
Player player = event.getPlayer();
|
|
||||||
if (!player.getInventory().getItem(event.getHand()).getType().equals(Material.LEAD)) return;
|
if (!player.getInventory().getItem(event.getHand()).getType().equals(Material.LEAD)) return;
|
||||||
|
|
||||||
Villager villager = (Villager) event.getRightClicked();
|
Villager villager = (Villager) event.getRightClicked();
|
||||||
|
|
||||||
if (villager.isLeashed()) {
|
if (villager.isLeashed()) {
|
||||||
// If player clicked leashed villager, unleash.
|
// If leash holder clicked leashed villager, unleash.
|
||||||
try {
|
try {
|
||||||
if (villager.getLeashHolder().getUniqueId().equals(player.getUniqueId())) {
|
if (villager.getLeashHolder().getUniqueId().equals(player.getUniqueId())) {
|
||||||
villager.setLeashHolder(null);
|
villager.setLeashHolder(null);
|
||||||
villagersThatShouldntOpenTradeView.add(villager.getUniqueId());
|
villagersThatShouldntOpenTradeView.add(villager.getUniqueId());
|
||||||
}
|
}
|
||||||
} catch (IllegalStateException ignored) {
|
} catch (IllegalStateException ignored) {
|
||||||
// This shouldn't throw because we check LivingEntity#isLeashed(),
|
// This shouldn't throw because we check LivingEntity#isLeashed(), but if for some reason it does, we catch it.
|
||||||
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ public class UnoptimizeOnJobLoose implements VillagerOptimizerModule, Listener {
|
|||||||
@Override
|
@Override
|
||||||
public boolean shouldEnable() {
|
public boolean shouldEnable() {
|
||||||
return VillagerOptimizer.getConfiguration().getBoolean("gameplay.unoptimize-on-job-loose.enable", true,
|
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)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
@ -3,7 +3,7 @@ package me.xginko.villageroptimizer.modules.optimization;
|
|||||||
import com.github.benmanes.caffeine.cache.Cache;
|
import com.github.benmanes.caffeine.cache.Cache;
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
import com.tcoded.folialib.impl.ServerImplementation;
|
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.VillagerCache;
|
||||||
import me.xginko.villageroptimizer.VillagerOptimizer;
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
import me.xginko.villageroptimizer.WrappedVillager;
|
import me.xginko.villageroptimizer.WrappedVillager;
|
||||||
@ -22,44 +22,48 @@ import org.bukkit.entity.Entity;
|
|||||||
import org.bukkit.entity.EntityType;
|
import org.bukkit.entity.EntityType;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.entity.Villager;
|
import org.bukkit.entity.Villager;
|
||||||
import org.bukkit.entity.memory.MemoryKey;
|
|
||||||
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.block.BlockBreakEvent;
|
import org.bukkit.event.block.BlockBreakEvent;
|
||||||
import org.bukkit.event.block.BlockPlaceEvent;
|
import org.bukkit.event.block.BlockPlaceEvent;
|
||||||
import org.bukkit.event.entity.VillagerCareerChangeEvent;
|
import org.bukkit.util.NumberConversions;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener {
|
public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener {
|
||||||
|
|
||||||
private final ServerImplementation scheduler;
|
private final ServerImplementation scheduler;
|
||||||
private final VillagerCache villagerCache;
|
private final VillagerCache villagerCache;
|
||||||
private final Cache<UUID, Location> cachedVillagerJobSites;
|
private final Cache<Location, WrappedTask> pending_optimizations;
|
||||||
private final long cooldown_millis;
|
private final long cooldown_millis, delay_millis, resettable_delay_millis;
|
||||||
private final double search_radius;
|
private final double search_radius, search_radius_squared;
|
||||||
private final boolean only_while_sneaking, log_enabled, notify_player;
|
private final boolean only_while_sneaking, log_enabled, notify_player;
|
||||||
|
|
||||||
public OptimizeByWorkstation() {
|
public OptimizeByWorkstation() {
|
||||||
shouldEnable();
|
shouldEnable();
|
||||||
this.scheduler = VillagerOptimizer.getFoliaLib().getImpl();
|
this.scheduler = VillagerOptimizer.getFoliaLib().getImpl();
|
||||||
this.villagerCache = VillagerOptimizer.getCache();
|
this.villagerCache = VillagerOptimizer.getCache();
|
||||||
this.cachedVillagerJobSites = Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(2)).build();
|
|
||||||
|
|
||||||
// Broken and unfinished, in progress
|
|
||||||
|
|
||||||
Config config = VillagerOptimizer.getConfiguration();
|
Config config = VillagerOptimizer.getConfiguration();
|
||||||
config.master().addComment("optimization-methods.workstation-optimization.enable", """
|
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
|
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.""");
|
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, """
|
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 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(
|
this.cooldown_millis = TimeUnit.SECONDS.toMillis(
|
||||||
config.getInt("optimization-methods.workstation-optimization.optimize-cooldown-seconds", 600, """
|
config.getInt("optimization-methods.workstation-optimization.optimize-cooldown-seconds", 600, """
|
||||||
Cooldown in seconds until a villager can be optimized again using a workstation.\s
|
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);
|
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)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
private void onBlockPlace(BlockPlaceEvent event) {
|
private void onBlockPlace(BlockPlaceEvent event) {
|
||||||
Block placed = event.getBlock();
|
final Block placed = event.getBlock();
|
||||||
Villager.Profession workstationProfession = CommonUtil.getWorkstationProfession(placed.getType());
|
final Villager.Profession workstationProfession = CommonUtil.getWorkstationProfession(placed.getType());
|
||||||
if (workstationProfession.equals(Villager.Profession.NONE)) return;
|
if (workstationProfession.equals(Villager.Profession.NONE)) return;
|
||||||
|
final Player player = event.getPlayer();
|
||||||
Player player = event.getPlayer();
|
|
||||||
if (!player.hasPermission(Optimize.WORKSTATION.get())) return;
|
if (!player.hasPermission(Optimize.WORKSTATION.get())) return;
|
||||||
if (only_while_sneaking && !player.isSneaking()) return;
|
if (only_while_sneaking && !player.isSneaking()) return;
|
||||||
|
|
||||||
final Location workstationLoc = placed.getLocation().toCenterLocation();
|
final Location workstationLoc = placed.getLocation().toCenterLocation();
|
||||||
WrappedVillager villagerThatClaimedWorkstation = null;
|
WrappedVillager toOptimize = null;
|
||||||
|
|
||||||
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;
|
||||||
Villager villager = (Villager) entity;
|
Villager villager = (Villager) entity;
|
||||||
if (!villager.getProfession().equals(workstationProfession)) continue;
|
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);
|
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)) {
|
if (wVillager.canOptimize(cooldown_millis)) {
|
||||||
villagerThatClaimedWorkstation = wVillager;
|
toOptimize = wVillager;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (villagerThatClaimedWorkstation == null) return;
|
if (toOptimize == null) return;
|
||||||
|
WrappedVillager finalToOptimize = toOptimize;
|
||||||
|
|
||||||
if (villagerThatClaimedWorkstation.canOptimize(cooldown_millis) || player.hasPermission(Bypass.WORKSTATION_COOLDOWN.get())) {
|
pending_optimizations.put(placed.getLocation(), scheduler.runAtLocationLater(workstationLoc,
|
||||||
VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(villagerThatClaimedWorkstation, OptimizationType.WORKSTATION, player, event.isAsynchronous());
|
() -> 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;
|
if (!optimizeEvent.callEvent()) return;
|
||||||
|
|
||||||
villagerThatClaimedWorkstation.setOptimizationType(optimizeEvent.getOptimizationType());
|
toOptimize.setOptimizationType(optimizeEvent.getOptimizationType());
|
||||||
villagerThatClaimedWorkstation.saveOptimizeTime();
|
toOptimize.saveOptimizeTime();
|
||||||
|
|
||||||
if (notify_player) {
|
if (notify_player) {
|
||||||
final TextReplacementConfig vilProfession = TextReplacementConfig.builder()
|
final TextReplacementConfig vilProfession = TextReplacementConfig.builder()
|
||||||
.matchLiteral("%vil_profession%")
|
.matchLiteral("%vil_profession%")
|
||||||
.replacement(villagerThatClaimedWorkstation.villager().getProfession().toString().toLowerCase())
|
.replacement(toOptimize.villager().getProfession().toString().toLowerCase())
|
||||||
.build();
|
.build();
|
||||||
final TextReplacementConfig placedWorkstation = TextReplacementConfig.builder()
|
final TextReplacementConfig placedWorkstation = TextReplacementConfig.builder()
|
||||||
.matchLiteral("%workstation%")
|
.matchLiteral("%workstation%")
|
||||||
@ -183,14 +151,16 @@ public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener
|
|||||||
.replaceText(placedWorkstation)
|
.replaceText(placedWorkstation)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (log_enabled)
|
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 {
|
} else {
|
||||||
CommonUtil.shakeHead(villagerThatClaimedWorkstation.villager());
|
CommonUtil.shakeHead(toOptimize.villager());
|
||||||
if (notify_player) {
|
if (notify_player) {
|
||||||
final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
|
final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
|
||||||
.matchLiteral("%time%")
|
.matchLiteral("%time%")
|
||||||
.replacement(CommonUtil.formatTime(villagerThatClaimedWorkstation.getOptimizeCooldownMillis(cooldown_millis)))
|
.replacement(CommonUtil.formatTime(toOptimize.getOptimizeCooldownMillis(cooldown_millis)))
|
||||||
.build();
|
.build();
|
||||||
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(timeLeft)
|
.replaceText(timeLeft)
|
||||||
@ -201,10 +171,14 @@ public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener
|
|||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
private void onBlockBreak(BlockBreakEvent event) {
|
private void onBlockBreak(BlockBreakEvent event) {
|
||||||
Block broken = event.getBlock();
|
final Block broken = event.getBlock();
|
||||||
Villager.Profession workstationProfession = CommonUtil.getWorkstationProfession(broken.getType());
|
// 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;
|
if (workstationProfession.equals(Villager.Profession.NONE)) return;
|
||||||
Player player = event.getPlayer();
|
final Player player = event.getPlayer();
|
||||||
if (!player.hasPermission(Optimize.WORKSTATION.get())) return;
|
if (!player.hasPermission(Optimize.WORKSTATION.get())) return;
|
||||||
if (only_while_sneaking && !player.isSneaking()) return;
|
if (only_while_sneaking && !player.isSneaking()) return;
|
||||||
|
|
||||||
@ -218,9 +192,9 @@ public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener
|
|||||||
if (!villager.getProfession().equals(workstationProfession)) continue;
|
if (!villager.getProfession().equals(workstationProfession)) continue;
|
||||||
|
|
||||||
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
|
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;
|
closestOptimizedVillager = wVillager;
|
||||||
closestDistance = distance;
|
closestDistance = distance;
|
||||||
}
|
}
|
||||||
|
@ -60,9 +60,4 @@ public class CommonUtil {
|
|||||||
default -> Villager.Profession.NONE;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user