make project use a single jar

This commit is contained in:
xGinko 2024-01-09 12:25:23 +01:00
parent 013b09bf1e
commit f8179ec975
81 changed files with 359 additions and 3489 deletions

View File

@ -1,99 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>me.xginko.VillagerOptimizer</groupId>
<artifactId>VillagerOptimizer</artifactId>
<version>1.0.2</version>
</parent>
<artifactId>1.16.5</artifactId>
<name>${project.parent.artifactId}-${project.parent.version}--${project.artifactId}</name>
<packaging>jar</packaging>
<properties>
<java.version>16</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>com.github.benmanes.caffeine</pattern>
<shadedPattern>me.xginko.villageroptimizer.caffeine</shadedPattern>
</relocation>
<relocation>
<pattern>org.bstats</pattern>
<shadedPattern>me.xginko.villageroptimizer.bstats</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
<repositories>
<repository>
<id>papermc-repo</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>sonatype</id>
<url>https://oss.sonatype.org/content/groups/public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.destroystokyo.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.16.5-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-minimessage</artifactId>
<version>4.14.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-plain</artifactId>
<version>4.14.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -1,179 +0,0 @@
package me.xginko.villageroptimizer;
import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand;
import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.config.LanguageCache;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.bstats.bukkit.Metrics;
import org.bukkit.NamespacedKey;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.*;
import java.util.jar.JarFile;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
public final class VillagerOptimizer extends JavaPlugin {
private static VillagerOptimizer instance;
private static VillagerCache villagerCache;
private static HashMap<String, LanguageCache> languageCacheMap;
private static Config config;
private static ConsoleCommandSender console;
private static Logger logger;
public final static Style plugin_style = Style.style(TextColor.color(102,255,230), TextDecoration.BOLD);
@Override
public void onEnable() {
instance = this;
logger = getLogger();
console = getServer().getConsoleSender();
console.sendMessage(Component.text("╭────────────────────────────────────────────────────────────╮").style(plugin_style));
console.sendMessage(Component.text("│ │").style(plugin_style));
console.sendMessage(Component.text("│ │").style(plugin_style));
console.sendMessage(Component.text("│ _ __ _ __ __ │").style(plugin_style));
console.sendMessage(Component.text("│ | | / /(_)/ // /___ _ ___ _ ___ ____ │").style(plugin_style));
console.sendMessage(Component.text("│ | |/ // // // // _ `// _ `// -_)/ __/ │").style(plugin_style));
console.sendMessage(Component.text("│ |___//_//_//_/ \\_,_/ \\_, / \\__//_/ │").style(plugin_style));
console.sendMessage(Component.text("│ ____ __ _ /___/_ │").style(plugin_style));
console.sendMessage(Component.text("│ / __ \\ ___ / /_ (_)__ _ (_)___ ___ ____ │").style(plugin_style));
console.sendMessage(Component.text("│ / /_/ // _ \\/ __// // ' \\ / //_ // -_)/ __/ │").style(plugin_style));
console.sendMessage(Component.text("\\____// .__/\\__//_//_/_/_//_/ /__/\\__//_/ │").style(plugin_style));
console.sendMessage(Component.text("│ /_/ by xGinko │").style(plugin_style));
console.sendMessage(Component.text("│ │").style(plugin_style));
console.sendMessage(Component.text("│ │").style(plugin_style));
console.sendMessage(Component.text("")
.style(plugin_style).append(Component.text("https://github.com/xGinko/VillagerOptimizer")
.color(NamedTextColor.GRAY)).append(Component.text("").style(plugin_style)));
console.sendMessage(Component.text("│ │").style(plugin_style));
console.sendMessage(Component.text("│ │").style(plugin_style));
console.sendMessage(Component.text("")
.style(plugin_style).append(Component.text(" ➤ Loading Translations...").style(plugin_style))
.append(Component.text("").style(plugin_style)));
reloadLang(true);
console.sendMessage(Component.text("")
.style(plugin_style).append(Component.text(" ➤ Loading Config...").style(plugin_style))
.append(Component.text("").style(plugin_style)));
reloadConfiguration();
console.sendMessage(Component.text("")
.style(plugin_style).append(Component.text(" ✓ Done.").color(NamedTextColor.WHITE).decorate(TextDecoration.BOLD))
.append(Component.text("").style(plugin_style)));
console.sendMessage(Component.text("│ │").style(plugin_style));
console.sendMessage(Component.text("│ │").style(plugin_style));
console.sendMessage(Component.text("╰────────────────────────────────────────────────────────────╯").style(plugin_style));
new Metrics(this, 19954);
}
public static VillagerOptimizer getInstance() {
return instance;
}
public static NamespacedKey getKey(String key) {
return new NamespacedKey(instance, key);
}
public static Config getConfiguration() {
return config;
}
public static VillagerCache getCache() {
return villagerCache;
}
public static ConsoleCommandSender getConsole() {
return console;
}
public static Logger getLog() {
return logger;
}
public static LanguageCache getLang(Locale locale) {
return getLang(locale.toString().toLowerCase());
}
public static LanguageCache getLang(CommandSender commandSender) {
return commandSender instanceof Player player ? getLang(player.locale()) : getLang(config.default_lang);
}
public static LanguageCache getLang(String lang) {
if (!config.auto_lang) return languageCacheMap.get(config.default_lang.toString().toLowerCase());
return languageCacheMap.getOrDefault(lang.replace("-", "_"), languageCacheMap.get(config.default_lang.toString().toLowerCase()));
}
public void reloadPlugin() {
reloadLang(false);
reloadConfiguration();
}
private void reloadConfiguration() {
try {
config = new Config();
villagerCache = new VillagerCache(config.cache_keep_time_seconds);
VillagerOptimizerCommand.reloadCommands();
VillagerOptimizerModule.reloadModules();
config.saveConfig();
} catch (Exception e) {
logger.severe("Error loading config! - " + e.getLocalizedMessage());
e.printStackTrace();
}
}
private void reloadLang(boolean startup) {
languageCacheMap = new HashMap<>();
try {
File langDirectory = new File(getDataFolder() + File.separator + "lang");
Files.createDirectories(langDirectory.toPath());
for (String fileName : getDefaultLanguageFiles()) {
final String localeString = fileName.substring(fileName.lastIndexOf(File.separator) + 1, fileName.lastIndexOf('.'));
if (startup) console.sendMessage(
Component.text("").style(plugin_style)
.append(Component.text(" "+localeString).color(NamedTextColor.WHITE).decorate(TextDecoration.BOLD))
.append(Component.text("").style(plugin_style)));
else logger.info(String.format("Found language file for %s", localeString));
languageCacheMap.put(localeString, new LanguageCache(localeString));
}
final Pattern langPattern = Pattern.compile("([a-z]{1,3}_[a-z]{1,3})(\\.yml)", Pattern.CASE_INSENSITIVE);
for (File langFile : langDirectory.listFiles()) {
final Matcher langMatcher = langPattern.matcher(langFile.getName());
if (langMatcher.find()) {
String localeString = langMatcher.group(1).toLowerCase();
if (!languageCacheMap.containsKey(localeString)) { // make sure it wasn't a default file that we already loaded
if (startup) console.sendMessage(
Component.text("").style(plugin_style)
.append(Component.text(" "+localeString).color(NamedTextColor.WHITE).decorate(TextDecoration.BOLD))
.append(Component.text("").style(plugin_style)));
else logger.info(String.format("Found language file for %s", localeString));
languageCacheMap.put(localeString, new LanguageCache(localeString));
}
}
}
} catch (Exception e) {
if (startup) console.sendMessage(
Component.text("").style(plugin_style)
.append(Component.text("LANG ERROR").color(NamedTextColor.RED).decorate(TextDecoration.BOLD))
.append(Component.text("").style(plugin_style)));
else logger.severe("Error loading language files! Language files will not reload to avoid errors, make sure to correct this before restarting the server!");
e.printStackTrace();
}
}
private Set<String> getDefaultLanguageFiles() {
try (final JarFile pluginJarFile = new JarFile(this.getFile())) {
return pluginJarFile.stream()
.map(ZipEntry::getName)
.filter(name -> name.startsWith("lang" + File.separator) && name.endsWith(".yml"))
.collect(Collectors.toSet());
} catch (IOException e) {
logger.severe("Failed getting default lang files! - "+e.getLocalizedMessage());
return Collections.emptySet();
}
}
}

View File

@ -1,45 +0,0 @@
package me.xginko.villageroptimizer.commands.villageroptimizer.subcommands;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.commands.SubCommand;
import me.xginko.villageroptimizer.enums.Permissions;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender;
import org.bukkit.event.HandlerList;
public class DisableSubCmd extends SubCommand {
@Override
public String getLabel() {
return "disable";
}
@Override
public TextComponent getDescription() {
return Component.text("Disable all plugin tasks and listeners.").color(NamedTextColor.GRAY);
}
@Override
public TextComponent getSyntax() {
return Component.text("/villageroptimizer disable").color(VillagerOptimizer.plugin_style.color());
}
@Override
public void perform(CommandSender sender, String[] args) {
if (sender.hasPermission(Permissions.Commands.DISABLE.get())) {
sender.sendMessage(Component.text("Disabling VillagerOptimizer...").color(NamedTextColor.RED));
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
HandlerList.unregisterAll(plugin);
plugin.getServer().getScheduler().cancelTasks(plugin);
VillagerOptimizerModule.modules.clear();
VillagerOptimizer.getCache().cacheMap().clear();
sender.sendMessage(Component.text("Disabled all plugin listeners and tasks.").color(NamedTextColor.GREEN));
sender.sendMessage(Component.text("You can enable the plugin again using the reload command.").color(NamedTextColor.YELLOW));
} else {
sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission);
}
}
}

View File

@ -1,146 +0,0 @@
package me.xginko.villageroptimizer.config;
import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
import me.xginko.villageroptimizer.VillagerOptimizer;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class Config {
private final @NotNull ConfigFile config;
public final @NotNull Locale default_lang;
public final boolean auto_lang;
public final long cache_keep_time_seconds;
public Config() throws Exception {
// Create plugin folder first if it does not exist yet
File pluginFolder = VillagerOptimizer.getInstance().getDataFolder();
if (!pluginFolder.exists() && !pluginFolder.mkdir())
VillagerOptimizer.getLog().severe("Failed to create plugin directory.");
// Load config.yml with ConfigMaster
this.config = ConfigFile.loadConfig(new File(pluginFolder, "config.yml"));
structureConfig();
this.default_lang = Locale.forLanguageTag(
getString("general.default-language", "en_us",
"The default language that will be used if auto-language is false or no matching language file was found.")
.replace("_", "-"));
this.auto_lang = getBoolean("general.auto-language", true,
"If set to true, will display messages based on client language");
this.cache_keep_time_seconds = getInt("general.cache-keep-time-seconds", 30,
"The amount of time in seconds a villager will be kept in the plugin's cache.");
}
public void saveConfig() {
try {
this.config.save();
} catch (Exception e) {
VillagerOptimizer.getLog().severe("Failed to save config file! - " + e.getLocalizedMessage());
}
}
private void structureConfig() {
this.config.addDefault("config-version", 1.00);
this.createTitledSection("General", "general");
this.createTitledSection("Optimization", "optimization-methods");
this.config.addDefault("optimization-methods.commands.unoptimizevillagers", null);
this.config.addComment("optimization-methods.commands", """
If you want to disable commands, negate the following permissions:\s
villageroptimizer.cmd.optimize\s
villageroptimizer.cmd.unoptimize
""");
this.config.addDefault("optimization-methods.nametag-optimization.enable", true);
this.createTitledSection("Villager Chunk Limit", "villager-chunk-limit");
this.createTitledSection("Gameplay", "gameplay");
this.config.addDefault("gameplay.restock-optimized-trades", null);
this.config.addDefault("gameplay.level-optimized-profession", null);
this.config.addDefault("gameplay.rename-optimized-villagers.enable", true);
this.config.addDefault("gameplay.villagers-spawn-as-adults.enable", false);
this.config.addDefault("gameplay.prevent-trading-with-unoptimized.enable", false);
this.config.addDefault("gameplay.prevent-entities-from-targeting-optimized.enable", true);
this.config.addDefault("gameplay.prevent-damage-to-optimized.enable", true);
}
public void createTitledSection(@NotNull String title, @NotNull String path) {
this.config.addSection(title);
this.config.addDefault(path, null);
}
public @NotNull ConfigFile master() {
return config;
}
public boolean getBoolean(@NotNull String path, boolean def, @NotNull String comment) {
this.config.addDefault(path, def, comment);
return this.config.getBoolean(path, def);
}
public boolean getBoolean(@NotNull String path, boolean def) {
this.config.addDefault(path, def);
return this.config.getBoolean(path, def);
}
public @NotNull String getString(@NotNull String path, @NotNull String def, @NotNull String comment) {
this.config.addDefault(path, def, comment);
return this.config.getString(path, def);
}
public @NotNull String getString(@NotNull String path, @NotNull String def) {
this.config.addDefault(path, def);
return this.config.getString(path, def);
}
public double getDouble(@NotNull String path, @NotNull Double def, @NotNull String comment) {
this.config.addDefault(path, def, comment);
return this.config.getDouble(path, def);
}
public double getDouble(@NotNull String path, @NotNull Double def) {
this.config.addDefault(path, def);
return this.config.getDouble(path, def);
}
public int getInt(@NotNull String path, int def, @NotNull String comment) {
this.config.addDefault(path, def, comment);
return this.config.getInteger(path, def);
}
public int getInt(@NotNull String path, int def) {
this.config.addDefault(path, def);
return this.config.getInteger(path, def);
}
public @NotNull List<String> getList(@NotNull String path, @NotNull List<String> def, @NotNull String comment) {
this.config.addDefault(path, def, comment);
return this.config.getStringList(path);
}
public @NotNull List<String> getList(@NotNull String path, @NotNull List<String> def) {
this.config.addDefault(path, def);
return this.config.getStringList(path);
}
public @NotNull ConfigSection getConfigSection(@NotNull String path, @NotNull Map<String, Object> defaultKeyValue) {
this.config.addDefault(path, null);
this.config.makeSectionLenient(path);
defaultKeyValue.forEach((string, object) -> this.config.addExample(path+"."+string, object));
return this.config.getConfigSection(path);
}
public @NotNull ConfigSection getConfigSection(@NotNull String path, @NotNull Map<String, Object> defaultKeyValue, @NotNull String comment) {
this.config.addDefault(path, null, comment);
this.config.makeSectionLenient(path);
defaultKeyValue.forEach((string, object) -> this.config.addExample(path+"."+string, object));
return this.config.getConfigSection(path);
}
public void addComment(@NotNull String path, @NotNull String comment) {
this.config.addComment(path, comment);
}
}

View File

@ -1,45 +0,0 @@
package me.xginko.villageroptimizer.enums;
public class Permissions {
public enum Commands {
VERSION("villageroptimizer.cmd.version"),
RELOAD("villageroptimizer.cmd.reload"),
DISABLE("villageroptimizer.cmd.disable"),
OPTIMIZE_RADIUS("villageroptimizer.cmd.optimize"),
UNOPTIMIZE_RADIUS("villageroptimizer.cmd.unoptimize");
private final String permission;
Commands(String permission) {
this.permission = permission;
}
public String get() {
return permission;
}
}
public enum Optimize {
NAMETAG("villageroptimizer.optimize.nametag"),
BLOCK("villageroptimizer.optimize.block"),
WORKSTATION("villageroptimizer.optimize.workstation");
private final String permission;
Optimize(String permission) {
this.permission = permission;
}
public String get() {
return permission;
}
}
public enum Bypass {
TRADE_PREVENTION("villageroptimizer.bypass.tradeprevention"),
RESTOCK_COOLDOWN("villageroptimizer.bypass.restockcooldown"),
NAMETAG_COOLDOWN("villageroptimizer.bypass.nametagcooldown"),
BLOCK_COOLDOWN("villageroptimizer.bypass.blockcooldown"),
WORKSTATION_COOLDOWN("villageroptimizer.bypass.workstationcooldown"),
COMMAND_COOLDOWN("villageroptimizer.bypass.commandcooldown");
private final String permission;
Bypass(String permission) {
this.permission = permission;
}
public String get() {
return permission;
}
}
}

View File

@ -1,79 +0,0 @@
package me.xginko.villageroptimizer.events;
import me.xginko.villageroptimizer.WrappedVillager;
import me.xginko.villageroptimizer.enums.OptimizationType;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class VillagerOptimizeEvent extends Event implements Cancellable {
private static final @NotNull HandlerList handlers = new HandlerList();
private final @NotNull WrappedVillager wrappedVillager;
private @NotNull OptimizationType type;
private final @Nullable Player whoOptimised;
private boolean isCancelled = false;
public VillagerOptimizeEvent(@NotNull WrappedVillager wrappedVillager, @NotNull OptimizationType type, @Nullable Player whoOptimised, boolean isAsync) throws IllegalArgumentException {
super(isAsync);
this.wrappedVillager = wrappedVillager;
this.whoOptimised = whoOptimised;
if (type.equals(OptimizationType.NONE)) {
throw new IllegalArgumentException("OptimizationType can't be NONE.");
} else {
this.type = type;
}
}
public VillagerOptimizeEvent(@NotNull WrappedVillager wrappedVillager, @NotNull OptimizationType type, @Nullable Player whoOptimised) throws IllegalArgumentException {
this.wrappedVillager = wrappedVillager;
this.whoOptimised = whoOptimised;
if (type.equals(OptimizationType.NONE)) {
throw new IllegalArgumentException("OptimizationType can't be NONE.");
} else {
this.type = type;
}
}
public @NotNull WrappedVillager getWrappedVillager() {
return wrappedVillager;
}
public @NotNull OptimizationType getOptimizationType() {
return type;
}
public void setOptimizationType(@NotNull OptimizationType type) throws IllegalArgumentException {
if (type.equals(OptimizationType.NONE)) {
throw new IllegalArgumentException("OptimizationType can't be NONE.");
} else {
this.type = type;
}
}
public @Nullable Player getWhoOptimised() {
return whoOptimised;
}
@Override
public void setCancelled(boolean cancel) {
isCancelled = cancel;
}
@Override
public boolean isCancelled() {
return isCancelled;
}
@Override
public @NotNull HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@ -1,171 +0,0 @@
package me.xginko.villageroptimizer.modules;
import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.utils.LogUtil;
import org.bukkit.Chunk;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Villager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Level;
public class VillagerChunkLimit implements VillagerOptimizerModule, Listener, Runnable {
private final Server server;
private final VillagerCache villagerCache;
private final List<Villager.Profession> non_optimized_removal_priority = new ArrayList<>(16);
private final List<Villager.Profession> optimized_removal_priority = new ArrayList<>(16);
private final long check_period;
private final int non_optimized_max_per_chunk, optimized_max_per_chunk;
private final boolean log_enabled;
protected VillagerChunkLimit() {
shouldEnable();
this.server = VillagerOptimizer.getInstance().getServer();
this.villagerCache = VillagerOptimizer.getCache();
Config config = VillagerOptimizer.getConfiguration();
config.addComment("villager-chunk-limit.enable", """
Checks chunks for too many villagers and removes excess villagers based on priority.""");
this.check_period = config.getInt("villager-chunk-limit.check-period-in-ticks", 600, """
Check all loaded chunks every X ticks. 1 second = 20 ticks\s
A shorter delay in between checks is more efficient but is also more resource intense.\s
A larger delay is less resource intense but could become inefficient.""");
this.log_enabled = config.getBoolean("villager-chunk-limit.log-removals", false);
this.non_optimized_max_per_chunk = config.getInt("villager-chunk-limit.unoptimized.max-per-chunk", 20,
"The maximum amount of unoptimized villagers per chunk.");
config.getList("villager-chunk-limit.unoptimized.removal-priority", List.of(
"NONE", "NITWIT", "SHEPHERD", "FISHERMAN", "BUTCHER", "CARTOGRAPHER", "LEATHERWORKER",
"FLETCHER", "MASON", "FARMER", "ARMORER", "TOOLSMITH", "WEAPONSMITH", "CLERIC", "LIBRARIAN"
), """
Professions that are in the top of the list are going to be scheduled for removal first.\s
Use enums from https://jd.papermc.io/paper/1.20/org/bukkit/entity/Villager.Profession.html"""
).forEach(configuredProfession -> {
try {
Villager.Profession profession = Villager.Profession.valueOf(configuredProfession);
this.non_optimized_removal_priority.add(profession);
} catch (IllegalArgumentException e) {
LogUtil.moduleLog(Level.WARNING, "villager-chunk-limit.unoptimized",
"Villager profession '"+configuredProfession+"' not recognized. " +
"Make sure you're using the correct profession enums from https://jd.papermc.io/paper/1.20/org/bukkit/entity/Villager.Profession.html.");
}
});
this.optimized_max_per_chunk = config.getInt("villager-chunk-limit.optimized.max-per-chunk", 60,
"The maximum amount of optimized villagers per chunk.");
config.getList("villager-chunk-limit.optimized.removal-priority", List.of(
"NONE", "NITWIT", "SHEPHERD", "FISHERMAN", "BUTCHER", "CARTOGRAPHER", "LEATHERWORKER",
"FLETCHER", "MASON", "FARMER", "ARMORER", "TOOLSMITH", "WEAPONSMITH", "CLERIC", "LIBRARIAN"
)).forEach(configuredProfession -> {
try {
Villager.Profession profession = Villager.Profession.valueOf(configuredProfession);
this.optimized_removal_priority.add(profession);
} catch (IllegalArgumentException e) {
LogUtil.moduleLog(Level.WARNING, "villager-chunk-limit.optimized",
"Villager profession '"+configuredProfession+"' not recognized. " +
"Make sure you're using the correct profession enums from https://jd.papermc.io/paper/1.20/org/bukkit/entity/Villager.Profession.html.");
}
});
}
@Override
public void enable() {
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
server.getPluginManager().registerEvents(this, plugin);
server.getScheduler().scheduleSyncRepeatingTask(plugin, this, check_period, check_period);
}
@Override
public boolean shouldEnable() {
return VillagerOptimizer.getConfiguration().getBoolean("villager-chunk-limit.enable", false);
}
@Override
public void run() {
for (World world : server.getWorlds()) {
for (Chunk chunk : world.getLoadedChunks()) {
this.manageVillagerCount(chunk);
}
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onCreatureSpawn(CreatureSpawnEvent event) {
Entity spawned = event.getEntity();
if (spawned.getType().equals(EntityType.VILLAGER)) {
this.manageVillagerCount(spawned.getChunk());
}
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
private void onInteract(PlayerInteractEntityEvent event) {
Entity clicked = event.getRightClicked();
if (clicked.getType().equals(EntityType.VILLAGER)) {
this.manageVillagerCount(clicked.getChunk());
}
}
private void manageVillagerCount(@NotNull Chunk chunk) {
// Collect all optimized and unoptimized villagers in that chunk
List<Villager> optimized_villagers = new ArrayList<>();
List<Villager> not_optimized_villagers = new ArrayList<>();
for (Entity entity : chunk.getEntities()) {
if (entity.getType().equals(EntityType.VILLAGER)) {
Villager villager = (Villager) entity;
if (villagerCache.getOrAdd(villager).isOptimized()) {
optimized_villagers.add(villager);
} else {
not_optimized_villagers.add(villager);
}
}
}
// Check if there are more unoptimized villagers in that chunk than allowed
final int not_optimized_villagers_too_many = not_optimized_villagers.size() - non_optimized_max_per_chunk;
if (not_optimized_villagers_too_many > 0) {
// Sort villagers by profession priority
not_optimized_villagers.sort(Comparator.comparingInt(villager -> {
final Villager.Profession profession = villager.getProfession();
return non_optimized_removal_priority.contains(profession) ? non_optimized_removal_priority.indexOf(profession) : Integer.MAX_VALUE;
}));
// Remove prioritized villagers that are too many
for (int i = 0; i < not_optimized_villagers_too_many; i++) {
Villager villager = not_optimized_villagers.get(i);
villager.remove();
if (log_enabled) LogUtil.moduleLog(Level.INFO, "villager-chunk-limit",
"Removed unoptimized villager of profession type '"+villager.getProfession().name()+"' at "+villager.getLocation()
);
}
}
// Check if there are more optimized villagers in that chunk than allowed
final int optimized_villagers_too_many = optimized_villagers.size() - optimized_max_per_chunk;
if (optimized_villagers_too_many > 0) {
// Sort villagers by profession priority
optimized_villagers.sort(Comparator.comparingInt(villager -> {
final Villager.Profession profession = villager.getProfession();
return optimized_removal_priority.contains(profession) ? optimized_removal_priority.indexOf(profession) : Integer.MAX_VALUE;
}));
// Remove prioritized villagers that are too many
for (int i = 0; i < optimized_villagers_too_many; i++) {
Villager villager = optimized_villagers.get(i);
villager.remove();
if (log_enabled) LogUtil.moduleLog(Level.INFO, "villager-chunk-limit",
"Removed optimized villager of profession type '"+villager.getProfession().name()+"' at "+villager.getLocation()
);
}
}
}
}

View File

@ -1,43 +0,0 @@
package me.xginko.villageroptimizer.modules;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.modules.gameplay.*;
import me.xginko.villageroptimizer.modules.optimization.OptimizeByBlock;
import me.xginko.villageroptimizer.modules.optimization.OptimizeByNametag;
import me.xginko.villageroptimizer.modules.optimization.OptimizeByWorkstation;
import org.bukkit.event.HandlerList;
import java.util.HashSet;
public interface VillagerOptimizerModule {
void enable();
boolean shouldEnable();
HashSet<VillagerOptimizerModule> modules = new HashSet<>();
static void reloadModules() {
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
HandlerList.unregisterAll(plugin);
plugin.getServer().getScheduler().cancelTasks(plugin);
modules.clear();
modules.add(new OptimizeByNametag());
modules.add(new OptimizeByBlock());
modules.add(new OptimizeByWorkstation());
modules.add(new RestockOptimizedTrades());
modules.add(new LevelOptimizedProfession());
modules.add(new RenameOptimizedVillagers());
modules.add(new MakeVillagersSpawnAdult());
modules.add(new PreventUnoptimizedTrading());
modules.add(new PreventOptimizedTargeting());
modules.add(new PreventOptimizedDamage());
modules.add(new VillagerChunkLimit());
modules.forEach(module -> {
if (module.shouldEnable()) module.enable();
});
}
}

View File

@ -1,84 +0,0 @@
package me.xginko.villageroptimizer.modules.gameplay;
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.modules.VillagerOptimizerModule;
import me.xginko.villageroptimizer.utils.CommonUtil;
import net.kyori.adventure.text.TextReplacementConfig;
import org.bukkit.entity.Player;
import org.bukkit.entity.Villager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
public class LevelOptimizedProfession implements VillagerOptimizerModule, Listener {
private final VillagerOptimizer plugin;
private final VillagerCache villagerCache;
private final boolean notify_player;
private final long cooldown;
public LevelOptimizedProfession() {
shouldEnable();
this.plugin = VillagerOptimizer.getInstance();
this.villagerCache = VillagerOptimizer.getCache();
Config config = VillagerOptimizer.getConfiguration();
config.addComment("gameplay.level-optimized-profession", """
This is needed to allow optimized villagers to level up.\s
Temporarily enables the villagers AI to allow it to level up and then disables it again.""");
this.cooldown = config.getInt("gameplay.level-optimized-profession.level-check-cooldown-seconds", 5, """
Cooldown in seconds until the level of a villager will be checked and updated again.\s
Recommended to leave as is.""") * 1000L;
this.notify_player = config.getBoolean("gameplay.level-optimized-profession.notify-player", true,
"Tell players to wait when a villager is leveling up.");
}
@Override
public void enable() {
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
@Override
public boolean shouldEnable() {
return true;
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
private void onTradeScreenClose(InventoryCloseEvent event) {
if (
event.getInventory().getType().equals(InventoryType.MERCHANT)
&& event.getInventory().getHolder() instanceof Villager villager
) {
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
if (!wVillager.isOptimized()) return;
if (wVillager.canLevelUp(cooldown)) {
if (wVillager.calculateLevel() > villager.getVillagerLevel()) {
villager.addPotionEffect(new PotionEffect(PotionEffectType.SLOW, 120, 120, false, false));
villager.setAware(true);
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
villager.setAware(false);
wVillager.saveLastLevelUp();
}, 100L);
}
} else {
if (notify_player) {
Player player = (Player) event.getPlayer();
final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
.matchLiteral("%time%")
.replacement(CommonUtil.formatTime(wVillager.getLevelCooldownMillis(cooldown)))
.build();
VillagerOptimizer.getLang(player.locale()).villager_leveling_up.forEach(line -> player.sendMessage(line.replaceText(timeLeft)));
}
}
}
}
}

View File

@ -1,38 +0,0 @@
package me.xginko.villageroptimizer.modules.gameplay;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Villager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.CreatureSpawnEvent;
public class MakeVillagersSpawnAdult implements VillagerOptimizerModule, Listener {
public MakeVillagersSpawnAdult() {}
@Override
public void enable() {
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
@Override
public boolean shouldEnable() {
return VillagerOptimizer.getConfiguration().getBoolean("gameplay.villagers-spawn-as-adults.enable", false, """
Spawned villagers will immediately be adults.\s
This is to save some more resources as players don't have to keep unoptimized\s
villagers loaded because they have to wait for them to turn into adults before they can\s
optimize them.""");
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onVillagerSpawn(CreatureSpawnEvent event) {
if (event.getEntityType().equals(EntityType.VILLAGER)) {
Villager villager = (Villager) event.getEntity();
if (!villager.isAdult()) villager.setAdult();
}
}
}

View File

@ -1,65 +0,0 @@
package me.xginko.villageroptimizer.modules.gameplay;
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.LogUtil;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Villager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageEvent;
import java.util.Arrays;
import java.util.HashSet;
public class PreventOptimizedDamage implements VillagerOptimizerModule, Listener {
private final VillagerCache villagerCache;
private final HashSet<EntityDamageEvent.DamageCause> damage_causes_to_cancel = new HashSet<>();
public PreventOptimizedDamage() {
shouldEnable();
this.villagerCache = VillagerOptimizer.getCache();
Config config = VillagerOptimizer.getConfiguration();
config.addComment("gameplay.prevent-damage-to-optimized.enable",
"Configure what kind of damage you want to cancel for optimized villagers here.");
config.getList("gameplay.prevent-damage-to-optimized.damage-causes-to-cancel",
Arrays.stream(EntityDamageEvent.DamageCause.values()).map(Enum::name).sorted().toList(), """
These are all current entries in the game. Remove what you do not need blocked.\s
If you want a description or need to add a previously removed type, refer to:\s
https://jd.papermc.io/paper/1.20/org/bukkit/event/entity/EntityDamageEvent.DamageCause.html"""
).forEach(configuredDamageCause -> {
try {
EntityDamageEvent.DamageCause damageCause = EntityDamageEvent.DamageCause.valueOf(configuredDamageCause);
this.damage_causes_to_cancel.add(damageCause);
} catch (IllegalArgumentException e) {
LogUtil.damageCauseNotRecognized("prevent-damage-to-optimized", configuredDamageCause);
}
});
}
@Override
public void enable() {
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
@Override
public boolean shouldEnable() {
return VillagerOptimizer.getConfiguration().getBoolean("gameplay.prevent-damage-to-optimized.enable", true);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onDamageByEntity(EntityDamageEvent event) {
if (
event.getEntityType().equals(EntityType.VILLAGER)
&& damage_causes_to_cancel.contains(event.getCause())
&& villagerCache.getOrAdd((Villager) event.getEntity()).isOptimized()
) {
event.setCancelled(true);
}
}
}

View File

@ -1,73 +0,0 @@
package me.xginko.villageroptimizer.modules.gameplay;
import com.destroystokyo.paper.event.entity.EntityPathfindEvent;
import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Mob;
import org.bukkit.entity.Villager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityTargetEvent;
public class PreventOptimizedTargeting implements VillagerOptimizerModule, Listener {
private final VillagerCache villagerCache;
public PreventOptimizedTargeting() {
this.villagerCache = VillagerOptimizer.getCache();
}
@Override
public void enable() {
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
@Override
public boolean shouldEnable() {
return VillagerOptimizer.getConfiguration().getBoolean("gameplay.prevent-entities-from-targeting-optimized.enable", true,
"Prevents hostile entities from targeting optimized villagers.");
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onTarget(EntityTargetEvent event) {
// 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)
&& villagerCache.getOrAdd((Villager) target).isOptimized()
) {
event.setTarget(null);
event.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onEntityTargetVillager(EntityPathfindEvent event) {
Entity target = event.getTargetEntity();
if (
target != null
&& target.getType().equals(EntityType.VILLAGER)
&& villagerCache.getOrAdd((Villager) target).isOptimized()
) {
event.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onEntityAttackVillager(EntityDamageByEntityEvent event) {
if (
event.getEntityType().equals(EntityType.VILLAGER)
&& event.getDamager() instanceof Mob attacker
&& villagerCache.getOrAdd((Villager) event.getEntity()).isOptimized()
) {
attacker.setTarget(null);
}
}
}

View File

@ -1,74 +0,0 @@
package me.xginko.villageroptimizer.modules.gameplay;
import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.enums.Permissions;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
import org.bukkit.entity.Player;
import org.bukkit.entity.Villager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.inventory.TradeSelectEvent;
public class PreventUnoptimizedTrading implements VillagerOptimizerModule, Listener {
private final VillagerCache villagerCache;
private final boolean notify_player;
public PreventUnoptimizedTrading() {
shouldEnable();
this.villagerCache = VillagerOptimizer.getCache();
Config config = VillagerOptimizer.getConfiguration();
config.addComment("gameplay.prevent-trading-with-unoptimized.enable", """
Will prevent players from selecting and using trades of unoptimized villagers.\s
Use this if you have a lot of villagers and therefore want to force your players to optimize them.\s
Inventories can still be opened so players can move villagers around.""");
this.notify_player = config.getBoolean("gameplay.prevent-trading-with-unoptimized.notify-player", true,
"Sends players a message when they try to trade with an unoptimized villager.");
}
@Override
public void enable() {
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
@Override
public boolean shouldEnable() {
return VillagerOptimizer.getConfiguration().getBoolean("gameplay.prevent-trading-with-unoptimized.enable", false);
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
private void onTradeOpen(TradeSelectEvent event) {
Player player = (Player) event.getWhoClicked();
if (player.hasPermission(Permissions.Bypass.TRADE_PREVENTION.get())) return;
if (
event.getInventory().getType().equals(InventoryType.MERCHANT)
&& event.getInventory().getHolder() instanceof Villager villager
&& !villagerCache.getOrAdd(villager).isOptimized()
) {
event.setCancelled(true);
if (notify_player)
VillagerOptimizer.getLang(player.locale()).optimize_for_trading.forEach(player::sendMessage);
}
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
private void onInventoryClick(InventoryClickEvent event) {
Player player = (Player) event.getWhoClicked();
if (player.hasPermission(Permissions.Bypass.TRADE_PREVENTION.get())) return;
if (
event.getInventory().getType().equals(InventoryType.MERCHANT)
&& event.getInventory().getHolder() instanceof Villager villager
&& !villagerCache.getOrAdd(villager).isOptimized()
) {
event.setCancelled(true);
if (notify_player)
VillagerOptimizer.getLang(player.locale()).optimize_for_trading.forEach(player::sendMessage);
}
}
}

View File

@ -1,73 +0,0 @@
package me.xginko.villageroptimizer.modules.gameplay;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.WrappedVillager;
import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.events.VillagerOptimizeEvent;
import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.entity.Villager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
public class RenameOptimizedVillagers implements VillagerOptimizerModule, Listener {
private final VillagerOptimizer plugin;
private final Component optimized_name;
private final boolean overwrite_previous_name;
public RenameOptimizedVillagers() {
shouldEnable();
this.plugin = VillagerOptimizer.getInstance();
Config config = VillagerOptimizer.getConfiguration();
config.addComment("gameplay.rename-optimized-villagers.enable", """
Will change a villager's name to the name configured below when they are optimized.\s
These names will be removed when unoptimized again if they were not changed in the meantime.
""");
this.optimized_name = MiniMessage.miniMessage().deserialize(config.getString("gameplay.rename-optimized-villagers.optimized-name", "<green>Optimized",
"The name that will be used to mark optimized villagers. Uses MiniMessage format."));
this.overwrite_previous_name = config.getBoolean("gameplay.rename-optimized-villagers.overwrite-existing-name", false,
"If set to true, will rename even if the villager has already been named.");
}
@Override
public void enable() {
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
@Override
public boolean shouldEnable() {
return VillagerOptimizer.getConfiguration().getBoolean("gameplay.rename-optimized-villagers.enable", true);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onOptimize(VillagerOptimizeEvent event) {
WrappedVillager wVillager = event.getWrappedVillager();
Villager villager = wVillager.villager();
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
if (overwrite_previous_name || villager.customName() == null) {
villager.customName(optimized_name);
wVillager.memorizeName(optimized_name);
}
}, 10L);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onUnOptimize(VillagerUnoptimizeEvent event) {
WrappedVillager wVillager = event.getWrappedVillager();
Villager villager = wVillager.villager();
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
final Component currentName = villager.customName();
final Component memorizedName = wVillager.getMemorizedName();
if (currentName != null && currentName.equals(memorizedName))
villager.customName(null);
if (memorizedName != null)
wVillager.forgetName();
}, 10L);
}
}

View File

@ -1,74 +0,0 @@
package me.xginko.villageroptimizer.modules.gameplay;
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.Permissions;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
import me.xginko.villageroptimizer.utils.CommonUtil;
import net.kyori.adventure.text.TextReplacementConfig;
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.Listener;
import org.bukkit.event.player.PlayerInteractEntityEvent;
public class RestockOptimizedTrades implements VillagerOptimizerModule, Listener {
private final VillagerCache villagerCache;
private final long restock_delay_millis;
private final boolean log_enabled, notify_player;
public RestockOptimizedTrades() {
shouldEnable();
this.villagerCache = VillagerOptimizer.getCache();
Config config = VillagerOptimizer.getConfiguration();
config.addComment("gameplay.restock-optimized-trades", """
This is for automatic restocking of trades for optimized villagers. Optimized Villagers\s
don't have enough AI to restock their trades naturally, so this is here as a workaround.""");
this.restock_delay_millis = config.getInt("gameplay.restock-optimized-trades.delay-in-ticks", 1000,
"1 second = 20 ticks. There are 24.000 ticks in a single minecraft day.") * 50L;
this.notify_player = config.getBoolean("gameplay.restock-optimized-trades.notify-player", true,
"Sends the player a message when the trades were restocked on a clicked villager.");
this.log_enabled = config.getBoolean("gameplay.restock-optimized-trades.log", false);
}
@Override
public void enable() {
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
@Override
public boolean shouldEnable() {
return true;
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
private void onInteract(PlayerInteractEntityEvent event) {
if (!event.getRightClicked().getType().equals(EntityType.VILLAGER)) return;
WrappedVillager wVillager = villagerCache.getOrAdd((Villager) event.getRightClicked());
if (!wVillager.isOptimized()) return;
Player player = event.getPlayer();
final boolean player_bypassing = player.hasPermission(Permissions.Bypass.RESTOCK_COOLDOWN.get());
if (wVillager.canRestock(restock_delay_millis) || player_bypassing) {
wVillager.restock();
wVillager.saveRestockTime();
if (notify_player && !player_bypassing) {
final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
.matchLiteral("%time%")
.replacement(CommonUtil.formatTime(wVillager.getRestockCooldownMillis(restock_delay_millis)))
.build();
VillagerOptimizer.getLang(player.locale()).trades_restocked.forEach(line -> player.sendMessage(line.replaceText(timeLeft)));
}
if (log_enabled)
VillagerOptimizer.getLog().info("Restocked optimized villager at "+ wVillager.villager().getLocation());
}
}
}

View File

@ -1,193 +0,0 @@
package me.xginko.villageroptimizer.modules.optimization;
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.events.VillagerOptimizeEvent;
import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
import me.xginko.villageroptimizer.utils.CommonUtil;
import me.xginko.villageroptimizer.utils.LogUtil;
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.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import java.util.HashSet;
import java.util.List;
public class OptimizeByBlock implements VillagerOptimizerModule, Listener {
private final VillagerCache villagerCache;
private final HashSet<Material> blocks_that_disable = new HashSet<>(4);
private final long cooldown;
private final double search_radius;
private final boolean only_while_sneaking, notify_player, log_enabled;
public OptimizeByBlock() {
shouldEnable();
this.villagerCache = VillagerOptimizer.getCache();
Config config = VillagerOptimizer.getConfiguration();
config.addComment("optimization-methods.block-optimization.enable", """
When enabled, the closest villager standing near a configured block being placed will be optimized.\s
If a configured block is broken nearby, the closest villager will become unoptimized again.""");
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) {
LogUtil.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.only_while_sneaking = config.getBoolean("optimization-methods.block-optimization.only-when-sneaking", true,
"Only optimize/unoptimize by workstation when player is sneaking during place or break.");
this.notify_player = config.getBoolean("optimization-methods.block-optimization.notify-player", true,
"Sends players a message when they successfully optimized or unoptimized a villager.");
this.log_enabled = config.getBoolean("optimization-methods.block-optimization.log", false);
}
@Override
public void enable() {
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
@Override
public boolean shouldEnable() {
return VillagerOptimizer.getConfiguration().getBoolean("optimization-methods.block-optimization.enable", false);
}
@EventHandler(priority = EventPriority.HIGHEST, 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;
if (only_while_sneaking && !player.isSneaking()) 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 && wVillager.canOptimize(cooldown)) {
closestOptimizableVillager = wVillager;
closestDistance = distance;
}
}
if (closestOptimizableVillager == null) return;
if (closestOptimizableVillager.canOptimize(cooldown) || player.hasPermission(Permissions.Bypass.BLOCK_COOLDOWN.get())) {
VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(closestOptimizableVillager, OptimizationType.BLOCK, player, event.isAsynchronous());
if (!optimizeEvent.callEvent()) return;
closestOptimizableVillager.setOptimization(optimizeEvent.getOptimizationType());
closestOptimizableVillager.saveOptimizeTime();
if (notify_player) {
final TextReplacementConfig vilProfession = TextReplacementConfig.builder()
.matchLiteral("%vil_profession%")
.replacement(closestOptimizableVillager.villager().getProfession().toString().toLowerCase())
.build();
final TextReplacementConfig placedMaterial = TextReplacementConfig.builder()
.matchLiteral("%blocktype%")
.replacement(placed.getType().toString().toLowerCase())
.build();
VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(line -> player.sendMessage(line
.replaceText(vilProfession)
.replaceText(placedMaterial)
));
}
if (log_enabled)
VillagerOptimizer.getLog().info("Villager was optimized by block at "+closestOptimizableVillager.villager().getLocation());
} else {
if (notify_player) {
final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
.matchLiteral("%time%")
.replacement(CommonUtil.formatTime(closestOptimizableVillager.getOptimizeCooldownMillis(cooldown)))
.build();
VillagerOptimizer.getLang(player.locale()).block_on_optimize_cooldown.forEach(line -> player.sendMessage(line.replaceText(timeLeft)));
}
}
}
@EventHandler(priority = EventPriority.HIGHEST, 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;
if (only_while_sneaking && !player.isSneaking()) 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 && wVillager.isOptimized()) {
closestOptimizedVillager = wVillager;
closestDistance = distance;
}
}
if (closestOptimizedVillager == null) return;
VillagerUnoptimizeEvent unOptimizeEvent = new VillagerUnoptimizeEvent(closestOptimizedVillager, player, OptimizationType.BLOCK, event.isAsynchronous());
if (!unOptimizeEvent.callEvent()) return;
closestOptimizedVillager.setOptimization(OptimizationType.NONE);
if (notify_player) {
final TextReplacementConfig vilProfession = TextReplacementConfig.builder()
.matchLiteral("%vil_profession%")
.replacement(closestOptimizedVillager.villager().getProfession().toString().toLowerCase())
.build();
final TextReplacementConfig brokenMaterial = TextReplacementConfig.builder()
.matchLiteral("%blocktype%")
.replacement(broken.getType().toString().toLowerCase())
.build();
VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(line -> player.sendMessage(line
.replaceText(vilProfession)
.replaceText(brokenMaterial)
));
}
if (log_enabled)
VillagerOptimizer.getLog().info("Villager unoptimized because nearby optimization block broken at: "+closestOptimizedVillager.villager().getLocation());
}
}

View File

@ -1,126 +0,0 @@
package me.xginko.villageroptimizer.modules.optimization;
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.events.VillagerOptimizeEvent;
import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
import me.xginko.villageroptimizer.utils.CommonUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextReplacementConfig;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.Material;
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.Listener;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.HashSet;
import java.util.List;
public class OptimizeByNametag implements VillagerOptimizerModule, Listener {
private final VillagerCache villagerCache;
private final HashSet<String> nametags = new HashSet<>(4);
private final long cooldown;
private final boolean consume_nametag, notify_player, log_enabled;
public OptimizeByNametag() {
shouldEnable();
this.villagerCache = VillagerOptimizer.getCache();
Config config = VillagerOptimizer.getConfiguration();
config.addComment("optimization-methods.nametag-optimization.enable", """
Enable optimization by naming villagers to one of the names configured below.\s
Nametag optimized villagers will be unoptimized again when they are renamed to something else.""");
this.nametags.addAll(config.getList("optimization-methods.nametag-optimization.names", List.of("Optimize", "DisableAI"),
"Names are case insensitive, capital letters won't matter.").stream().map(String::toLowerCase).toList());
this.consume_nametag = config.getBoolean("optimization-methods.nametag-optimization.nametags-get-consumed", true,
"Enable or disable consumption of the used nametag item.");
this.cooldown = config.getInt("optimization-methods.nametag-optimization.optimize-cooldown-seconds", 600, """
Cooldown in seconds until a villager can be optimized again using a nametag.\s
Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.""") * 1000L;
this.notify_player = config.getBoolean("optimization-methods.nametag-optimization.notify-player", true,
"Sends players a message when they successfully optimized a villager.");
this.log_enabled = config.getBoolean("optimization-methods.nametag-optimization.log", false);
}
@Override
public void enable() {
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
@Override
public boolean shouldEnable() {
return VillagerOptimizer.getConfiguration().getBoolean("optimization-methods.nametag-optimization.enable", true);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
if (!event.getRightClicked().getType().equals(EntityType.VILLAGER)) return;
Player player = event.getPlayer();
if (!player.hasPermission(Permissions.Optimize.NAMETAG.get())) return;
ItemStack usedItem = player.getInventory().getItem(event.getHand());
if (usedItem == null || !usedItem.getType().equals(Material.NAME_TAG)) return;
ItemMeta meta = usedItem.getItemMeta();
if (!meta.hasDisplayName()) return;
// Get component name first, so we can manually name the villager when canceling the event to avoid item consumption.
Component newVillagerName = meta.displayName();
assert newVillagerName != null; // Legitimate since we checked for hasDisplayName()
final String name = PlainTextComponentSerializer.plainText().serialize(newVillagerName);
Villager villager = (Villager) event.getRightClicked();
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
if (nametags.contains(name.toLowerCase())) {
if (wVillager.canOptimize(cooldown) || player.hasPermission(Permissions.Bypass.NAMETAG_COOLDOWN.get())) {
VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(wVillager, OptimizationType.NAMETAG, player, event.isAsynchronous());
if (!optimizeEvent.callEvent()) return;
if (!consume_nametag) {
event.setCancelled(true);
villager.customName(newVillagerName);
}
wVillager.setOptimization(optimizeEvent.getOptimizationType());
wVillager.saveOptimizeTime();
if (notify_player)
VillagerOptimizer.getLang(player.locale()).nametag_optimize_success.forEach(player::sendMessage);
if (log_enabled)
VillagerOptimizer.getLog().info(player.getName() + " optimized a villager using nametag: '" + name + "'");
} else {
event.setCancelled(true);
if (notify_player) {
final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
.matchLiteral("%time%")
.replacement(CommonUtil.formatTime(wVillager.getOptimizeCooldownMillis(cooldown)))
.build();
VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> player.sendMessage(line.replaceText(timeLeft)));
}
}
} else {
if (wVillager.isOptimized()) {
VillagerUnoptimizeEvent unOptimizeEvent = new VillagerUnoptimizeEvent(wVillager, player, OptimizationType.NAMETAG, event.isAsynchronous());
if (!unOptimizeEvent.callEvent()) return;
wVillager.setOptimization(OptimizationType.NONE);
if (notify_player)
VillagerOptimizer.getLang(player.locale()).nametag_unoptimize_success.forEach(player::sendMessage);
if (log_enabled)
VillagerOptimizer.getLog().info(event.getPlayer().getName() + " disabled optimizations for a villager using nametag: '" + name + "'");
}
}
}
}

View File

@ -1,200 +0,0 @@
package me.xginko.villageroptimizer.modules.optimization;
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.events.VillagerOptimizeEvent;
import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
import me.xginko.villageroptimizer.utils.CommonUtil;
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.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener {
private final VillagerCache villagerCache;
private final long cooldown;
private final double search_radius;
private final boolean only_while_sneaking, log_enabled, notify_player;
public OptimizeByWorkstation() {
shouldEnable();
this.villagerCache = VillagerOptimizer.getCache();
Config config = VillagerOptimizer.getConfiguration();
config.addComment("optimization-methods.workstation-optimization.enable", """
When enabled, the closest villager near a matching workstation being placed will be optimized.\s
If a nearby matching workstation is broken, the villager will become unoptimized again.""");
this.search_radius = config.getDouble("optimization-methods.workstation-optimization.search-radius-in-blocks", 2.0, """
The radius in blocks a villager can be away from the player when he places a workstation.\s
The closest unoptimized villager to the player will be optimized.""") / 2;
this.cooldown = config.getInt("optimization-methods.workstation-optimization.optimize-cooldown-seconds", 600, """
Cooldown in seconds until a villager can be optimized again using a workstation.\s
Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.""") * 1000L;
this.only_while_sneaking = config.getBoolean("optimization-methods.workstation-optimization.only-when-sneaking", true,
"Only optimize/unoptimize by workstation when player is sneaking during place or break");
this.notify_player = config.getBoolean("optimization-methods.workstation-optimization.notify-player", true,
"Sends players a message when they successfully optimized a villager.");
this.log_enabled = config.getBoolean("optimization-methods.workstation-optimization.log", false);
}
@Override
public void enable() {
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
@Override
public boolean shouldEnable() {
return VillagerOptimizer.getConfiguration().getBoolean("optimization-methods.workstation-optimization.enable", false);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onBlockPlace(BlockPlaceEvent event) {
Block placed = event.getBlock();
Villager.Profession workstationProfession = getWorkstationProfession(placed.getType());
if (workstationProfession.equals(Villager.Profession.NONE)) return;
Player player = event.getPlayer();
if (!player.hasPermission(Permissions.Optimize.WORKSTATION.get())) return;
if (only_while_sneaking && !player.isSneaking()) return;
final Location workstationLoc = placed.getLocation();
WrappedVillager closestOptimizableVillager = null;
double closestDistance = Double.MAX_VALUE;
for (Entity entity : workstationLoc.getNearbyEntities(search_radius, search_radius, search_radius)) {
if (!entity.getType().equals(EntityType.VILLAGER)) continue;
Villager villager = (Villager) entity;
if (!villager.getProfession().equals(workstationProfession)) continue;
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
final double distance = entity.getLocation().distance(workstationLoc);
if (distance < closestDistance && wVillager.canOptimize(cooldown)) {
closestOptimizableVillager = wVillager;
closestDistance = distance;
}
}
if (closestOptimizableVillager == null) return;
if (closestOptimizableVillager.canOptimize(cooldown) || player.hasPermission(Permissions.Bypass.WORKSTATION_COOLDOWN.get())) {
VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(closestOptimizableVillager, OptimizationType.WORKSTATION, player, event.isAsynchronous());
if (!optimizeEvent.callEvent()) return;
closestOptimizableVillager.setOptimization(optimizeEvent.getOptimizationType());
closestOptimizableVillager.saveOptimizeTime();
if (notify_player) {
final TextReplacementConfig vilProfession = TextReplacementConfig.builder()
.matchLiteral("%vil_profession%")
.replacement(closestOptimizableVillager.villager().getProfession().toString().toLowerCase())
.build();
final TextReplacementConfig placedWorkstation = TextReplacementConfig.builder()
.matchLiteral("%workstation%")
.replacement(placed.getType().toString().toLowerCase())
.build();
VillagerOptimizer.getLang(player.locale()).workstation_optimize_success.forEach(line -> player.sendMessage(line
.replaceText(vilProfession)
.replaceText(placedWorkstation)
));
}
if (log_enabled)
VillagerOptimizer.getLog().info(player.getName() + " optimized a villager using workstation: '" + placed.getType().toString().toLowerCase() + "'");
} else {
if (notify_player) {
final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
.matchLiteral("%time%")
.replacement(CommonUtil.formatTime(closestOptimizableVillager.getOptimizeCooldownMillis(cooldown)))
.build();
VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> player.sendMessage(line
.replaceText(timeLeft)
));
}
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onBlockBreak(BlockBreakEvent event) {
Block broken = event.getBlock();
Villager.Profession workstationProfession = getWorkstationProfession(broken.getType());
if (workstationProfession.equals(Villager.Profession.NONE)) return;
Player player = event.getPlayer();
if (!player.hasPermission(Permissions.Optimize.WORKSTATION.get())) return;
if (only_while_sneaking && !player.isSneaking()) return;
final Location workstationLoc = broken.getLocation();
WrappedVillager closestOptimizedVillager = null;
double closestDistance = Double.MAX_VALUE;
for (Entity entity : workstationLoc.getNearbyEntities(search_radius, search_radius, search_radius)) {
if (!entity.getType().equals(EntityType.VILLAGER)) continue;
Villager villager = (Villager) entity;
if (!villager.getProfession().equals(workstationProfession)) continue;
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
final double distance = entity.getLocation().distance(workstationLoc);
if (distance < closestDistance && wVillager.canOptimize(cooldown)) {
closestOptimizedVillager = wVillager;
closestDistance = distance;
}
}
if (closestOptimizedVillager == null) return;
VillagerUnoptimizeEvent unOptimizeEvent = new VillagerUnoptimizeEvent(closestOptimizedVillager, player, OptimizationType.WORKSTATION, event.isAsynchronous());
if (!unOptimizeEvent.callEvent()) return;
closestOptimizedVillager.setOptimization(OptimizationType.NONE);
if (notify_player) {
final TextReplacementConfig vilProfession = TextReplacementConfig.builder()
.matchLiteral("%vil_profession%")
.replacement(closestOptimizedVillager.villager().getProfession().toString().toLowerCase())
.build();
final TextReplacementConfig brokenWorkstation = TextReplacementConfig.builder()
.matchLiteral("%workstation%")
.replacement(broken.getType().toString().toLowerCase())
.build();
VillagerOptimizer.getLang(player.locale()).workstation_unoptimize_success.forEach(line -> player.sendMessage(line
.replaceText(vilProfession)
.replaceText(brokenWorkstation)
));
}
if (log_enabled)
VillagerOptimizer.getLog().info(player.getName() + " unoptimized a villager by breaking workstation: '" + broken.getType().toString().toLowerCase() + "'");
}
private Villager.Profession getWorkstationProfession(final Material workstation) {
return switch (workstation) {
case BARREL -> Villager.Profession.FISHERMAN;
case CARTOGRAPHY_TABLE -> Villager.Profession.CARTOGRAPHER;
case SMOKER -> Villager.Profession.BUTCHER;
case SMITHING_TABLE -> Villager.Profession.TOOLSMITH;
case GRINDSTONE -> Villager.Profession.WEAPONSMITH;
case BLAST_FURNACE -> Villager.Profession.ARMORER;
case CAULDRON -> Villager.Profession.LEATHERWORKER;
case BREWING_STAND -> Villager.Profession.CLERIC;
case COMPOSTER -> Villager.Profession.FARMER;
case FLETCHING_TABLE -> Villager.Profession.FLETCHER;
case LOOM -> Villager.Profession.SHEPHERD;
case LECTERN -> Villager.Profession.LIBRARIAN;
case STONECUTTER -> Villager.Profession.MASON;
default -> Villager.Profession.NONE;
};
}
}

View File

@ -1,24 +0,0 @@
package me.xginko.villageroptimizer.utils;
import org.jetbrains.annotations.NotNull;
import java.time.Duration;
import static java.lang.String.format;
public class CommonUtil {
public static @NotNull String formatTime(final long millis) {
Duration duration = Duration.ofMillis(millis);
final int seconds = duration.toSecondsPart();
final int minutes = duration.toMinutesPart();
final int hours = duration.toHoursPart();
if (hours > 0) {
return format("%02dh %02dm %02ds", hours, minutes, seconds);
} else if (minutes > 0) {
return format("%02dm %02ds", minutes, seconds);
} else {
return format("%02ds", seconds);
}
}
}

View File

@ -1,98 +0,0 @@
name: VillagerOptimizer
version: '${project.version}'
main: me.xginko.villageroptimizer.VillagerOptimizer
authors: [ xGinko ]
description: ${project.description}
website: ${project.url}
api-version: '1.16'
folia-supported: false
commands:
villageroptimizer:
usage: /villageroptimizer [ reload, version, disable ]
description: VillagerOptimizer admin commands
aliases:
- voptimizer
- vo
optimizevillagers:
usage: /optimizevillagers <blockradius>
description: Optmize villagers in a radius around you
aliases:
- optvils
- noai
unoptimizevillagers:
usage: /unoptimizevillagers <blockradius>
description: Unoptmize villagers in a radius around you
aliases:
- unoptvils
- noaiundo
permissions:
villageroptimizer.ignore:
description: Players with this permission won't be able to use the plugin features
children:
villageroptimizer.optimize.nametag: false
villageroptimizer.optimize.block: false
villageroptimizer.optimize.workstation: false
villageroptimizer.playerdefaults:
description: Default permissions for players
default: true
children:
villageroptimizer.cmd.optimize: true
villageroptimizer.cmd.unoptimize: true
villageroptimizer.optimize.*: true
villageroptimizer.*:
description: All plugin permissions
children:
villageroptimizer.cmd.*: true
villageroptimizer.bypass.*: true
villageroptimizer.optimize.*: true
villageroptimizer.optimize.*:
description: Optimization type permissions
children:
villageroptimizer.optimize.nametag: true
villageroptimizer.optimize.block: true
villageroptimizer.optimize.workstation: true
villageroptimizer.optimize.nametag:
description: Optimize/Unoptimize villagers using nametags
villageroptimizer.optimize.block:
description: Optimize/Unoptimize villagers using specific blocks
villageroptimizer.optimize.workstation:
description: Optimize/Unoptimize villagers using workstations
villageroptimizer.cmd.*:
description: All command permissions
children:
villageroptimizer.cmd.reload: true
villageroptimizer.cmd.disable: true
villageroptimizer.cmd.version: true
villageroptimizer.cmd.optimize: true
villageroptimizer.cmd.unoptimize: true
villageroptimizer.cmd.disable:
description: Disable the plugin
villageroptimizer.cmd.reload:
description: Reload the plugin configuration
villageroptimizer.cmd.version:
description: Show the plugin version
villageroptimizer.cmd.optimize:
description: Optimize villagers in a radius
villageroptimizer.cmd.unoptimize:
description: Unoptimize villagers in a radius
villageroptimizer.bypass.*:
description: All bypass permissions
children:
villageroptimizer.bypass.tradeprevention: true
villageroptimizer.bypass.restockcooldown: true
villageroptimizer.bypass.nametagcooldown: true
villageroptimizer.bypass.blockcooldown: true
villageroptimizer.bypass.workstationcooldown: true
villageroptimizer.bypass.commandcooldown: true
villageroptimizer.bypass.tradeprevention:
description: Bypass unoptimized trading prevention if enabled
villageroptimizer.bypass.restockcooldown:
description: Bypass permission for optimized trade restock cooldown
villageroptimizer.bypass.nametagcooldown:
description: Bypass permission for nametag optimization cooldown
villageroptimizer.bypass.blockcooldown:
description: Bypass permission for block optimization cooldown
villageroptimizer.bypass.workstationcooldown:
description: Bypass permission for workstation optimization cooldown
villageroptimizer.bypass.commandcooldown:
description: Bypass permission for command optimization cooldown

View File

@ -1,87 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>me.xginko.VillagerOptimizer</groupId>
<artifactId>VillagerOptimizer</artifactId>
<version>1.0.2</version>
</parent>
<artifactId>1.20.2</artifactId>
<name>${project.parent.artifactId}-${project.parent.version}--${project.artifactId}</name>
<packaging>jar</packaging>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>com.github.benmanes.caffeine</pattern>
<shadedPattern>me.xginko.villageroptimizer.caffeine</shadedPattern>
</relocation>
<relocation>
<pattern>org.bstats</pattern>
<shadedPattern>me.xginko.villageroptimizer.bstats</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
<repositories>
<repository>
<id>papermc-repo</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>sonatype</id>
<url>https://oss.sonatype.org/content/groups/public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.20.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -1,56 +0,0 @@
package me.xginko.villageroptimizer;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.bukkit.Bukkit;
import org.bukkit.entity.Villager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.time.Duration;
import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
public final class VillagerCache {
private final @NotNull Cache<UUID, WrappedVillager> villagerCache;
VillagerCache(long expireAfterWriteSeconds) {
this.villagerCache = Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(expireAfterWriteSeconds)).build();
}
public @NotNull ConcurrentMap<UUID, WrappedVillager> cacheMap() {
return this.villagerCache.asMap();
}
public @Nullable WrappedVillager get(@NotNull UUID uuid) {
WrappedVillager wrappedVillager = this.villagerCache.getIfPresent(uuid);
return wrappedVillager == null && Bukkit.getEntity(uuid) instanceof Villager villager ? this.add(villager) : wrappedVillager;
}
public @NotNull WrappedVillager getOrAdd(@NotNull Villager villager) {
WrappedVillager wrappedVillager = this.villagerCache.getIfPresent(villager.getUniqueId());
return wrappedVillager == null ? this.add(new WrappedVillager(villager)) : this.add(wrappedVillager);
}
public @NotNull WrappedVillager add(@NotNull WrappedVillager villager) {
this.villagerCache.put(villager.villager().getUniqueId(), villager);
return villager;
}
public @NotNull WrappedVillager add(@NotNull Villager villager) {
return this.add(new WrappedVillager(villager));
}
public boolean contains(@NotNull UUID uuid) {
return this.villagerCache.getIfPresent(uuid) != null;
}
public boolean contains(@NotNull WrappedVillager villager) {
return this.contains(villager.villager().getUniqueId());
}
public boolean contains(@NotNull Villager villager) {
return this.contains(villager.getUniqueId());
}
}

View File

@ -1,192 +0,0 @@
package me.xginko.villageroptimizer;
import me.xginko.villageroptimizer.enums.Keys;
import me.xginko.villageroptimizer.enums.OptimizationType;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.entity.Villager;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public final class WrappedVillager {
private final @NotNull Villager villager;
private final @NotNull PersistentDataContainer dataContainer;
WrappedVillager(@NotNull Villager villager) {
this.villager = villager;
this.dataContainer = villager.getPersistentDataContainer();
}
/**
* @return The villager inside the wrapper.
*/
public @NotNull Villager villager() {
return villager;
}
/**
* @return The data container inside the wrapper.
*/
public @NotNull PersistentDataContainer dataContainer() {
return dataContainer;
}
/**
* @return True if the villager is optimized by this plugin, otherwise false.
*/
public boolean isOptimized() {
return dataContainer.has(Keys.OPTIMIZATION_TYPE.key());
}
/**
* @param cooldown_millis The configured cooldown in millis until the next optimization is allowed to occur.
* @return True if villager can be optimized again, otherwise false.
*/
public boolean canOptimize(final long cooldown_millis) {
return getLastOptimize() + cooldown_millis <= System.currentTimeMillis();
}
/**
* @param type OptimizationType the villager should be set to.
*/
public void setOptimization(OptimizationType type) {
if (type.equals(OptimizationType.NONE) && isOptimized()) {
dataContainer.remove(Keys.OPTIMIZATION_TYPE.key());
villager.getScheduler().run(VillagerOptimizer.getInstance(), enableAI -> {
villager.setAware(true);
villager.setAI(true);
}, null);
} else {
dataContainer.set(Keys.OPTIMIZATION_TYPE.key(), PersistentDataType.STRING, type.name());
villager.getScheduler().run(VillagerOptimizer.getInstance(), disableAI -> {
villager.setAware(false);
}, null);
}
}
/**
* @return The current OptimizationType of the villager.
*/
public @NotNull OptimizationType getOptimizationType() {
return isOptimized() ? OptimizationType.valueOf(dataContainer.get(Keys.OPTIMIZATION_TYPE.key(), PersistentDataType.STRING)) : OptimizationType.NONE;
}
/**
* Saves the system time in millis when the villager was last optimized.
*/
public void saveOptimizeTime() {
dataContainer.set(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG, System.currentTimeMillis());
}
/**
* @return The system time in millis when the villager was last optimized, 0L if the villager was never optimized.
*/
public long getLastOptimize() {
return dataContainer.has(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG) ? dataContainer.get(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG) : 0L;
}
/**
* Here for convenience so the remaining millis since the last stored optimize time
* can be easily calculated.
* This enables new configured cooldowns to instantly apply instead of them being persistent.
*
* @param cooldown_millis The configured cooldown in milliseconds you want to check against.
* @return The time left in millis until the villager can be optimized again.
*/
public long getOptimizeCooldownMillis(final long cooldown_millis) {
return dataContainer.has(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG) ? (System.currentTimeMillis() - (dataContainer.get(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG) + cooldown_millis)) : cooldown_millis;
}
/**
* Here for convenience so the remaining millis since the last stored restock time
* can be easily calculated.
*
* @param cooldown_millis The configured cooldown in milliseconds you want to check against.
* @return True if the villager has been loaded long enough.
*/
public boolean canRestock(final long cooldown_millis) {
return getLastRestock() + cooldown_millis <= villager.getWorld().getFullTime();
}
/**
* Restock all trading recipes.
*/
public void restock() {
villager.getRecipes().forEach(recipe -> recipe.setUses(0));
}
/**
* Saves the time of the in-game world when the entity was last restocked.
*/
public void saveRestockTime() {
dataContainer.set(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG, villager.getWorld().getFullTime());
}
/**
* @return The time of the in-game world when the entity was last restocked.
*/
public long getLastRestock() {
return dataContainer.has(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG) ? dataContainer.get(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG) : 0L;
}
public long getRestockCooldownMillis(final long cooldown_millis) {
return dataContainer.has(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG) ? (villager.getWorld().getFullTime() - (dataContainer.get(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG) + cooldown_millis)) : cooldown_millis;
}
/**
* @return The level between 1-5 calculated from the villagers experience.
*/
public int calculateLevel() {
// https://minecraft.fandom.com/wiki/Trading#Mechanics
int vilEXP = villager.getVillagerExperience();
if (vilEXP >= 250) return 5;
if (vilEXP >= 150) return 4;
if (vilEXP >= 70) return 3;
if (vilEXP >= 10) return 2;
return 1;
}
/**
* @param cooldown_millis The configured cooldown in milliseconds you want to check against.
* @return Whether the villager can be leveled up or not with the checked milliseconds
*/
public boolean canLevelUp(final long cooldown_millis) {
return getLastLevelUpTime() + cooldown_millis <= villager.getWorld().getFullTime();
}
/**
* Saves the time of the in-game world when the entity was last leveled up.
*/
public void saveLastLevelUp() {
dataContainer.set(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG, villager.getWorld().getFullTime());
}
/**
* Here for convenience so the remaining millis since the last stored level-up time
* can be easily calculated.
*
* @return The time of the in-game world when the entity was last leveled up.
*/
public long getLastLevelUpTime() {
return dataContainer.has(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG) ? dataContainer.get(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG) : 0L;
}
public long getLevelCooldownMillis(final long cooldown_millis) {
return dataContainer.has(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG) ? (villager.getWorld().getFullTime() - (dataContainer.get(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG) + cooldown_millis)) : cooldown_millis;
}
public void memorizeName(final Component customName) {
dataContainer.set(Keys.LAST_OPTIMIZE_NAME.key(), PersistentDataType.STRING, MiniMessage.miniMessage().serialize(customName));
}
public @Nullable Component getMemorizedName() {
return dataContainer.has(Keys.LAST_OPTIMIZE_NAME.key()) ? MiniMessage.miniMessage().deserialize(dataContainer.get(Keys.LAST_OPTIMIZE_NAME.key(), PersistentDataType.STRING)) : null;
}
public void forgetName() {
dataContainer.remove(Keys.LAST_OPTIMIZE_NAME.key());
}
}

View File

@ -1,11 +0,0 @@
package me.xginko.villageroptimizer.commands;
import net.kyori.adventure.text.TextComponent;
import org.bukkit.command.CommandSender;
public abstract class SubCommand {
public abstract String getLabel();
public abstract TextComponent getDescription();
public abstract TextComponent getSyntax();
public abstract void perform(CommandSender sender, String[] args);
}

View File

@ -1,35 +0,0 @@
package me.xginko.villageroptimizer.commands;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.commands.optimizevillagers.OptVillagersRadius;
import me.xginko.villageroptimizer.commands.unoptimizevillagers.UnOptVillagersRadius;
import me.xginko.villageroptimizer.commands.villageroptimizer.VillagerOptimizerCmd;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandMap;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
public interface VillagerOptimizerCommand extends CommandExecutor {
String label();
HashSet<VillagerOptimizerCommand> commands = new HashSet<>();
static void reloadCommands() {
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
CommandMap commandMap = plugin.getServer().getCommandMap();
commands.forEach(command -> plugin.getCommand(command.label()).unregister(commandMap));
commands.clear();
commands.add(new VillagerOptimizerCmd());
commands.add(new OptVillagersRadius());
commands.add(new UnOptVillagersRadius());
commands.forEach(command -> plugin.getCommand(command.label()).setExecutor(command));
}
@Override
boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args);
}

View File

@ -1,142 +0,0 @@
package me.xginko.villageroptimizer.commands.optimizevillagers;
import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.WrappedVillager;
import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand;
import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.enums.OptimizationType;
import me.xginko.villageroptimizer.enums.Permissions;
import me.xginko.villageroptimizer.events.VillagerOptimizeEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextReplacementConfig;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Villager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class OptVillagersRadius implements VillagerOptimizerCommand, TabCompleter {
private final List<String> tabCompletes = List.of("5", "10", "25", "50");
private final long cooldown;
private final int max_radius;
public OptVillagersRadius() {
Config config = VillagerOptimizer.getConfiguration();
this.max_radius = config.getInt("optimization-methods.commands.optimizevillagers.max-block-radius", 100);
this.cooldown = config.getInt("optimization-methods.commands.optimizevillagers.cooldown-seconds", 600, """
Cooldown in seconds until a villager can be optimized again using the command.\s
Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.""") * 1000L;
}
@Override
public String label() {
return "optimizevillagers";
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
return args.length == 1 ? tabCompletes : null;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage(Component.text("This command can only be executed by a player.")
.color(NamedTextColor.RED).decorate(TextDecoration.BOLD));
return true;
}
if (!sender.hasPermission(Permissions.Commands.OPTIMIZE_RADIUS.get())) {
sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission);
return true;
}
if (args.length != 1) {
VillagerOptimizer.getLang(player.locale()).command_specify_radius.forEach(player::sendMessage);
return true;
}
try {
int specifiedRadius = Integer.parseInt(args[0]);
if (specifiedRadius > max_radius) {
final TextReplacementConfig limit = TextReplacementConfig.builder()
.matchLiteral("%distance%")
.replacement(Integer.toString(max_radius))
.build();
VillagerOptimizer.getLang(player.locale()).command_radius_limit_exceed.forEach(line -> player.sendMessage(line.replaceText(limit)));
return true;
}
VillagerCache villagerCache = VillagerOptimizer.getCache();
int successCount = 0;
int failCount = 0;
final boolean player_has_cooldown_bypass = player.hasPermission(Permissions.Bypass.COMMAND_COOLDOWN.get());
for (Entity entity : player.getNearbyEntities(specifiedRadius, specifiedRadius, specifiedRadius)) {
if (!entity.getType().equals(EntityType.VILLAGER)) continue;
Villager villager = (Villager) entity;
Villager.Profession profession = villager.getProfession();
if (profession.equals(Villager.Profession.NITWIT) || profession.equals(Villager.Profession.NONE)) continue;
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
if (player_has_cooldown_bypass || wVillager.canOptimize(cooldown)) {
VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(wVillager, OptimizationType.COMMAND, player);
if (optimizeEvent.callEvent()) {
wVillager.setOptimization(optimizeEvent.getOptimizationType());
wVillager.saveOptimizeTime();
successCount++;
}
} else {
failCount++;
}
}
if (successCount <= 0 && failCount <= 0) {
final TextReplacementConfig radius = TextReplacementConfig.builder()
.matchLiteral("%radius%")
.replacement(Integer.toString(specifiedRadius))
.build();
VillagerOptimizer.getLang(player.locale()).command_no_villagers_nearby.forEach(line -> player.sendMessage(line.replaceText(radius)));
return true;
}
if (successCount > 0) {
final TextReplacementConfig success_amount = TextReplacementConfig.builder()
.matchLiteral("%amount%")
.replacement(Integer.toString(successCount))
.build();
final TextReplacementConfig radius = TextReplacementConfig.builder()
.matchLiteral("%radius%")
.replacement(Integer.toString(specifiedRadius))
.build();
VillagerOptimizer.getLang(player.locale()).command_optimize_success.forEach(line -> player.sendMessage(line
.replaceText(success_amount)
.replaceText(radius)
));
}
if (failCount > 0) {
final TextReplacementConfig alreadyOptimized = TextReplacementConfig.builder()
.matchLiteral("%amount%")
.replacement(Integer.toString(failCount))
.build();
VillagerOptimizer.getLang(player.locale()).command_optimize_fail.forEach(line -> player.sendMessage(line.replaceText(alreadyOptimized)));
}
} catch (NumberFormatException e) {
VillagerOptimizer.getLang(player.locale()).command_radius_invalid.forEach(player::sendMessage);
}
return true;
}
}

View File

@ -1,121 +0,0 @@
package me.xginko.villageroptimizer.commands.unoptimizevillagers;
import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.WrappedVillager;
import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand;
import me.xginko.villageroptimizer.enums.OptimizationType;
import me.xginko.villageroptimizer.enums.Permissions;
import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextReplacementConfig;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Villager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class UnOptVillagersRadius implements VillagerOptimizerCommand, TabCompleter {
private final List<String> tabCompletes = List.of("5", "10", "25", "50");
private final int max_radius;
public UnOptVillagersRadius() {
this.max_radius = VillagerOptimizer.getConfiguration().getInt("optimization-methods.commands.unoptimizevillagers.max-block-radius", 100);
}
@Override
public String label() {
return "unoptimizevillagers";
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
return args.length == 1 ? tabCompletes : null;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage(Component.text("This command can only be executed by a player.")
.color(NamedTextColor.RED).decorate(TextDecoration.BOLD));
return true;
}
if (!sender.hasPermission(Permissions.Commands.UNOPTIMIZE_RADIUS.get())) {
sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission);
return true;
}
if (args.length != 1) {
VillagerOptimizer.getLang(player.locale()).command_specify_radius.forEach(player::sendMessage);
return true;
}
try {
int specifiedRadius = Integer.parseInt(args[0]);
if (specifiedRadius > max_radius) {
final TextReplacementConfig limit = TextReplacementConfig.builder()
.matchLiteral("%distance%")
.replacement(Integer.toString(max_radius))
.build();
VillagerOptimizer.getLang(player.locale()).command_radius_limit_exceed.forEach(line -> player.sendMessage(line.replaceText(limit)));
return true;
}
VillagerCache villagerCache = VillagerOptimizer.getCache();
int successCount = 0;
for (Entity entity : player.getNearbyEntities(specifiedRadius, specifiedRadius, specifiedRadius)) {
if (!entity.getType().equals(EntityType.VILLAGER)) continue;
Villager villager = (Villager) entity;
Villager.Profession profession = villager.getProfession();
if (profession.equals(Villager.Profession.NITWIT) || profession.equals(Villager.Profession.NONE)) continue;
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
if (wVillager.isOptimized()) {
VillagerUnoptimizeEvent unOptimizeEvent = new VillagerUnoptimizeEvent(wVillager, player, OptimizationType.COMMAND);
if (unOptimizeEvent.callEvent()) {
wVillager.setOptimization(OptimizationType.NONE);
successCount++;
}
}
}
if (successCount <= 0) {
final TextReplacementConfig radius = TextReplacementConfig.builder()
.matchLiteral("%radius%")
.replacement(Integer.toString(specifiedRadius))
.build();
VillagerOptimizer.getLang(player.locale()).command_no_villagers_nearby.forEach(line -> player.sendMessage(line.replaceText(radius)));
} else {
final TextReplacementConfig success_amount = TextReplacementConfig.builder()
.matchLiteral("%amount%")
.replacement(Integer.toString(successCount))
.build();
final TextReplacementConfig radius = TextReplacementConfig.builder()
.matchLiteral("%radius%")
.replacement(Integer.toString(specifiedRadius))
.build();
VillagerOptimizer.getLang(player.locale()).command_unoptimize_success.forEach(line -> player.sendMessage(line
.replaceText(success_amount)
.replaceText(radius)
));
}
} catch (NumberFormatException e) {
VillagerOptimizer.getLang(player.locale()).command_radius_invalid.forEach(player::sendMessage);
}
return true;
}
}

View File

@ -1,79 +0,0 @@
package me.xginko.villageroptimizer.commands.villageroptimizer;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.commands.SubCommand;
import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand;
import me.xginko.villageroptimizer.commands.villageroptimizer.subcommands.DisableSubCmd;
import me.xginko.villageroptimizer.commands.villageroptimizer.subcommands.ReloadSubCmd;
import me.xginko.villageroptimizer.commands.villageroptimizer.subcommands.VersionSubCmd;
import me.xginko.villageroptimizer.enums.Permissions;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class VillagerOptimizerCmd implements TabCompleter, VillagerOptimizerCommand {
private final List<SubCommand> subCommands = new ArrayList<>(3);
private final List<String> tabCompleter = new ArrayList<>(3);
public VillagerOptimizerCmd() {
subCommands.add(new ReloadSubCmd());
subCommands.add(new VersionSubCmd());
subCommands.add(new DisableSubCmd());
subCommands.forEach(subCommand -> tabCompleter.add(subCommand.getLabel()));
}
@Override
public String label() {
return "villageroptimizer";
}
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
return args.length == 1 ? tabCompleter : null;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (args.length > 0) {
boolean cmdExists = false;
for (SubCommand subCommand : subCommands) {
if (args[0].equalsIgnoreCase(subCommand.getLabel())) {
subCommand.perform(sender, args);
cmdExists = true;
break;
}
}
if (!cmdExists) sendCommandOverview(sender);
} else {
sendCommandOverview(sender);
}
return true;
}
private void sendCommandOverview(CommandSender sender) {
if (!sender.hasPermission(Permissions.Commands.RELOAD.get()) && !sender.hasPermission(Permissions.Commands.VERSION.get())) return;
sender.sendMessage(Component.text("-----------------------------------------------------").color(NamedTextColor.GRAY));
sender.sendMessage(Component.text("VillagerOptimizer Commands").color(VillagerOptimizer.plugin_style.color()));
sender.sendMessage(Component.text("-----------------------------------------------------").color(NamedTextColor.GRAY));
subCommands.forEach(subCommand -> sender.sendMessage(
subCommand.getSyntax().append(Component.text(" - ").color(NamedTextColor.DARK_GRAY)).append(subCommand.getDescription())));
sender.sendMessage(
Component.text("/optimizevillagers <blockradius>").color(VillagerOptimizer.plugin_style.color())
.append(Component.text(" - ").color(NamedTextColor.DARK_GRAY))
.append(Component.text("Optimize villagers in a radius").color(NamedTextColor.GRAY))
);
sender.sendMessage(
Component.text("/unoptmizevillagers <blockradius>").color(VillagerOptimizer.plugin_style.color())
.append(Component.text(" - ").color(NamedTextColor.DARK_GRAY))
.append(Component.text("Unoptimize villagers in a radius").color(NamedTextColor.GRAY))
);
sender.sendMessage(Component.text("-----------------------------------------------------").color(NamedTextColor.GRAY));
}
}

View File

@ -1,41 +0,0 @@
package me.xginko.villageroptimizer.commands.villageroptimizer.subcommands;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.commands.SubCommand;
import me.xginko.villageroptimizer.enums.Permissions;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender;
public class ReloadSubCmd extends SubCommand {
@Override
public String getLabel() {
return "reload";
}
@Override
public TextComponent getDescription() {
return Component.text("Reload the plugin configuration.").color(NamedTextColor.GRAY);
}
@Override
public TextComponent getSyntax() {
return Component.text("/villageroptimizer reload").color(VillagerOptimizer.plugin_style.color());
}
@Override
public void perform(CommandSender sender, String[] args) {
if (sender.hasPermission(Permissions.Commands.RELOAD.get())) {
sender.sendMessage(Component.text("Reloading VillagerOptimizer...").color(NamedTextColor.WHITE));
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
plugin.getServer().getAsyncScheduler().runNow(plugin, reloadPlugin -> {
plugin.reloadPlugin();
sender.sendMessage(Component.text("Reload complete.").color(NamedTextColor.GREEN));
});
} else {
sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission);
}
}
}

View File

@ -1,53 +0,0 @@
package me.xginko.villageroptimizer.commands.villageroptimizer.subcommands;
import io.papermc.paper.plugin.configuration.PluginMeta;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.commands.SubCommand;
import me.xginko.villageroptimizer.enums.Permissions;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender;
public class VersionSubCmd extends SubCommand {
@Override
public String getLabel() {
return "version";
}
@Override
public TextComponent getDescription() {
return Component.text("Show the plugin version.").color(NamedTextColor.GRAY);
}
@Override
public TextComponent getSyntax() {
return Component.text("/villageroptimizer version").color(VillagerOptimizer.plugin_style.color());
}
@Override
public void perform(CommandSender sender, String[] args) {
if (sender.hasPermission(Permissions.Commands.VERSION.get())) {
final PluginMeta pluginMeta = VillagerOptimizer.getInstance().getPluginMeta();
sender.sendMessage(
Component.newline()
.append(
Component.text(pluginMeta.getName()+" "+pluginMeta.getVersion())
.style(VillagerOptimizer.plugin_style)
.clickEvent(ClickEvent.openUrl(pluginMeta.getWebsite()))
)
.append(Component.text(" by ").color(NamedTextColor.GRAY))
.append(
Component.text(pluginMeta.getAuthors().get(0))
.color(NamedTextColor.WHITE)
.clickEvent(ClickEvent.openUrl("https://github.com/xGinko"))
)
.append(Component.newline())
);
} else {
sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission);
}
}
}

View File

@ -1,109 +0,0 @@
package me.xginko.villageroptimizer.config;
import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
import me.xginko.villageroptimizer.VillagerOptimizer;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.List;
public class LanguageCache {
private final @NotNull ConfigFile lang;
public final @NotNull Component no_permission;
public final @NotNull List<Component> nametag_optimize_success, nametag_on_optimize_cooldown, nametag_unoptimize_success,
block_optimize_success, block_on_optimize_cooldown, block_unoptimize_success,
workstation_optimize_success, workstation_on_optimize_cooldown, workstation_unoptimize_success,
command_optimize_success, command_radius_limit_exceed, command_optimize_fail, command_unoptimize_success,
command_specify_radius, command_radius_invalid, command_no_villagers_nearby,
trades_restocked, optimize_for_trading, villager_leveling_up;
public LanguageCache(String locale) throws Exception {
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
File langYML = new File(plugin.getDataFolder() + File.separator + "lang", locale + ".yml");
// Check if the lang folder has already been created
File parent = langYML.getParentFile();
if (!parent.exists() && !parent.mkdir())
VillagerOptimizer.getLog().severe("Failed to create lang directory.");
// Check if the file already exists and save the one from the plugins resources folder if it does not
if (!langYML.exists())
plugin.saveResource("lang" + File.separator + locale + ".yml", false);
// Finally load the lang file with configmaster
this.lang = ConfigFile.loadConfig(langYML);
// General
this.no_permission = getTranslation("messages.no-permission",
"<red>You don't have permission to use this command.");
this.trades_restocked = getListTranslation("messages.trades-restocked",
List.of("<green>All trades have been restocked! Next restock in %time%"));
this.optimize_for_trading = getListTranslation("messages.optimize-to-trade",
List.of("<red>You need to optimize this villager before you can trade with it."));
this.villager_leveling_up = getListTranslation("messages.villager-leveling-up",
List.of("<yellow>Villager is currently leveling up! You can use the villager again in %time%."));
// Nametag
this.nametag_optimize_success = getListTranslation("messages.nametag.optimize-success",
List.of("<green>Successfully optimized villager by using a nametag."));
this.nametag_on_optimize_cooldown = getListTranslation("messages.nametag.optimize-on-cooldown",
List.of("<gray>You need to wait %time% until you can optimize this villager again."));
this.nametag_unoptimize_success = getListTranslation("messages.nametag.unoptimize-success",
List.of("<green>Successfully unoptimized villager by using a nametag."));
// Block
this.block_optimize_success = getListTranslation("messages.block.optimize-success",
List.of("<green>%villagertype% villager successfully optimized using block %blocktype%."));
this.block_on_optimize_cooldown = getListTranslation("messages.block.optimize-on-cooldown",
List.of("<gray>You need to wait %time% until you can optimize this villager again."));
this.block_unoptimize_success = getListTranslation("messages.block.unoptimize-success",
List.of("<green>Successfully unoptimized %villagertype% villager by removing %blocktype%."));
// Workstation
this.workstation_optimize_success = getListTranslation("messages.workstation.optimize-success",
List.of("<green>%villagertype% villager successfully optimized using workstation %workstation%."));
this.workstation_on_optimize_cooldown = getListTranslation("messages.workstation.optimize-on-cooldown",
List.of("<gray>You need to wait %time% until you can optimize this villager again."));
this.workstation_unoptimize_success = getListTranslation("messages.workstation.unoptimize-success",
List.of("<green>Successfully unoptimized %villagertype% villager by removing workstation block %workstation%."));
// Command
this.command_optimize_success = getListTranslation("messages.command.optimize-success",
List.of("<green>Successfully optimized %amount% villager(s) in a radius of %radius% blocks."));
this.command_radius_limit_exceed = getListTranslation("messages.command.radius-limit-exceed",
List.of("<red>The radius you entered exceeds the limit of %distance% blocks."));
this.command_optimize_fail = getListTranslation("messages.command.optimize-fail",
List.of("<gray>%amount% villagers couldn't be optimized because they have recently been optimized."));
this.command_unoptimize_success = getListTranslation("messages.command.unoptimize-success",
List.of("<green>Successfully unoptimized %amount% villager(s) in a radius of %radius% blocks."));
this.command_specify_radius = getListTranslation("messages.command.specify-radius",
List.of("<red>Please specify a radius."));
this.command_radius_invalid = getListTranslation("messages.command.radius-invalid",
List.of("<red>The radius you entered is not a valid number. Try again."));
this.command_no_villagers_nearby = getListTranslation("messages.command.no-villagers-nearby",
List.of("<gray>Couldn't find any employed villagers within a radius of %radius%."));
try {
this.lang.save();
} catch (Exception e) {
VillagerOptimizer.getLog().severe("Failed to save language file: "+ langYML.getName() +" - " + e.getLocalizedMessage());
}
}
public @NotNull Component getTranslation(@NotNull String path, @NotNull String defaultTranslation) {
this.lang.addDefault(path, defaultTranslation);
return MiniMessage.miniMessage().deserialize(this.lang.getString(path, defaultTranslation));
}
public @NotNull Component getTranslation(@NotNull String path, @NotNull String defaultTranslation, @NotNull String comment) {
this.lang.addDefault(path, defaultTranslation, comment);
return MiniMessage.miniMessage().deserialize(this.lang.getString(path, defaultTranslation));
}
public @NotNull List<Component> getListTranslation(@NotNull String path, @NotNull List<String> defaultTranslation) {
this.lang.addDefault(path, defaultTranslation);
return this.lang.getStringList(path).stream().map(MiniMessage.miniMessage()::deserialize).toList();
}
public @NotNull List<Component> getListTranslation(@NotNull String path, @NotNull List<String> defaultTranslation, @NotNull String comment) {
this.lang.addDefault(path, defaultTranslation, comment);
return this.lang.getStringList(path).stream().map(MiniMessage.miniMessage()::deserialize).toList();
}
}

View File

@ -1,24 +0,0 @@
package me.xginko.villageroptimizer.enums;
import me.xginko.villageroptimizer.VillagerOptimizer;
import org.bukkit.NamespacedKey;
public enum Keys {
OPTIMIZATION_TYPE(VillagerOptimizer.getKey("optimization-type")),
LAST_OPTIMIZE(VillagerOptimizer.getKey("last-optimize")),
LAST_LEVELUP(VillagerOptimizer.getKey("last-levelup")),
LAST_RESTOCK(VillagerOptimizer.getKey("last-restock")),
LAST_OPTIMIZE_NAME(VillagerOptimizer.getKey("last-optimize-name"));
private final NamespacedKey key;
Keys(NamespacedKey key) {
this.key = key;
}
public NamespacedKey key() {
return key;
}
}

View File

@ -1,11 +0,0 @@
package me.xginko.villageroptimizer.enums;
public enum OptimizationType {
COMMAND,
NAMETAG,
WORKSTATION,
BLOCK,
NONE
}

View File

@ -1,45 +0,0 @@
package me.xginko.villageroptimizer.enums;
public class Permissions {
public enum Commands {
VERSION("villageroptimizer.cmd.version"),
RELOAD("villageroptimizer.cmd.reload"),
DISABLE("villageroptimizer.cmd.disable"),
OPTIMIZE_RADIUS("villageroptimizer.cmd.optimize"),
UNOPTIMIZE_RADIUS("villageroptimizer.cmd.unoptimize");
private final String permission;
Commands(String permission) {
this.permission = permission;
}
public String get() {
return permission;
}
}
public enum Optimize {
NAMETAG("villageroptimizer.optimize.nametag"),
BLOCK("villageroptimizer.optimize.block"),
WORKSTATION("villageroptimizer.optimize.workstation");
private final String permission;
Optimize(String permission) {
this.permission = permission;
}
public String get() {
return permission;
}
}
public enum Bypass {
TRADE_PREVENTION("villageroptimizer.bypass.tradeprevention"),
RESTOCK_COOLDOWN("villageroptimizer.bypass.restockcooldown"),
NAMETAG_COOLDOWN("villageroptimizer.bypass.nametagcooldown"),
BLOCK_COOLDOWN("villageroptimizer.bypass.blockcooldown"),
WORKSTATION_COOLDOWN("villageroptimizer.bypass.workstationcooldown"),
COMMAND_COOLDOWN("villageroptimizer.bypass.commandcooldown");
private final String permission;
Bypass(String permission) {
this.permission = permission;
}
public String get() {
return permission;
}
}
}

View File

@ -1,63 +0,0 @@
package me.xginko.villageroptimizer.events;
import me.xginko.villageroptimizer.WrappedVillager;
import me.xginko.villageroptimizer.enums.OptimizationType;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class VillagerUnoptimizeEvent extends Event implements Cancellable {
private static final @NotNull HandlerList handlers = new HandlerList();
private final @NotNull WrappedVillager wrappedVillager;
private final @NotNull OptimizationType unoptimizeType;
private final @Nullable Player whoUnoptimized;
private boolean isCancelled = false;
public VillagerUnoptimizeEvent(@NotNull WrappedVillager wrappedVillager, @Nullable Player whoUnoptimized, @NotNull OptimizationType unoptimizeType, boolean isAsync) {
super(isAsync);
this.wrappedVillager = wrappedVillager;
this.whoUnoptimized = whoUnoptimized;
this.unoptimizeType = unoptimizeType;
}
public VillagerUnoptimizeEvent(@NotNull WrappedVillager wrappedVillager, @Nullable Player whoUnoptimized, @NotNull OptimizationType unoptimizeType) {
this.wrappedVillager = wrappedVillager;
this.whoUnoptimized = whoUnoptimized;
this.unoptimizeType = unoptimizeType;
}
public @NotNull WrappedVillager getWrappedVillager() {
return wrappedVillager;
}
public @Nullable Player getWhoUnoptimized() {
return whoUnoptimized;
}
public @NotNull OptimizationType getWhichTypeUnoptimized() {
return unoptimizeType;
}
@Override
public boolean isCancelled() {
return isCancelled;
}
@Override
public void setCancelled(boolean cancel) {
this.isCancelled = cancel;
}
@Override
public @NotNull HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@ -1,26 +0,0 @@
package me.xginko.villageroptimizer.utils;
import me.xginko.villageroptimizer.VillagerOptimizer;
import java.util.logging.Level;
public class LogUtil {
public static void moduleLog(Level logLevel, String path, String logMessage) {
VillagerOptimizer.getLog().log(logLevel, "(" + path + ") " + logMessage);
}
public static void materialNotRecognized(String path, String material) {
moduleLog(Level.WARNING, path, "Material '" + material + "' not recognized. Please use correct Material enums from: " +
"https://jd.papermc.io/paper/1.20/org/bukkit/Material.html");
}
public static void damageCauseNotRecognized(String path, String cause) {
moduleLog(Level.WARNING, path, "DamageCause '" + cause + "' not recognized. Please use correct DamageCause enums from: " +
"https://jd.papermc.io/paper/1.20/org/bukkit/event/entity/EntityDamageEvent.DamageCause.html");
}
public static void entityTypeNotRecognized(String path, String entityType) {
moduleLog(Level.WARNING, path, "EntityType '" + entityType + "' not recognized. Please use correct Spigot EntityType enums for your Minecraft version!");
}
}

View File

@ -1,44 +0,0 @@
messages:
no-permission: "<red>Du hast keine Berechtigung für diesen Befehl."
optimize-to-trade:
- "<red>Optimiere den Dorfbewohner um mit ihm handeln zu können."
trades-restocked:
- "<green>Alle Angebote sind wieder verfügbar! Nächste Auffrischung in %time%."
villager-leveling-up:
- "<yellow>Dorfbewohner steigt gerade im Level auf und kann in %time% wieder handeln."
nametag:
optimize-success:
- "<green>Dorfbewohner erfolgreich mit einem Namensschild optimiert."
optimize-on-cooldown:
- "<gray>Du musst %time% warten, bevor du diesen Dorfbewohner erneut optimieren kannst."
unoptimize-success:
- "<green>Optimierung erfolgreich durch eine Namensschildänderung aufgehoben."
block:
optimize-success:
- "<green>%vil_profession% Dorfbewohner erfolgreich mit %blocktype% optimiert."
optimize-on-cooldown:
- "<gray>Du musst %time% warten, bevor du diesen Dorfbewohner erneut optimieren kannst."
unoptimize-success:
- "<green>Optimierung des %vil_profession% Dorfbewohners erfolgreich durch Abbauen von %blocktype% aufgehoben."
workstation:
optimize-success:
- "<green>%vil_profession% Dorfbewohner erfolgreich mit Arbeitsplatz-Block %blocktype% optimiert."
optimize-on-cooldown:
- "<gray>Du musst %time% warten, bevor du diesen Dorfbewohner erneut optimieren kannst."
unoptimize-success:
- "<green>Optimierung des %vil_profession% Dorfbewohners erfolgreich durch Abbauen von %blocktype% aufgehoben."
command:
optimize-success:
- "<green>Erfolgreich %amount% Dorfbewohner in einem Radius von %radius% Blöcken optimiert."
optimize-fail:
- "<gray>%amount% Dorfbewohner konnten nicht optimiert werden, da sie erst vor Kurzem optimiert wurden."
radius-limit-exceed:
- "<red>Der eingegebene Radius überschreitet das Limit von %distance% Blöcken."
unoptimize-success:
- "<green>Optimierung von %amount% Dorfbewohnern in einem Radius von %radius% Blöcken erfolgreich aufgehoben."
specify-radius:
- "<red>Bitte gib einen Radius an."
radius-invalid:
- "<red>Der eingegebene Radius ist keine gültige Zahl. Versuche es erneut."
no-villagers-nearby:
- "<gray>Es wurden keine beschäftigten Dorfbewohner innerhalb eines Radius von %radius% Blöcken gefunden."

View File

@ -1,44 +0,0 @@
messages:
no-permission: "<red>You don't have permission to use this command."
optimize-to-trade:
- "<red>You need to optimize this villager before you can trade with it."
trades-restocked:
- "<green>All trades have been restocked! Next restock in %time%."
villager-leveling-up:
- "<yellow>Villager is currently leveling up! You can use the villager again in %time%."
nametag:
optimize-success:
- "<green>Successfully optimized villager by using a nametag."
optimize-on-cooldown:
- "<gray>You need to wait %time% until you can optimize this villager again."
unoptimize-success:
- "<green>Successfully unoptimized villager by using a nametag."
block:
optimize-success:
- "<green>%vil_profession% villager successfully optimized using block %blocktype%."
optimize-on-cooldown:
- "<gray>You need to wait %time% until you can optimize this villager again."
unoptimize-success:
- "<green>Successfully unoptimized %vil_profession% villager by removing %blocktype%."
workstation:
optimize-success:
- "<green>%vil_profession% villager successfully optimized using workstation block %blocktype%."
optimize-on-cooldown:
- "<gray>You need to wait %time% until you can optimize this villager again."
unoptimize-success:
- "<green>Successfully unoptimized villager by removing workstation block %blocktype%."
command:
optimize-success:
- "<green>Successfully optimized %amount% villager(s) in a radius of %radius% blocks."
optimize-fail:
- "<gray>%amount% villagers couldn't be optimized because they have recently been optimized."
radius-limit-exceed:
- "<red>The radius you entered exceeds the limit of %distance% blocks."
unoptimize-success:
- "<green>Successfully unoptimized %amount% villager(s) in a radius of %radius% blocks."
specify-radius:
- "<red>Please specify a radius."
radius-invalid:
- "<red>The radius you entered is not a valid number. Try again."
no-villagers-nearby:
- "<gray>Couldn't find any employed villagers within a radius of %radius%."

View File

@ -1,44 +0,0 @@
messages:
no-permission: "<red>Non hai il permesso per usare questo comando."
optimize-to-trade:
- "<red>Devi ottimizzare il villager prima di poterci commerciare."
trades-restocked:
- "<green>Tutti i commerci sono stati riavviati! Prossimo riavvio in %time%."
villager-leveling-up:
- "<yellow>Il villager si sta ancora ottimizzando! Potrai utilizzarlo di nuovo tra %time%."
nametag:
optimize-success:
- "<green>Villager ottimizzato correttamente utilizzando il name-tag."
optimize-on-cooldown:
- "<gray>Devi attendere %time% prima di poter ottimizzare di nuovo questo villager."
unoptimize-success:
- "<green>Ottimizzazione rimossa con successo dal villager usando il name-tag."
block:
optimize-success:
- "<green>%vil_profession% ottimizzato con successo usando il blocco: %blocktype%."
optimize-on-cooldown:
- "<gray>Devi attendere %time% prima di poter ottimizzare di nuovo questo villager."
unoptimize-success:
- "<green>Ottimizzazione rimossa al villager %vil_profession% rimuovendo il blocco: %blocktype%."
workstation:
optimize-success:
- "<green>%vil_profession% ottimizzato con successo usando la workstation: %blocktype%."
optimize-on-cooldown:
- "<gray>Devi aspettare %time% prima di poter ottimizzare il villager."
unoptimize-success:
- "<green>Ottimizzazione rimossa al villager %vil_profession% rimuovendo la workstation: %blocktype%."
command:
optimize-success:
- "<green>Ottimizzato con successo %amount% villager in un raggio di %radius% blocchi."
optimize-fail:
- "<gray>%amount% villager non possono essere ottimizzati poiché sono stati già ottimizzati di recente."
radius-limit-exceed:
- "<red>Il raggio inserito supera il limite di %distance% blocchi."
unoptimize-success:
- "<green>Ottimizzazione rimossa con successo da %amount% villager in un raggio di %radius% blocchi."
specify-radius:
- "<red>Per favore, specifica un raggio."
radius-invalid:
- "<red>Il raggio inserito non è un numero valido, riprova."
no-villagers-nearby:
- "<gray>Non è stato possibile trovare villager entro un raggio di %radius% blocchi."

View File

@ -1,44 +0,0 @@
messages:
no-permission: "<red>У вас нет разрешения на использование данной команды."
optimize-to-trade:
- "<red>Вам необходимо оптимизировать данного жителя перед торговлей."
trades-restocked:
- "<green>Товары были обновлены. Следующее обновление через %time%."
villager-leveling-up:
- "<yellow>Данный житель повышает свой уровень. Вы сможете торговать через %time%"
nametag:
optimize-success:
- "<green>Житель был успешно оптимизирован биркой."
optimize-on-cooldown:
- "<gray>Подождите еще %time% для повторной оптимизации данного жителя."
unoptimize-success:
- "<green>Оптимизация была успешно снята с жителя."
block:
optimize-success:
- "<green>%vil_profession% был успешно оптимизирован используя %blocktype%."
optimize-on-cooldown:
- "<gray>Подождите еще %time% для повторной оптимизации данного жителя."
unoptimize-success:
- "<green>Оптимизация была успешно снята с %vil_profession% удалением %blocktype%."
workstation:
optimize-success:
- "<green>%vil_profession% был успешно оптимизирован используя %blocktype%."
optimize-on-cooldown:
- "<gray>Подождите еще %time% для повторной оптимизации данного жителя."
unoptimize-success:
- "<green>Оптимизация была успешно снята с %vil_profession% удалением %blocktype%."
command:
optimize-success:
- "<green>Успешно оптимизинованы %amount% жителей в радиусе %radius% блоков."
optimize-fail:
- "<gray>%amount% жителей не могут быть оптимизированы снова так как они уже были оптимизированы недавно."
radius-limit-exceed:
- "<red>Укажите радиус меньше %distance% блоков."
unoptimize-success:
- "<green>Оптимизация была успешно снята с %amount% жителей в радиусе %radius% блоков."
specify-radius:
- "<red>Пожалуйста, укажите радиус."
radius-invalid:
- "<red>Указанный вами радиус не является действительным значением."
no-villagers-nearby:
- "<gray>Не найдено работающих жителей в радиусе %radius% блоков."

View File

@ -1,98 +0,0 @@
name: VillagerOptimizer
version: '${project.version}'
main: me.xginko.villageroptimizer.VillagerOptimizer
authors: [ xGinko ]
description: ${project.description}
website: ${project.url}
api-version: '1.19'
folia-supported: true
commands:
villageroptimizer:
usage: /villageroptimizer [ reload, version, disable ]
description: VillagerOptimizer admin commands
aliases:
- voptimizer
- vo
optimizevillagers:
usage: /optimizevillagers <blockradius>
description: Optmize villagers in a radius around you
aliases:
- optvils
- noai
unoptimizevillagers:
usage: /unoptimizevillagers <blockradius>
description: Unoptmize villagers in a radius around you
aliases:
- unoptvils
- noaiundo
permissions:
villageroptimizer.ignore:
description: Players with this permission won't be able to use the plugin features
children:
villageroptimizer.optimize.nametag: false
villageroptimizer.optimize.block: false
villageroptimizer.optimize.workstation: false
villageroptimizer.playerdefaults:
description: Default permissions for players
default: true
children:
villageroptimizer.cmd.optimize: true
villageroptimizer.cmd.unoptimize: true
villageroptimizer.optimize.*: true
villageroptimizer.*:
description: All plugin permissions
children:
villageroptimizer.cmd.*: true
villageroptimizer.bypass.*: true
villageroptimizer.optimize.*: true
villageroptimizer.optimize.*:
description: Optimization type permissions
children:
villageroptimizer.optimize.nametag: true
villageroptimizer.optimize.block: true
villageroptimizer.optimize.workstation: true
villageroptimizer.optimize.nametag:
description: Optimize/Unoptimize villagers using nametags
villageroptimizer.optimize.block:
description: Optimize/Unoptimize villagers using specific blocks
villageroptimizer.optimize.workstation:
description: Optimize/Unoptimize villagers using workstations
villageroptimizer.cmd.*:
description: All command permissions
children:
villageroptimizer.cmd.reload: true
villageroptimizer.cmd.disable: true
villageroptimizer.cmd.version: true
villageroptimizer.cmd.optimize: true
villageroptimizer.cmd.unoptimize: true
villageroptimizer.cmd.disable:
description: Disable the plugin
villageroptimizer.cmd.reload:
description: Reload the plugin configuration
villageroptimizer.cmd.version:
description: Show the plugin version
villageroptimizer.cmd.optimize:
description: Optimize villagers in a radius
villageroptimizer.cmd.unoptimize:
description: Unoptimize villagers in a radius
villageroptimizer.bypass.*:
description: All bypass permissions
children:
villageroptimizer.bypass.tradeprevention: true
villageroptimizer.bypass.restockcooldown: true
villageroptimizer.bypass.nametagcooldown: true
villageroptimizer.bypass.blockcooldown: true
villageroptimizer.bypass.workstationcooldown: true
villageroptimizer.bypass.commandcooldown: true
villageroptimizer.bypass.tradeprevention:
description: Bypass unoptimized trading prevention if enabled
villageroptimizer.bypass.restockcooldown:
description: Bypass permission for optimized trade restock cooldown
villageroptimizer.bypass.nametagcooldown:
description: Bypass permission for nametag optimization cooldown
villageroptimizer.bypass.blockcooldown:
description: Bypass permission for block optimization cooldown
villageroptimizer.bypass.workstationcooldown:
description: Bypass permission for workstation optimization cooldown
villageroptimizer.bypass.commandcooldown:
description: Bypass permission for command optimization cooldown

66
pom.xml
View File

@ -6,18 +6,15 @@
<groupId>me.xginko.VillagerOptimizer</groupId> <groupId>me.xginko.VillagerOptimizer</groupId>
<artifactId>VillagerOptimizer</artifactId> <artifactId>VillagerOptimizer</artifactId>
<version>1.0.2</version> <version>1.1.0</version>
<modules> <packaging>jar</packaging>
<module>VillagerOptimizer-1.20.2</module>
<module>VillagerOptimizer-1.16.5</module>
</modules>
<packaging>pom</packaging>
<name>VillagerOptimizer</name> <name>VillagerOptimizer</name>
<description>Combat heavy villager lag by letting players optimize their trading halls.</description> <description>Combat heavy villager lag by letting players optimize their trading halls.</description>
<url>https://github.com/xGinko/VillagerOptimizer</url> <url>https://github.com/xGinko/VillagerOptimizer</url>
<properties> <properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
@ -43,8 +40,20 @@
<goal>shade</goal> <goal>shade</goal>
</goals> </goals>
<configuration> <configuration>
<createDependencyReducedPom>false</createDependencyReducedPom> <relocations>
<finalName>${project.parent.artifactId}-${project.parent.version}--${project.artifactId}</finalName> <relocation>
<pattern>com.tcoded.folialib</pattern>
<shadedPattern>me.xginko.villageroptimizer.libs.folialib</shadedPattern>
</relocation>
<relocation>
<pattern>com.github.benmanes.caffeine</pattern>
<shadedPattern>me.xginko.villageroptimizer.libs.caffeine</shadedPattern>
</relocation>
<relocation>
<pattern>org.bstats</pattern>
<shadedPattern>me.xginko.villageroptimizer.libs.bstats</shadedPattern>
</relocation>
</relocations>
<filters> <filters>
<filter> <filter>
<artifact>*:*</artifact> <artifact>*:*</artifact>
@ -54,6 +63,7 @@
</excludes> </excludes>
</filter> </filter>
</filters> </filters>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration> </configuration>
</execution> </execution>
</executions> </executions>
@ -69,16 +79,43 @@
<repositories> <repositories>
<repository> <repository>
<id>papermc</id> <id>papermc-repo</id>
<url>https://papermc.io/repo/repository/maven-public/</url> <url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>sonatype</id>
<url>https://oss.sonatype.org/content/groups/public/</url>
</repository> </repository>
<repository> <repository>
<id>configmaster-repo</id> <id>configmaster-repo</id>
<url>https://ci.pluginwiki.us/plugin/repository/everything/</url> <url>https://ci.pluginwiki.us/plugin/repository/everything/</url>
</repository> </repository>
<repository>
<id>folialib-repo</id>
<url>https://nexuslite.gcnt.net/repos/other/</url>
</repository>
</repositories> </repositories>
<dependencies> <dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.20.4-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- Needed for 1.16 as it's not in Paper on that version -->
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-minimessage</artifactId>
<version>4.15.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-plain</artifactId>
<version>4.15.0</version>
<scope>compile</scope>
</dependency>
<!-- Bukkit bStats --> <!-- Bukkit bStats -->
<dependency> <dependency>
<groupId>org.bstats</groupId> <groupId>org.bstats</groupId>
@ -93,12 +130,19 @@
<version>v2.0.0-rc.1</version> <version>v2.0.0-rc.1</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<!-- Caching --> <!-- Fast Caching -->
<dependency> <dependency>
<groupId>com.github.ben-manes.caffeine</groupId> <groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId> <artifactId>caffeine</artifactId>
<version>3.1.8</version> <version>3.1.8</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<!-- Folia Support -->
<dependency>
<groupId>com.tcoded</groupId>
<artifactId>FoliaLib</artifactId>
<version>0.3.1</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -15,7 +15,7 @@ public final class VillagerCache {
private final @NotNull Cache<UUID, WrappedVillager> villagerCache; private final @NotNull Cache<UUID, WrappedVillager> villagerCache;
VillagerCache(long expireAfterWriteSeconds) { public VillagerCache(long expireAfterWriteSeconds) {
this.villagerCache = Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(expireAfterWriteSeconds)).build(); this.villagerCache = Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(expireAfterWriteSeconds)).build();
} }

View File

@ -1,5 +1,7 @@
package me.xginko.villageroptimizer; package me.xginko.villageroptimizer;
import com.tcoded.folialib.FoliaLib;
import com.tcoded.folialib.impl.ServerImplementation;
import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand; import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand;
import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.config.LanguageCache; import me.xginko.villageroptimizer.config.LanguageCache;
@ -28,21 +30,23 @@ import java.util.stream.Collectors;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
public final class VillagerOptimizer extends JavaPlugin { public final class VillagerOptimizer extends JavaPlugin {
public static final Style plugin_style = Style.style(TextColor.color(102,255,230), TextDecoration.BOLD);
private static VillagerOptimizer instance; private static VillagerOptimizer instance;
private static VillagerCache villagerCache; private static VillagerCache villagerCache;
private static FoliaLib foliaLib;
private static HashMap<String, LanguageCache> languageCacheMap; private static HashMap<String, LanguageCache> languageCacheMap;
private static Config config; private static Config config;
private static ConsoleCommandSender console; private static ConsoleCommandSender console;
private static Logger logger; private static Logger logger;
public final static Style plugin_style = Style.style(TextColor.color(102,255,230), TextDecoration.BOLD);
@Override @Override
public void onEnable() { public void onEnable() {
instance = this; instance = this;
logger = getLogger(); logger = getLogger();
console = getServer().getConsoleSender(); console = getServer().getConsoleSender();
foliaLib = new FoliaLib(this);
console.sendMessage(Component.text("╭────────────────────────────────────────────────────────────╮").style(plugin_style)); console.sendMessage(Component.text("╭────────────────────────────────────────────────────────────╮").style(plugin_style));
console.sendMessage(Component.text("│ │").style(plugin_style)); console.sendMessage(Component.text("│ │").style(plugin_style));
console.sendMessage(Component.text("│ │").style(plugin_style)); console.sendMessage(Component.text("│ │").style(plugin_style));
@ -76,6 +80,7 @@ public final class VillagerOptimizer extends JavaPlugin {
console.sendMessage(Component.text("│ │").style(plugin_style)); console.sendMessage(Component.text("│ │").style(plugin_style));
console.sendMessage(Component.text("│ │").style(plugin_style)); console.sendMessage(Component.text("│ │").style(plugin_style));
console.sendMessage(Component.text("╰────────────────────────────────────────────────────────────╯").style(plugin_style)); console.sendMessage(Component.text("╰────────────────────────────────────────────────────────────╯").style(plugin_style));
new Metrics(this, 19954); new Metrics(this, 19954);
} }
@ -91,6 +96,12 @@ public final class VillagerOptimizer extends JavaPlugin {
public static VillagerCache getCache() { public static VillagerCache getCache() {
return villagerCache; return villagerCache;
} }
public static FoliaLib getFoliaLib() {
return foliaLib;
}
public static ServerImplementation getScheduler() {
return foliaLib.getImpl();
}
public static ConsoleCommandSender getConsole() { public static ConsoleCommandSender getConsole() {
return console; return console;
} }

View File

@ -55,11 +55,13 @@ public final class WrappedVillager {
public void setOptimization(OptimizationType type) { public void setOptimization(OptimizationType type) {
if (type.equals(OptimizationType.NONE) && isOptimized()) { if (type.equals(OptimizationType.NONE) && isOptimized()) {
dataContainer.remove(Keys.OPTIMIZATION_TYPE.key()); dataContainer.remove(Keys.OPTIMIZATION_TYPE.key());
VillagerOptimizer.getScheduler().runAtEntity(villager, enableAI -> {
villager.setAware(true); villager.setAware(true);
villager.setAI(true); villager.setAI(true);
});
} else { } else {
dataContainer.set(Keys.OPTIMIZATION_TYPE.key(), PersistentDataType.STRING, type.name()); dataContainer.set(Keys.OPTIMIZATION_TYPE.key(), PersistentDataType.STRING, type.name());
villager.setAware(false); VillagerOptimizer.getScheduler().runAtEntity(villager, disableAI -> villager.setAware(false));
} }
} }

View File

@ -5,8 +5,9 @@ import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.WrappedVillager; import me.xginko.villageroptimizer.WrappedVillager;
import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand; import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand;
import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.enums.permissions.Bypass;
import me.xginko.villageroptimizer.enums.permissions.Commands;
import me.xginko.villageroptimizer.enums.OptimizationType; import me.xginko.villageroptimizer.enums.OptimizationType;
import me.xginko.villageroptimizer.enums.Permissions;
import me.xginko.villageroptimizer.events.VillagerOptimizeEvent; import me.xginko.villageroptimizer.events.VillagerOptimizeEvent;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextReplacementConfig; import net.kyori.adventure.text.TextReplacementConfig;
@ -26,7 +27,7 @@ import java.util.List;
public class OptVillagersRadius implements VillagerOptimizerCommand, TabCompleter { public class OptVillagersRadius implements VillagerOptimizerCommand, TabCompleter {
private final List<String> tabCompletes = List.of("5", "10", "25", "50"); private final List<String> radiusSuggestions = List.of("5", "10", "25", "50");
private final long cooldown; private final long cooldown;
private final int max_radius; private final int max_radius;
@ -45,7 +46,7 @@ public class OptVillagersRadius implements VillagerOptimizerCommand, TabComplete
@Override @Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
return args.length == 1 ? tabCompletes : null; return args.length == 1 ? radiusSuggestions : null;
} }
@Override @Override
@ -56,7 +57,7 @@ public class OptVillagersRadius implements VillagerOptimizerCommand, TabComplete
return true; return true;
} }
if (!sender.hasPermission(Permissions.Commands.OPTIMIZE_RADIUS.get())) { if (!sender.hasPermission(Commands.OPTIMIZE_RADIUS.get())) {
sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission); sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission);
return true; return true;
} }
@ -81,7 +82,7 @@ public class OptVillagersRadius implements VillagerOptimizerCommand, TabComplete
VillagerCache villagerCache = VillagerOptimizer.getCache(); VillagerCache villagerCache = VillagerOptimizer.getCache();
int successCount = 0; int successCount = 0;
int failCount = 0; int failCount = 0;
final boolean player_has_cooldown_bypass = player.hasPermission(Permissions.Bypass.COMMAND_COOLDOWN.get()); final boolean player_has_cooldown_bypass = player.hasPermission(Bypass.COMMAND_COOLDOWN.get());
for (Entity entity : player.getNearbyEntities(specifiedRadius, specifiedRadius, specifiedRadius)) { for (Entity entity : player.getNearbyEntities(specifiedRadius, specifiedRadius, specifiedRadius)) {
if (!entity.getType().equals(EntityType.VILLAGER)) continue; if (!entity.getType().equals(EntityType.VILLAGER)) continue;

View File

@ -4,8 +4,8 @@ import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.WrappedVillager; import me.xginko.villageroptimizer.WrappedVillager;
import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand; import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand;
import me.xginko.villageroptimizer.enums.permissions.Commands;
import me.xginko.villageroptimizer.enums.OptimizationType; import me.xginko.villageroptimizer.enums.OptimizationType;
import me.xginko.villageroptimizer.enums.Permissions;
import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent; import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextReplacementConfig; import net.kyori.adventure.text.TextReplacementConfig;
@ -25,7 +25,7 @@ import java.util.List;
public class UnOptVillagersRadius implements VillagerOptimizerCommand, TabCompleter { public class UnOptVillagersRadius implements VillagerOptimizerCommand, TabCompleter {
private final List<String> tabCompletes = List.of("5", "10", "25", "50"); private final List<String> radiusSuggestions = List.of("5", "10", "25", "50");
private final int max_radius; private final int max_radius;
public UnOptVillagersRadius() { public UnOptVillagersRadius() {
@ -39,7 +39,7 @@ public class UnOptVillagersRadius implements VillagerOptimizerCommand, TabComple
@Override @Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
return args.length == 1 ? tabCompletes : null; return args.length == 1 ? radiusSuggestions : null;
} }
@Override @Override
@ -50,7 +50,7 @@ public class UnOptVillagersRadius implements VillagerOptimizerCommand, TabComple
return true; return true;
} }
if (!sender.hasPermission(Permissions.Commands.UNOPTIMIZE_RADIUS.get())) { if (!sender.hasPermission(Commands.UNOPTIMIZE_RADIUS.get())) {
sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission); sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission);
return true; return true;
} }

View File

@ -6,7 +6,7 @@ import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand;
import me.xginko.villageroptimizer.commands.villageroptimizer.subcommands.DisableSubCmd; import me.xginko.villageroptimizer.commands.villageroptimizer.subcommands.DisableSubCmd;
import me.xginko.villageroptimizer.commands.villageroptimizer.subcommands.ReloadSubCmd; import me.xginko.villageroptimizer.commands.villageroptimizer.subcommands.ReloadSubCmd;
import me.xginko.villageroptimizer.commands.villageroptimizer.subcommands.VersionSubCmd; import me.xginko.villageroptimizer.commands.villageroptimizer.subcommands.VersionSubCmd;
import me.xginko.villageroptimizer.enums.Permissions; import me.xginko.villageroptimizer.enums.permissions.Commands;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command; import org.bukkit.command.Command;
@ -14,19 +14,16 @@ import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter; import org.bukkit.command.TabCompleter;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class VillagerOptimizerCmd implements TabCompleter, VillagerOptimizerCommand { public class VillagerOptimizerCmd implements TabCompleter, VillagerOptimizerCommand {
private final List<SubCommand> subCommands = new ArrayList<>(3); private final List<SubCommand> subCommands;
private final List<String> tabCompleter = new ArrayList<>(3); private final List<String> tabCompleter;
public VillagerOptimizerCmd() { public VillagerOptimizerCmd() {
subCommands.add(new ReloadSubCmd()); subCommands = List.of(new ReloadSubCmd(), new VersionSubCmd(), new DisableSubCmd());
subCommands.add(new VersionSubCmd()); tabCompleter = subCommands.stream().map(SubCommand::getLabel).toList();
subCommands.add(new DisableSubCmd());
subCommands.forEach(subCommand -> tabCompleter.add(subCommand.getLabel()));
} }
@Override @Override
@ -58,7 +55,7 @@ public class VillagerOptimizerCmd implements TabCompleter, VillagerOptimizerComm
} }
private void sendCommandOverview(CommandSender sender) { private void sendCommandOverview(CommandSender sender) {
if (!sender.hasPermission(Permissions.Commands.RELOAD.get()) && !sender.hasPermission(Permissions.Commands.VERSION.get())) return; if (!sender.hasPermission(Commands.RELOAD.get()) && !sender.hasPermission(Commands.VERSION.get())) return;
sender.sendMessage(Component.text("-----------------------------------------------------").color(NamedTextColor.GRAY)); sender.sendMessage(Component.text("-----------------------------------------------------").color(NamedTextColor.GRAY));
sender.sendMessage(Component.text("VillagerOptimizer Commands").color(VillagerOptimizer.plugin_style.color())); sender.sendMessage(Component.text("VillagerOptimizer Commands").color(VillagerOptimizer.plugin_style.color()));
sender.sendMessage(Component.text("-----------------------------------------------------").color(NamedTextColor.GRAY)); sender.sendMessage(Component.text("-----------------------------------------------------").color(NamedTextColor.GRAY));

View File

@ -2,7 +2,7 @@ package me.xginko.villageroptimizer.commands.villageroptimizer.subcommands;
import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.commands.SubCommand; import me.xginko.villageroptimizer.commands.SubCommand;
import me.xginko.villageroptimizer.enums.Permissions; import me.xginko.villageroptimizer.enums.permissions.Commands;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TextComponent;
@ -28,7 +28,7 @@ public class DisableSubCmd extends SubCommand {
@Override @Override
public void perform(CommandSender sender, String[] args) { public void perform(CommandSender sender, String[] args) {
if (sender.hasPermission(Permissions.Commands.DISABLE.get())) { if (sender.hasPermission(Commands.DISABLE.get())) {
sender.sendMessage(Component.text("Disabling VillagerOptimizer...").color(NamedTextColor.RED)); sender.sendMessage(Component.text("Disabling VillagerOptimizer...").color(NamedTextColor.RED));
VillagerOptimizerModule.modules.forEach(VillagerOptimizerModule::disable); VillagerOptimizerModule.modules.forEach(VillagerOptimizerModule::disable);
VillagerOptimizerModule.modules.clear(); VillagerOptimizerModule.modules.clear();

View File

@ -2,7 +2,7 @@ package me.xginko.villageroptimizer.commands.villageroptimizer.subcommands;
import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.commands.SubCommand; import me.xginko.villageroptimizer.commands.SubCommand;
import me.xginko.villageroptimizer.enums.Permissions; import me.xginko.villageroptimizer.enums.permissions.Commands;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
@ -27,13 +27,10 @@ public class ReloadSubCmd extends SubCommand {
@Override @Override
public void perform(CommandSender sender, String[] args) { public void perform(CommandSender sender, String[] args) {
if (sender.hasPermission(Permissions.Commands.RELOAD.get())) { if (sender.hasPermission(Commands.RELOAD.get())) {
sender.sendMessage(Component.text("Reloading VillagerOptimizer...").color(NamedTextColor.WHITE)); sender.sendMessage(Component.text("Reloading VillagerOptimizer...").color(NamedTextColor.WHITE));
VillagerOptimizer plugin = VillagerOptimizer.getInstance(); VillagerOptimizer.getInstance().reloadPlugin();
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
plugin.reloadPlugin();
sender.sendMessage(Component.text("Reload complete.").color(NamedTextColor.GREEN)); sender.sendMessage(Component.text("Reload complete.").color(NamedTextColor.GREEN));
});
} else { } else {
sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission); sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission);
} }

View File

@ -2,7 +2,7 @@ package me.xginko.villageroptimizer.commands.villageroptimizer.subcommands;
import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.commands.SubCommand; import me.xginko.villageroptimizer.commands.SubCommand;
import me.xginko.villageroptimizer.enums.Permissions; import me.xginko.villageroptimizer.enums.permissions.Commands;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.ClickEvent;
@ -29,18 +29,18 @@ public class VersionSubCmd extends SubCommand {
@Override @Override
public void perform(CommandSender sender, String[] args) { public void perform(CommandSender sender, String[] args) {
if (sender.hasPermission(Permissions.Commands.VERSION.get())) { if (sender.hasPermission(Commands.VERSION.get())) {
final PluginDescriptionFile pluginMeta = VillagerOptimizer.getInstance().getDescription(); final PluginDescriptionFile pluginYML = VillagerOptimizer.getInstance().getDescription();
sender.sendMessage( sender.sendMessage(
Component.newline() Component.newline()
.append( .append(
Component.text(pluginMeta.getName()+" "+pluginMeta.getVersion()) Component.text(pluginYML.getName()+" "+pluginYML.getVersion())
.style(VillagerOptimizer.plugin_style) .style(VillagerOptimizer.plugin_style)
.clickEvent(ClickEvent.openUrl(pluginMeta.getWebsite())) .clickEvent(ClickEvent.openUrl(pluginYML.getWebsite()))
) )
.append(Component.text(" by ").color(NamedTextColor.GRAY)) .append(Component.text(" by ").color(NamedTextColor.GRAY))
.append( .append(
Component.text(pluginMeta.getAuthors().get(0)) Component.text(pluginYML.getAuthors().get(0))
.color(NamedTextColor.WHITE) .color(NamedTextColor.WHITE)
.clickEvent(ClickEvent.openUrl("https://github.com/xGinko")) .clickEvent(ClickEvent.openUrl("https://github.com/xGinko"))
) )

View File

@ -73,7 +73,7 @@ public class Config {
} }
public @NotNull ConfigFile master() { public @NotNull ConfigFile master() {
return config; return this.config;
} }
public boolean getBoolean(@NotNull String path, boolean def, @NotNull String comment) { public boolean getBoolean(@NotNull String path, boolean def, @NotNull String comment) {
@ -125,22 +125,4 @@ public class Config {
this.config.addDefault(path, def); this.config.addDefault(path, def);
return this.config.getStringList(path); return this.config.getStringList(path);
} }
public @NotNull ConfigSection getConfigSection(@NotNull String path, @NotNull Map<String, Object> defaultKeyValue) {
this.config.addDefault(path, null);
this.config.makeSectionLenient(path);
defaultKeyValue.forEach((string, object) -> this.config.addExample(path+"."+string, object));
return this.config.getConfigSection(path);
}
public @NotNull ConfigSection getConfigSection(@NotNull String path, @NotNull Map<String, Object> defaultKeyValue, @NotNull String comment) {
this.config.addDefault(path, null, comment);
this.config.makeSectionLenient(path);
defaultKeyValue.forEach((string, object) -> this.config.addExample(path+"."+string, object));
return this.config.getConfigSection(path);
}
public void addComment(@NotNull String path, @NotNull String comment) {
this.config.addComment(path, comment);
}
} }

View File

@ -4,7 +4,6 @@ import me.xginko.villageroptimizer.VillagerOptimizer;
import org.bukkit.NamespacedKey; import org.bukkit.NamespacedKey;
public enum Keys { public enum Keys {
OPTIMIZATION_TYPE(VillagerOptimizer.getKey("optimization-type")), OPTIMIZATION_TYPE(VillagerOptimizer.getKey("optimization-type")),
LAST_OPTIMIZE(VillagerOptimizer.getKey("last-optimize")), LAST_OPTIMIZE(VillagerOptimizer.getKey("last-optimize")),
LAST_LEVELUP(VillagerOptimizer.getKey("last-levelup")), LAST_LEVELUP(VillagerOptimizer.getKey("last-levelup")),
@ -12,13 +11,10 @@ public enum Keys {
LAST_OPTIMIZE_NAME(VillagerOptimizer.getKey("last-optimize-name")); LAST_OPTIMIZE_NAME(VillagerOptimizer.getKey("last-optimize-name"));
private final NamespacedKey key; private final NamespacedKey key;
Keys(NamespacedKey key) { Keys(NamespacedKey key) {
this.key = key; this.key = key;
} }
public NamespacedKey key() { public NamespacedKey key() {
return key; return key;
} }
} }

View File

@ -1,11 +1,9 @@
package me.xginko.villageroptimizer.enums; package me.xginko.villageroptimizer.enums;
public enum OptimizationType { public enum OptimizationType {
COMMAND, COMMAND,
NAMETAG, NAMETAG,
WORKSTATION, WORKSTATION,
BLOCK, BLOCK,
NONE NONE
} }

View File

@ -0,0 +1,28 @@
package me.xginko.villageroptimizer.enums.permissions;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
public enum Bypass {
TRADE_PREVENTION(new Permission("villageroptimizer.bypass.tradeprevention",
"Permission to bypass unoptimized trade prevention", PermissionDefault.FALSE)),
RESTOCK_COOLDOWN(new Permission("villageroptimizer.bypass.restockcooldown",
"Permission to bypass restock cooldown on optimized villagers", PermissionDefault.FALSE)),
NAMETAG_COOLDOWN(new Permission("villageroptimizer.bypass.nametagcooldown",
"Permission to bypass Nametag optimization cooldown", PermissionDefault.FALSE)),
BLOCK_COOLDOWN(new Permission("villageroptimizer.bypass.blockcooldown",
"Permission to bypass Block optimization cooldown", PermissionDefault.FALSE)),
WORKSTATION_COOLDOWN(new Permission("villageroptimizer.bypass.workstationcooldown",
"Permission to bypass Workstation optimization cooldown", PermissionDefault.FALSE)),
COMMAND_COOLDOWN(new Permission("villageroptimizer.bypass.commandcooldown",
"Permission to bypass command optimization cooldown", PermissionDefault.FALSE));
private final Permission permission;
Bypass(Permission permission) {
this.permission = permission;
}
public Permission get() {
return permission;
}
}

View File

@ -0,0 +1,26 @@
package me.xginko.villageroptimizer.enums.permissions;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
public enum Commands {
VERSION(new Permission("villageroptimizer.cmd.version",
"Permission get the plugin version", PermissionDefault.FALSE)),
RELOAD(new Permission("villageroptimizer.cmd.reload",
"Permission to reload the plugin config", PermissionDefault.FALSE)),
DISABLE(new Permission("villageroptimizer.cmd.disable",
"Permission to disable the plugin", PermissionDefault.FALSE)),
OPTIMIZE_RADIUS(new Permission("villageroptimizer.cmd.optimize",
"Permission to optimize villagers in a radius", PermissionDefault.TRUE)),
UNOPTIMIZE_RADIUS(new Permission("villageroptimizer.cmd.unoptimize",
"Permission to unoptimize villagers in a radius", PermissionDefault.TRUE));
private final Permission permission;
Commands(Permission permission) {
this.permission = permission;
}
public Permission get() {
return permission;
}
}

View File

@ -0,0 +1,22 @@
package me.xginko.villageroptimizer.enums.permissions;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
public enum Optimize {
NAMETAG(new Permission("villageroptimizer.optimize.nametag",
"Permission to optimize / unoptimize using Nametags", PermissionDefault.TRUE)),
BLOCK(new Permission("villageroptimizer.optimize.block",
"Permission to optimize / unoptimize using Blocks", PermissionDefault.TRUE)),
WORKSTATION(new Permission("villageroptimizer.optimize.workstation",
"Permission to optimize / unoptimize using Workstations", PermissionDefault.TRUE));
private final Permission permission;
Optimize(Permission permission) {
this.permission = permission;
}
public Permission get() {
return permission;
}
}

View File

@ -13,28 +13,28 @@ public class VillagerOptimizeEvent extends Event implements Cancellable {
private static final @NotNull HandlerList handlers = new HandlerList(); private static final @NotNull HandlerList handlers = new HandlerList();
private final @NotNull WrappedVillager wrappedVillager; private final @NotNull WrappedVillager wrappedVillager;
private @NotNull OptimizationType type; private @NotNull OptimizationType optimizationType;
private final @Nullable Player whoOptimised; private final @Nullable Player whoOptimised;
private boolean isCancelled = false; private boolean isCancelled = false;
public VillagerOptimizeEvent(@NotNull WrappedVillager wrappedVillager, @NotNull OptimizationType type, @Nullable Player whoOptimised, boolean isAsync) throws IllegalArgumentException { public VillagerOptimizeEvent(@NotNull WrappedVillager wrappedVillager, @NotNull OptimizationType optimizationType, @Nullable Player whoOptimised, boolean isAsync) throws IllegalArgumentException {
super(isAsync); super(isAsync);
this.wrappedVillager = wrappedVillager; this.wrappedVillager = wrappedVillager;
this.whoOptimised = whoOptimised; this.whoOptimised = whoOptimised;
if (type.equals(OptimizationType.NONE)) { if (optimizationType.equals(OptimizationType.NONE)) {
throw new IllegalArgumentException("OptimizationType can't be NONE."); throw new IllegalArgumentException("OptimizationType can't be NONE.");
} else { } else {
this.type = type; this.optimizationType = optimizationType;
} }
} }
public VillagerOptimizeEvent(@NotNull WrappedVillager wrappedVillager, @NotNull OptimizationType type, @Nullable Player whoOptimised) throws IllegalArgumentException { public VillagerOptimizeEvent(@NotNull WrappedVillager wrappedVillager, @NotNull OptimizationType optimizationType, @Nullable Player whoOptimised) throws IllegalArgumentException {
this.wrappedVillager = wrappedVillager; this.wrappedVillager = wrappedVillager;
this.whoOptimised = whoOptimised; this.whoOptimised = whoOptimised;
if (type.equals(OptimizationType.NONE)) { if (optimizationType.equals(OptimizationType.NONE)) {
throw new IllegalArgumentException("OptimizationType can't be NONE."); throw new IllegalArgumentException("OptimizationType can't be NONE.");
} else { } else {
this.type = type; this.optimizationType = optimizationType;
} }
} }
@ -43,14 +43,14 @@ public class VillagerOptimizeEvent extends Event implements Cancellable {
} }
public @NotNull OptimizationType getOptimizationType() { public @NotNull OptimizationType getOptimizationType() {
return type; return optimizationType;
} }
public void setOptimizationType(@NotNull OptimizationType type) throws IllegalArgumentException { public void setOptimizationType(@NotNull OptimizationType optimizationType) throws IllegalArgumentException {
if (type.equals(OptimizationType.NONE)) { if (optimizationType.equals(OptimizationType.NONE)) {
throw new IllegalArgumentException("OptimizationType can't be NONE."); throw new IllegalArgumentException("OptimizationType can't be NONE.");
} else { } else {
this.type = type; this.optimizationType = optimizationType;
} }
} }

View File

@ -13,21 +13,21 @@ public class VillagerUnoptimizeEvent extends Event implements Cancellable {
private static final @NotNull HandlerList handlers = new HandlerList(); private static final @NotNull HandlerList handlers = new HandlerList();
private final @NotNull WrappedVillager wrappedVillager; private final @NotNull WrappedVillager wrappedVillager;
private final @NotNull OptimizationType unoptimizeType; private final @NotNull OptimizationType unOptimizeType;
private final @Nullable Player whoUnoptimized; private final @Nullable Player whoUnoptimized;
private boolean isCancelled = false; private boolean isCancelled = false;
public VillagerUnoptimizeEvent(@NotNull WrappedVillager wrappedVillager, @Nullable Player whoUnoptimized, @NotNull OptimizationType unoptimizeType, boolean isAsync) { public VillagerUnoptimizeEvent(@NotNull WrappedVillager wrappedVillager, @Nullable Player whoUnoptimized, @NotNull OptimizationType unOptimizeType, boolean isAsync) {
super(isAsync); super(isAsync);
this.wrappedVillager = wrappedVillager; this.wrappedVillager = wrappedVillager;
this.whoUnoptimized = whoUnoptimized; this.whoUnoptimized = whoUnoptimized;
this.unoptimizeType = unoptimizeType; this.unOptimizeType = unOptimizeType;
} }
public VillagerUnoptimizeEvent(@NotNull WrappedVillager wrappedVillager, @Nullable Player whoUnoptimized, @NotNull OptimizationType unoptimizeType) { public VillagerUnoptimizeEvent(@NotNull WrappedVillager wrappedVillager, @Nullable Player whoUnoptimized, @NotNull OptimizationType unOptimizeType) {
this.wrappedVillager = wrappedVillager; this.wrappedVillager = wrappedVillager;
this.whoUnoptimized = whoUnoptimized; this.whoUnoptimized = whoUnoptimized;
this.unoptimizeType = unoptimizeType; this.unOptimizeType = unOptimizeType;
} }
public @NotNull WrappedVillager getWrappedVillager() { public @NotNull WrappedVillager getWrappedVillager() {
@ -39,7 +39,7 @@ public class VillagerUnoptimizeEvent extends Event implements Cancellable {
} }
public @NotNull OptimizationType getWhichTypeUnoptimized() { public @NotNull OptimizationType getWhichTypeUnoptimized() {
return unoptimizeType; return unOptimizeType;
} }
@Override @Override

View File

@ -1,9 +1,11 @@
package me.xginko.villageroptimizer.modules; package me.xginko.villageroptimizer.modules;
import io.papermc.paper.threadedregions.scheduler.ScheduledTask; import com.tcoded.folialib.impl.ServerImplementation;
import com.tcoded.folialib.wrapper.task.WrappedTask;
import me.xginko.villageroptimizer.VillagerCache; import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.utils.CommonUtil;
import me.xginko.villageroptimizer.utils.LogUtil; import me.xginko.villageroptimizer.utils.LogUtil;
import org.bukkit.Chunk; import org.bukkit.Chunk;
import org.bukkit.Server; import org.bukkit.Server;
@ -26,8 +28,9 @@ import java.util.logging.Level;
public class VillagerChunkLimit implements VillagerOptimizerModule, Listener { public class VillagerChunkLimit implements VillagerOptimizerModule, Listener {
private final ServerImplementation scheduler;
private final VillagerCache villagerCache; private final VillagerCache villagerCache;
private ScheduledTask periodic_chunk_check; private WrappedTask periodic_chunk_check;
private final List<Villager.Profession> non_optimized_removal_priority = new ArrayList<>(16); private final List<Villager.Profession> non_optimized_removal_priority = new ArrayList<>(16);
private final List<Villager.Profession> optimized_removal_priority = new ArrayList<>(16); private final List<Villager.Profession> optimized_removal_priority = new ArrayList<>(16);
private final long check_period; private final long check_period;
@ -36,9 +39,10 @@ public class VillagerChunkLimit implements VillagerOptimizerModule, Listener {
protected VillagerChunkLimit() { protected VillagerChunkLimit() {
shouldEnable(); shouldEnable();
this.scheduler = VillagerOptimizer.getScheduler();
this.villagerCache = VillagerOptimizer.getCache(); this.villagerCache = VillagerOptimizer.getCache();
Config config = VillagerOptimizer.getConfiguration(); Config config = VillagerOptimizer.getConfiguration();
config.addComment("villager-chunk-limit.enable", """ config.master().addComment("villager-chunk-limit.enable", """
Checks chunks for too many villagers and removes excess villagers based on priority."""); Checks chunks for too many villagers and removes excess villagers based on priority.""");
this.check_period = config.getInt("villager-chunk-limit.check-period-in-ticks", 600, """ this.check_period = config.getInt("villager-chunk-limit.check-period-in-ticks", 600, """
Check all loaded chunks every X ticks. 1 second = 20 ticks\s Check all loaded chunks every X ticks. 1 second = 20 ticks\s
@ -87,12 +91,12 @@ public class VillagerChunkLimit implements VillagerOptimizerModule, Listener {
final VillagerOptimizer plugin = VillagerOptimizer.getInstance(); final VillagerOptimizer plugin = VillagerOptimizer.getInstance();
final Server server = plugin.getServer(); final Server server = plugin.getServer();
server.getPluginManager().registerEvents(this, plugin); server.getPluginManager().registerEvents(this, plugin);
this.periodic_chunk_check = server.getGlobalRegionScheduler().runAtFixedRate(plugin, periodic_chunk_check -> {
this.periodic_chunk_check = scheduler.runTimer(() -> {
for (World world : server.getWorlds()) { for (World world : server.getWorlds()) {
for (Chunk chunk : world.getLoadedChunks()) { for (Chunk chunk : world.getLoadedChunks()) {
plugin.getServer().getRegionScheduler().run( if (skip_unloaded_entity_chunks && !CommonUtil.isEntitiesLoaded(chunk)) continue;
plugin, world, chunk.getX(), chunk.getZ(), check_chunk -> this.manageVillagerCount(chunk) this.manageVillagerCount(chunk);
);
} }
} }
}, check_period, check_period); }, check_period, check_period);
@ -111,23 +115,19 @@ public class VillagerChunkLimit implements VillagerOptimizerModule, Listener {
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onCreatureSpawn(CreatureSpawnEvent event) { private void onCreatureSpawn(CreatureSpawnEvent event) {
Entity spawned = event.getEntity(); if (event.getEntityType() == EntityType.VILLAGER) {
if (spawned.getType().equals(EntityType.VILLAGER)) { this.manageVillagerCount(event.getEntity().getChunk());
this.manageVillagerCount(spawned.getChunk());
} }
} }
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
private void onInteract(PlayerInteractEntityEvent event) { private void onInteract(PlayerInteractEntityEvent event) {
Entity clicked = event.getRightClicked(); if (event.getRightClicked().getType() == EntityType.VILLAGER) {
if (clicked.getType().equals(EntityType.VILLAGER)) { this.manageVillagerCount(event.getRightClicked().getChunk());
this.manageVillagerCount(clicked.getChunk());
} }
} }
private void manageVillagerCount(@NotNull Chunk chunk) { private void manageVillagerCount(@NotNull Chunk chunk) {
if (skip_unloaded_entity_chunks && !chunk.isEntitiesLoaded()) return;
// Collect all optimized and unoptimized villagers in that chunk // Collect all optimized and unoptimized villagers in that chunk
List<Villager> optimized_villagers = new ArrayList<>(); List<Villager> optimized_villagers = new ArrayList<>();
List<Villager> not_optimized_villagers = new ArrayList<>(); List<Villager> not_optimized_villagers = new ArrayList<>();
@ -154,10 +154,12 @@ public class VillagerChunkLimit implements VillagerOptimizerModule, Listener {
// Remove prioritized villagers that are too many // Remove prioritized villagers that are too many
for (int i = 0; i < not_optimized_villagers_too_many; i++) { for (int i = 0; i < not_optimized_villagers_too_many; i++) {
Villager villager = not_optimized_villagers.get(i); Villager villager = not_optimized_villagers.get(i);
scheduler.runAtEntity(villager, kill -> {
villager.remove(); villager.remove();
if (log_enabled) LogUtil.moduleLog(Level.INFO, "villager-chunk-limit", if (log_enabled) LogUtil.moduleLog(Level.INFO, "villager-chunk-limit",
"Removed unoptimized villager of profession type '"+villager.getProfession().name()+"' at "+villager.getLocation() "Removed unoptimized villager of profession type '" + villager.getProfession().name()
); + "' at " + villager.getLocation());
});
} }
} }
@ -172,10 +174,12 @@ public class VillagerChunkLimit implements VillagerOptimizerModule, Listener {
// Remove prioritized villagers that are too many // Remove prioritized villagers that are too many
for (int i = 0; i < optimized_villagers_too_many; i++) { for (int i = 0; i < optimized_villagers_too_many; i++) {
Villager villager = optimized_villagers.get(i); Villager villager = optimized_villagers.get(i);
scheduler.runAtEntity(villager, kill -> {
villager.remove(); villager.remove();
if (log_enabled) LogUtil.moduleLog(Level.INFO, "villager-chunk-limit", if (log_enabled) LogUtil.moduleLog(Level.INFO, "villager-chunk-limit",
"Removed optimized villager of profession type '"+villager.getProfession().name()+"' at "+villager.getLocation() "Removed optimized villager of profession type '" + villager.getProfession().name()
); + "' at " + villager.getLocation());
});
} }
} }
} }

View File

@ -1,5 +1,6 @@
package me.xginko.villageroptimizer.modules; package me.xginko.villageroptimizer.modules;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.modules.gameplay.*; import me.xginko.villageroptimizer.modules.gameplay.*;
import me.xginko.villageroptimizer.modules.optimization.OptimizeByBlock; import me.xginko.villageroptimizer.modules.optimization.OptimizeByBlock;
import me.xginko.villageroptimizer.modules.optimization.OptimizeByNametag; import me.xginko.villageroptimizer.modules.optimization.OptimizeByNametag;

View File

@ -18,24 +18,25 @@ import org.bukkit.event.inventory.InventoryType;
import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectType;
import java.util.concurrent.TimeUnit;
public class LevelOptimizedProfession implements VillagerOptimizerModule, Listener { public class LevelOptimizedProfession implements VillagerOptimizerModule, Listener {
private final VillagerOptimizer plugin;
private final VillagerCache villagerCache; private final VillagerCache villagerCache;
private final boolean notify_player; private final boolean notify_player;
private final long cooldown; private final long cooldown_millis;
public LevelOptimizedProfession() { public LevelOptimizedProfession() {
shouldEnable(); shouldEnable();
this.plugin = VillagerOptimizer.getInstance();
this.villagerCache = VillagerOptimizer.getCache(); this.villagerCache = VillagerOptimizer.getCache();
Config config = VillagerOptimizer.getConfiguration(); Config config = VillagerOptimizer.getConfiguration();
config.addComment("gameplay.level-optimized-profession", """ config.master().addComment("gameplay.level-optimized-profession", """
This is needed to allow optimized villagers to level up.\s This is needed to allow optimized villagers to level up.\s
Temporarily enables the villagers AI to allow it to level up and then disables it again."""); Temporarily enables the villagers AI to allow it to level up and then disables it again.""");
this.cooldown = config.getInt("gameplay.level-optimized-profession.level-check-cooldown-seconds", 5, """ this.cooldown_millis = TimeUnit.SECONDS.toMillis(
config.getInt("gameplay.level-optimized-profession.level-check-cooldown-seconds", 5, """
Cooldown in seconds until the level of a villager will be checked and updated again.\s Cooldown in seconds until the level of a villager will be checked and updated again.\s
Recommended to leave as is.""") * 1000L; Recommended to leave as is."""));
this.notify_player = config.getBoolean("gameplay.level-optimized-profession.notify-player", true, this.notify_player = config.getBoolean("gameplay.level-optimized-profession.notify-player", true,
"Tell players to wait when a villager is leveling up."); "Tell players to wait when a villager is leveling up.");
} }
@ -65,23 +66,23 @@ public class LevelOptimizedProfession implements VillagerOptimizerModule, Listen
WrappedVillager wVillager = villagerCache.getOrAdd(villager); WrappedVillager wVillager = villagerCache.getOrAdd(villager);
if (!wVillager.isOptimized()) return; if (!wVillager.isOptimized()) return;
if (wVillager.canLevelUp(cooldown)) { if (wVillager.canLevelUp(cooldown_millis)) {
if (wVillager.calculateLevel() > villager.getVillagerLevel()) { if (wVillager.calculateLevel() > villager.getVillagerLevel()) {
villager.getScheduler().run(plugin, enableAI -> { VillagerOptimizer.getScheduler().runAtEntity(villager, enableAI -> {
villager.addPotionEffect(new PotionEffect(PotionEffectType.SLOW, 120, 120, false, false)); villager.addPotionEffect(new PotionEffect(PotionEffectType.SLOW, 120, 120, false, false));
villager.setAware(true); villager.setAware(true);
}, null); });
villager.getScheduler().runDelayed(plugin, disableAI -> { VillagerOptimizer.getScheduler().runAtEntityLater(villager, disableAI -> {
villager.setAware(false); villager.setAware(false);
wVillager.saveLastLevelUp(); wVillager.saveLastLevelUp();
}, null, 100L); }, 5, TimeUnit.SECONDS);
} }
} else { } else {
if (notify_player) { if (notify_player) {
Player player = (Player) event.getPlayer(); Player player = (Player) event.getPlayer();
final TextReplacementConfig timeLeft = TextReplacementConfig.builder() final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
.matchLiteral("%time%") .matchLiteral("%time%")
.replacement(CommonUtil.formatTime(wVillager.getLevelCooldownMillis(cooldown))) .replacement(CommonUtil.formatTime(wVillager.getLevelCooldownMillis(cooldown_millis)))
.build(); .build();
VillagerOptimizer.getLang(player.locale()).villager_leveling_up.forEach(line -> player.sendMessage(line.replaceText(timeLeft))); VillagerOptimizer.getLang(player.locale()).villager_leveling_up.forEach(line -> player.sendMessage(line.replaceText(timeLeft)));
} }

View File

@ -36,7 +36,7 @@ public class MakeVillagersSpawnAdult implements VillagerOptimizerModule, Listene
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onVillagerSpawn(CreatureSpawnEvent event) { private void onVillagerSpawn(CreatureSpawnEvent event) {
if (event.getEntityType().equals(EntityType.VILLAGER)) { if (event.getEntityType() == EntityType.VILLAGER) {
Villager villager = (Villager) event.getEntity(); Villager villager = (Villager) event.getEntity();
if (!villager.isAdult()) villager.setAdult(); if (!villager.isAdult()) villager.setAdult();
} }

View File

@ -1,6 +1,6 @@
package me.xginko.villageroptimizer.modules.gameplay; package me.xginko.villageroptimizer.modules.gameplay;
import io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent; import com.destroystokyo.paper.event.entity.EntityKnockbackByEntityEvent;
import me.xginko.villageroptimizer.VillagerCache; import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.config.Config;
@ -16,21 +16,22 @@ import org.bukkit.event.entity.EntityDamageEvent;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set;
public class PreventOptimizedDamage implements VillagerOptimizerModule, Listener { public class PreventOptimizedDamage implements VillagerOptimizerModule, Listener {
private final VillagerCache villagerCache; private final VillagerCache villagerCache;
private final HashSet<EntityDamageEvent.DamageCause> damage_causes_to_cancel = new HashSet<>(); private final Set<EntityDamageEvent.DamageCause> damage_causes_to_cancel = new HashSet<>();
private final boolean push; private final boolean cancelKnockback;
public PreventOptimizedDamage() { public PreventOptimizedDamage() {
shouldEnable(); shouldEnable();
this.villagerCache = VillagerOptimizer.getCache(); this.villagerCache = VillagerOptimizer.getCache();
Config config = VillagerOptimizer.getConfiguration(); Config config = VillagerOptimizer.getConfiguration();
config.addComment("gameplay.prevent-damage-to-optimized.enable", config.master().addComment("gameplay.prevent-damage-to-optimized.enable",
"Configure what kind of damage you want to cancel for optimized villagers here."); "Configure what kind of damage you want to cancel for optimized villagers here.");
this.push = config.getBoolean("gameplay.prevent-damage-to-optimized.prevent-push-from-attack", true, this.cancelKnockback = config.getBoolean("gameplay.prevent-damage-to-optimized.prevent-knockback-from-entity", true,
"Prevents optimized villagers from getting pushed by an attacking entity"); "Prevents optimized villagers from getting knocked back by an attacking entity");
config.getList("gameplay.prevent-damage-to-optimized.damage-causes-to-cancel", config.getList("gameplay.prevent-damage-to-optimized.damage-causes-to-cancel",
Arrays.stream(EntityDamageEvent.DamageCause.values()).map(Enum::name).sorted().toList(), """ Arrays.stream(EntityDamageEvent.DamageCause.values()).map(Enum::name).sorted().toList(), """
These are all current entries in the game. Remove what you do not need blocked.\s These are all current entries in the game. Remove what you do not need blocked.\s
@ -74,9 +75,9 @@ public class PreventOptimizedDamage implements VillagerOptimizerModule, Listener
} }
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onPushByEntityAttack(EntityPushedByEntityAttackEvent event) { private void onKnockbackByEntity(EntityKnockbackByEntityEvent event) {
if ( if (
push cancelKnockback
&& event.getEntityType().equals(EntityType.VILLAGER) && event.getEntityType().equals(EntityType.VILLAGER)
&& villagerCache.getOrAdd((Villager) event.getEntity()).isOptimized() && villagerCache.getOrAdd((Villager) event.getEntity()).isOptimized()
) { ) {

View File

@ -46,7 +46,7 @@ public class PreventOptimizedTargeting implements VillagerOptimizerModule, Liste
Entity target = event.getTarget(); Entity target = event.getTarget();
if ( if (
target != null target != null
&& target.getType().equals(EntityType.VILLAGER) && target.getType() == EntityType.VILLAGER
&& villagerCache.getOrAdd((Villager) target).isOptimized() && villagerCache.getOrAdd((Villager) target).isOptimized()
) { ) {
event.setTarget(null); event.setTarget(null);
@ -59,7 +59,7 @@ public class PreventOptimizedTargeting implements VillagerOptimizerModule, Liste
Entity target = event.getTargetEntity(); Entity target = event.getTargetEntity();
if ( if (
target != null target != null
&& target.getType().equals(EntityType.VILLAGER) && target.getType() == EntityType.VILLAGER
&& villagerCache.getOrAdd((Villager) target).isOptimized() && villagerCache.getOrAdd((Villager) target).isOptimized()
) { ) {
event.setCancelled(true); event.setCancelled(true);
@ -69,7 +69,7 @@ public class PreventOptimizedTargeting implements VillagerOptimizerModule, Liste
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onEntityAttackVillager(EntityDamageByEntityEvent event) { private void onEntityAttackVillager(EntityDamageByEntityEvent event) {
if ( if (
event.getEntityType().equals(EntityType.VILLAGER) event.getEntityType() == EntityType.VILLAGER
&& event.getDamager() instanceof Mob attacker && event.getDamager() instanceof Mob attacker
&& villagerCache.getOrAdd((Villager) event.getEntity()).isOptimized() && villagerCache.getOrAdd((Villager) event.getEntity()).isOptimized()
) { ) {

View File

@ -3,7 +3,7 @@ package me.xginko.villageroptimizer.modules.gameplay;
import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.VillagerCache; import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.enums.Permissions; import me.xginko.villageroptimizer.enums.permissions.Bypass;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.entity.Villager; import org.bukkit.entity.Villager;
@ -24,7 +24,7 @@ public class PreventUnoptimizedTrading implements VillagerOptimizerModule, Liste
shouldEnable(); shouldEnable();
this.villagerCache = VillagerOptimizer.getCache(); this.villagerCache = VillagerOptimizer.getCache();
Config config = VillagerOptimizer.getConfiguration(); Config config = VillagerOptimizer.getConfiguration();
config.addComment("gameplay.prevent-trading-with-unoptimized.enable", """ config.master().addComment("gameplay.prevent-trading-with-unoptimized.enable", """
Will prevent players from selecting and using trades of unoptimized villagers.\s Will prevent players from selecting and using trades of unoptimized villagers.\s
Use this if you have a lot of villagers and therefore want to force your players to optimize them.\s Use this if you have a lot of villagers and therefore want to force your players to optimize them.\s
Inventories can still be opened so players can move villagers around."""); Inventories can still be opened so players can move villagers around.""");
@ -51,7 +51,7 @@ public class PreventUnoptimizedTrading implements VillagerOptimizerModule, Liste
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
private void onTradeOpen(TradeSelectEvent event) { private void onTradeOpen(TradeSelectEvent event) {
Player player = (Player) event.getWhoClicked(); Player player = (Player) event.getWhoClicked();
if (player.hasPermission(Permissions.Bypass.TRADE_PREVENTION.get())) return; if (player.hasPermission(Bypass.TRADE_PREVENTION.get())) return;
if ( if (
event.getInventory().getType().equals(InventoryType.MERCHANT) event.getInventory().getType().equals(InventoryType.MERCHANT)
&& event.getInventory().getHolder() instanceof Villager villager && event.getInventory().getHolder() instanceof Villager villager
@ -66,7 +66,7 @@ public class PreventUnoptimizedTrading implements VillagerOptimizerModule, Liste
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
private void onInventoryClick(InventoryClickEvent event) { private void onInventoryClick(InventoryClickEvent event) {
Player player = (Player) event.getWhoClicked(); Player player = (Player) event.getWhoClicked();
if (player.hasPermission(Permissions.Bypass.TRADE_PREVENTION.get())) return; if (player.hasPermission(Bypass.TRADE_PREVENTION.get())) return;
if ( if (
event.getInventory().getType().equals(InventoryType.MERCHANT) event.getInventory().getType().equals(InventoryType.MERCHANT)
&& event.getInventory().getHolder() instanceof Villager villager && event.getInventory().getHolder() instanceof Villager villager

View File

@ -1,5 +1,6 @@
package me.xginko.villageroptimizer.modules.gameplay; package me.xginko.villageroptimizer.modules.gameplay;
import com.tcoded.folialib.impl.ServerImplementation;
import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.WrappedVillager; import me.xginko.villageroptimizer.WrappedVillager;
import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.config.Config;
@ -16,18 +17,17 @@ import org.bukkit.event.Listener;
public class RenameOptimizedVillagers implements VillagerOptimizerModule, Listener { public class RenameOptimizedVillagers implements VillagerOptimizerModule, Listener {
private final VillagerOptimizer plugin; private final ServerImplementation scheduler;
private final Component optimized_name; private final Component optimized_name;
private final boolean overwrite_previous_name; private final boolean overwrite_previous_name;
public RenameOptimizedVillagers() { public RenameOptimizedVillagers() {
shouldEnable(); shouldEnable();
this.plugin = VillagerOptimizer.getInstance(); this.scheduler = VillagerOptimizer.getScheduler();
Config config = VillagerOptimizer.getConfiguration(); Config config = VillagerOptimizer.getConfiguration();
config.addComment("gameplay.rename-optimized-villagers.enable", """ config.master().addComment("gameplay.rename-optimized-villagers.enable", """
Will change a villager's name to the name configured below when they are optimized.\s Will change a villager's name to the name configured below when they are optimized.\s
These names will be removed when unoptimized again if they were not changed in the meantime. These names will be removed when unoptimized again if they were not changed in the meantime.""");
""");
this.optimized_name = MiniMessage.miniMessage().deserialize(config.getString("gameplay.rename-optimized-villagers.optimized-name", "<green>Optimized", this.optimized_name = MiniMessage.miniMessage().deserialize(config.getString("gameplay.rename-optimized-villagers.optimized-name", "<green>Optimized",
"The name that will be used to mark optimized villagers. Uses MiniMessage format.")); "The name that will be used to mark optimized villagers. Uses MiniMessage format."));
this.overwrite_previous_name = config.getBoolean("gameplay.rename-optimized-villagers.overwrite-existing-name", false, this.overwrite_previous_name = config.getBoolean("gameplay.rename-optimized-villagers.overwrite-existing-name", false,
@ -36,6 +36,7 @@ public class RenameOptimizedVillagers implements VillagerOptimizerModule, Listen
@Override @Override
public void enable() { public void enable() {
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
plugin.getServer().getPluginManager().registerEvents(this, plugin); plugin.getServer().getPluginManager().registerEvents(this, plugin);
} }
@ -54,12 +55,12 @@ public class RenameOptimizedVillagers implements VillagerOptimizerModule, Listen
WrappedVillager wVillager = event.getWrappedVillager(); WrappedVillager wVillager = event.getWrappedVillager();
Villager villager = wVillager.villager(); Villager villager = wVillager.villager();
villager.getScheduler().runDelayed(plugin, nameOptimized -> {
if (overwrite_previous_name || villager.customName() == null) { if (overwrite_previous_name || villager.customName() == null) {
scheduler.runAtEntityLater(villager, () -> {
villager.customName(optimized_name); villager.customName(optimized_name);
wVillager.memorizeName(optimized_name); wVillager.memorizeName(optimized_name);
}, 10L);
} }
}, null, 10L);
} }
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
@ -67,13 +68,13 @@ public class RenameOptimizedVillagers implements VillagerOptimizerModule, Listen
WrappedVillager wVillager = event.getWrappedVillager(); WrappedVillager wVillager = event.getWrappedVillager();
Villager villager = wVillager.villager(); Villager villager = wVillager.villager();
villager.getScheduler().runDelayed(plugin, unNameOptimized -> { scheduler.runAtEntityLater(villager, () -> {
final Component currentName = villager.customName(); final Component currentName = villager.customName();
final Component memorizedName = wVillager.getMemorizedName(); final Component memorizedName = wVillager.getMemorizedName();
if (currentName != null && currentName.equals(memorizedName)) if (currentName != null && currentName.equals(memorizedName))
villager.customName(null); villager.customName(null);
if (memorizedName != null) if (memorizedName != null)
wVillager.forgetName(); wVillager.forgetName();
}, null, 10L); }, 10L);
} }
} }

View File

@ -3,7 +3,7 @@ package me.xginko.villageroptimizer.modules.gameplay;
import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.VillagerCache; import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.enums.Permissions; import me.xginko.villageroptimizer.enums.permissions.Bypass;
import me.xginko.villageroptimizer.WrappedVillager; import me.xginko.villageroptimizer.WrappedVillager;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
import me.xginko.villageroptimizer.utils.CommonUtil; import me.xginko.villageroptimizer.utils.CommonUtil;
@ -27,7 +27,7 @@ public class RestockOptimizedTrades implements VillagerOptimizerModule, Listener
shouldEnable(); shouldEnable();
this.villagerCache = VillagerOptimizer.getCache(); this.villagerCache = VillagerOptimizer.getCache();
Config config = VillagerOptimizer.getConfiguration(); Config config = VillagerOptimizer.getConfiguration();
config.addComment("gameplay.restock-optimized-trades", """ config.master().addComment("gameplay.restock-optimized-trades", """
This is for automatic restocking of trades for optimized villagers. Optimized Villagers\s This is for automatic restocking of trades for optimized villagers. Optimized Villagers\s
don't have enough AI to restock their trades naturally, so this is here as a workaround."""); don't have enough AI to restock their trades naturally, so this is here as a workaround.""");
this.restock_delay_millis = config.getInt("gameplay.restock-optimized-trades.delay-in-ticks", 1000, this.restock_delay_millis = config.getInt("gameplay.restock-optimized-trades.delay-in-ticks", 1000,
@ -61,7 +61,7 @@ public class RestockOptimizedTrades implements VillagerOptimizerModule, Listener
if (!wVillager.isOptimized()) return; if (!wVillager.isOptimized()) return;
Player player = event.getPlayer(); Player player = event.getPlayer();
final boolean player_bypassing = player.hasPermission(Permissions.Bypass.RESTOCK_COOLDOWN.get()); final boolean player_bypassing = player.hasPermission(Bypass.RESTOCK_COOLDOWN.get());
if (wVillager.canRestock(restock_delay_millis) || player_bypassing) { if (wVillager.canRestock(restock_delay_millis) || player_bypassing) {
wVillager.restock(); wVillager.restock();

View File

@ -4,8 +4,9 @@ import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.WrappedVillager; import me.xginko.villageroptimizer.WrappedVillager;
import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.enums.permissions.Bypass;
import me.xginko.villageroptimizer.enums.OptimizationType; import me.xginko.villageroptimizer.enums.OptimizationType;
import me.xginko.villageroptimizer.enums.Permissions; import me.xginko.villageroptimizer.enums.permissions.Optimize;
import me.xginko.villageroptimizer.events.VillagerOptimizeEvent; import me.xginko.villageroptimizer.events.VillagerOptimizeEvent;
import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent; import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
@ -28,12 +29,14 @@ import org.bukkit.event.block.BlockPlaceEvent;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public class OptimizeByBlock implements VillagerOptimizerModule, Listener { public class OptimizeByBlock implements VillagerOptimizerModule, Listener {
private final VillagerCache villagerCache; private final VillagerCache villagerCache;
private final HashSet<Material> blocks_that_disable = new HashSet<>(4); private final Set<Material> blocks_that_disable = new HashSet<>();
private final long cooldown; private final long cooldown_millis;
private final double search_radius; private final double search_radius;
private final boolean only_while_sneaking, notify_player, log_enabled; private final boolean only_while_sneaking, notify_player, log_enabled;
@ -41,7 +44,7 @@ public class OptimizeByBlock implements VillagerOptimizerModule, Listener {
shouldEnable(); shouldEnable();
this.villagerCache = VillagerOptimizer.getCache(); this.villagerCache = VillagerOptimizer.getCache();
Config config = VillagerOptimizer.getConfiguration(); Config config = VillagerOptimizer.getConfiguration();
config.addComment("optimization-methods.block-optimization.enable", """ config.master().addComment("optimization-methods.block-optimization.enable", """
When enabled, the closest villager standing near a configured block being placed will be optimized.\s When enabled, the closest villager standing near a configured block being placed will be optimized.\s
If a configured block is broken nearby, the closest villager will become unoptimized again."""); If a configured block is broken nearby, the closest villager will become unoptimized again.""");
config.getList("optimization-methods.block-optimization.materials", List.of( config.getList("optimization-methods.block-optimization.materials", List.of(
@ -55,9 +58,10 @@ public class OptimizeByBlock implements VillagerOptimizerModule, Listener {
LogUtil.materialNotRecognized("block-optimization", configuredMaterial); LogUtil.materialNotRecognized("block-optimization", configuredMaterial);
} }
}); });
this.cooldown = config.getInt("optimization-methods.block-optimization.optimize-cooldown-seconds", 600, """ this.cooldown_millis = TimeUnit.SECONDS.toMillis(
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 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; Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior."""));
this.search_radius = config.getDouble("optimization-methods.block-optimization.search-radius-in-blocks", 2.0, """ 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 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; The closest unoptimized villager to the player will be optimized.""") / 2;
@ -89,7 +93,7 @@ public class OptimizeByBlock implements VillagerOptimizerModule, Listener {
Block placed = event.getBlock(); Block placed = event.getBlock();
if (!blocks_that_disable.contains(placed.getType())) return; if (!blocks_that_disable.contains(placed.getType())) return;
Player player = event.getPlayer(); Player player = event.getPlayer();
if (!player.hasPermission(Permissions.Optimize.BLOCK.get())) return; if (!player.hasPermission(Optimize.BLOCK.get())) return;
if (only_while_sneaking && !player.isSneaking()) return; if (only_while_sneaking && !player.isSneaking()) return;
final Location blockLoc = placed.getLocation(); final Location blockLoc = placed.getLocation();
@ -105,7 +109,7 @@ public class OptimizeByBlock implements VillagerOptimizerModule, Listener {
WrappedVillager wVillager = villagerCache.getOrAdd(villager); WrappedVillager wVillager = villagerCache.getOrAdd(villager);
final double distance = entity.getLocation().distance(blockLoc); final double distance = entity.getLocation().distance(blockLoc);
if (distance < closestDistance && wVillager.canOptimize(cooldown)) { if (distance < closestDistance && wVillager.canOptimize(cooldown_millis)) {
closestOptimizableVillager = wVillager; closestOptimizableVillager = wVillager;
closestDistance = distance; closestDistance = distance;
} }
@ -113,7 +117,7 @@ public class OptimizeByBlock implements VillagerOptimizerModule, Listener {
if (closestOptimizableVillager == null) return; if (closestOptimizableVillager == null) return;
if (closestOptimizableVillager.canOptimize(cooldown) || player.hasPermission(Permissions.Bypass.BLOCK_COOLDOWN.get())) { if (closestOptimizableVillager.canOptimize(cooldown_millis) || player.hasPermission(Bypass.BLOCK_COOLDOWN.get())) {
VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(closestOptimizableVillager, OptimizationType.BLOCK, player, event.isAsynchronous()); VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(closestOptimizableVillager, OptimizationType.BLOCK, player, event.isAsynchronous());
if (!optimizeEvent.callEvent()) return; if (!optimizeEvent.callEvent()) return;
@ -137,11 +141,11 @@ public class OptimizeByBlock implements VillagerOptimizerModule, Listener {
if (log_enabled) if (log_enabled)
VillagerOptimizer.getLog().info("Villager was optimized by block at "+closestOptimizableVillager.villager().getLocation()); VillagerOptimizer.getLog().info("Villager was optimized by block at "+closestOptimizableVillager.villager().getLocation());
} else { } else {
closestOptimizableVillager.villager().shakeHead(); CommonUtil.shakeHead(closestOptimizableVillager.villager());
if (notify_player) { if (notify_player) {
final TextReplacementConfig timeLeft = TextReplacementConfig.builder() final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
.matchLiteral("%time%") .matchLiteral("%time%")
.replacement(CommonUtil.formatTime(closestOptimizableVillager.getOptimizeCooldownMillis(cooldown))) .replacement(CommonUtil.formatTime(closestOptimizableVillager.getOptimizeCooldownMillis(cooldown_millis)))
.build(); .build();
VillagerOptimizer.getLang(player.locale()).block_on_optimize_cooldown.forEach(line -> player.sendMessage(line.replaceText(timeLeft))); VillagerOptimizer.getLang(player.locale()).block_on_optimize_cooldown.forEach(line -> player.sendMessage(line.replaceText(timeLeft)));
} }
@ -153,7 +157,7 @@ public class OptimizeByBlock implements VillagerOptimizerModule, Listener {
Block broken = event.getBlock(); Block broken = event.getBlock();
if (!blocks_that_disable.contains(broken.getType())) return; if (!blocks_that_disable.contains(broken.getType())) return;
Player player = event.getPlayer(); Player player = event.getPlayer();
if (!player.hasPermission(Permissions.Optimize.BLOCK.get())) return; if (!player.hasPermission(Optimize.BLOCK.get())) return;
if (only_while_sneaking && !player.isSneaking()) return; if (only_while_sneaking && !player.isSneaking()) return;
final Location blockLoc = broken.getLocation(); final Location blockLoc = broken.getLocation();

View File

@ -4,8 +4,9 @@ import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.WrappedVillager; import me.xginko.villageroptimizer.WrappedVillager;
import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.enums.permissions.Bypass;
import me.xginko.villageroptimizer.enums.OptimizationType; import me.xginko.villageroptimizer.enums.OptimizationType;
import me.xginko.villageroptimizer.enums.Permissions; import me.xginko.villageroptimizer.enums.permissions.Optimize;
import me.xginko.villageroptimizer.events.VillagerOptimizeEvent; import me.xginko.villageroptimizer.events.VillagerOptimizeEvent;
import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent; import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
@ -27,11 +28,13 @@ import org.bukkit.inventory.meta.ItemMeta;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class OptimizeByNametag implements VillagerOptimizerModule, Listener { public class OptimizeByNametag implements VillagerOptimizerModule, Listener {
private final VillagerCache villagerCache; private final VillagerCache villagerCache;
private final HashSet<String> nametags = new HashSet<>(4); private final Set<String> nametags;
private final long cooldown; private final long cooldown;
private final boolean consume_nametag, notify_player, log_enabled; private final boolean consume_nametag, notify_player, log_enabled;
@ -39,11 +42,12 @@ public class OptimizeByNametag implements VillagerOptimizerModule, Listener {
shouldEnable(); shouldEnable();
this.villagerCache = VillagerOptimizer.getCache(); this.villagerCache = VillagerOptimizer.getCache();
Config config = VillagerOptimizer.getConfiguration(); Config config = VillagerOptimizer.getConfiguration();
config.addComment("optimization-methods.nametag-optimization.enable", """ config.master().addComment("optimization-methods.nametag-optimization.enable", """
Enable optimization by naming villagers to one of the names configured below.\s Enable optimization by naming villagers to one of the names configured below.\s
Nametag optimized villagers will be unoptimized again when they are renamed to something else."""); Nametag optimized villagers will be unoptimized again when they are renamed to something else.""");
this.nametags.addAll(config.getList("optimization-methods.nametag-optimization.names", List.of("Optimize", "DisableAI"), this.nametags = config.getList("optimization-methods.nametag-optimization.names", List.of("Optimize", "DisableAI"),
"Names are case insensitive, capital letters won't matter.").stream().map(String::toLowerCase).toList()); "Names are case insensitive, capital letters won't matter.")
.stream().map(String::toLowerCase).collect(Collectors.toCollection(HashSet::new));
this.consume_nametag = config.getBoolean("optimization-methods.nametag-optimization.nametags-get-consumed", true, this.consume_nametag = config.getBoolean("optimization-methods.nametag-optimization.nametags-get-consumed", true,
"Enable or disable consumption of the used nametag item."); "Enable or disable consumption of the used nametag item.");
this.cooldown = config.getInt("optimization-methods.nametag-optimization.optimize-cooldown-seconds", 600, """ this.cooldown = config.getInt("optimization-methods.nametag-optimization.optimize-cooldown-seconds", 600, """
@ -74,7 +78,7 @@ public class OptimizeByNametag implements VillagerOptimizerModule, Listener {
private void onPlayerInteractEntity(PlayerInteractEntityEvent event) { private void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
if (!event.getRightClicked().getType().equals(EntityType.VILLAGER)) return; if (!event.getRightClicked().getType().equals(EntityType.VILLAGER)) return;
Player player = event.getPlayer(); Player player = event.getPlayer();
if (!player.hasPermission(Permissions.Optimize.NAMETAG.get())) return; if (!player.hasPermission(Optimize.NAMETAG.get())) return;
ItemStack usedItem = player.getInventory().getItem(event.getHand()); ItemStack usedItem = player.getInventory().getItem(event.getHand());
if (!usedItem.getType().equals(Material.NAME_TAG)) return; if (!usedItem.getType().equals(Material.NAME_TAG)) return;
@ -89,7 +93,7 @@ public class OptimizeByNametag implements VillagerOptimizerModule, Listener {
WrappedVillager wVillager = villagerCache.getOrAdd(villager); WrappedVillager wVillager = villagerCache.getOrAdd(villager);
if (nametags.contains(name.toLowerCase())) { if (nametags.contains(name.toLowerCase())) {
if (wVillager.canOptimize(cooldown) || player.hasPermission(Permissions.Bypass.NAMETAG_COOLDOWN.get())) { if (wVillager.canOptimize(cooldown) || player.hasPermission(Bypass.NAMETAG_COOLDOWN.get())) {
VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(wVillager, OptimizationType.NAMETAG, player, event.isAsynchronous()); VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(wVillager, OptimizationType.NAMETAG, player, event.isAsynchronous());
if (!optimizeEvent.callEvent()) return; if (!optimizeEvent.callEvent()) return;
@ -107,7 +111,7 @@ public class OptimizeByNametag implements VillagerOptimizerModule, Listener {
VillagerOptimizer.getLog().info(player.getName() + " optimized a villager using nametag: '" + name + "'"); VillagerOptimizer.getLog().info(player.getName() + " optimized a villager using nametag: '" + name + "'");
} else { } else {
event.setCancelled(true); event.setCancelled(true);
villager.shakeHead(); CommonUtil.shakeHead(villager);
if (notify_player) { if (notify_player) {
final TextReplacementConfig timeLeft = TextReplacementConfig.builder() final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
.matchLiteral("%time%") .matchLiteral("%time%")

View File

@ -4,8 +4,9 @@ import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.WrappedVillager; import me.xginko.villageroptimizer.WrappedVillager;
import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.enums.permissions.Bypass;
import me.xginko.villageroptimizer.enums.OptimizationType; import me.xginko.villageroptimizer.enums.OptimizationType;
import me.xginko.villageroptimizer.enums.Permissions; import me.xginko.villageroptimizer.enums.permissions.Optimize;
import me.xginko.villageroptimizer.events.VillagerOptimizeEvent; import me.xginko.villageroptimizer.events.VillagerOptimizeEvent;
import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent; import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
@ -25,10 +26,12 @@ import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.block.BlockPlaceEvent;
import java.util.concurrent.TimeUnit;
public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener { public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener {
private final VillagerCache villagerCache; private final VillagerCache villagerCache;
private final long cooldown; private final long cooldown_millis;
private final double search_radius; private final double search_radius;
private final boolean only_while_sneaking, log_enabled, notify_player; private final boolean only_while_sneaking, log_enabled, notify_player;
@ -36,15 +39,16 @@ public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener
shouldEnable(); shouldEnable();
this.villagerCache = VillagerOptimizer.getCache(); this.villagerCache = VillagerOptimizer.getCache();
Config config = VillagerOptimizer.getConfiguration(); Config config = VillagerOptimizer.getConfiguration();
config.addComment("optimization-methods.workstation-optimization.enable", """ config.master().addComment("optimization-methods.workstation-optimization.enable", """
When enabled, the closest villager near a matching workstation being placed will be optimized.\s When enabled, the closest villager near a matching workstation being placed will be optimized.\s
If a nearby matching workstation is broken, the villager will become unoptimized again."""); If a nearby matching workstation is broken, the villager will become unoptimized again.""");
this.search_radius = config.getDouble("optimization-methods.workstation-optimization.search-radius-in-blocks", 2.0, """ this.search_radius = config.getDouble("optimization-methods.workstation-optimization.search-radius-in-blocks", 2.0, """
The radius in blocks a villager can be away from the player when he places a workstation.\s The radius in blocks a villager can be away from the player when he places a workstation.\s
The closest unoptimized villager to the player will be optimized.""") / 2; The closest unoptimized villager to the player will be optimized.""") / 2;
this.cooldown = config.getInt("optimization-methods.workstation-optimization.optimize-cooldown-seconds", 600, """ this.cooldown_millis = TimeUnit.SECONDS.toMillis(
config.getInt("optimization-methods.workstation-optimization.optimize-cooldown-seconds", 600, """
Cooldown in seconds until a villager can be optimized again using a workstation.\s Cooldown in seconds until a villager can be optimized again using a workstation.\s
Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.""") * 1000L; Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior."""));
this.only_while_sneaking = config.getBoolean("optimization-methods.workstation-optimization.only-when-sneaking", true, this.only_while_sneaking = config.getBoolean("optimization-methods.workstation-optimization.only-when-sneaking", true,
"Only optimize/unoptimize by workstation when player is sneaking during place or break"); "Only optimize/unoptimize by workstation when player is sneaking during place or break");
this.notify_player = config.getBoolean("optimization-methods.workstation-optimization.notify-player", true, this.notify_player = config.getBoolean("optimization-methods.workstation-optimization.notify-player", true,
@ -74,7 +78,7 @@ public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener
Villager.Profession workstationProfession = getWorkstationProfession(placed.getType()); Villager.Profession workstationProfession = getWorkstationProfession(placed.getType());
if (workstationProfession.equals(Villager.Profession.NONE)) return; if (workstationProfession.equals(Villager.Profession.NONE)) return;
Player player = event.getPlayer(); Player player = event.getPlayer();
if (!player.hasPermission(Permissions.Optimize.WORKSTATION.get())) return; if (!player.hasPermission(Optimize.WORKSTATION.get())) return;
if (only_while_sneaking && !player.isSneaking()) return; if (only_while_sneaking && !player.isSneaking()) return;
final Location workstationLoc = placed.getLocation(); final Location workstationLoc = placed.getLocation();
@ -89,7 +93,7 @@ public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener
WrappedVillager wVillager = villagerCache.getOrAdd(villager); WrappedVillager wVillager = villagerCache.getOrAdd(villager);
final double distance = entity.getLocation().distance(workstationLoc); final double distance = entity.getLocation().distance(workstationLoc);
if (distance < closestDistance && wVillager.canOptimize(cooldown)) { if (distance < closestDistance && wVillager.canOptimize(cooldown_millis)) {
closestOptimizableVillager = wVillager; closestOptimizableVillager = wVillager;
closestDistance = distance; closestDistance = distance;
} }
@ -97,7 +101,7 @@ public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener
if (closestOptimizableVillager == null) return; if (closestOptimizableVillager == null) return;
if (closestOptimizableVillager.canOptimize(cooldown) || player.hasPermission(Permissions.Bypass.WORKSTATION_COOLDOWN.get())) { if (closestOptimizableVillager.canOptimize(cooldown_millis) || player.hasPermission(Bypass.WORKSTATION_COOLDOWN.get())) {
VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(closestOptimizableVillager, OptimizationType.WORKSTATION, player, event.isAsynchronous()); VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(closestOptimizableVillager, OptimizationType.WORKSTATION, player, event.isAsynchronous());
if (!optimizeEvent.callEvent()) return; if (!optimizeEvent.callEvent()) return;
@ -121,11 +125,11 @@ public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener
if (log_enabled) if (log_enabled)
VillagerOptimizer.getLog().info(player.getName() + " optimized a villager using workstation: '" + placed.getType().toString().toLowerCase() + "'"); VillagerOptimizer.getLog().info(player.getName() + " optimized a villager using workstation: '" + placed.getType().toString().toLowerCase() + "'");
} else { } else {
closestOptimizableVillager.villager().shakeHead(); CommonUtil.shakeHead(closestOptimizableVillager.villager());
if (notify_player) { if (notify_player) {
final TextReplacementConfig timeLeft = TextReplacementConfig.builder() final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
.matchLiteral("%time%") .matchLiteral("%time%")
.replacement(CommonUtil.formatTime(closestOptimizableVillager.getOptimizeCooldownMillis(cooldown))) .replacement(CommonUtil.formatTime(closestOptimizableVillager.getOptimizeCooldownMillis(cooldown_millis)))
.build(); .build();
VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> player.sendMessage(line VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> player.sendMessage(line
.replaceText(timeLeft) .replaceText(timeLeft)
@ -140,7 +144,7 @@ public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener
Villager.Profession workstationProfession = getWorkstationProfession(broken.getType()); Villager.Profession workstationProfession = getWorkstationProfession(broken.getType());
if (workstationProfession.equals(Villager.Profession.NONE)) return; if (workstationProfession.equals(Villager.Profession.NONE)) return;
Player player = event.getPlayer(); Player player = event.getPlayer();
if (!player.hasPermission(Permissions.Optimize.WORKSTATION.get())) return; if (!player.hasPermission(Optimize.WORKSTATION.get())) return;
if (only_while_sneaking && !player.isSneaking()) return; if (only_while_sneaking && !player.isSneaking()) return;
final Location workstationLoc = broken.getLocation(); final Location workstationLoc = broken.getLocation();
@ -155,7 +159,7 @@ public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener
WrappedVillager wVillager = villagerCache.getOrAdd(villager); WrappedVillager wVillager = villagerCache.getOrAdd(villager);
final double distance = entity.getLocation().distance(workstationLoc); final double distance = entity.getLocation().distance(workstationLoc);
if (distance < closestDistance && wVillager.canOptimize(cooldown)) { if (distance < closestDistance && wVillager.canOptimize(cooldown_millis)) {
closestOptimizedVillager = wVillager; closestOptimizedVillager = wVillager;
closestDistance = distance; closestDistance = distance;
} }

View File

@ -1,5 +1,7 @@
package me.xginko.villageroptimizer.utils; package me.xginko.villageroptimizer.utils;
import org.bukkit.Chunk;
import org.bukkit.entity.Villager;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.time.Duration; import java.time.Duration;
@ -21,4 +23,18 @@ public class CommonUtil {
return format("%02ds", seconds); return format("%02ds", seconds);
} }
} }
public static boolean isEntitiesLoaded(@NotNull Chunk chunk) {
try {
return chunk.isEntitiesLoaded();
} catch (NoSuchMethodError e) {
return chunk.isLoaded();
}
}
public static void shakeHead(@NotNull Villager villager) {
try {
villager.shakeHead();
} catch (NoSuchMethodError ignored) {}
}
} }

View File

@ -0,0 +1,28 @@
name: VillagerOptimizer
version: '${project.version}'
main: me.xginko.villageroptimizer.VillagerOptimizer
authors: [ xGinko ]
description: ${project.description}
website: ${project.url}
api-version: '1.16'
folia-supported: true
commands:
villageroptimizer:
usage: /villageroptimizer [ reload, version, disable ]
description: VillagerOptimizer admin commands
aliases:
- voptimizer
- vo
optimizevillagers:
usage: /optimizevillagers <blockradius>
description: Optmize villagers in a radius around you
aliases:
- optvils
- noai
unoptimizevillagers:
usage: /unoptimizevillagers <blockradius>
description: Unoptmize villagers in a radius around you
aliases:
- unoptvils
- noaiundo