finish block optimization

This commit is contained in:
xGinko 2023-09-26 22:03:50 +02:00
parent e78d79ee41
commit 3888275dea
5 changed files with 193 additions and 223 deletions

View File

@ -1,216 +0,0 @@
package me.xginko.villageroptimizer.modules;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.enums.OptimizationType;
import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.enums.Permissions;
import me.xginko.villageroptimizer.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;
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 org.bukkit.event.player.PlayerInteractEntityEvent;
import java.util.HashSet;
import java.util.List;
public class BlockOptimization implements VillagerOptimizerModule, Listener {
/*
* TODO: Think of better logic than just checking under the villagers feet for block
* -> use workstation optimization logic
* */
private final VillagerCache villagerCache;
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() {
shouldEnable();
this.villagerCache = VillagerOptimizer.getCache();
Config config = VillagerOptimizer.getConfiguration();
config.addComment("optimization-methods.block-optimization.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.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) {
LogUtils.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.maxVillagers = config.getInt("optimization-methods.block-optimization.max-villagers-per-block", 3,
"How many villagers can be optimized at once by placing a block under them.");
this.shouldNotifyPlayer = config.getBoolean("optimization-methods.block-optimization.notify-player", true,
"Sends players a message when they successfully optimized a villager.");
this.shouldLog = config.getBoolean("optimization-methods.block-optimization.log", false);
}
@Override
public void enable() {
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
@Override
public void disable() {
HandlerList.unregisterAll(this);
}
@Override
public boolean shouldEnable() {
return VillagerOptimizer.getConfiguration().getBoolean("optimization-methods.block-optimization.enable", false);
}
@EventHandler(priority = EventPriority.NORMAL, 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;
int counter = 0;
for (Entity entity : placed.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,1,0.5)) {
if (counter >= maxVillagers) return;
if (!entity.getType().equals(EntityType.VILLAGER)) continue;
WrappedVillager wVillager = villagerCache.getOrAdd((Villager) entity);
final OptimizationType type = wVillager.getOptimizationType();
if (!type.equals(OptimizationType.NONE) && !type.equals(OptimizationType.COMMAND)) continue;
if (wVillager.canOptimize(cooldown) || player.hasPermission(Permissions.Bypass.BLOCK_COOLDOWN.get())) {
wVillager.setOptimization(OptimizationType.BLOCK);
wVillager.saveOptimizeTime();
counter++;
if (shouldNotifyPlayer) {
final String villagerType = wVillager.villager().getProfession().toString().toLowerCase();
final String placedType = placed.getType().toString().toLowerCase();
VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(villagerType).build())
.replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(placedType).build())
));
}
if (shouldLog)
VillagerOptimizer.getLog().info("Villager was optimized by block at "+wVillager.villager().getLocation());
} else {
wVillager.villager().shakeHead();
if (shouldNotifyPlayer) {
final String timeLeft = CommonUtils.formatTime(wVillager.getOptimizeCooldownMillis(cooldown));
VillagerOptimizer.getLang(player.locale()).block_on_optimize_cooldown.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(timeLeft).build())));
}
}
}
}
@EventHandler(priority = EventPriority.NORMAL, 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;
int counter = 0;
for (Entity entity : broken.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,1,0.5)) {
if (!entity.getType().equals(EntityType.VILLAGER)) continue;
WrappedVillager wVillager = villagerCache.getOrAdd((Villager) entity);
if (wVillager.getOptimizationType().equals(OptimizationType.BLOCK)) {
if (counter >= maxVillagers) return;
wVillager.setOptimization(OptimizationType.NONE);
if (shouldNotifyPlayer) {
final String villagerType = wVillager.villager().getProfession().toString().toLowerCase();
final String brokenType = broken.getType().toString().toLowerCase();
VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(villagerType).build())
.replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(brokenType).build())
));
}
if (shouldLog)
VillagerOptimizer.getLog().info("Villager unoptimized because no longer standing on optimization block at "+wVillager.villager().getLocation());
}
}
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
private void onPlayerInteract(PlayerInteractEntityEvent event) {
Entity interacted = event.getRightClicked();
if (!interacted.getType().equals(EntityType.VILLAGER)) return;
Player player = event.getPlayer();
if (!player.hasPermission(Permissions.Optimize.BLOCK.get())) return;
WrappedVillager wVillager = villagerCache.getOrAdd((Villager) interacted);
final Location entityLegs = interacted.getLocation();
if (
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) || player.hasPermission(Permissions.Bypass.BLOCK_COOLDOWN.get())) {
wVillager.setOptimization(OptimizationType.BLOCK);
wVillager.saveOptimizeTime();
if (shouldNotifyPlayer) {
final String vilType = wVillager.villager().getProfession().toString().toLowerCase();
final String blockType = entityLegs.getBlock().getType().toString().toLowerCase();
VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(vilType).build())
.replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(blockType).build())
));
}
if (shouldLog)
VillagerOptimizer.getLog().info("Villager was optimized by block at "+wVillager.villager().getLocation());
} else {
wVillager.villager().shakeHead();
if (shouldNotifyPlayer) {
final String timeLeft = CommonUtils.formatTime(wVillager.getOptimizeCooldownMillis(cooldown));
VillagerOptimizer.getLang(player.locale()).block_on_optimize_cooldown.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(timeLeft).build()))
);
}
}
} else {
if (wVillager.getOptimizationType().equals(OptimizationType.BLOCK)) {
wVillager.setOptimization(OptimizationType.NONE);
if (shouldNotifyPlayer) {
final String villagerType = wVillager.villager().getProfession().toString().toLowerCase();
final String blockType = entityLegs.getBlock().getType().toString().toLowerCase();
VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(villagerType).build())
.replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(blockType).build())
));
}
if (shouldLog)
VillagerOptimizer.getLog().info("Villager unoptimized because no longer standing on optimization block at "+wVillager.villager().getLocation());
}
}
}
}

View File

@ -1,5 +1,9 @@
package me.xginko.villageroptimizer.modules; package me.xginko.villageroptimizer.modules;
import me.xginko.villageroptimizer.modules.optimizations.BlockOptimization;
import me.xginko.villageroptimizer.modules.optimizations.NametagOptimization;
import me.xginko.villageroptimizer.modules.optimizations.WorkstationOptimization;
import java.util.HashSet; import java.util.HashSet;
public interface VillagerOptimizerModule { public interface VillagerOptimizerModule {
@ -14,16 +18,17 @@ public interface VillagerOptimizerModule {
modules.forEach(VillagerOptimizerModule::disable); modules.forEach(VillagerOptimizerModule::disable);
modules.clear(); modules.clear();
modules.add(new BlockOptimization());
modules.add(new LevelVillagers());
modules.add(new NametagOptimization()); modules.add(new NametagOptimization());
modules.add(new BlockOptimization());
modules.add(new WorkstationOptimization());
modules.add(new LevelVillagers());
modules.add(new PreventUnoptimizedTrading()); modules.add(new PreventUnoptimizedTrading());
modules.add(new PreventVillagerDamage()); modules.add(new PreventVillagerDamage());
modules.add(new PreventVillagerTargetting()); modules.add(new PreventVillagerTargetting());
modules.add(new RestockTrades()); modules.add(new RestockTrades());
modules.add(new VillagerChunkLimit()); modules.add(new VillagerChunkLimit());
modules.add(new VillagersSpawnAdult()); modules.add(new VillagersSpawnAdult());
modules.add(new WorkstationOptimization());
modules.forEach(module -> { modules.forEach(module -> {
if (module.shouldEnable()) module.enable(); if (module.shouldEnable()) module.enable();

View File

@ -0,0 +1,179 @@
package me.xginko.villageroptimizer.modules.optimizations;
import me.xginko.villageroptimizer.VillagerCache;
import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.WrappedVillager;
import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.enums.OptimizationType;
import me.xginko.villageroptimizer.enums.Permissions;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
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;
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 BlockOptimization implements VillagerOptimizerModule, Listener {
private final VillagerCache villagerCache;
private final HashSet<Material> blocks_that_disable = new HashSet<>(4);
private final boolean shouldLog, shouldNotifyPlayer;
private final long cooldown;
private final double search_radius;
public BlockOptimization() {
shouldEnable();
this.villagerCache = VillagerOptimizer.getCache();
Config config = VillagerOptimizer.getConfiguration();
config.addComment("optimization-methods.block-optimization.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.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) {
LogUtils.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.shouldNotifyPlayer = config.getBoolean("optimization-methods.block-optimization.notify-player", true,
"Sends players a message when they successfully optimized a villager.");
this.shouldLog = config.getBoolean("optimization-methods.block-optimization.log", false);
}
@Override
public void enable() {
VillagerOptimizer plugin = VillagerOptimizer.getInstance();
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
@Override
public void disable() {
HandlerList.unregisterAll(this);
}
@Override
public boolean shouldEnable() {
return VillagerOptimizer.getConfiguration().getBoolean("optimization-methods.block-optimization.enable", false);
}
@EventHandler(priority = EventPriority.NORMAL, 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;
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) {
final OptimizationType type = wVillager.getOptimizationType();
if (type.equals(OptimizationType.NONE) || type.equals(OptimizationType.COMMAND)) {
closestOptimizableVillager = wVillager;
closestDistance = distance;
}
}
}
if (closestOptimizableVillager == null) return;
if (closestOptimizableVillager.canOptimize(cooldown) || player.hasPermission(Permissions.Bypass.BLOCK_COOLDOWN.get())) {
closestOptimizableVillager.setOptimization(OptimizationType.BLOCK);
closestOptimizableVillager.saveOptimizeTime();
if (shouldNotifyPlayer) {
final String villagerType = closestOptimizableVillager.villager().getProfession().toString().toLowerCase();
final String placedType = placed.getType().toString().toLowerCase();
VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(villagerType).build())
.replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(placedType).build())
));
}
if (shouldLog)
VillagerOptimizer.getLog().info("Villager was optimized by block at "+closestOptimizableVillager.villager().getLocation());
} else {
closestOptimizableVillager.villager().shakeHead();
if (shouldNotifyPlayer) {
final String timeLeft = CommonUtils.formatTime(closestOptimizableVillager.getOptimizeCooldownMillis(cooldown));
VillagerOptimizer.getLang(player.locale()).block_on_optimize_cooldown.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(timeLeft).build())));
}
}
}
@EventHandler(priority = EventPriority.NORMAL, 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;
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) {
final OptimizationType type = wVillager.getOptimizationType();
if (type.equals(OptimizationType.WORKSTATION) || type.equals(OptimizationType.COMMAND)) {
closestOptimizedVillager = wVillager;
closestDistance = distance;
}
}
}
if (closestOptimizedVillager == null) return;
closestOptimizedVillager.setOptimization(OptimizationType.NONE);
if (shouldNotifyPlayer) {
final String villagerType = closestOptimizedVillager.villager().getProfession().toString().toLowerCase();
final String brokenType = broken.getType().toString().toLowerCase();
VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(line -> player.sendMessage(line
.replaceText(TextReplacementConfig.builder().matchLiteral("%villagertype%").replacement(villagerType).build())
.replaceText(TextReplacementConfig.builder().matchLiteral("%blocktype%").replacement(brokenType).build())
));
}
if (shouldLog)
VillagerOptimizer.getLog().info("Villager unoptimized because no longer standing on optimization block at "+closestOptimizedVillager.villager().getLocation());
}
}

View File

@ -1,4 +1,4 @@
package me.xginko.villageroptimizer.modules; package me.xginko.villageroptimizer.modules.optimizations;
import io.papermc.paper.event.player.PlayerNameEntityEvent; import io.papermc.paper.event.player.PlayerNameEntityEvent;
import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.VillagerOptimizer;
@ -7,6 +7,7 @@ import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.enums.OptimizationType; import me.xginko.villageroptimizer.enums.OptimizationType;
import me.xginko.villageroptimizer.enums.Permissions; import me.xginko.villageroptimizer.enums.Permissions;
import me.xginko.villageroptimizer.WrappedVillager; import me.xginko.villageroptimizer.WrappedVillager;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
import me.xginko.villageroptimizer.utils.CommonUtils; import me.xginko.villageroptimizer.utils.CommonUtils;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextReplacementConfig; import net.kyori.adventure.text.TextReplacementConfig;
@ -31,7 +32,7 @@ public class NametagOptimization implements VillagerOptimizerModule, Listener {
private final boolean shouldLog, shouldNotifyPlayer, consumeNametag; private final boolean shouldLog, shouldNotifyPlayer, consumeNametag;
private final long cooldown; private final long cooldown;
protected NametagOptimization() { public NametagOptimization() {
shouldEnable(); shouldEnable();
this.villagerCache = VillagerOptimizer.getCache(); this.villagerCache = VillagerOptimizer.getCache();
Config config = VillagerOptimizer.getConfiguration(); Config config = VillagerOptimizer.getConfiguration();

View File

@ -1,4 +1,4 @@
package me.xginko.villageroptimizer.modules; package me.xginko.villageroptimizer.modules.optimizations;
import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.VillagerOptimizer;
import me.xginko.villageroptimizer.VillagerCache; import me.xginko.villageroptimizer.VillagerCache;
@ -6,6 +6,7 @@ import me.xginko.villageroptimizer.config.Config;
import me.xginko.villageroptimizer.enums.OptimizationType; import me.xginko.villageroptimizer.enums.OptimizationType;
import me.xginko.villageroptimizer.enums.Permissions; import me.xginko.villageroptimizer.enums.Permissions;
import me.xginko.villageroptimizer.WrappedVillager; import me.xginko.villageroptimizer.WrappedVillager;
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
import me.xginko.villageroptimizer.utils.CommonUtils; import me.xginko.villageroptimizer.utils.CommonUtils;
import me.xginko.villageroptimizer.utils.LogUtils; import me.xginko.villageroptimizer.utils.LogUtils;
import net.kyori.adventure.text.TextReplacementConfig; import net.kyori.adventure.text.TextReplacementConfig;
@ -37,7 +38,7 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene
private final long cooldown; private final long cooldown;
private final double search_radius; private final double search_radius;
protected WorkstationOptimization() { public WorkstationOptimization() {
shouldEnable(); shouldEnable();
this.villagerCache = VillagerOptimizer.getCache(); this.villagerCache = VillagerOptimizer.getCache();
Config config = VillagerOptimizer.getConfiguration(); Config config = VillagerOptimizer.getConfiguration();