diff --git a/src/main/java/me/xginko/villageroptimizer/modules/BlockOptimization.java b/src/main/java/me/xginko/villageroptimizer/modules/BlockOptimization.java deleted file mode 100644 index 3e95f36..0000000 --- a/src/main/java/me/xginko/villageroptimizer/modules/BlockOptimization.java +++ /dev/null @@ -1,216 +0,0 @@ -package me.xginko.villageroptimizer.modules; - -import me.xginko.villageroptimizer.VillagerOptimizer; -import me.xginko.villageroptimizer.config.Config; -import me.xginko.villageroptimizer.enums.OptimizationType; -import me.xginko.villageroptimizer.VillagerCache; -import me.xginko.villageroptimizer.enums.Permissions; -import me.xginko.villageroptimizer.WrappedVillager; -import me.xginko.villageroptimizer.utils.CommonUtils; -import me.xginko.villageroptimizer.utils.LogUtils; -import net.kyori.adventure.text.TextReplacementConfig; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -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 org.bukkit.event.player.PlayerInteractEntityEvent; - -import java.util.HashSet; -import java.util.List; - -public class BlockOptimization implements VillagerOptimizerModule, Listener { - - /* - * TODO: Think of better logic than just checking under the villagers feet for block - * -> use workstation optimization logic - * */ - - private final VillagerCache villagerCache; - private final HashSet blocks_that_disable = new HashSet<>(4); - private final boolean shouldLog, shouldNotifyPlayer; - private final int maxVillagers; - private final long cooldown; - - protected BlockOptimization() { - shouldEnable(); - this.villagerCache = VillagerOptimizer.getCache(); - Config config = VillagerOptimizer.getConfiguration(); - config.addComment("optimization-methods.block-optimization.enable", """ - When enabled, villagers standing on the configured specific blocks will become optimized once a\s - player interacts with them. If the block is broken or moved, the villager will become unoptimized\s - again once a player interacts with the villager afterwards."""); - config.getList("optimization-methods.block-optimization.materials", List.of( - "LAPIS_BLOCK", "GLOWSTONE", "IRON_BLOCK" - ), "Values here need to be valid bukkit Material enums for your server version." - ).forEach(configuredMaterial -> { - try { - Material disableBlock = Material.valueOf(configuredMaterial); - this.blocks_that_disable.add(disableBlock); - } catch (IllegalArgumentException e) { - LogUtils.materialNotRecognized("block-optimization", configuredMaterial); - } - }); - this.cooldown = config.getInt("optimization-methods.block-optimization.optimize-cooldown-seconds", 600, """ - Cooldown in seconds until a villager can be optimized again by using specific blocks. \s - Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.""") * 1000L; - this.maxVillagers = config.getInt("optimization-methods.block-optimization.max-villagers-per-block", 3, - "How many villagers can be optimized at once by placing a block under them."); - this.shouldNotifyPlayer = config.getBoolean("optimization-methods.block-optimization.notify-player", true, - "Sends players a message when they successfully optimized a villager."); - this.shouldLog = config.getBoolean("optimization-methods.block-optimization.log", false); - } - - @Override - public void enable() { - VillagerOptimizer plugin = VillagerOptimizer.getInstance(); - plugin.getServer().getPluginManager().registerEvents(this, plugin); - } - - @Override - public void disable() { - HandlerList.unregisterAll(this); - } - - @Override - public boolean shouldEnable() { - return VillagerOptimizer.getConfiguration().getBoolean("optimization-methods.block-optimization.enable", false); - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - private void onBlockPlace(BlockPlaceEvent event) { - Block placed = event.getBlock(); - if (!blocks_that_disable.contains(placed.getType())) return; - Player player = event.getPlayer(); - if (!player.hasPermission(Permissions.Optimize.BLOCK.get())) return; - - int counter = 0; - for (Entity entity : placed.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,1,0.5)) { - if (counter >= maxVillagers) return; - if (!entity.getType().equals(EntityType.VILLAGER)) continue; - - WrappedVillager wVillager = villagerCache.getOrAdd((Villager) entity); - final OptimizationType type = wVillager.getOptimizationType(); - if (!type.equals(OptimizationType.NONE) && !type.equals(OptimizationType.COMMAND)) continue; - - if (wVillager.canOptimize(cooldown) || player.hasPermission(Permissions.Bypass.BLOCK_COOLDOWN.get())) { - wVillager.setOptimization(OptimizationType.BLOCK); - wVillager.saveOptimizeTime(); - counter++; - if (shouldNotifyPlayer) { - final String villagerType = wVillager.villager().getProfession().toString().toLowerCase(); - final String placedType = placed.getType().toString().toLowerCase(); - VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(line -> player.sendMessage(line - .replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(villagerType).build()) - .replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(placedType).build()) - )); - } - if (shouldLog) - VillagerOptimizer.getLog().info("Villager was optimized by block at "+wVillager.villager().getLocation()); - } else { - wVillager.villager().shakeHead(); - if (shouldNotifyPlayer) { - final String timeLeft = CommonUtils.formatTime(wVillager.getOptimizeCooldownMillis(cooldown)); - VillagerOptimizer.getLang(player.locale()).block_on_optimize_cooldown.forEach(line -> player.sendMessage(line - .replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(timeLeft).build()))); - } - } - } - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - private void onBlockBreak(BlockBreakEvent event) { - Block broken = event.getBlock(); - if (!blocks_that_disable.contains(broken.getType())) return; - Player player = event.getPlayer(); - if (!player.hasPermission(Permissions.Optimize.BLOCK.get())) return; - - int counter = 0; - for (Entity entity : broken.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,1,0.5)) { - if (!entity.getType().equals(EntityType.VILLAGER)) continue; - - WrappedVillager wVillager = villagerCache.getOrAdd((Villager) entity); - - if (wVillager.getOptimizationType().equals(OptimizationType.BLOCK)) { - if (counter >= maxVillagers) return; - - wVillager.setOptimization(OptimizationType.NONE); - - if (shouldNotifyPlayer) { - final String villagerType = wVillager.villager().getProfession().toString().toLowerCase(); - final String brokenType = broken.getType().toString().toLowerCase(); - VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(line -> player.sendMessage(line - .replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(villagerType).build()) - .replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(brokenType).build()) - )); - } - if (shouldLog) - VillagerOptimizer.getLog().info("Villager unoptimized because no longer standing on optimization block at "+wVillager.villager().getLocation()); - } - } - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - private void onPlayerInteract(PlayerInteractEntityEvent event) { - Entity interacted = event.getRightClicked(); - if (!interacted.getType().equals(EntityType.VILLAGER)) return; - Player player = event.getPlayer(); - if (!player.hasPermission(Permissions.Optimize.BLOCK.get())) return; - - WrappedVillager wVillager = villagerCache.getOrAdd((Villager) interacted); - final Location entityLegs = interacted.getLocation(); - - if ( - blocks_that_disable.contains(entityLegs.getBlock().getType()) // check for blocks inside the entity's legs because of slabs and sink-in blocks - || blocks_that_disable.contains(entityLegs.clone().subtract(0,1,0).getBlock().getType()) - ) { - if (wVillager.isOptimized()) return; - - if (wVillager.canOptimize(cooldown) || player.hasPermission(Permissions.Bypass.BLOCK_COOLDOWN.get())) { - wVillager.setOptimization(OptimizationType.BLOCK); - wVillager.saveOptimizeTime(); - if (shouldNotifyPlayer) { - final String vilType = wVillager.villager().getProfession().toString().toLowerCase(); - final String blockType = entityLegs.getBlock().getType().toString().toLowerCase(); - VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(line -> player.sendMessage(line - .replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(vilType).build()) - .replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(blockType).build()) - )); - } - if (shouldLog) - VillagerOptimizer.getLog().info("Villager was optimized by block at "+wVillager.villager().getLocation()); - } else { - wVillager.villager().shakeHead(); - if (shouldNotifyPlayer) { - final String timeLeft = CommonUtils.formatTime(wVillager.getOptimizeCooldownMillis(cooldown)); - VillagerOptimizer.getLang(player.locale()).block_on_optimize_cooldown.forEach(line -> player.sendMessage(line - .replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(timeLeft).build())) - ); - } - } - } else { - if (wVillager.getOptimizationType().equals(OptimizationType.BLOCK)) { - wVillager.setOptimization(OptimizationType.NONE); - if (shouldNotifyPlayer) { - final String villagerType = wVillager.villager().getProfession().toString().toLowerCase(); - final String blockType = entityLegs.getBlock().getType().toString().toLowerCase(); - VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(line -> player.sendMessage(line - .replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(villagerType).build()) - .replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(blockType).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/VillagerOptimizerModule.java b/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java index cd5f9b8..1edc9cb 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java @@ -1,5 +1,9 @@ package me.xginko.villageroptimizer.modules; +import me.xginko.villageroptimizer.modules.optimizations.BlockOptimization; +import me.xginko.villageroptimizer.modules.optimizations.NametagOptimization; +import me.xginko.villageroptimizer.modules.optimizations.WorkstationOptimization; + import java.util.HashSet; public interface VillagerOptimizerModule { @@ -14,16 +18,17 @@ public interface VillagerOptimizerModule { modules.forEach(VillagerOptimizerModule::disable); modules.clear(); - modules.add(new BlockOptimization()); - modules.add(new LevelVillagers()); modules.add(new NametagOptimization()); + modules.add(new BlockOptimization()); + modules.add(new WorkstationOptimization()); + + modules.add(new LevelVillagers()); modules.add(new PreventUnoptimizedTrading()); modules.add(new PreventVillagerDamage()); modules.add(new PreventVillagerTargetting()); modules.add(new RestockTrades()); modules.add(new VillagerChunkLimit()); modules.add(new VillagersSpawnAdult()); - modules.add(new WorkstationOptimization()); modules.forEach(module -> { if (module.shouldEnable()) module.enable(); diff --git a/src/main/java/me/xginko/villageroptimizer/modules/optimizations/BlockOptimization.java b/src/main/java/me/xginko/villageroptimizer/modules/optimizations/BlockOptimization.java new file mode 100644 index 0000000..6139795 --- /dev/null +++ b/src/main/java/me/xginko/villageroptimizer/modules/optimizations/BlockOptimization.java @@ -0,0 +1,179 @@ +package me.xginko.villageroptimizer.modules.optimizations; + +import me.xginko.villageroptimizer.VillagerCache; +import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.config.Config; +import me.xginko.villageroptimizer.enums.OptimizationType; +import me.xginko.villageroptimizer.enums.Permissions; +import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; +import me.xginko.villageroptimizer.utils.CommonUtils; +import me.xginko.villageroptimizer.utils.LogUtils; +import net.kyori.adventure.text.TextReplacementConfig; +import org.bukkit.Location; +import org.bukkit.Material; +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.HashSet; +import java.util.List; + +public class BlockOptimization implements VillagerOptimizerModule, Listener { + + private final VillagerCache villagerCache; + private final HashSet blocks_that_disable = new HashSet<>(4); + private final boolean shouldLog, shouldNotifyPlayer; + private final long cooldown; + private final double search_radius; + + public BlockOptimization() { + shouldEnable(); + this.villagerCache = VillagerOptimizer.getCache(); + Config config = VillagerOptimizer.getConfiguration(); + config.addComment("optimization-methods.block-optimization.enable", """ + When enabled, villagers standing on the configured specific blocks will become optimized once a\s + player interacts with them. If the block is broken or moved, the villager will become unoptimized\s + again once a player interacts with the villager afterwards."""); + config.getList("optimization-methods.block-optimization.materials", List.of( + "LAPIS_BLOCK", "GLOWSTONE", "IRON_BLOCK" + ), "Values here need to be valid bukkit Material enums for your server version." + ).forEach(configuredMaterial -> { + try { + Material disableBlock = Material.valueOf(configuredMaterial); + this.blocks_that_disable.add(disableBlock); + } catch (IllegalArgumentException e) { + LogUtils.materialNotRecognized("block-optimization", configuredMaterial); + } + }); + this.cooldown = config.getInt("optimization-methods.block-optimization.optimize-cooldown-seconds", 600, """ + Cooldown in seconds until a villager can be optimized again by using specific blocks. \s + Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.""") * 1000L; + this.search_radius = config.getDouble("optimization-methods.block-optimization.search-radius-in-blocks", 2.0, """ + The radius in blocks a villager can be away from the player when he places an optimize block.\s + The closest unoptimized villager to the player will be optimized.""") / 2; + this.shouldNotifyPlayer = config.getBoolean("optimization-methods.block-optimization.notify-player", true, + "Sends players a message when they successfully optimized a villager."); + this.shouldLog = config.getBoolean("optimization-methods.block-optimization.log", false); + } + + @Override + public void enable() { + VillagerOptimizer plugin = VillagerOptimizer.getInstance(); + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + @Override + public void disable() { + HandlerList.unregisterAll(this); + } + + @Override + public boolean shouldEnable() { + return VillagerOptimizer.getConfiguration().getBoolean("optimization-methods.block-optimization.enable", false); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + private void onBlockPlace(BlockPlaceEvent event) { + Block placed = event.getBlock(); + if (!blocks_that_disable.contains(placed.getType())) return; + Player player = event.getPlayer(); + if (!player.hasPermission(Permissions.Optimize.BLOCK.get())) return; + + final Location blockLoc = placed.getLocation(); + WrappedVillager closestOptimizableVillager = null; + double closestDistance = Double.MAX_VALUE; + + for (Entity entity : blockLoc.getNearbyEntities(search_radius, search_radius, search_radius)) { + if (!entity.getType().equals(EntityType.VILLAGER)) continue; + Villager villager = (Villager) entity; + final Villager.Profession profession = villager.getProfession(); + if (profession.equals(Villager.Profession.NONE) || profession.equals(Villager.Profession.NITWIT)) continue; + + WrappedVillager wVillager = villagerCache.getOrAdd(villager); + final double distance = entity.getLocation().distance(blockLoc); + + if (distance < closestDistance) { + final OptimizationType type = wVillager.getOptimizationType(); + if (type.equals(OptimizationType.NONE) || type.equals(OptimizationType.COMMAND)) { + closestOptimizableVillager = wVillager; + closestDistance = distance; + } + } + } + + if (closestOptimizableVillager == null) return; + + if (closestOptimizableVillager.canOptimize(cooldown) || player.hasPermission(Permissions.Bypass.BLOCK_COOLDOWN.get())) { + closestOptimizableVillager.setOptimization(OptimizationType.BLOCK); + closestOptimizableVillager.saveOptimizeTime(); + if (shouldNotifyPlayer) { + final String villagerType = closestOptimizableVillager.villager().getProfession().toString().toLowerCase(); + final String placedType = placed.getType().toString().toLowerCase(); + VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(line -> player.sendMessage(line + .replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(villagerType).build()) + .replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(placedType).build()) + )); + } + if (shouldLog) + VillagerOptimizer.getLog().info("Villager was optimized by block at "+closestOptimizableVillager.villager().getLocation()); + } else { + closestOptimizableVillager.villager().shakeHead(); + if (shouldNotifyPlayer) { + final String timeLeft = CommonUtils.formatTime(closestOptimizableVillager.getOptimizeCooldownMillis(cooldown)); + VillagerOptimizer.getLang(player.locale()).block_on_optimize_cooldown.forEach(line -> player.sendMessage(line + .replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(timeLeft).build()))); + } + } + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + private void onBlockBreak(BlockBreakEvent event) { + Block broken = event.getBlock(); + if (!blocks_that_disable.contains(broken.getType())) return; + Player player = event.getPlayer(); + if (!player.hasPermission(Permissions.Optimize.BLOCK.get())) return; + + final Location blockLoc = broken.getLocation(); + WrappedVillager closestOptimizedVillager = null; + double closestDistance = Double.MAX_VALUE; + + for (Entity entity : blockLoc.getNearbyEntities(search_radius, search_radius, search_radius)) { + if (!entity.getType().equals(EntityType.VILLAGER)) continue; + Villager villager = (Villager) entity; + + WrappedVillager wVillager = villagerCache.getOrAdd(villager); + final double distance = entity.getLocation().distance(blockLoc); + + if (distance < closestDistance) { + final OptimizationType type = wVillager.getOptimizationType(); + if (type.equals(OptimizationType.WORKSTATION) || type.equals(OptimizationType.COMMAND)) { + closestOptimizedVillager = wVillager; + closestDistance = distance; + } + } + } + + if (closestOptimizedVillager == null) return; + + closestOptimizedVillager.setOptimization(OptimizationType.NONE); + if (shouldNotifyPlayer) { + final String villagerType = closestOptimizedVillager.villager().getProfession().toString().toLowerCase(); + final String brokenType = broken.getType().toString().toLowerCase(); + VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(line -> player.sendMessage(line + .replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(villagerType).build()) + .replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(brokenType).build()) + )); + } + if (shouldLog) + VillagerOptimizer.getLog().info("Villager unoptimized because no longer standing on optimization block at "+closestOptimizedVillager.villager().getLocation()); + } +} diff --git a/src/main/java/me/xginko/villageroptimizer/modules/NametagOptimization.java b/src/main/java/me/xginko/villageroptimizer/modules/optimizations/NametagOptimization.java similarity index 97% rename from src/main/java/me/xginko/villageroptimizer/modules/NametagOptimization.java rename to src/main/java/me/xginko/villageroptimizer/modules/optimizations/NametagOptimization.java index d9d2b5d..cb40e36 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/NametagOptimization.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/optimizations/NametagOptimization.java @@ -1,4 +1,4 @@ -package me.xginko.villageroptimizer.modules; +package me.xginko.villageroptimizer.modules.optimizations; import io.papermc.paper.event.player.PlayerNameEntityEvent; import me.xginko.villageroptimizer.VillagerOptimizer; @@ -7,6 +7,7 @@ import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.enums.OptimizationType; import me.xginko.villageroptimizer.enums.Permissions; import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; import me.xginko.villageroptimizer.utils.CommonUtils; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextReplacementConfig; @@ -31,7 +32,7 @@ public class NametagOptimization implements VillagerOptimizerModule, Listener { private final boolean shouldLog, shouldNotifyPlayer, consumeNametag; private final long cooldown; - protected NametagOptimization() { + public NametagOptimization() { shouldEnable(); this.villagerCache = VillagerOptimizer.getCache(); Config config = VillagerOptimizer.getConfiguration(); diff --git a/src/main/java/me/xginko/villageroptimizer/modules/WorkstationOptimization.java b/src/main/java/me/xginko/villageroptimizer/modules/optimizations/WorkstationOptimization.java similarity index 98% rename from src/main/java/me/xginko/villageroptimizer/modules/WorkstationOptimization.java rename to src/main/java/me/xginko/villageroptimizer/modules/optimizations/WorkstationOptimization.java index 057cc9b..ee093a3 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/WorkstationOptimization.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/optimizations/WorkstationOptimization.java @@ -1,4 +1,4 @@ -package me.xginko.villageroptimizer.modules; +package me.xginko.villageroptimizer.modules.optimizations; import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.VillagerCache; @@ -6,6 +6,7 @@ import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.enums.OptimizationType; import me.xginko.villageroptimizer.enums.Permissions; import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; import me.xginko.villageroptimizer.utils.CommonUtils; import me.xginko.villageroptimizer.utils.LogUtils; import net.kyori.adventure.text.TextReplacementConfig; @@ -37,7 +38,7 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene private final long cooldown; private final double search_radius; - protected WorkstationOptimization() { + public WorkstationOptimization() { shouldEnable(); this.villagerCache = VillagerOptimizer.getCache(); Config config = VillagerOptimizer.getConfiguration();