fix a bug that would kick players with an IllegalArgumentException when using workstation optimization

improve logging for some modules
improve formatting of enums
This commit is contained in:
xGinko 2024-04-28 16:42:36 +02:00
parent 4582cabc31
commit f43f9898f1
9 changed files with 129 additions and 44 deletions

View File

@ -6,7 +6,7 @@
<groupId>me.xginko</groupId>
<artifactId>VillagerOptimizer</artifactId>
<version>1.5.1</version>
<version>1.5.2</version>
<packaging>jar</packaging>
<name>VillagerOptimizer</name>
@ -23,7 +23,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<version>3.12.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>

View File

@ -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<String> 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()));
}
});
}

View File

@ -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()));
}
});
}

View File

@ -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()));
}
}
}

View File

@ -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()));
}
}
}

View File

@ -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()));
}
}
}

View File

@ -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()));
}
}
}

View File

@ -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;

View File

@ -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));
}
}