a days progress

This commit is contained in:
xGinko 2023-09-10 00:44:03 +02:00
parent 8b82d2994d
commit 5d106640a3
13 changed files with 256 additions and 135 deletions

View File

@ -9,8 +9,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.time.Duration;
import java.util.Collection;
import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
public class VillagerManager {
@ -20,8 +20,8 @@ public class VillagerManager {
this.villagerCache = Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(expireAfterWriteSeconds)).build();
}
public @NotNull Collection<WrappedVillager> getAll() {
return this.villagerCache.asMap().values();
public @NotNull ConcurrentMap<UUID, WrappedVillager> cacheMap() {
return this.villagerCache.asMap();
}
public @Nullable WrappedVillager get(@NotNull UUID uuid) {

View File

@ -3,11 +3,8 @@ package me.xginko.villageroptimizer.config;
import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.utils.LogUtils;
import org.bukkit.Material;
import java.io.File;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@ -15,66 +12,21 @@ import java.util.Map;
public class Config {
private final ConfigFile config;
public final Locale default_lang;
public final boolean auto_lang, enable_nametag_optimization, enable_workstation_optimization, enable_block_optimization;
public final long cache_keep_time_seconds, optimize_cooldown_millis;
public final HashSet<String> nametags = new HashSet<>(4);
public final HashSet<Material> blocks_that_disable = new HashSet<>(4);
public final HashSet<Material> workstations_that_disable = new HashSet<>(14);
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();
/**
* General
*/
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.");
/**
* Optimization
*/
this.optimize_cooldown_millis = getInt("optimization.state-change-cooldown-in-seconds", 600) * 1000L;
// Nametags
this.enable_nametag_optimization = getBoolean("optimization.methods.by-nametag.enable", true);
this.nametags.addAll(getList("optimization.methods.by-nametag.names", List.of("Optimize", "DisableAI"), "Names are case insensitive")
.stream().map(String::toLowerCase).toList());
// Workstations
this.enable_workstation_optimization = getBoolean("optimization.methods.by-workstation.enable", true, """
Optimize villagers that are standing near their acquired workstations /s
Values here need to be valid bukkit Material enums for your server version.
""");
this.getList("optimization.methods.by-workstation.workstation-materials", List.of(
"COMPOSTER", "SMOKER", "BARREL", "LOOM", "BLAST_FURNACE", "BREWING_STAND", "CAULDRON",
"FLETCHING_TABLE", "CARTOGRAPHY_TABLE", "LECTERN", "SMITHING_TABLE", "STONECUTTER", "GRINDSTONE"
)).forEach(configuredMaterial -> {
try {
Material disableBlock = Material.valueOf(configuredMaterial);
this.blocks_that_disable.add(disableBlock);
} catch (IllegalArgumentException e) {
LogUtils.materialNotRecognized("optimization.methods.by-workstation", configuredMaterial);
}
});
// Blocks
this.enable_block_optimization = getBoolean("optimization.methods.by-specific-block.enable", true, """
Optimize villagers that are standing on these specific block materials /s
Values here need to be valid bukkit Material enums for your server version.
""");
this.getList("optimization.methods.by-specific-block.materials", List.of(
"LAPIS_BLOCK", "GLOWSTONE", "IRON_BLOCK"
)).forEach(configuredMaterial -> {
try {
Material disableBlock = Material.valueOf(configuredMaterial);
this.blocks_that_disable.add(disableBlock);
} catch (IllegalArgumentException e) {
LogUtils.materialNotRecognized("optimization.methods.by-specific-block", configuredMaterial);
}
});
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 {

View File

@ -17,23 +17,52 @@ public class LanguageCache {
public final 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,
trades_restocked;
command_optimize_success, command_radius_limit_exceed, command_optimize_fail, command_unoptimize_success,
trades_restocked, optimize_for_trading, villager_leveling_up;
public LanguageCache(String lang) throws Exception {
this.lang = loadLang(new File(VillagerOptimizer.getInstance().getDataFolder() + File.separator + "lang", lang + ".yml"));
this.miniMessage = MiniMessage.miniMessage();
this.no_permission = getTranslation("messages.no-permission", "<red>You don't have permission to use this command.");
this.trades_restocked = getListTranslation("messages.trade-restock.success", List.of("<green>All trades restocked!"));
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."));
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%."));
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%."));
// 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.workstation.optimize-success",
List.of("<green>Successfully optimized %amount% villager(s) in a radius of %radius% blocks."));
this.command_radius_limit_exceed = getListTranslation("messages.workstation.radius-limit-exceed",
List.of("<red>The radius you entered exceeds the limit of %distance% blocks."));
this.command_optimize_fail = getListTranslation("messages.workstation.optimize-fail",
List.of("<gray>%amount% villagers couldn't be optimized because they have recently been optimized."));
this.command_unoptimize_success = getListTranslation("messages.workstation.unoptimize-success",
List.of("<green>Successfully unoptimized %amount% villager(s) in a radius of %radius% blocks."));
saveLang();
}

View File

@ -5,7 +5,7 @@ import org.bukkit.NamespacedKey;
public enum Keys {
OPTIMIZATION(VillagerOptimizer.getKey("optimization")),
OPTIMIZATION_TYPE(VillagerOptimizer.getKey("optimization-type")),
LAST_OPTIMIZE(VillagerOptimizer.getKey("last-optimize")),
LAST_LEVELUP(VillagerOptimizer.getKey("last-levelup")),
LAST_RESTOCK(VillagerOptimizer.getKey("last-restock"));

View File

@ -28,7 +28,7 @@ public final class WrappedVillager {
* @return True if the villager is optimized by the plugin, otherwise false.
*/
public boolean isOptimized() {
return dataContainer.has(Keys.OPTIMIZATION.key());
return dataContainer.has(Keys.OPTIMIZATION_TYPE.key());
}
/**
@ -44,11 +44,11 @@ public final class WrappedVillager {
*/
public void setOptimization(OptimizationType type) {
if (type.equals(OptimizationType.OFF) && isOptimized()) {
dataContainer.remove(Keys.OPTIMIZATION.key());
dataContainer.remove(Keys.OPTIMIZATION_TYPE.key());
villager.setAware(true);
villager.setAI(true);
} else {
dataContainer.set(Keys.OPTIMIZATION.key(), PersistentDataType.STRING, type.name());
dataContainer.set(Keys.OPTIMIZATION_TYPE.key(), PersistentDataType.STRING, type.name());
villager.setAware(false);
}
}
@ -57,7 +57,7 @@ public final class WrappedVillager {
* @return The current OptimizationType of the villager.
*/
public @NotNull OptimizationType getOptimizationType() {
return isOptimized() ? OptimizationType.valueOf(dataContainer.get(Keys.OPTIMIZATION.key(), PersistentDataType.STRING)) : OptimizationType.OFF;
return isOptimized() ? OptimizationType.valueOf(dataContainer.get(Keys.OPTIMIZATION_TYPE.key(), PersistentDataType.STRING)) : OptimizationType.OFF;
}
/**
@ -75,6 +75,10 @@ public final class WrappedVillager {
}
/**
* 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.
*/
@ -83,6 +87,9 @@ public final class WrappedVillager {
}
/**
* 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.
*/
@ -140,6 +147,9 @@ public final class WrappedVillager {
}
/**
* 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() {

View File

@ -6,8 +6,10 @@ import me.xginko.villageroptimizer.enums.OptimizationType;
import me.xginko.villageroptimizer.cache.VillagerManager;
import me.xginko.villageroptimizer.models.WrappedVillager;
import me.xginko.villageroptimizer.utils.CommonUtils;
import me.xginko.villageroptimizer.utils.LogUtils;
import net.kyori.adventure.text.TextReplacementConfig;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Entity;
@ -22,30 +24,44 @@ import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import java.util.HashSet;
import java.util.List;
public class BlockOptimization implements VillagerOptimizerModule, Listener {
private final VillagerManager villagerManager;
private final Config config;
private final HashSet<Material> blocks_that_disable = new HashSet<>(4);
private final boolean shouldLog, shouldNotifyPlayer;
private final int maxVillagers;
private final long cooldown;
protected BlockOptimization() {
this.villagerManager = VillagerOptimizer.getVillagerManager();
this.config = VillagerOptimizer.getConfiguration();
this.config.addComment("optimization.methods.by-specific-block.enable", """
Config config = VillagerOptimizer.getConfiguration();
config.addComment("optimization.methods.by-specific-block.enable", """
When enabled, villagers standing on the configured specific blocks will become optimized once a\s
player interacts with them. If the block is broken or moved, the villager will become unoptimized\s
again once a player interacts with the villager afterwards.
""");
config.getList("optimization.methods.by-specific-block.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) {
LogUtils.materialNotRecognized("optimization.methods.by-specific-block", configuredMaterial);
}
});
this.cooldown = config.getInt("optimization.methods.by-specific-block.optimize-cooldown-seconds", 600, """
Cooldown in seconds until a villager can be optimized again by using this method. \s
Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.
""") * 1000L;
this.maxVillagers = config.getInt("optimization.methods.by-specific-block.max-villagers-per-block", 3,
"How many villagers can be optimized at once by placing a block under them.");
this.shouldLog = config.getBoolean("optimization.methods.by-specific-block.log", false);
this.shouldNotifyPlayer = config.getBoolean("optimization.methods.by-specific-block.notify-player", true);
this.shouldLog = config.getBoolean("optimization.methods.by-specific-block.log", false);
}
@Override
@ -61,13 +77,13 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener {
@Override
public boolean shouldEnable() {
return config.enable_block_optimization;
return VillagerOptimizer.getConfiguration().getBoolean("optimization.methods.by-specific-block.enable", true);
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
private void onBlockPlace(BlockPlaceEvent event) {
Block placed = event.getBlock();
if (!config.blocks_that_disable.contains(placed.getType())) return;
if (!blocks_that_disable.contains(placed.getType())) return;
int counter = 0;
for (Entity entity : placed.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,1,0.5)) {
@ -91,6 +107,7 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener {
if (shouldLog)
VillagerOptimizer.getLog().info("Villager was optimized by block at "+wVillager.villager().getLocation());
} else {
wVillager.villager().shakeHead();
if (shouldNotifyPlayer) {
Player player = event.getPlayer();
final long optimizeCoolDown = wVillager.getOptimizeCooldownMillis(cooldown);
@ -104,7 +121,7 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener {
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
private void onBlockBreak(BlockBreakEvent event) {
Block broken = event.getBlock();
if (!config.blocks_that_disable.contains(broken.getType())) return;
if (!blocks_that_disable.contains(broken.getType())) return;
int counter = 0;
for (Entity entity : broken.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,1,0.5)) {
@ -139,8 +156,8 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener {
final Location entityLegs = interacted.getLocation();
if (
config.blocks_that_disable.contains(entityLegs.getBlock().getType()) // check for blocks inside the entity's legs because of slabs and sink-in blocks
|| config.blocks_that_disable.contains(entityLegs.clone().subtract(0,1,0).getBlock().getType())
blocks_that_disable.contains(entityLegs.getBlock().getType()) // check for blocks inside the entity's legs because of slabs and sink-in blocks
|| blocks_that_disable.contains(entityLegs.clone().subtract(0,1,0).getBlock().getType())
) {
if (wVillager.isOptimized()) return;
if (wVillager.canOptimize(cooldown)) {
@ -158,6 +175,7 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener {
if (shouldLog)
VillagerOptimizer.getLog().info("Villager was optimized by block at "+wVillager.villager().getLocation());
} else {
wVillager.villager().shakeHead();
if (shouldNotifyPlayer) {
Player player = event.getPlayer();
final long optimizeCoolDown = wVillager.getOptimizeCooldownMillis(cooldown);

View File

@ -2,22 +2,34 @@ package me.xginko.villageroptimizer.modules;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.cache.VillagerManager;
import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.models.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.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryType;
public class LevelVillagers implements VillagerOptimizerModule, Listener {
private final VillagerOptimizer plugin;
private final VillagerManager villagerManager;
private final long cooldown;
public LevelVillagers() {
this.plugin = VillagerOptimizer.getInstance();
this.villagerManager = VillagerOptimizer.getVillagerManager();
Config config = VillagerOptimizer.getConfiguration();
config.addComment("optimization.villager-leveling.enable", """
This is needed to allow optimized villagers to level up. s\
Temporarily enables the villagers AI to allow it to level up and then disables it again.
""");
this.cooldown = config.getInt("optimization.villager-leveling.level-check-cooldown-seconds", 5, """
Cooldown in seconds the level of a villager will be checked and updated again. \s
Recommended to leave as is.
""") * 1000L;
}
@Override
@ -33,13 +45,19 @@ public class LevelVillagers implements VillagerOptimizerModule, Listener {
@Override
public boolean shouldEnable() {
return false;
return VillagerOptimizer.getConfiguration().getBoolean("optimization.villager-leveling.enable", true);
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
private void onTradeInventoryClose(InventoryCloseEvent event) {
if (event.getInventory().getHolder() instanceof Villager villager) {
private void onTradeScreenClose(InventoryCloseEvent event) {
if (
event.getInventory().getType().equals(InventoryType.MERCHANT)
&& event.getInventory().getHolder() instanceof Villager villager
) {
WrappedVillager wVillager = villagerManager.getOrAdd(villager);
if (!wVillager.isOptimized()) return;
// logic missing
}
}
}

View File

@ -20,28 +20,33 @@ import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack;
import java.util.HashSet;
import java.util.List;
public class NametagOptimization implements VillagerOptimizerModule, Listener {
private final VillagerManager villagerManager;
private final Config config;
private final HashSet<String> nametags = new HashSet<>(4);
private final boolean shouldLog, shouldNotifyPlayer, consumeNametag;
private final long cooldown;
protected NametagOptimization() {
this.villagerManager = VillagerOptimizer.getVillagerManager();
this.config = VillagerOptimizer.getConfiguration();
this.config.addComment("optimization.methods.by-nametag.enable", """
Config config = VillagerOptimizer.getConfiguration();
config.addComment("optimization.methods.by-nametag.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.by-nametag.names", List.of("Optimize", "DisableAI"),
"Names are case insensitive, capital letters won't matter.").stream().map(String::toLowerCase).toList());
this.consumeNametag = config.getBoolean("optimization.methods.by-nametag.nametags-get-consumed", true,
"Enable or disable consumption of the used nametag item.");
this.cooldown = config.getInt("optimization.methods.by-workstation.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.shouldLog = config.getBoolean("optimization.methods.by-nametag.log", false);
this.shouldNotifyPlayer = config.getBoolean("optimization.methods.by-nametag.notify-player", true);
this.shouldLog = config.getBoolean("optimization.methods.by-nametag.log", false);
}
@Override
@ -57,7 +62,7 @@ public class NametagOptimization implements VillagerOptimizerModule, Listener {
@Override
public boolean shouldEnable() {
return config.enable_nametag_optimization;
return VillagerOptimizer.getConfiguration().getBoolean("optimization.methods.by-nametag.enable", true);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
@ -70,7 +75,7 @@ public class NametagOptimization implements VillagerOptimizerModule, Listener {
WrappedVillager wVillager = villagerManager.getOrAdd((Villager) event.getEntity());
Player player = event.getPlayer();
if (config.nametags.contains(nameTag.toLowerCase())) {
if (nametags.contains(nameTag.toLowerCase())) {
if (wVillager.isOptimized()) return;
if (wVillager.canOptimize(cooldown)) {
wVillager.setOptimization(OptimizationType.NAMETAG);
@ -87,6 +92,7 @@ public class NametagOptimization implements VillagerOptimizerModule, Listener {
VillagerOptimizer.getLog().info(player.getName() + " optimized a villager using nametag: '" + nameTag + "'");
} else {
event.setCancelled(true);
wVillager.villager().shakeHead();
if (shouldNotifyPlayer) {
final long optimizeCoolDown = wVillager.getOptimizeCooldownMillis(cooldown);
VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> player.sendMessage(line

View File

@ -0,0 +1,60 @@
package me.xginko.villageroptimizer.modules;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.cache.VillagerManager;
import me.xginko.villageroptimizer.config.Config;
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.InventoryType;
import org.bukkit.event.inventory.TradeSelectEvent;
public class PreventUnoptimizedTrading implements VillagerOptimizerModule, Listener {
private final VillagerManager villagerManager;
private final boolean notifyPlayer;
protected PreventUnoptimizedTrading() {
this.villagerManager = VillagerOptimizer.getVillagerManager();
Config config = VillagerOptimizer.getConfiguration();
config.addComment("optimization.prevent-trading-with-unoptimized-villagers.enable", """
Will prevent players from selecting and using trades of unoptimized villagers. s\
Use this if you have a lot of villagers and therefore want to force your players to optimize them. s\
Inventories can still be opened so players can move villagers around.
""");
this.notifyPlayer = config.getBoolean("optimization.prevent-trading-with-unoptimized-villagers.notify-player", true);
}
@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.prevent-trading-with-unoptimized-villagers.enable", false);
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
private void onTradeOpen(TradeSelectEvent event) {
if (
event.getInventory().getType().equals(InventoryType.MERCHANT)
&& event.getInventory().getHolder() instanceof Villager villager
&& !villagerManager.getOrAdd(villager).isOptimized()
) {
event.setCancelled(true);
if (!notifyPlayer) return;
Player player = (Player) event.getWhoClicked();
VillagerOptimizer.getLang(player.locale()).optimize_for_trading.forEach(player::sendMessage);
}
}
}

View File

@ -18,7 +18,7 @@ import org.bukkit.event.player.PlayerInteractEntityEvent;
public class RestockTrades implements VillagerOptimizerModule, Listener {
private final VillagerManager villagerManager;
private final long restock_delay;
private final long restock_delay_millis;
private final boolean shouldLog, notifyPlayer;
protected RestockTrades() {
@ -28,7 +28,8 @@ public class RestockTrades implements VillagerOptimizerModule, Listener {
This is for automatic restocking of trades for optimized villagers. Optimized Villagers\s
Don't have enough AI to do trade restocks themselves, so this needs to always be enabled.
""");
this.restock_delay = config.getInt("optimization.trade-restocking.delay-in-ticks", 1200) * 50L;
this.restock_delay_millis = config.getInt("optimization.trade-restocking.delay-in-ticks", 1000,
"1 second = 20 ticks. There are 24.000 ticks in a single minecraft day.") * 50L;
this.shouldLog = config.getBoolean("optimization.trade-restocking.log", false);
this.notifyPlayer = config.getBoolean("optimization.trade-restocking.notify-player", true,
"Sends the player a message when the trades were restocked on a clicked villager.");
@ -53,16 +54,16 @@ public class RestockTrades implements VillagerOptimizerModule, Listener {
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
private void onInteract(PlayerInteractEntityEvent event) {
if (!event.getRightClicked().getType().equals(EntityType.VILLAGER)) return;
WrappedVillager wVillager = villagerManager.getOrAdd((Villager) event.getRightClicked());
if (!wVillager.isOptimized()) return;
if (wVillager.canRestock(restock_delay)) {
WrappedVillager wVillager = villagerManager.getOrAdd((Villager) event.getRightClicked());
if (wVillager.isOptimized() && wVillager.canRestock(restock_delay_millis)) {
wVillager.restock();
wVillager.saveRestockTime();
if (notifyPlayer) {
Player player = event.getPlayer();
VillagerOptimizer.getLang(player.locale()).trades_restocked.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(restock_delay)).build()))
.replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(restock_delay_millis)).build()))
);
}
if (shouldLog)

View File

@ -14,17 +14,18 @@ public interface VillagerOptimizerModule {
modules.forEach(VillagerOptimizerModule::disable);
modules.clear();
modules.add(new VillagerChunkLimit());
modules.add(new NametagOptimization());
modules.add(new BlockOptimization());
modules.add(new WorkstationOptimization());
modules.add(new LevelVillagers());
modules.add(new NametagOptimization());
modules.add(new PreventUnoptimizedTrading());
modules.add(new PreventVillagerDamage());
modules.add(new PreventVillagerTargetting());
modules.add(new RestockTrades());
modules.add(new LevelVillagers());
modules.add(new VillagerChunkLimit());
modules.add(new WorkstationOptimization());
for (VillagerOptimizerModule module : modules) {
modules.forEach(module -> {
if (module.shouldEnable()) module.enable();
}
});
}
}

View File

@ -6,8 +6,10 @@ import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.enums.OptimizationType;
import me.xginko.villageroptimizer.models.WrappedVillager;
import me.xginko.villageroptimizer.utils.CommonUtils;
import me.xginko.villageroptimizer.utils.LogUtils;
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;
@ -20,21 +22,36 @@ 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 WorkstationOptimization implements VillagerOptimizerModule, Listener {
private final VillagerManager villagerManager;
private final Config config;
private final HashSet<Material> workstations_that_disable = new HashSet<>(14);
private final boolean shouldLog, shouldNotifyPlayer;
private final long cooldown;
private final double search_radius;
protected WorkstationOptimization() {
this.villagerManager = VillagerOptimizer.getVillagerManager();
this.config = VillagerOptimizer.getConfiguration();
this.config.addComment("optimization.methods.by-workstation.enable", """
When enabled, villagers near a configured radius to a workstation specific to your config\s
will be optimized.
""");
Config config = VillagerOptimizer.getConfiguration();
config.addComment("optimization.methods.by-workstation.enable", """
When enabled, villagers near a configured radius to a workstation specific to your config\s
will be optimized.
""");
config.getList("optimization.methods.by-workstation.workstation-materials", List.of(
"COMPOSTER", "SMOKER", "BARREL", "LOOM", "BLAST_FURNACE", "BREWING_STAND", "CAULDRON",
"FLETCHING_TABLE", "CARTOGRAPHY_TABLE", "LECTERN", "SMITHING_TABLE", "STONECUTTER", "GRINDSTONE"
), "Values here need to be valid bukkit Material enums for your server version."
).forEach(configuredMaterial -> {
try {
Material disableBlock = Material.valueOf(configuredMaterial);
this.workstations_that_disable.add(disableBlock);
} catch (IllegalArgumentException e) {
LogUtils.materialNotRecognized("optimization.methods.by-workstation", configuredMaterial);
}
});
this.search_radius = config.getDouble("optimization.methods.by-workstation.search-radius-in-blocks", 4.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.
@ -43,8 +60,8 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene
Cooldown in seconds until a villager can be optimized again using this method. \s
Here for configuration freedom. Recommended to leave as is to not enable any exploitable behavior.
""") * 1000L;
this.shouldLog = config.getBoolean("optimization.methods.by-workstation.log", false);
this.shouldNotifyPlayer = config.getBoolean("optimization.methods.by-workstation.notify-player", true);
this.shouldLog = config.getBoolean("optimization.methods.by-workstation.log", false);
}
@Override
@ -60,16 +77,16 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene
@Override
public boolean shouldEnable() {
return config.enable_workstation_optimization;
return VillagerOptimizer.getConfiguration().getBoolean("optimization.methods.by-workstation.enable", true);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onBlockPlace(BlockPlaceEvent event) {
Block placed = event.getBlock();
if (!config.workstations_that_disable.contains(placed.getType())) return;
if (!workstations_that_disable.contains(placed.getType())) return;
final Location workstationLoc = placed.getLocation();
WrappedVillager closest = null;
WrappedVillager closestOptimizableVillager = null;
double closestDistance = Double.MAX_VALUE;
for (Entity entity : workstationLoc.getNearbyEntities(search_radius, search_radius, search_radius)) {
@ -80,18 +97,18 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene
WrappedVillager wVillager = villagerManager.getOrAdd(villager);
if (!wVillager.isOptimized() && entity.getLocation().distance(workstationLoc) < closestDistance) {
closest = wVillager;
closestOptimizableVillager = wVillager;
}
}
if (closest == null) return;
if (closestOptimizableVillager == null) return;
if (closest.canOptimize(cooldown)) {
closest.setOptimization(OptimizationType.WORKSTATION);
closest.saveOptimizeTime();
if (closestOptimizableVillager.canOptimize(cooldown)) {
closestOptimizableVillager.setOptimization(OptimizationType.WORKSTATION);
closestOptimizableVillager.saveOptimizeTime();
if (shouldNotifyPlayer) {
Player player = event.getPlayer();
final String vilType = closest.villager().getProfession().toString().toLowerCase();
final String vilType = closestOptimizableVillager.villager().getProfession().toString().toLowerCase();
final String workstation = placed.getType().toString().toLowerCase();
VillagerOptimizer.getLang(player.locale()).workstation_unoptimize_success.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(vilType).build())
@ -101,9 +118,10 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene
if (shouldLog)
VillagerOptimizer.getLog().info(event.getPlayer().getName() + " optimized a villager using workstation: '" + placed.getType().toString().toLowerCase() + "'");
} else {
closestOptimizableVillager.villager().shakeHead();
if (shouldNotifyPlayer) {
Player player = event.getPlayer();
final long optimizeCoolDown = closest.getOptimizeCooldownMillis(cooldown);
final long optimizeCoolDown = closestOptimizableVillager.getOptimizeCooldownMillis(cooldown);
VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(optimizeCoolDown)).build())
));
@ -114,11 +132,11 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onBlockBreak(BlockBreakEvent event) {
Block placed = event.getBlock();
if (!config.workstations_that_disable.contains(placed.getType())) return;
if (!workstations_that_disable.contains(placed.getType())) return;
final Location workstationLoc = placed.getLocation();
WrappedVillager closest = null;
double closestDistance = Double.MAX_VALUE;
WrappedVillager closestOptimizedVillager = null;
double closestDistance = search_radius;
for (Entity entity : workstationLoc.getNearbyEntities(search_radius, search_radius, search_radius)) {
if (!entity.getType().equals(EntityType.VILLAGER)) continue;
@ -127,15 +145,18 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene
if (profession.equals(Villager.Profession.NONE) || profession.equals(Villager.Profession.NITWIT)) continue;
WrappedVillager wVillager = villagerManager.getOrAdd(villager);
if (wVillager.isOptimized() && entity.getLocation().distance(workstationLoc) < closestDistance) {
closest = wVillager;
final double distance = entity.getLocation().distance(workstationLoc);
if (wVillager.isOptimized() && distance < closestDistance) {
closestOptimizedVillager = wVillager;
closestDistance = distance;
}
}
if (closest != null && closest.getOptimizationType().equals(OptimizationType.WORKSTATION)) {
if (closestOptimizedVillager != null && closestOptimizedVillager.getOptimizationType().equals(OptimizationType.WORKSTATION)) {
if (shouldNotifyPlayer) {
Player player = event.getPlayer();
final String vilType = closest.villager().getProfession().toString().toLowerCase();
final String vilType = closestOptimizedVillager.villager().getProfession().toString().toLowerCase();
final String workstation = placed.getType().toString().toLowerCase();
VillagerOptimizer.getLang(player.locale()).workstation_unoptimize_success.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(vilType).build())

View File

@ -1,8 +1,11 @@
messages:
no-permission: "<red>You don't have permission to use this command."
trade-restock:
success:
- "<green>All trades restocked! Next restock in %time%"
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."
@ -27,7 +30,9 @@ messages:
command:
optimize-success:
- "<green>Successfully optimized %amount% villager(s) in a radius of %radius% blocks."
command-on-cooldown:
- "<gray>You need to wait %time% until you can use this command again."
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."