init
This commit is contained in:
commit
d542755a49
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Java
|
||||||
|
*.class
|
||||||
|
*.jar
|
||||||
|
hs_err_pid*
|
||||||
|
|
||||||
|
# Maven
|
||||||
|
target/
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
.idea
|
||||||
|
*.iml
|
86
pom.xml
Normal file
86
pom.xml
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?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>
|
||||||
|
|
||||||
|
<groupId>me.xginko</groupId>
|
||||||
|
<artifactId>VillagerOptimizer</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>VillagerOptimizer</name>
|
||||||
|
<description>Combat heavy lag caused by tons of loaded villagers by letting players optimize their trading halls.</description>
|
||||||
|
|
||||||
|
<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.4.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||||
|
<minimizeJar>true</minimizeJar>
|
||||||
|
</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>
|
||||||
|
<repository>
|
||||||
|
<id>configmaster-repo</id>
|
||||||
|
<url>https://ci.pluginwiki.us/plugin/repository/everything/</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.papermc.paper</groupId>
|
||||||
|
<artifactId>paper-api</artifactId>
|
||||||
|
<version>1.20.1-R0.1-SNAPSHOT</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.thatsmusic99</groupId>
|
||||||
|
<artifactId>ConfigurationMaster-API</artifactId>
|
||||||
|
<version>v2.0.0-BETA-9</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
146
src/main/java/me/xginko/villageroptimizer/VillagerOptimizer.java
Normal file
146
src/main/java/me/xginko/villageroptimizer/VillagerOptimizer.java
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
package me.xginko.villageroptimizer;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.config.Config;
|
||||||
|
import me.xginko.villageroptimizer.config.LanguageCache;
|
||||||
|
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
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.JarEntry;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public final class VillagerOptimizer extends JavaPlugin {
|
||||||
|
|
||||||
|
private static VillagerOptimizer instance;
|
||||||
|
private static Logger logger;
|
||||||
|
private static Config config;
|
||||||
|
private static HashMap<String, LanguageCache> languageCacheMap;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
instance = this;
|
||||||
|
logger = getLogger();
|
||||||
|
|
||||||
|
logger.info("Loading Translations");
|
||||||
|
reloadLang();
|
||||||
|
|
||||||
|
logger.info("Loading Config");
|
||||||
|
reloadConfiguration();
|
||||||
|
|
||||||
|
logger.info("Done.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable() {
|
||||||
|
// Plugin shutdown logic
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LanguageCache getLang(String lang) {
|
||||||
|
lang = lang.replace("-", "_");
|
||||||
|
if (config.auto_lang) {
|
||||||
|
return languageCacheMap.getOrDefault(lang, languageCacheMap.get(config.default_lang.toString().toLowerCase()));
|
||||||
|
} else {
|
||||||
|
return languageCacheMap.get(config.default_lang.toString().toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LanguageCache getLang(Locale locale) {
|
||||||
|
return getLang(locale.toString().toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LanguageCache getLang(CommandSender commandSender) {
|
||||||
|
if (commandSender instanceof Player player) {
|
||||||
|
return getLang(player.locale());
|
||||||
|
} else {
|
||||||
|
return getLang(config.default_lang);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reloadPlugin() {
|
||||||
|
reloadLang();
|
||||||
|
reloadConfiguration();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reloadConfiguration() {
|
||||||
|
try {
|
||||||
|
config = new Config();
|
||||||
|
VillagerOptimizerModule.reloadModules();
|
||||||
|
config.saveConfig();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.severe("Failed to load config file! - " + e.getLocalizedMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reloadLang() {
|
||||||
|
languageCacheMap = new HashMap<>();
|
||||||
|
try {
|
||||||
|
File langDirectory = new File(getDataFolder() + "/lang");
|
||||||
|
Files.createDirectories(langDirectory.toPath());
|
||||||
|
for (String fileName : getDefaultLanguageFiles()) {
|
||||||
|
String localeString = fileName.substring(fileName.lastIndexOf('/') + 1, fileName.lastIndexOf('.'));
|
||||||
|
logger.info(String.format("Found language file for %s", localeString));
|
||||||
|
LanguageCache langCache = new LanguageCache(localeString);
|
||||||
|
languageCacheMap.put(localeString, langCache);
|
||||||
|
}
|
||||||
|
Pattern langPattern = Pattern.compile("([a-z]{1,3}_[a-z]{1,3})(\\.yml)", Pattern.CASE_INSENSITIVE);
|
||||||
|
for (File langFile : langDirectory.listFiles()) {
|
||||||
|
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
|
||||||
|
logger.info(String.format("Found language file for %s", localeString));
|
||||||
|
LanguageCache langCache = new LanguageCache(localeString);
|
||||||
|
languageCacheMap.put(localeString, langCache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
logger.severe("Error loading language files! Language files will not reload to avoid errors, make sure to correct this before restarting the server!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> getDefaultLanguageFiles() {
|
||||||
|
Set<String> languageFiles = new HashSet<>();
|
||||||
|
try (JarFile jarFile = new JarFile(this.getFile())) {
|
||||||
|
Enumeration<JarEntry> entries = jarFile.entries();
|
||||||
|
while (entries.hasMoreElements()) {
|
||||||
|
String path = entries.nextElement().getName();
|
||||||
|
if (path.startsWith("lang/") && path.endsWith(".yml")) {
|
||||||
|
languageFiles.add(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.severe("Error while getting default language file names! - " + e.getLocalizedMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return languageFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Logger getLog() {
|
||||||
|
return logger;
|
||||||
|
}
|
||||||
|
}
|
157
src/main/java/me/xginko/villageroptimizer/config/Config.java
Normal file
157
src/main/java/me/xginko/villageroptimizer/config/Config.java
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
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 me.xginko.villageroptimizer.utils.LogUtils;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class Config {
|
||||||
|
|
||||||
|
private final ConfigFile config;
|
||||||
|
|
||||||
|
public final Locale default_lang;
|
||||||
|
public final boolean auto_lang;
|
||||||
|
|
||||||
|
public final HashSet<String> names_that_disable = new HashSet<>(2);
|
||||||
|
public final HashSet<Material> blocks_that_disable = new HashSet<>(2);
|
||||||
|
public final HashSet<Material> workstations_that_disable = new HashSet<>(13);
|
||||||
|
|
||||||
|
public Config() throws Exception {
|
||||||
|
this.config = loadConfig(new File(VillagerOptimizer.getInstance().getDataFolder(), "config.yml"));
|
||||||
|
structureConfig();
|
||||||
|
|
||||||
|
// Language Settings
|
||||||
|
this.default_lang = Locale.forLanguageTag(getString("language.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("language.auto-language", true, "If set to true, will display messages based on client language");
|
||||||
|
|
||||||
|
|
||||||
|
// AI-Disabling
|
||||||
|
this.names_that_disable.addAll(getList("ai-disabling.names-that-disable", List.of("Optimize", "DisableAI")));
|
||||||
|
getList("ai-disabling.blocks-that-disable", List.of("EMERALD_BLOCK", "COBBLESTONE")).forEach(configuredMaterial -> {
|
||||||
|
try {
|
||||||
|
Material disableBlock = Material.valueOf(configuredMaterial);
|
||||||
|
this.blocks_that_disable.add(disableBlock);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
LogUtils.materialNotRecognized("blocks-that-disable", configuredMaterial);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
getList("ai-disabling.workstations-that-disable", List.of(
|
||||||
|
"COMPOSTER", "SMOKER", "BARREL", "LOOM", "BLAST_FURNACE", "BREWING_STAND", "CAULDRON",
|
||||||
|
"FLETCHING_TABLE", "CARTOGRAPHY_TABLE", "LECTERN", "SMITHING_TABLE", "STONECUTTER", "GRINDSTONE"
|
||||||
|
)).forEach(configuredMaterial -> {
|
||||||
|
try {
|
||||||
|
Material disableBlock = Material.valueOf(configuredMaterial);
|
||||||
|
this.blocks_that_disable.add(disableBlock);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
LogUtils.materialNotRecognized("workstations-that-disable", configuredMaterial);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConfigFile loadConfig(File ymlFile) throws Exception {
|
||||||
|
File parent = new File(ymlFile.getParent());
|
||||||
|
if (!parent.exists())
|
||||||
|
if (!parent.mkdir())
|
||||||
|
VillagerOptimizer.getLog().severe("Unable to create plugin config directory.");
|
||||||
|
if (!ymlFile.exists())
|
||||||
|
ymlFile.createNewFile(); // Result can be ignored because this method only returns false if the file already exists
|
||||||
|
return ConfigFile.loadConfig(ymlFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveConfig() {
|
||||||
|
try {
|
||||||
|
config.save();
|
||||||
|
} catch (Exception e) {
|
||||||
|
VillagerOptimizer.getLog().severe("Failed to save config file! - " + e.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void structureConfig() {
|
||||||
|
config.addDefault("config-version", 1.00);
|
||||||
|
createTitledSection("Language", "language");
|
||||||
|
createTitledSection("AI Disabling", "ai-disabling");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createTitledSection(String title, String path) {
|
||||||
|
config.addSection(title);
|
||||||
|
config.addDefault(path, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getBoolean(String path, boolean def, String comment) {
|
||||||
|
config.addDefault(path, def, comment);
|
||||||
|
return config.getBoolean(path, def);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getBoolean(String path, boolean def) {
|
||||||
|
config.addDefault(path, def);
|
||||||
|
return config.getBoolean(path, def);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString(String path, String def, String comment) {
|
||||||
|
config.addDefault(path, def, comment);
|
||||||
|
return config.getString(path, def);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString(String path, String def) {
|
||||||
|
config.addDefault(path, def);
|
||||||
|
return config.getString(path, def);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getDouble(String path, Double def, String comment) {
|
||||||
|
config.addDefault(path, def, comment);
|
||||||
|
return config.getDouble(path, def);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getDouble(String path, Double def) {
|
||||||
|
config.addDefault(path, def);
|
||||||
|
return config.getDouble(path, def);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInt(String path, int def, String comment) {
|
||||||
|
config.addDefault(path, def, comment);
|
||||||
|
return config.getInteger(path, def);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInt(String path, int def) {
|
||||||
|
config.addDefault(path, def);
|
||||||
|
return config.getInteger(path, def);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getList(String path, List<String> def, String comment) {
|
||||||
|
config.addDefault(path, def, comment);
|
||||||
|
return config.getStringList(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getList(String path, List<String> def) {
|
||||||
|
config.addDefault(path, def);
|
||||||
|
return config.getStringList(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigSection getConfigSection(String path, Map<String, Object> defaultKeyValue) {
|
||||||
|
config.makeSectionLenient(path);
|
||||||
|
config.addDefault(path, defaultKeyValue);
|
||||||
|
return config.getConfigSection(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigSection getConfigSection(String path, Map<String, Object> defaultKeyValue, String comment) {
|
||||||
|
config.makeSectionLenient(path);
|
||||||
|
config.addDefault(path, defaultKeyValue, comment);
|
||||||
|
return config.getConfigSection(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addComment(String path, String comment) {
|
||||||
|
config.addComment(path, comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addComments(String path, String... comments) {
|
||||||
|
config.addComments(path, comments);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
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 java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class LanguageCache {
|
||||||
|
|
||||||
|
private final ConfigFile lang;
|
||||||
|
private final MiniMessage miniMessage;
|
||||||
|
|
||||||
|
public final Component no_permission;
|
||||||
|
|
||||||
|
public LanguageCache(String lang) throws Exception {
|
||||||
|
this.lang = loadLang(new File(VillagerOptimizer.getInstance().getDataFolder() + File.separator + "lang", lang + ".yml"));
|
||||||
|
this.miniMessage = MiniMessage.miniMessage();
|
||||||
|
|
||||||
|
// No Permission
|
||||||
|
this.no_permission = getTranslation("no-permission", "<red>You don't have permission to use this command.", false);
|
||||||
|
|
||||||
|
saveLang();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConfigFile loadLang(File ymlFile) throws Exception {
|
||||||
|
File parent = new File(ymlFile.getParent());
|
||||||
|
if (!parent.exists())
|
||||||
|
if (!parent.mkdir())
|
||||||
|
VillagerOptimizer.getLog().severe("Unable to create lang directory.");
|
||||||
|
if (!ymlFile.exists())
|
||||||
|
ymlFile.createNewFile(); // Result can be ignored because this method only returns false if the file already exists
|
||||||
|
return ConfigFile.loadConfig(ymlFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveLang() {
|
||||||
|
try {
|
||||||
|
lang.save();
|
||||||
|
} catch (Exception e) {
|
||||||
|
VillagerOptimizer.getLog().severe("Failed to save language file: "+ lang.getFile().getName() +" - " + e.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Component getTranslation(String path, String defaultTranslation, boolean upperCase) {
|
||||||
|
lang.addDefault(path, defaultTranslation);
|
||||||
|
return miniMessage.deserialize(upperCase ? lang.getString(path, defaultTranslation).toUpperCase() : lang.getString(path, defaultTranslation));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Component getTranslation(String path, String defaultTranslation, boolean upperCase, String comment) {
|
||||||
|
lang.addDefault(path, defaultTranslation, comment);
|
||||||
|
return miniMessage.deserialize(upperCase ? lang.getString(path, defaultTranslation).toUpperCase() : lang.getString(path, defaultTranslation));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Component> getListTranslation(String path, List<String> defaultTranslation, boolean upperCase) {
|
||||||
|
lang.addDefault(path, defaultTranslation);
|
||||||
|
return lang.getStringList(path).stream().map(line -> miniMessage.deserialize(upperCase ? line.toUpperCase() : line)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Component> getListTranslation(String path, List<String> defaultTranslation, boolean upperCase, String comment) {
|
||||||
|
lang.addDefault(path, defaultTranslation, comment);
|
||||||
|
return lang.getStringList(path).stream().map(line -> miniMessage.deserialize(upperCase ? line.toUpperCase() : line)).toList();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package me.xginko.villageroptimizer.models;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
|
||||||
|
public class OptimizedVillager {
|
||||||
|
|
||||||
|
private final Villager villager;
|
||||||
|
|
||||||
|
|
||||||
|
public OptimizedVillager(Villager villager) {
|
||||||
|
this.villager = villager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Villager villager() {
|
||||||
|
return villager;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,119 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules;
|
||||||
|
|
||||||
|
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.config.Config;
|
||||||
|
import me.xginko.villageroptimizer.utils.LogUtils;
|
||||||
|
import org.bukkit.Chunk;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.HandlerList;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public class ChunkLimit implements VillagerOptimizerModule, Listener {
|
||||||
|
|
||||||
|
private final VillagerOptimizer plugin;
|
||||||
|
private ScheduledTask scheduledTask;
|
||||||
|
private final List<Villager.Profession> removalPriority = new ArrayList<>();
|
||||||
|
private final int maxVillagersPerChunk;
|
||||||
|
private final boolean logIsEnabled;
|
||||||
|
private final long checkPeriod;
|
||||||
|
|
||||||
|
public ChunkLimit() {
|
||||||
|
shouldEnable();
|
||||||
|
this.plugin = VillagerOptimizer.getInstance();
|
||||||
|
Config config = VillagerOptimizer.getConfiguration();
|
||||||
|
this.maxVillagersPerChunk = config.getInt("villager-chunk-limit.max-villagers-per-chunk", 25);
|
||||||
|
this.logIsEnabled = config.getBoolean("villager-chunk-limit.log-removals", false);
|
||||||
|
this.checkPeriod = config.getInt("villager-chunk-limit.check-period-in-ticks", 600, "check all chunks every x ticks.");
|
||||||
|
config.getList("villager-chunk-limit.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."
|
||||||
|
|
||||||
|
).forEach(configuredProfession -> {
|
||||||
|
try {
|
||||||
|
Villager.Profession profession = Villager.Profession.valueOf(configuredProfession);
|
||||||
|
this.removalPriority.add(profession);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
LogUtils.moduleLog(Level.WARNING, "villager-chunk-limit", "Villager profession '"+configuredProfession+"' not recognized. Make sure you're using the correct profession enums.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
this.scheduledTask = plugin.getServer().getGlobalRegionScheduler().runAtFixedRate(plugin, task -> run(), checkPeriod, checkPeriod);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnable() {
|
||||||
|
return VillagerOptimizer.getConfiguration().getBoolean("villager-chunk-limit.enable", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disable() {
|
||||||
|
HandlerList.unregisterAll(this);
|
||||||
|
if (scheduledTask != null) scheduledTask.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||||
|
private void onCreateSpawn(CreatureSpawnEvent event) {
|
||||||
|
if (event.getEntityType().equals(EntityType.VILLAGER)) {
|
||||||
|
checkVillagersInChunk(event.getEntity().getChunk());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void run() {
|
||||||
|
for (World world : plugin.getServer().getWorlds()) {
|
||||||
|
for (Chunk chunk : world.getLoadedChunks()) {
|
||||||
|
plugin.getServer().getRegionScheduler().run(plugin, world, chunk.getX(), chunk.getZ(), task -> checkVillagersInChunk(chunk));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkVillagersInChunk(Chunk chunk) {
|
||||||
|
// Create a list with all villagers in that chunk
|
||||||
|
List<Villager> villagers_in_chunk = new ArrayList<>();
|
||||||
|
for (Entity entity : chunk.getEntities()) {
|
||||||
|
if (entity.getType().equals(EntityType.VILLAGER)) {
|
||||||
|
villagers_in_chunk.add((Villager) entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there are more villagers in that chunk than allowed
|
||||||
|
int amount_over_the_limit = villagers_in_chunk.size() - maxVillagersPerChunk;
|
||||||
|
if (amount_over_the_limit <= 0) return;
|
||||||
|
|
||||||
|
// Sort villager list by profession priority
|
||||||
|
villagers_in_chunk.sort(Comparator.comparingInt(this::getProfessionPriority));
|
||||||
|
|
||||||
|
// Remove prioritized villagers that are too many
|
||||||
|
for (int i = 0; i < amount_over_the_limit; i++) {
|
||||||
|
Villager villager = villagers_in_chunk.get(i);
|
||||||
|
if (logIsEnabled) LogUtils.moduleLog(
|
||||||
|
Level.INFO,
|
||||||
|
"villager-chunk-limit",
|
||||||
|
"Removing villager of profession type '"+villager.getProfession()+"' at "+villager.getLocation()
|
||||||
|
);
|
||||||
|
villager.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getProfessionPriority(Villager villager) {
|
||||||
|
Villager.Profession profession = villager.getProfession();
|
||||||
|
return removalPriority.contains(profession) ? removalPriority.indexOf(profession) : Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
public interface VillagerOptimizerModule {
|
||||||
|
|
||||||
|
void enable();
|
||||||
|
void disable();
|
||||||
|
boolean shouldEnable();
|
||||||
|
|
||||||
|
HashSet<VillagerOptimizerModule> modules = new HashSet<>();
|
||||||
|
|
||||||
|
static void reloadModules() {
|
||||||
|
modules.forEach(VillagerOptimizerModule::disable);
|
||||||
|
modules.clear();
|
||||||
|
|
||||||
|
// Modules here
|
||||||
|
|
||||||
|
|
||||||
|
for (VillagerOptimizerModule module : modules) {
|
||||||
|
if (module.shouldEnable()) module.enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package me.xginko.villageroptimizer.utils;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
|
||||||
|
public class CalculateLevel {
|
||||||
|
public static long villagerEXP (Villager vil) {
|
||||||
|
|
||||||
|
int vilEXP = vil.getVillagerExperience();
|
||||||
|
|
||||||
|
// Villager Level depending on their XP
|
||||||
|
// source: https://minecraft.fandom.com/wiki/Trading#Mechanics
|
||||||
|
|
||||||
|
if (vilEXP >= 250) return 5;
|
||||||
|
if (vilEXP >= 150) return 4;
|
||||||
|
if (vilEXP >= 70) return 3;
|
||||||
|
if (vilEXP >= 10) return 2;
|
||||||
|
|
||||||
|
// default level is 1
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package me.xginko.villageroptimizer.utils;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public class LogUtils {
|
||||||
|
|
||||||
|
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 Spigot Material enums for your Minecraft version!");
|
||||||
|
}
|
||||||
|
|
||||||
|
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!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void enchantmentNotRecognized(String path, String enchantment) {
|
||||||
|
moduleLog(Level.WARNING, path, "Enchantment '" + enchantment + "' not recognized. Please use correct Spigot Enchantment enums for your Minecraft version!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void integerNotRecognized(String path, String element) {
|
||||||
|
moduleLog(Level.WARNING, path, "The configured amount for "+element+" is not an integer.");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package me.xginko.villageroptimizer.utils;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
|
||||||
|
public enum NamespacedKeys {
|
||||||
|
|
||||||
|
COOLDOWN(VillagerOptimizer.getKey("cooldown")),
|
||||||
|
TIME(VillagerOptimizer.getKey("time")),
|
||||||
|
LEVEL_COOLDOWN(VillagerOptimizer.getKey("level-cooldown")),
|
||||||
|
NAMETAG_DISABLED(VillagerOptimizer.getKey("nametag-disabled")),
|
||||||
|
BLOCK_DISABLED(VillagerOptimizer.getKey("block-disabled")),
|
||||||
|
WORKSTATION_DISABLED(VillagerOptimizer.getKey("workstation-disabled"));
|
||||||
|
|
||||||
|
private final NamespacedKey key;
|
||||||
|
|
||||||
|
NamespacedKeys(NamespacedKey key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NamespacedKey get() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,117 @@
|
|||||||
|
package me.xginko.villageroptimizer.utils;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
|
import org.bukkit.block.BlockFace;
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.bukkit.inventory.MerchantRecipe;
|
||||||
|
import org.bukkit.persistence.PersistentDataType;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class VillagerUtils {
|
||||||
|
|
||||||
|
public static boolean isDisabled(Villager villager) {
|
||||||
|
return hasDisabledByBlock(villager) || hasDisabledByWorkstation(villager) || hasMarker(villager);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void restockTrades(Villager villager) {
|
||||||
|
for (MerchantRecipe recipe : villager.getRecipes()) {
|
||||||
|
recipe.setUses(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean shouldDisable(Villager villager) {
|
||||||
|
// Check nametag
|
||||||
|
Component nameTag = villager.customName();
|
||||||
|
if (nameTag != null) {
|
||||||
|
if (VillagerOptimizer.getConfiguration().names_that_disable.contains(PlainTextComponentSerializer.plainText().serialize(nameTag))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check block below
|
||||||
|
if (VillagerOptimizer.getConfiguration().blocks_that_disable.contains(villager.getLocation().getBlock().getRelative(BlockFace.DOWN).getType())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Workstation
|
||||||
|
return getDisabledByWorkstation(villager).orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Helper Methods for storing and reading data inside villagers using PersistentDataContainer
|
||||||
|
*
|
||||||
|
* */
|
||||||
|
|
||||||
|
// Disabled by Block
|
||||||
|
public static void setDisabledByBlock(Villager villager, boolean state) {
|
||||||
|
villager.getPersistentDataContainer().set(NamespacedKeys.BLOCK_DISABLED.get(), PersistentDataType.BOOLEAN, state);
|
||||||
|
}
|
||||||
|
public static boolean isDisabledByBlock(Villager villager) {
|
||||||
|
if (villager.getPersistentDataContainer().has(NamespacedKeys.BLOCK_DISABLED.get(), PersistentDataType.BOOLEAN)) {
|
||||||
|
return villager.getPersistentDataContainer().get(NamespacedKeys.BLOCK_DISABLED.get(), PersistentDataType.BOOLEAN);
|
||||||
|
} else {
|
||||||
|
setDisabledByBlock(villager, false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disabled by Workstation
|
||||||
|
public static void setDisabledByWorkstation(Villager villager, Boolean state) {
|
||||||
|
villager.getPersistentDataContainer().set(NamespacedKeys.WORKSTATION_DISABLED.get(), PersistentDataType.BOOLEAN, state);
|
||||||
|
}
|
||||||
|
public static boolean hasDisabledByWorkstation(Villager villager) {
|
||||||
|
return villager.getPersistentDataContainer().has(NamespacedKeys.WORKSTATION_DISABLED.get(), PersistentDataType.BOOLEAN);
|
||||||
|
}
|
||||||
|
public static Optional<Boolean> getDisabledByWorkstation(Villager villager) {
|
||||||
|
return Optional.ofNullable(villager.getPersistentDataContainer().get(NamespacedKeys.WORKSTATION_DISABLED.get(), PersistentDataType.BOOLEAN));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cooldown
|
||||||
|
public static void setCooldown(Villager villager, long cooldown_millis) {
|
||||||
|
villager.getPersistentDataContainer().set(NamespacedKeys.COOLDOWN.get(), PersistentDataType.LONG, System.currentTimeMillis() + cooldown_millis);
|
||||||
|
}
|
||||||
|
public static boolean hasCooldown(Villager villager) {
|
||||||
|
return villager.getPersistentDataContainer().has(NamespacedKeys.COOLDOWN.get(), PersistentDataType.LONG);
|
||||||
|
}
|
||||||
|
public static Optional<Long> getCooldown(Villager villager) {
|
||||||
|
return Optional.ofNullable(villager.getPersistentDataContainer().get(NamespacedKeys.COOLDOWN.get(), PersistentDataType.LONG));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time
|
||||||
|
public static void setTime(Villager villager) {
|
||||||
|
villager.getPersistentDataContainer().set(NamespacedKeys.TIME.get(), PersistentDataType.LONG, villager.getWorld().getFullTime());
|
||||||
|
}
|
||||||
|
public static boolean hasTime(Villager villager) {
|
||||||
|
return villager.getPersistentDataContainer().has(NamespacedKeys.TIME.get(), PersistentDataType.LONG);
|
||||||
|
}
|
||||||
|
public static Optional<Long> getTime(Villager villager) {
|
||||||
|
return Optional.ofNullable(villager.getPersistentDataContainer().get(NamespacedKeys.TIME.get(), PersistentDataType.LONG));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Level Cooldown
|
||||||
|
public static void setLevelCooldown(Villager villager, long cooldown_millis) {
|
||||||
|
villager.getPersistentDataContainer().set(NamespacedKeys.LEVEL_COOLDOWN.get(), PersistentDataType.LONG, System.currentTimeMillis() + cooldown_millis);
|
||||||
|
}
|
||||||
|
public static boolean hasLevelCooldown(Villager villager, JavaPlugin plugin) {
|
||||||
|
return villager.getPersistentDataContainer().has(NamespacedKeys.LEVEL_COOLDOWN.get(), PersistentDataType.LONG);
|
||||||
|
}
|
||||||
|
public static Optional<Long> getLevelCooldown(Villager villager) {
|
||||||
|
return Optional.ofNullable(villager.getPersistentDataContainer().get(NamespacedKeys.LEVEL_COOLDOWN.get(), PersistentDataType.LONG));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marker
|
||||||
|
public static void setMarker(Villager villager) {
|
||||||
|
villager.getPersistentDataContainer().set(NamespacedKeys.NAMETAG_DISABLED.get(), PersistentDataType.BYTE, (byte)1);
|
||||||
|
}
|
||||||
|
public static boolean hasMarker(Villager villager) {
|
||||||
|
return villager.getPersistentDataContainer().has(NamespacedKeys.NAMETAG_DISABLED.get(), PersistentDataType.BYTE);
|
||||||
|
}
|
||||||
|
public static void removeMarker(Villager villager) {
|
||||||
|
villager.getPersistentDataContainer().remove(NamespacedKeys.NAMETAG_DISABLED.get());
|
||||||
|
}
|
||||||
|
}
|
0
src/main/resources/lang/en_us.yml
Normal file
0
src/main/resources/lang/en_us.yml
Normal file
4
src/main/resources/plugin.yml
Normal file
4
src/main/resources/plugin.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
name: VillagerOptimizer
|
||||||
|
version: '${project.version}'
|
||||||
|
main: me.xginko.villageroptimizer.VillagerOptimizer
|
||||||
|
api-version: '1.20'
|
Loading…
x
Reference in New Issue
Block a user