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