diff --git a/VillagerOptimizer-1.16.5/pom.xml b/VillagerOptimizer-1.16.5/pom.xml new file mode 100644 index 0000000..d778ad9 --- /dev/null +++ b/VillagerOptimizer-1.16.5/pom.xml @@ -0,0 +1,98 @@ + + + 4.0.0 + + + me.xginko.VillagerOptimizer + VillagerOptimizer + 1.0.0 + + + 1.16.5 + ${project.parent.artifactId}-${project.parent.version}--${project.artifactId} + jar + + + 16 + UTF-8 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.1 + + + package + + shade + + + + + org.bstats + me.xginko.villageroptimizer.bstats + + + false + + + + + + + + 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/ + + + + + + com.destroystokyo.paper + paper-api + 1.16.5-R0.1-SNAPSHOT + provided + + + net.kyori + adventure-text-minimessage + 4.14.0 + + + net.kyori + adventure-text-serializer-plain + 4.14.0 + compile + + + diff --git a/src/main/java/me/xginko/villageroptimizer/VillagerCache.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/VillagerCache.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/VillagerCache.java rename to VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/VillagerCache.java diff --git a/src/main/java/me/xginko/villageroptimizer/VillagerOptimizer.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/VillagerOptimizer.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/VillagerOptimizer.java rename to VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/VillagerOptimizer.java diff --git a/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/WrappedVillager.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/WrappedVillager.java new file mode 100644 index 0000000..1d65f8f --- /dev/null +++ b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/WrappedVillager.java @@ -0,0 +1,188 @@ +package me.xginko.villageroptimizer; + +import me.xginko.villageroptimizer.enums.Keys; +import me.xginko.villageroptimizer.enums.OptimizationType; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.bukkit.entity.Villager; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class WrappedVillager { + + private final @NotNull Villager villager; + private final @NotNull PersistentDataContainer dataContainer; + + WrappedVillager(@NotNull Villager villager) { + this.villager = villager; + this.dataContainer = this.villager.getPersistentDataContainer(); + } + + /** + * @return The villager inside the wrapper. + */ + public @NotNull Villager villager() { + return villager; + } + + /** + * @return The data container inside the wrapper. + */ + public @NotNull PersistentDataContainer dataContainer() { + return dataContainer; + } + + /** + * @return True if the villager is optimized by this plugin, otherwise false. + */ + public boolean isOptimized() { + return dataContainer.has(Keys.OPTIMIZATION_TYPE.key(), PersistentDataType.STRING); + } + + /** + * @param cooldown_millis The configured cooldown in millis until the next optimization is allowed to occur. + * @return True if villager can be optimized again, otherwise false. + */ + public boolean canOptimize(final long cooldown_millis) { + return getLastOptimize() + cooldown_millis <= System.currentTimeMillis(); + } + + /** + * @param type OptimizationType the villager should be set to. + */ + public void setOptimization(OptimizationType type) { + if (type.equals(OptimizationType.NONE) && isOptimized()) { + dataContainer.remove(Keys.OPTIMIZATION_TYPE.key()); + villager.setAware(true); + villager.setAI(true); + } else { + dataContainer.set(Keys.OPTIMIZATION_TYPE.key(), PersistentDataType.STRING, type.name()); + villager.setAware(false); + } + } + + /** + * @return The current OptimizationType of the villager. + */ + public @NotNull OptimizationType getOptimizationType() { + return isOptimized() ? OptimizationType.valueOf(dataContainer.get(Keys.OPTIMIZATION_TYPE.key(), PersistentDataType.STRING)) : OptimizationType.NONE; + } + + /** + * Saves the system time in millis when the villager was last optimized. + */ + public void saveOptimizeTime() { + dataContainer.set(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG, System.currentTimeMillis()); + } + + /** + * @return The system time in millis when the villager was last optimized, 0L if the villager was never optimized. + */ + public long getLastOptimize() { + return dataContainer.has(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG) ? dataContainer.get(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG) : 0L; + } + + /** + * Here for convenience so the remaining millis since the last stored optimize time + * can be easily calculated. + * This enables new configured cooldowns to instantly apply instead of them being persistent. + * + * @param cooldown_millis The configured cooldown in milliseconds you want to check against. + * @return The time left in millis until the villager can be optimized again. + */ + public long getOptimizeCooldownMillis(final long cooldown_millis) { + return dataContainer.has(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG) ? (System.currentTimeMillis() - (dataContainer.get(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG) + cooldown_millis)) : cooldown_millis; + } + + /** + * Here for convenience so the remaining millis since the last stored restock time + * can be easily calculated. + * + * @param cooldown_millis The configured cooldown in milliseconds you want to check against. + * @return True if the villager has been loaded long enough. + */ + public boolean canRestock(final long cooldown_millis) { + return getLastRestock() + cooldown_millis <= villager.getWorld().getFullTime(); + } + + /** + * Restock all trading recipes. + */ + public void restock() { + villager.getRecipes().forEach(recipe -> recipe.setUses(0)); + } + + /** + * Saves the time of the in-game world when the entity was last restocked. + */ + public void saveRestockTime() { + dataContainer.set(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG, villager.getWorld().getFullTime()); + } + + /** + * @return The time of the in-game world when the entity was last restocked. + */ + public long getLastRestock() { + return dataContainer.has(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG) ? dataContainer.get(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG) : 0L; + } + + public long getRestockCooldownMillis(final long cooldown_millis) { + return dataContainer.has(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG) ? (villager.getWorld().getFullTime() - (dataContainer.get(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG) + cooldown_millis)) : cooldown_millis; + } + + /** + * @return The level between 1-5 calculated from the villagers experience. + */ + public int calculateLevel() { + // https://minecraft.fandom.com/wiki/Trading#Mechanics + int vilEXP = villager.getVillagerExperience(); + if (vilEXP >= 250) return 5; + if (vilEXP >= 150) return 4; + if (vilEXP >= 70) return 3; + if (vilEXP >= 10) return 2; + return 1; + } + + /** + * @param cooldown_millis The configured cooldown in milliseconds you want to check against. + * @return Whether the villager can be leveled up or not with the checked milliseconds + */ + public boolean canLevelUp(final long cooldown_millis) { + return getLastLevelUpTime() + cooldown_millis <= villager.getWorld().getFullTime(); + } + + /** + * Saves the time of the in-game world when the entity was last leveled up. + */ + public void saveLastLevelUp() { + dataContainer.set(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG, villager.getWorld().getFullTime()); + } + + /** + * Here for convenience so the remaining millis since the last stored level-up time + * can be easily calculated. + * + * @return The time of the in-game world when the entity was last leveled up. + */ + public long getLastLevelUpTime() { + return dataContainer.has(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG) ? dataContainer.get(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG) : 0L; + } + + public long getLevelCooldownMillis(final long cooldown_millis) { + return dataContainer.has(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG) ? (villager.getWorld().getFullTime() - (dataContainer.get(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG) + cooldown_millis)) : cooldown_millis; + } + + public void memorizeName(final Component customName) { + dataContainer.set(Keys.LAST_OPTIMIZE_NAME.key(), PersistentDataType.STRING, MiniMessage.miniMessage().serialize(customName)); + } + + public @Nullable Component getMemorizedName() { + return dataContainer.has(Keys.LAST_OPTIMIZE_NAME.key(), PersistentDataType.STRING) ? MiniMessage.miniMessage().deserialize(dataContainer.get(Keys.LAST_OPTIMIZE_NAME.key(), PersistentDataType.STRING)) : null; + } + + public void forgetName() { + dataContainer.remove(Keys.LAST_OPTIMIZE_NAME.key()); + } +} \ No newline at end of file diff --git a/src/main/java/me/xginko/villageroptimizer/commands/SubCommand.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/commands/SubCommand.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/commands/SubCommand.java rename to VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/commands/SubCommand.java diff --git a/src/main/java/me/xginko/villageroptimizer/commands/VillagerOptimizerCommand.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/commands/VillagerOptimizerCommand.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/commands/VillagerOptimizerCommand.java rename to VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/commands/VillagerOptimizerCommand.java diff --git a/src/main/java/me/xginko/villageroptimizer/commands/optimizevillagers/OptVillagersRadius.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/commands/optimizevillagers/OptVillagersRadius.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/commands/optimizevillagers/OptVillagersRadius.java rename to VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/commands/optimizevillagers/OptVillagersRadius.java diff --git a/src/main/java/me/xginko/villageroptimizer/commands/unoptimizevillagers/UnOptVillagersRadius.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/commands/unoptimizevillagers/UnOptVillagersRadius.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/commands/unoptimizevillagers/UnOptVillagersRadius.java rename to VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/commands/unoptimizevillagers/UnOptVillagersRadius.java diff --git a/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/VillagerOptimizerCmd.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/VillagerOptimizerCmd.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/VillagerOptimizerCmd.java rename to VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/VillagerOptimizerCmd.java diff --git a/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/subcommands/DisableSubCmd.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/subcommands/DisableSubCmd.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/subcommands/DisableSubCmd.java rename to VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/subcommands/DisableSubCmd.java diff --git a/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/subcommands/ReloadSubCmd.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/subcommands/ReloadSubCmd.java new file mode 100644 index 0000000..700b736 --- /dev/null +++ b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/subcommands/ReloadSubCmd.java @@ -0,0 +1,41 @@ +package me.xginko.villageroptimizer.commands.villageroptimizer.subcommands; + +import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.commands.SubCommand; +import me.xginko.villageroptimizer.enums.Permissions; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.command.CommandSender; + +public class ReloadSubCmd extends SubCommand { + + @Override + public String getLabel() { + return "reload"; + } + + @Override + public TextComponent getDescription() { + return Component.text("Reload the plugin configuration.").color(NamedTextColor.GRAY); + } + + @Override + public TextComponent getSyntax() { + return Component.text("/villageroptimizer reload").color(VillagerOptimizer.plugin_style.color()); + } + + @Override + public void perform(CommandSender sender, String[] args) { + if (sender.hasPermission(Permissions.Commands.RELOAD.get())) { + sender.sendMessage(Component.text("Reloading VillagerOptimizer...").color(NamedTextColor.WHITE)); + VillagerOptimizer plugin = VillagerOptimizer.getInstance(); + plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> { + plugin.reloadPlugin(); + sender.sendMessage(Component.text("Reload complete.").color(NamedTextColor.GREEN)); + }); + } else { + sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission); + } + } +} \ No newline at end of file diff --git a/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/subcommands/VersionSubCmd.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/subcommands/VersionSubCmd.java new file mode 100644 index 0000000..6e1a910 --- /dev/null +++ b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/subcommands/VersionSubCmd.java @@ -0,0 +1,53 @@ +package me.xginko.villageroptimizer.commands.villageroptimizer.subcommands; + +import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.commands.SubCommand; +import me.xginko.villageroptimizer.enums.Permissions; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.PluginDescriptionFile; + +public class VersionSubCmd extends SubCommand { + + @Override + public String getLabel() { + return "version"; + } + + @Override + public TextComponent getDescription() { + return Component.text("Show the plugin version.").color(NamedTextColor.GRAY); + } + + @Override + public TextComponent getSyntax() { + return Component.text("/villageroptimizer version").color(VillagerOptimizer.plugin_style.color()); + } + + @Override + public void perform(CommandSender sender, String[] args) { + if (sender.hasPermission(Permissions.Commands.VERSION.get())) { + final PluginDescriptionFile pluginMeta = VillagerOptimizer.getInstance().getDescription(); + sender.sendMessage( + Component.newline() + .append( + Component.text(pluginMeta.getName()+" "+pluginMeta.getVersion()) + .style(VillagerOptimizer.plugin_style) + .clickEvent(ClickEvent.openUrl(pluginMeta.getWebsite())) + ) + .append(Component.text(" by ").color(NamedTextColor.GRAY)) + .append( + Component.text(pluginMeta.getAuthors().get(0)) + .color(NamedTextColor.WHITE) + .clickEvent(ClickEvent.openUrl("https://github.com/xGinko")) + ) + .append(Component.newline()) + ); + } else { + sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission); + } + } +} \ No newline at end of file diff --git a/src/main/java/me/xginko/villageroptimizer/config/Config.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/config/Config.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/config/Config.java rename to VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/config/Config.java diff --git a/src/main/java/me/xginko/villageroptimizer/config/LanguageCache.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/config/LanguageCache.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/config/LanguageCache.java rename to VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/config/LanguageCache.java diff --git a/src/main/java/me/xginko/villageroptimizer/enums/Keys.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/enums/Keys.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/enums/Keys.java rename to VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/enums/Keys.java diff --git a/src/main/java/me/xginko/villageroptimizer/enums/OptimizationType.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/enums/OptimizationType.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/enums/OptimizationType.java rename to VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/enums/OptimizationType.java diff --git a/src/main/java/me/xginko/villageroptimizer/enums/Permissions.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/enums/Permissions.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/enums/Permissions.java rename to VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/enums/Permissions.java diff --git a/src/main/java/me/xginko/villageroptimizer/events/VillagerOptimizeEvent.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/events/VillagerOptimizeEvent.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/events/VillagerOptimizeEvent.java rename to VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/events/VillagerOptimizeEvent.java diff --git a/src/main/java/me/xginko/villageroptimizer/events/VillagerUnoptimizeEvent.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/events/VillagerUnoptimizeEvent.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/events/VillagerUnoptimizeEvent.java rename to VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/events/VillagerUnoptimizeEvent.java diff --git a/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/RenameOptimizedVillagers.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/RenameOptimizedVillagers.java new file mode 100644 index 0000000..f64edd4 --- /dev/null +++ b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/RenameOptimizedVillagers.java @@ -0,0 +1,83 @@ +package me.xginko.villageroptimizer.modules; + +import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.config.Config; +import me.xginko.villageroptimizer.events.VillagerOptimizeEvent; +import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.bukkit.entity.Villager; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; + +public class RenameOptimizedVillagers implements VillagerOptimizerModule, Listener { + + private final VillagerOptimizer plugin; + private final Component optimized_name; + private final boolean overwrite_previous_name; + + protected RenameOptimizedVillagers() { + this.plugin = VillagerOptimizer.getInstance(); + Config config = VillagerOptimizer.getConfiguration(); + config.addComment("general.rename-villagers.enable", """ + Will change a villager's name to the name configured below when they are optimized.\s + These names will be removed when unoptimized again if they were not changed in the meantime. + """); + this.optimized_name = MiniMessage.miniMessage().deserialize(config.getString("general.rename-villagers.optimized-name", "Optimized", + "The name that will be used to mark optimized villagers. Uses MiniMessage format.")); + this.overwrite_previous_name = config.getBoolean("general.rename-villagers.overwrite-existing-name", false, + "If set to true, will rename even if the villager has already been named."); + } + + @Override + public void enable() { + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + @Override + public void disable() { + HandlerList.unregisterAll(this); + } + + @Override + public boolean shouldEnable() { + return VillagerOptimizer.getConfiguration().getBoolean("general.rename-villagers.enable", true); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onOptimize(VillagerOptimizeEvent event) { + WrappedVillager wVillager = event.getWrappedVillager(); + Villager villager = wVillager.villager(); + + plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> { + if (overwrite_previous_name) { + villager.customName(optimized_name); + wVillager.memorizeName(optimized_name); + } else { + final Component currentName = villager.customName(); + if (currentName == null) { + villager.customName(optimized_name); + wVillager.memorizeName(optimized_name); + } + } + }, 10L); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onUnOptimize(VillagerUnoptimizeEvent event) { + WrappedVillager wVillager = event.getWrappedVillager(); + Villager villager = wVillager.villager(); + + plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> { + final Component currentName = villager.customName(); + final Component memorizedName = wVillager.getMemorizedName(); + if (memorizedName != null) + wVillager.forgetName(); + if (currentName != null && currentName.equals(memorizedName)) + villager.customName(null); + }, 10L); + } +} \ No newline at end of file diff --git a/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/VillagerChunkLimit.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/VillagerChunkLimit.java new file mode 100644 index 0000000..079b5b8 --- /dev/null +++ b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/VillagerChunkLimit.java @@ -0,0 +1,153 @@ +package me.xginko.villageroptimizer.modules; + +import me.xginko.villageroptimizer.VillagerCache; +import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.config.Config; +import me.xginko.villageroptimizer.utils.LogUtil; +import org.bukkit.Chunk; +import org.bukkit.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 org.bukkit.event.player.PlayerInteractEntityEvent; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.logging.Level; + +public class VillagerChunkLimit implements VillagerOptimizerModule, Listener { + + private final VillagerOptimizer plugin; + private final VillagerCache villagerCache; + private final List removalPriority = new ArrayList<>(16); + private final long check_period; + private final int max_unoptimized_per_chunk, max_optimized_per_chunk; + private final boolean logIsEnabled; + + protected VillagerChunkLimit() { + shouldEnable(); + this.plugin = VillagerOptimizer.getInstance(); + this.villagerCache = VillagerOptimizer.getCache(); + Config config = VillagerOptimizer.getConfiguration(); + config.addComment("villager-chunk-limit.enable", """ + Checks chunks for too many villagers and removes excess villagers based on priority.\s + Naturally, optimized villagers will be picked last since they don't affect performance\s + as much as unoptimized villagers."""); + this.max_unoptimized_per_chunk = config.getInt("villager-chunk-limit.max-unoptimized-per-chunk", 30, + "The maximum amount of unoptimized villagers per chunk."); + this.max_optimized_per_chunk = config.getInt("villager-chunk-limit.max-optimized-per-chunk", 20, + "The maximum amount of optimized villagers per chunk."); + this.check_period = config.getInt("villager-chunk-limit.check-period-in-ticks", 600, + "Check all loaded chunks every X ticks. 1 second = 20 ticks"); + this.logIsEnabled = config.getBoolean("villager-chunk-limit.log-removals", false); + 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) { + LogUtil.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); + plugin.getServer().getScheduler().scheduleSyncRepeatingTask(plugin, () -> { + plugin.getServer().getWorlds().forEach(world -> { + for (Chunk chunk : world.getLoadedChunks()) { + this.checkVillagersInChunk(chunk); + } + }); + }, check_period, check_period); + } + + @Override + public boolean shouldEnable() { + return VillagerOptimizer.getConfiguration().getBoolean("villager-chunk-limit.enable", false); + } + + @Override + public void disable() { + HandlerList.unregisterAll(this); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onCreatureSpawn(CreatureSpawnEvent event) { + Entity spawned = event.getEntity(); + if (spawned.getType().equals(EntityType.VILLAGER)) { + checkVillagersInChunk(spawned.getChunk()); + } + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + private void onInteract(PlayerInteractEntityEvent event) { + Entity clicked = event.getRightClicked(); + if (clicked.getType().equals(EntityType.VILLAGER)) { + checkVillagersInChunk(clicked.getChunk()); + } + } + + private void checkVillagersInChunk(Chunk chunk) { + // Create lists with all optimized and unoptimzed villagers in that chunk + List unoptimized_villagers = new ArrayList<>(); + List optimized_villagers = new ArrayList<>(); + + // Collect villagers accordingly + for (Entity entity : chunk.getEntities()) { + if (entity.getType().equals(EntityType.VILLAGER)) { + Villager villager = (Villager) entity; + if (villagerCache.getOrAdd(villager).isOptimized()) { + optimized_villagers.add(villager); + } else { + unoptimized_villagers.add(villager); + } + } + } + + // Check if there are more unoptimized villagers in that chunk than allowed + final int unoptimized_vils_too_many = unoptimized_villagers.size() - max_unoptimized_per_chunk; + if (unoptimized_vils_too_many > 0) { + // Sort villagers by profession priority + unoptimized_villagers.sort(Comparator.comparingInt(this::getProfessionPriority)); + // Remove prioritized villagers that are too many + for (int i = 0; i < unoptimized_vils_too_many; i++) { + Villager villager = unoptimized_villagers.get(i); + villager.remove(); + if (logIsEnabled) LogUtil.moduleLog(Level.INFO, "villager-chunk-limit", + "Removed unoptimized villager of profession type '"+villager.getProfession().name()+"' at "+villager.getLocation()); + } + } + + // Check if there are more optimized villagers in that chunk than allowed + final int optimized_vils_too_many = optimized_villagers.size() - max_optimized_per_chunk; + if (optimized_vils_too_many > 0) { + // Sort villagers by profession priority + optimized_villagers.sort(Comparator.comparingInt(this::getProfessionPriority)); + // Remove prioritized villagers that are too many + for (int i = 0; i < optimized_vils_too_many; i++) { + Villager villager = optimized_villagers.get(i); + villager.remove(); + if (logIsEnabled) LogUtil.moduleLog(Level.INFO, "villager-chunk-limit", + "Removed optimized villager of profession type '"+villager.getProfession().name()+"' at "+villager.getLocation()); + } + } + } + + private int getProfessionPriority(Villager villager) { + final Villager.Profession profession = villager.getProfession(); + return removalPriority.contains(profession) ? removalPriority.indexOf(profession) : Integer.MAX_VALUE; + } +} \ No newline at end of file diff --git a/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java rename to VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java diff --git a/src/main/java/me/xginko/villageroptimizer/modules/extras/MakeVillagersSpawnAdult.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/extras/MakeVillagersSpawnAdult.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/modules/extras/MakeVillagersSpawnAdult.java rename to VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/extras/MakeVillagersSpawnAdult.java diff --git a/src/main/java/me/xginko/villageroptimizer/modules/extras/PreventUnoptimizedTrading.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/extras/PreventUnoptimizedTrading.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/modules/extras/PreventUnoptimizedTrading.java rename to VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/extras/PreventUnoptimizedTrading.java diff --git a/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/extras/PreventVillagerDamage.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/extras/PreventVillagerDamage.java new file mode 100644 index 0000000..19b67bb --- /dev/null +++ b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/extras/PreventVillagerDamage.java @@ -0,0 +1,88 @@ +package me.xginko.villageroptimizer.modules.extras; + +import me.xginko.villageroptimizer.VillagerCache; +import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.config.Config; +import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Mob; +import org.bukkit.entity.Villager; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByBlockEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; + +public class PreventVillagerDamage implements VillagerOptimizerModule, Listener { + + private final VillagerCache villagerCache; + private final boolean block, player, mob, other; + + public PreventVillagerDamage() { + shouldEnable(); + this.villagerCache = VillagerOptimizer.getCache(); + Config config = VillagerOptimizer.getConfiguration(); + config.addComment("gameplay.prevent-damage.enable", + "Configure what kind of damage you want to cancel for optimized villagers here."); + this.block = config.getBoolean("gameplay.prevent-damage.damagers.block", false, + "Prevents damage from blocks like lava, tnt, respawn anchors, etc."); + this.player = config.getBoolean("gameplay.prevent-damage.damagers.player", false, + "Prevents damage from getting hit by players."); + this.mob = config.getBoolean("gameplay.prevent-damage.damagers.mob", true, + "Prevents damage from hostile mobs."); + this.other = config.getBoolean("gameplay.prevent-damage.damagers.other", true, + "Prevents damage from all other entities."); + } + + @Override + public void enable() { + VillagerOptimizer plugin = VillagerOptimizer.getInstance(); + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + @Override + public void disable() { + HandlerList.unregisterAll(this); + } + + @Override + public boolean shouldEnable() { + return VillagerOptimizer.getConfiguration().getBoolean("gameplay.prevent-damage.enable", true); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onDamageByEntity(EntityDamageByEntityEvent event) { + if ( + event.getEntityType().equals(EntityType.VILLAGER) + && villagerCache.getOrAdd((Villager) event.getEntity()).isOptimized() + ) { + Entity damager = event.getDamager(); + if (damager.getType().equals(EntityType.PLAYER)) { + if (player) event.setCancelled(true); + return; + } + + if (damager instanceof Mob) { + if (mob) event.setCancelled(true); + return; + } + + if (other) { + event.setCancelled(true); + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onDamageByBlock(EntityDamageByBlockEvent event) { + if ( + block + && event.getEntityType().equals(EntityType.VILLAGER) + && villagerCache.getOrAdd((Villager) event.getEntity()).isOptimized() + ) { + event.setCancelled(true); + } + } +} \ No newline at end of file diff --git a/src/main/java/me/xginko/villageroptimizer/modules/extras/PreventVillagerTargetting.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/extras/PreventVillagerTargetting.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/modules/extras/PreventVillagerTargetting.java rename to VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/extras/PreventVillagerTargetting.java diff --git a/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/mechanics/LevelVillagers.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/mechanics/LevelVillagers.java new file mode 100644 index 0000000..37a5d62 --- /dev/null +++ b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/mechanics/LevelVillagers.java @@ -0,0 +1,90 @@ +package me.xginko.villageroptimizer.modules.mechanics; + +import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.VillagerCache; +import me.xginko.villageroptimizer.config.Config; +import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; +import me.xginko.villageroptimizer.utils.CommonUtil; +import net.kyori.adventure.text.TextReplacementConfig; +import org.bukkit.entity.Player; +import org.bukkit.entity.Villager; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +public class LevelVillagers implements VillagerOptimizerModule, Listener { + + private final VillagerOptimizer plugin; + private final VillagerCache villagerCache; + private final boolean shouldNotify; + private final long cooldown; + + public LevelVillagers() { + shouldEnable(); + this.plugin = VillagerOptimizer.getInstance(); + this.villagerCache = VillagerOptimizer.getCache(); + Config config = VillagerOptimizer.getConfiguration(); + config.addComment("gameplay.villager-leveling.enable", """ + This is needed to allow optimized villagers to level up.\s + Temporarily enables the villagers AI to allow it to level up and then disables it again."""); + this.cooldown = config.getInt("gameplay.villager-leveling.level-check-cooldown-seconds", 5, """ + Cooldown in seconds until the level of a villager will be checked and updated again.\s + Recommended to leave as is.""") * 1000L; + this.shouldNotify = config.getBoolean("gameplay.villager-leveling.notify-player", true, + "Tell players to wait when a villager is leveling up."); + } + + @Override + public void enable() { + VillagerOptimizer plugin = VillagerOptimizer.getInstance(); + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + @Override + public void disable() { + HandlerList.unregisterAll(this); + } + + @Override + public boolean shouldEnable() { + return VillagerOptimizer.getConfiguration().getBoolean("gameplay.villager-leveling.enable", true); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + private void onTradeScreenClose(InventoryCloseEvent event) { + if ( + event.getInventory().getType().equals(InventoryType.MERCHANT) + && event.getInventory().getHolder() instanceof Villager villager + ) { + WrappedVillager wVillager = villagerCache.getOrAdd(villager); + if (!wVillager.isOptimized()) return; + + if (wVillager.canLevelUp(cooldown)) { + if (wVillager.calculateLevel() > villager.getVillagerLevel()) { + villager.addPotionEffect(new PotionEffect(PotionEffectType.SLOW, 120, 120, false, false)); + villager.setAware(true); + + plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> { + villager.setAware(false); + wVillager.saveLastLevelUp(); + }, 100L); + } + } else { + if (shouldNotify) { + Player player = (Player) event.getPlayer(); + final TextReplacementConfig timeLeft = TextReplacementConfig.builder() + .matchLiteral("%time%") + .replacement(CommonUtil.formatTime(wVillager.getLevelCooldownMillis(cooldown))) + .build(); + VillagerOptimizer.getLang(player.locale()).villager_leveling_up.forEach(line -> player.sendMessage(line.replaceText(timeLeft))); + } + } + } + } +} diff --git a/src/main/java/me/xginko/villageroptimizer/modules/mechanics/RestockTrades.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/mechanics/RestockTrades.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/modules/mechanics/RestockTrades.java rename to VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/mechanics/RestockTrades.java diff --git a/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/optimizations/OptimizeByBlock.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/optimizations/OptimizeByBlock.java new file mode 100644 index 0000000..ee0d34c --- /dev/null +++ b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/optimizations/OptimizeByBlock.java @@ -0,0 +1,201 @@ +package me.xginko.villageroptimizer.modules.optimizations; + +import me.xginko.villageroptimizer.VillagerCache; +import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.config.Config; +import me.xginko.villageroptimizer.enums.OptimizationType; +import me.xginko.villageroptimizer.enums.Permissions; +import me.xginko.villageroptimizer.events.VillagerOptimizeEvent; +import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent; +import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; +import me.xginko.villageroptimizer.utils.CommonUtil; +import me.xginko.villageroptimizer.utils.LogUtil; +import net.kyori.adventure.text.TextReplacementConfig; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.entity.Villager; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; + +import java.util.HashSet; +import java.util.List; + +public class OptimizeByBlock implements VillagerOptimizerModule, Listener { + + private final VillagerCache villagerCache; + private final HashSet blocks_that_disable = new HashSet<>(4); + private final long cooldown; + private final double search_radius; + private final boolean onlyWhileSneaking, shouldNotifyPlayer, shouldLog; + + public OptimizeByBlock() { + shouldEnable(); + this.villagerCache = VillagerOptimizer.getCache(); + Config config = VillagerOptimizer.getConfiguration(); + config.addComment("optimization-methods.block-optimization.enable", """ + When enabled, the closest villager standing near a configured block being placed will be optimized.\s + If a configured block is broken nearby, the closest villager will become unoptimized again."""); + config.getList("optimization-methods.block-optimization.materials", List.of( + "LAPIS_BLOCK", "GLOWSTONE", "IRON_BLOCK" + ), "Values here need to be valid bukkit Material enums for your server version." + ).forEach(configuredMaterial -> { + try { + Material disableBlock = Material.valueOf(configuredMaterial); + this.blocks_that_disable.add(disableBlock); + } catch (IllegalArgumentException e) { + LogUtil.materialNotRecognized("block-optimization", configuredMaterial); + } + }); + this.cooldown = config.getInt("optimization-methods.block-optimization.optimize-cooldown-seconds", 600, """ + Cooldown in seconds until a villager can be optimized again by using specific blocks. \s + Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.""") * 1000L; + this.search_radius = config.getDouble("optimization-methods.block-optimization.search-radius-in-blocks", 2.0, """ + The radius in blocks a villager can be away from the player when he places an optimize block.\s + The closest unoptimized villager to the player will be optimized.""") / 2; + this.onlyWhileSneaking = config.getBoolean("optimization-methods.block-optimization.only-when-sneaking", true, + "Only optimize/unoptimize by workstation when player is sneaking during place or break."); + this.shouldNotifyPlayer = config.getBoolean("optimization-methods.block-optimization.notify-player", true, + "Sends players a message when they successfully optimized or unoptimized a villager."); + this.shouldLog = config.getBoolean("optimization-methods.block-optimization.log", false); + } + + @Override + public void enable() { + VillagerOptimizer plugin = VillagerOptimizer.getInstance(); + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + @Override + public void disable() { + HandlerList.unregisterAll(this); + } + + @Override + public boolean shouldEnable() { + return VillagerOptimizer.getConfiguration().getBoolean("optimization-methods.block-optimization.enable", false); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onBlockPlace(BlockPlaceEvent event) { + Block placed = event.getBlock(); + if (!blocks_that_disable.contains(placed.getType())) return; + Player player = event.getPlayer(); + if (!player.hasPermission(Permissions.Optimize.BLOCK.get())) return; + if (onlyWhileSneaking && !player.isSneaking()) return; + + final Location blockLoc = placed.getLocation(); + WrappedVillager closestOptimizableVillager = null; + double closestDistance = Double.MAX_VALUE; + + for (Entity entity : blockLoc.getNearbyEntities(search_radius, search_radius, search_radius)) { + if (!entity.getType().equals(EntityType.VILLAGER)) continue; + Villager villager = (Villager) entity; + final Villager.Profession profession = villager.getProfession(); + if (profession.equals(Villager.Profession.NONE) || profession.equals(Villager.Profession.NITWIT)) continue; + + WrappedVillager wVillager = villagerCache.getOrAdd(villager); + final double distance = entity.getLocation().distance(blockLoc); + + if (distance < closestDistance && wVillager.canOptimize(cooldown)) { + closestOptimizableVillager = wVillager; + closestDistance = distance; + } + } + + if (closestOptimizableVillager == null) return; + + if (closestOptimizableVillager.canOptimize(cooldown) || player.hasPermission(Permissions.Bypass.BLOCK_COOLDOWN.get())) { + VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(closestOptimizableVillager, OptimizationType.BLOCK, event.isAsynchronous()); + VillagerOptimizer.callEvent(optimizeEvent); + if (optimizeEvent.isCancelled()) return; + + closestOptimizableVillager.setOptimization(optimizeEvent.getOptimizationType()); + closestOptimizableVillager.saveOptimizeTime(); + + if (shouldNotifyPlayer) { + final TextReplacementConfig vilProfession = TextReplacementConfig.builder() + .matchLiteral("%vil_profession%") + .replacement(closestOptimizableVillager.villager().getProfession().toString().toLowerCase()) + .build(); + final TextReplacementConfig placedMaterial = TextReplacementConfig.builder() + .matchLiteral("%blocktype%") + .replacement(placed.getType().toString().toLowerCase()) + .build(); + VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(line -> player.sendMessage(line + .replaceText(vilProfession) + .replaceText(placedMaterial) + )); + } + if (shouldLog) + VillagerOptimizer.getLog().info("Villager was optimized by block at "+closestOptimizableVillager.villager().getLocation()); + } else { + if (shouldNotifyPlayer) { + final TextReplacementConfig timeLeft = TextReplacementConfig.builder() + .matchLiteral("%time%") + .replacement(CommonUtil.formatTime(closestOptimizableVillager.getOptimizeCooldownMillis(cooldown))) + .build(); + VillagerOptimizer.getLang(player.locale()).block_on_optimize_cooldown.forEach(line -> player.sendMessage(line.replaceText(timeLeft))); + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onBlockBreak(BlockBreakEvent event) { + Block broken = event.getBlock(); + if (!blocks_that_disable.contains(broken.getType())) return; + Player player = event.getPlayer(); + if (!player.hasPermission(Permissions.Optimize.BLOCK.get())) return; + if (onlyWhileSneaking && !player.isSneaking()) return; + + final Location blockLoc = broken.getLocation(); + WrappedVillager closestOptimizedVillager = null; + double closestDistance = Double.MAX_VALUE; + + for (Entity entity : blockLoc.getNearbyEntities(search_radius, search_radius, search_radius)) { + if (!entity.getType().equals(EntityType.VILLAGER)) continue; + Villager villager = (Villager) entity; + + WrappedVillager wVillager = villagerCache.getOrAdd(villager); + final double distance = entity.getLocation().distance(blockLoc); + + if (distance < closestDistance && wVillager.isOptimized()) { + closestOptimizedVillager = wVillager; + closestDistance = distance; + } + } + + if (closestOptimizedVillager == null) return; + + VillagerUnoptimizeEvent unOptimizeEvent = new VillagerUnoptimizeEvent(closestOptimizedVillager, event.isAsynchronous()); + VillagerOptimizer.callEvent(unOptimizeEvent); + if (unOptimizeEvent.isCancelled()) return; + + closestOptimizedVillager.setOptimization(OptimizationType.NONE); + + if (shouldNotifyPlayer) { + final TextReplacementConfig vilProfession = TextReplacementConfig.builder() + .matchLiteral("%vil_profession%") + .replacement(closestOptimizedVillager.villager().getProfession().toString().toLowerCase()) + .build(); + final TextReplacementConfig brokenMaterial = TextReplacementConfig.builder() + .matchLiteral("%blocktype%") + .replacement(broken.getType().toString().toLowerCase()) + .build(); + VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(line -> player.sendMessage(line + .replaceText(vilProfession) + .replaceText(brokenMaterial) + )); + } + if (shouldLog) + VillagerOptimizer.getLog().info("Villager unoptimized because nearby optimization block broken at: "+closestOptimizedVillager.villager().getLocation()); + } +} \ No newline at end of file diff --git a/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/optimizations/OptimizeByNametag.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/optimizations/OptimizeByNametag.java new file mode 100644 index 0000000..6c46a77 --- /dev/null +++ b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/optimizations/OptimizeByNametag.java @@ -0,0 +1,136 @@ +package me.xginko.villageroptimizer.modules.optimizations; + +import me.xginko.villageroptimizer.VillagerCache; +import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.config.Config; +import me.xginko.villageroptimizer.enums.OptimizationType; +import me.xginko.villageroptimizer.enums.Permissions; +import me.xginko.villageroptimizer.events.VillagerOptimizeEvent; +import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent; +import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; +import me.xginko.villageroptimizer.utils.CommonUtil; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextReplacementConfig; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.Material; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.entity.Villager; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.HashSet; +import java.util.List; + +public class OptimizeByNametag implements VillagerOptimizerModule, Listener { + + private final VillagerCache villagerCache; + private final HashSet nametags = new HashSet<>(4); + private final long cooldown; + private final boolean consumeNametag, shouldNotifyPlayer, shouldLog; + + public OptimizeByNametag() { + shouldEnable(); + this.villagerCache = VillagerOptimizer.getCache(); + Config config = VillagerOptimizer.getConfiguration(); + config.addComment("optimization-methods.nametag-optimization.enable", """ + Enable optimization by naming villagers to one of the names configured below.\s + Nametag optimized villagers will be unoptimized again when they are renamed to something else."""); + this.nametags.addAll(config.getList("optimization-methods.nametag-optimization.names", List.of("Optimize", "DisableAI"), + "Names are case insensitive, capital letters won't matter.").stream().map(String::toLowerCase).toList()); + this.consumeNametag = config.getBoolean("optimization-methods.nametag-optimization.nametags-get-consumed", true, + "Enable or disable consumption of the used nametag item."); + this.cooldown = config.getInt("optimization-methods.nametag-optimization.optimize-cooldown-seconds", 600, """ + Cooldown in seconds until a villager can be optimized again using a nametag.\s + Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.""") * 1000L; + this.shouldNotifyPlayer = config.getBoolean("optimization-methods.nametag-optimization.notify-player", true, + "Sends players a message when they successfully optimized a villager."); + this.shouldLog = config.getBoolean("optimization-methods.nametag-optimization.log", false); + } + + @Override + public void enable() { + VillagerOptimizer plugin = VillagerOptimizer.getInstance(); + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + @Override + public void disable() { + HandlerList.unregisterAll(this); + } + + @Override + public boolean shouldEnable() { + return VillagerOptimizer.getConfiguration().getBoolean("optimization-methods.nametag-optimization.enable", true); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onPlayerInteractEntity(PlayerInteractEntityEvent event) { + if (!event.getRightClicked().getType().equals(EntityType.VILLAGER)) return; + Player player = event.getPlayer(); + if (!player.hasPermission(Permissions.Optimize.NAMETAG.get())) return; + + ItemStack usedItem = player.getInventory().getItem(event.getHand()); + if (!usedItem.getType().equals(Material.NAME_TAG)) return; + ItemMeta meta = usedItem.getItemMeta(); + if (!meta.hasDisplayName()) return; + + // Get component name first, so we can manually name the villager when canceling the event to avoid item consumption. + Component newVillagerName = meta.displayName(); + assert newVillagerName != null; // Legitimate since we checked for hasDisplayName() + + + final String name = PlainTextComponentSerializer.plainText().serialize(newVillagerName); + Villager villager = (Villager) event.getRightClicked(); + WrappedVillager wVillager = villagerCache.getOrAdd(villager); + + if (nametags.contains(name.toLowerCase())) { + if (wVillager.canOptimize(cooldown) || player.hasPermission(Permissions.Bypass.NAMETAG_COOLDOWN.get())) { + if (!consumeNametag) { + event.setCancelled(true); + villager.customName(newVillagerName); + } + + VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(wVillager, OptimizationType.NAMETAG, event.isAsynchronous()); + VillagerOptimizer.callEvent(optimizeEvent); + if (optimizeEvent.isCancelled()) return; + + wVillager.setOptimization(optimizeEvent.getOptimizationType()); + wVillager.saveOptimizeTime(); + + if (shouldNotifyPlayer) + VillagerOptimizer.getLang(player.locale()).nametag_optimize_success.forEach(player::sendMessage); + if (shouldLog) + VillagerOptimizer.getLog().info(player.getName() + " optimized a villager using nametag: '" + name + "'"); + } else { + event.setCancelled(true); + if (shouldNotifyPlayer) { + final TextReplacementConfig timeLeft = TextReplacementConfig.builder() + .matchLiteral("%time%") + .replacement(CommonUtil.formatTime(wVillager.getOptimizeCooldownMillis(cooldown))) + .build(); + VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> player.sendMessage(line.replaceText(timeLeft))); + } + } + } else { + if (wVillager.isOptimized()) { + VillagerUnoptimizeEvent unOptimizeEvent = new VillagerUnoptimizeEvent(wVillager, event.isAsynchronous()); + VillagerOptimizer.callEvent(unOptimizeEvent); + if (unOptimizeEvent.isCancelled()) return; + + wVillager.setOptimization(OptimizationType.NONE); + + if (shouldNotifyPlayer) + VillagerOptimizer.getLang(player.locale()).nametag_unoptimize_success.forEach(player::sendMessage); + if (shouldLog) + VillagerOptimizer.getLog().info(event.getPlayer().getName() + " disabled optimizations for a villager using nametag: '" + name + "'"); + } + } + } +} \ No newline at end of file diff --git a/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/optimizations/OptimizeByWorkstation.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/optimizations/OptimizeByWorkstation.java new file mode 100644 index 0000000..671ea47 --- /dev/null +++ b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/modules/optimizations/OptimizeByWorkstation.java @@ -0,0 +1,208 @@ +package me.xginko.villageroptimizer.modules.optimizations; + +import me.xginko.villageroptimizer.VillagerCache; +import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.config.Config; +import me.xginko.villageroptimizer.enums.OptimizationType; +import me.xginko.villageroptimizer.enums.Permissions; +import me.xginko.villageroptimizer.events.VillagerOptimizeEvent; +import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent; +import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; +import me.xginko.villageroptimizer.utils.CommonUtil; +import net.kyori.adventure.text.TextReplacementConfig; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.entity.Villager; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; + +public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener { + + private final VillagerCache villagerCache; + private final long cooldown; + private final double search_radius; + private final boolean onlyWhileSneaking, shouldLog, shouldNotifyPlayer; + + public OptimizeByWorkstation() { + shouldEnable(); + this.villagerCache = VillagerOptimizer.getCache(); + Config config = VillagerOptimizer.getConfiguration(); + config.addComment("optimization-methods.workstation-optimization.enable", """ + When enabled, the closest villager near a matching workstation being placed will be optimized.\s + If a nearby matching workstation is broken, the villager will become unoptimized again."""); + this.search_radius = config.getDouble("optimization-methods.workstation-optimization.search-radius-in-blocks", 2.0, """ + The radius in blocks a villager can be away from the player when he places a workstation.\s + The closest unoptimized villager to the player will be optimized.""") / 2; + this.cooldown = config.getInt("optimization-methods.workstation-optimization.optimize-cooldown-seconds", 600, """ + Cooldown in seconds until a villager can be optimized again using a workstation.\s + Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.""") * 1000L; + this.onlyWhileSneaking = config.getBoolean("optimization-methods.workstation-optimization.only-when-sneaking", true, + "Only optimize/unoptimize by workstation when player is sneaking during place or break"); + this.shouldNotifyPlayer = config.getBoolean("optimization-methods.workstation-optimization.notify-player", true, + "Sends players a message when they successfully optimized a villager."); + this.shouldLog = config.getBoolean("optimization-methods.workstation-optimization.log", false); + } + + @Override + public void enable() { + VillagerOptimizer plugin = VillagerOptimizer.getInstance(); + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + @Override + public void disable() { + HandlerList.unregisterAll(this); + } + + @Override + public boolean shouldEnable() { + return VillagerOptimizer.getConfiguration().getBoolean("optimization-methods.workstation-optimization.enable", false); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onBlockPlace(BlockPlaceEvent event) { + Block placed = event.getBlock(); + Villager.Profession workstationProfession = getWorkstationProfession(placed.getType()); + if (workstationProfession.equals(Villager.Profession.NONE)) return; + Player player = event.getPlayer(); + if (!player.hasPermission(Permissions.Optimize.WORKSTATION.get())) return; + if (onlyWhileSneaking && !player.isSneaking()) return; + + final Location workstationLoc = placed.getLocation(); + WrappedVillager closestOptimizableVillager = null; + double closestDistance = Double.MAX_VALUE; + + for (Entity entity : workstationLoc.getNearbyEntities(search_radius, search_radius, search_radius)) { + if (!entity.getType().equals(EntityType.VILLAGER)) continue; + Villager villager = (Villager) entity; + if (!villager.getProfession().equals(workstationProfession)) continue; + + WrappedVillager wVillager = villagerCache.getOrAdd(villager); + final double distance = entity.getLocation().distance(workstationLoc); + + if (distance < closestDistance && wVillager.canOptimize(cooldown)) { + closestOptimizableVillager = wVillager; + closestDistance = distance; + } + } + + if (closestOptimizableVillager == null) return; + + if (closestOptimizableVillager.canOptimize(cooldown) || player.hasPermission(Permissions.Bypass.WORKSTATION_COOLDOWN.get())) { + VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(closestOptimizableVillager, OptimizationType.WORKSTATION, event.isAsynchronous()); + VillagerOptimizer.callEvent(optimizeEvent); + if (optimizeEvent.isCancelled()) return; + + closestOptimizableVillager.setOptimization(optimizeEvent.getOptimizationType()); + closestOptimizableVillager.saveOptimizeTime(); + + if (shouldNotifyPlayer) { + final TextReplacementConfig vilProfession = TextReplacementConfig.builder() + .matchLiteral("%vil_profession%") + .replacement(closestOptimizableVillager.villager().getProfession().toString().toLowerCase()) + .build(); + final TextReplacementConfig placedWorkstation = TextReplacementConfig.builder() + .matchLiteral("%workstation%") + .replacement(placed.getType().toString().toLowerCase()) + .build(); + VillagerOptimizer.getLang(player.locale()).workstation_optimize_success.forEach(line -> player.sendMessage(line + .replaceText(vilProfession) + .replaceText(placedWorkstation) + )); + } + if (shouldLog) + VillagerOptimizer.getLog().info(player.getName() + " optimized a villager using workstation: '" + placed.getType().toString().toLowerCase() + "'"); + } else { + if (shouldNotifyPlayer) { + final TextReplacementConfig timeLeft = TextReplacementConfig.builder() + .matchLiteral("%time%") + .replacement(CommonUtil.formatTime(closestOptimizableVillager.getOptimizeCooldownMillis(cooldown))) + .build(); + VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> player.sendMessage(line + .replaceText(timeLeft) + )); + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onBlockBreak(BlockBreakEvent event) { + Block broken = event.getBlock(); + Villager.Profession workstationProfession = getWorkstationProfession(broken.getType()); + if (workstationProfession.equals(Villager.Profession.NONE)) return; + Player player = event.getPlayer(); + if (!player.hasPermission(Permissions.Optimize.WORKSTATION.get())) return; + if (onlyWhileSneaking && !player.isSneaking()) return; + + final Location workstationLoc = broken.getLocation(); + WrappedVillager closestOptimizedVillager = null; + double closestDistance = Double.MAX_VALUE; + + for (Entity entity : workstationLoc.getNearbyEntities(search_radius, search_radius, search_radius)) { + if (!entity.getType().equals(EntityType.VILLAGER)) continue; + Villager villager = (Villager) entity; + if (!villager.getProfession().equals(workstationProfession)) continue; + + WrappedVillager wVillager = villagerCache.getOrAdd(villager); + final double distance = entity.getLocation().distance(workstationLoc); + + if (distance < closestDistance && wVillager.canOptimize(cooldown)) { + closestOptimizedVillager = wVillager; + closestDistance = distance; + } + } + + if (closestOptimizedVillager == null) return; + + VillagerUnoptimizeEvent unOptimizeEvent = new VillagerUnoptimizeEvent(closestOptimizedVillager, event.isAsynchronous()); + VillagerOptimizer.callEvent(unOptimizeEvent); + if (unOptimizeEvent.isCancelled()) return; + + closestOptimizedVillager.setOptimization(OptimizationType.NONE); + + if (shouldNotifyPlayer) { + final TextReplacementConfig vilProfession = TextReplacementConfig.builder() + .matchLiteral("%vil_profession%") + .replacement(closestOptimizedVillager.villager().getProfession().toString().toLowerCase()) + .build(); + final TextReplacementConfig brokenWorkstation = TextReplacementConfig.builder() + .matchLiteral("%workstation%") + .replacement(broken.getType().toString().toLowerCase()) + .build(); + VillagerOptimizer.getLang(player.locale()).workstation_unoptimize_success.forEach(line -> player.sendMessage(line + .replaceText(vilProfession) + .replaceText(brokenWorkstation) + )); + } + if (shouldLog) + VillagerOptimizer.getLog().info(player.getName() + " unoptimized a villager by breaking workstation: '" + broken.getType().toString().toLowerCase() + "'"); + } + + private Villager.Profession getWorkstationProfession(final Material workstation) { + return switch (workstation) { + case BARREL -> Villager.Profession.FISHERMAN; + case CARTOGRAPHY_TABLE -> Villager.Profession.CARTOGRAPHER; + case SMOKER -> Villager.Profession.BUTCHER; + case SMITHING_TABLE -> Villager.Profession.TOOLSMITH; + case GRINDSTONE -> Villager.Profession.WEAPONSMITH; + case BLAST_FURNACE -> Villager.Profession.ARMORER; + case CAULDRON -> Villager.Profession.LEATHERWORKER; + case BREWING_STAND -> Villager.Profession.CLERIC; + case COMPOSTER -> Villager.Profession.FARMER; + case FLETCHING_TABLE -> Villager.Profession.FLETCHER; + case LOOM -> Villager.Profession.SHEPHERD; + case LECTERN -> Villager.Profession.LIBRARIAN; + case STONECUTTER -> Villager.Profession.MASON; + default -> Villager.Profession.NONE; + }; + } +} \ No newline at end of file diff --git a/src/main/java/me/xginko/villageroptimizer/utils/CommonUtil.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/utils/CommonUtil.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/utils/CommonUtil.java rename to VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/utils/CommonUtil.java diff --git a/src/main/java/me/xginko/villageroptimizer/utils/LogUtil.java b/VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/utils/LogUtil.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/utils/LogUtil.java rename to VillagerOptimizer-1.16.5/src/main/java/me/xginko/villageroptimizer/utils/LogUtil.java diff --git a/src/main/resources/lang/en_us.yml b/VillagerOptimizer-1.16.5/src/main/resources/lang/en_us.yml similarity index 100% rename from src/main/resources/lang/en_us.yml rename to VillagerOptimizer-1.16.5/src/main/resources/lang/en_us.yml diff --git a/src/main/resources/plugin.yml b/VillagerOptimizer-1.16.5/src/main/resources/plugin.yml similarity index 100% rename from src/main/resources/plugin.yml rename to VillagerOptimizer-1.16.5/src/main/resources/plugin.yml diff --git a/VillagerOptimizer-1.20.2/pom.xml b/VillagerOptimizer-1.20.2/pom.xml new file mode 100644 index 0000000..fa2ef2a --- /dev/null +++ b/VillagerOptimizer-1.20.2/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + + + me.xginko.VillagerOptimizer + VillagerOptimizer + 1.0.0 + + + 1.20.2 + ${project.parent.artifactId}-${project.parent.version}--${project.artifactId} + jar + + + 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.5.1 + + + package + + shade + + + + + org.bstats + me.xginko.villageroptimizer.bstats + + + false + + + + + + + + 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.2-R0.1-SNAPSHOT + provided + + + diff --git a/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/VillagerCache.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/VillagerCache.java new file mode 100644 index 0000000..5eb4423 --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/VillagerCache.java @@ -0,0 +1,56 @@ +package me.xginko.villageroptimizer; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import org.bukkit.Bukkit; +import org.bukkit.entity.Villager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.time.Duration; +import java.util.UUID; +import java.util.concurrent.ConcurrentMap; + +public final class VillagerCache { + + private final @NotNull Cache villagerCache; + + VillagerCache(long expireAfterWriteSeconds) { + this.villagerCache = Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(expireAfterWriteSeconds)).build(); + } + + public @NotNull ConcurrentMap cacheMap() { + return this.villagerCache.asMap(); + } + + public @Nullable WrappedVillager get(@NotNull UUID uuid) { + WrappedVillager wrappedVillager = villagerCache.getIfPresent(uuid); + return wrappedVillager == null && Bukkit.getEntity(uuid) instanceof Villager villager ? add(villager) : wrappedVillager; + } + + public @NotNull WrappedVillager getOrAdd(@NotNull Villager villager) { + WrappedVillager wrappedVillager = villagerCache.getIfPresent(villager.getUniqueId()); + return wrappedVillager == null ? add(new WrappedVillager(villager)) : add(wrappedVillager); + } + + public @NotNull WrappedVillager add(@NotNull WrappedVillager villager) { + villagerCache.put(villager.villager().getUniqueId(), villager); + return villager; + } + + public @NotNull WrappedVillager add(@NotNull Villager villager) { + return add(new WrappedVillager(villager)); + } + + public boolean contains(@NotNull UUID uuid) { + return villagerCache.getIfPresent(uuid) != null; + } + + public boolean contains(@NotNull WrappedVillager villager) { + return villagerCache.getIfPresent(villager.villager().getUniqueId()) != null; + } + + public boolean contains(@NotNull Villager villager) { + return villagerCache.getIfPresent(villager.getUniqueId()) != null; + } +} \ No newline at end of file diff --git a/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/VillagerOptimizer.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/VillagerOptimizer.java new file mode 100644 index 0000000..07da4d8 --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/VillagerOptimizer.java @@ -0,0 +1,175 @@ +package me.xginko.villageroptimizer; + +import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand; +import me.xginko.villageroptimizer.config.Config; +import me.xginko.villageroptimizer.config.LanguageCache; +import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.NamespacedKey; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +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 VillagerCache villagerCache; + private static HashMap languageCacheMap; + private static Config config; + private static Logger logger; + + public final static Style plugin_style = Style.style(TextColor.color(102,255,230), TextDecoration.BOLD); + + @Override + public void onEnable() { + instance = this; + logger = getLogger(); + ConsoleCommandSender console = getServer().getConsoleSender(); + console.sendMessage(Component.text("╭────────────────────────────────────────────────────────────╮").style(plugin_style)); + console.sendMessage(Component.text("│ │").style(plugin_style)); + console.sendMessage(Component.text("│ │").style(plugin_style)); + console.sendMessage(Component.text("│ _ __ _ __ __ │").style(plugin_style)); + console.sendMessage(Component.text("│ | | / /(_)/ // /___ _ ___ _ ___ ____ │").style(plugin_style)); + console.sendMessage(Component.text("│ | |/ // // // // _ `// _ `// -_)/ __/ │").style(plugin_style)); + console.sendMessage(Component.text("│ |___//_//_//_/ \\_,_/ \\_, / \\__//_/ │").style(plugin_style)); + console.sendMessage(Component.text("│ ____ __ _ /___/_ │").style(plugin_style)); + console.sendMessage(Component.text("│ / __ \\ ___ / /_ (_)__ _ (_)___ ___ ____ │").style(plugin_style)); + console.sendMessage(Component.text("│ / /_/ // _ \\/ __// // ' \\ / //_ // -_)/ __/ │").style(plugin_style)); + console.sendMessage(Component.text("│ \\____// .__/\\__//_//_/_/_//_/ /__/\\__//_/ │").style(plugin_style)); + console.sendMessage(Component.text("│ /_/ by xGinko │").style(plugin_style)); + console.sendMessage(Component.text("│ │").style(plugin_style)); + console.sendMessage(Component.text("│ │").style(plugin_style)); + console.sendMessage(Component.text("│ ").style(plugin_style).append(Component.text("https://github.com/xGinko/VillagerOptimizer").color(NamedTextColor.GRAY)).append(Component.text(" │").style(plugin_style))); + console.sendMessage(Component.text("│ │").style(plugin_style)); + console.sendMessage(Component.text("│ │").style(plugin_style)); + console.sendMessage(Component.text("│ ").style(plugin_style).append(Component.text(" ➤ Loading Translations...").style(plugin_style)).append(Component.text(" │").style(plugin_style))); + reloadLang(true); + console.sendMessage(Component.text("│ ").style(plugin_style).append(Component.text(" ➤ Loading Config...").style(plugin_style)).append(Component.text(" │").style(plugin_style))); + reloadConfiguration(); + console.sendMessage(Component.text("│ ").style(plugin_style).append(Component.text(" ✓ Done.").color(NamedTextColor.WHITE).decorate(TextDecoration.BOLD)).append(Component.text(" │").style(plugin_style))); + console.sendMessage(Component.text("│ │").style(plugin_style)); + console.sendMessage(Component.text("│ │").style(plugin_style)); + console.sendMessage(Component.text("╰────────────────────────────────────────────────────────────╯").style(plugin_style)); + } + + public static VillagerOptimizer getInstance() { + return instance; + } + public static NamespacedKey getKey(String key) { + return new NamespacedKey(instance, key); + } + public static Config getConfiguration() { + return config; + } + public static VillagerCache getCache() { + return villagerCache; + } + public static Logger getLog() { + return logger; + } + public static LanguageCache getLang(Locale locale) { + return getLang(locale.toString().toLowerCase()); + } + public static LanguageCache getLang(CommandSender commandSender) { + return commandSender instanceof Player player ? getLang(player.locale()) : getLang(config.default_lang); + } + public static LanguageCache getLang(String lang) { + return config.auto_lang ? languageCacheMap.getOrDefault(lang.replace("-", "_"), languageCacheMap.get(config.default_lang.toString().toLowerCase())) : languageCacheMap.get(config.default_lang.toString().toLowerCase()); + } + public static void callEvent(Event event) { + instance.getServer().getPluginManager().callEvent(event); + } + + public void reloadPlugin() { + reloadLang(false); + reloadConfiguration(); + } + + private void reloadConfiguration() { + try { + config = new Config(); + villagerCache = new VillagerCache(config.cache_keep_time_seconds); + VillagerOptimizerCommand.reloadCommands(); + VillagerOptimizerModule.reloadModules(); + config.saveConfig(); + } catch (Exception e) { + logger.severe("Error loading config! - " + e.getLocalizedMessage()); + e.printStackTrace(); + } + } + + private void reloadLang(boolean startup) { + languageCacheMap = new HashMap<>(); + ConsoleCommandSender console = getServer().getConsoleSender(); + try { + File langDirectory = new File(getDataFolder() + "/lang"); + Files.createDirectories(langDirectory.toPath()); + for (String fileName : getDefaultLanguageFiles()) { + String localeString = fileName.substring(fileName.lastIndexOf('/') + 1, fileName.lastIndexOf('.')); + if (startup) console.sendMessage( + Component.text("│ ").style(plugin_style) + .append(Component.text(" "+localeString).color(NamedTextColor.WHITE).decorate(TextDecoration.BOLD)) + .append(Component.text(" │").style(plugin_style))); + else logger.info(String.format("Found language file for %s", localeString)); + 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 + if (startup) console.sendMessage( + Component.text("│ ").style(plugin_style) + .append(Component.text(" "+localeString).color(NamedTextColor.WHITE).decorate(TextDecoration.BOLD)) + .append(Component.text(" │").style(plugin_style))); + else logger.info(String.format("Found language file for %s", localeString)); + LanguageCache langCache = new LanguageCache(localeString); + languageCacheMap.put(localeString, langCache); + } + } + } + } catch (Exception e) { + if (startup) console.sendMessage( + Component.text("│ ").style(plugin_style) + .append(Component.text("LANG ERROR").color(NamedTextColor.RED).decorate(TextDecoration.BOLD)) + .append(Component.text(" │").style(plugin_style))); + else logger.severe("Error loading language files! Language files will not reload to avoid errors, make sure to correct this before restarting the server!"); + e.printStackTrace(); + } + } + + private Set getDefaultLanguageFiles() { + Set languageFiles = new HashSet<>(); + try (JarFile jarFile = new JarFile(this.getFile())) { + jarFile.entries().asIterator().forEachRemaining(jarFileEntry -> { + final String path = jarFileEntry.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; + } +} diff --git a/src/main/java/me/xginko/villageroptimizer/WrappedVillager.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/WrappedVillager.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/WrappedVillager.java rename to VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/WrappedVillager.java diff --git a/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/commands/SubCommand.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/commands/SubCommand.java new file mode 100644 index 0000000..4ce3d53 --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/commands/SubCommand.java @@ -0,0 +1,11 @@ +package me.xginko.villageroptimizer.commands; + +import net.kyori.adventure.text.TextComponent; +import org.bukkit.command.CommandSender; + +public abstract class SubCommand { + public abstract String getLabel(); + public abstract TextComponent getDescription(); + public abstract TextComponent getSyntax(); + public abstract void perform(CommandSender sender, String[] args); +} diff --git a/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/commands/VillagerOptimizerCommand.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/commands/VillagerOptimizerCommand.java new file mode 100644 index 0000000..acfbcd8 --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/commands/VillagerOptimizerCommand.java @@ -0,0 +1,35 @@ +package me.xginko.villageroptimizer.commands; + +import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.commands.optimizevillagers.OptVillagersRadius; +import me.xginko.villageroptimizer.commands.unoptimizevillagers.UnOptVillagersRadius; +import me.xginko.villageroptimizer.commands.villageroptimizer.VillagerOptimizerCmd; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandMap; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.util.HashSet; + +public interface VillagerOptimizerCommand extends CommandExecutor { + + String label(); + + HashSet commands = new HashSet<>(); + static void reloadCommands() { + VillagerOptimizer plugin = VillagerOptimizer.getInstance(); + CommandMap commandMap = plugin.getServer().getCommandMap(); + commands.forEach(command -> plugin.getCommand(command.label()).unregister(commandMap)); + commands.clear(); + + commands.add(new VillagerOptimizerCmd()); + commands.add(new OptVillagersRadius()); + commands.add(new UnOptVillagersRadius()); + + commands.forEach(command -> plugin.getCommand(command.label()).setExecutor(command)); + } + + @Override + boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args); +} diff --git a/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/commands/optimizevillagers/OptVillagersRadius.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/commands/optimizevillagers/OptVillagersRadius.java new file mode 100644 index 0000000..5e734bd --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/commands/optimizevillagers/OptVillagersRadius.java @@ -0,0 +1,142 @@ +package me.xginko.villageroptimizer.commands.optimizevillagers; + +import me.xginko.villageroptimizer.VillagerCache; +import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand; +import me.xginko.villageroptimizer.config.Config; +import me.xginko.villageroptimizer.enums.OptimizationType; +import me.xginko.villageroptimizer.enums.Permissions; +import me.xginko.villageroptimizer.events.VillagerOptimizeEvent; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextReplacementConfig; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.entity.Villager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; + +public class OptVillagersRadius implements VillagerOptimizerCommand, TabCompleter { + + private final List tabCompletes = List.of("5", "10", "25", "50"); + private final long cooldown; + private final int maxRadius; + + public OptVillagersRadius() { + Config config = VillagerOptimizer.getConfiguration(); + this.maxRadius = config.getInt("optimization-methods.commands.optimizevillagers.max-block-radius", 100); + this.cooldown = config.getInt("optimization-methods.commands.optimizevillagers.cooldown-seconds", 600, """ + Cooldown in seconds until a villager can be optimized again using the command.\s + Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.""") * 1000L; + } + + @Override + public String label() { + return "optimizevillagers"; + } + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + return args.length == 1 ? tabCompletes : Collections.emptyList(); + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) { + if (!(sender instanceof Player player)) { + sender.sendMessage(Component.text("This command can only be executed by a player.") + .color(NamedTextColor.RED).decorate(TextDecoration.BOLD)); + return true; + } + + if (sender.hasPermission(Permissions.Commands.OPTIMIZE_RADIUS.get())) { + if (args.length != 1) { + VillagerOptimizer.getLang(player.locale()).command_specify_radius.forEach(player::sendMessage); + return true; + } + + try { + int specifiedRadius = Integer.parseInt(args[0]); + + if (specifiedRadius > maxRadius) { + final TextReplacementConfig limit = TextReplacementConfig.builder() + .matchLiteral("%distance%") + .replacement(Integer.toString(maxRadius)) + .build(); + VillagerOptimizer.getLang(player.locale()).command_radius_limit_exceed.forEach(line -> player.sendMessage(line.replaceText(limit))); + return true; + } + + VillagerCache villagerCache = VillagerOptimizer.getCache(); + int successCount = 0; + int failCount = 0; + + for (Entity entity : player.getNearbyEntities(specifiedRadius, specifiedRadius, specifiedRadius)) { + if (!entity.getType().equals(EntityType.VILLAGER)) continue; + Villager villager = (Villager) entity; + Villager.Profession profession = villager.getProfession(); + if (profession.equals(Villager.Profession.NITWIT) || profession.equals(Villager.Profession.NONE)) continue; + + WrappedVillager wVillager = villagerCache.getOrAdd(villager); + + if (wVillager.canOptimize(cooldown)) { + VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(wVillager, OptimizationType.COMMAND); + VillagerOptimizer.callEvent(optimizeEvent); + if (!optimizeEvent.isCancelled()) { + wVillager.setOptimization(optimizeEvent.getOptimizationType()); + wVillager.saveOptimizeTime(); + successCount++; + } + } else { + failCount++; + } + } + + if (successCount <= 0 && failCount <= 0) { + final TextReplacementConfig radius = TextReplacementConfig.builder() + .matchLiteral("%radius%") + .replacement(Integer.toString(specifiedRadius)) + .build(); + VillagerOptimizer.getLang(player.locale()).command_no_villagers_nearby.forEach(line -> player.sendMessage(line.replaceText(radius))); + return true; + } + + if (successCount > 0) { + final TextReplacementConfig success_amount = TextReplacementConfig.builder() + .matchLiteral("%amount%") + .replacement(Integer.toString(successCount)) + .build(); + final TextReplacementConfig radius = TextReplacementConfig.builder() + .matchLiteral("%radius%") + .replacement(Integer.toString(specifiedRadius)) + .build(); + VillagerOptimizer.getLang(player.locale()).command_optimize_success.forEach(line -> player.sendMessage(line + .replaceText(success_amount) + .replaceText(radius) + )); + } + if (failCount > 0) { + final TextReplacementConfig alreadyOptimized = TextReplacementConfig.builder() + .matchLiteral("%amount%") + .replacement(Integer.toString(failCount)) + .build(); + VillagerOptimizer.getLang(player.locale()).command_optimize_fail.forEach(line -> player.sendMessage(line.replaceText(alreadyOptimized))); + } + } catch (NumberFormatException e) { + VillagerOptimizer.getLang(player.locale()).command_radius_invalid.forEach(player::sendMessage); + } + } else { + sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission); + } + + return true; + } +} \ No newline at end of file diff --git a/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/commands/unoptimizevillagers/UnOptVillagersRadius.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/commands/unoptimizevillagers/UnOptVillagersRadius.java new file mode 100644 index 0000000..229790a --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/commands/unoptimizevillagers/UnOptVillagersRadius.java @@ -0,0 +1,122 @@ +package me.xginko.villageroptimizer.commands.unoptimizevillagers; + +import me.xginko.villageroptimizer.VillagerCache; +import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand; +import me.xginko.villageroptimizer.enums.OptimizationType; +import me.xginko.villageroptimizer.enums.Permissions; +import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextReplacementConfig; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.entity.Villager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; + +public class UnOptVillagersRadius implements VillagerOptimizerCommand, TabCompleter { + + private final List tabCompletes = List.of("5", "10", "25", "50"); + private final int maxRadius; + + public UnOptVillagersRadius() { + this.maxRadius = VillagerOptimizer.getConfiguration().getInt("optimization-methods.commands.unoptimizevillagers.max-block-radius", 100); + } + + @Override + public String label() { + return "unoptimizevillagers"; + } + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + return args.length == 1 ? tabCompletes : Collections.emptyList(); + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) { + if (!(sender instanceof Player player)) { + sender.sendMessage(Component.text("This command can only be executed by a player.") + .color(NamedTextColor.RED).decorate(TextDecoration.BOLD)); + return true; + } + + if (sender.hasPermission(Permissions.Commands.UNOPTIMIZE_RADIUS.get())) { + if (args.length != 1) { + VillagerOptimizer.getLang(player.locale()).command_specify_radius.forEach(player::sendMessage); + return true; + } + + try { + int specifiedRadius = Integer.parseInt(args[0]); + + if (specifiedRadius > maxRadius) { + final TextReplacementConfig limit = TextReplacementConfig.builder() + .matchLiteral("%distance%") + .replacement(Integer.toString(maxRadius)) + .build(); + VillagerOptimizer.getLang(player.locale()).command_radius_limit_exceed.forEach(line -> player.sendMessage(line.replaceText(limit))); + return true; + } + + VillagerCache villagerCache = VillagerOptimizer.getCache(); + int successCount = 0; + + for (Entity entity : player.getNearbyEntities(specifiedRadius, specifiedRadius, specifiedRadius)) { + if (!entity.getType().equals(EntityType.VILLAGER)) continue; + Villager villager = (Villager) entity; + Villager.Profession profession = villager.getProfession(); + if (profession.equals(Villager.Profession.NITWIT) || profession.equals(Villager.Profession.NONE)) continue; + + WrappedVillager wVillager = villagerCache.getOrAdd(villager); + + if (wVillager.isOptimized()) { + VillagerUnoptimizeEvent unOptimizeEvent = new VillagerUnoptimizeEvent(wVillager); + VillagerOptimizer.callEvent(unOptimizeEvent); + if (!unOptimizeEvent.isCancelled()) { + wVillager.setOptimization(OptimizationType.NONE); + successCount++; + } + } + } + + if (successCount <= 0) { + final TextReplacementConfig radius = TextReplacementConfig.builder() + .matchLiteral("%radius%") + .replacement(Integer.toString(specifiedRadius)) + .build(); + VillagerOptimizer.getLang(player.locale()).command_no_villagers_nearby.forEach(line -> player.sendMessage(line.replaceText(radius))); + } else { + final TextReplacementConfig success_amount = TextReplacementConfig.builder() + .matchLiteral("%amount%") + .replacement(Integer.toString(successCount)) + .build(); + final TextReplacementConfig radius = TextReplacementConfig.builder() + .matchLiteral("%radius%") + .replacement(Integer.toString(specifiedRadius)) + .build(); + VillagerOptimizer.getLang(player.locale()).command_unoptimize_success.forEach(line -> player.sendMessage(line + .replaceText(success_amount) + .replaceText(radius) + )); + } + } catch (NumberFormatException e) { + VillagerOptimizer.getLang(player.locale()).command_radius_invalid.forEach(player::sendMessage); + } + } else { + sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission); + } + + return true; + } +} \ No newline at end of file diff --git a/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/VillagerOptimizerCmd.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/VillagerOptimizerCmd.java new file mode 100644 index 0000000..9f75aea --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/VillagerOptimizerCmd.java @@ -0,0 +1,80 @@ +package me.xginko.villageroptimizer.commands.villageroptimizer; + +import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.commands.SubCommand; +import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand; +import me.xginko.villageroptimizer.commands.villageroptimizer.subcommands.DisableSubCmd; +import me.xginko.villageroptimizer.commands.villageroptimizer.subcommands.ReloadSubCmd; +import me.xginko.villageroptimizer.commands.villageroptimizer.subcommands.VersionSubCmd; +import me.xginko.villageroptimizer.enums.Permissions; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class VillagerOptimizerCmd implements TabCompleter, VillagerOptimizerCommand { + + private final List subCommands = new ArrayList<>(3); + private final List tabCompleter = new ArrayList<>(3); + + public VillagerOptimizerCmd() { + subCommands.add(new ReloadSubCmd()); + subCommands.add(new VersionSubCmd()); + subCommands.add(new DisableSubCmd()); + subCommands.forEach(subCommand -> tabCompleter.add(subCommand.getLabel())); + } + + @Override + public String label() { + return "villageroptimizer"; + } + + @Override + public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) { + return args.length == 1 ? tabCompleter : Collections.emptyList(); + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) { + if (args.length > 0) { + boolean cmdExists = false; + for (SubCommand subCommand : subCommands) { + if (args[0].equalsIgnoreCase(subCommand.getLabel())) { + subCommand.perform(sender, args); + cmdExists = true; + break; + } + } + if (!cmdExists) sendCommandOverview(sender); + } else { + sendCommandOverview(sender); + } + return true; + } + + private void sendCommandOverview(CommandSender sender) { + if (!sender.hasPermission(Permissions.Commands.RELOAD.get()) && !sender.hasPermission(Permissions.Commands.VERSION.get())) return; + sender.sendMessage(Component.text("-----------------------------------------------------").color(NamedTextColor.GRAY)); + sender.sendMessage(Component.text("VillagerOptimizer Commands").color(VillagerOptimizer.plugin_style.color())); + sender.sendMessage(Component.text("-----------------------------------------------------").color(NamedTextColor.GRAY)); + subCommands.forEach(subCommand -> sender.sendMessage( + subCommand.getSyntax().append(Component.text(" - ").color(NamedTextColor.DARK_GRAY)).append(subCommand.getDescription()))); + sender.sendMessage( + Component.text("/optimizevillagers ").color(VillagerOptimizer.plugin_style.color()) + .append(Component.text(" - ").color(NamedTextColor.DARK_GRAY)) + .append(Component.text("Optimize villagers in a radius").color(NamedTextColor.GRAY)) + ); + sender.sendMessage( + Component.text("/unoptmizevillagers ").color(VillagerOptimizer.plugin_style.color()) + .append(Component.text(" - ").color(NamedTextColor.DARK_GRAY)) + .append(Component.text("Unoptimize villagers in a radius").color(NamedTextColor.GRAY)) + ); + sender.sendMessage(Component.text("-----------------------------------------------------").color(NamedTextColor.GRAY)); + } +} \ No newline at end of file diff --git a/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/subcommands/DisableSubCmd.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/subcommands/DisableSubCmd.java new file mode 100644 index 0000000..d7801e1 --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/subcommands/DisableSubCmd.java @@ -0,0 +1,42 @@ +package me.xginko.villageroptimizer.commands.villageroptimizer.subcommands; + +import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.commands.SubCommand; +import me.xginko.villageroptimizer.enums.Permissions; +import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.command.CommandSender; + +public class DisableSubCmd extends SubCommand { + + @Override + public String getLabel() { + return "disable"; + } + + @Override + public TextComponent getDescription() { + return Component.text("Disable all plugin tasks and listeners.").color(NamedTextColor.GRAY); + } + + @Override + public TextComponent getSyntax() { + return Component.text("/villageroptimizer disable").color(VillagerOptimizer.plugin_style.color()); + } + + @Override + public void perform(CommandSender sender, String[] args) { + if (sender.hasPermission(Permissions.Commands.RELOAD.get())) { + sender.sendMessage(Component.text("Disabling VillagerOptimizer...").color(NamedTextColor.RED)); + VillagerOptimizerModule.modules.forEach(VillagerOptimizerModule::disable); + VillagerOptimizerModule.modules.clear(); + VillagerOptimizer.getCache().cacheMap().clear(); + sender.sendMessage(Component.text("Disabled all plugin listeners and tasks.").color(NamedTextColor.GREEN)); + sender.sendMessage(Component.text("You can enable the plugin again using the reload command.").color(NamedTextColor.YELLOW)); + } else { + sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission); + } + } +} \ No newline at end of file diff --git a/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/subcommands/ReloadSubCmd.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/subcommands/ReloadSubCmd.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/subcommands/ReloadSubCmd.java rename to VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/subcommands/ReloadSubCmd.java diff --git a/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/subcommands/VersionSubCmd.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/subcommands/VersionSubCmd.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/subcommands/VersionSubCmd.java rename to VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/commands/villageroptimizer/subcommands/VersionSubCmd.java diff --git a/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/config/Config.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/config/Config.java new file mode 100644 index 0000000..b0532ac --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/config/Config.java @@ -0,0 +1,147 @@ +package me.xginko.villageroptimizer.config; + +import io.github.thatsmusic99.configurationmaster.api.ConfigFile; +import io.github.thatsmusic99.configurationmaster.api.ConfigSection; +import me.xginko.villageroptimizer.VillagerOptimizer; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class Config { + + private final @NotNull ConfigFile config; + public final @NotNull Locale default_lang; + public final boolean auto_lang; + public final long cache_keep_time_seconds; + + public Config() throws Exception { + this.config = loadConfig(new File(VillagerOptimizer.getInstance().getDataFolder(), "config.yml")); + structureConfig(); + this.default_lang = Locale.forLanguageTag( + getString("general.default-language", "en_us", + "The default language that will be used if auto-language is false or no matching language file was found.") + .replace("_", "-")); + this.auto_lang = getBoolean("general.auto-language", true, + "If set to true, will display messages based on client language"); + this.cache_keep_time_seconds = getInt("general.cache-keep-time-seconds", 30, + "The amount of time in seconds a villager will be kept in the plugin's cache."); + } + + private ConfigFile loadConfig(File ymlFile) throws Exception { + File parent = new File(ymlFile.getParent()); + if (!parent.exists() && !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("General", "general"); + createTitledSection("Optimization", "optimization-methods"); + config.addDefault("optimization-methods.commands.unoptimizevillagers", null); + config.addComment("optimization-methods.commands", """ + If you want to disable commands, negate the following permissions:\s + villageroptimizer.cmd.optimize\s + villageroptimizer.cmd.unoptimize + """); + config.addDefault("optimization-methods.nametag-optimization.enable", true); + createTitledSection("Villager Chunk Limit", "villager-chunk-limit"); + createTitledSection("Gameplay", "gameplay"); + config.addDefault("gameplay.villagers-spawn-as-adults.enable", false); + config.addDefault("gameplay.prevent-trading-with-unoptimized.enable", false); + config.addDefault("gameplay.villager-leveling.enable", true); + config.addDefault("gameplay.trade-restocking.enable", true); + config.addDefault("gameplay.prevent-targeting.enable", true); + config.addDefault("gameplay.prevent-damage.enable", true); + } + + public void createTitledSection(@NotNull String title, @NotNull String path) { + config.addSection(title); + config.addDefault(path, null); + } + + public @NotNull ConfigFile master() { + return config; + } + + public boolean getBoolean(@NotNull String path, boolean def, @NotNull String comment) { + config.addDefault(path, def, comment); + return config.getBoolean(path, def); + } + + public boolean getBoolean(@NotNull String path, boolean def) { + config.addDefault(path, def); + return config.getBoolean(path, def); + } + + public @NotNull String getString(@NotNull String path, @NotNull String def, @NotNull String comment) { + config.addDefault(path, def, comment); + return config.getString(path, def); + } + + public @NotNull String getString(@NotNull String path, @NotNull String def) { + config.addDefault(path, def); + return config.getString(path, def); + } + + public double getDouble(@NotNull String path, @NotNull Double def, @NotNull String comment) { + config.addDefault(path, def, comment); + return config.getDouble(path, def); + } + + public double getDouble(@NotNull String path, @NotNull Double def) { + config.addDefault(path, def); + return config.getDouble(path, def); + } + + public int getInt(@NotNull String path, int def, @NotNull String comment) { + config.addDefault(path, def, comment); + return config.getInteger(path, def); + } + + public int getInt(@NotNull String path, int def) { + config.addDefault(path, def); + return config.getInteger(path, def); + } + + public @NotNull List getList(@NotNull String path, @NotNull List def, @NotNull String comment) { + config.addDefault(path, def, comment); + return config.getStringList(path); + } + + public @NotNull List getList(@NotNull String path, @NotNull List def) { + config.addDefault(path, def); + return config.getStringList(path); + } + + public @NotNull ConfigSection getConfigSection(@NotNull String path, @NotNull Map defaultKeyValue) { + config.addDefault(path, null); + config.makeSectionLenient(path); + defaultKeyValue.forEach((string, object) -> config.addExample(path+"."+string, object)); + return config.getConfigSection(path); + } + + public @NotNull ConfigSection getConfigSection(@NotNull String path, @NotNull Map defaultKeyValue, @NotNull String comment) { + config.addDefault(path, null, comment); + config.makeSectionLenient(path); + defaultKeyValue.forEach((string, object) -> config.addExample(path+"."+string, object)); + return config.getConfigSection(path); + } + + public void addComment(@NotNull String path, @NotNull String comment) { + config.addComment(path, comment); + } +} diff --git a/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/config/LanguageCache.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/config/LanguageCache.java new file mode 100644 index 0000000..962b4c0 --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/config/LanguageCache.java @@ -0,0 +1,115 @@ +package me.xginko.villageroptimizer.config; + +import io.github.thatsmusic99.configurationmaster.api.ConfigFile; +import me.xginko.villageroptimizer.VillagerOptimizer; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.List; + +public class LanguageCache { + + private final @NotNull ConfigFile lang; + private final @NotNull MiniMessage miniMessage; + + public final @NotNull Component no_permission; + public final @NotNull List nametag_optimize_success, nametag_on_optimize_cooldown, nametag_unoptimize_success, + block_optimize_success, block_on_optimize_cooldown, block_unoptimize_success, + workstation_optimize_success, workstation_on_optimize_cooldown, workstation_unoptimize_success, + command_optimize_success, command_radius_limit_exceed, command_optimize_fail, command_unoptimize_success, + command_specify_radius, command_radius_invalid, command_no_villagers_nearby, + trades_restocked, optimize_for_trading, villager_leveling_up; + + public LanguageCache(String lang) throws Exception { + this.lang = loadLang(new File(VillagerOptimizer.getInstance().getDataFolder() + File.separator + "lang", lang + ".yml")); + this.miniMessage = MiniMessage.miniMessage(); + + // General + this.no_permission = getTranslation("messages.no-permission", + "You don't have permission to use this command."); + this.trades_restocked = getListTranslation("messages.trades-restocked", + List.of("All trades have been restocked! Next restock in %time%")); + this.optimize_for_trading = getListTranslation("messages.optimize-to-trade", + List.of("You need to optimize this villager before you can trade with it.")); + this.villager_leveling_up = getListTranslation("messages.villager-leveling-up", + List.of("Villager is currently leveling up! You can use the villager again in %time%.")); + // Nametag + this.nametag_optimize_success = getListTranslation("messages.nametag.optimize-success", + List.of("Successfully optimized villager by using a nametag.")); + this.nametag_on_optimize_cooldown = getListTranslation("messages.nametag.optimize-on-cooldown", + List.of("You need to wait %time% until you can optimize this villager again.")); + this.nametag_unoptimize_success = getListTranslation("messages.nametag.unoptimize-success", + List.of("Successfully unoptimized villager by using a nametag.")); + // Block + this.block_optimize_success = getListTranslation("messages.block.optimize-success", + List.of("%villagertype% villager successfully optimized using block %blocktype%.")); + this.block_on_optimize_cooldown = getListTranslation("messages.block.optimize-on-cooldown", + List.of("You need to wait %time% until you can optimize this villager again.")); + this.block_unoptimize_success = getListTranslation("messages.block.unoptimize-success", + List.of("Successfully unoptimized %villagertype% villager by removing %blocktype%.")); + // Workstation + this.workstation_optimize_success = getListTranslation("messages.workstation.optimize-success", + List.of("%villagertype% villager successfully optimized using workstation %workstation%.")); + this.workstation_on_optimize_cooldown = getListTranslation("messages.workstation.optimize-on-cooldown", + List.of("You need to wait %time% until you can optimize this villager again.")); + this.workstation_unoptimize_success = getListTranslation("messages.workstation.unoptimize-success", + List.of("Successfully unoptimized %villagertype% villager by removing workstation block %workstation%.")); + // Command + this.command_optimize_success = getListTranslation("messages.command.optimize-success", + List.of("Successfully optimized %amount% villager(s) in a radius of %radius% blocks.")); + this.command_radius_limit_exceed = getListTranslation("messages.command.radius-limit-exceed", + List.of("The radius you entered exceeds the limit of %distance% blocks.")); + this.command_optimize_fail = getListTranslation("messages.command.optimize-fail", + List.of("%amount% villagers couldn't be optimized because they have recently been optimized.")); + this.command_unoptimize_success = getListTranslation("messages.command.unoptimize-success", + List.of("Successfully unoptimized %amount% villager(s) in a radius of %radius% blocks.")); + this.command_specify_radius = getListTranslation("messages.command.specify-radius", + List.of("Please specify a radius.")); + this.command_radius_invalid = getListTranslation("messages.command.radius-invalid", + List.of("The radius you entered is not a valid number. Try again.")); + this.command_no_villagers_nearby = getListTranslation("messages.command.no-villagers-nearby", + List.of("Couldn't find any employed villagers within a radius of %radius%.")); + + 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 @NotNull Component getTranslation(@NotNull String path, @NotNull String defaultTranslation) { + lang.addDefault(path, defaultTranslation); + return miniMessage.deserialize(lang.getString(path, defaultTranslation)); + } + + public @NotNull Component getTranslation(@NotNull String path, @NotNull String defaultTranslation, @NotNull String comment) { + lang.addDefault(path, defaultTranslation, comment); + return miniMessage.deserialize(lang.getString(path, defaultTranslation)); + } + + public @NotNull List getListTranslation(@NotNull String path, @NotNull List defaultTranslation) { + lang.addDefault(path, defaultTranslation); + return lang.getStringList(path).stream().map(miniMessage::deserialize).toList(); + } + + public @NotNull List getListTranslation(@NotNull String path, @NotNull List defaultTranslation, @NotNull String comment) { + lang.addDefault(path, defaultTranslation, comment); + return lang.getStringList(path).stream().map(miniMessage::deserialize).toList(); + } +} diff --git a/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/enums/Keys.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/enums/Keys.java new file mode 100644 index 0000000..4c75d96 --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/enums/Keys.java @@ -0,0 +1,24 @@ +package me.xginko.villageroptimizer.enums; + +import me.xginko.villageroptimizer.VillagerOptimizer; +import org.bukkit.NamespacedKey; + +public enum Keys { + + OPTIMIZATION_TYPE(VillagerOptimizer.getKey("optimization-type")), + LAST_OPTIMIZE(VillagerOptimizer.getKey("last-optimize")), + LAST_LEVELUP(VillagerOptimizer.getKey("last-levelup")), + LAST_RESTOCK(VillagerOptimizer.getKey("last-restock")), + LAST_OPTIMIZE_NAME(VillagerOptimizer.getKey("last-optimize-name")); + + private final NamespacedKey key; + + Keys(NamespacedKey key) { + this.key = key; + } + + public NamespacedKey key() { + return key; + } + +} diff --git a/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/enums/OptimizationType.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/enums/OptimizationType.java new file mode 100644 index 0000000..09c304b --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/enums/OptimizationType.java @@ -0,0 +1,11 @@ +package me.xginko.villageroptimizer.enums; + +public enum OptimizationType { + + COMMAND, + NAMETAG, + WORKSTATION, + BLOCK, + NONE + +} diff --git a/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/enums/Permissions.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/enums/Permissions.java new file mode 100644 index 0000000..aec2615 --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/enums/Permissions.java @@ -0,0 +1,44 @@ +package me.xginko.villageroptimizer.enums; + +public class Permissions { + public enum Commands { + VERSION("villageroptimizer.cmd.version"), + RELOAD("villageroptimizer.cmd.reload"), + OPTIMIZE_RADIUS("villageroptimizer.cmd.optimize"), + UNOPTIMIZE_RADIUS("villageroptimizer.cmd.unoptimize"); + private final String key; + Commands(String key) { + this.key = key; + } + public String get() { + return key; + } + } + public enum Optimize { + NAMETAG("villageroptimizer.optimize.nametag"), + BLOCK("villageroptimizer.optimize.block"), + WORKSTATION("villageroptimizer.optimize.workstation"); + private final String key; + Optimize(String key) { + this.key = key; + } + public String get() { + return key; + } + } + public enum Bypass { + TRADE_PREVENTION("villageroptimizer.bypass.tradeprevention"), + RESTOCK_COOLDOWN("villageroptimizer.bypass.restockcooldown"), + NAMETAG_COOLDOWN("villageroptimizer.bypass.nametagcooldown"), + BLOCK_COOLDOWN("villageroptimizer.bypass.blockcooldown"), + WORKSTATION_COOLDOWN("villageroptimizer.bypass.workstationcooldown"), + COMMAND_COOLDOWN("villageroptimizer.bypass.commandcooldown"); + private final String key; + Bypass(String key) { + this.key = key; + } + public String get() { + return key; + } + } +} diff --git a/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/events/VillagerOptimizeEvent.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/events/VillagerOptimizeEvent.java new file mode 100644 index 0000000..2dc79e9 --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/events/VillagerOptimizeEvent.java @@ -0,0 +1,70 @@ +package me.xginko.villageroptimizer.events; + +import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.enums.OptimizationType; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +public class VillagerOptimizeEvent extends Event implements Cancellable { + + private static final @NotNull HandlerList handlers = new HandlerList(); + private final @NotNull WrappedVillager wrappedVillager; + private @NotNull OptimizationType type; + private boolean isCancelled = false; + + public VillagerOptimizeEvent(@NotNull WrappedVillager wrappedVillager, @NotNull OptimizationType type, boolean isAsync) throws IllegalArgumentException { + super(isAsync); + this.wrappedVillager = wrappedVillager; + if (type.equals(OptimizationType.NONE)) { + throw new IllegalArgumentException("OptimizationType can't be NONE."); + } else { + this.type = type; + } + } + + public VillagerOptimizeEvent(@NotNull WrappedVillager wrappedVillager, @NotNull OptimizationType type) throws IllegalArgumentException { + this.wrappedVillager = wrappedVillager; + if (type.equals(OptimizationType.NONE)) { + throw new IllegalArgumentException("OptimizationType can't be NONE."); + } else { + this.type = type; + } + } + + public @NotNull WrappedVillager getWrappedVillager() { + return wrappedVillager; + } + + public @NotNull OptimizationType getOptimizationType() { + return type; + } + + public void setOptimizationType(@NotNull OptimizationType type) throws IllegalArgumentException { + if (type.equals(OptimizationType.NONE)) { + throw new IllegalArgumentException("OptimizationType can't be NONE."); + } else { + this.type = type; + } + } + + @Override + public void setCancelled(boolean cancel) { + isCancelled = cancel; + } + + @Override + public boolean isCancelled() { + return isCancelled; + } + + @Override + public @NotNull HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/events/VillagerUnoptimizeEvent.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/events/VillagerUnoptimizeEvent.java new file mode 100644 index 0000000..41f1f9f --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/events/VillagerUnoptimizeEvent.java @@ -0,0 +1,46 @@ +package me.xginko.villageroptimizer.events; + +import me.xginko.villageroptimizer.WrappedVillager; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +public class VillagerUnoptimizeEvent extends Event implements Cancellable { + + private static final @NotNull HandlerList handlers = new HandlerList(); + private final @NotNull WrappedVillager wrappedVillager; + private boolean isCancelled = false; + + public VillagerUnoptimizeEvent(@NotNull WrappedVillager wrappedVillager, boolean isAsync) { + super(isAsync); + this.wrappedVillager = wrappedVillager; + } + + public VillagerUnoptimizeEvent(@NotNull WrappedVillager wrappedVillager) { + this.wrappedVillager = wrappedVillager; + } + + public @NotNull WrappedVillager getWrappedVillager() { + return wrappedVillager; + } + + @Override + public boolean isCancelled() { + return isCancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.isCancelled = cancel; + } + + @Override + public @NotNull HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/me/xginko/villageroptimizer/modules/RenameOptimizedVillagers.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/RenameOptimizedVillagers.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/modules/RenameOptimizedVillagers.java rename to VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/RenameOptimizedVillagers.java diff --git a/src/main/java/me/xginko/villageroptimizer/modules/VillagerChunkLimit.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/VillagerChunkLimit.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/modules/VillagerChunkLimit.java rename to VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/VillagerChunkLimit.java diff --git a/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java new file mode 100644 index 0000000..7f17ebe --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java @@ -0,0 +1,46 @@ +package me.xginko.villageroptimizer.modules; + +import me.xginko.villageroptimizer.modules.extras.PreventUnoptimizedTrading; +import me.xginko.villageroptimizer.modules.extras.PreventVillagerDamage; +import me.xginko.villageroptimizer.modules.extras.PreventVillagerTargetting; +import me.xginko.villageroptimizer.modules.extras.MakeVillagersSpawnAdult; +import me.xginko.villageroptimizer.modules.mechanics.LevelVillagers; +import me.xginko.villageroptimizer.modules.mechanics.RestockTrades; +import me.xginko.villageroptimizer.modules.optimizations.OptimizeByBlock; +import me.xginko.villageroptimizer.modules.optimizations.OptimizeByNametag; +import me.xginko.villageroptimizer.modules.optimizations.OptimizeByWorkstation; + +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.add(new OptimizeByNametag()); + modules.add(new OptimizeByBlock()); + modules.add(new OptimizeByWorkstation()); + + modules.add(new RestockTrades()); + modules.add(new LevelVillagers()); + + modules.add(new MakeVillagersSpawnAdult()); + modules.add(new PreventUnoptimizedTrading()); + modules.add(new PreventVillagerDamage()); + modules.add(new PreventVillagerTargetting()); + + modules.add(new VillagerChunkLimit()); + modules.add(new RenameOptimizedVillagers()); + + modules.forEach(module -> { + if (module.shouldEnable()) module.enable(); + }); + } +} diff --git a/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/extras/MakeVillagersSpawnAdult.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/extras/MakeVillagersSpawnAdult.java new file mode 100644 index 0000000..a536642 --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/extras/MakeVillagersSpawnAdult.java @@ -0,0 +1,44 @@ +package me.xginko.villageroptimizer.modules.extras; + +import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Villager; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.CreatureSpawnEvent; + +public class MakeVillagersSpawnAdult implements VillagerOptimizerModule, Listener { + + public MakeVillagersSpawnAdult() {} + + @Override + public void enable() { + VillagerOptimizer plugin = VillagerOptimizer.getInstance(); + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + @Override + public void disable() { + HandlerList.unregisterAll(this); + } + + @Override + public boolean shouldEnable() { + return VillagerOptimizer.getConfiguration().getBoolean("gameplay.villagers-spawn-as-adults.enable", false, """ + Spawned villagers will immediately be adults.\s + This is to save some more performance as players don't have to keep unoptimized\s + villagers loaded because they have to wait for them to turn into adults before they can\s + optimize them."""); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onVillagerSpawn(CreatureSpawnEvent event) { + if (event.getEntityType().equals(EntityType.VILLAGER)) { + Villager villager = (Villager) event.getEntity(); + if (!villager.isAdult()) villager.setAdult(); + } + } +} diff --git a/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/extras/PreventUnoptimizedTrading.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/extras/PreventUnoptimizedTrading.java new file mode 100644 index 0000000..54fb7ac --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/extras/PreventUnoptimizedTrading.java @@ -0,0 +1,80 @@ +package me.xginko.villageroptimizer.modules.extras; + +import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.VillagerCache; +import me.xginko.villageroptimizer.config.Config; +import me.xginko.villageroptimizer.enums.Permissions; +import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; +import org.bukkit.entity.Player; +import org.bukkit.entity.Villager; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.inventory.TradeSelectEvent; + +public class PreventUnoptimizedTrading implements VillagerOptimizerModule, Listener { + + private final VillagerCache villagerCache; + private final boolean notifyPlayer; + + public PreventUnoptimizedTrading() { + shouldEnable(); + this.villagerCache = VillagerOptimizer.getCache(); + Config config = VillagerOptimizer.getConfiguration(); + config.addComment("gameplay.prevent-trading-with-unoptimized.enable", """ + Will prevent players from selecting and using trades of unoptimized villagers.\s + Use this if you have a lot of villagers and therefore want to force your players to optimize them.\s + Inventories can still be opened so players can move villagers around."""); + this.notifyPlayer = config.getBoolean("gameplay.prevent-trading-with-unoptimized.notify-player", true, + "Sends players a message when they try to trade with an unoptimized villager."); + } + + @Override + public void enable() { + VillagerOptimizer plugin = VillagerOptimizer.getInstance(); + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + @Override + public void disable() { + HandlerList.unregisterAll(this); + } + + @Override + public boolean shouldEnable() { + return VillagerOptimizer.getConfiguration().getBoolean("gameplay.prevent-trading-with-unoptimized.enable", false); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + private void onTradeOpen(TradeSelectEvent event) { + Player player = (Player) event.getWhoClicked(); + if (player.hasPermission(Permissions.Bypass.TRADE_PREVENTION.get())) return; + if ( + event.getInventory().getType().equals(InventoryType.MERCHANT) + && event.getInventory().getHolder() instanceof Villager villager + && !villagerCache.getOrAdd(villager).isOptimized() + ) { + event.setCancelled(true); + if (notifyPlayer) + VillagerOptimizer.getLang(player.locale()).optimize_for_trading.forEach(player::sendMessage); + } + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + private void onInventoryClick(InventoryClickEvent event) { + Player player = (Player) event.getWhoClicked(); + if (player.hasPermission(Permissions.Bypass.TRADE_PREVENTION.get())) return; + if ( + event.getInventory().getType().equals(InventoryType.MERCHANT) + && event.getInventory().getHolder() instanceof Villager villager + && !villagerCache.getOrAdd(villager).isOptimized() + ) { + event.setCancelled(true); + if (notifyPlayer) + VillagerOptimizer.getLang(player.locale()).optimize_for_trading.forEach(player::sendMessage); + } + } +} diff --git a/src/main/java/me/xginko/villageroptimizer/modules/extras/PreventVillagerDamage.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/extras/PreventVillagerDamage.java similarity index 96% rename from src/main/java/me/xginko/villageroptimizer/modules/extras/PreventVillagerDamage.java rename to VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/extras/PreventVillagerDamage.java index 75dd232..9db543b 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/extras/PreventVillagerDamage.java +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/extras/PreventVillagerDamage.java @@ -56,7 +56,7 @@ public class PreventVillagerDamage implements VillagerOptimizerModule, Listener } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - private void onDamageReceive(EntityDamageByEntityEvent event) { + private void onDamageByEntity(EntityDamageByEntityEvent event) { if ( event.getEntityType().equals(EntityType.VILLAGER) && villagerCache.getOrAdd((Villager) event.getEntity()).isOptimized() @@ -79,7 +79,7 @@ public class PreventVillagerDamage implements VillagerOptimizerModule, Listener } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - private void onDamageReceive(EntityDamageByBlockEvent event) { + private void onDamageByBlock(EntityDamageByBlockEvent event) { if ( block && event.getEntityType().equals(EntityType.VILLAGER) diff --git a/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/extras/PreventVillagerTargetting.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/extras/PreventVillagerTargetting.java new file mode 100644 index 0000000..dc4e0f2 --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/extras/PreventVillagerTargetting.java @@ -0,0 +1,78 @@ +package me.xginko.villageroptimizer.modules.extras; + +import com.destroystokyo.paper.event.entity.EntityPathfindEvent; +import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.VillagerCache; +import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Mob; +import org.bukkit.entity.Villager; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityTargetLivingEntityEvent; + +public class PreventVillagerTargetting implements VillagerOptimizerModule, Listener { + + private final VillagerCache villagerCache; + + public PreventVillagerTargetting() { + this.villagerCache = VillagerOptimizer.getCache(); + } + + @Override + public void enable() { + VillagerOptimizer plugin = VillagerOptimizer.getInstance(); + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + @Override + public void disable() { + HandlerList.unregisterAll(this); + } + + @Override + public boolean shouldEnable() { + return VillagerOptimizer.getConfiguration().getBoolean("gameplay.prevent-targeting.enable", true, + "Prevents hostile entities from targeting optimized villagers."); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onTarget(EntityTargetLivingEntityEvent event) { + // Yes, instanceof checks would look way more beautiful here but checking type is much faster + Entity target = event.getTarget(); + if ( + target != null + && target.getType().equals(EntityType.VILLAGER) + && villagerCache.getOrAdd((Villager) target).isOptimized() + ) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onEntityTargetVillager(EntityPathfindEvent event) { + Entity target = event.getTargetEntity(); + if ( + target != null + && target.getType().equals(EntityType.VILLAGER) + && villagerCache.getOrAdd((Villager) target).isOptimized() + ) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onEntityAttackVillager(EntityDamageByEntityEvent event) { + if ( + event.getEntityType().equals(EntityType.VILLAGER) + && event.getDamager() instanceof Mob attacker + && villagerCache.getOrAdd((Villager) event.getEntity()).isOptimized() + ) { + attacker.setTarget(null); + } + } + } diff --git a/src/main/java/me/xginko/villageroptimizer/modules/mechanics/LevelVillagers.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/mechanics/LevelVillagers.java similarity index 98% rename from src/main/java/me/xginko/villageroptimizer/modules/mechanics/LevelVillagers.java rename to VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/mechanics/LevelVillagers.java index 28fda83..85c7217 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/mechanics/LevelVillagers.java +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/mechanics/LevelVillagers.java @@ -68,7 +68,7 @@ public class LevelVillagers implements VillagerOptimizerModule, Listener { if (wVillager.canLevelUp(cooldown)) { if (wVillager.calculateLevel() > villager.getVillagerLevel()) { villager.getScheduler().run(plugin, enableAI -> { - villager.addPotionEffect(new PotionEffect(PotionEffectType.SLOW, (int) (20 + (cooldown / 50L)), 120, false, false)); + villager.addPotionEffect(new PotionEffect(PotionEffectType.SLOW, 120, 120, false, false)); villager.setAware(true); }, null); villager.getScheduler().runDelayed(plugin, disableAI -> { diff --git a/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/mechanics/RestockTrades.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/mechanics/RestockTrades.java new file mode 100644 index 0000000..594db8f --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/mechanics/RestockTrades.java @@ -0,0 +1,80 @@ +package me.xginko.villageroptimizer.modules.mechanics; + +import me.xginko.villageroptimizer.VillagerOptimizer; +import me.xginko.villageroptimizer.VillagerCache; +import me.xginko.villageroptimizer.config.Config; +import me.xginko.villageroptimizer.enums.Permissions; +import me.xginko.villageroptimizer.WrappedVillager; +import me.xginko.villageroptimizer.modules.VillagerOptimizerModule; +import me.xginko.villageroptimizer.utils.CommonUtil; +import net.kyori.adventure.text.TextReplacementConfig; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.entity.Villager; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerInteractEntityEvent; + +public class RestockTrades implements VillagerOptimizerModule, Listener { + + private final VillagerCache villagerCache; + private final long restock_delay_millis; + private final boolean shouldLog, notifyPlayer; + + public RestockTrades() { + shouldEnable(); + this.villagerCache = VillagerOptimizer.getCache(); + Config config = VillagerOptimizer.getConfiguration(); + config.addComment("gameplay.trade-restocking.enable", """ + This is for automatic restocking of trades for optimized villagers. Optimized Villagers\s + Don't have enough AI to do trade restocks themselves, so this needs to always be enabled."""); + this.restock_delay_millis = config.getInt("gameplay.trade-restocking.delay-in-ticks", 1000, + "1 second = 20 ticks. There are 24.000 ticks in a single minecraft day.") * 50L; + this.notifyPlayer = config.getBoolean("gameplay.trade-restocking.notify-player", true, + "Sends the player a message when the trades were restocked on a clicked villager."); + this.shouldLog = config.getBoolean("gameplay.trade-restocking.log", false); + } + + @Override + public void enable() { + VillagerOptimizer plugin = VillagerOptimizer.getInstance(); + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + @Override + public void disable() { + HandlerList.unregisterAll(this); + } + + @Override + public boolean shouldEnable() { + return VillagerOptimizer.getConfiguration().getBoolean("gameplay.trade-restocking.enable", true); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + private void onInteract(PlayerInteractEntityEvent event) { + if (!event.getRightClicked().getType().equals(EntityType.VILLAGER)) return; + + WrappedVillager wVillager = villagerCache.getOrAdd((Villager) event.getRightClicked()); + if (!wVillager.isOptimized()) return; + Player player = event.getPlayer(); + + final boolean player_bypassing = player.hasPermission(Permissions.Bypass.RESTOCK_COOLDOWN.get()); + + if (wVillager.canRestock(restock_delay_millis) || player_bypassing) { + wVillager.restock(); + wVillager.saveRestockTime(); + if (notifyPlayer && !player_bypassing) { + final TextReplacementConfig timeLeft = TextReplacementConfig.builder() + .matchLiteral("%time%") + .replacement(CommonUtil.formatTime(wVillager.getRestockCooldownMillis(restock_delay_millis))) + .build(); + VillagerOptimizer.getLang(player.locale()).trades_restocked.forEach(line -> player.sendMessage(line.replaceText(timeLeft))); + } + if (shouldLog) + VillagerOptimizer.getLog().info("Restocked optimized villager at "+ wVillager.villager().getLocation()); + } + } +} diff --git a/src/main/java/me/xginko/villageroptimizer/modules/optimizations/OptimizeByBlock.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/optimizations/OptimizeByBlock.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/modules/optimizations/OptimizeByBlock.java rename to VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/optimizations/OptimizeByBlock.java diff --git a/src/main/java/me/xginko/villageroptimizer/modules/optimizations/OptimizeByNametag.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/optimizations/OptimizeByNametag.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/modules/optimizations/OptimizeByNametag.java rename to VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/optimizations/OptimizeByNametag.java diff --git a/src/main/java/me/xginko/villageroptimizer/modules/optimizations/OptimizeByWorkstation.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/optimizations/OptimizeByWorkstation.java similarity index 100% rename from src/main/java/me/xginko/villageroptimizer/modules/optimizations/OptimizeByWorkstation.java rename to VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/modules/optimizations/OptimizeByWorkstation.java diff --git a/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/utils/CommonUtil.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/utils/CommonUtil.java new file mode 100644 index 0000000..1a0e2f1 --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/utils/CommonUtil.java @@ -0,0 +1,24 @@ +package me.xginko.villageroptimizer.utils; + +import org.jetbrains.annotations.NotNull; + +import java.time.Duration; + +import static java.lang.String.format; + +public class CommonUtil { + public static @NotNull String formatTime(final long millis) { + Duration duration = Duration.ofMillis(millis); + final int seconds = duration.toSecondsPart(); + final int minutes = duration.toMinutesPart(); + final int hours = duration.toHoursPart(); + + if (hours > 0) { + return format("%02dh %02dm %02ds", hours, minutes, seconds); + } else if (minutes > 0) { + return format("%02dm %02ds", minutes, seconds); + } else { + return format("%02ds", seconds); + } + } +} diff --git a/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/utils/LogUtil.java b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/utils/LogUtil.java new file mode 100644 index 0000000..b0bd059 --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/java/me/xginko/villageroptimizer/utils/LogUtil.java @@ -0,0 +1,28 @@ +package me.xginko.villageroptimizer.utils; + +import me.xginko.villageroptimizer.VillagerOptimizer; + +import java.util.logging.Level; + +public class LogUtil { + + public static void moduleLog(Level logLevel, String path, String logMessage) { + VillagerOptimizer.getLog().log(logLevel, "(" + path + ") " + logMessage); + } + + public static void materialNotRecognized(String path, String material) { + moduleLog(Level.WARNING, path, "Material '" + material + "' not recognized. Please use correct 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/VillagerOptimizer-1.20.2/src/main/resources/lang/en_us.yml b/VillagerOptimizer-1.20.2/src/main/resources/lang/en_us.yml new file mode 100644 index 0000000..44e663f --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/resources/lang/en_us.yml @@ -0,0 +1,44 @@ +messages: + no-permission: "You don't have permission to use this command." + optimize-to-trade: + - "You need to optimize this villager before you can trade with it." + trades-restocked: + - "All trades have been restocked! Next restock in %time%." + villager-leveling-up: + - "Villager is currently leveling up! You can use the villager again in %time%" + nametag: + optimize-success: + - "Successfully optimized villager by using a nametag." + optimize-on-cooldown: + - "You need to wait %time% until you can optimize this villager again." + unoptimize-success: + - "Successfully unoptimized villager by using a nametag." + block: + optimize-success: + - "%vil_profession% villager successfully optimized using block %blocktype%." + optimize-on-cooldown: + - "You need to wait %time% until you can optimize this villager again." + unoptimize-success: + - "Successfully unoptimized %vil_profession% villager by removing %blocktype%." + workstation: + optimize-success: + - "%vil_profession% villager successfully optimized using workstation block %blocktype%." + optimize-on-cooldown: + - "You need to wait %time% until you can optimize this villager again." + unoptimize-success: + - "Successfully unoptimized villager by removing workstation block %blocktype%." + command: + optimize-success: + - "Successfully optimized %amount% villager(s) in a radius of %radius% blocks." + optimize-fail: + - "%amount% villagers couldn't be optimized because they have recently been optimized." + radius-limit-exceed: + - "The radius you entered exceeds the limit of %distance% blocks." + unoptimize-success: + - "Successfully unoptimized %amount% villager(s) in a radius of %radius% blocks." + specify-radius: + - "Please specify a radius." + radius-invalid: + - "The radius you entered is not a valid number. Try again." + no-villagers-nearby: + - "Couldn't find any employed villagers within a radius of %radius%." \ No newline at end of file diff --git a/VillagerOptimizer-1.20.2/src/main/resources/plugin.yml b/VillagerOptimizer-1.20.2/src/main/resources/plugin.yml new file mode 100644 index 0000000..d6221c3 --- /dev/null +++ b/VillagerOptimizer-1.20.2/src/main/resources/plugin.yml @@ -0,0 +1,93 @@ +name: VillagerOptimizer +version: '${project.version}' +main: me.xginko.villageroptimizer.VillagerOptimizer +authors: [ xGinko ] +description: ${project.description} +website: ${project.url} +api-version: '1.19' +folia-supported: true +commands: + villageroptimizer: + usage: /villageroptimizer [ reload, version ] + description: VillagerOptimizer admin commands + aliases: + - voptimizer + - vo + optimizevillagers: + usage: /optimizevillagers + description: Optmize villagers in a radius around you + aliases: + - optvils + unoptimizevillagers: + usage: /unoptimizevillagers + description: Unoptmize villagers in a radius around you + aliases: + - unoptvils +permissions: + villageroptimizer.ignore: + description: Players with this permission won't be able to use the plugin features + children: + villageroptimizer.optimize.nametag: false + villageroptimizer.optimize.block: false + villageroptimizer.optimize.workstation: false + villageroptimizer.playerdefaults: + description: Default permissions for players + default: true + children: + villageroptimizer.cmd.optimize: true + villageroptimizer.cmd.unoptimize: true + villageroptimizer.optimize.*: true + villageroptimizer.*: + description: All plugin permissions + children: + villageroptimizer.cmd.*: true + villageroptimizer.bypass.*: true + villageroptimizer.optimize.*: true + villageroptimizer.optimize.*: + description: Optimization type permissions + children: + villageroptimizer.optimize.nametag: true + villageroptimizer.optimize.block: true + villageroptimizer.optimize.workstation: true + villageroptimizer.optimize.nametag: + description: Optimize/Unoptimize villagers using nametags + villageroptimizer.optimize.block: + description: Optimize/Unoptimize villagers using specific blocks + villageroptimizer.optimize.workstation: + description: Optimize/Unoptimize villagers using workstations + villageroptimizer.cmd.*: + description: All command permissions + children: + villageroptimizer.cmd.reload: true + villageroptimizer.cmd.version: true + villageroptimizer.cmd.optimize: true + villageroptimizer.cmd.unoptimize: true + villageroptimizer.cmd.reload: + description: Reload the plugin configuration + villageroptimizer.cmd.version: + description: Show the plugin version + villageroptimizer.cmd.optimize: + description: Optimize villagers in a radius + villageroptimizer.cmd.unoptimize: + description: Unoptimize villagers in a radius + villageroptimizer.bypass.*: + description: All bypass permissions + children: + villageroptimizer.bypass.tradeprevention: true + villageroptimizer.bypass.restockcooldown: true + villageroptimizer.bypass.nametagcooldown: true + villageroptimizer.bypass.blockcooldown: true + villageroptimizer.bypass.workstationcooldown: true + villageroptimizer.bypass.commandcooldown: true + villageroptimizer.bypass.tradeprevention: + description: Bypass unoptimized trading prevention if enabled + villageroptimizer.bypass.restockcooldown: + description: Bypass permission for optimized trade restock cooldown + villageroptimizer.bypass.nametagcooldown: + description: Bypass permission for nametag optimization cooldown + villageroptimizer.bypass.blockcooldown: + description: Bypass permission for block optimization cooldown + villageroptimizer.bypass.workstationcooldown: + description: Bypass permission for workstation optimization cooldown + villageroptimizer.bypass.commandcooldown: + description: Bypass permission for command optimization cooldown \ No newline at end of file diff --git a/pom.xml b/pom.xml index 9826e44..72399eb 100644 --- a/pom.xml +++ b/pom.xml @@ -4,17 +4,20 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - me.xginko + me.xginko.VillagerOptimizer VillagerOptimizer 1.0.0 - jar + + VillagerOptimizer-1.20.2 + VillagerOptimizer-1.16.5 + + pom VillagerOptimizer Combat heavy villager lag by letting players optimize their trading halls. https://github.com/xGinko/VillagerOptimizer - 17 UTF-8 @@ -32,7 +35,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.5.0 + 3.5.1 package @@ -41,6 +44,7 @@ false + ${project.parent.artifactId}-${project.parent.version}--${project.artifactId} *:* @@ -65,12 +69,8 @@ - papermc-repo - https://repo.papermc.io/repository/maven-public/ - - - sonatype - https://oss.sonatype.org/content/groups/public/ + papermc + https://papermc.io/repo/repository/maven-public/ configmaster-repo @@ -79,18 +79,21 @@ + - io.papermc.paper - paper-api - 1.20.1-R0.1-SNAPSHOT - provided + org.bstats + bstats-bukkit + 3.0.2 + compile + com.github.thatsmusic99 ConfigurationMaster-API v2.0.0-rc.1 compile + com.github.ben-manes.caffeine caffeine