diff --git a/pom.xml b/pom.xml index ff01e3e..9c0ffd2 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ me.xginko VillagerOptimizer - 1.5.1 + 1.5.2 jar VillagerOptimizer @@ -23,7 +23,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.11.0 + 3.12.1 ${java.version} ${java.version} diff --git a/src/main/java/me/xginko/villageroptimizer/modules/VillagerChunkLimit.java b/src/main/java/me/xginko/villageroptimizer/modules/VillagerChunkLimit.java index 172613a..0b4279d 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/VillagerChunkLimit.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/VillagerChunkLimit.java @@ -6,6 +6,7 @@ import me.xginko.villageroptimizer.VillagerCache; import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.utils.GenericUtil; +import me.xginko.villageroptimizer.utils.LocationUtil; import org.bukkit.Chunk; import org.bukkit.Server; import org.bukkit.World; @@ -22,6 +23,7 @@ import org.jetbrains.annotations.NotNull; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; public class VillagerChunkLimit implements VillagerOptimizerModule, Listener { @@ -49,9 +51,19 @@ public class VillagerChunkLimit implements VillagerOptimizerModule, Listener { this.log_enabled = config.getBoolean(configPath() + ".log-removals", true); this.non_optimized_max_per_chunk = config.getInt(configPath() + ".unoptimized.max-per-chunk", 20, "The maximum amount of unoptimized villagers per chunk."); - this.non_optimized_removal_priority = config.getList(configPath() + ".unoptimized.removal-priority", Arrays.asList( - "NONE", "NITWIT", "SHEPHERD", "FISHERMAN", "BUTCHER", "CARTOGRAPHER", "LEATHERWORKER", - "FLETCHER", "MASON", "FARMER", "ARMORER", "TOOLSMITH", "WEAPONSMITH", "CLERIC", "LIBRARIAN"), + final List defaults = Stream.of( + "NONE", "NITWIT", "SHEPHERD", "FISHERMAN", "BUTCHER", "CARTOGRAPHER", "LEATHERWORKER", + "FLETCHER", "MASON", "FARMER", "ARMORER", "TOOLSMITH", "WEAPONSMITH", "CLERIC", "LIBRARIAN") + .filter(profession -> { + try { + // Make sure no scary warnings appear when creating config defaults + Villager.Profession.valueOf(profession); + return true; + } catch (IllegalArgumentException e) { + return false; + } + }).collect(Collectors.toList()); + this.non_optimized_removal_priority = config.getList(configPath() + ".unoptimized.removal-priority", defaults, "Professions that are in the top of the list are going to be scheduled for removal first.\n" + "Use enums from https://jd.papermc.io/paper/1.20/org/bukkit/entity/Villager.Profession.html") .stream() @@ -69,9 +81,7 @@ public class VillagerChunkLimit implements VillagerOptimizerModule, Listener { .collect(Collectors.toList()); this.optimized_max_per_chunk = config.getInt(configPath() + ".optimized.max-per-chunk", 60, "The maximum amount of optimized villagers per chunk."); - this.optimized_removal_priority = config.getList(configPath() + ".optimized.removal-priority", Arrays.asList( - "NONE", "NITWIT", "SHEPHERD", "FISHERMAN", "BUTCHER", "CARTOGRAPHER", "LEATHERWORKER", - "FLETCHER", "MASON", "FARMER", "ARMORER", "TOOLSMITH", "WEAPONSMITH", "CLERIC", "LIBRARIAN")) + this.optimized_removal_priority = config.getList(configPath() + ".optimized.removal-priority", defaults) .stream() .map(configuredProfession -> { try { @@ -164,8 +174,8 @@ public class VillagerChunkLimit implements VillagerOptimizerModule, Listener { scheduler.runAtEntity(villager, kill -> { villager.remove(); if (log_enabled) { - info("Removed unoptimized villager with profession '" + villager.getProfession() + "' at " + - GenericUtil.formatLocation(villager.getLocation())); + info("Removed unoptimized villager with profession '" + GenericUtil.formatEnum(villager.getProfession()) + "' at " + + LocationUtil.toString(villager.getLocation())); } }); } @@ -186,8 +196,8 @@ public class VillagerChunkLimit implements VillagerOptimizerModule, Listener { villager.remove(); if (log_enabled) { - info("Removed optimized villager with profession '" + villager.getProfession() + "' at " + - GenericUtil.formatLocation(villager.getLocation())); + info("Removed optimized villager with profession '" + GenericUtil.formatEnum(villager.getProfession()) + "' at " + + LocationUtil.toString(villager.getLocation())); } }); } diff --git a/src/main/java/me/xginko/villageroptimizer/modules/gameplay/EnableLeashingVillagers.java b/src/main/java/me/xginko/villageroptimizer/modules/gameplay/EnableLeashingVillagers.java index 45f8ba4..1c066d8 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/gameplay/EnableLeashingVillagers.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/gameplay/EnableLeashingVillagers.java @@ -5,7 +5,7 @@ import me.xginko.villageroptimizer.VillagerCache; import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; -import me.xginko.villageroptimizer.utils.GenericUtil; +import me.xginko.villageroptimizer.utils.LocationUtil; import org.bukkit.GameMode; import org.bukkit.Material; import org.bukkit.entity.EntityType; @@ -90,7 +90,7 @@ public class EnableLeashingVillagers implements VillagerOptimizerModule, Listene handItem.subtract(1); // Manually consume for survival players if (log_enabled) { - info(player.getName() + " leashed a villager at " + GenericUtil.formatLocation(villager.getLocation())); + info(player.getName() + " leashed a villager at " + LocationUtil.toString(villager.getLocation())); } }); } diff --git a/src/main/java/me/xginko/villageroptimizer/modules/gameplay/RestockOptimizedTrades.java b/src/main/java/me/xginko/villageroptimizer/modules/gameplay/RestockOptimizedTrades.java index 50672f3..75b5544 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/gameplay/RestockOptimizedTrades.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/gameplay/RestockOptimizedTrades.java @@ -2,6 +2,7 @@ package me.xginko.villageroptimizer.modules.gameplay; import me.xginko.villageroptimizer.VillagerCache; import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.utils.LocationUtil; import me.xginko.villageroptimizer.wrapper.WrappedVillager; import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.enums.Permissions; @@ -85,7 +86,7 @@ public class RestockOptimizedTrades implements VillagerOptimizerModule, Listener } if (log_enabled) { - info("Restocked optimized villager at " + GenericUtil.formatLocation(wVillager.villager().getLocation())); + info("Restocked optimized villager at " + LocationUtil.toString(wVillager.villager().getLocation())); } } } diff --git a/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByBlock.java b/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByBlock.java index 6bd6ac2..f81d54c 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByBlock.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByBlock.java @@ -2,7 +2,6 @@ package me.xginko.villageroptimizer.modules.optimization; import me.xginko.villageroptimizer.VillagerCache; import me.xginko.villageroptimizer.VillagerOptimizer; -import me.xginko.villageroptimizer.wrapper.WrappedVillager; import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.enums.OptimizationType; import me.xginko.villageroptimizer.enums.Permissions; @@ -11,7 +10,8 @@ import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent; import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; import me.xginko.villageroptimizer.utils.GenericUtil; import me.xginko.villageroptimizer.utils.KyoriUtil; -import net.kyori.adventure.text.Component; +import me.xginko.villageroptimizer.utils.LocationUtil; +import me.xginko.villageroptimizer.wrapper.WrappedVillager; import net.kyori.adventure.text.TextReplacementConfig; import org.bukkit.Location; import org.bukkit.Material; @@ -26,7 +26,10 @@ import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockPlaceEvent; import java.time.Duration; -import java.util.*; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -110,7 +113,8 @@ public class OptimizeByBlock implements VillagerOptimizerModule, Listener { for (Villager villager : blockLoc.getNearbyEntitiesByType(Villager.class, search_radius)) { final Villager.Profession profession = villager.getProfession(); if (profession.equals(Villager.Profession.NONE) || profession.equals(Villager.Profession.NITWIT)) continue; - final double distance = villager.getLocation().distanceSquared(blockLoc); + + final double distance = LocationUtil.relDistanceSquared3D(villager.getLocation(), blockLoc); if (distance >= closestDistance) continue; final WrappedVillager wVillager = villagerCache.getOrAdd(villager); @@ -137,19 +141,19 @@ public class OptimizeByBlock implements VillagerOptimizerModule, Listener { if (notify_player) { final TextReplacementConfig vilProfession = TextReplacementConfig.builder() .matchLiteral("%vil_profession%") - .replacement(closestOptimizableVillager.villager().getProfession().toString().toLowerCase()) + .replacement(GenericUtil.formatEnum(closestOptimizableVillager.villager().getProfession())) .build(); final TextReplacementConfig placedMaterial = TextReplacementConfig.builder() .matchLiteral("%blocktype%") - .replacement(placed.getType().toString().toLowerCase()) + .replacement(GenericUtil.formatEnum(placed.getType())) .build(); VillagerOptimizer.getLang(player.locale()).block_optimize_success .forEach(line -> KyoriUtil.sendMessage(player, line.replaceText(vilProfession).replaceText(placedMaterial))); } if (log_enabled) { - VillagerOptimizer.getPrefixedLogger().info(Component.text(player.getName() + " optimized villager by block at " + - GenericUtil.formatLocation(closestOptimizableVillager.villager().getLocation())).color(GenericUtil.COLOR)); + info(player.getName() + " optimized villager at " + + LocationUtil.toString(closestOptimizableVillager.villager().getLocation())); } } else { closestOptimizableVillager.sayNo(); @@ -177,7 +181,7 @@ public class OptimizeByBlock implements VillagerOptimizerModule, Listener { double closestDistance = Double.MAX_VALUE; for (Villager villager : blockLoc.getNearbyEntitiesByType(Villager.class, search_radius)) { - final double distance = villager.getLocation().distanceSquared(blockLoc); + final double distance = LocationUtil.relDistanceSquared3D(villager.getLocation(), blockLoc); if (distance >= closestDistance) continue; final WrappedVillager wVillager = villagerCache.getOrAdd(villager); @@ -202,19 +206,19 @@ public class OptimizeByBlock implements VillagerOptimizerModule, Listener { if (notify_player) { final TextReplacementConfig vilProfession = TextReplacementConfig.builder() .matchLiteral("%vil_profession%") - .replacement(closestOptimizedVillager.villager().getProfession().toString().toLowerCase()) + .replacement(GenericUtil.formatEnum(closestOptimizedVillager.villager().getProfession())) .build(); final TextReplacementConfig brokenMaterial = TextReplacementConfig.builder() .matchLiteral("%blocktype%") - .replacement(broken.getType().toString().toLowerCase()) + .replacement(GenericUtil.formatEnum(broken.getType())) .build(); VillagerOptimizer.getLang(player.locale()).block_unoptimize_success .forEach(line -> KyoriUtil.sendMessage(player, line.replaceText(vilProfession).replaceText(brokenMaterial))); } if (log_enabled) { - VillagerOptimizer.getPrefixedLogger().info(Component.text(player.getName() + " unoptimized villager by block at " + - GenericUtil.formatLocation(closestOptimizedVillager.villager().getLocation())).color(GenericUtil.COLOR)); + info(player.getName() + " unoptimized villager using " + GenericUtil.formatEnum(broken.getType()) + + LocationUtil.toString(closestOptimizedVillager.villager().getLocation())); } } } \ No newline at end of file diff --git a/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByNametag.java b/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByNametag.java index 9612f2f..43b1ea0 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByNametag.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByNametag.java @@ -2,6 +2,7 @@ package me.xginko.villageroptimizer.modules.optimization; import me.xginko.villageroptimizer.VillagerCache; import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.utils.LocationUtil; import me.xginko.villageroptimizer.wrapper.WrappedVillager; import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.enums.OptimizationType; @@ -123,7 +124,7 @@ public class OptimizeByNametag implements VillagerOptimizerModule, Listener { if (log_enabled) { info(player.getName() + " optimized villager using nametag '" + nameTagPlainText + "' at " + - GenericUtil.formatLocation(wVillager.villager().getLocation())); + LocationUtil.toString(wVillager.villager().getLocation())); } } else { event.setCancelled(true); @@ -156,7 +157,7 @@ public class OptimizeByNametag implements VillagerOptimizerModule, Listener { if (log_enabled) { info(player.getName() + " unoptimized villager using nametag '" + nameTagPlainText + "' at " + - GenericUtil.formatLocation(wVillager.villager().getLocation())); + LocationUtil.toString(wVillager.villager().getLocation())); } } } diff --git a/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByWorkstation.java b/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByWorkstation.java index e366ab2..770ec2c 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByWorkstation.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/optimization/OptimizeByWorkstation.java @@ -3,6 +3,7 @@ package me.xginko.villageroptimizer.modules.optimization; import com.tcoded.folialib.impl.ServerImplementation; import me.xginko.villageroptimizer.VillagerCache; import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.utils.LocationUtil; import me.xginko.villageroptimizer.wrapper.WrappedVillager; import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.enums.OptimizationType; @@ -108,7 +109,7 @@ public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener if (villager.getProfession() != workstationProfession) continue; WrappedVillager wrapped = villagerCache.getOrAdd(villager); if (wrapped.getJobSite() == null) continue; - if (wrapped.getJobSite().distanceSquared(workstationLoc) > 1) continue; + if (LocationUtil.relDistanceSquared3D(wrapped.getJobSite(), workstationLoc) > 1) continue; if (!wrapped.canOptimize(cooldown_millis) && !player.hasPermission(Permissions.Bypass.WORKSTATION_COOLDOWN.get())) { wrapped.sayNo(); @@ -139,19 +140,19 @@ public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener if (notify_player) { final TextReplacementConfig vilProfession = TextReplacementConfig.builder() .matchLiteral("%vil_profession%") - .replacement(wrapped.villager().getProfession().toString().toLowerCase()) + .replacement(GenericUtil.formatEnum(wrapped.villager().getProfession())) .build(); final TextReplacementConfig placedWorkstation = TextReplacementConfig.builder() .matchLiteral("%blocktype%") - .replacement(placed.getType().toString().toLowerCase()) + .replacement(GenericUtil.formatEnum(placed.getType())) .build(); VillagerOptimizer.getLang(player.locale()).workstation_optimize_success .forEach(line -> KyoriUtil.sendMessage(player, line.replaceText(vilProfession).replaceText(placedWorkstation))); } if (log_enabled) { - info(player.getName() + " optimized villager using workstation " + placed.getType() + " at " + - GenericUtil.formatLocation(wrapped.villager().getLocation())); + info(player.getName() + " optimized villager using workstation " + GenericUtil.formatEnum(placed.getType()) + " at " + + LocationUtil.toString(wrapped.villager().getLocation())); } taskComplete.set(true); @@ -176,7 +177,7 @@ public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener for (Villager villager : workstationLoc.getNearbyEntitiesByType(Villager.class, search_radius)) { if (!villager.getProfession().equals(workstationProfession)) continue; - final double distance = villager.getLocation().distanceSquared(workstationLoc); + final double distance = LocationUtil.relDistanceSquared3D(villager.getLocation(), workstationLoc); if (distance >= closestDistance) continue; WrappedVillager wrapped = villagerCache.getOrAdd(villager); @@ -203,19 +204,19 @@ public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener if (notify_player) { final TextReplacementConfig vilProfession = TextReplacementConfig.builder() .matchLiteral("%vil_profession%") - .replacement(closestOptimized.villager().getProfession().toString().toLowerCase()) + .replacement(GenericUtil.formatEnum(closestOptimized.villager().getProfession())) .build(); final TextReplacementConfig brokenWorkstation = TextReplacementConfig.builder() .matchLiteral("%blocktype%") - .replacement(broken.getType().toString().toLowerCase()) + .replacement(GenericUtil.formatEnum(broken.getType())) .build(); VillagerOptimizer.getLang(player.locale()).workstation_unoptimize_success .forEach(line -> KyoriUtil.sendMessage(player, line.replaceText(vilProfession).replaceText(brokenWorkstation))); } if (log_enabled) { - info(player.getName() + " unoptimized villager using workstation " + broken.getType() + " at " + - GenericUtil.formatLocation(closestOptimized.villager().getLocation())); + info(player.getName() + " unoptimized villager using workstation " + GenericUtil.formatEnum(broken.getType()) + " at " + + LocationUtil.toString(closestOptimized.villager().getLocation())); } } } \ No newline at end of file diff --git a/src/main/java/me/xginko/villageroptimizer/utils/GenericUtil.java b/src/main/java/me/xginko/villageroptimizer/utils/GenericUtil.java index 4782fc1..bd4ed49 100644 --- a/src/main/java/me/xginko/villageroptimizer/utils/GenericUtil.java +++ b/src/main/java/me/xginko/villageroptimizer/utils/GenericUtil.java @@ -3,15 +3,14 @@ package me.xginko.villageroptimizer.utils; import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextDecoration; -import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.bukkit.Chunk; -import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.Villager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.time.Duration; +import java.util.Locale; public class GenericUtil { @@ -34,8 +33,16 @@ public class GenericUtil { } } - public static @NotNull String formatLocation(@NotNull Location location) { - return "[" + location.getWorld().getName() + "] x=" + location.getBlockX() + ", y=" + location.getBlockY() + ", z=" + location.getBlockZ(); + public static @NotNull String formatEnum(@NotNull Enum input) { + // Turn something like "REDSTONE_TORCH" into "redstone torch" + String[] lowercaseWords = input.name().toLowerCase(Locale.ROOT).split("-"); + for (int i = 0; i < lowercaseWords.length; i++) { + String word = lowercaseWords[i]; + // Capitalize first letter for each word + lowercaseWords[i] = word.substring(0, 1).toUpperCase() + word.substring(1); + } + // return as nice string + return String.join(" ", lowercaseWords); } private static boolean specificChunkLoadedMethodAvailable = true; diff --git a/src/main/java/me/xginko/villageroptimizer/utils/LocationUtil.java b/src/main/java/me/xginko/villageroptimizer/utils/LocationUtil.java new file mode 100644 index 0000000..9b26371 --- /dev/null +++ b/src/main/java/me/xginko/villageroptimizer/utils/LocationUtil.java @@ -0,0 +1,61 @@ +package me.xginko.villageroptimizer.utils; + +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.util.NumberConversions; +import org.jetbrains.annotations.NotNull; + +public class LocationUtil { + + public static @NotNull String toString(@NotNull Location location) { + return "[" + location.getWorld().getName() + "] x=" + location.getBlockX() + ", y=" + location.getBlockY() + ", z=" + location.getBlockZ(); + } + + public static double relDistanceSquared2D(@NotNull Location from, @NotNull Location to) { + World.Environment toEnv = to.getWorld().getEnvironment(); + World.Environment fromEnv = from.getWorld().getEnvironment(); + double toX = to.getX(); + double toZ = to.getZ(); + double fromX = from.getX(); + double fromZ = from.getZ(); + + // Make sure distance is relative since one block in the nether equates to 8 in the overworld/end + if (toEnv != fromEnv) { + if (fromEnv == World.Environment.NETHER) { + fromX *= 8; + fromZ *= 8; + } + if (toEnv == World.Environment.NETHER) { + toX *= 8; + toZ *= 8; + } + } + + return NumberConversions.square(toX - fromX) + NumberConversions.square(toZ - fromZ); + } + + public static double relDistanceSquared3D(@NotNull Location from, @NotNull Location to) { + double toY = to.getY(); + double fromY = from.getY(); + + // Clamp Y levels the same way minecraft would for portal creation logic + if (fromY < to.getWorld().getMinHeight()) + fromY = to.getWorld().getMinHeight(); + if (fromY > to.getWorld().getMaxHeight()) + fromY = to.getWorld().getMaxHeight(); + if (toY < from.getWorld().getMinHeight()) + toY = from.getWorld().getMinHeight(); + if (toY > from.getWorld().getMaxHeight()) + toY = from.getWorld().getMaxHeight(); + + return relDistanceSquared2D(from, to) + NumberConversions.square(toY - fromY); + } + + public static double relDistance2D(@NotNull Location from, @NotNull Location to) { + return Math.sqrt(relDistanceSquared2D(from, to)); + } + + public static double relDistance3D(@NotNull Location from, @NotNull Location to) { + return Math.sqrt(relDistanceSquared3D(from, to)); + } +}