Compare commits
No commits in common. "master" and "1.0.1" have entirely different histories.
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@ -1 +0,0 @@
|
|||||||
ko_fi: xginko
|
|
26
.github/ISSUE_TEMPLATE/bug_report.md
vendored
26
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
name: 'Bug report about: Create a report to help us improve title: "[BUG]"'
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
name: 'Feature request about: Suggest an idea for this project title:'
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
22
.github/workflows/maven-publish.yml
vendored
22
.github/workflows/maven-publish.yml
vendored
@ -1,22 +0,0 @@
|
|||||||
name: Build and upload jar
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ main ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ main ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Set up JDK 8
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
distribution: 'temurin'
|
|
||||||
java-version: '8'
|
|
||||||
- name: Build with Maven
|
|
||||||
run: mvn -B package --file pom.xml
|
|
12
README.md
12
README.md
@ -12,13 +12,6 @@ There are 4 methods to do so:
|
|||||||
|
|
||||||
It aims to be highly customizable and performant. Offering a multilang system, which displays messages based on the player client's language setting as well as an optimize- and unoptimize event, so you may extend the plugin with your own custom solutions.
|
It aims to be highly customizable and performant. Offering a multilang system, which displays messages based on the player client's language setting as well as an optimize- and unoptimize event, so you may extend the plugin with your own custom solutions.
|
||||||
|
|
||||||
### Commands:
|
|
||||||
| Command | Aliases | Description |
|
|
||||||
|:---------------------------------------------:|:-------------------:|:------------------------------------------:|
|
|
||||||
| /villageroptimizer [reload, version, disable] | voptimizer, vo | VillagerOptimizer admin commands |
|
|
||||||
| /optimizevillagers <blockradius> | noai, optvils | Optmize villagers in a radius around you |
|
|
||||||
| /unoptimizevillagers <blockradius> | noaiundo, unoptvils | Unoptmize villagers in a radius around you |
|
|
||||||
|
|
||||||
Other features:
|
Other features:
|
||||||
- Prevent trading with unoptimized villagers to encourage players to optimize them
|
- Prevent trading with unoptimized villagers to encourage players to optimize them
|
||||||
- Smart villager chunk limit with configurable max numbers for optimized and unoptimized villagers and a villager profession based priorisation system (you can configure what kind of villagers should be deleted first, like for example nitwits or jobless villagers.)
|
- Smart villager chunk limit with configurable max numbers for optimized and unoptimized villagers and a villager profession based priorisation system (you can configure what kind of villagers should be deleted first, like for example nitwits or jobless villagers.)
|
||||||
@ -31,4 +24,7 @@ Other features:
|
|||||||
|
|
||||||
### Found a bug or got an idea for an enhancement? Open an issue or join the [discord](https://discord.com/invite/3UgsYf3qyc)!
|
### Found a bug or got an idea for an enhancement? Open an issue or join the [discord](https://discord.com/invite/3UgsYf3qyc)!
|
||||||
|
|
||||||
# Supports Paper/Folia 1.16 - 1.20.4
|
|
||||||
|
# Important:
|
||||||
|
### The 1.16 version is meant to be used on Paper servers 1.16-1.19.4
|
||||||
|
### The 1.20.2 version is meant to be used on Paper 1.20+ and Folia 1.19.4+ servers
|
||||||
|
99
VillagerOptimizer-1.16.5/pom.xml
Normal file
99
VillagerOptimizer-1.16.5/pom.xml
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>me.xginko.VillagerOptimizer</groupId>
|
||||||
|
<artifactId>VillagerOptimizer</artifactId>
|
||||||
|
<version>1.0.1</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>1.16.5</artifactId>
|
||||||
|
<name>${project.parent.artifactId}-${project.parent.version}--${project.artifactId}</name>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>16</java.version>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.11.0</version>
|
||||||
|
<configuration>
|
||||||
|
<source>${java.version}</source>
|
||||||
|
<target>${java.version}</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>3.5.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||||
|
<relocations>
|
||||||
|
<relocation>
|
||||||
|
<pattern>com.github.benmanes.caffeine</pattern>
|
||||||
|
<shadedPattern>me.xginko.villageroptimizer.caffeine</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
<relocation>
|
||||||
|
<pattern>org.bstats</pattern>
|
||||||
|
<shadedPattern>me.xginko.villageroptimizer.bstats</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
</relocations>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<filtering>true</filtering>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>papermc-repo</id>
|
||||||
|
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>sonatype</id>
|
||||||
|
<url>https://oss.sonatype.org/content/groups/public/</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.destroystokyo.paper</groupId>
|
||||||
|
<artifactId>paper-api</artifactId>
|
||||||
|
<version>1.16.5-R0.1-SNAPSHOT</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.kyori</groupId>
|
||||||
|
<artifactId>adventure-text-minimessage</artifactId>
|
||||||
|
<version>4.14.0</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.kyori</groupId>
|
||||||
|
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||||
|
<version>4.14.0</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
@ -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<UUID, WrappedVillager> villagerCache;
|
||||||
|
|
||||||
|
VillagerCache(long expireAfterWriteSeconds) {
|
||||||
|
this.villagerCache = Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(expireAfterWriteSeconds)).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull ConcurrentMap<UUID, WrappedVillager> cacheMap() {
|
||||||
|
return this.villagerCache.asMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable WrappedVillager get(@NotNull UUID uuid) {
|
||||||
|
WrappedVillager wrappedVillager = this.villagerCache.getIfPresent(uuid);
|
||||||
|
return wrappedVillager == null && Bukkit.getEntity(uuid) instanceof Villager villager ? this.add(villager) : wrappedVillager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull WrappedVillager getOrAdd(@NotNull Villager villager) {
|
||||||
|
WrappedVillager wrappedVillager = this.villagerCache.getIfPresent(villager.getUniqueId());
|
||||||
|
return wrappedVillager == null ? this.add(new WrappedVillager(villager)) : this.add(wrappedVillager);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull WrappedVillager add(@NotNull WrappedVillager villager) {
|
||||||
|
this.villagerCache.put(villager.villager().getUniqueId(), villager);
|
||||||
|
return villager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull WrappedVillager add(@NotNull Villager villager) {
|
||||||
|
return add(new WrappedVillager(villager));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(@NotNull UUID uuid) {
|
||||||
|
return this.villagerCache.getIfPresent(uuid) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(@NotNull WrappedVillager villager) {
|
||||||
|
return this.contains(villager.villager().getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(@NotNull Villager villager) {
|
||||||
|
return this.contains(villager.getUniqueId());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,173 @@
|
|||||||
|
package me.xginko.villageroptimizer;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand;
|
||||||
|
import me.xginko.villageroptimizer.config.Config;
|
||||||
|
import me.xginko.villageroptimizer.config.LanguageCache;
|
||||||
|
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import net.kyori.adventure.text.format.Style;
|
||||||
|
import net.kyori.adventure.text.format.TextColor;
|
||||||
|
import net.kyori.adventure.text.format.TextDecoration;
|
||||||
|
import org.bstats.bukkit.Metrics;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.ConsoleCommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.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<String, LanguageCache> 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));
|
||||||
|
new Metrics(this, 19954);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VillagerOptimizer getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
public static NamespacedKey getKey(String key) {
|
||||||
|
return new NamespacedKey(instance, key);
|
||||||
|
}
|
||||||
|
public static Config getConfiguration() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
public static VillagerCache getCache() {
|
||||||
|
return villagerCache;
|
||||||
|
}
|
||||||
|
public static 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 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("Found language file for " + 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("Found language file for " + 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<String> getDefaultLanguageFiles() {
|
||||||
|
Set<String> 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 files! - " + e.getLocalizedMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return languageFiles;
|
||||||
|
}
|
||||||
|
}
|
@ -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 = 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());
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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<VillagerOptimizerCommand> commands = new HashSet<>();
|
||||||
|
static void reloadCommands() {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
CommandMap commandMap = plugin.getServer().getCommandMap();
|
||||||
|
commands.forEach(command -> plugin.getCommand(command.label()).unregister(commandMap));
|
||||||
|
commands.clear();
|
||||||
|
|
||||||
|
commands.add(new VillagerOptimizerCmd());
|
||||||
|
commands.add(new OptVillagersRadius());
|
||||||
|
commands.add(new UnOptVillagersRadius());
|
||||||
|
|
||||||
|
commands.forEach(command -> plugin.getCommand(command.label()).setExecutor(command));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args);
|
||||||
|
}
|
@ -1,19 +1,20 @@
|
|||||||
package me.xginko.villageroptimizer.commands.optimizevillagers;
|
package me.xginko.villageroptimizer.commands.optimizevillagers;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerCache;
|
||||||
import me.xginko.villageroptimizer.VillagerOptimizer;
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.WrappedVillager;
|
||||||
import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand;
|
import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand;
|
||||||
import me.xginko.villageroptimizer.config.Config;
|
import me.xginko.villageroptimizer.config.Config;
|
||||||
import me.xginko.villageroptimizer.struct.enums.OptimizationType;
|
import me.xginko.villageroptimizer.enums.OptimizationType;
|
||||||
import me.xginko.villageroptimizer.struct.enums.Permissions;
|
import me.xginko.villageroptimizer.enums.Permissions;
|
||||||
import me.xginko.villageroptimizer.events.VillagerOptimizeEvent;
|
import me.xginko.villageroptimizer.events.VillagerOptimizeEvent;
|
||||||
import me.xginko.villageroptimizer.utils.KyoriUtil;
|
|
||||||
import me.xginko.villageroptimizer.wrapper.WrappedVillager;
|
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.TextReplacementConfig;
|
import net.kyori.adventure.text.TextReplacementConfig;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import net.kyori.adventure.text.format.TextDecoration;
|
import net.kyori.adventure.text.format.TextDecoration;
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.TabCompleter;
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.entity.EntityType;
|
import org.bukkit.entity.EntityType;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
@ -21,90 +22,79 @@ import org.bukkit.entity.Villager;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class OptVillagersRadius extends VillagerOptimizerCommand {
|
public class OptVillagersRadius implements VillagerOptimizerCommand, TabCompleter {
|
||||||
|
|
||||||
|
private final List<String> tabCompletes = List.of("5", "10", "25", "50");
|
||||||
private final long cooldown;
|
private final long cooldown;
|
||||||
private final int max_radius;
|
private final int max_radius;
|
||||||
|
|
||||||
public OptVillagersRadius() {
|
public OptVillagersRadius() {
|
||||||
super("optimizevillagers");
|
Config config = VillagerOptimizer.getConfiguration();
|
||||||
Config config = VillagerOptimizer.config();
|
|
||||||
this.max_radius = config.getInt("optimization-methods.commands.optimizevillagers.max-block-radius", 100);
|
this.max_radius = config.getInt("optimization-methods.commands.optimizevillagers.max-block-radius", 100);
|
||||||
this.cooldown = config.getInt("optimization-methods.commands.optimizevillagers.cooldown-seconds", 600,
|
this.cooldown = config.getInt("optimization-methods.commands.optimizevillagers.cooldown-seconds", 600, """
|
||||||
"Cooldown in seconds until a villager can be optimized again using the command.\n" +
|
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;
|
Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.""") * 1000L;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable List<String> onTabComplete(
|
public String label() {
|
||||||
@NotNull CommandSender sender, @NotNull Command command, @NotNull String commandLabel, @NotNull String[] args
|
return "optimizevillagers";
|
||||||
) {
|
|
||||||
return args.length == 1 ? RADIUS_SUGGESTIONS : Collections.emptyList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(
|
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
@NotNull CommandSender sender, @NotNull Command command, @NotNull String commandLabel, @NotNull String[] args
|
return args.length == 1 ? tabCompletes : null;
|
||||||
) {
|
|
||||||
if (!sender.hasPermission(Permissions.Commands.OPTIMIZE_RADIUS.get())) {
|
|
||||||
KyoriUtil.sendMessage(sender, VillagerOptimizer.getLang(sender).no_permission);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(sender instanceof Player)) {
|
@Override
|
||||||
KyoriUtil.sendMessage(sender, Component.text("This command can only be executed by a player.")
|
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));
|
.color(NamedTextColor.RED).decorate(TextDecoration.BOLD));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Player player = (Player) sender;
|
if (!sender.hasPermission(Permissions.Commands.OPTIMIZE_RADIUS.get())) {
|
||||||
|
sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (args.length != 1) {
|
if (args.length != 1) {
|
||||||
VillagerOptimizer.getLang(player.locale()).command_specify_radius
|
VillagerOptimizer.getLang(player.locale()).command_specify_radius.forEach(player::sendMessage);
|
||||||
.forEach(line -> KyoriUtil.sendMessage(sender, line));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final int specifiedRadius = Integer.parseInt(args[0]);
|
int specifiedRadius = Integer.parseInt(args[0]);
|
||||||
// Turn negative numbers into positive ones
|
|
||||||
final int safeRadius = (int) Math.sqrt(specifiedRadius * specifiedRadius);
|
|
||||||
|
|
||||||
if (safeRadius == 0) {
|
if (specifiedRadius > max_radius) {
|
||||||
VillagerOptimizer.getLang(player.locale()).command_radius_invalid
|
|
||||||
.forEach(line -> KyoriUtil.sendMessage(sender, line));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (safeRadius > max_radius) {
|
|
||||||
final TextReplacementConfig limit = TextReplacementConfig.builder()
|
final TextReplacementConfig limit = TextReplacementConfig.builder()
|
||||||
.matchLiteral("%distance%")
|
.matchLiteral("%distance%")
|
||||||
.replacement(Integer.toString(max_radius))
|
.replacement(Integer.toString(max_radius))
|
||||||
.build();
|
.build();
|
||||||
VillagerOptimizer.getLang(player.locale()).command_radius_limit_exceed
|
VillagerOptimizer.getLang(player.locale()).command_radius_limit_exceed.forEach(line -> player.sendMessage(line.replaceText(limit)));
|
||||||
.forEach(line -> KyoriUtil.sendMessage(player, line.replaceText(limit)));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VillagerCache villagerCache = VillagerOptimizer.getCache();
|
||||||
int successCount = 0;
|
int successCount = 0;
|
||||||
int failCount = 0;
|
int failCount = 0;
|
||||||
final boolean player_has_cooldown_bypass = player.hasPermission(Permissions.Bypass.COMMAND_COOLDOWN.get());
|
final boolean player_has_cooldown_bypass = player.hasPermission(Permissions.Bypass.COMMAND_COOLDOWN.get());
|
||||||
|
|
||||||
for (Entity entity : player.getNearbyEntities(safeRadius, safeRadius, safeRadius)) {
|
for (Entity entity : player.getNearbyEntities(specifiedRadius, specifiedRadius, specifiedRadius)) {
|
||||||
if (!entity.getType().equals(EntityType.VILLAGER)) continue;
|
if (!entity.getType().equals(EntityType.VILLAGER)) continue;
|
||||||
Villager villager = (Villager) entity;
|
Villager villager = (Villager) entity;
|
||||||
Villager.Profession profession = villager.getProfession();
|
Villager.Profession profession = villager.getProfession();
|
||||||
if (profession.equals(Villager.Profession.NITWIT) || profession.equals(Villager.Profession.NONE)) continue;
|
if (profession.equals(Villager.Profession.NITWIT) || profession.equals(Villager.Profession.NONE)) continue;
|
||||||
|
|
||||||
WrappedVillager wVillager = VillagerOptimizer.wrappers().get(villager, WrappedVillager::new);
|
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
|
||||||
|
|
||||||
if (player_has_cooldown_bypass || wVillager.canOptimize(cooldown)) {
|
if (player_has_cooldown_bypass || wVillager.canOptimize(cooldown)) {
|
||||||
VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(wVillager, OptimizationType.COMMAND, player);
|
VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(wVillager, OptimizationType.COMMAND, player);
|
||||||
if (optimizeEvent.callEvent()) {
|
if (optimizeEvent.callEvent()) {
|
||||||
wVillager.setOptimizationType(optimizeEvent.getOptimizationType());
|
wVillager.setOptimization(optimizeEvent.getOptimizationType());
|
||||||
wVillager.saveOptimizeTime();
|
wVillager.saveOptimizeTime();
|
||||||
successCount++;
|
successCount++;
|
||||||
}
|
}
|
||||||
@ -116,10 +106,9 @@ public class OptVillagersRadius extends VillagerOptimizerCommand {
|
|||||||
if (successCount <= 0 && failCount <= 0) {
|
if (successCount <= 0 && failCount <= 0) {
|
||||||
final TextReplacementConfig radius = TextReplacementConfig.builder()
|
final TextReplacementConfig radius = TextReplacementConfig.builder()
|
||||||
.matchLiteral("%radius%")
|
.matchLiteral("%radius%")
|
||||||
.replacement(Integer.toString(safeRadius))
|
.replacement(Integer.toString(specifiedRadius))
|
||||||
.build();
|
.build();
|
||||||
VillagerOptimizer.getLang(player.locale()).command_no_villagers_nearby
|
VillagerOptimizer.getLang(player.locale()).command_no_villagers_nearby.forEach(line -> player.sendMessage(line.replaceText(radius)));
|
||||||
.forEach(line -> KyoriUtil.sendMessage(player, line.replaceText(radius)));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,22 +119,22 @@ public class OptVillagersRadius extends VillagerOptimizerCommand {
|
|||||||
.build();
|
.build();
|
||||||
final TextReplacementConfig radius = TextReplacementConfig.builder()
|
final TextReplacementConfig radius = TextReplacementConfig.builder()
|
||||||
.matchLiteral("%radius%")
|
.matchLiteral("%radius%")
|
||||||
.replacement(Integer.toString(safeRadius))
|
.replacement(Integer.toString(specifiedRadius))
|
||||||
.build();
|
.build();
|
||||||
VillagerOptimizer.getLang(player.locale()).command_optimize_success
|
VillagerOptimizer.getLang(player.locale()).command_optimize_success.forEach(line -> player.sendMessage(line
|
||||||
.forEach(line -> KyoriUtil.sendMessage(player, line.replaceText(success_amount).replaceText(radius)));
|
.replaceText(success_amount)
|
||||||
|
.replaceText(radius)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
if (failCount > 0) {
|
if (failCount > 0) {
|
||||||
final TextReplacementConfig alreadyOptimized = TextReplacementConfig.builder()
|
final TextReplacementConfig alreadyOptimized = TextReplacementConfig.builder()
|
||||||
.matchLiteral("%amount%")
|
.matchLiteral("%amount%")
|
||||||
.replacement(Integer.toString(failCount))
|
.replacement(Integer.toString(failCount))
|
||||||
.build();
|
.build();
|
||||||
VillagerOptimizer.getLang(player.locale()).command_optimize_fail
|
VillagerOptimizer.getLang(player.locale()).command_optimize_fail.forEach(line -> player.sendMessage(line.replaceText(alreadyOptimized)));
|
||||||
.forEach(line -> KyoriUtil.sendMessage(player, line.replaceText(alreadyOptimized)));
|
|
||||||
}
|
}
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
VillagerOptimizer.getLang(player.locale()).command_radius_invalid
|
VillagerOptimizer.getLang(player.locale()).command_radius_invalid.forEach(player::sendMessage);
|
||||||
.forEach(line -> KyoriUtil.sendMessage(player, line));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
@ -1,18 +1,19 @@
|
|||||||
package me.xginko.villageroptimizer.commands.unoptimizevillagers;
|
package me.xginko.villageroptimizer.commands.unoptimizevillagers;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerCache;
|
||||||
import me.xginko.villageroptimizer.VillagerOptimizer;
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.WrappedVillager;
|
||||||
import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand;
|
import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand;
|
||||||
import me.xginko.villageroptimizer.struct.enums.OptimizationType;
|
import me.xginko.villageroptimizer.enums.OptimizationType;
|
||||||
import me.xginko.villageroptimizer.struct.enums.Permissions;
|
import me.xginko.villageroptimizer.enums.Permissions;
|
||||||
import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent;
|
import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent;
|
||||||
import me.xginko.villageroptimizer.utils.KyoriUtil;
|
|
||||||
import me.xginko.villageroptimizer.wrapper.WrappedVillager;
|
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.TextReplacementConfig;
|
import net.kyori.adventure.text.TextReplacementConfig;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import net.kyori.adventure.text.format.TextDecoration;
|
import net.kyori.adventure.text.format.TextDecoration;
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.TabCompleter;
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.entity.EntityType;
|
import org.bukkit.entity.EntityType;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
@ -20,84 +21,72 @@ import org.bukkit.entity.Villager;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class UnOptVillagersRadius extends VillagerOptimizerCommand {
|
public class UnOptVillagersRadius implements VillagerOptimizerCommand, TabCompleter {
|
||||||
|
|
||||||
|
private final List<String> tabCompletes = List.of("5", "10", "25", "50");
|
||||||
private final int max_radius;
|
private final int max_radius;
|
||||||
|
|
||||||
public UnOptVillagersRadius() {
|
public UnOptVillagersRadius() {
|
||||||
super("unoptimizevillagers");
|
this.max_radius = VillagerOptimizer.getConfiguration().getInt("optimization-methods.commands.unoptimizevillagers.max-block-radius", 100);
|
||||||
this.max_radius = VillagerOptimizer.config()
|
|
||||||
.getInt("optimization-methods.commands.unoptimizevillagers.max-block-radius", 100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable List<String> onTabComplete(
|
public String label() {
|
||||||
@NotNull CommandSender sender, @NotNull Command command, @NotNull String commandLabel, @NotNull String[] args
|
return "unoptimizevillagers";
|
||||||
) {
|
|
||||||
return args.length == 1 ? RADIUS_SUGGESTIONS : Collections.emptyList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(
|
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
@NotNull CommandSender sender, @NotNull Command command, @NotNull String commandLabel, @NotNull String[] args
|
return args.length == 1 ? tabCompletes : null;
|
||||||
) {
|
|
||||||
if (!sender.hasPermission(Permissions.Commands.UNOPTIMIZE_RADIUS.get())) {
|
|
||||||
KyoriUtil.sendMessage(sender, VillagerOptimizer.getLang(sender).no_permission);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(sender instanceof Player)) {
|
@Override
|
||||||
KyoriUtil.sendMessage(sender, Component.text("This command can only be executed by a player.")
|
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));
|
.color(NamedTextColor.RED).decorate(TextDecoration.BOLD));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Player player = (Player) sender;
|
if (!sender.hasPermission(Permissions.Commands.UNOPTIMIZE_RADIUS.get())) {
|
||||||
|
sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (args.length != 1) {
|
if (args.length != 1) {
|
||||||
VillagerOptimizer.getLang(player.locale()).command_specify_radius
|
VillagerOptimizer.getLang(player.locale()).command_specify_radius.forEach(player::sendMessage);
|
||||||
.forEach(line -> KyoriUtil.sendMessage(sender, line));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final int specifiedRadius = Integer.parseInt(args[0]);
|
int specifiedRadius = Integer.parseInt(args[0]);
|
||||||
// Turn negative numbers into positive ones
|
|
||||||
final int safeRadius = (int) Math.sqrt(specifiedRadius * specifiedRadius);
|
|
||||||
|
|
||||||
if (safeRadius == 0) {
|
if (specifiedRadius > max_radius) {
|
||||||
VillagerOptimizer.getLang(player.locale()).command_radius_invalid
|
|
||||||
.forEach(line -> KyoriUtil.sendMessage(sender, line));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (safeRadius > max_radius) {
|
|
||||||
final TextReplacementConfig limit = TextReplacementConfig.builder()
|
final TextReplacementConfig limit = TextReplacementConfig.builder()
|
||||||
.matchLiteral("%distance%")
|
.matchLiteral("%distance%")
|
||||||
.replacement(Integer.toString(max_radius))
|
.replacement(Integer.toString(max_radius))
|
||||||
.build();
|
.build();
|
||||||
VillagerOptimizer.getLang(player.locale()).command_radius_limit_exceed
|
VillagerOptimizer.getLang(player.locale()).command_radius_limit_exceed.forEach(line -> player.sendMessage(line.replaceText(limit)));
|
||||||
.forEach(line -> KyoriUtil.sendMessage(player, line.replaceText(limit)));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VillagerCache villagerCache = VillagerOptimizer.getCache();
|
||||||
int successCount = 0;
|
int successCount = 0;
|
||||||
|
|
||||||
for (Entity entity : player.getNearbyEntities(safeRadius, safeRadius, safeRadius)) {
|
for (Entity entity : player.getNearbyEntities(specifiedRadius, specifiedRadius, specifiedRadius)) {
|
||||||
if (!entity.getType().equals(EntityType.VILLAGER)) continue;
|
if (!entity.getType().equals(EntityType.VILLAGER)) continue;
|
||||||
Villager villager = (Villager) entity;
|
Villager villager = (Villager) entity;
|
||||||
Villager.Profession profession = villager.getProfession();
|
Villager.Profession profession = villager.getProfession();
|
||||||
if (profession.equals(Villager.Profession.NITWIT) || profession.equals(Villager.Profession.NONE)) continue;
|
if (profession.equals(Villager.Profession.NITWIT) || profession.equals(Villager.Profession.NONE)) continue;
|
||||||
|
|
||||||
WrappedVillager wVillager = VillagerOptimizer.wrappers().get(villager, WrappedVillager::new);
|
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
|
||||||
|
|
||||||
if (wVillager.isOptimized()) {
|
if (wVillager.isOptimized()) {
|
||||||
VillagerUnoptimizeEvent unOptimizeEvent = new VillagerUnoptimizeEvent(wVillager, player, OptimizationType.COMMAND);
|
VillagerUnoptimizeEvent unOptimizeEvent = new VillagerUnoptimizeEvent(wVillager, player, OptimizationType.COMMAND);
|
||||||
if (unOptimizeEvent.callEvent()) {
|
if (unOptimizeEvent.callEvent()) {
|
||||||
wVillager.setOptimizationType(OptimizationType.NONE);
|
wVillager.setOptimization(OptimizationType.NONE);
|
||||||
successCount++;
|
successCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,10 +95,9 @@ public class UnOptVillagersRadius extends VillagerOptimizerCommand {
|
|||||||
if (successCount <= 0) {
|
if (successCount <= 0) {
|
||||||
final TextReplacementConfig radius = TextReplacementConfig.builder()
|
final TextReplacementConfig radius = TextReplacementConfig.builder()
|
||||||
.matchLiteral("%radius%")
|
.matchLiteral("%radius%")
|
||||||
.replacement(Integer.toString(safeRadius))
|
.replacement(Integer.toString(specifiedRadius))
|
||||||
.build();
|
.build();
|
||||||
VillagerOptimizer.getLang(player.locale()).command_no_villagers_nearby
|
VillagerOptimizer.getLang(player.locale()).command_no_villagers_nearby.forEach(line -> player.sendMessage(line.replaceText(radius)));
|
||||||
.forEach(line -> KyoriUtil.sendMessage(player, line.replaceText(radius)));
|
|
||||||
} else {
|
} else {
|
||||||
final TextReplacementConfig success_amount = TextReplacementConfig.builder()
|
final TextReplacementConfig success_amount = TextReplacementConfig.builder()
|
||||||
.matchLiteral("%amount%")
|
.matchLiteral("%amount%")
|
||||||
@ -117,14 +105,15 @@ public class UnOptVillagersRadius extends VillagerOptimizerCommand {
|
|||||||
.build();
|
.build();
|
||||||
final TextReplacementConfig radius = TextReplacementConfig.builder()
|
final TextReplacementConfig radius = TextReplacementConfig.builder()
|
||||||
.matchLiteral("%radius%")
|
.matchLiteral("%radius%")
|
||||||
.replacement(Integer.toString(safeRadius))
|
.replacement(Integer.toString(specifiedRadius))
|
||||||
.build();
|
.build();
|
||||||
VillagerOptimizer.getLang(player.locale()).command_unoptimize_success
|
VillagerOptimizer.getLang(player.locale()).command_unoptimize_success.forEach(line -> player.sendMessage(line
|
||||||
.forEach(line -> KyoriUtil.sendMessage(player, line.replaceText(success_amount).replaceText(radius)));
|
.replaceText(success_amount)
|
||||||
|
.replaceText(radius)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
VillagerOptimizer.getLang(player.locale()).command_radius_invalid
|
VillagerOptimizer.getLang(player.locale()).command_radius_invalid.forEach(player::sendMessage);
|
||||||
.forEach(line -> KyoriUtil.sendMessage(player, line));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
@ -0,0 +1,79 @@
|
|||||||
|
package me.xginko.villageroptimizer.commands.villageroptimizer;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.commands.SubCommand;
|
||||||
|
import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand;
|
||||||
|
import me.xginko.villageroptimizer.commands.villageroptimizer.subcommands.DisableSubCmd;
|
||||||
|
import me.xginko.villageroptimizer.commands.villageroptimizer.subcommands.ReloadSubCmd;
|
||||||
|
import me.xginko.villageroptimizer.commands.villageroptimizer.subcommands.VersionSubCmd;
|
||||||
|
import me.xginko.villageroptimizer.enums.Permissions;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.TabCompleter;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class VillagerOptimizerCmd implements TabCompleter, VillagerOptimizerCommand {
|
||||||
|
|
||||||
|
private final List<SubCommand> subCommands = new ArrayList<>(3);
|
||||||
|
private final List<String> tabCompleter = new ArrayList<>(3);
|
||||||
|
|
||||||
|
public VillagerOptimizerCmd() {
|
||||||
|
subCommands.add(new ReloadSubCmd());
|
||||||
|
subCommands.add(new VersionSubCmd());
|
||||||
|
subCommands.add(new DisableSubCmd());
|
||||||
|
subCommands.forEach(subCommand -> tabCompleter.add(subCommand.getLabel()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String label() {
|
||||||
|
return "villageroptimizer";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
|
||||||
|
return args.length == 1 ? tabCompleter : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
|
||||||
|
if (args.length > 0) {
|
||||||
|
boolean cmdExists = false;
|
||||||
|
for (SubCommand subCommand : subCommands) {
|
||||||
|
if (args[0].equalsIgnoreCase(subCommand.getLabel())) {
|
||||||
|
subCommand.perform(sender, args);
|
||||||
|
cmdExists = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!cmdExists) sendCommandOverview(sender);
|
||||||
|
} else {
|
||||||
|
sendCommandOverview(sender);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendCommandOverview(CommandSender sender) {
|
||||||
|
if (!sender.hasPermission(Permissions.Commands.RELOAD.get()) && !sender.hasPermission(Permissions.Commands.VERSION.get())) return;
|
||||||
|
sender.sendMessage(Component.text("-----------------------------------------------------").color(NamedTextColor.GRAY));
|
||||||
|
sender.sendMessage(Component.text("VillagerOptimizer Commands").color(VillagerOptimizer.plugin_style.color()));
|
||||||
|
sender.sendMessage(Component.text("-----------------------------------------------------").color(NamedTextColor.GRAY));
|
||||||
|
subCommands.forEach(subCommand -> sender.sendMessage(
|
||||||
|
subCommand.getSyntax().append(Component.text(" - ").color(NamedTextColor.DARK_GRAY)).append(subCommand.getDescription())));
|
||||||
|
sender.sendMessage(
|
||||||
|
Component.text("/optimizevillagers <blockradius>").color(VillagerOptimizer.plugin_style.color())
|
||||||
|
.append(Component.text(" - ").color(NamedTextColor.DARK_GRAY))
|
||||||
|
.append(Component.text("Optimize villagers in a radius").color(NamedTextColor.GRAY))
|
||||||
|
);
|
||||||
|
sender.sendMessage(
|
||||||
|
Component.text("/unoptmizevillagers <blockradius>").color(VillagerOptimizer.plugin_style.color())
|
||||||
|
.append(Component.text(" - ").color(NamedTextColor.DARK_GRAY))
|
||||||
|
.append(Component.text("Unoptimize villagers in a radius").color(NamedTextColor.GRAY))
|
||||||
|
);
|
||||||
|
sender.sendMessage(Component.text("-----------------------------------------------------").color(NamedTextColor.GRAY));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package me.xginko.villageroptimizer.commands.villageroptimizer.subcommands;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.commands.SubCommand;
|
||||||
|
import me.xginko.villageroptimizer.enums.Permissions;
|
||||||
|
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.TextComponent;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.event.HandlerList;
|
||||||
|
|
||||||
|
public class DisableSubCmd extends SubCommand {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLabel() {
|
||||||
|
return "disable";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextComponent getDescription() {
|
||||||
|
return Component.text("Disable all plugin tasks and listeners.").color(NamedTextColor.GRAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextComponent getSyntax() {
|
||||||
|
return Component.text("/villageroptimizer disable").color(VillagerOptimizer.plugin_style.color());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void perform(CommandSender sender, String[] args) {
|
||||||
|
if (sender.hasPermission(Permissions.Commands.DISABLE.get())) {
|
||||||
|
sender.sendMessage(Component.text("Disabling VillagerOptimizer...").color(NamedTextColor.RED));
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
HandlerList.unregisterAll(plugin);
|
||||||
|
plugin.getServer().getScheduler().cancelTasks(plugin);
|
||||||
|
VillagerOptimizerModule.modules.clear();
|
||||||
|
VillagerOptimizer.getCache().cacheMap().clear();
|
||||||
|
sender.sendMessage(Component.text("Disabled all plugin listeners and tasks.").color(NamedTextColor.GREEN));
|
||||||
|
sender.sendMessage(Component.text("You can enable the plugin again using the reload command.").color(NamedTextColor.YELLOW));
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,146 @@
|
|||||||
|
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 = ymlFile.getParentFile();
|
||||||
|
if (!parent.exists() && !parent.mkdir())
|
||||||
|
VillagerOptimizer.getLog().severe("Unable to create plugin config directory.");
|
||||||
|
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.restock-optimized-trades", null);
|
||||||
|
config.addDefault("gameplay.level-optimized-profession", null);
|
||||||
|
config.addDefault("gameplay.rename-optimized-villagers.enable", true);
|
||||||
|
config.addDefault("gameplay.villagers-spawn-as-adults.enable", false);
|
||||||
|
config.addDefault("gameplay.prevent-trading-with-unoptimized.enable", false);
|
||||||
|
config.addDefault("gameplay.prevent-entities-from-targeting-optimized.enable", true);
|
||||||
|
config.addDefault("gameplay.prevent-damage-to-optimized.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<String> getList(@NotNull String path, @NotNull List<String> def, @NotNull String comment) {
|
||||||
|
config.addDefault(path, def, comment);
|
||||||
|
return config.getStringList(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull List<String> getList(@NotNull String path, @NotNull List<String> def) {
|
||||||
|
config.addDefault(path, def);
|
||||||
|
return config.getStringList(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull ConfigSection getConfigSection(@NotNull String path, @NotNull Map<String, Object> 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<String, Object> 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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
package me.xginko.villageroptimizer.config;
|
||||||
|
|
||||||
|
import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class LanguageCache {
|
||||||
|
|
||||||
|
private final @NotNull ConfigFile lang;
|
||||||
|
|
||||||
|
public final @NotNull Component no_permission;
|
||||||
|
public final @NotNull List<Component> nametag_optimize_success, nametag_on_optimize_cooldown, nametag_unoptimize_success,
|
||||||
|
block_optimize_success, block_on_optimize_cooldown, block_unoptimize_success,
|
||||||
|
workstation_optimize_success, workstation_on_optimize_cooldown, workstation_unoptimize_success,
|
||||||
|
command_optimize_success, command_radius_limit_exceed, command_optimize_fail, command_unoptimize_success,
|
||||||
|
command_specify_radius, command_radius_invalid, command_no_villagers_nearby,
|
||||||
|
trades_restocked, optimize_for_trading, villager_leveling_up;
|
||||||
|
|
||||||
|
public LanguageCache(String locale) throws Exception {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
File langYML = new File(plugin.getDataFolder() + File.separator + "lang", locale + ".yml");
|
||||||
|
// Check if the lang folder has already been created
|
||||||
|
File parent = langYML.getParentFile();
|
||||||
|
if (!parent.exists() && !parent.mkdir())
|
||||||
|
VillagerOptimizer.getLog().severe("Unable to create lang directory.");
|
||||||
|
// Check if the file already exists and save the one from the plugins resources folder if it does not
|
||||||
|
if (!langYML.exists())
|
||||||
|
plugin.saveResource("lang/" + locale + ".yml", false);
|
||||||
|
// Finally load the lang file with configmaster
|
||||||
|
this.lang = ConfigFile.loadConfig(langYML);
|
||||||
|
|
||||||
|
// General
|
||||||
|
this.no_permission = getTranslation("messages.no-permission",
|
||||||
|
"<red>You don't have permission to use this command.");
|
||||||
|
this.trades_restocked = getListTranslation("messages.trades-restocked",
|
||||||
|
List.of("<green>All trades have been restocked! Next restock in %time%"));
|
||||||
|
this.optimize_for_trading = getListTranslation("messages.optimize-to-trade",
|
||||||
|
List.of("<red>You need to optimize this villager before you can trade with it."));
|
||||||
|
this.villager_leveling_up = getListTranslation("messages.villager-leveling-up",
|
||||||
|
List.of("<yellow>Villager is currently leveling up! You can use the villager again in %time%."));
|
||||||
|
// Nametag
|
||||||
|
this.nametag_optimize_success = getListTranslation("messages.nametag.optimize-success",
|
||||||
|
List.of("<green>Successfully optimized villager by using a nametag."));
|
||||||
|
this.nametag_on_optimize_cooldown = getListTranslation("messages.nametag.optimize-on-cooldown",
|
||||||
|
List.of("<gray>You need to wait %time% until you can optimize this villager again."));
|
||||||
|
this.nametag_unoptimize_success = getListTranslation("messages.nametag.unoptimize-success",
|
||||||
|
List.of("<green>Successfully unoptimized villager by using a nametag."));
|
||||||
|
// Block
|
||||||
|
this.block_optimize_success = getListTranslation("messages.block.optimize-success",
|
||||||
|
List.of("<green>%villagertype% villager successfully optimized using block %blocktype%."));
|
||||||
|
this.block_on_optimize_cooldown = getListTranslation("messages.block.optimize-on-cooldown",
|
||||||
|
List.of("<gray>You need to wait %time% until you can optimize this villager again."));
|
||||||
|
this.block_unoptimize_success = getListTranslation("messages.block.unoptimize-success",
|
||||||
|
List.of("<green>Successfully unoptimized %villagertype% villager by removing %blocktype%."));
|
||||||
|
// Workstation
|
||||||
|
this.workstation_optimize_success = getListTranslation("messages.workstation.optimize-success",
|
||||||
|
List.of("<green>%villagertype% villager successfully optimized using workstation %workstation%."));
|
||||||
|
this.workstation_on_optimize_cooldown = getListTranslation("messages.workstation.optimize-on-cooldown",
|
||||||
|
List.of("<gray>You need to wait %time% until you can optimize this villager again."));
|
||||||
|
this.workstation_unoptimize_success = getListTranslation("messages.workstation.unoptimize-success",
|
||||||
|
List.of("<green>Successfully unoptimized %villagertype% villager by removing workstation block %workstation%."));
|
||||||
|
// Command
|
||||||
|
this.command_optimize_success = getListTranslation("messages.command.optimize-success",
|
||||||
|
List.of("<green>Successfully optimized %amount% villager(s) in a radius of %radius% blocks."));
|
||||||
|
this.command_radius_limit_exceed = getListTranslation("messages.command.radius-limit-exceed",
|
||||||
|
List.of("<red>The radius you entered exceeds the limit of %distance% blocks."));
|
||||||
|
this.command_optimize_fail = getListTranslation("messages.command.optimize-fail",
|
||||||
|
List.of("<gray>%amount% villagers couldn't be optimized because they have recently been optimized."));
|
||||||
|
this.command_unoptimize_success = getListTranslation("messages.command.unoptimize-success",
|
||||||
|
List.of("<green>Successfully unoptimized %amount% villager(s) in a radius of %radius% blocks."));
|
||||||
|
this.command_specify_radius = getListTranslation("messages.command.specify-radius",
|
||||||
|
List.of("<red>Please specify a radius."));
|
||||||
|
this.command_radius_invalid = getListTranslation("messages.command.radius-invalid",
|
||||||
|
List.of("<red>The radius you entered is not a valid number. Try again."));
|
||||||
|
this.command_no_villagers_nearby = getListTranslation("messages.command.no-villagers-nearby",
|
||||||
|
List.of("<gray>Couldn't find any employed villagers within a radius of %radius%."));
|
||||||
|
|
||||||
|
try {
|
||||||
|
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.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.miniMessage().deserialize(lang.getString(path, defaultTranslation));
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull List<Component> getListTranslation(@NotNull String path, @NotNull List<String> defaultTranslation) {
|
||||||
|
lang.addDefault(path, defaultTranslation);
|
||||||
|
return lang.getStringList(path).stream().map(MiniMessage.miniMessage()::deserialize).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull List<Component> getListTranslation(@NotNull String path, @NotNull List<String> defaultTranslation, @NotNull String comment) {
|
||||||
|
lang.addDefault(path, defaultTranslation, comment);
|
||||||
|
return lang.getStringList(path).stream().map(MiniMessage.miniMessage()::deserialize).toList();
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
package me.xginko.villageroptimizer.struct.enums;
|
package me.xginko.villageroptimizer.enums;
|
||||||
|
|
||||||
public enum OptimizationType {
|
public enum OptimizationType {
|
||||||
CHUNK_LIMIT,
|
|
||||||
REGIONAL_ACTIVITY,
|
|
||||||
COMMAND,
|
COMMAND,
|
||||||
NAMETAG,
|
NAMETAG,
|
||||||
WORKSTATION,
|
WORKSTATION,
|
||||||
BLOCK,
|
BLOCK,
|
||||||
NONE
|
NONE
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package me.xginko.villageroptimizer.enums;
|
||||||
|
|
||||||
|
public class Permissions {
|
||||||
|
public enum Commands {
|
||||||
|
VERSION("villageroptimizer.cmd.version"),
|
||||||
|
RELOAD("villageroptimizer.cmd.reload"),
|
||||||
|
DISABLE("villageroptimizer.cmd.disable"),
|
||||||
|
OPTIMIZE_RADIUS("villageroptimizer.cmd.optimize"),
|
||||||
|
UNOPTIMIZE_RADIUS("villageroptimizer.cmd.unoptimize");
|
||||||
|
private final String permission;
|
||||||
|
Commands(String permission) {
|
||||||
|
this.permission = permission;
|
||||||
|
}
|
||||||
|
public String get() {
|
||||||
|
return permission;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public enum Optimize {
|
||||||
|
NAMETAG("villageroptimizer.optimize.nametag"),
|
||||||
|
BLOCK("villageroptimizer.optimize.block"),
|
||||||
|
WORKSTATION("villageroptimizer.optimize.workstation");
|
||||||
|
private final String permission;
|
||||||
|
Optimize(String permission) {
|
||||||
|
this.permission = permission;
|
||||||
|
}
|
||||||
|
public String get() {
|
||||||
|
return permission;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public enum Bypass {
|
||||||
|
TRADE_PREVENTION("villageroptimizer.bypass.tradeprevention"),
|
||||||
|
RESTOCK_COOLDOWN("villageroptimizer.bypass.restockcooldown"),
|
||||||
|
NAMETAG_COOLDOWN("villageroptimizer.bypass.nametagcooldown"),
|
||||||
|
BLOCK_COOLDOWN("villageroptimizer.bypass.blockcooldown"),
|
||||||
|
WORKSTATION_COOLDOWN("villageroptimizer.bypass.workstationcooldown"),
|
||||||
|
COMMAND_COOLDOWN("villageroptimizer.bypass.commandcooldown");
|
||||||
|
private final String permission;
|
||||||
|
Bypass(String permission) {
|
||||||
|
this.permission = permission;
|
||||||
|
}
|
||||||
|
public String get() {
|
||||||
|
return permission;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
package me.xginko.villageroptimizer.events;
|
package me.xginko.villageroptimizer.events;
|
||||||
|
|
||||||
import me.xginko.villageroptimizer.wrapper.WrappedVillager;
|
import me.xginko.villageroptimizer.WrappedVillager;
|
||||||
import me.xginko.villageroptimizer.struct.enums.OptimizationType;
|
import me.xginko.villageroptimizer.enums.OptimizationType;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.Cancellable;
|
import org.bukkit.event.Cancellable;
|
||||||
import org.bukkit.event.Event;
|
import org.bukkit.event.Event;
|
||||||
@ -13,37 +13,28 @@ public class VillagerOptimizeEvent extends Event implements Cancellable {
|
|||||||
|
|
||||||
private static final @NotNull HandlerList handlers = new HandlerList();
|
private static final @NotNull HandlerList handlers = new HandlerList();
|
||||||
private final @NotNull WrappedVillager wrappedVillager;
|
private final @NotNull WrappedVillager wrappedVillager;
|
||||||
private @NotNull OptimizationType optimizationType;
|
private @NotNull OptimizationType type;
|
||||||
private final @Nullable Player whoOptimised;
|
private final @Nullable Player whoOptimised;
|
||||||
private boolean isCancelled = false;
|
private boolean isCancelled = false;
|
||||||
|
|
||||||
public VillagerOptimizeEvent(
|
public VillagerOptimizeEvent(@NotNull WrappedVillager wrappedVillager, @NotNull OptimizationType type, @Nullable Player whoOptimised, boolean isAsync) throws IllegalArgumentException {
|
||||||
@NotNull WrappedVillager wrappedVillager,
|
|
||||||
@NotNull OptimizationType optimizationType,
|
|
||||||
@Nullable Player whoOptimised,
|
|
||||||
boolean isAsync
|
|
||||||
) throws IllegalArgumentException {
|
|
||||||
super(isAsync);
|
super(isAsync);
|
||||||
this.wrappedVillager = wrappedVillager;
|
this.wrappedVillager = wrappedVillager;
|
||||||
this.whoOptimised = whoOptimised;
|
this.whoOptimised = whoOptimised;
|
||||||
if (optimizationType.equals(OptimizationType.NONE)) {
|
if (type.equals(OptimizationType.NONE)) {
|
||||||
throw new IllegalArgumentException("OptimizationType can't be NONE.");
|
throw new IllegalArgumentException("OptimizationType can't be NONE.");
|
||||||
} else {
|
} else {
|
||||||
this.optimizationType = optimizationType;
|
this.type = type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public VillagerOptimizeEvent(
|
public VillagerOptimizeEvent(@NotNull WrappedVillager wrappedVillager, @NotNull OptimizationType type, @Nullable Player whoOptimised) throws IllegalArgumentException {
|
||||||
@NotNull WrappedVillager wrappedVillager,
|
|
||||||
@NotNull OptimizationType optimizationType,
|
|
||||||
@Nullable Player whoOptimised
|
|
||||||
) throws IllegalArgumentException {
|
|
||||||
this.wrappedVillager = wrappedVillager;
|
this.wrappedVillager = wrappedVillager;
|
||||||
this.whoOptimised = whoOptimised;
|
this.whoOptimised = whoOptimised;
|
||||||
if (optimizationType.equals(OptimizationType.NONE)) {
|
if (type.equals(OptimizationType.NONE)) {
|
||||||
throw new IllegalArgumentException("OptimizationType can't be NONE.");
|
throw new IllegalArgumentException("OptimizationType can't be NONE.");
|
||||||
} else {
|
} else {
|
||||||
this.optimizationType = optimizationType;
|
this.type = type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,14 +43,14 @@ public class VillagerOptimizeEvent extends Event implements Cancellable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public @NotNull OptimizationType getOptimizationType() {
|
public @NotNull OptimizationType getOptimizationType() {
|
||||||
return optimizationType;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOptimizationType(@NotNull OptimizationType optimizationType) throws IllegalArgumentException {
|
public void setOptimizationType(@NotNull OptimizationType type) throws IllegalArgumentException {
|
||||||
if (optimizationType.equals(OptimizationType.NONE)) {
|
if (type.equals(OptimizationType.NONE)) {
|
||||||
throw new IllegalArgumentException("OptimizationType can't be NONE.");
|
throw new IllegalArgumentException("OptimizationType can't be NONE.");
|
||||||
} else {
|
} else {
|
||||||
this.optimizationType = optimizationType;
|
this.type = type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
package me.xginko.villageroptimizer.events;
|
package me.xginko.villageroptimizer.events;
|
||||||
|
|
||||||
import me.xginko.villageroptimizer.wrapper.WrappedVillager;
|
import me.xginko.villageroptimizer.WrappedVillager;
|
||||||
import me.xginko.villageroptimizer.struct.enums.OptimizationType;
|
import me.xginko.villageroptimizer.enums.OptimizationType;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.Cancellable;
|
import org.bukkit.event.Cancellable;
|
||||||
import org.bukkit.event.Event;
|
import org.bukkit.event.Event;
|
||||||
@ -13,30 +13,21 @@ public class VillagerUnoptimizeEvent extends Event implements Cancellable {
|
|||||||
|
|
||||||
private static final @NotNull HandlerList handlers = new HandlerList();
|
private static final @NotNull HandlerList handlers = new HandlerList();
|
||||||
private final @NotNull WrappedVillager wrappedVillager;
|
private final @NotNull WrappedVillager wrappedVillager;
|
||||||
private final @NotNull OptimizationType unOptimizeType;
|
private final @NotNull OptimizationType unoptimizeType;
|
||||||
private final @Nullable Player whoUnoptimized;
|
private final @Nullable Player whoUnoptimized;
|
||||||
private boolean isCancelled = false;
|
private boolean isCancelled = false;
|
||||||
|
|
||||||
public VillagerUnoptimizeEvent(
|
public VillagerUnoptimizeEvent(@NotNull WrappedVillager wrappedVillager, @Nullable Player whoUnoptimized, @NotNull OptimizationType unoptimizeType, boolean isAsync) {
|
||||||
@NotNull WrappedVillager wrappedVillager,
|
|
||||||
@Nullable Player whoUnoptimized,
|
|
||||||
@NotNull OptimizationType unOptimizeType,
|
|
||||||
boolean isAsync
|
|
||||||
) {
|
|
||||||
super(isAsync);
|
super(isAsync);
|
||||||
this.wrappedVillager = wrappedVillager;
|
this.wrappedVillager = wrappedVillager;
|
||||||
this.whoUnoptimized = whoUnoptimized;
|
this.whoUnoptimized = whoUnoptimized;
|
||||||
this.unOptimizeType = unOptimizeType;
|
this.unoptimizeType = unoptimizeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public VillagerUnoptimizeEvent(
|
public VillagerUnoptimizeEvent(@NotNull WrappedVillager wrappedVillager, @Nullable Player whoUnoptimized, @NotNull OptimizationType unoptimizeType) {
|
||||||
@NotNull WrappedVillager wrappedVillager,
|
|
||||||
@Nullable Player whoUnoptimized,
|
|
||||||
@NotNull OptimizationType unOptimizeType
|
|
||||||
) {
|
|
||||||
this.wrappedVillager = wrappedVillager;
|
this.wrappedVillager = wrappedVillager;
|
||||||
this.whoUnoptimized = whoUnoptimized;
|
this.whoUnoptimized = whoUnoptimized;
|
||||||
this.unOptimizeType = unOptimizeType;
|
this.unoptimizeType = unoptimizeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NotNull WrappedVillager getWrappedVillager() {
|
public @NotNull WrappedVillager getWrappedVillager() {
|
||||||
@ -48,7 +39,7 @@ public class VillagerUnoptimizeEvent extends Event implements Cancellable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public @NotNull OptimizationType getWhichTypeUnoptimized() {
|
public @NotNull OptimizationType getWhichTypeUnoptimized() {
|
||||||
return unOptimizeType;
|
return unoptimizeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
@ -0,0 +1,171 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerCache;
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.config.Config;
|
||||||
|
import me.xginko.villageroptimizer.utils.LogUtil;
|
||||||
|
import org.bukkit.Chunk;
|
||||||
|
import org.bukkit.Server;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||||
|
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public class VillagerChunkLimit implements VillagerOptimizerModule, Listener, Runnable {
|
||||||
|
|
||||||
|
private final Server server;
|
||||||
|
private final VillagerCache villagerCache;
|
||||||
|
private final List<Villager.Profession> non_optimized_removal_priority = new ArrayList<>(16);
|
||||||
|
private final List<Villager.Profession> optimized_removal_priority = new ArrayList<>(16);
|
||||||
|
private final long check_period;
|
||||||
|
private final int non_optimized_max_per_chunk, optimized_max_per_chunk;
|
||||||
|
private final boolean log_enabled;
|
||||||
|
|
||||||
|
protected VillagerChunkLimit() {
|
||||||
|
shouldEnable();
|
||||||
|
this.server = VillagerOptimizer.getInstance().getServer();
|
||||||
|
this.villagerCache = VillagerOptimizer.getCache();
|
||||||
|
Config config = VillagerOptimizer.getConfiguration();
|
||||||
|
config.addComment("villager-chunk-limit.enable", """
|
||||||
|
Checks chunks for too many villagers and removes excess villagers based on priority.""");
|
||||||
|
this.check_period = config.getInt("villager-chunk-limit.check-period-in-ticks", 600, """
|
||||||
|
Check all loaded chunks every X ticks. 1 second = 20 ticks\s
|
||||||
|
A shorter delay in between checks is more efficient but is also more resource intense.\s
|
||||||
|
A larger delay is less resource intense but could become inefficient.""");
|
||||||
|
this.log_enabled = config.getBoolean("villager-chunk-limit.log-removals", false);
|
||||||
|
this.non_optimized_max_per_chunk = config.getInt("villager-chunk-limit.unoptimized.max-per-chunk", 20,
|
||||||
|
"The maximum amount of unoptimized villagers per chunk.");
|
||||||
|
config.getList("villager-chunk-limit.unoptimized.removal-priority", List.of(
|
||||||
|
"NONE", "NITWIT", "SHEPHERD", "FISHERMAN", "BUTCHER", "CARTOGRAPHER", "LEATHERWORKER",
|
||||||
|
"FLETCHER", "MASON", "FARMER", "ARMORER", "TOOLSMITH", "WEAPONSMITH", "CLERIC", "LIBRARIAN"
|
||||||
|
), """
|
||||||
|
Professions that are in the top of the list are going to be scheduled for removal first.\s
|
||||||
|
Use enums from https://jd.papermc.io/paper/1.20/org/bukkit/entity/Villager.Profession.html"""
|
||||||
|
).forEach(configuredProfession -> {
|
||||||
|
try {
|
||||||
|
Villager.Profession profession = Villager.Profession.valueOf(configuredProfession);
|
||||||
|
this.non_optimized_removal_priority.add(profession);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
LogUtil.moduleLog(Level.WARNING, "villager-chunk-limit.unoptimized",
|
||||||
|
"Villager profession '"+configuredProfession+"' not recognized. " +
|
||||||
|
"Make sure you're using the correct profession enums from https://jd.papermc.io/paper/1.20/org/bukkit/entity/Villager.Profession.html.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.optimized_max_per_chunk = config.getInt("villager-chunk-limit.optimized.max-per-chunk", 60,
|
||||||
|
"The maximum amount of optimized villagers per chunk.");
|
||||||
|
config.getList("villager-chunk-limit.optimized.removal-priority", List.of(
|
||||||
|
"NONE", "NITWIT", "SHEPHERD", "FISHERMAN", "BUTCHER", "CARTOGRAPHER", "LEATHERWORKER",
|
||||||
|
"FLETCHER", "MASON", "FARMER", "ARMORER", "TOOLSMITH", "WEAPONSMITH", "CLERIC", "LIBRARIAN"
|
||||||
|
)).forEach(configuredProfession -> {
|
||||||
|
try {
|
||||||
|
Villager.Profession profession = Villager.Profession.valueOf(configuredProfession);
|
||||||
|
this.optimized_removal_priority.add(profession);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
LogUtil.moduleLog(Level.WARNING, "villager-chunk-limit.optimized",
|
||||||
|
"Villager profession '"+configuredProfession+"' not recognized. " +
|
||||||
|
"Make sure you're using the correct profession enums from https://jd.papermc.io/paper/1.20/org/bukkit/entity/Villager.Profession.html.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
server.getPluginManager().registerEvents(this, plugin);
|
||||||
|
server.getScheduler().scheduleSyncRepeatingTask(plugin, this, check_period, check_period);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnable() {
|
||||||
|
return VillagerOptimizer.getConfiguration().getBoolean("villager-chunk-limit.enable", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
for (World world : server.getWorlds()) {
|
||||||
|
for (Chunk chunk : world.getLoadedChunks()) {
|
||||||
|
this.manageVillagerCount(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
private void onCreatureSpawn(CreatureSpawnEvent event) {
|
||||||
|
Entity spawned = event.getEntity();
|
||||||
|
if (spawned.getType().equals(EntityType.VILLAGER)) {
|
||||||
|
this.manageVillagerCount(spawned.getChunk());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||||
|
private void onInteract(PlayerInteractEntityEvent event) {
|
||||||
|
Entity clicked = event.getRightClicked();
|
||||||
|
if (clicked.getType().equals(EntityType.VILLAGER)) {
|
||||||
|
this.manageVillagerCount(clicked.getChunk());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void manageVillagerCount(@NotNull Chunk chunk) {
|
||||||
|
// Collect all optimized and unoptimized villagers in that chunk
|
||||||
|
List<Villager> optimized_villagers = new ArrayList<>();
|
||||||
|
List<Villager> not_optimized_villagers = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Entity entity : chunk.getEntities()) {
|
||||||
|
if (entity.getType().equals(EntityType.VILLAGER)) {
|
||||||
|
Villager villager = (Villager) entity;
|
||||||
|
if (villagerCache.getOrAdd(villager).isOptimized()) {
|
||||||
|
optimized_villagers.add(villager);
|
||||||
|
} else {
|
||||||
|
not_optimized_villagers.add(villager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there are more unoptimized villagers in that chunk than allowed
|
||||||
|
final int not_optimized_villagers_too_many = not_optimized_villagers.size() - non_optimized_max_per_chunk;
|
||||||
|
if (not_optimized_villagers_too_many > 0) {
|
||||||
|
// Sort villagers by profession priority
|
||||||
|
not_optimized_villagers.sort(Comparator.comparingInt(villager -> {
|
||||||
|
final Villager.Profession profession = villager.getProfession();
|
||||||
|
return non_optimized_removal_priority.contains(profession) ? non_optimized_removal_priority.indexOf(profession) : Integer.MAX_VALUE;
|
||||||
|
}));
|
||||||
|
// Remove prioritized villagers that are too many
|
||||||
|
for (int i = 0; i < not_optimized_villagers_too_many; i++) {
|
||||||
|
Villager villager = not_optimized_villagers.get(i);
|
||||||
|
villager.remove();
|
||||||
|
if (log_enabled) LogUtil.moduleLog(Level.INFO, "villager-chunk-limit",
|
||||||
|
"Removed unoptimized villager of profession type '"+villager.getProfession().name()+"' at "+villager.getLocation()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there are more optimized villagers in that chunk than allowed
|
||||||
|
final int optimized_villagers_too_many = optimized_villagers.size() - optimized_max_per_chunk;
|
||||||
|
if (optimized_villagers_too_many > 0) {
|
||||||
|
// Sort villagers by profession priority
|
||||||
|
optimized_villagers.sort(Comparator.comparingInt(villager -> {
|
||||||
|
final Villager.Profession profession = villager.getProfession();
|
||||||
|
return optimized_removal_priority.contains(profession) ? optimized_removal_priority.indexOf(profession) : Integer.MAX_VALUE;
|
||||||
|
}));
|
||||||
|
// Remove prioritized villagers that are too many
|
||||||
|
for (int i = 0; i < optimized_villagers_too_many; i++) {
|
||||||
|
Villager villager = optimized_villagers.get(i);
|
||||||
|
villager.remove();
|
||||||
|
if (log_enabled) LogUtil.moduleLog(Level.INFO, "villager-chunk-limit",
|
||||||
|
"Removed optimized villager of profession type '"+villager.getProfession().name()+"' at "+villager.getLocation()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.modules.gameplay.*;
|
||||||
|
import me.xginko.villageroptimizer.modules.optimization.OptimizeByBlock;
|
||||||
|
import me.xginko.villageroptimizer.modules.optimization.OptimizeByNametag;
|
||||||
|
import me.xginko.villageroptimizer.modules.optimization.OptimizeByWorkstation;
|
||||||
|
import org.bukkit.event.HandlerList;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
public interface VillagerOptimizerModule {
|
||||||
|
|
||||||
|
void enable();
|
||||||
|
boolean shouldEnable();
|
||||||
|
|
||||||
|
HashSet<VillagerOptimizerModule> modules = new HashSet<>();
|
||||||
|
|
||||||
|
static void reloadModules() {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
HandlerList.unregisterAll(plugin);
|
||||||
|
plugin.getServer().getScheduler().cancelTasks(plugin);
|
||||||
|
modules.clear();
|
||||||
|
|
||||||
|
modules.add(new OptimizeByNametag());
|
||||||
|
modules.add(new OptimizeByBlock());
|
||||||
|
modules.add(new OptimizeByWorkstation());
|
||||||
|
|
||||||
|
modules.add(new RestockOptimizedTrades());
|
||||||
|
modules.add(new LevelOptimizedProfession());
|
||||||
|
modules.add(new RenameOptimizedVillagers());
|
||||||
|
modules.add(new MakeVillagersSpawnAdult());
|
||||||
|
modules.add(new PreventUnoptimizedTrading());
|
||||||
|
modules.add(new PreventOptimizedTargeting());
|
||||||
|
modules.add(new PreventOptimizedDamage());
|
||||||
|
|
||||||
|
modules.add(new VillagerChunkLimit());
|
||||||
|
|
||||||
|
modules.forEach(module -> {
|
||||||
|
if (module.shouldEnable()) module.enable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules.gameplay;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerCache;
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.WrappedVillager;
|
||||||
|
import me.xginko.villageroptimizer.config.Config;
|
||||||
|
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
||||||
|
import me.xginko.villageroptimizer.utils.CommonUtil;
|
||||||
|
import net.kyori.adventure.text.TextReplacementConfig;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||||
|
import org.bukkit.event.inventory.InventoryType;
|
||||||
|
import org.bukkit.potion.PotionEffect;
|
||||||
|
import org.bukkit.potion.PotionEffectType;
|
||||||
|
|
||||||
|
public class LevelOptimizedProfession implements VillagerOptimizerModule, Listener {
|
||||||
|
|
||||||
|
private final VillagerOptimizer plugin;
|
||||||
|
private final VillagerCache villagerCache;
|
||||||
|
private final boolean notify_player;
|
||||||
|
private final long cooldown;
|
||||||
|
|
||||||
|
public LevelOptimizedProfession() {
|
||||||
|
shouldEnable();
|
||||||
|
this.plugin = VillagerOptimizer.getInstance();
|
||||||
|
this.villagerCache = VillagerOptimizer.getCache();
|
||||||
|
Config config = VillagerOptimizer.getConfiguration();
|
||||||
|
config.addComment("gameplay.level-optimized-profession", """
|
||||||
|
This is needed to allow optimized villagers to level up.\s
|
||||||
|
Temporarily enables the villagers AI to allow it to level up and then disables it again.""");
|
||||||
|
this.cooldown = config.getInt("gameplay.level-optimized-profession.level-check-cooldown-seconds", 5, """
|
||||||
|
Cooldown in seconds until the level of a villager will be checked and updated again.\s
|
||||||
|
Recommended to leave as is.""") * 1000L;
|
||||||
|
this.notify_player = config.getBoolean("gameplay.level-optimized-profession.notify-player", true,
|
||||||
|
"Tell players to wait when a villager is leveling up.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||||
|
private void onTradeScreenClose(InventoryCloseEvent event) {
|
||||||
|
if (
|
||||||
|
event.getInventory().getType().equals(InventoryType.MERCHANT)
|
||||||
|
&& event.getInventory().getHolder() instanceof Villager villager
|
||||||
|
) {
|
||||||
|
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
|
||||||
|
if (!wVillager.isOptimized()) return;
|
||||||
|
|
||||||
|
if (wVillager.canLevelUp(cooldown)) {
|
||||||
|
if (wVillager.calculateLevel() > villager.getVillagerLevel()) {
|
||||||
|
villager.addPotionEffect(new PotionEffect(PotionEffectType.SLOW, 120, 120, false, false));
|
||||||
|
villager.setAware(true);
|
||||||
|
|
||||||
|
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
|
||||||
|
villager.setAware(false);
|
||||||
|
wVillager.saveLastLevelUp();
|
||||||
|
}, 100L);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (notify_player) {
|
||||||
|
Player player = (Player) event.getPlayer();
|
||||||
|
final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%time%")
|
||||||
|
.replacement(CommonUtil.formatTime(wVillager.getLevelCooldownMillis(cooldown)))
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).villager_leveling_up.forEach(line -> player.sendMessage(line.replaceText(timeLeft)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules.gameplay;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||||
|
|
||||||
|
public class MakeVillagersSpawnAdult implements VillagerOptimizerModule, Listener {
|
||||||
|
|
||||||
|
public MakeVillagersSpawnAdult() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnable() {
|
||||||
|
return VillagerOptimizer.getConfiguration().getBoolean("gameplay.villagers-spawn-as-adults.enable", false, """
|
||||||
|
Spawned villagers will immediately be adults.\s
|
||||||
|
This is to save some more resources as players don't have to keep unoptimized\s
|
||||||
|
villagers loaded because they have to wait for them to turn into adults before they can\s
|
||||||
|
optimize them.""");
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
private void onVillagerSpawn(CreatureSpawnEvent event) {
|
||||||
|
if (event.getEntityType().equals(EntityType.VILLAGER)) {
|
||||||
|
Villager villager = (Villager) event.getEntity();
|
||||||
|
if (!villager.isAdult()) villager.setAdult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules.gameplay;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerCache;
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.config.Config;
|
||||||
|
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
||||||
|
import me.xginko.villageroptimizer.utils.LogUtil;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.entity.EntityDamageEvent;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
public class PreventOptimizedDamage implements VillagerOptimizerModule, Listener {
|
||||||
|
|
||||||
|
private final VillagerCache villagerCache;
|
||||||
|
private final HashSet<EntityDamageEvent.DamageCause> damage_causes_to_cancel = new HashSet<>();
|
||||||
|
|
||||||
|
public PreventOptimizedDamage() {
|
||||||
|
shouldEnable();
|
||||||
|
this.villagerCache = VillagerOptimizer.getCache();
|
||||||
|
Config config = VillagerOptimizer.getConfiguration();
|
||||||
|
config.addComment("gameplay.prevent-damage-to-optimized.enable",
|
||||||
|
"Configure what kind of damage you want to cancel for optimized villagers here.");
|
||||||
|
config.getList("gameplay.prevent-damage-to-optimized.damage-causes-to-cancel",
|
||||||
|
Arrays.stream(EntityDamageEvent.DamageCause.values()).map(Enum::name).sorted().toList(), """
|
||||||
|
These are all current entries in the game. Remove what you do not need blocked.\s
|
||||||
|
If you want a description or need to add a previously removed type, refer to:\s
|
||||||
|
https://jd.papermc.io/paper/1.20/org/bukkit/event/entity/EntityDamageEvent.DamageCause.html"""
|
||||||
|
).forEach(configuredDamageCause -> {
|
||||||
|
try {
|
||||||
|
EntityDamageEvent.DamageCause damageCause = EntityDamageEvent.DamageCause.valueOf(configuredDamageCause);
|
||||||
|
this.damage_causes_to_cancel.add(damageCause);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
LogUtil.damageCauseNotRecognized("prevent-damage-to-optimized", configuredDamageCause);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnable() {
|
||||||
|
return VillagerOptimizer.getConfiguration().getBoolean("gameplay.prevent-damage-to-optimized.enable", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
private void onDamageByEntity(EntityDamageEvent event) {
|
||||||
|
if (
|
||||||
|
event.getEntityType().equals(EntityType.VILLAGER)
|
||||||
|
&& damage_causes_to_cancel.contains(event.getCause())
|
||||||
|
&& villagerCache.getOrAdd((Villager) event.getEntity()).isOptimized()
|
||||||
|
) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules.gameplay;
|
||||||
|
|
||||||
|
import com.destroystokyo.paper.event.entity.EntityPathfindEvent;
|
||||||
|
import me.xginko.villageroptimizer.VillagerCache;
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Mob;
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||||
|
import org.bukkit.event.entity.EntityTargetEvent;
|
||||||
|
|
||||||
|
public class PreventOptimizedTargeting implements VillagerOptimizerModule, Listener {
|
||||||
|
|
||||||
|
private final VillagerCache villagerCache;
|
||||||
|
|
||||||
|
public PreventOptimizedTargeting() {
|
||||||
|
this.villagerCache = VillagerOptimizer.getCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnable() {
|
||||||
|
return VillagerOptimizer.getConfiguration().getBoolean("gameplay.prevent-entities-from-targeting-optimized.enable", true,
|
||||||
|
"Prevents hostile entities from targeting optimized villagers.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
private void onTarget(EntityTargetEvent event) {
|
||||||
|
// Yes, instanceof checks would look way more beautiful here but checking type is much faster
|
||||||
|
Entity target = event.getTarget();
|
||||||
|
if (
|
||||||
|
target != null
|
||||||
|
&& target.getType().equals(EntityType.VILLAGER)
|
||||||
|
&& villagerCache.getOrAdd((Villager) target).isOptimized()
|
||||||
|
) {
|
||||||
|
event.setTarget(null);
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
private void onEntityTargetVillager(EntityPathfindEvent event) {
|
||||||
|
Entity target = event.getTargetEntity();
|
||||||
|
if (
|
||||||
|
target != null
|
||||||
|
&& target.getType().equals(EntityType.VILLAGER)
|
||||||
|
&& villagerCache.getOrAdd((Villager) target).isOptimized()
|
||||||
|
) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
private void onEntityAttackVillager(EntityDamageByEntityEvent event) {
|
||||||
|
if (
|
||||||
|
event.getEntityType().equals(EntityType.VILLAGER)
|
||||||
|
&& event.getDamager() instanceof Mob attacker
|
||||||
|
&& villagerCache.getOrAdd((Villager) event.getEntity()).isOptimized()
|
||||||
|
) {
|
||||||
|
attacker.setTarget(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules.gameplay;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerCache;
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.config.Config;
|
||||||
|
import me.xginko.villageroptimizer.enums.Permissions;
|
||||||
|
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||||
|
import org.bukkit.event.inventory.InventoryType;
|
||||||
|
import org.bukkit.event.inventory.TradeSelectEvent;
|
||||||
|
|
||||||
|
public class PreventUnoptimizedTrading implements VillagerOptimizerModule, Listener {
|
||||||
|
|
||||||
|
private final VillagerCache villagerCache;
|
||||||
|
private final boolean notify_player;
|
||||||
|
|
||||||
|
public PreventUnoptimizedTrading() {
|
||||||
|
shouldEnable();
|
||||||
|
this.villagerCache = VillagerOptimizer.getCache();
|
||||||
|
Config config = VillagerOptimizer.getConfiguration();
|
||||||
|
config.addComment("gameplay.prevent-trading-with-unoptimized.enable", """
|
||||||
|
Will prevent players from selecting and using trades of unoptimized villagers.\s
|
||||||
|
Use this if you have a lot of villagers and therefore want to force your players to optimize them.\s
|
||||||
|
Inventories can still be opened so players can move villagers around.""");
|
||||||
|
this.notify_player = config.getBoolean("gameplay.prevent-trading-with-unoptimized.notify-player", true,
|
||||||
|
"Sends players a message when they try to trade with an unoptimized villager.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnable() {
|
||||||
|
return VillagerOptimizer.getConfiguration().getBoolean("gameplay.prevent-trading-with-unoptimized.enable", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||||
|
private void onTradeOpen(TradeSelectEvent event) {
|
||||||
|
Player player = (Player) event.getWhoClicked();
|
||||||
|
if (player.hasPermission(Permissions.Bypass.TRADE_PREVENTION.get())) return;
|
||||||
|
if (
|
||||||
|
event.getInventory().getType().equals(InventoryType.MERCHANT)
|
||||||
|
&& event.getInventory().getHolder() instanceof Villager villager
|
||||||
|
&& !villagerCache.getOrAdd(villager).isOptimized()
|
||||||
|
) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
if (notify_player)
|
||||||
|
VillagerOptimizer.getLang(player.locale()).optimize_for_trading.forEach(player::sendMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||||
|
private void onInventoryClick(InventoryClickEvent event) {
|
||||||
|
Player player = (Player) event.getWhoClicked();
|
||||||
|
if (player.hasPermission(Permissions.Bypass.TRADE_PREVENTION.get())) return;
|
||||||
|
if (
|
||||||
|
event.getInventory().getType().equals(InventoryType.MERCHANT)
|
||||||
|
&& event.getInventory().getHolder() instanceof Villager villager
|
||||||
|
&& !villagerCache.getOrAdd(villager).isOptimized()
|
||||||
|
) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
if (notify_player)
|
||||||
|
VillagerOptimizer.getLang(player.locale()).optimize_for_trading.forEach(player::sendMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules.gameplay;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.WrappedVillager;
|
||||||
|
import me.xginko.villageroptimizer.config.Config;
|
||||||
|
import me.xginko.villageroptimizer.events.VillagerOptimizeEvent;
|
||||||
|
import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent;
|
||||||
|
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
|
||||||
|
public class RenameOptimizedVillagers implements VillagerOptimizerModule, Listener {
|
||||||
|
|
||||||
|
private final VillagerOptimizer plugin;
|
||||||
|
private final Component optimized_name;
|
||||||
|
private final boolean overwrite_previous_name;
|
||||||
|
|
||||||
|
public RenameOptimizedVillagers() {
|
||||||
|
shouldEnable();
|
||||||
|
this.plugin = VillagerOptimizer.getInstance();
|
||||||
|
Config config = VillagerOptimizer.getConfiguration();
|
||||||
|
config.addComment("gameplay.rename-optimized-villagers.enable", """
|
||||||
|
Will change a villager's name to the name configured below when they are optimized.\s
|
||||||
|
These names will be removed when unoptimized again if they were not changed in the meantime.
|
||||||
|
""");
|
||||||
|
this.optimized_name = MiniMessage.miniMessage().deserialize(config.getString("gameplay.rename-optimized-villagers.optimized-name", "<green>Optimized",
|
||||||
|
"The name that will be used to mark optimized villagers. Uses MiniMessage format."));
|
||||||
|
this.overwrite_previous_name = config.getBoolean("gameplay.rename-optimized-villagers.overwrite-existing-name", false,
|
||||||
|
"If set to true, will rename even if the villager has already been named.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnable() {
|
||||||
|
return VillagerOptimizer.getConfiguration().getBoolean("gameplay.rename-optimized-villagers.enable", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
private void onOptimize(VillagerOptimizeEvent event) {
|
||||||
|
WrappedVillager wVillager = event.getWrappedVillager();
|
||||||
|
Villager villager = wVillager.villager();
|
||||||
|
|
||||||
|
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
|
||||||
|
if (overwrite_previous_name || villager.customName() == null) {
|
||||||
|
villager.customName(optimized_name);
|
||||||
|
wVillager.memorizeName(optimized_name);
|
||||||
|
}
|
||||||
|
}, 10L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
private void onUnOptimize(VillagerUnoptimizeEvent event) {
|
||||||
|
WrappedVillager wVillager = event.getWrappedVillager();
|
||||||
|
Villager villager = wVillager.villager();
|
||||||
|
|
||||||
|
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
|
||||||
|
final Component currentName = villager.customName();
|
||||||
|
final Component memorizedName = wVillager.getMemorizedName();
|
||||||
|
if (currentName != null && currentName.equals(memorizedName))
|
||||||
|
villager.customName(null);
|
||||||
|
if (memorizedName != null)
|
||||||
|
wVillager.forgetName();
|
||||||
|
}, 10L);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules.gameplay;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerCache;
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.WrappedVillager;
|
||||||
|
import me.xginko.villageroptimizer.config.Config;
|
||||||
|
import me.xginko.villageroptimizer.enums.Permissions;
|
||||||
|
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
||||||
|
import me.xginko.villageroptimizer.utils.CommonUtil;
|
||||||
|
import net.kyori.adventure.text.TextReplacementConfig;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||||
|
|
||||||
|
public class RestockOptimizedTrades implements VillagerOptimizerModule, Listener {
|
||||||
|
|
||||||
|
private final VillagerCache villagerCache;
|
||||||
|
private final long restock_delay_millis;
|
||||||
|
private final boolean log_enabled, notify_player;
|
||||||
|
|
||||||
|
public RestockOptimizedTrades() {
|
||||||
|
shouldEnable();
|
||||||
|
this.villagerCache = VillagerOptimizer.getCache();
|
||||||
|
Config config = VillagerOptimizer.getConfiguration();
|
||||||
|
config.addComment("gameplay.restock-optimized-trades", """
|
||||||
|
This is for automatic restocking of trades for optimized villagers. Optimized Villagers\s
|
||||||
|
don't have enough AI to restock their trades naturally, so this is here as a workaround.""");
|
||||||
|
this.restock_delay_millis = config.getInt("gameplay.restock-optimized-trades.delay-in-ticks", 1000,
|
||||||
|
"1 second = 20 ticks. There are 24.000 ticks in a single minecraft day.") * 50L;
|
||||||
|
this.notify_player = config.getBoolean("gameplay.restock-optimized-trades.notify-player", true,
|
||||||
|
"Sends the player a message when the trades were restocked on a clicked villager.");
|
||||||
|
this.log_enabled = config.getBoolean("gameplay.restock-optimized-trades.log", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||||
|
private void onInteract(PlayerInteractEntityEvent event) {
|
||||||
|
if (!event.getRightClicked().getType().equals(EntityType.VILLAGER)) return;
|
||||||
|
|
||||||
|
WrappedVillager wVillager = villagerCache.getOrAdd((Villager) event.getRightClicked());
|
||||||
|
if (!wVillager.isOptimized()) return;
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
|
||||||
|
final boolean player_bypassing = player.hasPermission(Permissions.Bypass.RESTOCK_COOLDOWN.get());
|
||||||
|
|
||||||
|
if (wVillager.canRestock(restock_delay_millis) || player_bypassing) {
|
||||||
|
wVillager.restock();
|
||||||
|
wVillager.saveRestockTime();
|
||||||
|
if (notify_player && !player_bypassing) {
|
||||||
|
final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%time%")
|
||||||
|
.replacement(CommonUtil.formatTime(wVillager.getRestockCooldownMillis(restock_delay_millis)))
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).trades_restocked.forEach(line -> player.sendMessage(line.replaceText(timeLeft)));
|
||||||
|
}
|
||||||
|
if (log_enabled)
|
||||||
|
VillagerOptimizer.getLog().info("Restocked optimized villager at "+ wVillager.villager().getLocation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,193 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules.optimization;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerCache;
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.WrappedVillager;
|
||||||
|
import me.xginko.villageroptimizer.config.Config;
|
||||||
|
import me.xginko.villageroptimizer.enums.OptimizationType;
|
||||||
|
import me.xginko.villageroptimizer.enums.Permissions;
|
||||||
|
import me.xginko.villageroptimizer.events.VillagerOptimizeEvent;
|
||||||
|
import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent;
|
||||||
|
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
||||||
|
import me.xginko.villageroptimizer.utils.CommonUtil;
|
||||||
|
import me.xginko.villageroptimizer.utils.LogUtil;
|
||||||
|
import net.kyori.adventure.text.TextReplacementConfig;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.block.BlockBreakEvent;
|
||||||
|
import org.bukkit.event.block.BlockPlaceEvent;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class OptimizeByBlock implements VillagerOptimizerModule, Listener {
|
||||||
|
|
||||||
|
private final VillagerCache villagerCache;
|
||||||
|
private final HashSet<Material> blocks_that_disable = new HashSet<>(4);
|
||||||
|
private final long cooldown;
|
||||||
|
private final double search_radius;
|
||||||
|
private final boolean only_while_sneaking, notify_player, log_enabled;
|
||||||
|
|
||||||
|
public OptimizeByBlock() {
|
||||||
|
shouldEnable();
|
||||||
|
this.villagerCache = VillagerOptimizer.getCache();
|
||||||
|
Config config = VillagerOptimizer.getConfiguration();
|
||||||
|
config.addComment("optimization-methods.block-optimization.enable", """
|
||||||
|
When enabled, the closest villager standing near a configured block being placed will be optimized.\s
|
||||||
|
If a configured block is broken nearby, the closest villager will become unoptimized again.""");
|
||||||
|
config.getList("optimization-methods.block-optimization.materials", List.of(
|
||||||
|
"LAPIS_BLOCK", "GLOWSTONE", "IRON_BLOCK"
|
||||||
|
), "Values here need to be valid bukkit Material enums for your server version."
|
||||||
|
).forEach(configuredMaterial -> {
|
||||||
|
try {
|
||||||
|
Material disableBlock = Material.valueOf(configuredMaterial);
|
||||||
|
this.blocks_that_disable.add(disableBlock);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
LogUtil.materialNotRecognized("block-optimization", configuredMaterial);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.cooldown = config.getInt("optimization-methods.block-optimization.optimize-cooldown-seconds", 600, """
|
||||||
|
Cooldown in seconds until a villager can be optimized again by using specific blocks. \s
|
||||||
|
Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.""") * 1000L;
|
||||||
|
this.search_radius = config.getDouble("optimization-methods.block-optimization.search-radius-in-blocks", 2.0, """
|
||||||
|
The radius in blocks a villager can be away from the player when he places an optimize block.\s
|
||||||
|
The closest unoptimized villager to the player will be optimized.""") / 2;
|
||||||
|
this.only_while_sneaking = config.getBoolean("optimization-methods.block-optimization.only-when-sneaking", true,
|
||||||
|
"Only optimize/unoptimize by workstation when player is sneaking during place or break.");
|
||||||
|
this.notify_player = config.getBoolean("optimization-methods.block-optimization.notify-player", true,
|
||||||
|
"Sends players a message when they successfully optimized or unoptimized a villager.");
|
||||||
|
this.log_enabled = config.getBoolean("optimization-methods.block-optimization.log", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnable() {
|
||||||
|
return VillagerOptimizer.getConfiguration().getBoolean("optimization-methods.block-optimization.enable", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
private void onBlockPlace(BlockPlaceEvent event) {
|
||||||
|
Block placed = event.getBlock();
|
||||||
|
if (!blocks_that_disable.contains(placed.getType())) return;
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
if (!player.hasPermission(Permissions.Optimize.BLOCK.get())) return;
|
||||||
|
if (only_while_sneaking && !player.isSneaking()) return;
|
||||||
|
|
||||||
|
final Location blockLoc = placed.getLocation();
|
||||||
|
WrappedVillager closestOptimizableVillager = null;
|
||||||
|
double closestDistance = Double.MAX_VALUE;
|
||||||
|
|
||||||
|
for (Entity entity : blockLoc.getNearbyEntities(search_radius, search_radius, search_radius)) {
|
||||||
|
if (!entity.getType().equals(EntityType.VILLAGER)) continue;
|
||||||
|
Villager villager = (Villager) entity;
|
||||||
|
final Villager.Profession profession = villager.getProfession();
|
||||||
|
if (profession.equals(Villager.Profession.NONE) || profession.equals(Villager.Profession.NITWIT)) continue;
|
||||||
|
|
||||||
|
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
|
||||||
|
final double distance = entity.getLocation().distance(blockLoc);
|
||||||
|
|
||||||
|
if (distance < closestDistance && wVillager.canOptimize(cooldown)) {
|
||||||
|
closestOptimizableVillager = wVillager;
|
||||||
|
closestDistance = distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closestOptimizableVillager == null) return;
|
||||||
|
|
||||||
|
if (closestOptimizableVillager.canOptimize(cooldown) || player.hasPermission(Permissions.Bypass.BLOCK_COOLDOWN.get())) {
|
||||||
|
VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(closestOptimizableVillager, OptimizationType.BLOCK, player, event.isAsynchronous());
|
||||||
|
if (!optimizeEvent.callEvent()) return;
|
||||||
|
|
||||||
|
closestOptimizableVillager.setOptimization(optimizeEvent.getOptimizationType());
|
||||||
|
closestOptimizableVillager.saveOptimizeTime();
|
||||||
|
|
||||||
|
if (notify_player) {
|
||||||
|
final TextReplacementConfig vilProfession = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%vil_profession%")
|
||||||
|
.replacement(closestOptimizableVillager.villager().getProfession().toString().toLowerCase())
|
||||||
|
.build();
|
||||||
|
final TextReplacementConfig placedMaterial = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%blocktype%")
|
||||||
|
.replacement(placed.getType().toString().toLowerCase())
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(line -> player.sendMessage(line
|
||||||
|
.replaceText(vilProfession)
|
||||||
|
.replaceText(placedMaterial)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (log_enabled)
|
||||||
|
VillagerOptimizer.getLog().info("Villager was optimized by block at "+closestOptimizableVillager.villager().getLocation());
|
||||||
|
} else {
|
||||||
|
if (notify_player) {
|
||||||
|
final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%time%")
|
||||||
|
.replacement(CommonUtil.formatTime(closestOptimizableVillager.getOptimizeCooldownMillis(cooldown)))
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).block_on_optimize_cooldown.forEach(line -> player.sendMessage(line.replaceText(timeLeft)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
private void onBlockBreak(BlockBreakEvent event) {
|
||||||
|
Block broken = event.getBlock();
|
||||||
|
if (!blocks_that_disable.contains(broken.getType())) return;
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
if (!player.hasPermission(Permissions.Optimize.BLOCK.get())) return;
|
||||||
|
if (only_while_sneaking && !player.isSneaking()) return;
|
||||||
|
|
||||||
|
final Location blockLoc = broken.getLocation();
|
||||||
|
WrappedVillager closestOptimizedVillager = null;
|
||||||
|
double closestDistance = Double.MAX_VALUE;
|
||||||
|
|
||||||
|
for (Entity entity : blockLoc.getNearbyEntities(search_radius, search_radius, search_radius)) {
|
||||||
|
if (!entity.getType().equals(EntityType.VILLAGER)) continue;
|
||||||
|
Villager villager = (Villager) entity;
|
||||||
|
|
||||||
|
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
|
||||||
|
final double distance = entity.getLocation().distance(blockLoc);
|
||||||
|
|
||||||
|
if (distance < closestDistance && wVillager.isOptimized()) {
|
||||||
|
closestOptimizedVillager = wVillager;
|
||||||
|
closestDistance = distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closestOptimizedVillager == null) return;
|
||||||
|
|
||||||
|
VillagerUnoptimizeEvent unOptimizeEvent = new VillagerUnoptimizeEvent(closestOptimizedVillager, player, OptimizationType.BLOCK, event.isAsynchronous());
|
||||||
|
if (!unOptimizeEvent.callEvent()) return;
|
||||||
|
|
||||||
|
closestOptimizedVillager.setOptimization(OptimizationType.NONE);
|
||||||
|
|
||||||
|
if (notify_player) {
|
||||||
|
final TextReplacementConfig vilProfession = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%vil_profession%")
|
||||||
|
.replacement(closestOptimizedVillager.villager().getProfession().toString().toLowerCase())
|
||||||
|
.build();
|
||||||
|
final TextReplacementConfig brokenMaterial = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%blocktype%")
|
||||||
|
.replacement(broken.getType().toString().toLowerCase())
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(line -> player.sendMessage(line
|
||||||
|
.replaceText(vilProfession)
|
||||||
|
.replaceText(brokenMaterial)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (log_enabled)
|
||||||
|
VillagerOptimizer.getLog().info("Villager unoptimized because nearby optimization block broken at: "+closestOptimizedVillager.villager().getLocation());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,126 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules.optimization;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerCache;
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.WrappedVillager;
|
||||||
|
import me.xginko.villageroptimizer.config.Config;
|
||||||
|
import me.xginko.villageroptimizer.enums.OptimizationType;
|
||||||
|
import me.xginko.villageroptimizer.enums.Permissions;
|
||||||
|
import me.xginko.villageroptimizer.events.VillagerOptimizeEvent;
|
||||||
|
import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent;
|
||||||
|
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
||||||
|
import me.xginko.villageroptimizer.utils.CommonUtil;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.TextReplacementConfig;
|
||||||
|
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class OptimizeByNametag implements VillagerOptimizerModule, Listener {
|
||||||
|
|
||||||
|
private final VillagerCache villagerCache;
|
||||||
|
private final HashSet<String> nametags = new HashSet<>(4);
|
||||||
|
private final long cooldown;
|
||||||
|
private final boolean consume_nametag, notify_player, log_enabled;
|
||||||
|
|
||||||
|
public OptimizeByNametag() {
|
||||||
|
shouldEnable();
|
||||||
|
this.villagerCache = VillagerOptimizer.getCache();
|
||||||
|
Config config = VillagerOptimizer.getConfiguration();
|
||||||
|
config.addComment("optimization-methods.nametag-optimization.enable", """
|
||||||
|
Enable optimization by naming villagers to one of the names configured below.\s
|
||||||
|
Nametag optimized villagers will be unoptimized again when they are renamed to something else.""");
|
||||||
|
this.nametags.addAll(config.getList("optimization-methods.nametag-optimization.names", List.of("Optimize", "DisableAI"),
|
||||||
|
"Names are case insensitive, capital letters won't matter.").stream().map(String::toLowerCase).toList());
|
||||||
|
this.consume_nametag = config.getBoolean("optimization-methods.nametag-optimization.nametags-get-consumed", true,
|
||||||
|
"Enable or disable consumption of the used nametag item.");
|
||||||
|
this.cooldown = config.getInt("optimization-methods.nametag-optimization.optimize-cooldown-seconds", 600, """
|
||||||
|
Cooldown in seconds until a villager can be optimized again using a nametag.\s
|
||||||
|
Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.""") * 1000L;
|
||||||
|
this.notify_player = config.getBoolean("optimization-methods.nametag-optimization.notify-player", true,
|
||||||
|
"Sends players a message when they successfully optimized a villager.");
|
||||||
|
this.log_enabled = config.getBoolean("optimization-methods.nametag-optimization.log", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnable() {
|
||||||
|
return VillagerOptimizer.getConfiguration().getBoolean("optimization-methods.nametag-optimization.enable", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
private void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
|
||||||
|
if (!event.getRightClicked().getType().equals(EntityType.VILLAGER)) return;
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
if (!player.hasPermission(Permissions.Optimize.NAMETAG.get())) return;
|
||||||
|
|
||||||
|
ItemStack usedItem = player.getInventory().getItem(event.getHand());
|
||||||
|
if (usedItem == null || !usedItem.getType().equals(Material.NAME_TAG)) return;
|
||||||
|
ItemMeta meta = usedItem.getItemMeta();
|
||||||
|
if (!meta.hasDisplayName()) return;
|
||||||
|
|
||||||
|
// Get component name first, so we can manually name the villager when canceling the event to avoid item consumption.
|
||||||
|
Component newVillagerName = meta.displayName();
|
||||||
|
assert newVillagerName != null; // Legitimate since we checked for hasDisplayName()
|
||||||
|
final String name = PlainTextComponentSerializer.plainText().serialize(newVillagerName);
|
||||||
|
Villager villager = (Villager) event.getRightClicked();
|
||||||
|
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
|
||||||
|
|
||||||
|
if (nametags.contains(name.toLowerCase())) {
|
||||||
|
if (wVillager.canOptimize(cooldown) || player.hasPermission(Permissions.Bypass.NAMETAG_COOLDOWN.get())) {
|
||||||
|
VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(wVillager, OptimizationType.NAMETAG, player, event.isAsynchronous());
|
||||||
|
if (!optimizeEvent.callEvent()) return;
|
||||||
|
|
||||||
|
if (!consume_nametag) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
villager.customName(newVillagerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
wVillager.setOptimization(optimizeEvent.getOptimizationType());
|
||||||
|
wVillager.saveOptimizeTime();
|
||||||
|
|
||||||
|
if (notify_player)
|
||||||
|
VillagerOptimizer.getLang(player.locale()).nametag_optimize_success.forEach(player::sendMessage);
|
||||||
|
if (log_enabled)
|
||||||
|
VillagerOptimizer.getLog().info(player.getName() + " optimized a villager using nametag: '" + name + "'");
|
||||||
|
} else {
|
||||||
|
event.setCancelled(true);
|
||||||
|
if (notify_player) {
|
||||||
|
final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%time%")
|
||||||
|
.replacement(CommonUtil.formatTime(wVillager.getOptimizeCooldownMillis(cooldown)))
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> player.sendMessage(line.replaceText(timeLeft)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (wVillager.isOptimized()) {
|
||||||
|
VillagerUnoptimizeEvent unOptimizeEvent = new VillagerUnoptimizeEvent(wVillager, player, OptimizationType.NAMETAG, event.isAsynchronous());
|
||||||
|
if (!unOptimizeEvent.callEvent()) return;
|
||||||
|
|
||||||
|
wVillager.setOptimization(OptimizationType.NONE);
|
||||||
|
|
||||||
|
if (notify_player)
|
||||||
|
VillagerOptimizer.getLang(player.locale()).nametag_unoptimize_success.forEach(player::sendMessage);
|
||||||
|
if (log_enabled)
|
||||||
|
VillagerOptimizer.getLog().info(event.getPlayer().getName() + " disabled optimizations for a villager using nametag: '" + name + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,200 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules.optimization;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerCache;
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.WrappedVillager;
|
||||||
|
import me.xginko.villageroptimizer.config.Config;
|
||||||
|
import me.xginko.villageroptimizer.enums.OptimizationType;
|
||||||
|
import me.xginko.villageroptimizer.enums.Permissions;
|
||||||
|
import me.xginko.villageroptimizer.events.VillagerOptimizeEvent;
|
||||||
|
import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent;
|
||||||
|
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
||||||
|
import me.xginko.villageroptimizer.utils.CommonUtil;
|
||||||
|
import net.kyori.adventure.text.TextReplacementConfig;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.block.BlockBreakEvent;
|
||||||
|
import org.bukkit.event.block.BlockPlaceEvent;
|
||||||
|
|
||||||
|
public class OptimizeByWorkstation implements VillagerOptimizerModule, Listener {
|
||||||
|
|
||||||
|
private final VillagerCache villagerCache;
|
||||||
|
private final long cooldown;
|
||||||
|
private final double search_radius;
|
||||||
|
private final boolean only_while_sneaking, log_enabled, notify_player;
|
||||||
|
|
||||||
|
public OptimizeByWorkstation() {
|
||||||
|
shouldEnable();
|
||||||
|
this.villagerCache = VillagerOptimizer.getCache();
|
||||||
|
Config config = VillagerOptimizer.getConfiguration();
|
||||||
|
config.addComment("optimization-methods.workstation-optimization.enable", """
|
||||||
|
When enabled, the closest villager near a matching workstation being placed will be optimized.\s
|
||||||
|
If a nearby matching workstation is broken, the villager will become unoptimized again.""");
|
||||||
|
this.search_radius = config.getDouble("optimization-methods.workstation-optimization.search-radius-in-blocks", 2.0, """
|
||||||
|
The radius in blocks a villager can be away from the player when he places a workstation.\s
|
||||||
|
The closest unoptimized villager to the player will be optimized.""") / 2;
|
||||||
|
this.cooldown = config.getInt("optimization-methods.workstation-optimization.optimize-cooldown-seconds", 600, """
|
||||||
|
Cooldown in seconds until a villager can be optimized again using a workstation.\s
|
||||||
|
Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.""") * 1000L;
|
||||||
|
this.only_while_sneaking = config.getBoolean("optimization-methods.workstation-optimization.only-when-sneaking", true,
|
||||||
|
"Only optimize/unoptimize by workstation when player is sneaking during place or break");
|
||||||
|
this.notify_player = config.getBoolean("optimization-methods.workstation-optimization.notify-player", true,
|
||||||
|
"Sends players a message when they successfully optimized a villager.");
|
||||||
|
this.log_enabled = config.getBoolean("optimization-methods.workstation-optimization.log", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnable() {
|
||||||
|
return VillagerOptimizer.getConfiguration().getBoolean("optimization-methods.workstation-optimization.enable", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
private void onBlockPlace(BlockPlaceEvent event) {
|
||||||
|
Block placed = event.getBlock();
|
||||||
|
Villager.Profession workstationProfession = getWorkstationProfession(placed.getType());
|
||||||
|
if (workstationProfession.equals(Villager.Profession.NONE)) return;
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
if (!player.hasPermission(Permissions.Optimize.WORKSTATION.get())) return;
|
||||||
|
if (only_while_sneaking && !player.isSneaking()) return;
|
||||||
|
|
||||||
|
final Location workstationLoc = placed.getLocation();
|
||||||
|
WrappedVillager closestOptimizableVillager = null;
|
||||||
|
double closestDistance = Double.MAX_VALUE;
|
||||||
|
|
||||||
|
for (Entity entity : workstationLoc.getNearbyEntities(search_radius, search_radius, search_radius)) {
|
||||||
|
if (!entity.getType().equals(EntityType.VILLAGER)) continue;
|
||||||
|
Villager villager = (Villager) entity;
|
||||||
|
if (!villager.getProfession().equals(workstationProfession)) continue;
|
||||||
|
|
||||||
|
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
|
||||||
|
final double distance = entity.getLocation().distance(workstationLoc);
|
||||||
|
|
||||||
|
if (distance < closestDistance && wVillager.canOptimize(cooldown)) {
|
||||||
|
closestOptimizableVillager = wVillager;
|
||||||
|
closestDistance = distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closestOptimizableVillager == null) return;
|
||||||
|
|
||||||
|
if (closestOptimizableVillager.canOptimize(cooldown) || player.hasPermission(Permissions.Bypass.WORKSTATION_COOLDOWN.get())) {
|
||||||
|
VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(closestOptimizableVillager, OptimizationType.WORKSTATION, player, event.isAsynchronous());
|
||||||
|
if (!optimizeEvent.callEvent()) return;
|
||||||
|
|
||||||
|
closestOptimizableVillager.setOptimization(optimizeEvent.getOptimizationType());
|
||||||
|
closestOptimizableVillager.saveOptimizeTime();
|
||||||
|
|
||||||
|
if (notify_player) {
|
||||||
|
final TextReplacementConfig vilProfession = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%vil_profession%")
|
||||||
|
.replacement(closestOptimizableVillager.villager().getProfession().toString().toLowerCase())
|
||||||
|
.build();
|
||||||
|
final TextReplacementConfig placedWorkstation = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%workstation%")
|
||||||
|
.replacement(placed.getType().toString().toLowerCase())
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).workstation_optimize_success.forEach(line -> player.sendMessage(line
|
||||||
|
.replaceText(vilProfession)
|
||||||
|
.replaceText(placedWorkstation)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (log_enabled)
|
||||||
|
VillagerOptimizer.getLog().info(player.getName() + " optimized a villager using workstation: '" + placed.getType().toString().toLowerCase() + "'");
|
||||||
|
} else {
|
||||||
|
if (notify_player) {
|
||||||
|
final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%time%")
|
||||||
|
.replacement(CommonUtil.formatTime(closestOptimizableVillager.getOptimizeCooldownMillis(cooldown)))
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> player.sendMessage(line
|
||||||
|
.replaceText(timeLeft)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
private void onBlockBreak(BlockBreakEvent event) {
|
||||||
|
Block broken = event.getBlock();
|
||||||
|
Villager.Profession workstationProfession = getWorkstationProfession(broken.getType());
|
||||||
|
if (workstationProfession.equals(Villager.Profession.NONE)) return;
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
if (!player.hasPermission(Permissions.Optimize.WORKSTATION.get())) return;
|
||||||
|
if (only_while_sneaking && !player.isSneaking()) return;
|
||||||
|
|
||||||
|
final Location workstationLoc = broken.getLocation();
|
||||||
|
WrappedVillager closestOptimizedVillager = null;
|
||||||
|
double closestDistance = Double.MAX_VALUE;
|
||||||
|
|
||||||
|
for (Entity entity : workstationLoc.getNearbyEntities(search_radius, search_radius, search_radius)) {
|
||||||
|
if (!entity.getType().equals(EntityType.VILLAGER)) continue;
|
||||||
|
Villager villager = (Villager) entity;
|
||||||
|
if (!villager.getProfession().equals(workstationProfession)) continue;
|
||||||
|
|
||||||
|
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
|
||||||
|
final double distance = entity.getLocation().distance(workstationLoc);
|
||||||
|
|
||||||
|
if (distance < closestDistance && wVillager.canOptimize(cooldown)) {
|
||||||
|
closestOptimizedVillager = wVillager;
|
||||||
|
closestDistance = distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closestOptimizedVillager == null) return;
|
||||||
|
|
||||||
|
VillagerUnoptimizeEvent unOptimizeEvent = new VillagerUnoptimizeEvent(closestOptimizedVillager, player, OptimizationType.WORKSTATION, event.isAsynchronous());
|
||||||
|
if (!unOptimizeEvent.callEvent()) return;
|
||||||
|
|
||||||
|
closestOptimizedVillager.setOptimization(OptimizationType.NONE);
|
||||||
|
|
||||||
|
if (notify_player) {
|
||||||
|
final TextReplacementConfig vilProfession = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%vil_profession%")
|
||||||
|
.replacement(closestOptimizedVillager.villager().getProfession().toString().toLowerCase())
|
||||||
|
.build();
|
||||||
|
final TextReplacementConfig brokenWorkstation = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%workstation%")
|
||||||
|
.replacement(broken.getType().toString().toLowerCase())
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).workstation_unoptimize_success.forEach(line -> player.sendMessage(line
|
||||||
|
.replaceText(vilProfession)
|
||||||
|
.replaceText(brokenWorkstation)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (log_enabled)
|
||||||
|
VillagerOptimizer.getLog().info(player.getName() + " unoptimized a villager by breaking workstation: '" + broken.getType().toString().toLowerCase() + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Villager.Profession getWorkstationProfession(final Material workstation) {
|
||||||
|
return switch (workstation) {
|
||||||
|
case BARREL -> Villager.Profession.FISHERMAN;
|
||||||
|
case CARTOGRAPHY_TABLE -> Villager.Profession.CARTOGRAPHER;
|
||||||
|
case SMOKER -> Villager.Profession.BUTCHER;
|
||||||
|
case SMITHING_TABLE -> Villager.Profession.TOOLSMITH;
|
||||||
|
case GRINDSTONE -> Villager.Profession.WEAPONSMITH;
|
||||||
|
case BLAST_FURNACE -> Villager.Profession.ARMORER;
|
||||||
|
case CAULDRON -> Villager.Profession.LEATHERWORKER;
|
||||||
|
case BREWING_STAND -> Villager.Profession.CLERIC;
|
||||||
|
case COMPOSTER -> Villager.Profession.FARMER;
|
||||||
|
case FLETCHING_TABLE -> Villager.Profession.FLETCHER;
|
||||||
|
case LOOM -> Villager.Profession.SHEPHERD;
|
||||||
|
case LECTERN -> Villager.Profession.LIBRARIAN;
|
||||||
|
case STONECUTTER -> Villager.Profession.MASON;
|
||||||
|
default -> Villager.Profession.NONE;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package me.xginko.villageroptimizer.utils;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public class LogUtil {
|
||||||
|
|
||||||
|
public static void moduleLog(Level logLevel, String path, String logMessage) {
|
||||||
|
VillagerOptimizer.getLog().log(logLevel, "(" + path + ") " + logMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void materialNotRecognized(String path, String material) {
|
||||||
|
moduleLog(Level.WARNING, path, "Material '" + material + "' not recognized. Please use correct Material enums from: " +
|
||||||
|
"https://jd.papermc.io/paper/1.20/org/bukkit/Material.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void damageCauseNotRecognized(String path, String cause) {
|
||||||
|
moduleLog(Level.WARNING, path, "DamageCause '" + cause + "' not recognized. Please use correct DamageCause enums from: " +
|
||||||
|
"https://jd.papermc.io/paper/1.20/org/bukkit/event/entity/EntityDamageEvent.DamageCause.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void entityTypeNotRecognized(String path, String entityType) {
|
||||||
|
moduleLog(Level.WARNING, path, "EntityType '" + entityType + "' not recognized. Please use correct Spigot EntityType enums for your Minecraft version!");
|
||||||
|
}
|
||||||
|
}
|
@ -29,7 +29,7 @@ messages:
|
|||||||
- "<green>Оптимизация была успешно снята с %vil_profession% удалением %blocktype%."
|
- "<green>Оптимизация была успешно снята с %vil_profession% удалением %blocktype%."
|
||||||
command:
|
command:
|
||||||
optimize-success:
|
optimize-success:
|
||||||
- "<green>Успешно оптимизировано %amount% жителей в радиусе %radius% блоков."
|
- "<green>Успешно оптимизинованы %amount% жителей в радиусе %radius% блоков."
|
||||||
optimize-fail:
|
optimize-fail:
|
||||||
- "<gray>%amount% жителей не могут быть оптимизированы снова так как они уже были оптимизированы недавно."
|
- "<gray>%amount% жителей не могут быть оптимизированы снова так как они уже были оптимизированы недавно."
|
||||||
radius-limit-exceed:
|
radius-limit-exceed:
|
98
VillagerOptimizer-1.16.5/src/main/resources/plugin.yml
Normal file
98
VillagerOptimizer-1.16.5/src/main/resources/plugin.yml
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
name: VillagerOptimizer
|
||||||
|
version: '${project.version}'
|
||||||
|
main: me.xginko.villageroptimizer.VillagerOptimizer
|
||||||
|
authors: [ xGinko ]
|
||||||
|
description: ${project.description}
|
||||||
|
website: ${project.url}
|
||||||
|
api-version: '1.16'
|
||||||
|
folia-supported: false
|
||||||
|
commands:
|
||||||
|
villageroptimizer:
|
||||||
|
usage: /villageroptimizer [ reload, version, disable ]
|
||||||
|
description: VillagerOptimizer admin commands
|
||||||
|
aliases:
|
||||||
|
- voptimizer
|
||||||
|
- vo
|
||||||
|
optimizevillagers:
|
||||||
|
usage: /optimizevillagers <blockradius>
|
||||||
|
description: Optmize villagers in a radius around you
|
||||||
|
aliases:
|
||||||
|
- optvils
|
||||||
|
- noai
|
||||||
|
unoptimizevillagers:
|
||||||
|
usage: /unoptimizevillagers <blockradius>
|
||||||
|
description: Unoptmize villagers in a radius around you
|
||||||
|
aliases:
|
||||||
|
- unoptvils
|
||||||
|
- noaiundo
|
||||||
|
permissions:
|
||||||
|
villageroptimizer.ignore:
|
||||||
|
description: Players with this permission won't be able to use the plugin features
|
||||||
|
children:
|
||||||
|
villageroptimizer.optimize.nametag: false
|
||||||
|
villageroptimizer.optimize.block: false
|
||||||
|
villageroptimizer.optimize.workstation: false
|
||||||
|
villageroptimizer.playerdefaults:
|
||||||
|
description: Default permissions for players
|
||||||
|
default: true
|
||||||
|
children:
|
||||||
|
villageroptimizer.cmd.optimize: true
|
||||||
|
villageroptimizer.cmd.unoptimize: true
|
||||||
|
villageroptimizer.optimize.*: true
|
||||||
|
villageroptimizer.*:
|
||||||
|
description: All plugin permissions
|
||||||
|
children:
|
||||||
|
villageroptimizer.cmd.*: true
|
||||||
|
villageroptimizer.bypass.*: true
|
||||||
|
villageroptimizer.optimize.*: true
|
||||||
|
villageroptimizer.optimize.*:
|
||||||
|
description: Optimization type permissions
|
||||||
|
children:
|
||||||
|
villageroptimizer.optimize.nametag: true
|
||||||
|
villageroptimizer.optimize.block: true
|
||||||
|
villageroptimizer.optimize.workstation: true
|
||||||
|
villageroptimizer.optimize.nametag:
|
||||||
|
description: Optimize/Unoptimize villagers using nametags
|
||||||
|
villageroptimizer.optimize.block:
|
||||||
|
description: Optimize/Unoptimize villagers using specific blocks
|
||||||
|
villageroptimizer.optimize.workstation:
|
||||||
|
description: Optimize/Unoptimize villagers using workstations
|
||||||
|
villageroptimizer.cmd.*:
|
||||||
|
description: All command permissions
|
||||||
|
children:
|
||||||
|
villageroptimizer.cmd.reload: true
|
||||||
|
villageroptimizer.cmd.disable: true
|
||||||
|
villageroptimizer.cmd.version: true
|
||||||
|
villageroptimizer.cmd.optimize: true
|
||||||
|
villageroptimizer.cmd.unoptimize: true
|
||||||
|
villageroptimizer.cmd.disable:
|
||||||
|
description: Disable the plugin
|
||||||
|
villageroptimizer.cmd.reload:
|
||||||
|
description: Reload the plugin configuration
|
||||||
|
villageroptimizer.cmd.version:
|
||||||
|
description: Show the plugin version
|
||||||
|
villageroptimizer.cmd.optimize:
|
||||||
|
description: Optimize villagers in a radius
|
||||||
|
villageroptimizer.cmd.unoptimize:
|
||||||
|
description: Unoptimize villagers in a radius
|
||||||
|
villageroptimizer.bypass.*:
|
||||||
|
description: All bypass permissions
|
||||||
|
children:
|
||||||
|
villageroptimizer.bypass.tradeprevention: true
|
||||||
|
villageroptimizer.bypass.restockcooldown: true
|
||||||
|
villageroptimizer.bypass.nametagcooldown: true
|
||||||
|
villageroptimizer.bypass.blockcooldown: true
|
||||||
|
villageroptimizer.bypass.workstationcooldown: true
|
||||||
|
villageroptimizer.bypass.commandcooldown: true
|
||||||
|
villageroptimizer.bypass.tradeprevention:
|
||||||
|
description: Bypass unoptimized trading prevention if enabled
|
||||||
|
villageroptimizer.bypass.restockcooldown:
|
||||||
|
description: Bypass permission for optimized trade restock cooldown
|
||||||
|
villageroptimizer.bypass.nametagcooldown:
|
||||||
|
description: Bypass permission for nametag optimization cooldown
|
||||||
|
villageroptimizer.bypass.blockcooldown:
|
||||||
|
description: Bypass permission for block optimization cooldown
|
||||||
|
villageroptimizer.bypass.workstationcooldown:
|
||||||
|
description: Bypass permission for workstation optimization cooldown
|
||||||
|
villageroptimizer.bypass.commandcooldown:
|
||||||
|
description: Bypass permission for command optimization cooldown
|
87
VillagerOptimizer-1.20.2/pom.xml
Normal file
87
VillagerOptimizer-1.20.2/pom.xml
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>me.xginko.VillagerOptimizer</groupId>
|
||||||
|
<artifactId>VillagerOptimizer</artifactId>
|
||||||
|
<version>1.0.1</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>1.20.2</artifactId>
|
||||||
|
<name>${project.parent.artifactId}-${project.parent.version}--${project.artifactId}</name>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.11.0</version>
|
||||||
|
<configuration>
|
||||||
|
<source>${java.version}</source>
|
||||||
|
<target>${java.version}</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>3.5.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||||
|
<relocations>
|
||||||
|
<relocation>
|
||||||
|
<pattern>com.github.benmanes.caffeine</pattern>
|
||||||
|
<shadedPattern>me.xginko.villageroptimizer.caffeine</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
<relocation>
|
||||||
|
<pattern>org.bstats</pattern>
|
||||||
|
<shadedPattern>me.xginko.villageroptimizer.bstats</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
</relocations>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<filtering>true</filtering>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>papermc-repo</id>
|
||||||
|
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>sonatype</id>
|
||||||
|
<url>https://oss.sonatype.org/content/groups/public/</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.papermc.paper</groupId>
|
||||||
|
<artifactId>paper-api</artifactId>
|
||||||
|
<version>1.20.2-R0.1-SNAPSHOT</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
@ -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<UUID, WrappedVillager> villagerCache;
|
||||||
|
|
||||||
|
VillagerCache(long expireAfterWriteSeconds) {
|
||||||
|
this.villagerCache = Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(expireAfterWriteSeconds)).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull ConcurrentMap<UUID, WrappedVillager> cacheMap() {
|
||||||
|
return this.villagerCache.asMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable WrappedVillager get(@NotNull UUID uuid) {
|
||||||
|
WrappedVillager wrappedVillager = this.villagerCache.getIfPresent(uuid);
|
||||||
|
return wrappedVillager == null && Bukkit.getEntity(uuid) instanceof Villager villager ? this.add(villager) : wrappedVillager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull WrappedVillager getOrAdd(@NotNull Villager villager) {
|
||||||
|
WrappedVillager wrappedVillager = this.villagerCache.getIfPresent(villager.getUniqueId());
|
||||||
|
return wrappedVillager == null ? this.add(new WrappedVillager(villager)) : this.add(wrappedVillager);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull WrappedVillager add(@NotNull WrappedVillager villager) {
|
||||||
|
this.villagerCache.put(villager.villager().getUniqueId(), villager);
|
||||||
|
return villager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull WrappedVillager add(@NotNull Villager villager) {
|
||||||
|
return add(new WrappedVillager(villager));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(@NotNull UUID uuid) {
|
||||||
|
return this.villagerCache.getIfPresent(uuid) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(@NotNull WrappedVillager villager) {
|
||||||
|
return this.contains(villager.villager().getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(@NotNull Villager villager) {
|
||||||
|
return this.contains(villager.getUniqueId());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,173 @@
|
|||||||
|
package me.xginko.villageroptimizer;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand;
|
||||||
|
import me.xginko.villageroptimizer.config.Config;
|
||||||
|
import me.xginko.villageroptimizer.config.LanguageCache;
|
||||||
|
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import net.kyori.adventure.text.format.Style;
|
||||||
|
import net.kyori.adventure.text.format.TextColor;
|
||||||
|
import net.kyori.adventure.text.format.TextDecoration;
|
||||||
|
import org.bstats.bukkit.Metrics;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.ConsoleCommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.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<String, LanguageCache> 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));
|
||||||
|
new Metrics(this, 19954);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VillagerOptimizer getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
public static NamespacedKey getKey(String key) {
|
||||||
|
return new NamespacedKey(instance, key);
|
||||||
|
}
|
||||||
|
public static Config getConfiguration() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
public static VillagerCache getCache() {
|
||||||
|
return villagerCache;
|
||||||
|
}
|
||||||
|
public static 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 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("Found language file for " + 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("Found language file for " + 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<String> getDefaultLanguageFiles() {
|
||||||
|
Set<String> 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 files! - " + e.getLocalizedMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return languageFiles;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,192 @@
|
|||||||
|
package me.xginko.villageroptimizer;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.enums.Keys;
|
||||||
|
import me.xginko.villageroptimizer.enums.OptimizationType;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.bukkit.persistence.PersistentDataContainer;
|
||||||
|
import org.bukkit.persistence.PersistentDataType;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public final class WrappedVillager {
|
||||||
|
|
||||||
|
private final @NotNull Villager villager;
|
||||||
|
private final @NotNull PersistentDataContainer dataContainer;
|
||||||
|
|
||||||
|
WrappedVillager(@NotNull Villager villager) {
|
||||||
|
this.villager = villager;
|
||||||
|
this.dataContainer = villager.getPersistentDataContainer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The villager inside the wrapper.
|
||||||
|
*/
|
||||||
|
public @NotNull Villager villager() {
|
||||||
|
return villager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The data container inside the wrapper.
|
||||||
|
*/
|
||||||
|
public @NotNull PersistentDataContainer dataContainer() {
|
||||||
|
return dataContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return True if the villager is optimized by this plugin, otherwise false.
|
||||||
|
*/
|
||||||
|
public boolean isOptimized() {
|
||||||
|
return dataContainer.has(Keys.OPTIMIZATION_TYPE.key());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param cooldown_millis The configured cooldown in millis until the next optimization is allowed to occur.
|
||||||
|
* @return True if villager can be optimized again, otherwise false.
|
||||||
|
*/
|
||||||
|
public boolean canOptimize(final long cooldown_millis) {
|
||||||
|
return getLastOptimize() + cooldown_millis <= System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param type OptimizationType the villager should be set to.
|
||||||
|
*/
|
||||||
|
public void setOptimization(OptimizationType type) {
|
||||||
|
if (type.equals(OptimizationType.NONE) && isOptimized()) {
|
||||||
|
dataContainer.remove(Keys.OPTIMIZATION_TYPE.key());
|
||||||
|
villager.getScheduler().run(VillagerOptimizer.getInstance(), enableAI -> {
|
||||||
|
villager.setAware(true);
|
||||||
|
villager.setAI(true);
|
||||||
|
}, null);
|
||||||
|
} else {
|
||||||
|
dataContainer.set(Keys.OPTIMIZATION_TYPE.key(), PersistentDataType.STRING, type.name());
|
||||||
|
villager.getScheduler().run(VillagerOptimizer.getInstance(), disableAI -> {
|
||||||
|
villager.setAware(false);
|
||||||
|
}, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current OptimizationType of the villager.
|
||||||
|
*/
|
||||||
|
public @NotNull OptimizationType getOptimizationType() {
|
||||||
|
return isOptimized() ? OptimizationType.valueOf(dataContainer.get(Keys.OPTIMIZATION_TYPE.key(), PersistentDataType.STRING)) : OptimizationType.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the system time in millis when the villager was last optimized.
|
||||||
|
*/
|
||||||
|
public void saveOptimizeTime() {
|
||||||
|
dataContainer.set(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG, System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The system time in millis when the villager was last optimized, 0L if the villager was never optimized.
|
||||||
|
*/
|
||||||
|
public long getLastOptimize() {
|
||||||
|
return dataContainer.has(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG) ? dataContainer.get(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG) : 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Here for convenience so the remaining millis since the last stored optimize time
|
||||||
|
* can be easily calculated.
|
||||||
|
* This enables new configured cooldowns to instantly apply instead of them being persistent.
|
||||||
|
*
|
||||||
|
* @param cooldown_millis The configured cooldown in milliseconds you want to check against.
|
||||||
|
* @return The time left in millis until the villager can be optimized again.
|
||||||
|
*/
|
||||||
|
public long getOptimizeCooldownMillis(final long cooldown_millis) {
|
||||||
|
return dataContainer.has(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG) ? (System.currentTimeMillis() - (dataContainer.get(Keys.LAST_OPTIMIZE.key(), PersistentDataType.LONG) + cooldown_millis)) : cooldown_millis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Here for convenience so the remaining millis since the last stored restock time
|
||||||
|
* can be easily calculated.
|
||||||
|
*
|
||||||
|
* @param cooldown_millis The configured cooldown in milliseconds you want to check against.
|
||||||
|
* @return True if the villager has been loaded long enough.
|
||||||
|
*/
|
||||||
|
public boolean canRestock(final long cooldown_millis) {
|
||||||
|
return getLastRestock() + cooldown_millis <= villager.getWorld().getFullTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restock all trading recipes.
|
||||||
|
*/
|
||||||
|
public void restock() {
|
||||||
|
villager.getRecipes().forEach(recipe -> recipe.setUses(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the time of the in-game world when the entity was last restocked.
|
||||||
|
*/
|
||||||
|
public void saveRestockTime() {
|
||||||
|
dataContainer.set(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG, villager.getWorld().getFullTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The time of the in-game world when the entity was last restocked.
|
||||||
|
*/
|
||||||
|
public long getLastRestock() {
|
||||||
|
return dataContainer.has(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG) ? dataContainer.get(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG) : 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRestockCooldownMillis(final long cooldown_millis) {
|
||||||
|
return dataContainer.has(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG) ? (villager.getWorld().getFullTime() - (dataContainer.get(Keys.LAST_RESTOCK.key(), PersistentDataType.LONG) + cooldown_millis)) : cooldown_millis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The level between 1-5 calculated from the villagers experience.
|
||||||
|
*/
|
||||||
|
public int calculateLevel() {
|
||||||
|
// https://minecraft.fandom.com/wiki/Trading#Mechanics
|
||||||
|
int vilEXP = villager.getVillagerExperience();
|
||||||
|
if (vilEXP >= 250) return 5;
|
||||||
|
if (vilEXP >= 150) return 4;
|
||||||
|
if (vilEXP >= 70) return 3;
|
||||||
|
if (vilEXP >= 10) return 2;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param cooldown_millis The configured cooldown in milliseconds you want to check against.
|
||||||
|
* @return Whether the villager can be leveled up or not with the checked milliseconds
|
||||||
|
*/
|
||||||
|
public boolean canLevelUp(final long cooldown_millis) {
|
||||||
|
return getLastLevelUpTime() + cooldown_millis <= villager.getWorld().getFullTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the time of the in-game world when the entity was last leveled up.
|
||||||
|
*/
|
||||||
|
public void saveLastLevelUp() {
|
||||||
|
dataContainer.set(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG, villager.getWorld().getFullTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Here for convenience so the remaining millis since the last stored level-up time
|
||||||
|
* can be easily calculated.
|
||||||
|
*
|
||||||
|
* @return The time of the in-game world when the entity was last leveled up.
|
||||||
|
*/
|
||||||
|
public long getLastLevelUpTime() {
|
||||||
|
return dataContainer.has(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG) ? dataContainer.get(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG) : 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLevelCooldownMillis(final long cooldown_millis) {
|
||||||
|
return dataContainer.has(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG) ? (villager.getWorld().getFullTime() - (dataContainer.get(Keys.LAST_LEVELUP.key(), PersistentDataType.LONG) + cooldown_millis)) : cooldown_millis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void memorizeName(final Component customName) {
|
||||||
|
dataContainer.set(Keys.LAST_OPTIMIZE_NAME.key(), PersistentDataType.STRING, MiniMessage.miniMessage().serialize(customName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Component getMemorizedName() {
|
||||||
|
return dataContainer.has(Keys.LAST_OPTIMIZE_NAME.key()) ? MiniMessage.miniMessage().deserialize(dataContainer.get(Keys.LAST_OPTIMIZE_NAME.key(), PersistentDataType.STRING)) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void forgetName() {
|
||||||
|
dataContainer.remove(Keys.LAST_OPTIMIZE_NAME.key());
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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<VillagerOptimizerCommand> commands = new HashSet<>();
|
||||||
|
static void reloadCommands() {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
CommandMap commandMap = plugin.getServer().getCommandMap();
|
||||||
|
commands.forEach(command -> plugin.getCommand(command.label()).unregister(commandMap));
|
||||||
|
commands.clear();
|
||||||
|
|
||||||
|
commands.add(new VillagerOptimizerCmd());
|
||||||
|
commands.add(new OptVillagersRadius());
|
||||||
|
commands.add(new UnOptVillagersRadius());
|
||||||
|
|
||||||
|
commands.forEach(command -> plugin.getCommand(command.label()).setExecutor(command));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args);
|
||||||
|
}
|
@ -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.List;
|
||||||
|
|
||||||
|
public class OptVillagersRadius implements VillagerOptimizerCommand, TabCompleter {
|
||||||
|
|
||||||
|
private final List<String> tabCompletes = List.of("5", "10", "25", "50");
|
||||||
|
private final long cooldown;
|
||||||
|
private final int max_radius;
|
||||||
|
|
||||||
|
public OptVillagersRadius() {
|
||||||
|
Config config = VillagerOptimizer.getConfiguration();
|
||||||
|
this.max_radius = config.getInt("optimization-methods.commands.optimizevillagers.max-block-radius", 100);
|
||||||
|
this.cooldown = config.getInt("optimization-methods.commands.optimizevillagers.cooldown-seconds", 600, """
|
||||||
|
Cooldown in seconds until a villager can be optimized again using the command.\s
|
||||||
|
Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.""") * 1000L;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String label() {
|
||||||
|
return "optimizevillagers";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
return args.length == 1 ? tabCompletes : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage(Component.text("This command can only be executed by a player.")
|
||||||
|
.color(NamedTextColor.RED).decorate(TextDecoration.BOLD));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sender.hasPermission(Permissions.Commands.OPTIMIZE_RADIUS.get())) {
|
||||||
|
sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length != 1) {
|
||||||
|
VillagerOptimizer.getLang(player.locale()).command_specify_radius.forEach(player::sendMessage);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
int specifiedRadius = Integer.parseInt(args[0]);
|
||||||
|
|
||||||
|
if (specifiedRadius > max_radius) {
|
||||||
|
final TextReplacementConfig limit = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%distance%")
|
||||||
|
.replacement(Integer.toString(max_radius))
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).command_radius_limit_exceed.forEach(line -> player.sendMessage(line.replaceText(limit)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
VillagerCache villagerCache = VillagerOptimizer.getCache();
|
||||||
|
int successCount = 0;
|
||||||
|
int failCount = 0;
|
||||||
|
final boolean player_has_cooldown_bypass = player.hasPermission(Permissions.Bypass.COMMAND_COOLDOWN.get());
|
||||||
|
|
||||||
|
for (Entity entity : player.getNearbyEntities(specifiedRadius, specifiedRadius, specifiedRadius)) {
|
||||||
|
if (!entity.getType().equals(EntityType.VILLAGER)) continue;
|
||||||
|
Villager villager = (Villager) entity;
|
||||||
|
Villager.Profession profession = villager.getProfession();
|
||||||
|
if (profession.equals(Villager.Profession.NITWIT) || profession.equals(Villager.Profession.NONE)) continue;
|
||||||
|
|
||||||
|
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
|
||||||
|
|
||||||
|
if (player_has_cooldown_bypass || wVillager.canOptimize(cooldown)) {
|
||||||
|
VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(wVillager, OptimizationType.COMMAND, player);
|
||||||
|
if (optimizeEvent.callEvent()) {
|
||||||
|
wVillager.setOptimization(optimizeEvent.getOptimizationType());
|
||||||
|
wVillager.saveOptimizeTime();
|
||||||
|
successCount++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
failCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (successCount <= 0 && failCount <= 0) {
|
||||||
|
final TextReplacementConfig radius = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%radius%")
|
||||||
|
.replacement(Integer.toString(specifiedRadius))
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).command_no_villagers_nearby.forEach(line -> player.sendMessage(line.replaceText(radius)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (successCount > 0) {
|
||||||
|
final TextReplacementConfig success_amount = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%amount%")
|
||||||
|
.replacement(Integer.toString(successCount))
|
||||||
|
.build();
|
||||||
|
final TextReplacementConfig radius = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%radius%")
|
||||||
|
.replacement(Integer.toString(specifiedRadius))
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).command_optimize_success.forEach(line -> player.sendMessage(line
|
||||||
|
.replaceText(success_amount)
|
||||||
|
.replaceText(radius)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (failCount > 0) {
|
||||||
|
final TextReplacementConfig alreadyOptimized = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%amount%")
|
||||||
|
.replacement(Integer.toString(failCount))
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).command_optimize_fail.forEach(line -> player.sendMessage(line.replaceText(alreadyOptimized)));
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
VillagerOptimizer.getLang(player.locale()).command_radius_invalid.forEach(player::sendMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,121 @@
|
|||||||
|
package me.xginko.villageroptimizer.commands.unoptimizevillagers;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerCache;
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.WrappedVillager;
|
||||||
|
import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand;
|
||||||
|
import me.xginko.villageroptimizer.enums.OptimizationType;
|
||||||
|
import me.xginko.villageroptimizer.enums.Permissions;
|
||||||
|
import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.TextReplacementConfig;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import net.kyori.adventure.text.format.TextDecoration;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.TabCompleter;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class UnOptVillagersRadius implements VillagerOptimizerCommand, TabCompleter {
|
||||||
|
|
||||||
|
private final List<String> tabCompletes = List.of("5", "10", "25", "50");
|
||||||
|
private final int max_radius;
|
||||||
|
|
||||||
|
public UnOptVillagersRadius() {
|
||||||
|
this.max_radius = VillagerOptimizer.getConfiguration().getInt("optimization-methods.commands.unoptimizevillagers.max-block-radius", 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String label() {
|
||||||
|
return "unoptimizevillagers";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
return args.length == 1 ? tabCompletes : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage(Component.text("This command can only be executed by a player.")
|
||||||
|
.color(NamedTextColor.RED).decorate(TextDecoration.BOLD));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sender.hasPermission(Permissions.Commands.UNOPTIMIZE_RADIUS.get())) {
|
||||||
|
sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length != 1) {
|
||||||
|
VillagerOptimizer.getLang(player.locale()).command_specify_radius.forEach(player::sendMessage);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
int specifiedRadius = Integer.parseInt(args[0]);
|
||||||
|
|
||||||
|
if (specifiedRadius > max_radius) {
|
||||||
|
final TextReplacementConfig limit = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%distance%")
|
||||||
|
.replacement(Integer.toString(max_radius))
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).command_radius_limit_exceed.forEach(line -> player.sendMessage(line.replaceText(limit)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
VillagerCache villagerCache = VillagerOptimizer.getCache();
|
||||||
|
int successCount = 0;
|
||||||
|
|
||||||
|
for (Entity entity : player.getNearbyEntities(specifiedRadius, specifiedRadius, specifiedRadius)) {
|
||||||
|
if (!entity.getType().equals(EntityType.VILLAGER)) continue;
|
||||||
|
Villager villager = (Villager) entity;
|
||||||
|
Villager.Profession profession = villager.getProfession();
|
||||||
|
if (profession.equals(Villager.Profession.NITWIT) || profession.equals(Villager.Profession.NONE)) continue;
|
||||||
|
|
||||||
|
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
|
||||||
|
|
||||||
|
if (wVillager.isOptimized()) {
|
||||||
|
VillagerUnoptimizeEvent unOptimizeEvent = new VillagerUnoptimizeEvent(wVillager, player, OptimizationType.COMMAND);
|
||||||
|
if (unOptimizeEvent.callEvent()) {
|
||||||
|
wVillager.setOptimization(OptimizationType.NONE);
|
||||||
|
successCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (successCount <= 0) {
|
||||||
|
final TextReplacementConfig radius = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%radius%")
|
||||||
|
.replacement(Integer.toString(specifiedRadius))
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).command_no_villagers_nearby.forEach(line -> player.sendMessage(line.replaceText(radius)));
|
||||||
|
} else {
|
||||||
|
final TextReplacementConfig success_amount = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%amount%")
|
||||||
|
.replacement(Integer.toString(successCount))
|
||||||
|
.build();
|
||||||
|
final TextReplacementConfig radius = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%radius%")
|
||||||
|
.replacement(Integer.toString(specifiedRadius))
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).command_unoptimize_success.forEach(line -> player.sendMessage(line
|
||||||
|
.replaceText(success_amount)
|
||||||
|
.replaceText(radius)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
VillagerOptimizer.getLang(player.locale()).command_radius_invalid.forEach(player::sendMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
package me.xginko.villageroptimizer.commands.villageroptimizer;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.commands.SubCommand;
|
||||||
|
import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand;
|
||||||
|
import me.xginko.villageroptimizer.commands.villageroptimizer.subcommands.DisableSubCmd;
|
||||||
|
import me.xginko.villageroptimizer.commands.villageroptimizer.subcommands.ReloadSubCmd;
|
||||||
|
import me.xginko.villageroptimizer.commands.villageroptimizer.subcommands.VersionSubCmd;
|
||||||
|
import me.xginko.villageroptimizer.enums.Permissions;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.TabCompleter;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class VillagerOptimizerCmd implements TabCompleter, VillagerOptimizerCommand {
|
||||||
|
|
||||||
|
private final List<SubCommand> subCommands = new ArrayList<>(3);
|
||||||
|
private final List<String> tabCompleter = new ArrayList<>(3);
|
||||||
|
|
||||||
|
public VillagerOptimizerCmd() {
|
||||||
|
subCommands.add(new ReloadSubCmd());
|
||||||
|
subCommands.add(new VersionSubCmd());
|
||||||
|
subCommands.add(new DisableSubCmd());
|
||||||
|
subCommands.forEach(subCommand -> tabCompleter.add(subCommand.getLabel()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String label() {
|
||||||
|
return "villageroptimizer";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
|
||||||
|
return args.length == 1 ? tabCompleter : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
|
||||||
|
if (args.length > 0) {
|
||||||
|
boolean cmdExists = false;
|
||||||
|
for (SubCommand subCommand : subCommands) {
|
||||||
|
if (args[0].equalsIgnoreCase(subCommand.getLabel())) {
|
||||||
|
subCommand.perform(sender, args);
|
||||||
|
cmdExists = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!cmdExists) sendCommandOverview(sender);
|
||||||
|
} else {
|
||||||
|
sendCommandOverview(sender);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendCommandOverview(CommandSender sender) {
|
||||||
|
if (!sender.hasPermission(Permissions.Commands.RELOAD.get()) && !sender.hasPermission(Permissions.Commands.VERSION.get())) return;
|
||||||
|
sender.sendMessage(Component.text("-----------------------------------------------------").color(NamedTextColor.GRAY));
|
||||||
|
sender.sendMessage(Component.text("VillagerOptimizer Commands").color(VillagerOptimizer.plugin_style.color()));
|
||||||
|
sender.sendMessage(Component.text("-----------------------------------------------------").color(NamedTextColor.GRAY));
|
||||||
|
subCommands.forEach(subCommand -> sender.sendMessage(
|
||||||
|
subCommand.getSyntax().append(Component.text(" - ").color(NamedTextColor.DARK_GRAY)).append(subCommand.getDescription())));
|
||||||
|
sender.sendMessage(
|
||||||
|
Component.text("/optimizevillagers <blockradius>").color(VillagerOptimizer.plugin_style.color())
|
||||||
|
.append(Component.text(" - ").color(NamedTextColor.DARK_GRAY))
|
||||||
|
.append(Component.text("Optimize villagers in a radius").color(NamedTextColor.GRAY))
|
||||||
|
);
|
||||||
|
sender.sendMessage(
|
||||||
|
Component.text("/unoptmizevillagers <blockradius>").color(VillagerOptimizer.plugin_style.color())
|
||||||
|
.append(Component.text(" - ").color(NamedTextColor.DARK_GRAY))
|
||||||
|
.append(Component.text("Unoptimize villagers in a radius").color(NamedTextColor.GRAY))
|
||||||
|
);
|
||||||
|
sender.sendMessage(Component.text("-----------------------------------------------------").color(NamedTextColor.GRAY));
|
||||||
|
}
|
||||||
|
}
|
@ -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.DISABLE.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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().getAsyncScheduler().runNow(plugin, reloadPlugin -> {
|
||||||
|
plugin.reloadPlugin();
|
||||||
|
sender.sendMessage(Component.text("Reload complete.").color(NamedTextColor.GREEN));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
package me.xginko.villageroptimizer.commands.villageroptimizer.subcommands;
|
||||||
|
|
||||||
|
import io.papermc.paper.plugin.configuration.PluginMeta;
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.commands.SubCommand;
|
||||||
|
import me.xginko.villageroptimizer.enums.Permissions;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.TextComponent;
|
||||||
|
import net.kyori.adventure.text.event.ClickEvent;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
||||||
|
public class VersionSubCmd extends SubCommand {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLabel() {
|
||||||
|
return "version";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextComponent getDescription() {
|
||||||
|
return Component.text("Show the plugin version.").color(NamedTextColor.GRAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextComponent getSyntax() {
|
||||||
|
return Component.text("/villageroptimizer version").color(VillagerOptimizer.plugin_style.color());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void perform(CommandSender sender, String[] args) {
|
||||||
|
if (sender.hasPermission(Permissions.Commands.VERSION.get())) {
|
||||||
|
final PluginMeta pluginMeta = VillagerOptimizer.getInstance().getPluginMeta();
|
||||||
|
sender.sendMessage(
|
||||||
|
Component.newline()
|
||||||
|
.append(
|
||||||
|
Component.text(pluginMeta.getName()+" "+pluginMeta.getVersion())
|
||||||
|
.style(VillagerOptimizer.plugin_style)
|
||||||
|
.clickEvent(ClickEvent.openUrl(pluginMeta.getWebsite()))
|
||||||
|
)
|
||||||
|
.append(Component.text(" by ").color(NamedTextColor.GRAY))
|
||||||
|
.append(
|
||||||
|
Component.text(pluginMeta.getAuthors().get(0))
|
||||||
|
.color(NamedTextColor.WHITE)
|
||||||
|
.clickEvent(ClickEvent.openUrl("https://github.com/xGinko"))
|
||||||
|
)
|
||||||
|
.append(Component.newline())
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(VillagerOptimizer.getLang(sender).no_permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,146 @@
|
|||||||
|
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 = ymlFile.getParentFile();
|
||||||
|
if (!parent.exists() && !parent.mkdir())
|
||||||
|
VillagerOptimizer.getLog().severe("Unable to create plugin config directory.");
|
||||||
|
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.restock-optimized-trades", null);
|
||||||
|
config.addDefault("gameplay.level-optimized-profession", null);
|
||||||
|
config.addDefault("gameplay.rename-optimized-villagers.enable", true);
|
||||||
|
config.addDefault("gameplay.villagers-spawn-as-adults.enable", false);
|
||||||
|
config.addDefault("gameplay.prevent-trading-with-unoptimized.enable", false);
|
||||||
|
config.addDefault("gameplay.prevent-entities-from-targeting-optimized.enable", true);
|
||||||
|
config.addDefault("gameplay.prevent-damage-to-optimized.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<String> getList(@NotNull String path, @NotNull List<String> def, @NotNull String comment) {
|
||||||
|
config.addDefault(path, def, comment);
|
||||||
|
return config.getStringList(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull List<String> getList(@NotNull String path, @NotNull List<String> def) {
|
||||||
|
config.addDefault(path, def);
|
||||||
|
return config.getStringList(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull ConfigSection getConfigSection(@NotNull String path, @NotNull Map<String, Object> 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<String, Object> 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);
|
||||||
|
}
|
||||||
|
}
|
@ -2,15 +2,12 @@ package me.xginko.villageroptimizer.config;
|
|||||||
|
|
||||||
import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
|
import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
|
||||||
import me.xginko.villageroptimizer.VillagerOptimizer;
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
import me.xginko.villageroptimizer.utils.KyoriUtil;
|
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class LanguageCache {
|
public class LanguageCache {
|
||||||
|
|
||||||
@ -20,7 +17,6 @@ public class LanguageCache {
|
|||||||
public final @NotNull List<Component> nametag_optimize_success, nametag_on_optimize_cooldown, nametag_unoptimize_success,
|
public final @NotNull List<Component> nametag_optimize_success, nametag_on_optimize_cooldown, nametag_unoptimize_success,
|
||||||
block_optimize_success, block_on_optimize_cooldown, block_unoptimize_success,
|
block_optimize_success, block_on_optimize_cooldown, block_unoptimize_success,
|
||||||
workstation_optimize_success, workstation_on_optimize_cooldown, workstation_unoptimize_success,
|
workstation_optimize_success, workstation_on_optimize_cooldown, workstation_unoptimize_success,
|
||||||
activity_optimize_success,
|
|
||||||
command_optimize_success, command_radius_limit_exceed, command_optimize_fail, command_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,
|
command_specify_radius, command_radius_invalid, command_no_villagers_nearby,
|
||||||
trades_restocked, optimize_for_trading, villager_leveling_up;
|
trades_restocked, optimize_for_trading, villager_leveling_up;
|
||||||
@ -31,77 +27,83 @@ public class LanguageCache {
|
|||||||
// Check if the lang folder has already been created
|
// Check if the lang folder has already been created
|
||||||
File parent = langYML.getParentFile();
|
File parent = langYML.getParentFile();
|
||||||
if (!parent.exists() && !parent.mkdir())
|
if (!parent.exists() && !parent.mkdir())
|
||||||
VillagerOptimizer.logger().error("Failed to create lang directory.");
|
VillagerOptimizer.getLog().severe("Unable to create lang directory.");
|
||||||
// Check if the file already exists and save the one from the plugin's resources folder if it does not
|
// Check if the file already exists and save the one from the plugins resources folder if it does not
|
||||||
if (!langYML.exists())
|
if (!langYML.exists())
|
||||||
plugin.saveResource("lang/" + locale + ".yml", false);
|
plugin.saveResource("lang/" + locale + ".yml", false);
|
||||||
// Finally, load the lang file with configmaster
|
// Finally load the lang file with configmaster
|
||||||
this.lang = ConfigFile.loadConfig(langYML);
|
this.lang = ConfigFile.loadConfig(langYML);
|
||||||
|
|
||||||
// General
|
// General
|
||||||
this.no_permission = getTranslation("messages.no-permission",
|
this.no_permission = getTranslation("messages.no-permission",
|
||||||
"<red>You don't have permission to use this command.");
|
"<red>You don't have permission to use this command.");
|
||||||
this.trades_restocked = getListTranslation("messages.trades-restocked",
|
this.trades_restocked = getListTranslation("messages.trades-restocked",
|
||||||
"<green>All trades have been restocked! Next restock in %time%");
|
List.of("<green>All trades have been restocked! Next restock in %time%"));
|
||||||
this.optimize_for_trading = getListTranslation("messages.optimize-to-trade",
|
this.optimize_for_trading = getListTranslation("messages.optimize-to-trade",
|
||||||
"<red>You need to optimize this villager before you can trade with it.");
|
List.of("<red>You need to optimize this villager before you can trade with it."));
|
||||||
this.villager_leveling_up = getListTranslation("messages.villager-leveling-up",
|
this.villager_leveling_up = getListTranslation("messages.villager-leveling-up",
|
||||||
"<yellow>Villager is currently leveling up! You can use the villager again in %time%.");
|
List.of("<yellow>Villager is currently leveling up! You can use the villager again in %time%."));
|
||||||
// Nametag
|
// Nametag
|
||||||
this.nametag_optimize_success = getListTranslation("messages.nametag.optimize-success",
|
this.nametag_optimize_success = getListTranslation("messages.nametag.optimize-success",
|
||||||
"<green>Successfully optimized villager by using a nametag.");
|
List.of("<green>Successfully optimized villager by using a nametag."));
|
||||||
this.nametag_on_optimize_cooldown = getListTranslation("messages.nametag.optimize-on-cooldown",
|
this.nametag_on_optimize_cooldown = getListTranslation("messages.nametag.optimize-on-cooldown",
|
||||||
"<gray>You need to wait %time% until you can optimize this villager again.");
|
List.of("<gray>You need to wait %time% until you can optimize this villager again."));
|
||||||
this.nametag_unoptimize_success = getListTranslation("messages.nametag.unoptimize-success",
|
this.nametag_unoptimize_success = getListTranslation("messages.nametag.unoptimize-success",
|
||||||
"<green>Successfully unoptimized villager by using a nametag.");
|
List.of("<green>Successfully unoptimized villager by using a nametag."));
|
||||||
// Block
|
// Block
|
||||||
this.block_optimize_success = getListTranslation("messages.block.optimize-success",
|
this.block_optimize_success = getListTranslation("messages.block.optimize-success",
|
||||||
"<green>%villagertype% villager successfully optimized using block %blocktype%.");
|
List.of("<green>%villagertype% villager successfully optimized using block %blocktype%."));
|
||||||
this.block_on_optimize_cooldown = getListTranslation("messages.block.optimize-on-cooldown",
|
this.block_on_optimize_cooldown = getListTranslation("messages.block.optimize-on-cooldown",
|
||||||
"<gray>You need to wait %time% until you can optimize this villager again.");
|
List.of("<gray>You need to wait %time% until you can optimize this villager again."));
|
||||||
this.block_unoptimize_success = getListTranslation("messages.block.unoptimize-success",
|
this.block_unoptimize_success = getListTranslation("messages.block.unoptimize-success",
|
||||||
"<green>Successfully unoptimized %villagertype% villager by removing %blocktype%.");
|
List.of("<green>Successfully unoptimized %villagertype% villager by removing %blocktype%."));
|
||||||
// Workstation
|
// Workstation
|
||||||
this.workstation_optimize_success = getListTranslation("messages.workstation.optimize-success",
|
this.workstation_optimize_success = getListTranslation("messages.workstation.optimize-success",
|
||||||
"<green>%villagertype% villager successfully optimized using workstation %blocktype%.");
|
List.of("<green>%villagertype% villager successfully optimized using workstation %workstation%."));
|
||||||
this.workstation_on_optimize_cooldown = getListTranslation("messages.workstation.optimize-on-cooldown",
|
this.workstation_on_optimize_cooldown = getListTranslation("messages.workstation.optimize-on-cooldown",
|
||||||
"<gray>You need to wait %time% until you can optimize this villager again.");
|
List.of("<gray>You need to wait %time% until you can optimize this villager again."));
|
||||||
this.workstation_unoptimize_success = getListTranslation("messages.workstation.unoptimize-success",
|
this.workstation_unoptimize_success = getListTranslation("messages.workstation.unoptimize-success",
|
||||||
"<green>Successfully unoptimized %villagertype% villager by removing workstation block %blocktype%.");
|
List.of("<green>Successfully unoptimized %villagertype% villager by removing workstation block %workstation%."));
|
||||||
// Activity
|
|
||||||
this.activity_optimize_success = getListTranslation("messages.activity.optimized-near-you",
|
|
||||||
"<gray>%amount% villagers close to you were automatically optimized due to high activity.");
|
|
||||||
|
|
||||||
// Command
|
// Command
|
||||||
this.command_optimize_success = getListTranslation("messages.command.optimize-success",
|
this.command_optimize_success = getListTranslation("messages.command.optimize-success",
|
||||||
"<green>Successfully optimized %amount% villager(s) in a radius of %radius% blocks.");
|
List.of("<green>Successfully optimized %amount% villager(s) in a radius of %radius% blocks."));
|
||||||
this.command_radius_limit_exceed = getListTranslation("messages.command.radius-limit-exceed",
|
this.command_radius_limit_exceed = getListTranslation("messages.command.radius-limit-exceed",
|
||||||
"<red>The radius you entered exceeds the limit of %distance% blocks.");
|
List.of("<red>The radius you entered exceeds the limit of %distance% blocks."));
|
||||||
this.command_optimize_fail = getListTranslation("messages.command.optimize-fail",
|
this.command_optimize_fail = getListTranslation("messages.command.optimize-fail",
|
||||||
"<gray>%amount% villagers couldn't be optimized because they have recently been optimized.");
|
List.of("<gray>%amount% villagers couldn't be optimized because they have recently been optimized."));
|
||||||
this.command_unoptimize_success = getListTranslation("messages.command.unoptimize-success",
|
this.command_unoptimize_success = getListTranslation("messages.command.unoptimize-success",
|
||||||
"<green>Successfully unoptimized %amount% villager(s) in a radius of %radius% blocks.");
|
List.of("<green>Successfully unoptimized %amount% villager(s) in a radius of %radius% blocks."));
|
||||||
this.command_specify_radius = getListTranslation("messages.command.specify-radius",
|
this.command_specify_radius = getListTranslation("messages.command.specify-radius",
|
||||||
"<red>Please specify a radius.");
|
List.of("<red>Please specify a radius."));
|
||||||
this.command_radius_invalid = getListTranslation("messages.command.radius-invalid",
|
this.command_radius_invalid = getListTranslation("messages.command.radius-invalid",
|
||||||
"<red>The radius you entered is not a valid number. Try again.");
|
List.of("<red>The radius you entered is not a valid number. Try again."));
|
||||||
this.command_no_villagers_nearby = getListTranslation("messages.command.no-villagers-nearby",
|
this.command_no_villagers_nearby = getListTranslation("messages.command.no-villagers-nearby",
|
||||||
"<gray>Couldn't find any employed villagers within a radius of %radius%.");
|
List.of("<gray>Couldn't find any employed villagers within a radius of %radius%."));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.lang.save();
|
lang.save();
|
||||||
} catch (Throwable throwable) {
|
} catch (Exception e) {
|
||||||
VillagerOptimizer.logger().error("Failed to save language file: " + langYML.getName(), throwable);
|
VillagerOptimizer.getLog().severe("Failed to save language file: "+ lang.getFile().getName() +" - " + e.getLocalizedMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NotNull Component getTranslation(@NotNull String path, @NotNull String defaultTranslation) {
|
public @NotNull Component getTranslation(@NotNull String path, @NotNull String defaultTranslation) {
|
||||||
this.lang.addDefault(path, defaultTranslation);
|
lang.addDefault(path, defaultTranslation);
|
||||||
return MiniMessage.miniMessage().deserialize(KyoriUtil.translateChatColor(this.lang.getString(path, defaultTranslation)));
|
return MiniMessage.miniMessage().deserialize(lang.getString(path, defaultTranslation));
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NotNull List<Component> getListTranslation(@NotNull String path, @NotNull String... defaultTranslation) {
|
public @NotNull Component getTranslation(@NotNull String path, @NotNull String defaultTranslation, @NotNull String comment) {
|
||||||
this.lang.addDefault(path, Arrays.asList(defaultTranslation));
|
lang.addDefault(path, defaultTranslation, comment);
|
||||||
return this.lang.getStringList(path).stream().map(KyoriUtil::translateChatColor).map(MiniMessage.miniMessage()::deserialize).collect(Collectors.toList());
|
return MiniMessage.miniMessage().deserialize(lang.getString(path, defaultTranslation));
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull List<Component> getListTranslation(@NotNull String path, @NotNull List<String> defaultTranslation) {
|
||||||
|
lang.addDefault(path, defaultTranslation);
|
||||||
|
return lang.getStringList(path).stream().map(MiniMessage.miniMessage()::deserialize).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull List<Component> getListTranslation(@NotNull String path, @NotNull List<String> defaultTranslation, @NotNull String comment) {
|
||||||
|
lang.addDefault(path, defaultTranslation, comment);
|
||||||
|
return lang.getStringList(path).stream().map(MiniMessage.miniMessage()::deserialize).toList();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package me.xginko.villageroptimizer.enums;
|
||||||
|
|
||||||
|
public enum OptimizationType {
|
||||||
|
|
||||||
|
COMMAND,
|
||||||
|
NAMETAG,
|
||||||
|
WORKSTATION,
|
||||||
|
BLOCK,
|
||||||
|
NONE
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package me.xginko.villageroptimizer.enums;
|
||||||
|
|
||||||
|
public class Permissions {
|
||||||
|
public enum Commands {
|
||||||
|
VERSION("villageroptimizer.cmd.version"),
|
||||||
|
RELOAD("villageroptimizer.cmd.reload"),
|
||||||
|
DISABLE("villageroptimizer.cmd.disable"),
|
||||||
|
OPTIMIZE_RADIUS("villageroptimizer.cmd.optimize"),
|
||||||
|
UNOPTIMIZE_RADIUS("villageroptimizer.cmd.unoptimize");
|
||||||
|
private final String permission;
|
||||||
|
Commands(String permission) {
|
||||||
|
this.permission = permission;
|
||||||
|
}
|
||||||
|
public String get() {
|
||||||
|
return permission;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public enum Optimize {
|
||||||
|
NAMETAG("villageroptimizer.optimize.nametag"),
|
||||||
|
BLOCK("villageroptimizer.optimize.block"),
|
||||||
|
WORKSTATION("villageroptimizer.optimize.workstation");
|
||||||
|
private final String permission;
|
||||||
|
Optimize(String permission) {
|
||||||
|
this.permission = permission;
|
||||||
|
}
|
||||||
|
public String get() {
|
||||||
|
return permission;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public enum Bypass {
|
||||||
|
TRADE_PREVENTION("villageroptimizer.bypass.tradeprevention"),
|
||||||
|
RESTOCK_COOLDOWN("villageroptimizer.bypass.restockcooldown"),
|
||||||
|
NAMETAG_COOLDOWN("villageroptimizer.bypass.nametagcooldown"),
|
||||||
|
BLOCK_COOLDOWN("villageroptimizer.bypass.blockcooldown"),
|
||||||
|
WORKSTATION_COOLDOWN("villageroptimizer.bypass.workstationcooldown"),
|
||||||
|
COMMAND_COOLDOWN("villageroptimizer.bypass.commandcooldown");
|
||||||
|
private final String permission;
|
||||||
|
Bypass(String permission) {
|
||||||
|
this.permission = permission;
|
||||||
|
}
|
||||||
|
public String get() {
|
||||||
|
return permission;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
package me.xginko.villageroptimizer.events;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.WrappedVillager;
|
||||||
|
import me.xginko.villageroptimizer.enums.OptimizationType;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.Cancellable;
|
||||||
|
import org.bukkit.event.Event;
|
||||||
|
import org.bukkit.event.HandlerList;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public class VillagerOptimizeEvent extends Event implements Cancellable {
|
||||||
|
|
||||||
|
private static final @NotNull HandlerList handlers = new HandlerList();
|
||||||
|
private final @NotNull WrappedVillager wrappedVillager;
|
||||||
|
private @NotNull OptimizationType type;
|
||||||
|
private final @Nullable Player whoOptimised;
|
||||||
|
private boolean isCancelled = false;
|
||||||
|
|
||||||
|
public VillagerOptimizeEvent(@NotNull WrappedVillager wrappedVillager, @NotNull OptimizationType type, @Nullable Player whoOptimised, boolean isAsync) throws IllegalArgumentException {
|
||||||
|
super(isAsync);
|
||||||
|
this.wrappedVillager = wrappedVillager;
|
||||||
|
this.whoOptimised = whoOptimised;
|
||||||
|
if (type.equals(OptimizationType.NONE)) {
|
||||||
|
throw new IllegalArgumentException("OptimizationType can't be NONE.");
|
||||||
|
} else {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public VillagerOptimizeEvent(@NotNull WrappedVillager wrappedVillager, @NotNull OptimizationType type, @Nullable Player whoOptimised) throws IllegalArgumentException {
|
||||||
|
this.wrappedVillager = wrappedVillager;
|
||||||
|
this.whoOptimised = whoOptimised;
|
||||||
|
if (type.equals(OptimizationType.NONE)) {
|
||||||
|
throw new IllegalArgumentException("OptimizationType can't be NONE.");
|
||||||
|
} else {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull WrappedVillager getWrappedVillager() {
|
||||||
|
return wrappedVillager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull OptimizationType getOptimizationType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOptimizationType(@NotNull OptimizationType type) throws IllegalArgumentException {
|
||||||
|
if (type.equals(OptimizationType.NONE)) {
|
||||||
|
throw new IllegalArgumentException("OptimizationType can't be NONE.");
|
||||||
|
} else {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Player getWhoOptimised() {
|
||||||
|
return whoOptimised;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCancelled(boolean cancel) {
|
||||||
|
isCancelled = cancel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCancelled() {
|
||||||
|
return isCancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull HandlerList getHandlers() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HandlerList getHandlerList() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
package me.xginko.villageroptimizer.events;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.WrappedVillager;
|
||||||
|
import me.xginko.villageroptimizer.enums.OptimizationType;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.Cancellable;
|
||||||
|
import org.bukkit.event.Event;
|
||||||
|
import org.bukkit.event.HandlerList;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public class VillagerUnoptimizeEvent extends Event implements Cancellable {
|
||||||
|
|
||||||
|
private static final @NotNull HandlerList handlers = new HandlerList();
|
||||||
|
private final @NotNull WrappedVillager wrappedVillager;
|
||||||
|
private final @NotNull OptimizationType unoptimizeType;
|
||||||
|
private final @Nullable Player whoUnoptimized;
|
||||||
|
private boolean isCancelled = false;
|
||||||
|
|
||||||
|
public VillagerUnoptimizeEvent(@NotNull WrappedVillager wrappedVillager, @Nullable Player whoUnoptimized, @NotNull OptimizationType unoptimizeType, boolean isAsync) {
|
||||||
|
super(isAsync);
|
||||||
|
this.wrappedVillager = wrappedVillager;
|
||||||
|
this.whoUnoptimized = whoUnoptimized;
|
||||||
|
this.unoptimizeType = unoptimizeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VillagerUnoptimizeEvent(@NotNull WrappedVillager wrappedVillager, @Nullable Player whoUnoptimized, @NotNull OptimizationType unoptimizeType) {
|
||||||
|
this.wrappedVillager = wrappedVillager;
|
||||||
|
this.whoUnoptimized = whoUnoptimized;
|
||||||
|
this.unoptimizeType = unoptimizeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull WrappedVillager getWrappedVillager() {
|
||||||
|
return wrappedVillager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Player getWhoUnoptimized() {
|
||||||
|
return whoUnoptimized;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull OptimizationType getWhichTypeUnoptimized() {
|
||||||
|
return unoptimizeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCancelled() {
|
||||||
|
return isCancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCancelled(boolean cancel) {
|
||||||
|
this.isCancelled = cancel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull HandlerList getHandlers() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HandlerList getHandlerList() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,182 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules;
|
||||||
|
|
||||||
|
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
|
||||||
|
import me.xginko.villageroptimizer.VillagerCache;
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.config.Config;
|
||||||
|
import me.xginko.villageroptimizer.utils.LogUtil;
|
||||||
|
import org.bukkit.Chunk;
|
||||||
|
import org.bukkit.Server;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.HandlerList;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||||
|
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public class VillagerChunkLimit implements VillagerOptimizerModule, Listener {
|
||||||
|
|
||||||
|
private final VillagerCache villagerCache;
|
||||||
|
private ScheduledTask periodic_chunk_check;
|
||||||
|
private final List<Villager.Profession> non_optimized_removal_priority = new ArrayList<>(16);
|
||||||
|
private final List<Villager.Profession> optimized_removal_priority = new ArrayList<>(16);
|
||||||
|
private final long check_period;
|
||||||
|
private final int non_optimized_max_per_chunk, optimized_max_per_chunk;
|
||||||
|
private final boolean log_enabled, skip_unloaded_entity_chunks;
|
||||||
|
|
||||||
|
protected VillagerChunkLimit() {
|
||||||
|
shouldEnable();
|
||||||
|
this.villagerCache = VillagerOptimizer.getCache();
|
||||||
|
Config config = VillagerOptimizer.getConfiguration();
|
||||||
|
config.addComment("villager-chunk-limit.enable", """
|
||||||
|
Checks chunks for too many villagers and removes excess villagers based on priority.""");
|
||||||
|
this.check_period = config.getInt("villager-chunk-limit.check-period-in-ticks", 600, """
|
||||||
|
Check all loaded chunks every X ticks. 1 second = 20 ticks\s
|
||||||
|
A shorter delay in between checks is more efficient but is also more resource intense.\s
|
||||||
|
A larger delay is less resource intense but could become inefficient.""");
|
||||||
|
this.skip_unloaded_entity_chunks = config.getBoolean("villager-chunk-limit.skip-if-chunk-has-not-loaded-entities", true,
|
||||||
|
"Does not check chunks that don't have their entities loaded.");
|
||||||
|
this.log_enabled = config.getBoolean("villager-chunk-limit.log-removals", false);
|
||||||
|
this.non_optimized_max_per_chunk = config.getInt("villager-chunk-limit.unoptimized.max-per-chunk", 20,
|
||||||
|
"The maximum amount of unoptimized villagers per chunk.");
|
||||||
|
config.getList("villager-chunk-limit.unoptimized.removal-priority", List.of(
|
||||||
|
"NONE", "NITWIT", "SHEPHERD", "FISHERMAN", "BUTCHER", "CARTOGRAPHER", "LEATHERWORKER",
|
||||||
|
"FLETCHER", "MASON", "FARMER", "ARMORER", "TOOLSMITH", "WEAPONSMITH", "CLERIC", "LIBRARIAN"
|
||||||
|
), """
|
||||||
|
Professions that are in the top of the list are going to be scheduled for removal first.\s
|
||||||
|
Use enums from https://jd.papermc.io/paper/1.20/org/bukkit/entity/Villager.Profession.html"""
|
||||||
|
).forEach(configuredProfession -> {
|
||||||
|
try {
|
||||||
|
Villager.Profession profession = Villager.Profession.valueOf(configuredProfession);
|
||||||
|
this.non_optimized_removal_priority.add(profession);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
LogUtil.moduleLog(Level.WARNING, "villager-chunk-limit.unoptimized",
|
||||||
|
"Villager profession '"+configuredProfession+"' not recognized. " +
|
||||||
|
"Make sure you're using the correct profession enums from https://jd.papermc.io/paper/1.20/org/bukkit/entity/Villager.Profession.html.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.optimized_max_per_chunk = config.getInt("villager-chunk-limit.optimized.max-per-chunk", 60,
|
||||||
|
"The maximum amount of optimized villagers per chunk.");
|
||||||
|
config.getList("villager-chunk-limit.optimized.removal-priority", List.of(
|
||||||
|
"NONE", "NITWIT", "SHEPHERD", "FISHERMAN", "BUTCHER", "CARTOGRAPHER", "LEATHERWORKER",
|
||||||
|
"FLETCHER", "MASON", "FARMER", "ARMORER", "TOOLSMITH", "WEAPONSMITH", "CLERIC", "LIBRARIAN"
|
||||||
|
)).forEach(configuredProfession -> {
|
||||||
|
try {
|
||||||
|
Villager.Profession profession = Villager.Profession.valueOf(configuredProfession);
|
||||||
|
this.optimized_removal_priority.add(profession);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
LogUtil.moduleLog(Level.WARNING, "villager-chunk-limit.optimized",
|
||||||
|
"Villager profession '"+configuredProfession+"' not recognized. " +
|
||||||
|
"Make sure you're using the correct profession enums from https://jd.papermc.io/paper/1.20/org/bukkit/entity/Villager.Profession.html.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
final VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
final Server server = plugin.getServer();
|
||||||
|
server.getPluginManager().registerEvents(this, plugin);
|
||||||
|
this.periodic_chunk_check = server.getGlobalRegionScheduler().runAtFixedRate(plugin, periodic_chunk_check -> {
|
||||||
|
for (World world : server.getWorlds()) {
|
||||||
|
for (Chunk chunk : world.getLoadedChunks()) {
|
||||||
|
plugin.getServer().getRegionScheduler().run(
|
||||||
|
plugin, world, chunk.getX(), chunk.getZ(), check_chunk -> this.manageVillagerCount(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);
|
||||||
|
if (periodic_chunk_check != null) periodic_chunk_check.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
private void onCreatureSpawn(CreatureSpawnEvent event) {
|
||||||
|
Entity spawned = event.getEntity();
|
||||||
|
if (spawned.getType().equals(EntityType.VILLAGER)) {
|
||||||
|
this.manageVillagerCount(spawned.getChunk());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||||
|
private void onInteract(PlayerInteractEntityEvent event) {
|
||||||
|
Entity clicked = event.getRightClicked();
|
||||||
|
if (clicked.getType().equals(EntityType.VILLAGER)) {
|
||||||
|
this.manageVillagerCount(clicked.getChunk());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void manageVillagerCount(@NotNull Chunk chunk) {
|
||||||
|
if (skip_unloaded_entity_chunks && !chunk.isEntitiesLoaded()) return;
|
||||||
|
|
||||||
|
// Collect all optimized and unoptimized villagers in that chunk
|
||||||
|
List<Villager> optimized_villagers = new ArrayList<>();
|
||||||
|
List<Villager> not_optimized_villagers = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Entity entity : chunk.getEntities()) {
|
||||||
|
if (entity.getType().equals(EntityType.VILLAGER)) {
|
||||||
|
Villager villager = (Villager) entity;
|
||||||
|
if (villagerCache.getOrAdd(villager).isOptimized()) {
|
||||||
|
optimized_villagers.add(villager);
|
||||||
|
} else {
|
||||||
|
not_optimized_villagers.add(villager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there are more unoptimized villagers in that chunk than allowed
|
||||||
|
final int not_optimized_villagers_too_many = not_optimized_villagers.size() - non_optimized_max_per_chunk;
|
||||||
|
if (not_optimized_villagers_too_many > 0) {
|
||||||
|
// Sort villagers by profession priority
|
||||||
|
not_optimized_villagers.sort(Comparator.comparingInt(villager -> {
|
||||||
|
final Villager.Profession profession = villager.getProfession();
|
||||||
|
return non_optimized_removal_priority.contains(profession) ? non_optimized_removal_priority.indexOf(profession) : Integer.MAX_VALUE;
|
||||||
|
}));
|
||||||
|
// Remove prioritized villagers that are too many
|
||||||
|
for (int i = 0; i < not_optimized_villagers_too_many; i++) {
|
||||||
|
Villager villager = not_optimized_villagers.get(i);
|
||||||
|
villager.remove();
|
||||||
|
if (log_enabled) LogUtil.moduleLog(Level.INFO, "villager-chunk-limit",
|
||||||
|
"Removed unoptimized villager of profession type '"+villager.getProfession().name()+"' at "+villager.getLocation()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there are more optimized villagers in that chunk than allowed
|
||||||
|
final int optimized_villagers_too_many = optimized_villagers.size() - optimized_max_per_chunk;
|
||||||
|
if (optimized_villagers_too_many > 0) {
|
||||||
|
// Sort villagers by profession priority
|
||||||
|
optimized_villagers.sort(Comparator.comparingInt(villager -> {
|
||||||
|
final Villager.Profession profession = villager.getProfession();
|
||||||
|
return optimized_removal_priority.contains(profession) ? optimized_removal_priority.indexOf(profession) : Integer.MAX_VALUE;
|
||||||
|
}));
|
||||||
|
// Remove prioritized villagers that are too many
|
||||||
|
for (int i = 0; i < optimized_villagers_too_many; i++) {
|
||||||
|
Villager villager = optimized_villagers.get(i);
|
||||||
|
villager.remove();
|
||||||
|
if (log_enabled) LogUtil.moduleLog(Level.INFO, "villager-chunk-limit",
|
||||||
|
"Removed optimized villager of profession type '"+villager.getProfession().name()+"' at "+villager.getLocation()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.modules.gameplay.*;
|
||||||
|
import me.xginko.villageroptimizer.modules.optimization.OptimizeByBlock;
|
||||||
|
import me.xginko.villageroptimizer.modules.optimization.OptimizeByNametag;
|
||||||
|
import me.xginko.villageroptimizer.modules.optimization.OptimizeByWorkstation;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
public interface VillagerOptimizerModule {
|
||||||
|
|
||||||
|
void enable();
|
||||||
|
void disable();
|
||||||
|
boolean shouldEnable();
|
||||||
|
|
||||||
|
HashSet<VillagerOptimizerModule> modules = new HashSet<>();
|
||||||
|
|
||||||
|
static void reloadModules() {
|
||||||
|
modules.forEach(VillagerOptimizerModule::disable);
|
||||||
|
modules.clear();
|
||||||
|
|
||||||
|
modules.add(new OptimizeByNametag());
|
||||||
|
modules.add(new OptimizeByBlock());
|
||||||
|
modules.add(new OptimizeByWorkstation());
|
||||||
|
|
||||||
|
modules.add(new RestockOptimizedTrades());
|
||||||
|
modules.add(new LevelOptimizedProfession());
|
||||||
|
modules.add(new RenameOptimizedVillagers());
|
||||||
|
modules.add(new MakeVillagersSpawnAdult());
|
||||||
|
modules.add(new PreventUnoptimizedTrading());
|
||||||
|
modules.add(new PreventOptimizedTargeting());
|
||||||
|
modules.add(new PreventOptimizedDamage());
|
||||||
|
|
||||||
|
modules.add(new VillagerChunkLimit());
|
||||||
|
|
||||||
|
modules.forEach(module -> {
|
||||||
|
if (module.shouldEnable()) module.enable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules.gameplay;
|
||||||
|
|
||||||
|
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 LevelOptimizedProfession implements VillagerOptimizerModule, Listener {
|
||||||
|
|
||||||
|
private final VillagerOptimizer plugin;
|
||||||
|
private final VillagerCache villagerCache;
|
||||||
|
private final boolean notify_player;
|
||||||
|
private final long cooldown;
|
||||||
|
|
||||||
|
public LevelOptimizedProfession() {
|
||||||
|
shouldEnable();
|
||||||
|
this.plugin = VillagerOptimizer.getInstance();
|
||||||
|
this.villagerCache = VillagerOptimizer.getCache();
|
||||||
|
Config config = VillagerOptimizer.getConfiguration();
|
||||||
|
config.addComment("gameplay.level-optimized-profession", """
|
||||||
|
This is needed to allow optimized villagers to level up.\s
|
||||||
|
Temporarily enables the villagers AI to allow it to level up and then disables it again.""");
|
||||||
|
this.cooldown = config.getInt("gameplay.level-optimized-profession.level-check-cooldown-seconds", 5, """
|
||||||
|
Cooldown in seconds until the level of a villager will be checked and updated again.\s
|
||||||
|
Recommended to leave as is.""") * 1000L;
|
||||||
|
this.notify_player = config.getBoolean("gameplay.level-optimized-profession.notify-player", true,
|
||||||
|
"Tell players to wait when a villager is leveling up.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disable() {
|
||||||
|
HandlerList.unregisterAll(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||||
|
private void onTradeScreenClose(InventoryCloseEvent event) {
|
||||||
|
if (
|
||||||
|
event.getInventory().getType().equals(InventoryType.MERCHANT)
|
||||||
|
&& event.getInventory().getHolder() instanceof Villager villager
|
||||||
|
) {
|
||||||
|
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
|
||||||
|
if (!wVillager.isOptimized()) return;
|
||||||
|
|
||||||
|
if (wVillager.canLevelUp(cooldown)) {
|
||||||
|
if (wVillager.calculateLevel() > villager.getVillagerLevel()) {
|
||||||
|
villager.getScheduler().run(plugin, enableAI -> {
|
||||||
|
villager.addPotionEffect(new PotionEffect(PotionEffectType.SLOW, 120, 120, false, false));
|
||||||
|
villager.setAware(true);
|
||||||
|
}, null);
|
||||||
|
villager.getScheduler().runDelayed(plugin, disableAI -> {
|
||||||
|
villager.setAware(false);
|
||||||
|
wVillager.saveLastLevelUp();
|
||||||
|
}, null, 100L);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (notify_player) {
|
||||||
|
Player player = (Player) event.getPlayer();
|
||||||
|
final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%time%")
|
||||||
|
.replacement(CommonUtil.formatTime(wVillager.getLevelCooldownMillis(cooldown)))
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).villager_leveling_up.forEach(line -> player.sendMessage(line.replaceText(timeLeft)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
package me.xginko.villageroptimizer.modules.gameplay;
|
package me.xginko.villageroptimizer.modules.gameplay;
|
||||||
|
|
||||||
import com.cryptomorin.xseries.XEntityType;
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
import org.bukkit.entity.Villager;
|
import org.bukkit.entity.Villager;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.EventPriority;
|
import org.bukkit.event.EventPriority;
|
||||||
@ -9,19 +10,13 @@ import org.bukkit.event.HandlerList;
|
|||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||||
|
|
||||||
public class MakeVillagersSpawnAdult extends VillagerOptimizerModule implements Listener {
|
public class MakeVillagersSpawnAdult implements VillagerOptimizerModule, Listener {
|
||||||
|
|
||||||
public MakeVillagersSpawnAdult() {
|
public MakeVillagersSpawnAdult() {}
|
||||||
super("gameplay.villagers-spawn-as-adults");
|
|
||||||
config.master().addComment(configPath + ".enable",
|
|
||||||
"Spawned villagers will immediately be adults.\n" +
|
|
||||||
"This is to save some more resources as players don't have to keep unoptimized\n" +
|
|
||||||
"villagers loaded because they have to wait for them to turn into adults before they can\n" +
|
|
||||||
"optimize them.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enable() {
|
public void enable() {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,13 +27,17 @@ public class MakeVillagersSpawnAdult extends VillagerOptimizerModule implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldEnable() {
|
public boolean shouldEnable() {
|
||||||
return config.getBoolean(configPath + ".enable", false);
|
return VillagerOptimizer.getConfiguration().getBoolean("gameplay.villagers-spawn-as-adults.enable", false, """
|
||||||
|
Spawned villagers will immediately be adults.\s
|
||||||
|
This is to save some more resources as players don't have to keep unoptimized\s
|
||||||
|
villagers loaded because they have to wait for them to turn into adults before they can\s
|
||||||
|
optimize them.""");
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
private void onVillagerSpawn(CreatureSpawnEvent event) {
|
private void onVillagerSpawn(CreatureSpawnEvent event) {
|
||||||
if (event.getEntityType() == XEntityType.VILLAGER.get()) {
|
if (event.getEntityType().equals(EntityType.VILLAGER)) {
|
||||||
final Villager villager = (Villager) event.getEntity();
|
Villager villager = (Villager) event.getEntity();
|
||||||
if (!villager.isAdult()) villager.setAdult();
|
if (!villager.isAdult()) villager.setAdult();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules.gameplay;
|
||||||
|
|
||||||
|
import io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent;
|
||||||
|
import me.xginko.villageroptimizer.VillagerCache;
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.config.Config;
|
||||||
|
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
||||||
|
import me.xginko.villageroptimizer.utils.LogUtil;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.HandlerList;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.entity.EntityDamageEvent;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
public class PreventOptimizedDamage implements VillagerOptimizerModule, Listener {
|
||||||
|
|
||||||
|
private final VillagerCache villagerCache;
|
||||||
|
private final HashSet<EntityDamageEvent.DamageCause> damage_causes_to_cancel = new HashSet<>();
|
||||||
|
private final boolean push;
|
||||||
|
|
||||||
|
public PreventOptimizedDamage() {
|
||||||
|
shouldEnable();
|
||||||
|
this.villagerCache = VillagerOptimizer.getCache();
|
||||||
|
Config config = VillagerOptimizer.getConfiguration();
|
||||||
|
config.addComment("gameplay.prevent-damage-to-optimized.enable",
|
||||||
|
"Configure what kind of damage you want to cancel for optimized villagers here.");
|
||||||
|
this.push = config.getBoolean("gameplay.prevent-damage-to-optimized.prevent-push-from-attack", true,
|
||||||
|
"Prevents optimized villagers from getting pushed by an attacking entity");
|
||||||
|
config.getList("gameplay.prevent-damage-to-optimized.damage-causes-to-cancel",
|
||||||
|
Arrays.stream(EntityDamageEvent.DamageCause.values()).map(Enum::name).sorted().toList(), """
|
||||||
|
These are all current entries in the game. Remove what you do not need blocked.\s
|
||||||
|
If you want a description or need to add a previously removed type, refer to:\s
|
||||||
|
https://jd.papermc.io/paper/1.20/org/bukkit/event/entity/EntityDamageEvent.DamageCause.html"""
|
||||||
|
).forEach(configuredDamageCause -> {
|
||||||
|
try {
|
||||||
|
EntityDamageEvent.DamageCause damageCause = EntityDamageEvent.DamageCause.valueOf(configuredDamageCause);
|
||||||
|
this.damage_causes_to_cancel.add(damageCause);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
LogUtil.damageCauseNotRecognized("prevent-damage-to-optimized", configuredDamageCause);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disable() {
|
||||||
|
HandlerList.unregisterAll(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnable() {
|
||||||
|
return VillagerOptimizer.getConfiguration().getBoolean("gameplay.prevent-damage-to-optimized.enable", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
private void onDamageByEntity(EntityDamageEvent event) {
|
||||||
|
if (
|
||||||
|
event.getEntityType().equals(EntityType.VILLAGER)
|
||||||
|
&& damage_causes_to_cancel.contains(event.getCause())
|
||||||
|
&& villagerCache.getOrAdd((Villager) event.getEntity()).isOptimized()
|
||||||
|
) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
private void onPushByEntityAttack(EntityPushedByEntityAttackEvent event) {
|
||||||
|
if (
|
||||||
|
push
|
||||||
|
&& event.getEntityType().equals(EntityType.VILLAGER)
|
||||||
|
&& villagerCache.getOrAdd((Villager) event.getEntity()).isOptimized()
|
||||||
|
) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
package me.xginko.villageroptimizer.modules.gameplay;
|
package me.xginko.villageroptimizer.modules.gameplay;
|
||||||
|
|
||||||
import com.cryptomorin.xseries.XEntityType;
|
import com.destroystokyo.paper.event.entity.EntityPathfindEvent;
|
||||||
|
import me.xginko.villageroptimizer.VillagerCache;
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
||||||
import me.xginko.villageroptimizer.wrapper.WrappedVillager;
|
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
import org.bukkit.entity.Mob;
|
import org.bukkit.entity.Mob;
|
||||||
import org.bukkit.entity.Villager;
|
import org.bukkit.entity.Villager;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
@ -13,16 +15,17 @@ import org.bukkit.event.Listener;
|
|||||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||||
import org.bukkit.event.entity.EntityTargetEvent;
|
import org.bukkit.event.entity.EntityTargetEvent;
|
||||||
|
|
||||||
public class PreventOptimizedTargeting extends VillagerOptimizerModule implements Listener {
|
public class PreventOptimizedTargeting implements VillagerOptimizerModule, Listener {
|
||||||
|
|
||||||
|
private final VillagerCache villagerCache;
|
||||||
|
|
||||||
public PreventOptimizedTargeting() {
|
public PreventOptimizedTargeting() {
|
||||||
super("gameplay.prevent-entities-from-targeting-optimized");
|
this.villagerCache = VillagerOptimizer.getCache();
|
||||||
config.master().addComment(configPath + ".enable",
|
|
||||||
"Prevents hostile entities from targeting optimized villagers.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enable() {
|
public void enable() {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,16 +36,18 @@ public class PreventOptimizedTargeting extends VillagerOptimizerModule implement
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldEnable() {
|
public boolean shouldEnable() {
|
||||||
return config.getBoolean(configPath + ".enable", true);
|
return VillagerOptimizer.getConfiguration().getBoolean("gameplay.prevent-entities-from-targeting-optimized.enable", true,
|
||||||
|
"Prevents hostile entities from targeting optimized villagers.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
private void onTarget(EntityTargetEvent event) {
|
private void onTarget(EntityTargetEvent event) {
|
||||||
final Entity target = event.getTarget();
|
// Yes, instanceof checks would look way more beautiful here but checking type is much faster
|
||||||
|
Entity target = event.getTarget();
|
||||||
if (
|
if (
|
||||||
target != null
|
target != null
|
||||||
&& target.getType() == XEntityType.VILLAGER.get()
|
&& target.getType().equals(EntityType.VILLAGER)
|
||||||
&& wrapperCache.get((Villager) target, WrappedVillager::new).isOptimized()
|
&& villagerCache.getOrAdd((Villager) target).isOptimized()
|
||||||
) {
|
) {
|
||||||
event.setTarget(null);
|
event.setTarget(null);
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
@ -50,12 +55,12 @@ public class PreventOptimizedTargeting extends VillagerOptimizerModule implement
|
|||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
private void onEntityTargetVillager(com.destroystokyo.paper.event.entity.EntityPathfindEvent event) {
|
private void onEntityTargetVillager(EntityPathfindEvent event) {
|
||||||
final Entity target = event.getTargetEntity();
|
Entity target = event.getTargetEntity();
|
||||||
if (
|
if (
|
||||||
target != null
|
target != null
|
||||||
&& target.getType() == XEntityType.VILLAGER.get()
|
&& target.getType().equals(EntityType.VILLAGER)
|
||||||
&& wrapperCache.get((Villager) target, WrappedVillager::new).isOptimized()
|
&& villagerCache.getOrAdd((Villager) target).isOptimized()
|
||||||
) {
|
) {
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
}
|
}
|
||||||
@ -64,11 +69,11 @@ public class PreventOptimizedTargeting extends VillagerOptimizerModule implement
|
|||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
private void onEntityAttackVillager(EntityDamageByEntityEvent event) {
|
private void onEntityAttackVillager(EntityDamageByEntityEvent event) {
|
||||||
if (
|
if (
|
||||||
event.getEntityType() == XEntityType.VILLAGER.get()
|
event.getEntityType().equals(EntityType.VILLAGER)
|
||||||
&& event.getDamager() instanceof Mob
|
&& event.getDamager() instanceof Mob attacker
|
||||||
&& wrapperCache.get((Villager) event.getEntity(), WrappedVillager::new).isOptimized()
|
&& villagerCache.getOrAdd((Villager) event.getEntity()).isOptimized()
|
||||||
) {
|
) {
|
||||||
((Mob) event.getDamager()).setTarget(null);
|
attacker.setTarget(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules.gameplay;
|
||||||
|
|
||||||
|
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 notify_player;
|
||||||
|
|
||||||
|
public PreventUnoptimizedTrading() {
|
||||||
|
shouldEnable();
|
||||||
|
this.villagerCache = VillagerOptimizer.getCache();
|
||||||
|
Config config = VillagerOptimizer.getConfiguration();
|
||||||
|
config.addComment("gameplay.prevent-trading-with-unoptimized.enable", """
|
||||||
|
Will prevent players from selecting and using trades of unoptimized villagers.\s
|
||||||
|
Use this if you have a lot of villagers and therefore want to force your players to optimize them.\s
|
||||||
|
Inventories can still be opened so players can move villagers around.""");
|
||||||
|
this.notify_player = config.getBoolean("gameplay.prevent-trading-with-unoptimized.notify-player", true,
|
||||||
|
"Sends players a message when they try to trade with an unoptimized villager.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public 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 (notify_player)
|
||||||
|
VillagerOptimizer.getLang(player.locale()).optimize_for_trading.forEach(player::sendMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||||
|
private void onInventoryClick(InventoryClickEvent event) {
|
||||||
|
Player player = (Player) event.getWhoClicked();
|
||||||
|
if (player.hasPermission(Permissions.Bypass.TRADE_PREVENTION.get())) return;
|
||||||
|
if (
|
||||||
|
event.getInventory().getType().equals(InventoryType.MERCHANT)
|
||||||
|
&& event.getInventory().getHolder() instanceof Villager villager
|
||||||
|
&& !villagerCache.getOrAdd(villager).isOptimized()
|
||||||
|
) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
if (notify_player)
|
||||||
|
VillagerOptimizer.getLang(player.locale()).optimize_for_trading.forEach(player::sendMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules.gameplay;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.WrappedVillager;
|
||||||
|
import me.xginko.villageroptimizer.config.Config;
|
||||||
|
import me.xginko.villageroptimizer.events.VillagerOptimizeEvent;
|
||||||
|
import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent;
|
||||||
|
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.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;
|
||||||
|
|
||||||
|
public RenameOptimizedVillagers() {
|
||||||
|
shouldEnable();
|
||||||
|
this.plugin = VillagerOptimizer.getInstance();
|
||||||
|
Config config = VillagerOptimizer.getConfiguration();
|
||||||
|
config.addComment("gameplay.rename-optimized-villagers.enable", """
|
||||||
|
Will change a villager's name to the name configured below when they are optimized.\s
|
||||||
|
These names will be removed when unoptimized again if they were not changed in the meantime.
|
||||||
|
""");
|
||||||
|
this.optimized_name = MiniMessage.miniMessage().deserialize(config.getString("gameplay.rename-optimized-villagers.optimized-name", "<green>Optimized",
|
||||||
|
"The name that will be used to mark optimized villagers. Uses MiniMessage format."));
|
||||||
|
this.overwrite_previous_name = config.getBoolean("gameplay.rename-optimized-villagers.overwrite-existing-name", false,
|
||||||
|
"If set to true, will rename even if the villager has already been named.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disable() {
|
||||||
|
HandlerList.unregisterAll(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnable() {
|
||||||
|
return VillagerOptimizer.getConfiguration().getBoolean("gameplay.rename-optimized-villagers.enable", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
private void onOptimize(VillagerOptimizeEvent event) {
|
||||||
|
WrappedVillager wVillager = event.getWrappedVillager();
|
||||||
|
Villager villager = wVillager.villager();
|
||||||
|
|
||||||
|
villager.getScheduler().runDelayed(plugin, nameOptimized -> {
|
||||||
|
if (overwrite_previous_name || villager.customName() == null) {
|
||||||
|
villager.customName(optimized_name);
|
||||||
|
wVillager.memorizeName(optimized_name);
|
||||||
|
}
|
||||||
|
}, null, 10L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
private void onUnOptimize(VillagerUnoptimizeEvent event) {
|
||||||
|
WrappedVillager wVillager = event.getWrappedVillager();
|
||||||
|
Villager villager = wVillager.villager();
|
||||||
|
|
||||||
|
villager.getScheduler().runDelayed(plugin, unNameOptimized -> {
|
||||||
|
final Component currentName = villager.customName();
|
||||||
|
final Component memorizedName = wVillager.getMemorizedName();
|
||||||
|
if (currentName != null && currentName.equals(memorizedName))
|
||||||
|
villager.customName(null);
|
||||||
|
if (memorizedName != null)
|
||||||
|
wVillager.forgetName();
|
||||||
|
}, null, 10L);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules.gameplay;
|
||||||
|
|
||||||
|
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 RestockOptimizedTrades implements VillagerOptimizerModule, Listener {
|
||||||
|
|
||||||
|
private final VillagerCache villagerCache;
|
||||||
|
private final long restock_delay_millis;
|
||||||
|
private final boolean log_enabled, notify_player;
|
||||||
|
|
||||||
|
public RestockOptimizedTrades() {
|
||||||
|
shouldEnable();
|
||||||
|
this.villagerCache = VillagerOptimizer.getCache();
|
||||||
|
Config config = VillagerOptimizer.getConfiguration();
|
||||||
|
config.addComment("gameplay.restock-optimized-trades", """
|
||||||
|
This is for automatic restocking of trades for optimized villagers. Optimized Villagers\s
|
||||||
|
don't have enough AI to restock their trades naturally, so this is here as a workaround.""");
|
||||||
|
this.restock_delay_millis = config.getInt("gameplay.restock-optimized-trades.delay-in-ticks", 1000,
|
||||||
|
"1 second = 20 ticks. There are 24.000 ticks in a single minecraft day.") * 50L;
|
||||||
|
this.notify_player = config.getBoolean("gameplay.restock-optimized-trades.notify-player", true,
|
||||||
|
"Sends the player a message when the trades were restocked on a clicked villager.");
|
||||||
|
this.log_enabled = config.getBoolean("gameplay.restock-optimized-trades.log", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disable() {
|
||||||
|
HandlerList.unregisterAll(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||||
|
private void onInteract(PlayerInteractEntityEvent event) {
|
||||||
|
if (!event.getRightClicked().getType().equals(EntityType.VILLAGER)) return;
|
||||||
|
|
||||||
|
WrappedVillager wVillager = villagerCache.getOrAdd((Villager) event.getRightClicked());
|
||||||
|
if (!wVillager.isOptimized()) return;
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
|
||||||
|
final boolean player_bypassing = player.hasPermission(Permissions.Bypass.RESTOCK_COOLDOWN.get());
|
||||||
|
|
||||||
|
if (wVillager.canRestock(restock_delay_millis) || player_bypassing) {
|
||||||
|
wVillager.restock();
|
||||||
|
wVillager.saveRestockTime();
|
||||||
|
if (notify_player && !player_bypassing) {
|
||||||
|
final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%time%")
|
||||||
|
.replacement(CommonUtil.formatTime(wVillager.getRestockCooldownMillis(restock_delay_millis)))
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).trades_restocked.forEach(line -> player.sendMessage(line.replaceText(timeLeft)));
|
||||||
|
}
|
||||||
|
if (log_enabled)
|
||||||
|
VillagerOptimizer.getLog().info("Restocked optimized villager at "+ wVillager.villager().getLocation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,200 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules.optimization;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerCache;
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.WrappedVillager;
|
||||||
|
import me.xginko.villageroptimizer.config.Config;
|
||||||
|
import me.xginko.villageroptimizer.enums.OptimizationType;
|
||||||
|
import me.xginko.villageroptimizer.enums.Permissions;
|
||||||
|
import me.xginko.villageroptimizer.events.VillagerOptimizeEvent;
|
||||||
|
import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent;
|
||||||
|
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
||||||
|
import me.xginko.villageroptimizer.utils.CommonUtil;
|
||||||
|
import me.xginko.villageroptimizer.utils.LogUtil;
|
||||||
|
import net.kyori.adventure.text.TextReplacementConfig;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.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<Material> blocks_that_disable = new HashSet<>(4);
|
||||||
|
private final long cooldown;
|
||||||
|
private final double search_radius;
|
||||||
|
private final boolean only_while_sneaking, notify_player, log_enabled;
|
||||||
|
|
||||||
|
public OptimizeByBlock() {
|
||||||
|
shouldEnable();
|
||||||
|
this.villagerCache = VillagerOptimizer.getCache();
|
||||||
|
Config config = VillagerOptimizer.getConfiguration();
|
||||||
|
config.addComment("optimization-methods.block-optimization.enable", """
|
||||||
|
When enabled, the closest villager standing near a configured block being placed will be optimized.\s
|
||||||
|
If a configured block is broken nearby, the closest villager will become unoptimized again.""");
|
||||||
|
config.getList("optimization-methods.block-optimization.materials", List.of(
|
||||||
|
"LAPIS_BLOCK", "GLOWSTONE", "IRON_BLOCK"
|
||||||
|
), "Values here need to be valid bukkit Material enums for your server version."
|
||||||
|
).forEach(configuredMaterial -> {
|
||||||
|
try {
|
||||||
|
Material disableBlock = Material.valueOf(configuredMaterial);
|
||||||
|
this.blocks_that_disable.add(disableBlock);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
LogUtil.materialNotRecognized("block-optimization", configuredMaterial);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.cooldown = config.getInt("optimization-methods.block-optimization.optimize-cooldown-seconds", 600, """
|
||||||
|
Cooldown in seconds until a villager can be optimized again by using specific blocks. \s
|
||||||
|
Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.""") * 1000L;
|
||||||
|
this.search_radius = config.getDouble("optimization-methods.block-optimization.search-radius-in-blocks", 2.0, """
|
||||||
|
The radius in blocks a villager can be away from the player when he places an optimize block.\s
|
||||||
|
The closest unoptimized villager to the player will be optimized.""") / 2;
|
||||||
|
this.only_while_sneaking = config.getBoolean("optimization-methods.block-optimization.only-when-sneaking", true,
|
||||||
|
"Only optimize/unoptimize by workstation when player is sneaking during place or break.");
|
||||||
|
this.notify_player = config.getBoolean("optimization-methods.block-optimization.notify-player", true,
|
||||||
|
"Sends players a message when they successfully optimized or unoptimized a villager.");
|
||||||
|
this.log_enabled = config.getBoolean("optimization-methods.block-optimization.log", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public 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 (only_while_sneaking && !player.isSneaking()) return;
|
||||||
|
|
||||||
|
final Location blockLoc = placed.getLocation();
|
||||||
|
WrappedVillager closestOptimizableVillager = null;
|
||||||
|
double closestDistance = Double.MAX_VALUE;
|
||||||
|
|
||||||
|
for (Entity entity : blockLoc.getNearbyEntities(search_radius, search_radius, search_radius)) {
|
||||||
|
if (!entity.getType().equals(EntityType.VILLAGER)) continue;
|
||||||
|
Villager villager = (Villager) entity;
|
||||||
|
final Villager.Profession profession = villager.getProfession();
|
||||||
|
if (profession.equals(Villager.Profession.NONE) || profession.equals(Villager.Profession.NITWIT)) continue;
|
||||||
|
|
||||||
|
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
|
||||||
|
final double distance = entity.getLocation().distance(blockLoc);
|
||||||
|
|
||||||
|
if (distance < closestDistance && wVillager.canOptimize(cooldown)) {
|
||||||
|
closestOptimizableVillager = wVillager;
|
||||||
|
closestDistance = distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closestOptimizableVillager == null) return;
|
||||||
|
|
||||||
|
if (closestOptimizableVillager.canOptimize(cooldown) || player.hasPermission(Permissions.Bypass.BLOCK_COOLDOWN.get())) {
|
||||||
|
VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(closestOptimizableVillager, OptimizationType.BLOCK, player, event.isAsynchronous());
|
||||||
|
if (!optimizeEvent.callEvent()) return;
|
||||||
|
|
||||||
|
closestOptimizableVillager.setOptimization(optimizeEvent.getOptimizationType());
|
||||||
|
closestOptimizableVillager.saveOptimizeTime();
|
||||||
|
|
||||||
|
if (notify_player) {
|
||||||
|
final TextReplacementConfig vilProfession = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%vil_profession%")
|
||||||
|
.replacement(closestOptimizableVillager.villager().getProfession().toString().toLowerCase())
|
||||||
|
.build();
|
||||||
|
final TextReplacementConfig placedMaterial = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%blocktype%")
|
||||||
|
.replacement(placed.getType().toString().toLowerCase())
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(line -> player.sendMessage(line
|
||||||
|
.replaceText(vilProfession)
|
||||||
|
.replaceText(placedMaterial)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (log_enabled)
|
||||||
|
VillagerOptimizer.getLog().info("Villager was optimized by block at "+closestOptimizableVillager.villager().getLocation());
|
||||||
|
} else {
|
||||||
|
closestOptimizableVillager.villager().shakeHead();
|
||||||
|
if (notify_player) {
|
||||||
|
final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%time%")
|
||||||
|
.replacement(CommonUtil.formatTime(closestOptimizableVillager.getOptimizeCooldownMillis(cooldown)))
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).block_on_optimize_cooldown.forEach(line -> player.sendMessage(line.replaceText(timeLeft)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
private void onBlockBreak(BlockBreakEvent event) {
|
||||||
|
Block broken = event.getBlock();
|
||||||
|
if (!blocks_that_disable.contains(broken.getType())) return;
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
if (!player.hasPermission(Permissions.Optimize.BLOCK.get())) return;
|
||||||
|
if (only_while_sneaking && !player.isSneaking()) return;
|
||||||
|
|
||||||
|
final Location blockLoc = broken.getLocation();
|
||||||
|
WrappedVillager closestOptimizedVillager = null;
|
||||||
|
double closestDistance = Double.MAX_VALUE;
|
||||||
|
|
||||||
|
for (Entity entity : blockLoc.getNearbyEntities(search_radius, search_radius, search_radius)) {
|
||||||
|
if (!entity.getType().equals(EntityType.VILLAGER)) continue;
|
||||||
|
Villager villager = (Villager) entity;
|
||||||
|
|
||||||
|
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
|
||||||
|
final double distance = entity.getLocation().distance(blockLoc);
|
||||||
|
|
||||||
|
if (distance < closestDistance && wVillager.isOptimized()) {
|
||||||
|
closestOptimizedVillager = wVillager;
|
||||||
|
closestDistance = distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closestOptimizedVillager == null) return;
|
||||||
|
|
||||||
|
VillagerUnoptimizeEvent unOptimizeEvent = new VillagerUnoptimizeEvent(closestOptimizedVillager, player, OptimizationType.BLOCK, event.isAsynchronous());
|
||||||
|
if (!unOptimizeEvent.callEvent()) return;
|
||||||
|
|
||||||
|
closestOptimizedVillager.setOptimization(OptimizationType.NONE);
|
||||||
|
|
||||||
|
if (notify_player) {
|
||||||
|
final TextReplacementConfig vilProfession = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%vil_profession%")
|
||||||
|
.replacement(closestOptimizedVillager.villager().getProfession().toString().toLowerCase())
|
||||||
|
.build();
|
||||||
|
final TextReplacementConfig brokenMaterial = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%blocktype%")
|
||||||
|
.replacement(broken.getType().toString().toLowerCase())
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(line -> player.sendMessage(line
|
||||||
|
.replaceText(vilProfession)
|
||||||
|
.replaceText(brokenMaterial)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (log_enabled)
|
||||||
|
VillagerOptimizer.getLog().info("Villager unoptimized because nearby optimization block broken at: "+closestOptimizedVillager.villager().getLocation());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,133 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules.optimization;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerCache;
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.WrappedVillager;
|
||||||
|
import me.xginko.villageroptimizer.config.Config;
|
||||||
|
import me.xginko.villageroptimizer.enums.OptimizationType;
|
||||||
|
import me.xginko.villageroptimizer.enums.Permissions;
|
||||||
|
import me.xginko.villageroptimizer.events.VillagerOptimizeEvent;
|
||||||
|
import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent;
|
||||||
|
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
||||||
|
import me.xginko.villageroptimizer.utils.CommonUtil;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.TextReplacementConfig;
|
||||||
|
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.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<String> nametags = new HashSet<>(4);
|
||||||
|
private final long cooldown;
|
||||||
|
private final boolean consume_nametag, notify_player, log_enabled;
|
||||||
|
|
||||||
|
public OptimizeByNametag() {
|
||||||
|
shouldEnable();
|
||||||
|
this.villagerCache = VillagerOptimizer.getCache();
|
||||||
|
Config config = VillagerOptimizer.getConfiguration();
|
||||||
|
config.addComment("optimization-methods.nametag-optimization.enable", """
|
||||||
|
Enable optimization by naming villagers to one of the names configured below.\s
|
||||||
|
Nametag optimized villagers will be unoptimized again when they are renamed to something else.""");
|
||||||
|
this.nametags.addAll(config.getList("optimization-methods.nametag-optimization.names", List.of("Optimize", "DisableAI"),
|
||||||
|
"Names are case insensitive, capital letters won't matter.").stream().map(String::toLowerCase).toList());
|
||||||
|
this.consume_nametag = config.getBoolean("optimization-methods.nametag-optimization.nametags-get-consumed", true,
|
||||||
|
"Enable or disable consumption of the used nametag item.");
|
||||||
|
this.cooldown = config.getInt("optimization-methods.nametag-optimization.optimize-cooldown-seconds", 600, """
|
||||||
|
Cooldown in seconds until a villager can be optimized again using a nametag.\s
|
||||||
|
Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.""") * 1000L;
|
||||||
|
this.notify_player = config.getBoolean("optimization-methods.nametag-optimization.notify-player", true,
|
||||||
|
"Sends players a message when they successfully optimized a villager.");
|
||||||
|
this.log_enabled = config.getBoolean("optimization-methods.nametag-optimization.log", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public 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())) {
|
||||||
|
VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(wVillager, OptimizationType.NAMETAG, player, event.isAsynchronous());
|
||||||
|
if (!optimizeEvent.callEvent()) return;
|
||||||
|
|
||||||
|
if (!consume_nametag) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
villager.customName(newVillagerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
wVillager.setOptimization(optimizeEvent.getOptimizationType());
|
||||||
|
wVillager.saveOptimizeTime();
|
||||||
|
|
||||||
|
if (notify_player)
|
||||||
|
VillagerOptimizer.getLang(player.locale()).nametag_optimize_success.forEach(player::sendMessage);
|
||||||
|
if (log_enabled)
|
||||||
|
VillagerOptimizer.getLog().info(player.getName() + " optimized a villager using nametag: '" + name + "'");
|
||||||
|
} else {
|
||||||
|
event.setCancelled(true);
|
||||||
|
villager.shakeHead();
|
||||||
|
if (notify_player) {
|
||||||
|
final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%time%")
|
||||||
|
.replacement(CommonUtil.formatTime(wVillager.getOptimizeCooldownMillis(cooldown)))
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> player.sendMessage(line.replaceText(timeLeft)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (wVillager.isOptimized()) {
|
||||||
|
VillagerUnoptimizeEvent unOptimizeEvent = new VillagerUnoptimizeEvent(wVillager, player, OptimizationType.NAMETAG, event.isAsynchronous());
|
||||||
|
if (!unOptimizeEvent.callEvent()) return;
|
||||||
|
|
||||||
|
wVillager.setOptimization(OptimizationType.NONE);
|
||||||
|
|
||||||
|
if (notify_player)
|
||||||
|
VillagerOptimizer.getLang(player.locale()).nametag_unoptimize_success.forEach(player::sendMessage);
|
||||||
|
if (log_enabled)
|
||||||
|
VillagerOptimizer.getLog().info(event.getPlayer().getName() + " disabled optimizations for a villager using nametag: '" + name + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,207 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules.optimization;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerCache;
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.WrappedVillager;
|
||||||
|
import me.xginko.villageroptimizer.config.Config;
|
||||||
|
import me.xginko.villageroptimizer.enums.OptimizationType;
|
||||||
|
import me.xginko.villageroptimizer.enums.Permissions;
|
||||||
|
import me.xginko.villageroptimizer.events.VillagerOptimizeEvent;
|
||||||
|
import me.xginko.villageroptimizer.events.VillagerUnoptimizeEvent;
|
||||||
|
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
||||||
|
import me.xginko.villageroptimizer.utils.CommonUtil;
|
||||||
|
import net.kyori.adventure.text.TextReplacementConfig;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.entity.Villager;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.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 only_while_sneaking, log_enabled, notify_player;
|
||||||
|
|
||||||
|
public OptimizeByWorkstation() {
|
||||||
|
shouldEnable();
|
||||||
|
this.villagerCache = VillagerOptimizer.getCache();
|
||||||
|
Config config = VillagerOptimizer.getConfiguration();
|
||||||
|
config.addComment("optimization-methods.workstation-optimization.enable", """
|
||||||
|
When enabled, the closest villager near a matching workstation being placed will be optimized.\s
|
||||||
|
If a nearby matching workstation is broken, the villager will become unoptimized again.""");
|
||||||
|
this.search_radius = config.getDouble("optimization-methods.workstation-optimization.search-radius-in-blocks", 2.0, """
|
||||||
|
The radius in blocks a villager can be away from the player when he places a workstation.\s
|
||||||
|
The closest unoptimized villager to the player will be optimized.""") / 2;
|
||||||
|
this.cooldown = config.getInt("optimization-methods.workstation-optimization.optimize-cooldown-seconds", 600, """
|
||||||
|
Cooldown in seconds until a villager can be optimized again using a workstation.\s
|
||||||
|
Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.""") * 1000L;
|
||||||
|
this.only_while_sneaking = config.getBoolean("optimization-methods.workstation-optimization.only-when-sneaking", true,
|
||||||
|
"Only optimize/unoptimize by workstation when player is sneaking during place or break");
|
||||||
|
this.notify_player = config.getBoolean("optimization-methods.workstation-optimization.notify-player", true,
|
||||||
|
"Sends players a message when they successfully optimized a villager.");
|
||||||
|
this.log_enabled = config.getBoolean("optimization-methods.workstation-optimization.log", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public 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 (only_while_sneaking && !player.isSneaking()) return;
|
||||||
|
|
||||||
|
final Location workstationLoc = placed.getLocation();
|
||||||
|
WrappedVillager closestOptimizableVillager = null;
|
||||||
|
double closestDistance = Double.MAX_VALUE;
|
||||||
|
|
||||||
|
for (Entity entity : workstationLoc.getNearbyEntities(search_radius, search_radius, search_radius)) {
|
||||||
|
if (!entity.getType().equals(EntityType.VILLAGER)) continue;
|
||||||
|
Villager villager = (Villager) entity;
|
||||||
|
if (!villager.getProfession().equals(workstationProfession)) continue;
|
||||||
|
|
||||||
|
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
|
||||||
|
final double distance = entity.getLocation().distance(workstationLoc);
|
||||||
|
|
||||||
|
if (distance < closestDistance && wVillager.canOptimize(cooldown)) {
|
||||||
|
closestOptimizableVillager = wVillager;
|
||||||
|
closestDistance = distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closestOptimizableVillager == null) return;
|
||||||
|
|
||||||
|
if (closestOptimizableVillager.canOptimize(cooldown) || player.hasPermission(Permissions.Bypass.WORKSTATION_COOLDOWN.get())) {
|
||||||
|
VillagerOptimizeEvent optimizeEvent = new VillagerOptimizeEvent(closestOptimizableVillager, OptimizationType.WORKSTATION, player, event.isAsynchronous());
|
||||||
|
if (!optimizeEvent.callEvent()) return;
|
||||||
|
|
||||||
|
closestOptimizableVillager.setOptimization(optimizeEvent.getOptimizationType());
|
||||||
|
closestOptimizableVillager.saveOptimizeTime();
|
||||||
|
|
||||||
|
if (notify_player) {
|
||||||
|
final TextReplacementConfig vilProfession = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%vil_profession%")
|
||||||
|
.replacement(closestOptimizableVillager.villager().getProfession().toString().toLowerCase())
|
||||||
|
.build();
|
||||||
|
final TextReplacementConfig placedWorkstation = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%workstation%")
|
||||||
|
.replacement(placed.getType().toString().toLowerCase())
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).workstation_optimize_success.forEach(line -> player.sendMessage(line
|
||||||
|
.replaceText(vilProfession)
|
||||||
|
.replaceText(placedWorkstation)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (log_enabled)
|
||||||
|
VillagerOptimizer.getLog().info(player.getName() + " optimized a villager using workstation: '" + placed.getType().toString().toLowerCase() + "'");
|
||||||
|
} else {
|
||||||
|
closestOptimizableVillager.villager().shakeHead();
|
||||||
|
if (notify_player) {
|
||||||
|
final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%time%")
|
||||||
|
.replacement(CommonUtil.formatTime(closestOptimizableVillager.getOptimizeCooldownMillis(cooldown)))
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> player.sendMessage(line
|
||||||
|
.replaceText(timeLeft)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
private void onBlockBreak(BlockBreakEvent event) {
|
||||||
|
Block broken = event.getBlock();
|
||||||
|
Villager.Profession workstationProfession = getWorkstationProfession(broken.getType());
|
||||||
|
if (workstationProfession.equals(Villager.Profession.NONE)) return;
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
if (!player.hasPermission(Permissions.Optimize.WORKSTATION.get())) return;
|
||||||
|
if (only_while_sneaking && !player.isSneaking()) return;
|
||||||
|
|
||||||
|
final Location workstationLoc = broken.getLocation();
|
||||||
|
WrappedVillager closestOptimizedVillager = null;
|
||||||
|
double closestDistance = Double.MAX_VALUE;
|
||||||
|
|
||||||
|
for (Entity entity : workstationLoc.getNearbyEntities(search_radius, search_radius, search_radius)) {
|
||||||
|
if (!entity.getType().equals(EntityType.VILLAGER)) continue;
|
||||||
|
Villager villager = (Villager) entity;
|
||||||
|
if (!villager.getProfession().equals(workstationProfession)) continue;
|
||||||
|
|
||||||
|
WrappedVillager wVillager = villagerCache.getOrAdd(villager);
|
||||||
|
final double distance = entity.getLocation().distance(workstationLoc);
|
||||||
|
|
||||||
|
if (distance < closestDistance && wVillager.canOptimize(cooldown)) {
|
||||||
|
closestOptimizedVillager = wVillager;
|
||||||
|
closestDistance = distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closestOptimizedVillager == null) return;
|
||||||
|
|
||||||
|
VillagerUnoptimizeEvent unOptimizeEvent = new VillagerUnoptimizeEvent(closestOptimizedVillager, player, OptimizationType.WORKSTATION, event.isAsynchronous());
|
||||||
|
if (!unOptimizeEvent.callEvent()) return;
|
||||||
|
|
||||||
|
closestOptimizedVillager.setOptimization(OptimizationType.NONE);
|
||||||
|
|
||||||
|
if (notify_player) {
|
||||||
|
final TextReplacementConfig vilProfession = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%vil_profession%")
|
||||||
|
.replacement(closestOptimizedVillager.villager().getProfession().toString().toLowerCase())
|
||||||
|
.build();
|
||||||
|
final TextReplacementConfig brokenWorkstation = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%workstation%")
|
||||||
|
.replacement(broken.getType().toString().toLowerCase())
|
||||||
|
.build();
|
||||||
|
VillagerOptimizer.getLang(player.locale()).workstation_unoptimize_success.forEach(line -> player.sendMessage(line
|
||||||
|
.replaceText(vilProfession)
|
||||||
|
.replaceText(brokenWorkstation)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (log_enabled)
|
||||||
|
VillagerOptimizer.getLog().info(player.getName() + " unoptimized a villager by breaking workstation: '" + broken.getType().toString().toLowerCase() + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Villager.Profession getWorkstationProfession(final Material workstation) {
|
||||||
|
return switch (workstation) {
|
||||||
|
case BARREL -> Villager.Profession.FISHERMAN;
|
||||||
|
case CARTOGRAPHY_TABLE -> Villager.Profession.CARTOGRAPHER;
|
||||||
|
case SMOKER -> Villager.Profession.BUTCHER;
|
||||||
|
case SMITHING_TABLE -> Villager.Profession.TOOLSMITH;
|
||||||
|
case GRINDSTONE -> Villager.Profession.WEAPONSMITH;
|
||||||
|
case BLAST_FURNACE -> Villager.Profession.ARMORER;
|
||||||
|
case CAULDRON -> Villager.Profession.LEATHERWORKER;
|
||||||
|
case BREWING_STAND -> Villager.Profession.CLERIC;
|
||||||
|
case COMPOSTER -> Villager.Profession.FARMER;
|
||||||
|
case FLETCHING_TABLE -> Villager.Profession.FLETCHER;
|
||||||
|
case LOOM -> Villager.Profession.SHEPHERD;
|
||||||
|
case LECTERN -> Villager.Profession.LIBRARIAN;
|
||||||
|
case STONECUTTER -> Villager.Profession.MASON;
|
||||||
|
default -> Villager.Profession.NONE;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package me.xginko.villageroptimizer.utils;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public class LogUtil {
|
||||||
|
|
||||||
|
public static void moduleLog(Level logLevel, String path, String logMessage) {
|
||||||
|
VillagerOptimizer.getLog().log(logLevel, "(" + path + ") " + logMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void materialNotRecognized(String path, String material) {
|
||||||
|
moduleLog(Level.WARNING, path, "Material '" + material + "' not recognized. Please use correct Material enums from: " +
|
||||||
|
"https://jd.papermc.io/paper/1.20/org/bukkit/Material.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void damageCauseNotRecognized(String path, String cause) {
|
||||||
|
moduleLog(Level.WARNING, path, "DamageCause '" + cause + "' not recognized. Please use correct DamageCause enums from: " +
|
||||||
|
"https://jd.papermc.io/paper/1.20/org/bukkit/event/entity/EntityDamageEvent.DamageCause.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void entityTypeNotRecognized(String path, String entityType) {
|
||||||
|
moduleLog(Level.WARNING, path, "EntityType '" + entityType + "' not recognized. Please use correct Spigot EntityType enums for your Minecraft version!");
|
||||||
|
}
|
||||||
|
}
|
44
VillagerOptimizer-1.20.2/src/main/resources/lang/de_de.yml
Normal file
44
VillagerOptimizer-1.20.2/src/main/resources/lang/de_de.yml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
messages:
|
||||||
|
no-permission: "<red>Du hast keine Berechtigung für diesen Befehl."
|
||||||
|
optimize-to-trade:
|
||||||
|
- "<red>Optimiere den Dorfbewohner um mit ihm handeln zu können."
|
||||||
|
trades-restocked:
|
||||||
|
- "<green>Alle Angebote sind wieder verfügbar! Nächste Auffrischung in %time%."
|
||||||
|
villager-leveling-up:
|
||||||
|
- "<yellow>Dorfbewohner steigt gerade im Level auf und kann in %time% wieder handeln."
|
||||||
|
nametag:
|
||||||
|
optimize-success:
|
||||||
|
- "<green>Dorfbewohner erfolgreich mit einem Namensschild optimiert."
|
||||||
|
optimize-on-cooldown:
|
||||||
|
- "<gray>Du musst %time% warten, bevor du diesen Dorfbewohner erneut optimieren kannst."
|
||||||
|
unoptimize-success:
|
||||||
|
- "<green>Optimierung erfolgreich durch eine Namensschildänderung aufgehoben."
|
||||||
|
block:
|
||||||
|
optimize-success:
|
||||||
|
- "<green>%vil_profession% Dorfbewohner erfolgreich mit %blocktype% optimiert."
|
||||||
|
optimize-on-cooldown:
|
||||||
|
- "<gray>Du musst %time% warten, bevor du diesen Dorfbewohner erneut optimieren kannst."
|
||||||
|
unoptimize-success:
|
||||||
|
- "<green>Optimierung des %vil_profession% Dorfbewohners erfolgreich durch Abbauen von %blocktype% aufgehoben."
|
||||||
|
workstation:
|
||||||
|
optimize-success:
|
||||||
|
- "<green>%vil_profession% Dorfbewohner erfolgreich mit Arbeitsplatz-Block %blocktype% optimiert."
|
||||||
|
optimize-on-cooldown:
|
||||||
|
- "<gray>Du musst %time% warten, bevor du diesen Dorfbewohner erneut optimieren kannst."
|
||||||
|
unoptimize-success:
|
||||||
|
- "<green>Optimierung des %vil_profession% Dorfbewohners erfolgreich durch Abbauen von %blocktype% aufgehoben."
|
||||||
|
command:
|
||||||
|
optimize-success:
|
||||||
|
- "<green>Erfolgreich %amount% Dorfbewohner in einem Radius von %radius% Blöcken optimiert."
|
||||||
|
optimize-fail:
|
||||||
|
- "<gray>%amount% Dorfbewohner konnten nicht optimiert werden, da sie erst vor Kurzem optimiert wurden."
|
||||||
|
radius-limit-exceed:
|
||||||
|
- "<red>Der eingegebene Radius überschreitet das Limit von %distance% Blöcken."
|
||||||
|
unoptimize-success:
|
||||||
|
- "<green>Optimierung von %amount% Dorfbewohnern in einem Radius von %radius% Blöcken erfolgreich aufgehoben."
|
||||||
|
specify-radius:
|
||||||
|
- "<red>Bitte gib einen Radius an."
|
||||||
|
radius-invalid:
|
||||||
|
- "<red>Der eingegebene Radius ist keine gültige Zahl. Versuche es erneut."
|
||||||
|
no-villagers-nearby:
|
||||||
|
- "<gray>Es wurden keine beschäftigten Dorfbewohner innerhalb eines Radius von %radius% Blöcken gefunden."
|
44
VillagerOptimizer-1.20.2/src/main/resources/lang/en_us.yml
Normal file
44
VillagerOptimizer-1.20.2/src/main/resources/lang/en_us.yml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
messages:
|
||||||
|
no-permission: "<red>You don't have permission to use this command."
|
||||||
|
optimize-to-trade:
|
||||||
|
- "<red>You need to optimize this villager before you can trade with it."
|
||||||
|
trades-restocked:
|
||||||
|
- "<green>All trades have been restocked! Next restock in %time%."
|
||||||
|
villager-leveling-up:
|
||||||
|
- "<yellow>Villager is currently leveling up! You can use the villager again in %time%."
|
||||||
|
nametag:
|
||||||
|
optimize-success:
|
||||||
|
- "<green>Successfully optimized villager by using a nametag."
|
||||||
|
optimize-on-cooldown:
|
||||||
|
- "<gray>You need to wait %time% until you can optimize this villager again."
|
||||||
|
unoptimize-success:
|
||||||
|
- "<green>Successfully unoptimized villager by using a nametag."
|
||||||
|
block:
|
||||||
|
optimize-success:
|
||||||
|
- "<green>%vil_profession% villager successfully optimized using block %blocktype%."
|
||||||
|
optimize-on-cooldown:
|
||||||
|
- "<gray>You need to wait %time% until you can optimize this villager again."
|
||||||
|
unoptimize-success:
|
||||||
|
- "<green>Successfully unoptimized %vil_profession% villager by removing %blocktype%."
|
||||||
|
workstation:
|
||||||
|
optimize-success:
|
||||||
|
- "<green>%vil_profession% villager successfully optimized using workstation block %blocktype%."
|
||||||
|
optimize-on-cooldown:
|
||||||
|
- "<gray>You need to wait %time% until you can optimize this villager again."
|
||||||
|
unoptimize-success:
|
||||||
|
- "<green>Successfully unoptimized villager by removing workstation block %blocktype%."
|
||||||
|
command:
|
||||||
|
optimize-success:
|
||||||
|
- "<green>Successfully optimized %amount% villager(s) in a radius of %radius% blocks."
|
||||||
|
optimize-fail:
|
||||||
|
- "<gray>%amount% villagers couldn't be optimized because they have recently been optimized."
|
||||||
|
radius-limit-exceed:
|
||||||
|
- "<red>The radius you entered exceeds the limit of %distance% blocks."
|
||||||
|
unoptimize-success:
|
||||||
|
- "<green>Successfully unoptimized %amount% villager(s) in a radius of %radius% blocks."
|
||||||
|
specify-radius:
|
||||||
|
- "<red>Please specify a radius."
|
||||||
|
radius-invalid:
|
||||||
|
- "<red>The radius you entered is not a valid number. Try again."
|
||||||
|
no-villagers-nearby:
|
||||||
|
- "<gray>Couldn't find any employed villagers within a radius of %radius%."
|
44
VillagerOptimizer-1.20.2/src/main/resources/lang/it_it.yml
Normal file
44
VillagerOptimizer-1.20.2/src/main/resources/lang/it_it.yml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
messages:
|
||||||
|
no-permission: "<red>Non hai il permesso per usare questo comando."
|
||||||
|
optimize-to-trade:
|
||||||
|
- "<red>Devi ottimizzare il villager prima di poterci commerciare."
|
||||||
|
trades-restocked:
|
||||||
|
- "<green>Tutti i commerci sono stati riavviati! Prossimo riavvio in %time%."
|
||||||
|
villager-leveling-up:
|
||||||
|
- "<yellow>Il villager si sta ancora ottimizzando! Potrai utilizzarlo di nuovo tra %time%."
|
||||||
|
nametag:
|
||||||
|
optimize-success:
|
||||||
|
- "<green>Villager ottimizzato correttamente utilizzando il name-tag."
|
||||||
|
optimize-on-cooldown:
|
||||||
|
- "<gray>Devi attendere %time% prima di poter ottimizzare di nuovo questo villager."
|
||||||
|
unoptimize-success:
|
||||||
|
- "<green>Ottimizzazione rimossa con successo dal villager usando il name-tag."
|
||||||
|
block:
|
||||||
|
optimize-success:
|
||||||
|
- "<green>%vil_profession% ottimizzato con successo usando il blocco: %blocktype%."
|
||||||
|
optimize-on-cooldown:
|
||||||
|
- "<gray>Devi attendere %time% prima di poter ottimizzare di nuovo questo villager."
|
||||||
|
unoptimize-success:
|
||||||
|
- "<green>Ottimizzazione rimossa al villager %vil_profession% rimuovendo il blocco: %blocktype%."
|
||||||
|
workstation:
|
||||||
|
optimize-success:
|
||||||
|
- "<green>%vil_profession% ottimizzato con successo usando la workstation: %blocktype%."
|
||||||
|
optimize-on-cooldown:
|
||||||
|
- "<gray>Devi aspettare %time% prima di poter ottimizzare il villager."
|
||||||
|
unoptimize-success:
|
||||||
|
- "<green>Ottimizzazione rimossa al villager %vil_profession% rimuovendo la workstation: %blocktype%."
|
||||||
|
command:
|
||||||
|
optimize-success:
|
||||||
|
- "<green>Ottimizzato con successo %amount% villager in un raggio di %radius% blocchi."
|
||||||
|
optimize-fail:
|
||||||
|
- "<gray>%amount% villager non possono essere ottimizzati poiché sono stati già ottimizzati di recente."
|
||||||
|
radius-limit-exceed:
|
||||||
|
- "<red>Il raggio inserito supera il limite di %distance% blocchi."
|
||||||
|
unoptimize-success:
|
||||||
|
- "<green>Ottimizzazione rimossa con successo da %amount% villager in un raggio di %radius% blocchi."
|
||||||
|
specify-radius:
|
||||||
|
- "<red>Per favore, specifica un raggio."
|
||||||
|
radius-invalid:
|
||||||
|
- "<red>Il raggio inserito non è un numero valido, riprova."
|
||||||
|
no-villagers-nearby:
|
||||||
|
- "<gray>Non è stato possibile trovare villager entro un raggio di %radius% blocchi."
|
44
VillagerOptimizer-1.20.2/src/main/resources/lang/ru_ru.yml
Normal file
44
VillagerOptimizer-1.20.2/src/main/resources/lang/ru_ru.yml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
messages:
|
||||||
|
no-permission: "<red>У вас нет разрешения на использование данной команды."
|
||||||
|
optimize-to-trade:
|
||||||
|
- "<red>Вам необходимо оптимизировать данного жителя перед торговлей."
|
||||||
|
trades-restocked:
|
||||||
|
- "<green>Товары были обновлены. Следующее обновление через %time%."
|
||||||
|
villager-leveling-up:
|
||||||
|
- "<yellow>Данный житель повышает свой уровень. Вы сможете торговать через %time%"
|
||||||
|
nametag:
|
||||||
|
optimize-success:
|
||||||
|
- "<green>Житель был успешно оптимизирован биркой."
|
||||||
|
optimize-on-cooldown:
|
||||||
|
- "<gray>Подождите еще %time% для повторной оптимизации данного жителя."
|
||||||
|
unoptimize-success:
|
||||||
|
- "<green>Оптимизация была успешно снята с жителя."
|
||||||
|
block:
|
||||||
|
optimize-success:
|
||||||
|
- "<green>%vil_profession% был успешно оптимизирован используя %blocktype%."
|
||||||
|
optimize-on-cooldown:
|
||||||
|
- "<gray>Подождите еще %time% для повторной оптимизации данного жителя."
|
||||||
|
unoptimize-success:
|
||||||
|
- "<green>Оптимизация была успешно снята с %vil_profession% удалением %blocktype%."
|
||||||
|
workstation:
|
||||||
|
optimize-success:
|
||||||
|
- "<green>%vil_profession% был успешно оптимизирован используя %blocktype%."
|
||||||
|
optimize-on-cooldown:
|
||||||
|
- "<gray>Подождите еще %time% для повторной оптимизации данного жителя."
|
||||||
|
unoptimize-success:
|
||||||
|
- "<green>Оптимизация была успешно снята с %vil_profession% удалением %blocktype%."
|
||||||
|
command:
|
||||||
|
optimize-success:
|
||||||
|
- "<green>Успешно оптимизинованы %amount% жителей в радиусе %radius% блоков."
|
||||||
|
optimize-fail:
|
||||||
|
- "<gray>%amount% жителей не могут быть оптимизированы снова так как они уже были оптимизированы недавно."
|
||||||
|
radius-limit-exceed:
|
||||||
|
- "<red>Укажите радиус меньше %distance% блоков."
|
||||||
|
unoptimize-success:
|
||||||
|
- "<green>Оптимизация была успешно снята с %amount% жителей в радиусе %radius% блоков."
|
||||||
|
specify-radius:
|
||||||
|
- "<red>Пожалуйста, укажите радиус."
|
||||||
|
radius-invalid:
|
||||||
|
- "<red>Указанный вами радиус не является действительным значением."
|
||||||
|
no-villagers-nearby:
|
||||||
|
- "<gray>Не найдено работающих жителей в радиусе %radius% блоков."
|
98
VillagerOptimizer-1.20.2/src/main/resources/plugin.yml
Normal file
98
VillagerOptimizer-1.20.2/src/main/resources/plugin.yml
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
name: VillagerOptimizer
|
||||||
|
version: '${project.version}'
|
||||||
|
main: me.xginko.villageroptimizer.VillagerOptimizer
|
||||||
|
authors: [ xGinko ]
|
||||||
|
description: ${project.description}
|
||||||
|
website: ${project.url}
|
||||||
|
api-version: '1.19'
|
||||||
|
folia-supported: true
|
||||||
|
commands:
|
||||||
|
villageroptimizer:
|
||||||
|
usage: /villageroptimizer [ reload, version, disable ]
|
||||||
|
description: VillagerOptimizer admin commands
|
||||||
|
aliases:
|
||||||
|
- voptimizer
|
||||||
|
- vo
|
||||||
|
optimizevillagers:
|
||||||
|
usage: /optimizevillagers <blockradius>
|
||||||
|
description: Optmize villagers in a radius around you
|
||||||
|
aliases:
|
||||||
|
- optvils
|
||||||
|
- noai
|
||||||
|
unoptimizevillagers:
|
||||||
|
usage: /unoptimizevillagers <blockradius>
|
||||||
|
description: Unoptmize villagers in a radius around you
|
||||||
|
aliases:
|
||||||
|
- unoptvils
|
||||||
|
- noaiundo
|
||||||
|
permissions:
|
||||||
|
villageroptimizer.ignore:
|
||||||
|
description: Players with this permission won't be able to use the plugin features
|
||||||
|
children:
|
||||||
|
villageroptimizer.optimize.nametag: false
|
||||||
|
villageroptimizer.optimize.block: false
|
||||||
|
villageroptimizer.optimize.workstation: false
|
||||||
|
villageroptimizer.playerdefaults:
|
||||||
|
description: Default permissions for players
|
||||||
|
default: true
|
||||||
|
children:
|
||||||
|
villageroptimizer.cmd.optimize: true
|
||||||
|
villageroptimizer.cmd.unoptimize: true
|
||||||
|
villageroptimizer.optimize.*: true
|
||||||
|
villageroptimizer.*:
|
||||||
|
description: All plugin permissions
|
||||||
|
children:
|
||||||
|
villageroptimizer.cmd.*: true
|
||||||
|
villageroptimizer.bypass.*: true
|
||||||
|
villageroptimizer.optimize.*: true
|
||||||
|
villageroptimizer.optimize.*:
|
||||||
|
description: Optimization type permissions
|
||||||
|
children:
|
||||||
|
villageroptimizer.optimize.nametag: true
|
||||||
|
villageroptimizer.optimize.block: true
|
||||||
|
villageroptimizer.optimize.workstation: true
|
||||||
|
villageroptimizer.optimize.nametag:
|
||||||
|
description: Optimize/Unoptimize villagers using nametags
|
||||||
|
villageroptimizer.optimize.block:
|
||||||
|
description: Optimize/Unoptimize villagers using specific blocks
|
||||||
|
villageroptimizer.optimize.workstation:
|
||||||
|
description: Optimize/Unoptimize villagers using workstations
|
||||||
|
villageroptimizer.cmd.*:
|
||||||
|
description: All command permissions
|
||||||
|
children:
|
||||||
|
villageroptimizer.cmd.reload: true
|
||||||
|
villageroptimizer.cmd.disable: true
|
||||||
|
villageroptimizer.cmd.version: true
|
||||||
|
villageroptimizer.cmd.optimize: true
|
||||||
|
villageroptimizer.cmd.unoptimize: true
|
||||||
|
villageroptimizer.cmd.disable:
|
||||||
|
description: Disable the plugin
|
||||||
|
villageroptimizer.cmd.reload:
|
||||||
|
description: Reload the plugin configuration
|
||||||
|
villageroptimizer.cmd.version:
|
||||||
|
description: Show the plugin version
|
||||||
|
villageroptimizer.cmd.optimize:
|
||||||
|
description: Optimize villagers in a radius
|
||||||
|
villageroptimizer.cmd.unoptimize:
|
||||||
|
description: Unoptimize villagers in a radius
|
||||||
|
villageroptimizer.bypass.*:
|
||||||
|
description: All bypass permissions
|
||||||
|
children:
|
||||||
|
villageroptimizer.bypass.tradeprevention: true
|
||||||
|
villageroptimizer.bypass.restockcooldown: true
|
||||||
|
villageroptimizer.bypass.nametagcooldown: true
|
||||||
|
villageroptimizer.bypass.blockcooldown: true
|
||||||
|
villageroptimizer.bypass.workstationcooldown: true
|
||||||
|
villageroptimizer.bypass.commandcooldown: true
|
||||||
|
villageroptimizer.bypass.tradeprevention:
|
||||||
|
description: Bypass unoptimized trading prevention if enabled
|
||||||
|
villageroptimizer.bypass.restockcooldown:
|
||||||
|
description: Bypass permission for optimized trade restock cooldown
|
||||||
|
villageroptimizer.bypass.nametagcooldown:
|
||||||
|
description: Bypass permission for nametag optimization cooldown
|
||||||
|
villageroptimizer.bypass.blockcooldown:
|
||||||
|
description: Bypass permission for block optimization cooldown
|
||||||
|
villageroptimizer.bypass.workstationcooldown:
|
||||||
|
description: Bypass permission for workstation optimization cooldown
|
||||||
|
villageroptimizer.bypass.commandcooldown:
|
||||||
|
description: Bypass permission for command optimization cooldown
|
125
pom.xml
125
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">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<groupId>me.xginko</groupId>
|
<groupId>me.xginko.VillagerOptimizer</groupId>
|
||||||
<artifactId>VillagerOptimizer</artifactId>
|
<artifactId>VillagerOptimizer</artifactId>
|
||||||
<version>1.7.0</version>
|
<version>1.0.1</version>
|
||||||
<packaging>jar</packaging>
|
<modules>
|
||||||
|
<module>VillagerOptimizer-1.20.2</module>
|
||||||
|
<module>VillagerOptimizer-1.16.5</module>
|
||||||
|
</modules>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
<name>VillagerOptimizer</name>
|
<name>VillagerOptimizer</name>
|
||||||
<description>Combat heavy villager lag by letting players optimize their trading halls.</description>
|
<description>Combat heavy villager lag by letting players optimize their trading halls.</description>
|
||||||
<url>https://github.com/xGinko/VillagerOptimizer</url>
|
<url>https://github.com/xGinko/VillagerOptimizer</url>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>1.8</java.version>
|
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
@ -23,7 +26,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.12.1</version>
|
<version>3.11.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<source>${java.version}</source>
|
<source>${java.version}</source>
|
||||||
<target>${java.version}</target>
|
<target>${java.version}</target>
|
||||||
@ -40,51 +43,17 @@
|
|||||||
<goal>shade</goal>
|
<goal>shade</goal>
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<relocations>
|
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||||
<relocation>
|
<finalName>${project.parent.artifactId}-${project.parent.version}--${project.artifactId}</finalName>
|
||||||
<pattern>com.github.benmanes.caffeine</pattern>
|
|
||||||
<shadedPattern>me.xginko.villageroptimizer.libs.caffeine</shadedPattern>
|
|
||||||
</relocation>
|
|
||||||
<relocation>
|
|
||||||
<pattern>org.bstats</pattern>
|
|
||||||
<shadedPattern>me.xginko.villageroptimizer.libs.bstats</shadedPattern>
|
|
||||||
</relocation>
|
|
||||||
<relocation>
|
|
||||||
<pattern>net.kyori</pattern>
|
|
||||||
<shadedPattern>me.xginko.villageroptimizer.libs.kyori</shadedPattern>
|
|
||||||
</relocation>
|
|
||||||
<relocation>
|
|
||||||
<pattern>io.github.thatsmusic99.configurationmaster</pattern>
|
|
||||||
<shadedPattern>me.xginko.villageroptimizer.libs.configmaster</shadedPattern>
|
|
||||||
</relocation>
|
|
||||||
<relocation>
|
|
||||||
<pattern>org.reflections</pattern>
|
|
||||||
<shadedPattern>me.xginko.villageroptimizer.libs.reflections</shadedPattern>
|
|
||||||
</relocation>
|
|
||||||
<relocation>
|
|
||||||
<pattern>com.cryptomorin.xseries</pattern>
|
|
||||||
<shadedPattern>me.xginko.villageroptimizer.libs.xseries</shadedPattern>
|
|
||||||
</relocation>
|
|
||||||
<relocation>
|
|
||||||
<pattern>space.arim.morepaperlib</pattern>
|
|
||||||
<shadedPattern>me.xginko.villageroptimizer.libs.morepaperlib</shadedPattern>
|
|
||||||
</relocation>
|
|
||||||
</relocations>
|
|
||||||
<filters>
|
<filters>
|
||||||
<filter>
|
<filter>
|
||||||
<artifact>*:*</artifact>
|
<artifact>*:*</artifact>
|
||||||
<excludes>
|
<excludes>
|
||||||
<exclude>com/cryptomorin/xseries/XBiome*</exclude>
|
<exclude>module-info.class</exclude>
|
||||||
<exclude>com/cryptomorin/xseries/NMSExtras*</exclude>
|
|
||||||
<exclude>com/cryptomorin/xseries/NoteBlockMusic*</exclude>
|
|
||||||
<exclude>com/cryptomorin/xseries/SkullCacheListener*</exclude>
|
|
||||||
<exclude>META-INF/MANIFEST.MF</exclude>
|
<exclude>META-INF/MANIFEST.MF</exclude>
|
||||||
<exclude>META-INF/LICENSE</exclude>
|
|
||||||
<exclude>META-INF/LICENSE.txt</exclude>
|
|
||||||
</excludes>
|
</excludes>
|
||||||
</filter>
|
</filter>
|
||||||
</filters>
|
</filters>
|
||||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
@ -100,94 +69,36 @@
|
|||||||
|
|
||||||
<repositories>
|
<repositories>
|
||||||
<repository>
|
<repository>
|
||||||
<id>papermc-repo</id>
|
<id>papermc</id>
|
||||||
<url>https://repo.papermc.io/repository/maven-public/</url>
|
<url>https://papermc.io/repo/repository/maven-public/</url>
|
||||||
</repository>
|
|
||||||
<repository>
|
|
||||||
<id>sonatype</id>
|
|
||||||
<url>https://oss.sonatype.org/content/groups/public/</url>
|
|
||||||
</repository>
|
</repository>
|
||||||
<repository>
|
<repository>
|
||||||
<id>configmaster-repo</id>
|
<id>configmaster-repo</id>
|
||||||
<url>https://ci.pluginwiki.us/plugin/repository/everything/</url>
|
<url>https://ci.pluginwiki.us/plugin/repository/everything/</url>
|
||||||
</repository>
|
</repository>
|
||||||
<repository>
|
|
||||||
<id>morepaperlib-repo</id>
|
|
||||||
<url>https://mvn-repo.arim.space/lesser-gpl3/</url>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
</repositories>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
|
||||||
<groupId>io.papermc.paper</groupId>
|
|
||||||
<artifactId>paper-api</artifactId>
|
|
||||||
<version>1.20.4-R0.1-SNAPSHOT</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
|
||||||
<artifactId>log4j-core</artifactId>
|
|
||||||
<version>2.23.1</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.reflections</groupId>
|
|
||||||
<artifactId>reflections</artifactId>
|
|
||||||
<version>0.10.2</version>
|
|
||||||
</dependency>
|
|
||||||
<!-- Adventure API for easier cross-version compatibility -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>net.kyori</groupId>
|
|
||||||
<artifactId>adventure-platform-bukkit</artifactId>
|
|
||||||
<version>4.3.3</version>
|
|
||||||
</dependency>
|
|
||||||
<!-- Adventure MiniMessage for parsing fancy tags in lang files -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>net.kyori</groupId>
|
|
||||||
<artifactId>adventure-text-minimessage</artifactId>
|
|
||||||
<version>4.17.0</version>
|
|
||||||
</dependency>
|
|
||||||
<!-- Needed to actually display colors in ComponentLogger -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>net.kyori</groupId>
|
|
||||||
<artifactId>adventure-text-serializer-ansi</artifactId>
|
|
||||||
<version>4.17.0</version>
|
|
||||||
</dependency>
|
|
||||||
<!-- Adventure ComponentLogger for colorful slf4j logging -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>net.kyori</groupId>
|
|
||||||
<artifactId>adventure-text-logger-slf4j</artifactId>
|
|
||||||
<version>4.17.0</version>
|
|
||||||
</dependency>
|
|
||||||
<!-- Bukkit bStats -->
|
<!-- Bukkit bStats -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.bstats</groupId>
|
<groupId>org.bstats</groupId>
|
||||||
<artifactId>bstats-bukkit</artifactId>
|
<artifactId>bstats-bukkit</artifactId>
|
||||||
<version>3.0.2</version>
|
<version>3.0.2</version>
|
||||||
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- Enhanced config.yml manager -->
|
<!-- Enhanced config.yml manager -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.thatsmusic99</groupId>
|
<groupId>com.github.thatsmusic99</groupId>
|
||||||
<artifactId>ConfigurationMaster-API</artifactId>
|
<artifactId>ConfigurationMaster-API</artifactId>
|
||||||
<version>v2.0.0-rc.1</version>
|
<version>v2.0.0-rc.1</version>
|
||||||
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- Fast Caching (Needs to be 2.9.3 for java 8 support) -->
|
<!-- Caching -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||||
<artifactId>caffeine</artifactId>
|
<artifactId>caffeine</artifactId>
|
||||||
<version>2.9.3</version>
|
<version>3.1.8</version>
|
||||||
</dependency>
|
<scope>compile</scope>
|
||||||
<!-- Folia Support -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>space.arim.morepaperlib</groupId>
|
|
||||||
<artifactId>morepaperlib</artifactId>
|
|
||||||
<version>0.4.3</version>
|
|
||||||
</dependency>
|
|
||||||
<!-- Cross-Version Support -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.github.cryptomorin</groupId>
|
|
||||||
<artifactId>XSeries</artifactId>
|
|
||||||
<version>11.2.1</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@ -1,259 +0,0 @@
|
|||||||
package me.xginko.villageroptimizer;
|
|
||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.Cache;
|
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
|
||||||
import me.xginko.villageroptimizer.commands.VillagerOptimizerCommand;
|
|
||||||
import me.xginko.villageroptimizer.config.Config;
|
|
||||||
import me.xginko.villageroptimizer.config.LanguageCache;
|
|
||||||
import me.xginko.villageroptimizer.struct.enums.Permissions;
|
|
||||||
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
|
||||||
import me.xginko.villageroptimizer.utils.Util;
|
|
||||||
import me.xginko.villageroptimizer.wrapper.WrappedVillager;
|
|
||||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
|
||||||
import net.kyori.adventure.text.Component;
|
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import net.kyori.adventure.text.format.TextDecoration;
|
|
||||||
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
|
|
||||||
import org.apache.logging.log4j.Level;
|
|
||||||
import org.apache.logging.log4j.core.config.Configurator;
|
|
||||||
import org.bstats.bukkit.Metrics;
|
|
||||||
import org.bukkit.command.CommandSender;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.entity.Villager;
|
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import space.arim.morepaperlib.MorePaperLib;
|
|
||||||
import space.arim.morepaperlib.commands.CommandRegistration;
|
|
||||||
import space.arim.morepaperlib.scheduling.GracefulScheduling;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.SortedSet;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
import java.util.jar.JarFile;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
|
|
||||||
public final class VillagerOptimizer extends JavaPlugin {
|
|
||||||
|
|
||||||
private static VillagerOptimizer instance;
|
|
||||||
private static CommandRegistration commandRegistration;
|
|
||||||
private static GracefulScheduling scheduling;
|
|
||||||
private static Cache<Villager, WrappedVillager> wrapperCache;
|
|
||||||
private static Map<String, LanguageCache> languageCacheMap;
|
|
||||||
private static Config config;
|
|
||||||
private static BukkitAudiences audiences;
|
|
||||||
private static ComponentLogger logger;
|
|
||||||
private static Metrics bStats;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoad() {
|
|
||||||
// Disable reflection logging
|
|
||||||
String shadedLibs = getClass().getPackage().getName() + ".libs";
|
|
||||||
Configurator.setLevel(shadedLibs + ".reflections.Reflections", Level.OFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnable() {
|
|
||||||
instance = this;
|
|
||||||
MorePaperLib morePaperLib = new MorePaperLib(this);
|
|
||||||
commandRegistration = morePaperLib.commandRegistration();
|
|
||||||
scheduling = morePaperLib.scheduling();
|
|
||||||
audiences = BukkitAudiences.create(this);
|
|
||||||
logger = ComponentLogger.logger(getLogger().getName());
|
|
||||||
bStats = new Metrics(this, 19954);
|
|
||||||
|
|
||||||
if (getServer().getPluginManager().getPlugin("AntiVillagerLag") != null) {
|
|
||||||
logger.warn("While VillagerOptimizer can read data previously created by AVL, running");
|
|
||||||
logger.warn("both plugins at the same time is unsafe and definitely will cause issues.");
|
|
||||||
logger.warn("To protect your game from corruption, VillagerOptimizer will now disable!");
|
|
||||||
logger.warn("Please decide for one of the plugins!");
|
|
||||||
getServer().getPluginManager().disablePlugin(this);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
getDataFolder().mkdirs();
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("Failed to create plugin directory! Cannot enable!", e);
|
|
||||||
getServer().getPluginManager().disablePlugin(this);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(Component.text("╭────────────────────────────────────────────────────────────╮").style(Util.PL_STYLE));
|
|
||||||
logger.info(Component.text("│ │").style(Util.PL_STYLE));
|
|
||||||
logger.info(Component.text("│ │").style(Util.PL_STYLE));
|
|
||||||
logger.info(Component.text("│ _ __ _ __ __ │").style(Util.PL_STYLE));
|
|
||||||
logger.info(Component.text("│ | | / /(_)/ // /___ _ ___ _ ___ ____ │").style(Util.PL_STYLE));
|
|
||||||
logger.info(Component.text("│ | |/ // // // // _ `// _ `// -_)/ __/ │").style(Util.PL_STYLE));
|
|
||||||
logger.info(Component.text("│ |___//_//_//_/ \\_,_/ \\_, / \\__//_/ │").style(Util.PL_STYLE));
|
|
||||||
logger.info(Component.text("│ ____ __ _ /___/_ │").style(Util.PL_STYLE));
|
|
||||||
logger.info(Component.text("│ / __ \\ ___ / /_ (_)__ _ (_)___ ___ ____ │").style(Util.PL_STYLE));
|
|
||||||
logger.info(Component.text("│ / /_/ // _ \\/ __// // ' \\ / //_ // -_)/ __/ │").style(Util.PL_STYLE));
|
|
||||||
logger.info(Component.text("│ \\____// .__/\\__//_//_/_/_//_/ /__/\\__//_/ │").style(Util.PL_STYLE));
|
|
||||||
logger.info(Component.text("│ /_/ by xGinko │").style(Util.PL_STYLE));
|
|
||||||
logger.info(Component.text("│ │").style(Util.PL_STYLE));
|
|
||||||
logger.info(Component.text("│ │").style(Util.PL_STYLE));
|
|
||||||
logger.info(Component.text("│ ")
|
|
||||||
.style(Util.PL_STYLE).append(Component.text("https://github.com/xGinko/VillagerOptimizer")
|
|
||||||
.color(NamedTextColor.GRAY)).append(Component.text(" │").style(Util.PL_STYLE)));
|
|
||||||
logger.info(Component.text("│ │").style(Util.PL_STYLE));
|
|
||||||
logger.info(Component.text("│ │").style(Util.PL_STYLE));
|
|
||||||
Permissions.registerAll();
|
|
||||||
|
|
||||||
logger.info(Component.text("│ ")
|
|
||||||
.style(Util.PL_STYLE).append(Component.text(" ➤ Loading Config...").style(Util.PL_STYLE))
|
|
||||||
.append(Component.text(" │").style(Util.PL_STYLE)));
|
|
||||||
reloadConfiguration();
|
|
||||||
|
|
||||||
logger.info(Component.text("│ ")
|
|
||||||
.style(Util.PL_STYLE).append(Component.text(" ➤ Loading Translations...").style(Util.PL_STYLE))
|
|
||||||
.append(Component.text(" │").style(Util.PL_STYLE)));
|
|
||||||
reloadLang(true);
|
|
||||||
|
|
||||||
logger.info(Component.text("│ ")
|
|
||||||
.style(Util.PL_STYLE).append(Component.text(" ✓ Done.").color(NamedTextColor.WHITE).decorate(TextDecoration.BOLD))
|
|
||||||
.append(Component.text(" │").style(Util.PL_STYLE)));
|
|
||||||
logger.info(Component.text("│ │").style(Util.PL_STYLE));
|
|
||||||
logger.info(Component.text("│ │").style(Util.PL_STYLE));
|
|
||||||
logger.info(Component.text("╰────────────────────────────────────────────────────────────╯").style(Util.PL_STYLE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisable() {
|
|
||||||
VillagerOptimizerModule.ENABLED_MODULES.forEach(VillagerOptimizerModule::disable);
|
|
||||||
VillagerOptimizerModule.ENABLED_MODULES.clear();
|
|
||||||
VillagerOptimizerCommand.COMMANDS.forEach(VillagerOptimizerCommand::disable);
|
|
||||||
VillagerOptimizerCommand.COMMANDS.clear();
|
|
||||||
if (wrapperCache != null) {
|
|
||||||
wrapperCache.cleanUp();
|
|
||||||
wrapperCache = null;
|
|
||||||
}
|
|
||||||
if (scheduling != null) {
|
|
||||||
scheduling.cancelGlobalTasks();
|
|
||||||
scheduling = null;
|
|
||||||
}
|
|
||||||
if (audiences != null) {
|
|
||||||
audiences.close();
|
|
||||||
audiences = null;
|
|
||||||
}
|
|
||||||
if (bStats != null) {
|
|
||||||
bStats.shutdown();
|
|
||||||
bStats = null;
|
|
||||||
}
|
|
||||||
commandRegistration = null;
|
|
||||||
languageCacheMap = null;
|
|
||||||
instance = null;
|
|
||||||
config = null;
|
|
||||||
logger = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull VillagerOptimizer getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull GracefulScheduling scheduling() {
|
|
||||||
return scheduling;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull CommandRegistration commandRegistration() {
|
|
||||||
return commandRegistration;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull Cache<Villager, WrappedVillager> wrappers() {
|
|
||||||
return wrapperCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull Config config() {
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull ComponentLogger logger() {
|
|
||||||
return logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull BukkitAudiences audiences() {
|
|
||||||
return audiences;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull LanguageCache getLang(Locale locale) {
|
|
||||||
return getLang(locale.toString().toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull LanguageCache getLang(CommandSender commandSender) {
|
|
||||||
return commandSender instanceof Player ? getLang(((Player) commandSender).locale()) : getLang(config.default_lang);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull LanguageCache getLang(String lang) {
|
|
||||||
if (!config.auto_lang) return languageCacheMap.get(config.default_lang.toString().toLowerCase());
|
|
||||||
return languageCacheMap.getOrDefault(lang.replace("-", "_"), languageCacheMap.get(config.default_lang.toString().toLowerCase()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reloadPlugin() {
|
|
||||||
reloadLang(false);
|
|
||||||
reloadConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reloadConfiguration() {
|
|
||||||
try {
|
|
||||||
config = new Config();
|
|
||||||
if (wrapperCache != null) wrapperCache.cleanUp();
|
|
||||||
wrapperCache = Caffeine.newBuilder().expireAfterWrite(config.cache_keep_time).build();
|
|
||||||
VillagerOptimizerCommand.reloadCommands();
|
|
||||||
VillagerOptimizerModule.reloadModules();
|
|
||||||
config.saveConfig();
|
|
||||||
} catch (Exception exception) {
|
|
||||||
logger.error("Error during config reload!", exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reloadLang(boolean logFancy) {
|
|
||||||
try {
|
|
||||||
final SortedSet<String> availableLocales = getAvailableTranslations();
|
|
||||||
if (!config.auto_lang) {
|
|
||||||
final String defaultLang = config.default_lang.toString().replace("-", "_").toLowerCase();
|
|
||||||
if (!availableLocales.contains(defaultLang))
|
|
||||||
throw new FileNotFoundException("Could not find any translation file for language '" + config.default_lang + "'");
|
|
||||||
availableLocales.removeIf(localeString -> !localeString.equalsIgnoreCase(defaultLang));
|
|
||||||
}
|
|
||||||
languageCacheMap = new HashMap<>(availableLocales.size());
|
|
||||||
for (String localeString : availableLocales) {
|
|
||||||
if (logFancy) logger.info(Component.text("│ ").style(Util.PL_STYLE)
|
|
||||||
.append(Component.text(" "+localeString).color(NamedTextColor.WHITE).decorate(TextDecoration.BOLD))
|
|
||||||
.append(Component.text(" │").style(Util.PL_STYLE)));
|
|
||||||
else logger.info(String.format("Found language file for %s", localeString));
|
|
||||||
languageCacheMap.put(localeString, new LanguageCache(localeString));
|
|
||||||
}
|
|
||||||
} catch (Throwable t) {
|
|
||||||
if (logFancy) logger.error(Component.text("│ ").style(Util.PL_STYLE)
|
|
||||||
.append(Component.text("LANG ERROR").color(NamedTextColor.RED).decorate(TextDecoration.BOLD))
|
|
||||||
.append(Component.text(" │").style(Util.PL_STYLE)), t);
|
|
||||||
else logger.error("Error while loading translation files!", t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull SortedSet<String> getAvailableTranslations() {
|
|
||||||
try (final JarFile pluginJar = new JarFile(getFile())) {
|
|
||||||
final File langDirectory = new File(getDataFolder() + "/lang");
|
|
||||||
Files.createDirectories(langDirectory.toPath());
|
|
||||||
final Pattern langPattern = Pattern.compile("([a-z]{1,3}_[a-z]{1,3})(\\.yml)", Pattern.CASE_INSENSITIVE);
|
|
||||||
return Stream.concat(pluginJar.stream().map(ZipEntry::getName), Arrays.stream(langDirectory.listFiles()).map(File::getName))
|
|
||||||
.map(langPattern::matcher)
|
|
||||||
.filter(Matcher::find)
|
|
||||||
.map(matcher -> matcher.group(1))
|
|
||||||
.collect(Collectors.toCollection(TreeSet::new));
|
|
||||||
} catch (Throwable t) {
|
|
||||||
logger.error("Failed while searching for available translations!", t);
|
|
||||||
return Collections.emptySortedSet();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
package me.xginko.villageroptimizer.commands;
|
|
||||||
|
|
||||||
import net.kyori.adventure.text.TextComponent;
|
|
||||||
import org.bukkit.command.CommandExecutor;
|
|
||||||
import org.bukkit.command.TabCompleter;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public abstract class SubCommand implements CommandExecutor, TabCompleter {
|
|
||||||
|
|
||||||
private final String label;
|
|
||||||
private final TextComponent syntax, description;
|
|
||||||
|
|
||||||
public SubCommand(String label, TextComponent syntax, TextComponent description) {
|
|
||||||
this.label = label;
|
|
||||||
this.syntax = syntax;
|
|
||||||
this.description = description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull String mergeArgs(@NotNull String[] args, int start) {
|
|
||||||
return String.join(" ", Arrays.copyOfRange(args, start, args.length));
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull String label() {
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull TextComponent syntax() {
|
|
||||||
return syntax;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull TextComponent description() {
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
package me.xginko.villageroptimizer.commands;
|
|
||||||
|
|
||||||
import me.xginko.villageroptimizer.VillagerOptimizer;
|
|
||||||
import me.xginko.villageroptimizer.struct.Disableable;
|
|
||||||
import me.xginko.villageroptimizer.struct.Enableable;
|
|
||||||
import org.bukkit.command.CommandException;
|
|
||||||
import org.bukkit.command.CommandExecutor;
|
|
||||||
import org.bukkit.command.PluginCommand;
|
|
||||||
import org.bukkit.command.TabCompleter;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.reflections.Reflections;
|
|
||||||
import org.reflections.scanners.Scanners;
|
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public abstract class VillagerOptimizerCommand implements Enableable, Disableable, CommandExecutor, TabCompleter {
|
|
||||||
|
|
||||||
public static final Set<VillagerOptimizerCommand> COMMANDS = new HashSet<>();
|
|
||||||
public static final List<String> RADIUS_SUGGESTIONS = Arrays.asList("5", "10", "25", "50");
|
|
||||||
public static final Reflections COMMANDS_PACKAGE = new Reflections(VillagerOptimizerCommand.class.getPackage().getName());
|
|
||||||
|
|
||||||
public final PluginCommand pluginCommand;
|
|
||||||
|
|
||||||
protected VillagerOptimizerCommand(@NotNull String name) throws CommandException {
|
|
||||||
PluginCommand pluginCommand = VillagerOptimizer.getInstance().getCommand(name);
|
|
||||||
if (pluginCommand != null) this.pluginCommand = pluginCommand;
|
|
||||||
else throw new CommandException("Command cannot be enabled because it's not defined in the plugin.yml.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void reloadCommands() {
|
|
||||||
COMMANDS.forEach(VillagerOptimizerCommand::disable);
|
|
||||||
COMMANDS.clear();
|
|
||||||
|
|
||||||
for (Class<?> clazz : COMMANDS_PACKAGE.get(Scanners.SubTypes.of(VillagerOptimizerCommand.class).asClass())) {
|
|
||||||
if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())) continue;
|
|
||||||
|
|
||||||
try {
|
|
||||||
COMMANDS.add((VillagerOptimizerCommand) clazz.getDeclaredConstructor().newInstance());
|
|
||||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
|
|
||||||
VillagerOptimizer.logger().error("Failed initialising command class '{}'.", clazz.getSimpleName(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
COMMANDS.forEach(VillagerOptimizerCommand::enable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void enable() {
|
|
||||||
pluginCommand.setExecutor(this);
|
|
||||||
pluginCommand.setTabCompleter(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void disable() {
|
|
||||||
pluginCommand.unregister(VillagerOptimizer.commandRegistration().getServerCommandMap());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
package me.xginko.villageroptimizer.commands.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.struct.enums.Permissions;
|
|
||||||
import me.xginko.villageroptimizer.utils.KyoriUtil;
|
|
||||||
import me.xginko.villageroptimizer.utils.Util;
|
|
||||||
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.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class VillagerOptimizerCmd extends VillagerOptimizerCommand {
|
|
||||||
|
|
||||||
private final List<SubCommand> subCommands;
|
|
||||||
private final List<String> tabCompletes;
|
|
||||||
|
|
||||||
public VillagerOptimizerCmd() {
|
|
||||||
super("villageroptimizer");
|
|
||||||
subCommands = Arrays.asList(new ReloadSubCmd(), new VersionSubCmd(), new DisableSubCmd());
|
|
||||||
tabCompletes = subCommands.stream().map(SubCommand::label).collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable List<String> onTabComplete(
|
|
||||||
@NotNull CommandSender sender, @NotNull Command command, @NotNull String commandLabel, @NotNull String[] args
|
|
||||||
) {
|
|
||||||
if (args.length == 1) {
|
|
||||||
return tabCompletes;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.length >= 2) {
|
|
||||||
for (SubCommand subCommand : subCommands) {
|
|
||||||
if (args[0].equalsIgnoreCase(subCommand.label())) {
|
|
||||||
return subCommand.onTabComplete(sender, command, commandLabel, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCommand(
|
|
||||||
@NotNull CommandSender sender, @NotNull Command command, @NotNull String commandLabel, @NotNull String[] args
|
|
||||||
) {
|
|
||||||
if (args.length >= 1) {
|
|
||||||
for (SubCommand subCommand : subCommands) {
|
|
||||||
if (args[0].equalsIgnoreCase(subCommand.label())) {
|
|
||||||
return subCommand.onCommand(sender, command, commandLabel, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
overview(sender);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void overview(CommandSender sender) {
|
|
||||||
if (!sender.hasPermission(Permissions.Commands.RELOAD.get()) && !sender.hasPermission(Permissions.Commands.VERSION.get())) return;
|
|
||||||
KyoriUtil.sendMessage(sender, Component.text("-----------------------------------------------------").color(NamedTextColor.GRAY));
|
|
||||||
KyoriUtil.sendMessage(sender, Component.text("VillagerOptimizer Commands").color(Util.PL_COLOR));
|
|
||||||
KyoriUtil.sendMessage(sender, Component.text("-----------------------------------------------------").color(NamedTextColor.GRAY));
|
|
||||||
subCommands.forEach(subCommand -> KyoriUtil.sendMessage(sender,
|
|
||||||
subCommand.syntax().append(Component.text(" - ").color(NamedTextColor.DARK_GRAY)).append(subCommand.description())));
|
|
||||||
KyoriUtil.sendMessage(sender,
|
|
||||||
Component.text("/optimizevillagers <blockradius>").color(Util.PL_COLOR)
|
|
||||||
.append(Component.text(" - ").color(NamedTextColor.DARK_GRAY))
|
|
||||||
.append(Component.text("Optimize villagers in a radius").color(NamedTextColor.GRAY))
|
|
||||||
);
|
|
||||||
KyoriUtil.sendMessage(sender,
|
|
||||||
Component.text("/unoptmizevillagers <blockradius>").color(Util.PL_COLOR)
|
|
||||||
.append(Component.text(" - ").color(NamedTextColor.DARK_GRAY))
|
|
||||||
.append(Component.text("Unoptimize villagers in a radius").color(NamedTextColor.GRAY))
|
|
||||||
);
|
|
||||||
KyoriUtil.sendMessage(sender, Component.text("-----------------------------------------------------").color(NamedTextColor.GRAY));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
package me.xginko.villageroptimizer.commands.villageroptimizer.subcommands;
|
|
||||||
|
|
||||||
import me.xginko.villageroptimizer.VillagerOptimizer;
|
|
||||||
import me.xginko.villageroptimizer.commands.SubCommand;
|
|
||||||
import me.xginko.villageroptimizer.struct.enums.Permissions;
|
|
||||||
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
|
||||||
import me.xginko.villageroptimizer.utils.KyoriUtil;
|
|
||||||
import me.xginko.villageroptimizer.utils.Util;
|
|
||||||
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.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class DisableSubCmd extends SubCommand {
|
|
||||||
|
|
||||||
public DisableSubCmd() {
|
|
||||||
super(
|
|
||||||
"disable",
|
|
||||||
Component.text("/villageroptimizer disable").color(Util.PL_COLOR),
|
|
||||||
Component.text("Disable all plugin tasks and listeners.").color(NamedTextColor.GRAY)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable List<String> onTabComplete(
|
|
||||||
@NotNull CommandSender sender, @NotNull Command command, @NotNull String commandLabel, @NotNull String[] args
|
|
||||||
) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCommand(
|
|
||||||
@NotNull CommandSender sender, @NotNull Command command, @NotNull String commandLabel, @NotNull String[] args
|
|
||||||
) {
|
|
||||||
if (!sender.hasPermission(Permissions.Commands.DISABLE.get())) {
|
|
||||||
KyoriUtil.sendMessage(sender, VillagerOptimizer.getLang(sender).no_permission);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
KyoriUtil.sendMessage(sender, Component.text("Disabling VillagerOptimizer...").color(NamedTextColor.RED));
|
|
||||||
VillagerOptimizerModule.ENABLED_MODULES.forEach(VillagerOptimizerModule::disable);
|
|
||||||
VillagerOptimizerModule.ENABLED_MODULES.clear();
|
|
||||||
KyoriUtil.sendMessage(sender, Component.text("Disabled all plugin listeners and tasks.").color(NamedTextColor.GREEN));
|
|
||||||
KyoriUtil.sendMessage(sender, Component.text("You can enable the plugin again using the reload command.").color(NamedTextColor.YELLOW));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
package me.xginko.villageroptimizer.commands.villageroptimizer.subcommands;
|
|
||||||
|
|
||||||
import me.xginko.villageroptimizer.VillagerOptimizer;
|
|
||||||
import me.xginko.villageroptimizer.commands.SubCommand;
|
|
||||||
import me.xginko.villageroptimizer.struct.enums.Permissions;
|
|
||||||
import me.xginko.villageroptimizer.utils.KyoriUtil;
|
|
||||||
import me.xginko.villageroptimizer.utils.Util;
|
|
||||||
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.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ReloadSubCmd extends SubCommand {
|
|
||||||
|
|
||||||
public ReloadSubCmd() {
|
|
||||||
super(
|
|
||||||
"reload",
|
|
||||||
Component.text("/villageroptimizer reload").color(Util.PL_COLOR),
|
|
||||||
Component.text("Reload the plugin configuration.").color(NamedTextColor.GRAY));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable List<String> onTabComplete(
|
|
||||||
@NotNull CommandSender sender, @NotNull Command command, @NotNull String commandLabel, @NotNull String[] args
|
|
||||||
) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCommand(
|
|
||||||
@NotNull CommandSender sender, @NotNull Command command, @NotNull String commandLabel, @NotNull String[] args
|
|
||||||
) {
|
|
||||||
if (!sender.hasPermission(Permissions.Commands.RELOAD.get())) {
|
|
||||||
KyoriUtil.sendMessage(sender, VillagerOptimizer.getLang(sender).no_permission);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
KyoriUtil.sendMessage(sender, Component.text("Reloading VillagerOptimizer...").color(NamedTextColor.WHITE));
|
|
||||||
VillagerOptimizer.scheduling().asyncScheduler().run(reload -> {
|
|
||||||
VillagerOptimizer.getInstance().reloadPlugin();
|
|
||||||
KyoriUtil.sendMessage(sender, Component.text("Reload complete.").color(NamedTextColor.GREEN));
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
package me.xginko.villageroptimizer.commands.villageroptimizer.subcommands;
|
|
||||||
|
|
||||||
import io.papermc.paper.plugin.configuration.PluginMeta;
|
|
||||||
import me.xginko.villageroptimizer.VillagerOptimizer;
|
|
||||||
import me.xginko.villageroptimizer.commands.SubCommand;
|
|
||||||
import me.xginko.villageroptimizer.struct.enums.Permissions;
|
|
||||||
import me.xginko.villageroptimizer.utils.KyoriUtil;
|
|
||||||
import me.xginko.villageroptimizer.utils.Util;
|
|
||||||
import net.kyori.adventure.text.Component;
|
|
||||||
import net.kyori.adventure.text.event.ClickEvent;
|
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import org.bukkit.command.Command;
|
|
||||||
import org.bukkit.command.CommandSender;
|
|
||||||
import org.bukkit.plugin.PluginDescriptionFile;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class VersionSubCmd extends SubCommand {
|
|
||||||
|
|
||||||
public VersionSubCmd() {
|
|
||||||
super(
|
|
||||||
"version",
|
|
||||||
Component.text("/villageroptimizer version").color(Util.PL_COLOR),
|
|
||||||
Component.text("Show the plugin version.").color(NamedTextColor.GRAY)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable List<String> onTabComplete(
|
|
||||||
@NotNull CommandSender sender, @NotNull Command command, @NotNull String commandLabel, @NotNull String[] args
|
|
||||||
) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCommand(
|
|
||||||
@NotNull CommandSender sender, @NotNull Command command, @NotNull String commandLabel, @NotNull String[] args
|
|
||||||
) {
|
|
||||||
if (!sender.hasPermission(Permissions.Commands.VERSION.get())) {
|
|
||||||
KyoriUtil.sendMessage(sender, VillagerOptimizer.getLang(sender).no_permission);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
String name, version, website, author;
|
|
||||||
|
|
||||||
try {
|
|
||||||
final PluginMeta pluginMeta = VillagerOptimizer.getInstance().getPluginMeta();
|
|
||||||
name = pluginMeta.getName();
|
|
||||||
version = pluginMeta.getVersion();
|
|
||||||
website = pluginMeta.getWebsite();
|
|
||||||
author = pluginMeta.getAuthors().get(0);
|
|
||||||
} catch (Throwable versionIncompatible) {
|
|
||||||
final PluginDescriptionFile pluginYML = VillagerOptimizer.getInstance().getDescription();
|
|
||||||
name = pluginYML.getName();
|
|
||||||
version = pluginYML.getVersion();
|
|
||||||
website = pluginYML.getWebsite();
|
|
||||||
author = pluginYML.getAuthors().get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
KyoriUtil.sendMessage(sender, Component.newline()
|
|
||||||
.append(
|
|
||||||
Component.text(name + " " + version)
|
|
||||||
.style(Util.PL_STYLE)
|
|
||||||
.clickEvent(ClickEvent.openUrl(website))
|
|
||||||
)
|
|
||||||
.append(Component.text(" by ").color(NamedTextColor.GRAY))
|
|
||||||
.append(
|
|
||||||
Component.text(author)
|
|
||||||
.color(NamedTextColor.WHITE)
|
|
||||||
.clickEvent(ClickEvent.openUrl("https://github.com/xGinko"))
|
|
||||||
)
|
|
||||||
.append(Component.newline())
|
|
||||||
);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,130 +0,0 @@
|
|||||||
package me.xginko.villageroptimizer.config;
|
|
||||||
|
|
||||||
import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
|
|
||||||
import me.xginko.villageroptimizer.VillagerOptimizer;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
public class Config {
|
|
||||||
|
|
||||||
private final @NotNull ConfigFile config;
|
|
||||||
public final @NotNull Locale default_lang;
|
|
||||||
public final @NotNull Duration cache_keep_time;
|
|
||||||
public final boolean auto_lang, support_other_plugins;
|
|
||||||
|
|
||||||
public Config() throws Exception {
|
|
||||||
// Load config.yml with ConfigMaster
|
|
||||||
this.config = ConfigFile.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\n" +
|
|
||||||
"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 = Duration.ofSeconds(Math.max(1, getInt("general.cache-keep-time-seconds", 30,
|
|
||||||
"The amount of time in seconds a villager will be kept in the plugin's cache.")));
|
|
||||||
this.support_other_plugins = getBoolean("general.support-avl-villagers", false,
|
|
||||||
"Enable if you have previously used AntiVillagerLag\n" +
|
|
||||||
"(https://www.spigotmc.org/resources/antivillagerlag.102949/).\n" +
|
|
||||||
"Tries to read pre-existing info like optimization state so players\n" +
|
|
||||||
"don't need to reoptimize their villagers.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void saveConfig() {
|
|
||||||
try {
|
|
||||||
this.config.save();
|
|
||||||
} catch (Throwable throwable) {
|
|
||||||
VillagerOptimizer.logger().error("Failed to save config file!", throwable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void structureConfig() {
|
|
||||||
this.config.addDefault("config-version", 1.00);
|
|
||||||
this.createTitledSection("General", "general");
|
|
||||||
this.createTitledSection("Optimization", "optimization-methods");
|
|
||||||
this.config.addDefault("optimization-methods.commands.unoptimizevillagers", null);
|
|
||||||
this.config.addComment("optimization-methods.commands",
|
|
||||||
"If you want to disable commands, negate the following permissions:\n" +
|
|
||||||
"villageroptimizer.cmd.optimize\n" +
|
|
||||||
"villageroptimizer.cmd.unoptimize");
|
|
||||||
this.config.addDefault("optimization-methods.nametag-optimization.enable", true);
|
|
||||||
this.createTitledSection("Villager Chunk Limit", "villager-chunk-limit");
|
|
||||||
this.createTitledSection("Gameplay", "gameplay");
|
|
||||||
this.config.addDefault("gameplay.prevent-trading-with-unoptimized.enable", false);
|
|
||||||
this.config.addDefault("gameplay.restock-optimized-trades", null);
|
|
||||||
this.config.addDefault("gameplay.level-optimized-profession", null);
|
|
||||||
this.config.addDefault("gameplay.unoptimize-on-job-loose.enable", true);
|
|
||||||
this.config.addDefault("gameplay.villagers-can-be-leashed.enable", true);
|
|
||||||
this.config.addDefault("gameplay.villagers-spawn-as-adults.enable", false);
|
|
||||||
this.config.addDefault("gameplay.rename-optimized-villagers.enable", false);
|
|
||||||
this.config.addDefault("gameplay.prevent-entities-from-targeting-optimized.enable", true);
|
|
||||||
this.config.addDefault("gameplay.prevent-damage-to-optimized.enable", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void createTitledSection(@NotNull String title, @NotNull String path) {
|
|
||||||
this.config.addSection(title);
|
|
||||||
this.config.addDefault(path, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull ConfigFile master() {
|
|
||||||
return this.config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getBoolean(@NotNull String path, boolean def, @NotNull String comment) {
|
|
||||||
this.config.addDefault(path, def, comment);
|
|
||||||
return this.config.getBoolean(path, def);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getBoolean(@NotNull String path, boolean def) {
|
|
||||||
this.config.addDefault(path, def);
|
|
||||||
return this.config.getBoolean(path, def);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull String getString(@NotNull String path, @NotNull String def, @NotNull String comment) {
|
|
||||||
this.config.addDefault(path, def, comment);
|
|
||||||
return this.config.getString(path, def);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull String getString(@NotNull String path, @NotNull String def) {
|
|
||||||
this.config.addDefault(path, def);
|
|
||||||
return this.config.getString(path, def);
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getDouble(@NotNull String path, @NotNull Double def, @NotNull String comment) {
|
|
||||||
this.config.addDefault(path, def, comment);
|
|
||||||
return this.config.getDouble(path, def);
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getDouble(@NotNull String path, @NotNull Double def) {
|
|
||||||
this.config.addDefault(path, def);
|
|
||||||
return this.config.getDouble(path, def);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getInt(@NotNull String path, int def, @NotNull String comment) {
|
|
||||||
this.config.addDefault(path, def, comment);
|
|
||||||
return this.config.getInteger(path, def);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getInt(@NotNull String path, int def) {
|
|
||||||
this.config.addDefault(path, def);
|
|
||||||
return this.config.getInteger(path, def);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull <T> List<T> getList(@NotNull String path, @NotNull List<T> def, @NotNull String comment) {
|
|
||||||
this.config.addDefault(path, def, comment);
|
|
||||||
return this.config.getList(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull <T> List<T> getList(@NotNull String path, @NotNull List<T> def) {
|
|
||||||
this.config.addDefault(path, def);
|
|
||||||
return this.config.getList(path);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package me.xginko.villageroptimizer.logging;
|
|
||||||
|
|
||||||
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
|
|
||||||
import net.kyori.adventure.text.logger.slf4j.ComponentLoggerProvider;
|
|
||||||
import net.kyori.adventure.text.serializer.ansi.ANSIComponentSerializer;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
@SuppressWarnings("UnstableApiUsage")
|
|
||||||
public final class ComponentLoggerProviderImpl implements ComponentLoggerProvider {
|
|
||||||
private static final @NotNull ANSIComponentSerializer SERIALIZER = ANSIComponentSerializer.builder()
|
|
||||||
.flattener(TranslatableMapper.FLATTENER)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull ComponentLogger logger(
|
|
||||||
final @NotNull LoggerHelper helper,
|
|
||||||
final @NotNull String name
|
|
||||||
) {
|
|
||||||
return helper.delegating(LoggerFactory.getLogger(name), SERIALIZER::serialize);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
package me.xginko.villageroptimizer.logging;
|
|
||||||
|
|
||||||
import net.kyori.adventure.text.Component;
|
|
||||||
import net.kyori.adventure.text.TranslatableComponent;
|
|
||||||
import net.kyori.adventure.text.flattener.ComponentFlattener;
|
|
||||||
import net.kyori.adventure.translation.GlobalTranslator;
|
|
||||||
import net.kyori.adventure.translation.TranslationRegistry;
|
|
||||||
import net.kyori.adventure.translation.Translator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public enum TranslatableMapper implements BiConsumer<TranslatableComponent, Consumer<Component>> {
|
|
||||||
INSTANCE;
|
|
||||||
|
|
||||||
public static final @NotNull ComponentFlattener FLATTENER = ComponentFlattener.basic().toBuilder()
|
|
||||||
.complexMapper(TranslatableComponent.class, TranslatableMapper.INSTANCE)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void accept(
|
|
||||||
final @NotNull TranslatableComponent translatableComponent,
|
|
||||||
final @NotNull Consumer<Component> componentConsumer
|
|
||||||
) {
|
|
||||||
for (final Translator source : GlobalTranslator.translator().sources()) {
|
|
||||||
if (source instanceof TranslationRegistry && ((TranslationRegistry) source).contains(translatableComponent.key())) {
|
|
||||||
componentConsumer.accept(GlobalTranslator.render(translatableComponent, Locale.getDefault()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final @Nullable String fallback = translatableComponent.fallback();
|
|
||||||
if (fallback == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (final Translator source : GlobalTranslator.translator().sources()) {
|
|
||||||
if (source instanceof TranslationRegistry && ((TranslationRegistry) source).contains(fallback)) {
|
|
||||||
componentConsumer.accept(GlobalTranslator.render(Component.translatable(fallback), Locale.getDefault()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,232 +0,0 @@
|
|||||||
package me.xginko.villageroptimizer.modules;
|
|
||||||
|
|
||||||
import com.cryptomorin.xseries.XEntityType;
|
|
||||||
import me.xginko.villageroptimizer.struct.models.ExpiringSet;
|
|
||||||
import me.xginko.villageroptimizer.utils.LocationUtil;
|
|
||||||
import me.xginko.villageroptimizer.utils.Util;
|
|
||||||
import me.xginko.villageroptimizer.wrapper.WrappedVillager;
|
|
||||||
import org.bukkit.Chunk;
|
|
||||||
import org.bukkit.World;
|
|
||||||
import org.bukkit.entity.Entity;
|
|
||||||
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 org.jetbrains.annotations.NotNull;
|
|
||||||
import space.arim.morepaperlib.scheduling.ScheduledTask;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public class VillagerChunkLimit extends VillagerOptimizerModule implements Runnable, Listener {
|
|
||||||
|
|
||||||
private ScheduledTask periodic_chunk_check;
|
|
||||||
private final List<Villager.Profession> non_optimized_removal_priority, optimized_removal_priority;
|
|
||||||
private final Set<Villager.Profession> profession_whitelist;
|
|
||||||
private final ExpiringSet<Chunk> checked_chunks;
|
|
||||||
private final long check_period;
|
|
||||||
private final int non_optimized_max_per_chunk, optimized_max_per_chunk;
|
|
||||||
private final boolean log_enabled, skip_unloaded_chunks, use_whitelist;
|
|
||||||
|
|
||||||
protected VillagerChunkLimit() {
|
|
||||||
super("villager-chunk-limit");
|
|
||||||
config.master().addComment(configPath + ".enable",
|
|
||||||
"Checks chunks for too many villagers and removes excess villagers based on priority.");
|
|
||||||
this.check_period = config.getInt(configPath + ".check-period-in-ticks", 600,
|
|
||||||
"Check all loaded chunks every X ticks. 1 second = 20 ticks\n" +
|
|
||||||
"A shorter delay in between checks is more efficient but is also more resource intense.\n" +
|
|
||||||
"A larger delay is less resource intense but could become inefficient.");
|
|
||||||
this.skip_unloaded_chunks = config.getBoolean(configPath + ".skip-not-fully-loaded-chunks", true,
|
|
||||||
"Does not check chunks that don't have their entities loaded.");
|
|
||||||
this.checked_chunks = new ExpiringSet<>(Duration.ofSeconds(
|
|
||||||
Math.max(1, config.getInt(configPath + ".chunk-check-cooldown-seconds", 5,
|
|
||||||
"The delay in seconds a chunk will not be checked again after the first time.\n" +
|
|
||||||
"Reduces chances to lag the server due to overchecking."))));
|
|
||||||
this.log_enabled = config.getBoolean(configPath + ".log-removals", true);
|
|
||||||
List<String> defaults = Stream.of(
|
|
||||||
"NONE", "NITWIT", "SHEPHERD", "FISHERMAN", "BUTCHER", "CARTOGRAPHER", "LEATHERWORKER",
|
|
||||||
"FLETCHER", "MASON", "FARMER", "ARMORER", "TOOLSMITH", "WEAPONSMITH", "CLERIC", "LIBRARIAN")
|
|
||||||
.filter(profession -> {
|
|
||||||
try {
|
|
||||||
// Make sure no scary warnings appear when creating config defaults
|
|
||||||
Villager.Profession.valueOf(profession);
|
|
||||||
return true;
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}).collect(Collectors.toList());
|
|
||||||
this.use_whitelist = config.getBoolean(configPath + ".whitelist.enable", false,
|
|
||||||
"Enable if you only want to manage villager counts for certain profession types.");
|
|
||||||
this.profession_whitelist = config.getList(configPath + ".whitelist.professions", Arrays.asList("NONE", "NITWIT"),
|
|
||||||
"Professions in this list will not be touched by the chunk limit.")
|
|
||||||
.stream()
|
|
||||||
.map(configuredProfession -> {
|
|
||||||
try {
|
|
||||||
return Villager.Profession.valueOf(configuredProfession);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
warn("(whitelist) Villager profession '" + configuredProfession +
|
|
||||||
"' not recognized. Make sure you're using the correct profession enums from " +
|
|
||||||
"https://jd.papermc.io/paper/1.20/org/bukkit/entity/Villager.Profession.html.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.collect(Collectors.toCollection(HashSet::new));
|
|
||||||
this.non_optimized_max_per_chunk = config.getInt(configPath + ".unoptimized.max-per-chunk", 20,
|
|
||||||
"The maximum amount of unoptimized villagers per chunk.");
|
|
||||||
this.non_optimized_removal_priority = config.getList(configPath + ".unoptimized.removal-priority", new ArrayList<>(defaults),
|
|
||||||
"Professions that are in the top of the list are going to be scheduled for removal first.\n" +
|
|
||||||
"Use enums from https://jd.papermc.io/paper/1.20/org/bukkit/entity/Villager.Profession.html")
|
|
||||||
.stream()
|
|
||||||
.map(configuredProfession -> {
|
|
||||||
try {
|
|
||||||
return Villager.Profession.valueOf(configuredProfession);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
warn("(unoptimized) Villager profession '" + configuredProfession +
|
|
||||||
"' not recognized. Make sure you're using the correct profession enums from " +
|
|
||||||
"https://jd.papermc.io/paper/1.20/org/bukkit/entity/Villager.Profession.html.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
this.optimized_max_per_chunk = config.getInt(configPath + ".optimized.max-per-chunk", 60,
|
|
||||||
"The maximum amount of optimized villagers per chunk.");
|
|
||||||
this.optimized_removal_priority = config.getList(configPath + ".optimized.removal-priority", new ArrayList<>(defaults))
|
|
||||||
.stream()
|
|
||||||
.map(configuredProfession -> {
|
|
||||||
try {
|
|
||||||
return Villager.Profession.valueOf(configuredProfession);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
warn("(optimized) Villager profession '" + configuredProfession + "' not recognized. " +
|
|
||||||
"Make sure you're using the correct profession enums from " +
|
|
||||||
"https://jd.papermc.io/paper/1.20/org/bukkit/entity/Villager.Profession.html.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void enable() {
|
|
||||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
|
||||||
periodic_chunk_check = scheduling.globalRegionalScheduler().runAtFixedRate(this, check_period, check_period);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldEnable() {
|
|
||||||
return config.getBoolean(configPath + ".enable", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void disable() {
|
|
||||||
HandlerList.unregisterAll(this);
|
|
||||||
if (periodic_chunk_check != null) periodic_chunk_check.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
for (World world : plugin.getServer().getWorlds()) {
|
|
||||||
for (Chunk chunk : world.getLoadedChunks()) {
|
|
||||||
scheduling.regionSpecificScheduler(chunk.getWorld(), chunk.getX(), chunk.getZ()).run(() -> {
|
|
||||||
if (!skip_unloaded_chunks || Util.isChunkLoaded(chunk)) {
|
|
||||||
manageVillagerCount(chunk);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
|
||||||
private void onCreatureSpawn(CreatureSpawnEvent event) {
|
|
||||||
if (event.getEntityType() == XEntityType.VILLAGER.get()) {
|
|
||||||
scheduling.regionSpecificScheduler(event.getLocation()).run(() -> {
|
|
||||||
manageVillagerCount(event.getEntity().getChunk());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
|
||||||
private void onInteract(PlayerInteractEntityEvent event) {
|
|
||||||
if (event.getRightClicked().getType() == XEntityType.VILLAGER.get()) {
|
|
||||||
scheduling.regionSpecificScheduler(event.getRightClicked().getLocation()).run(() -> {
|
|
||||||
manageVillagerCount(event.getRightClicked().getChunk());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void manageVillagerCount(@NotNull Chunk chunk) {
|
|
||||||
// Remember which chunk we have already checked
|
|
||||||
if (checked_chunks.contains(chunk)) return;
|
|
||||||
else checked_chunks.add(chunk);
|
|
||||||
|
|
||||||
// Collect all optimized and unoptimized villagers in that chunk
|
|
||||||
List<Villager> optimized_villagers = new ArrayList<>();
|
|
||||||
List<Villager> not_optimized_villagers = new ArrayList<>();
|
|
||||||
|
|
||||||
for (Entity entity : chunk.getEntities()) {
|
|
||||||
if (entity.getType() != XEntityType.VILLAGER.get()) continue;
|
|
||||||
|
|
||||||
Villager villager = (Villager) entity;
|
|
||||||
|
|
||||||
// Ignore villager if profession is not in the whitelist
|
|
||||||
if (use_whitelist && profession_whitelist.contains(villager.getProfession())) continue;
|
|
||||||
|
|
||||||
if (wrapperCache.get(villager, WrappedVillager::new).isOptimized()) {
|
|
||||||
optimized_villagers.add(villager);
|
|
||||||
} else {
|
|
||||||
not_optimized_villagers.add(villager);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if there are more unoptimized villagers in that chunk than allowed
|
|
||||||
final int not_optimized_villagers_too_many = not_optimized_villagers.size() - non_optimized_max_per_chunk;
|
|
||||||
if (not_optimized_villagers_too_many > 0) {
|
|
||||||
// Sort villagers by profession priority
|
|
||||||
not_optimized_villagers.sort(Comparator.comparingInt(villager -> {
|
|
||||||
final Villager.Profession profession = villager.getProfession();
|
|
||||||
return non_optimized_removal_priority.contains(profession) ? non_optimized_removal_priority.indexOf(profession) : Integer.MAX_VALUE;
|
|
||||||
}));
|
|
||||||
// Remove prioritized villagers that are too many
|
|
||||||
for (int i = 0; i < not_optimized_villagers_too_many; i++) {
|
|
||||||
Villager villager = not_optimized_villagers.get(i);
|
|
||||||
scheduling.entitySpecificScheduler(villager).run(kill -> {
|
|
||||||
villager.remove();
|
|
||||||
if (log_enabled) info("Removed unoptimized villager with profession '" +
|
|
||||||
Util.toNiceString(villager.getProfession()) + "' at " + LocationUtil.toString(villager.getLocation()));
|
|
||||||
}, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if there are more optimized villagers in that chunk than allowed
|
|
||||||
final int optimized_villagers_too_many = optimized_villagers.size() - optimized_max_per_chunk;
|
|
||||||
if (optimized_villagers_too_many > 0) {
|
|
||||||
// Sort villagers by profession priority
|
|
||||||
optimized_villagers.sort(Comparator.comparingInt(villager -> {
|
|
||||||
final Villager.Profession profession = villager.getProfession();
|
|
||||||
return optimized_removal_priority.contains(profession) ? optimized_removal_priority.indexOf(profession) : Integer.MAX_VALUE;
|
|
||||||
}));
|
|
||||||
// Remove prioritized villagers that are too many
|
|
||||||
for (int i = 0; i < optimized_villagers_too_many; i++) {
|
|
||||||
Villager villager = optimized_villagers.get(i);
|
|
||||||
scheduling.entitySpecificScheduler(villager).run(kill -> {
|
|
||||||
villager.remove();
|
|
||||||
if (log_enabled) info("Removed unoptimized villager with profession '" +
|
|
||||||
Util.toNiceString(villager.getProfession()) + "' at " + LocationUtil.toString(villager.getLocation()));
|
|
||||||
}, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,87 +0,0 @@
|
|||||||
package me.xginko.villageroptimizer.modules;
|
|
||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.Cache;
|
|
||||||
import me.xginko.villageroptimizer.VillagerOptimizer;
|
|
||||||
import me.xginko.villageroptimizer.config.Config;
|
|
||||||
import me.xginko.villageroptimizer.struct.Disableable;
|
|
||||||
import me.xginko.villageroptimizer.struct.Enableable;
|
|
||||||
import me.xginko.villageroptimizer.wrapper.WrappedVillager;
|
|
||||||
import org.bukkit.entity.Villager;
|
|
||||||
import org.reflections.Reflections;
|
|
||||||
import org.reflections.scanners.Scanners;
|
|
||||||
import space.arim.morepaperlib.scheduling.GracefulScheduling;
|
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public abstract class VillagerOptimizerModule implements Enableable, Disableable {
|
|
||||||
|
|
||||||
private static final Reflections MODULES_PACKAGE = new Reflections(VillagerOptimizerModule.class.getPackage().getName());
|
|
||||||
public static final Set<VillagerOptimizerModule> ENABLED_MODULES = new HashSet<>();
|
|
||||||
|
|
||||||
public abstract boolean shouldEnable();
|
|
||||||
|
|
||||||
protected final VillagerOptimizer plugin;
|
|
||||||
protected final Config config;
|
|
||||||
protected final Cache<Villager, WrappedVillager> wrapperCache;
|
|
||||||
protected final GracefulScheduling scheduling;
|
|
||||||
public final String configPath;
|
|
||||||
private final String logFormat;
|
|
||||||
|
|
||||||
public VillagerOptimizerModule(String configPath) {
|
|
||||||
this.plugin = VillagerOptimizer.getInstance();
|
|
||||||
this.config = VillagerOptimizer.config();
|
|
||||||
this.wrapperCache = VillagerOptimizer.wrappers();
|
|
||||||
this.scheduling = VillagerOptimizer.scheduling();
|
|
||||||
this.configPath = configPath;
|
|
||||||
shouldEnable(); // Ensure enable option is always first
|
|
||||||
String[] paths = configPath.split("\\.");
|
|
||||||
if (paths.length <= 2) {
|
|
||||||
this.logFormat = "<" + configPath + "> {}";
|
|
||||||
} else {
|
|
||||||
this.logFormat = "<" + paths[paths.length - 2] + "." + paths[paths.length - 1] + "> {}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void reloadModules() {
|
|
||||||
ENABLED_MODULES.forEach(VillagerOptimizerModule::disable);
|
|
||||||
ENABLED_MODULES.clear();
|
|
||||||
|
|
||||||
for (Class<?> clazz : MODULES_PACKAGE.get(Scanners.SubTypes.of(VillagerOptimizerModule.class).asClass())) {
|
|
||||||
if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())) continue;
|
|
||||||
|
|
||||||
try {
|
|
||||||
VillagerOptimizerModule module = (VillagerOptimizerModule) clazz.getDeclaredConstructor().newInstance();
|
|
||||||
if (module.shouldEnable()) {
|
|
||||||
ENABLED_MODULES.add(module);
|
|
||||||
}
|
|
||||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
|
|
||||||
VillagerOptimizer.logger().error("Failed initialising module class '{}'.", clazz.getSimpleName(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ENABLED_MODULES.forEach(VillagerOptimizerModule::enable);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void error(String message, Throwable throwable) {
|
|
||||||
VillagerOptimizer.logger().error(logFormat, message, throwable);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void error(String message) {
|
|
||||||
VillagerOptimizer.logger().error(logFormat, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void warn(String message) {
|
|
||||||
VillagerOptimizer.logger().warn(logFormat, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void info(String message) {
|
|
||||||
VillagerOptimizer.logger().info(logFormat, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void notRecognized(Class<?> clazz, String unrecognized) {
|
|
||||||
warn("Unable to parse " + clazz.getSimpleName() + " at '" + unrecognized + "'. Please check your configuration.");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
package me.xginko.villageroptimizer.modules.gameplay;
|
|
||||||
|
|
||||||
import com.cryptomorin.xseries.XEntityType;
|
|
||||||
import com.cryptomorin.xseries.XMaterial;
|
|
||||||
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
|
||||||
import me.xginko.villageroptimizer.utils.LocationUtil;
|
|
||||||
import me.xginko.villageroptimizer.wrapper.WrappedVillager;
|
|
||||||
import org.bukkit.GameMode;
|
|
||||||
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.entity.PlayerLeashEntityEvent;
|
|
||||||
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
|
||||||
import org.bukkit.inventory.ItemStack;
|
|
||||||
|
|
||||||
public class EnableLeashingVillagers extends VillagerOptimizerModule implements Listener {
|
|
||||||
|
|
||||||
private final boolean only_optimized, log_enabled;
|
|
||||||
|
|
||||||
public EnableLeashingVillagers() {
|
|
||||||
super("gameplay.villagers-can-be-leashed");
|
|
||||||
config.master().addComment(configPath + ".enable",
|
|
||||||
"Enable leashing of villagers, enabling players to easily move villagers to where they want them to be.");
|
|
||||||
this.only_optimized = config.getBoolean(configPath + ".only-optimized", false,
|
|
||||||
"If set to true, only optimized villagers can be leashed.");
|
|
||||||
this.log_enabled = config.getBoolean(configPath + ".log", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void enable() {
|
|
||||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void disable() {
|
|
||||||
HandlerList.unregisterAll(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldEnable() {
|
|
||||||
return config.getBoolean(configPath + ".enable", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
|
||||||
private void onLeash(PlayerInteractEntityEvent event) {
|
|
||||||
if (event.getRightClicked().getType() != XEntityType.VILLAGER.get()) return;
|
|
||||||
final Player player = event.getPlayer();
|
|
||||||
final ItemStack handItem = player.getInventory().getItem(event.getHand());
|
|
||||||
if (handItem == null || handItem.getType() != XMaterial.LEAD.parseMaterial()) return;
|
|
||||||
|
|
||||||
final Villager villager = (Villager) event.getRightClicked();
|
|
||||||
if (villager.isLeashed()) return;
|
|
||||||
if (only_optimized && !wrapperCache.get(villager, WrappedVillager::new).isOptimized()) return;
|
|
||||||
|
|
||||||
event.setCancelled(true); // Cancel the event, so we don't interact with the villager
|
|
||||||
|
|
||||||
// Call event for compatibility with other plugins, constructing non deprecated if available
|
|
||||||
PlayerLeashEntityEvent leashEvent;
|
|
||||||
try {
|
|
||||||
leashEvent = new PlayerLeashEntityEvent(villager, player, player, event.getHand());
|
|
||||||
} catch (Throwable versionIncompatible) {
|
|
||||||
leashEvent = new PlayerLeashEntityEvent(villager, player, player);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If canceled by any plugin, do nothing
|
|
||||||
if (!leashEvent.callEvent()) return;
|
|
||||||
|
|
||||||
scheduling.entitySpecificScheduler(villager).run(leash -> {
|
|
||||||
// Legitimate to not use entities from the event object since they are final in PlayerLeashEntityEvent
|
|
||||||
if (!villager.setLeashHolder(player)) return;
|
|
||||||
if (player.getGameMode().equals(GameMode.SURVIVAL))
|
|
||||||
handItem.subtract(1); // Manually consume for survival players
|
|
||||||
|
|
||||||
if (log_enabled) {
|
|
||||||
info(player.getName() + " leashed a villager at " + LocationUtil.toString(villager.getLocation()));
|
|
||||||
}
|
|
||||||
}, null);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
package me.xginko.villageroptimizer.modules.gameplay;
|
|
||||||
|
|
||||||
import com.cryptomorin.xseries.XEntityType;
|
|
||||||
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
|
||||||
import me.xginko.villageroptimizer.wrapper.WrappedVillager;
|
|
||||||
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.EntityTransformEvent;
|
|
||||||
|
|
||||||
public class FixOptimisationAfterCure extends VillagerOptimizerModule implements Listener {
|
|
||||||
|
|
||||||
public FixOptimisationAfterCure() {
|
|
||||||
super("post-cure-optimization-fix");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void enable() {
|
|
||||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void disable() {
|
|
||||||
HandlerList.unregisterAll(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldEnable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
|
||||||
private void onTransform(EntityTransformEvent event) {
|
|
||||||
if (
|
|
||||||
event.getTransformReason() == EntityTransformEvent.TransformReason.CURED
|
|
||||||
&& event.getTransformedEntity().getType() == XEntityType.VILLAGER.get()
|
|
||||||
) {
|
|
||||||
Villager villager = (Villager) event.getTransformedEntity();
|
|
||||||
scheduling.entitySpecificScheduler(villager).runDelayed(() -> {
|
|
||||||
WrappedVillager wVillager = wrapperCache.get(villager, WrappedVillager::new);
|
|
||||||
wVillager.setOptimizationType(wVillager.getOptimizationType());
|
|
||||||
}, null, 40L);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
package me.xginko.villageroptimizer.modules.gameplay;
|
|
||||||
|
|
||||||
import com.cryptomorin.xseries.XPotion;
|
|
||||||
import me.xginko.villageroptimizer.VillagerOptimizer;
|
|
||||||
import me.xginko.villageroptimizer.config.Config;
|
|
||||||
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
|
||||||
import me.xginko.villageroptimizer.utils.KyoriUtil;
|
|
||||||
import me.xginko.villageroptimizer.utils.Util;
|
|
||||||
import me.xginko.villageroptimizer.wrapper.WrappedVillager;
|
|
||||||
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 java.time.Duration;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
public class LevelOptimizedProfession extends VillagerOptimizerModule implements Listener {
|
|
||||||
|
|
||||||
private static final PotionEffect SUPER_SLOWNESS = new PotionEffect(
|
|
||||||
XPotion.SLOWNESS.getPotionEffectType(), 120, 120, false, false);
|
|
||||||
|
|
||||||
private final boolean notify_player;
|
|
||||||
private final long cooldown_millis;
|
|
||||||
|
|
||||||
public LevelOptimizedProfession() {
|
|
||||||
super("gameplay.level-optimized-profession");
|
|
||||||
Config config = VillagerOptimizer.config();
|
|
||||||
config.master().addComment(configPath,
|
|
||||||
"This is needed to allow optimized villagers to level up.\n" +
|
|
||||||
"Temporarily enables the villagers AI to allow it to level up and then disables it again.");
|
|
||||||
this.cooldown_millis = TimeUnit.SECONDS.toMillis(
|
|
||||||
config.getInt(configPath + ".level-check-cooldown-seconds", 5,
|
|
||||||
"Cooldown in seconds until the level of a villager will be checked and updated again.\n" +
|
|
||||||
"Recommended to leave as is."));
|
|
||||||
this.notify_player = config.getBoolean(configPath + ".notify-player", true,
|
|
||||||
"Tell players to wait when a villager is leveling up.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void enable() {
|
|
||||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void disable() {
|
|
||||||
HandlerList.unregisterAll(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldEnable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
|
||||||
private void onTradeScreenClose(InventoryCloseEvent event) {
|
|
||||||
if (
|
|
||||||
event.getInventory().getType() == InventoryType.MERCHANT
|
|
||||||
&& event.getInventory().getHolder() instanceof Villager
|
|
||||||
) {
|
|
||||||
final Villager villager = (Villager) event.getInventory().getHolder();
|
|
||||||
final WrappedVillager wVillager = wrapperCache.get(villager, WrappedVillager::new);
|
|
||||||
if (!wVillager.isOptimized()) return;
|
|
||||||
|
|
||||||
if (wVillager.canLevelUp(cooldown_millis)) {
|
|
||||||
if (wVillager.calculateLevel() <= villager.getVillagerLevel()) return;
|
|
||||||
|
|
||||||
scheduling.entitySpecificScheduler(villager).run(enableAI -> {
|
|
||||||
villager.addPotionEffect(SUPER_SLOWNESS);
|
|
||||||
villager.setAware(true);
|
|
||||||
scheduling.entitySpecificScheduler(villager).runDelayed(disableAI -> {
|
|
||||||
villager.setAware(false);
|
|
||||||
wVillager.saveLastLevelUp();
|
|
||||||
}, null, 100L);
|
|
||||||
}, null);
|
|
||||||
} else {
|
|
||||||
if (notify_player) {
|
|
||||||
Player player = (Player) event.getPlayer();
|
|
||||||
final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
|
|
||||||
.matchLiteral("%time%")
|
|
||||||
.replacement(Util.formatDuration(Duration.ofMillis(wVillager.getLevelCooldownMillis(cooldown_millis))))
|
|
||||||
.build();
|
|
||||||
VillagerOptimizer.getLang(player.locale()).villager_leveling_up
|
|
||||||
.forEach(line -> KyoriUtil.sendMessage(player, line.replaceText(timeLeft)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,85 +0,0 @@
|
|||||||
package me.xginko.villageroptimizer.modules.gameplay;
|
|
||||||
|
|
||||||
import com.cryptomorin.xseries.XEntityType;
|
|
||||||
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
|
||||||
import me.xginko.villageroptimizer.wrapper.WrappedVillager;
|
|
||||||
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.EntityDamageEvent;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class PreventOptimizedDamage extends VillagerOptimizerModule implements Listener {
|
|
||||||
|
|
||||||
private final Set<EntityDamageEvent.DamageCause> damage_causes_to_cancel;
|
|
||||||
private final boolean cancel_knockback;
|
|
||||||
|
|
||||||
public PreventOptimizedDamage() {
|
|
||||||
super("gameplay.prevent-damage-to-optimized");
|
|
||||||
config.master().addComment(configPath + ".enable",
|
|
||||||
"Configure what kind of damage you want to cancel for optimized villagers here.");
|
|
||||||
this.cancel_knockback = config.getBoolean(configPath + ".prevent-knockback-from-entity", true,
|
|
||||||
"Prevents optimized villagers from getting knocked back by an attacking entity");
|
|
||||||
this.damage_causes_to_cancel = config.getList(configPath + ".damage-causes-to-cancel",
|
|
||||||
Arrays.stream(EntityDamageEvent.DamageCause.values()).map(Enum::name).sorted().collect(Collectors.toList()),
|
|
||||||
"These are all current entries in the game. Remove what you do not need blocked.\n" +
|
|
||||||
"If you want a description or need to add a previously removed type, refer to:\n" +
|
|
||||||
"https://jd.papermc.io/paper/1.20/org/bukkit/event/entity/EntityDamageEvent.DamageCause.html")
|
|
||||||
.stream()
|
|
||||||
.map(configuredDamageCause -> {
|
|
||||||
try {
|
|
||||||
return EntityDamageEvent.DamageCause.valueOf(configuredDamageCause);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
warn("DamageCause '" + configuredDamageCause + "' not recognized. Please use correct DamageCause enums from: " +
|
|
||||||
"https://jd.papermc.io/paper/1.20/org/bukkit/event/entity/EntityDamageEvent.DamageCause.html");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.collect(Collectors.toCollection(() -> EnumSet.noneOf(EntityDamageEvent.DamageCause.class)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void enable() {
|
|
||||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void disable() {
|
|
||||||
HandlerList.unregisterAll(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldEnable() {
|
|
||||||
return config.getBoolean(configPath + ".enable", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
|
||||||
private void onDamageByEntity(EntityDamageEvent event) {
|
|
||||||
if (
|
|
||||||
event.getEntityType() == XEntityType.VILLAGER.get()
|
|
||||||
&& damage_causes_to_cancel.contains(event.getCause())
|
|
||||||
&& wrapperCache.get((Villager) event.getEntity(), WrappedVillager::new).isOptimized()
|
|
||||||
) {
|
|
||||||
event.setCancelled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
|
||||||
private void onKnockbackByEntity(com.destroystokyo.paper.event.entity.EntityKnockbackByEntityEvent event) {
|
|
||||||
if (
|
|
||||||
cancel_knockback
|
|
||||||
&& event.getEntityType() == XEntityType.VILLAGER.get()
|
|
||||||
&& wrapperCache.get((Villager) event.getEntity(), WrappedVillager::new).isOptimized()
|
|
||||||
) {
|
|
||||||
event.setCancelled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
package me.xginko.villageroptimizer.modules.gameplay;
|
|
||||||
|
|
||||||
import me.xginko.villageroptimizer.VillagerOptimizer;
|
|
||||||
import me.xginko.villageroptimizer.struct.enums.Permissions;
|
|
||||||
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
|
||||||
import me.xginko.villageroptimizer.utils.KyoriUtil;
|
|
||||||
import me.xginko.villageroptimizer.wrapper.WrappedVillager;
|
|
||||||
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 extends VillagerOptimizerModule implements Listener {
|
|
||||||
|
|
||||||
private final boolean notify_player;
|
|
||||||
|
|
||||||
public PreventUnoptimizedTrading() {
|
|
||||||
super("gameplay.prevent-trading-with-unoptimized");
|
|
||||||
config.master().addComment(configPath + ".enable",
|
|
||||||
"Will prevent players from selecting and using trades of unoptimized villagers.\n" +
|
|
||||||
"Use this if you have a lot of villagers and therefore want to force your players to optimize them.\n" +
|
|
||||||
"Inventories can still be opened so players can move villagers around.");
|
|
||||||
this.notify_player = config.getBoolean(configPath + ".notify-player", true,
|
|
||||||
"Sends players a message when they try to trade with an unoptimized villager.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void enable() {
|
|
||||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void disable() {
|
|
||||||
HandlerList.unregisterAll(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldEnable() {
|
|
||||||
return config.getBoolean(configPath + ".enable", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
|
||||||
private void onTradeOpen(TradeSelectEvent event) {
|
|
||||||
if (event.getInventory().getType() != InventoryType.MERCHANT) return;
|
|
||||||
if (event.getWhoClicked().hasPermission(Permissions.Bypass.TRADE_PREVENTION.get())) return;
|
|
||||||
if (!(event.getInventory().getHolder() instanceof Villager)) return;
|
|
||||||
if (wrapperCache.get((Villager) event.getInventory().getHolder(), WrappedVillager::new).isOptimized()) return;
|
|
||||||
|
|
||||||
event.setCancelled(true);
|
|
||||||
|
|
||||||
if (notify_player) {
|
|
||||||
Player player = (Player) event.getWhoClicked();
|
|
||||||
VillagerOptimizer.getLang(player.locale()).optimize_for_trading.forEach(line -> KyoriUtil.sendMessage(player, line));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
|
||||||
private void onInventoryClick(InventoryClickEvent event) {
|
|
||||||
if (event.getInventory().getType() != InventoryType.MERCHANT) return;
|
|
||||||
if (event.getWhoClicked().hasPermission(Permissions.Bypass.TRADE_PREVENTION.get())) return;
|
|
||||||
if (!(event.getInventory().getHolder() instanceof Villager)) return;
|
|
||||||
if (wrapperCache.get((Villager) event.getInventory().getHolder(), WrappedVillager::new).isOptimized()) return;
|
|
||||||
|
|
||||||
event.setCancelled(true);
|
|
||||||
|
|
||||||
if (notify_player) {
|
|
||||||
Player player = (Player) event.getWhoClicked();
|
|
||||||
VillagerOptimizer.getLang(player.locale()).optimize_for_trading.forEach(line -> KyoriUtil.sendMessage(player, line));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,110 +0,0 @@
|
|||||||
package me.xginko.villageroptimizer.modules.gameplay;
|
|
||||||
|
|
||||||
import com.cryptomorin.xseries.XEntityType;
|
|
||||||
import me.xginko.villageroptimizer.VillagerOptimizer;
|
|
||||||
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
|
||||||
import me.xginko.villageroptimizer.struct.enums.Permissions;
|
|
||||||
import me.xginko.villageroptimizer.utils.KyoriUtil;
|
|
||||||
import me.xginko.villageroptimizer.utils.LocationUtil;
|
|
||||||
import me.xginko.villageroptimizer.utils.Util;
|
|
||||||
import me.xginko.villageroptimizer.wrapper.WrappedVillager;
|
|
||||||
import net.kyori.adventure.text.TextReplacementConfig;
|
|
||||||
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 java.time.Duration;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.SortedSet;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
public class RestockOptimizedTrades extends VillagerOptimizerModule implements Listener {
|
|
||||||
|
|
||||||
private final SortedSet<Long> restockDayTimes;
|
|
||||||
private final boolean log_enabled, notify_player;
|
|
||||||
|
|
||||||
public RestockOptimizedTrades() {
|
|
||||||
super("gameplay.restock-optimized-trades");
|
|
||||||
config.master().addComment(configPath,
|
|
||||||
"This is for automatic restocking of trades for optimized villagers. Optimized Villagers\n" +
|
|
||||||
"don't have enough AI to restock their trades naturally, so this is here as a workaround.");
|
|
||||||
this.restockDayTimes = new TreeSet<>(Comparator.reverseOrder());
|
|
||||||
this.restockDayTimes.addAll(config.getList(configPath + ".restock-times", Arrays.asList(1000L, 13000L),
|
|
||||||
"At which (tick-)times during the day villagers will restock.\n" +
|
|
||||||
"There are 24.000 ticks in a single minecraft day."));
|
|
||||||
this.notify_player = config.getBoolean(configPath + ".notify-player", true,
|
|
||||||
"Sends the player a message when the trades were restocked on a clicked villager.");
|
|
||||||
this.log_enabled = config.getBoolean(configPath + ".log", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void enable() {
|
|
||||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void disable() {
|
|
||||||
HandlerList.unregisterAll(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldEnable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
|
||||||
private void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
|
|
||||||
if (event.getRightClicked().getType() != XEntityType.VILLAGER.get()) return;
|
|
||||||
|
|
||||||
WrappedVillager wrapped = wrapperCache.get((Villager) event.getRightClicked(), WrappedVillager::new);
|
|
||||||
if (!wrapped.isOptimized()) return;
|
|
||||||
|
|
||||||
if (event.getPlayer().hasPermission(Permissions.Bypass.RESTOCK_COOLDOWN.get())) {
|
|
||||||
wrapped.restock();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
long lastRestockFullTimeTicks = wrapped.getLastRestockFullTime();
|
|
||||||
long currentFullTimeTicks = wrapped.currentFullTimeTicks();
|
|
||||||
long currentDayTimeTicks = wrapped.currentDayTimeTicks();
|
|
||||||
|
|
||||||
long currentDay = currentFullTimeTicks - currentDayTimeTicks;
|
|
||||||
long ticksTillRestock = (24000 + currentDay + restockDayTimes.first()) - currentFullTimeTicks;
|
|
||||||
|
|
||||||
boolean restocked = false;
|
|
||||||
|
|
||||||
for (Long restockDayTime : restockDayTimes) {
|
|
||||||
long restockTimeToday = currentDay + restockDayTime;
|
|
||||||
|
|
||||||
if (currentFullTimeTicks < restockTimeToday || lastRestockFullTimeTicks >= restockTimeToday) {
|
|
||||||
ticksTillRestock = Math.min(ticksTillRestock, restockTimeToday - currentFullTimeTicks);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!restocked) {
|
|
||||||
wrapped.restock();
|
|
||||||
wrapped.saveRestockTime();
|
|
||||||
restocked = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!restocked) return;
|
|
||||||
|
|
||||||
if (notify_player) {
|
|
||||||
final TextReplacementConfig timeLeft = TextReplacementConfig.builder()
|
|
||||||
.matchLiteral("%time%")
|
|
||||||
.replacement(Util.formatDuration(Duration.ofMillis(ticksTillRestock * 50L)))
|
|
||||||
.build();
|
|
||||||
VillagerOptimizer.getLang(event.getPlayer().locale()).trades_restocked
|
|
||||||
.forEach(line -> KyoriUtil.sendMessage(event.getPlayer(), line.replaceText(timeLeft)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (log_enabled) {
|
|
||||||
info("Restocked optimized villager at " + LocationUtil.toString(wrapped.villager.getLocation()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user