diff --git a/src/main/java/me/xginko/villageroptimizer/VillagerOptimizer.java b/src/main/java/me/xginko/villageroptimizer/VillagerOptimizer.java index d41aeba..c33d491 100644 --- a/src/main/java/me/xginko/villageroptimizer/VillagerOptimizer.java +++ b/src/main/java/me/xginko/villageroptimizer/VillagerOptimizer.java @@ -1,22 +1,13 @@ package me.xginko.villageroptimizer; +import me.xginko.villageroptimizer.cache.VillagerManager; import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.config.LanguageCache; -import me.xginko.villageroptimizer.enums.OptimizationType; -import me.xginko.villageroptimizer.cache.VillagerManager; -import me.xginko.villageroptimizer.models.WrappedVillager; import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; -import org.bukkit.Location; import org.bukkit.NamespacedKey; -import org.bukkit.block.BlockFace; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import org.bukkit.entity.Villager; -import org.bukkit.entity.memory.MemoryKey; import org.bukkit.plugin.java.JavaPlugin; -import org.jetbrains.annotations.NotNull; import java.io.File; import java.io.IOException; @@ -146,54 +137,4 @@ public final class VillagerOptimizer extends JavaPlugin { return getLang(config.default_lang); } } - - public static OptimizationType computeOptimization(@NotNull WrappedVillager wrapped) { - if (config.enable_nametag_optimization) { - Component name = wrapped.villager().customName(); - if (name != null && config.nametags.contains(PlainTextComponentSerializer.plainText().serialize(name).toLowerCase())) { - return OptimizationType.NAMETAG; - } - } - if (config.enable_block_optimization) { - if (config.blocks_that_disable.contains(wrapped.villager().getLocation().getBlock().getRelative(BlockFace.DOWN).getType())) { - return OptimizationType.BLOCK; - } - } - if (config.enable_workstation_optimization) { - final Location jobSite = wrapped.villager().getMemory(MemoryKey.JOB_SITE); - if ( - jobSite != null - && config.workstations_that_disable.contains(jobSite.getBlock().getType()) - && wrapped.villager().getLocation().distance(jobSite) <= config.workstation_max_distance - ) { - return OptimizationType.WORKSTATION; - } - } - return wrapped.getOptimizationType(); - } - - public static OptimizationType computeOptimization(@NotNull Villager villager) { - if (config.enable_nametag_optimization) { - Component name = villager.customName(); - if (name != null && config.nametags.contains(PlainTextComponentSerializer.plainText().serialize(name).toLowerCase())) { - return OptimizationType.NAMETAG; - } - } - if (config.enable_block_optimization) { - if (config.blocks_that_disable.contains(villager.getLocation().getBlock().getRelative(BlockFace.DOWN).getType())) { - return OptimizationType.BLOCK; - } - } - if (config.enable_workstation_optimization) { - final Location jobSite = villager.getMemory(MemoryKey.JOB_SITE); - if ( - jobSite != null - && config.workstations_that_disable.contains(jobSite.getBlock().getType()) - && villager.getLocation().distance(jobSite) <= config.workstation_max_distance - ) { - return OptimizationType.WORKSTATION; - } - } - return villagerManager.getOrAdd(villager).getOptimizationType(); - } } diff --git a/src/main/java/me/xginko/villageroptimizer/config/LanguageCache.java b/src/main/java/me/xginko/villageroptimizer/config/LanguageCache.java index 98c373b..3318b97 100644 --- a/src/main/java/me/xginko/villageroptimizer/config/LanguageCache.java +++ b/src/main/java/me/xginko/villageroptimizer/config/LanguageCache.java @@ -16,7 +16,7 @@ public class LanguageCache { public final Component no_permission; public final List nametag_optimize_success, nametag_on_optimize_cooldown, nametag_unoptimize_success, block_optimize_success, block_on_optimize_cooldown, block_unoptimize_success, - workstation_optimization_success, workstation_on_optimize_cooldown, workstation_unoptimize_success; + workstation_optimize_success, workstation_on_optimize_cooldown, workstation_unoptimize_success; public LanguageCache(String lang) throws Exception { this.lang = loadLang(new File(VillagerOptimizer.getInstance().getDataFolder() + File.separator + "lang", lang + ".yml")); @@ -28,10 +28,10 @@ public class LanguageCache { this.nametag_unoptimize_success = getListTranslation("messages.nametag.unoptimize-success", List.of("Successfully unoptimized villager by using a nametag.")); this.block_optimize_success = getListTranslation("messages.block.optimize-success", List.of("%villagertype% villager successfully optimized using block %blocktype%.")); this.block_on_optimize_cooldown = getListTranslation("messages.block.optimize-on-cooldown", List.of("You need to wait %time% until you can optimize this villager again.")); - this.block_unoptimize_success = getListTranslation("messages.block.unoptimize-success", List.of("Successfully unoptimized villager by moving it off a %blocktype% block.")); - this.workstation_optimization_success = getListTranslation("messages.workstation.optimize-success", List.of("%villagertype% villager successfully optimized using workstation block %blocktype%.")); + this.block_unoptimize_success = getListTranslation("messages.block.unoptimize-success", List.of("Successfully unoptimized %villagertype% villager by removing %blocktype%.")); + this.workstation_optimize_success = getListTranslation("messages.workstation.optimize-success", List.of("%villagertype% villager successfully optimized using workstation %workstation%.")); this.workstation_on_optimize_cooldown = getListTranslation("messages.workstation.optimize-on-cooldown", List.of("You need to wait %time% until you can optimize this villager again.")); - this.workstation_unoptimize_success = getListTranslation("messages.workstation.unoptimize-success", List.of("Successfully unoptimized villager by removing workstation block %blocktype%.")); + this.workstation_unoptimize_success = getListTranslation("messages.workstation.unoptimize-success", List.of("Successfully unoptimized %villagertype% villager by removing workstation block %workstation%.")); saveLang(); } diff --git a/src/main/java/me/xginko/villageroptimizer/models/WrappedVillager.java b/src/main/java/me/xginko/villageroptimizer/models/WrappedVillager.java index 5f16888..51f21ab 100644 --- a/src/main/java/me/xginko/villageroptimizer/models/WrappedVillager.java +++ b/src/main/java/me/xginko/villageroptimizer/models/WrappedVillager.java @@ -11,11 +11,11 @@ import org.jetbrains.annotations.NotNull; public final class WrappedVillager { private final @NotNull Villager villager; - private final @NotNull PersistentDataContainer villagerData; + private final @NotNull PersistentDataContainer dataContainer; public WrappedVillager(@NotNull Villager villager) { this.villager = villager; - this.villagerData = this.villager.getPersistentDataContainer(); + this.dataContainer = this.villager.getPersistentDataContainer(); } public @NotNull Villager villager() { @@ -27,41 +27,37 @@ public final class WrappedVillager { } public boolean isOptimized() { - return villagerData.has(Keys.OPTIMIZED.key()); - } - - public @NotNull OptimizationType computeOptimization() { - return VillagerOptimizer.computeOptimization(this); + return dataContainer.has(Keys.OPTIMIZED.key()); } public boolean setOptimization(OptimizationType type) { if (type.equals(OptimizationType.OFF) && isOptimized()) { - villagerData.remove(Keys.OPTIMIZED.key()); + dataContainer.remove(Keys.OPTIMIZED.key()); villager.setAware(true); villager.setAI(true); } else { if (isOnOptimizeCooldown()) return false; - setOptimizeCooldown(VillagerOptimizer.getConfiguration().state_change_cooldown); - villagerData.set(Keys.OPTIMIZED.key(), PersistentDataType.STRING, type.name()); + dataContainer.set(Keys.OPTIMIZED.key(), PersistentDataType.STRING, type.name()); villager.setAware(false); + setOptimizeCooldown(VillagerOptimizer.getConfiguration().state_change_cooldown); } return true; } public @NotNull OptimizationType getOptimizationType() { - return isOptimized() ? OptimizationType.valueOf(villagerData.get(Keys.OPTIMIZED.key(), PersistentDataType.STRING)) : OptimizationType.OFF; + return isOptimized() ? OptimizationType.valueOf(dataContainer.get(Keys.OPTIMIZED.key(), PersistentDataType.STRING)) : OptimizationType.OFF; } public void setOptimizeCooldown(long milliseconds) { - villagerData.set(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG, System.currentTimeMillis() + milliseconds); + dataContainer.set(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG, System.currentTimeMillis() + milliseconds); } public long getOptimizeCooldown() { - return villagerData.has(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG) ? System.currentTimeMillis() - villagerData.get(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG) : 0L; + return dataContainer.has(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG) ? System.currentTimeMillis() - dataContainer.get(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG) : 0L; } public boolean isOnOptimizeCooldown() { - return villagerData.has(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG) && villagerData.get(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG) <= System.currentTimeMillis(); + return dataContainer.has(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG) && dataContainer.get(Keys.COOLDOWN_OPTIMIZE.key(), PersistentDataType.LONG) <= System.currentTimeMillis(); } public void restock() { @@ -69,20 +65,20 @@ public final class WrappedVillager { } public void setExpCooldown(long milliseconds) { - villagerData.set(Keys.COOLDOWN_EXPERIENCE.key(), PersistentDataType.LONG, System.currentTimeMillis() + milliseconds); + dataContainer.set(Keys.COOLDOWN_EXPERIENCE.key(), PersistentDataType.LONG, System.currentTimeMillis() + milliseconds); } public boolean isOnExpCooldown() { - return villagerData.has(Keys.COOLDOWN_EXPERIENCE.key(), PersistentDataType.LONG) && villagerData.get(Keys.COOLDOWN_EXPERIENCE.key(), PersistentDataType.LONG) <= System.currentTimeMillis(); + return dataContainer.has(Keys.COOLDOWN_EXPERIENCE.key(), PersistentDataType.LONG) && dataContainer.get(Keys.COOLDOWN_EXPERIENCE.key(), PersistentDataType.LONG) <= System.currentTimeMillis(); } public long saveWorldTime() { final long worldTime = villager.getWorld().getFullTime(); - villagerData.set(Keys.WORLDTIME.key(), PersistentDataType.LONG, worldTime); + dataContainer.set(Keys.WORLDTIME.key(), PersistentDataType.LONG, worldTime); return worldTime; } public long getSavedWorldTime() { - return villagerData.has(Keys.WORLDTIME.key(), PersistentDataType.LONG) ? villagerData.get(Keys.WORLDTIME.key(), PersistentDataType.LONG) : saveWorldTime(); + return dataContainer.has(Keys.WORLDTIME.key(), PersistentDataType.LONG) ? dataContainer.get(Keys.WORLDTIME.key(), PersistentDataType.LONG) : saveWorldTime(); } } diff --git a/src/main/java/me/xginko/villageroptimizer/modules/BlockOptimization.java b/src/main/java/me/xginko/villageroptimizer/modules/BlockOptimization.java index ae3f5c0..f337e9d 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/BlockOptimization.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/BlockOptimization.java @@ -61,14 +61,17 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener { Block placed = event.getBlock(); if (!config.blocks_that_disable.contains(placed.getType())) return; - placed.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,0.5,0.5).forEach(entity -> { + placed.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,1,0.5).forEach(entity -> { if (entity.getType().equals(EntityType.VILLAGER)) { WrappedVillager wVillager = villagerManager.getOrAdd((Villager) entity); if (!wVillager.isOptimized()) { if (wVillager.setOptimization(OptimizationType.BLOCK)) { if (shouldNotifyPlayer) { Player player = event.getPlayer(); - VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(player::sendMessage); + VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(line -> player.sendMessage(line + .replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(wVillager.villager().getProfession().toString().toLowerCase()).build()) + .replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(placed.getType().toString().toLowerCase()).build()) + )); } if (shouldLog) VillagerOptimizer.getLog().info("Villager was optimized by block at "+wVillager.villager().getLocation()); @@ -89,14 +92,17 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener { Block broken = event.getBlock(); if (!config.blocks_that_disable.contains(broken.getType())) return; - broken.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,0.5,0.5).forEach(entity -> { + broken.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,1,0.5).forEach(entity -> { if (entity.getType().equals(EntityType.VILLAGER)) { WrappedVillager wVillager = villagerManager.getOrAdd((Villager) entity); if (wVillager.getOptimizationType().equals(OptimizationType.BLOCK)) { wVillager.setOptimization(OptimizationType.OFF); if (shouldNotifyPlayer) { Player player = event.getPlayer(); - VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(player::sendMessage); + VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(line -> player.sendMessage(line + .replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(wVillager.villager().getProfession().toString().toLowerCase()).build()) + .replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(broken.getType().toString().toLowerCase()).build()) + )); } if (shouldLog) VillagerOptimizer.getLog().info("Villager unoptimized because no longer standing on optimization block at "+wVillager.villager().getLocation()); @@ -121,7 +127,10 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener { if (wVillager.setOptimization(OptimizationType.BLOCK)) { if (shouldNotifyPlayer) { Player player = event.getPlayer(); - VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(player::sendMessage); + VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(line -> player.sendMessage(line + .replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(wVillager.villager().getProfession().toString().toLowerCase()).build()) + .replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(entityLegs.getBlock().getType().toString().toLowerCase()).build()) + )); } if (shouldLog) VillagerOptimizer.getLog().info("Villager was optimized by block at "+wVillager.villager().getLocation()); @@ -138,7 +147,10 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener { wVillager.setOptimization(OptimizationType.OFF); if (shouldNotifyPlayer) { Player player = event.getPlayer(); - VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(player::sendMessage); + VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(line -> player.sendMessage(line + .replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(wVillager.villager().getProfession().toString().toLowerCase()).build()) + .replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(entityLegs.getBlock().getType().toString().toLowerCase()).build()) + )); } if (shouldLog) VillagerOptimizer.getLog().info("Villager unoptimized because no longer standing on optimization block at "+wVillager.villager().getLocation()); diff --git a/src/main/java/me/xginko/villageroptimizer/modules/PreventVillagerDamage.java b/src/main/java/me/xginko/villageroptimizer/modules/PreventVillagerDamage.java index 567bca4..759b695 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/PreventVillagerDamage.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/PreventVillagerDamage.java @@ -37,16 +37,20 @@ public class PreventVillagerDamage implements VillagerOptimizerModule, Listener @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) private void onDamageReceive(EntityDamageEvent event) { - if (!event.getEntityType().equals(EntityType.VILLAGER)) return; - if (villagerManager.getOrAdd((Villager) event.getEntity()).isOptimized()) { + if ( + !event.getEntityType().equals(EntityType.VILLAGER) + && villagerManager.getOrAdd((Villager) event.getEntity()).isOptimized() + ) { event.setCancelled(true); } } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) private void onPushByEntityAttack(EntityPushedByEntityAttackEvent event) { - if (!event.getEntityType().equals(EntityType.VILLAGER)) return; - if (villagerManager.getOrAdd((Villager) event.getEntity()).isOptimized()) { + if ( + !event.getEntityType().equals(EntityType.VILLAGER) + && villagerManager.getOrAdd((Villager) event.getEntity()).isOptimized() + ) { event.setCancelled(true); } } diff --git a/src/main/java/me/xginko/villageroptimizer/modules/PreventVillagerTargetting.java b/src/main/java/me/xginko/villageroptimizer/modules/PreventVillagerTargetting.java index 47a5e09..cf9763b 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/PreventVillagerTargetting.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/PreventVillagerTargetting.java @@ -3,6 +3,7 @@ package me.xginko.villageroptimizer.modules; import com.destroystokyo.paper.event.entity.EntityPathfindEvent; import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.cache.VillagerManager; +import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Mob; import org.bukkit.entity.Villager; @@ -39,14 +40,25 @@ public class PreventVillagerTargetting implements VillagerOptimizerModule, Liste @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) private void onTarget(EntityTargetLivingEntityEvent event) { - if (event.getTarget() instanceof Villager villager && villagerManager.getOrAdd(villager).isOptimized()) { + // Yes, instanceof checks would look way more beautiful here but checking type is much faster + Entity target = event.getTarget(); + if ( + target != null + && target.getType().equals(EntityType.VILLAGER) + && villagerManager.getOrAdd((Villager) target).isOptimized() + ) { event.setCancelled(true); } } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) private void onEntityTargetVillager(EntityPathfindEvent event) { - if (event.getTargetEntity() instanceof Villager villager && villagerManager.getOrAdd(villager).isOptimized()) { + Entity target = event.getTargetEntity(); + if ( + target != null + && target.getType().equals(EntityType.VILLAGER) + && villagerManager.getOrAdd((Villager) target).isOptimized() + ) { event.setCancelled(true); } } diff --git a/src/main/java/me/xginko/villageroptimizer/modules/ChunkLimit.java b/src/main/java/me/xginko/villageroptimizer/modules/VillagerChunkLimit.java similarity index 75% rename from src/main/java/me/xginko/villageroptimizer/modules/ChunkLimit.java rename to src/main/java/me/xginko/villageroptimizer/modules/VillagerChunkLimit.java index 4a4fc9c..8fd65e8 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/ChunkLimit.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/VillagerChunkLimit.java @@ -2,11 +2,10 @@ package me.xginko.villageroptimizer.modules; import io.papermc.paper.threadedregions.scheduler.ScheduledTask; import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.cache.VillagerManager; import me.xginko.villageroptimizer.config.Config; -import me.xginko.villageroptimizer.models.WrappedVillager; import me.xginko.villageroptimizer.utils.LogUtils; import org.bukkit.Chunk; -import org.bukkit.World; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Villager; @@ -15,24 +14,27 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.logging.Level; -public class ChunkLimit implements VillagerOptimizerModule, Listener { +public class VillagerChunkLimit implements VillagerOptimizerModule, Listener { private final VillagerOptimizer plugin; + private final VillagerManager villagerManager; private ScheduledTask scheduledTask; - private final List removalPriority = new ArrayList<>(); + private final List removalPriority = new ArrayList<>(16); private final int maxVillagersPerChunk; private final boolean logIsEnabled; private final long checkPeriod; - protected ChunkLimit() { + protected VillagerChunkLimit() { shouldEnable(); this.plugin = VillagerOptimizer.getInstance(); + this.villagerManager = VillagerOptimizer.getVillagerManager(); Config config = VillagerOptimizer.getConfiguration(); this.maxVillagersPerChunk = config.getInt("villager-chunk-limit.max-villagers-per-chunk", 25); this.logIsEnabled = config.getBoolean("villager-chunk-limit.log-removals", false); @@ -70,19 +72,28 @@ public class ChunkLimit implements VillagerOptimizerModule, Listener { if (scheduledTask != null) scheduledTask.cancel(); } + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onCreatureSpawn(CreatureSpawnEvent event) { + Entity spawned = event.getEntity(); + if (spawned.getType().equals(EntityType.VILLAGER)) { + checkVillagersInChunk(spawned.getChunk()); + } + } + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - private void onCreateSpawn(CreatureSpawnEvent event) { - if (event.getEntityType().equals(EntityType.VILLAGER)) { - checkVillagersInChunk(event.getEntity().getChunk()); + private void onInteract(PlayerInteractEntityEvent event) { + Entity clicked = event.getRightClicked(); + if (clicked.getType().equals(EntityType.VILLAGER)) { + checkVillagersInChunk(clicked.getChunk()); } } private void run() { - for (World world : plugin.getServer().getWorlds()) { + plugin.getServer().getWorlds().forEach(world -> { for (Chunk chunk : world.getLoadedChunks()) { plugin.getServer().getRegionScheduler().run(plugin, world, chunk.getX(), chunk.getZ(), task -> checkVillagersInChunk(chunk)); } - } + }); } private void checkVillagersInChunk(Chunk chunk) { @@ -104,17 +115,14 @@ public class ChunkLimit implements VillagerOptimizerModule, Listener { // Remove prioritized villagers that are too many for (int i = 0; i < amount_over_the_limit; i++) { Villager villager = villagers_in_chunk.get(i); - if (logIsEnabled) LogUtils.moduleLog( - Level.INFO, - "villager-chunk-limit", - "Removing villager of profession type '"+villager.getProfession()+"' at "+villager.getLocation() - ); villager.remove(); + if (logIsEnabled) LogUtils.moduleLog(Level.INFO, "villager-chunk-limit", + "Removed villager of profession type '"+villager.getProfession()+"' at "+villager.getLocation()); } } private int getProfessionPriority(Villager villager) { - Villager.Profession profession = villager.getProfession(); - return removalPriority.contains(profession) && !WrappedVillager.fromCache(villager).isOptimized() ? removalPriority.indexOf(profession) : Integer.MAX_VALUE; + final Villager.Profession profession = villager.getProfession(); + return removalPriority.contains(profession) && !villagerManager.getOrAdd(villager).isOptimized() ? removalPriority.indexOf(profession) : Integer.MAX_VALUE; } } diff --git a/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java b/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java index 7e6a8f6..f568e2e 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java @@ -14,7 +14,7 @@ public interface VillagerOptimizerModule { modules.forEach(VillagerOptimizerModule::disable); modules.clear(); - modules.add(new ChunkLimit()); + modules.add(new VillagerChunkLimit()); modules.add(new NametagOptimization()); modules.add(new BlockOptimization()); modules.add(new WorkstationOptimization()); diff --git a/src/main/java/me/xginko/villageroptimizer/modules/WorkstationOptimization.java b/src/main/java/me/xginko/villageroptimizer/modules/WorkstationOptimization.java index fc844af..d66aa29 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/WorkstationOptimization.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/WorkstationOptimization.java @@ -1,28 +1,46 @@ package me.xginko.villageroptimizer.modules; -import io.papermc.paper.event.entity.EntityMoveEvent; import me.xginko.villageroptimizer.VillagerOptimizer; -import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.cache.VillagerManager; +import me.xginko.villageroptimizer.config.Config; +import me.xginko.villageroptimizer.enums.OptimizationType; +import me.xginko.villageroptimizer.models.WrappedVillager; +import me.xginko.villageroptimizer.utils.CommonUtils; +import net.kyori.adventure.text.TextReplacementConfig; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; +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.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; + +import java.util.Comparator; +import java.util.List; public class WorkstationOptimization implements VillagerOptimizerModule, Listener { private final VillagerManager villagerManager; private final Config config; private final boolean shouldLog, shouldNotifyPlayer; + private final double search_radius; protected WorkstationOptimization() { this.villagerManager = VillagerOptimizer.getVillagerManager(); this.config = VillagerOptimizer.getConfiguration(); this.config.addComment("optimization.methods.by-workstation.enable", """ - When enabled, villagers near a configured radius to a workstation specific to their profession\s + When enabled, villagers near a configured radius to a workstation specific to your config\s will be optimized. """); + 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 closest unoptimized villager to the player will be optimized. + """); this.shouldLog = config.getBoolean("optimization.methods.by-workstation.log", false); this.shouldNotifyPlayer = config.getBoolean("optimization.methods.by-workstation.notify-player", true); } @@ -43,10 +61,87 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene return config.enable_workstation_optimization; } - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - private void onEntityMove(EntityMoveEvent event) { - if (!event.getEntity().getType().equals(EntityType.VILLAGER)) return; + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onBlockPlace(BlockPlaceEvent event) { + Block placed = event.getBlock(); + if (!config.workstations_that_disable.contains(placed.getType())) return; + final Location workstationLoc = placed.getLocation(); + List nearbyUnoptimized = workstationLoc.getNearbyEntities(search_radius, search_radius, search_radius) + .stream().sorted(Comparator.comparingInt(entity -> { + if (entity.getType().equals(EntityType.VILLAGER)) { + Villager villager = (Villager) entity; + Villager.Profession profession = villager.getProfession(); + if ( + !profession.equals(Villager.Profession.NONE) + && !profession.equals(Villager.Profession.NITWIT) + && !villagerManager.getOrAdd(villager).isOptimized() + ) { + return (int) entity.getLocation().distance(workstationLoc); + } + } + return Integer.MAX_VALUE; + })).toList(); + if (nearbyUnoptimized.isEmpty()) return; + + WrappedVillager closest = villagerManager.getOrAdd((Villager) nearbyUnoptimized.get(0)); + + if (closest.setOptimization(OptimizationType.WORKSTATION)) { + if (shouldNotifyPlayer) { + Player player = event.getPlayer(); + VillagerOptimizer.getLang(player.locale()).workstation_optimize_success.forEach(line -> player.sendMessage(line + .replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(closest.villager().getProfession().toString().toLowerCase()).build()) + .replaceText(TextReplacementConfig.builder().matchLiteral("%workstation%").replacement(placed.getType().toString().toLowerCase()).build()) + )); + } + if (shouldLog) + VillagerOptimizer.getLog().info(event.getPlayer().getName() + " optimized a villager using workstation: '" + placed.getType().toString().toLowerCase() + "'"); + } else { + if (shouldNotifyPlayer) { + Player player = event.getPlayer(); + VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> player.sendMessage(line + .replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(closest.getOptimizeCooldown())).build()))); + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onBlockBreak(BlockBreakEvent event) { + Block placed = event.getBlock(); + if (!config.workstations_that_disable.contains(placed.getType())) return; + + final Location workstationLoc = placed.getLocation(); + + List nearbyOptimized = workstationLoc.getNearbyEntities(search_radius, search_radius, search_radius) + .stream().sorted(Comparator.comparingInt(entity -> { + if (entity.getType().equals(EntityType.VILLAGER)) { + Villager villager = (Villager) entity; + Villager.Profession profession = villager.getProfession(); + if ( + !profession.equals(Villager.Profession.NONE) + && !profession.equals(Villager.Profession.NITWIT) + && villagerManager.getOrAdd(villager).isOptimized() + ) { + return (int) entity.getLocation().distance(workstationLoc); + } + } + return Integer.MAX_VALUE; + })).toList(); + if (nearbyOptimized.isEmpty()) return; + + WrappedVillager closest = villagerManager.getOrAdd((Villager) nearbyOptimized.get(0)); + + if (closest.getOptimizationType().equals(OptimizationType.WORKSTATION)) { + if (shouldNotifyPlayer) { + Player player = event.getPlayer(); + VillagerOptimizer.getLang(player.locale()).workstation_unoptimize_success.forEach(line -> player.sendMessage(line + .replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(closest.villager().getProfession().toString().toLowerCase()).build()) + .replaceText(TextReplacementConfig.builder().matchLiteral("%workstation%").replacement(placed.getType().toString().toLowerCase()).build()) + )); + } + if (shouldLog) + VillagerOptimizer.getLog().info(event.getPlayer().getName() + " unoptimized a villager by breaking workstation: '" + placed.getType().toString().toLowerCase() + "'"); + } } } diff --git a/src/main/resources/lang/en_us.yml b/src/main/resources/lang/en_us.yml index 14f2537..23a6df7 100644 --- a/src/main/resources/lang/en_us.yml +++ b/src/main/resources/lang/en_us.yml @@ -13,7 +13,7 @@ messages: optimize-on-cooldown: - "You need to wait %time% until you can optimize this villager again." unoptimize-success: - - "Successfully unoptimized villager by moving it off a %blocktype% block." + - "Successfully unoptimized %villagertype% villager by removing %blocktype%." workstation: optimize-success: - "%villagertype% villager successfully optimized using workstation block %blocktype%."