commit d542755a49a452856e9357cb49c72a5e9b1e4a0e Author: xGinko Date: Mon Sep 4 23:24:57 2023 +0200 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c4be7ca --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Java +*.class +*.jar +hs_err_pid* + +# Maven +target/ + +# IntelliJ +.idea +*.iml \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a8ddf14 --- /dev/null +++ b/pom.xml @@ -0,0 +1,86 @@ + + + 4.0.0 + + me.xginko + VillagerOptimizer + 1.0.0-SNAPSHOT + jar + + VillagerOptimizer + Combat heavy lag caused by tons of loaded villagers by letting players optimize their trading halls. + + + 17 + UTF-8 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-shade-plugin + 3.4.1 + + + package + + shade + + + false + true + + + + + + + + src/main/resources + true + + + + + + + papermc-repo + https://repo.papermc.io/repository/maven-public/ + + + sonatype + https://oss.sonatype.org/content/groups/public/ + + + configmaster-repo + https://ci.pluginwiki.us/plugin/repository/everything/ + + + + + + io.papermc.paper + paper-api + 1.20.1-R0.1-SNAPSHOT + provided + + + com.github.thatsmusic99 + ConfigurationMaster-API + v2.0.0-BETA-9 + compile + + + diff --git a/src/main/java/me/xginko/villageroptimizer/VillagerOptimizer.java b/src/main/java/me/xginko/villageroptimizer/VillagerOptimizer.java new file mode 100644 index 0000000..9b74450 --- /dev/null +++ b/src/main/java/me/xginko/villageroptimizer/VillagerOptimizer.java @@ -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 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 getDefaultLanguageFiles() { + Set languageFiles = new HashSet<>(); + try (JarFile jarFile = new JarFile(this.getFile())) { + Enumeration 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; + } +} diff --git a/src/main/java/me/xginko/villageroptimizer/config/Config.java b/src/main/java/me/xginko/villageroptimizer/config/Config.java new file mode 100644 index 0000000..ebe92da --- /dev/null +++ b/src/main/java/me/xginko/villageroptimizer/config/Config.java @@ -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 names_that_disable = new HashSet<>(2); + public final HashSet blocks_that_disable = new HashSet<>(2); + public final HashSet 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 getList(String path, List def, String comment) { + config.addDefault(path, def, comment); + return config.getStringList(path); + } + + public List getList(String path, List def) { + config.addDefault(path, def); + return config.getStringList(path); + } + + public ConfigSection getConfigSection(String path, Map defaultKeyValue) { + config.makeSectionLenient(path); + config.addDefault(path, defaultKeyValue); + return config.getConfigSection(path); + } + + public ConfigSection getConfigSection(String path, Map 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); + } +} diff --git a/src/main/java/me/xginko/villageroptimizer/config/LanguageCache.java b/src/main/java/me/xginko/villageroptimizer/config/LanguageCache.java new file mode 100644 index 0000000..8a3f913 --- /dev/null +++ b/src/main/java/me/xginko/villageroptimizer/config/LanguageCache.java @@ -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", "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 getListTranslation(String path, List defaultTranslation, boolean upperCase) { + lang.addDefault(path, defaultTranslation); + return lang.getStringList(path).stream().map(line -> miniMessage.deserialize(upperCase ? line.toUpperCase() : line)).toList(); + } + + public List getListTranslation(String path, List defaultTranslation, boolean upperCase, String comment) { + lang.addDefault(path, defaultTranslation, comment); + return lang.getStringList(path).stream().map(line -> miniMessage.deserialize(upperCase ? line.toUpperCase() : line)).toList(); + } +} diff --git a/src/main/java/me/xginko/villageroptimizer/models/OptimizedVillager.java b/src/main/java/me/xginko/villageroptimizer/models/OptimizedVillager.java new file mode 100644 index 0000000..232fae8 --- /dev/null +++ b/src/main/java/me/xginko/villageroptimizer/models/OptimizedVillager.java @@ -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; + } +} diff --git a/src/main/java/me/xginko/villageroptimizer/modules/ChunkLimit.java b/src/main/java/me/xginko/villageroptimizer/modules/ChunkLimit.java new file mode 100644 index 0000000..5251905 --- /dev/null +++ b/src/main/java/me/xginko/villageroptimizer/modules/ChunkLimit.java @@ -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 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 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; + } +} diff --git a/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java b/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java new file mode 100644 index 0000000..8743654 --- /dev/null +++ b/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java @@ -0,0 +1,24 @@ +package me.xginko.villageroptimizer.modules; + +import java.util.HashSet; + +public interface VillagerOptimizerModule { + + void enable(); + void disable(); + boolean shouldEnable(); + + HashSet modules = new HashSet<>(); + + static void reloadModules() { + modules.forEach(VillagerOptimizerModule::disable); + modules.clear(); + + // Modules here + + + for (VillagerOptimizerModule module : modules) { + if (module.shouldEnable()) module.enable(); + } + } +} diff --git a/src/main/java/me/xginko/villageroptimizer/utils/CalculateLevel.java b/src/main/java/me/xginko/villageroptimizer/utils/CalculateLevel.java new file mode 100644 index 0000000..941b21c --- /dev/null +++ b/src/main/java/me/xginko/villageroptimizer/utils/CalculateLevel.java @@ -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; + } +} diff --git a/src/main/java/me/xginko/villageroptimizer/utils/LogUtils.java b/src/main/java/me/xginko/villageroptimizer/utils/LogUtils.java new file mode 100644 index 0000000..20c5c70 --- /dev/null +++ b/src/main/java/me/xginko/villageroptimizer/utils/LogUtils.java @@ -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."); + } +} \ No newline at end of file diff --git a/src/main/java/me/xginko/villageroptimizer/utils/NamespacedKeys.java b/src/main/java/me/xginko/villageroptimizer/utils/NamespacedKeys.java new file mode 100644 index 0000000..8d939e4 --- /dev/null +++ b/src/main/java/me/xginko/villageroptimizer/utils/NamespacedKeys.java @@ -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; + } +} diff --git a/src/main/java/me/xginko/villageroptimizer/utils/VillagerUtils.java b/src/main/java/me/xginko/villageroptimizer/utils/VillagerUtils.java new file mode 100644 index 0000000..26e2dd4 --- /dev/null +++ b/src/main/java/me/xginko/villageroptimizer/utils/VillagerUtils.java @@ -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 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 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 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 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()); + } +} diff --git a/src/main/resources/lang/en_us.yml b/src/main/resources/lang/en_us.yml new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..e8b0226 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,4 @@ +name: VillagerOptimizer +version: '${project.version}' +main: me.xginko.villageroptimizer.VillagerOptimizer +api-version: '1.20'