diff --git a/src/main/java/me/xginko/villageroptimizer/config/LanguageCache.java b/src/main/java/me/xginko/villageroptimizer/config/LanguageCache.java index 1206a30..98c373b 100644 --- a/src/main/java/me/xginko/villageroptimizer/config/LanguageCache.java +++ b/src/main/java/me/xginko/villageroptimizer/config/LanguageCache.java @@ -15,7 +15,7 @@ public class LanguageCache { public final Component no_permission; public final List nametag_optimize_success, nametag_on_optimize_cooldown, nametag_unoptimize_success, - block_optimization_success, block_on_optimize_cooldown, block_unoptimize_success, + block_optimize_success, block_on_optimize_cooldown, block_unoptimize_success, workstation_optimization_success, workstation_on_optimize_cooldown, workstation_unoptimize_success; public LanguageCache(String lang) throws Exception { @@ -26,7 +26,7 @@ public class LanguageCache { this.nametag_optimize_success = getListTranslation("messages.nametag.optimize-success", List.of("Successfully optimized villager by using a nametag.")); this.nametag_on_optimize_cooldown = getListTranslation("messages.nametag.optimize-on-cooldown", List.of("You need to wait %time% until you can optimize this villager again.")); this.nametag_unoptimize_success = getListTranslation("messages.nametag.unoptimize-success", List.of("Successfully unoptimized villager by using a nametag.")); - this.block_optimization_success = getListTranslation("messages.block.optimize-success", List.of("%villagertype% villager successfully optimized using block %blocktype%.")); + this.block_optimize_success = getListTranslation("messages.block.optimize-success", List.of("%villagertype% villager successfully optimized using block %blocktype%.")); this.block_on_optimize_cooldown = getListTranslation("messages.block.optimize-on-cooldown", List.of("You need to wait %time% until you can optimize this villager again.")); this.block_unoptimize_success = getListTranslation("messages.block.unoptimize-success", List.of("Successfully unoptimized villager by moving it off a %blocktype% block.")); this.workstation_optimization_success = getListTranslation("messages.workstation.optimize-success", List.of("%villagertype% villager successfully optimized using workstation block %blocktype%.")); diff --git a/src/main/java/me/xginko/villageroptimizer/models/WrappedVillager.java b/src/main/java/me/xginko/villageroptimizer/models/WrappedVillager.java index b40bc42..5720aea 100644 --- a/src/main/java/me/xginko/villageroptimizer/models/WrappedVillager.java +++ b/src/main/java/me/xginko/villageroptimizer/models/WrappedVillager.java @@ -15,14 +15,14 @@ public final class WrappedVillager { public WrappedVillager(@NotNull Villager villager) { this.villager = villager; - this.villagerData = villager.getPersistentDataContainer(); + this.villagerData = this.villager.getPersistentDataContainer(); } public @NotNull Villager villager() { return villager; } - public static @NotNull WrappedVillager fromVillager(Villager villager) { + public static @NotNull WrappedVillager fromCache(Villager villager) { return VillagerOptimizer.getVillagerCache().get(villager); } diff --git a/src/main/java/me/xginko/villageroptimizer/modules/AntiVillagerDamage.java b/src/main/java/me/xginko/villageroptimizer/modules/AntiVillagerDamage.java index a3cfc95..ba28331 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/AntiVillagerDamage.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/AntiVillagerDamage.java @@ -36,16 +36,16 @@ public class AntiVillagerDamage implements VillagerOptimizerModule, Listener { } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - private void onDamage(EntityDamageEvent event) { - if (!event.getEntity().getType().equals(EntityType.VILLAGER)) return; + private void onDamageReceive(EntityDamageEvent event) { + if (!event.getEntityType().equals(EntityType.VILLAGER)) return; if (cache.get((Villager) event.getEntity()).isOptimized()) { event.setCancelled(true); } } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - private void onPathfind(EntityPushedByEntityAttackEvent event) { - if (!event.getEntity().getType().equals(EntityType.VILLAGER)) return; + private void onPushByEntityAttack(EntityPushedByEntityAttackEvent event) { + if (!event.getEntityType().equals(EntityType.VILLAGER)) return; if (cache.get((Villager) event.getEntity()).isOptimized()) { event.setCancelled(true); } diff --git a/src/main/java/me/xginko/villageroptimizer/modules/AntiVillagerTargetting.java b/src/main/java/me/xginko/villageroptimizer/modules/AntiVillagerTargetting.java index a0c5f91..5510d9a 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/AntiVillagerTargetting.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/AntiVillagerTargetting.java @@ -3,11 +3,14 @@ package me.xginko.villageroptimizer.modules; import com.destroystokyo.paper.event.entity.EntityPathfindEvent; import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.models.VillagerCache; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Mob; import org.bukkit.entity.Villager; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityTargetLivingEntityEvent; public class AntiVillagerTargetting implements VillagerOptimizerModule, Listener { @@ -42,9 +45,20 @@ public class AntiVillagerTargetting implements VillagerOptimizerModule, Listener } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - private void onTarget(EntityPathfindEvent event) { + private void onEntityTargetVillager(EntityPathfindEvent event) { if (event.getTargetEntity() instanceof Villager villager && cache.get(villager).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 + && cache.get((Villager) event.getEntity()).isOptimized() + ) { + attacker.setTarget(null); + } + } + } diff --git a/src/main/java/me/xginko/villageroptimizer/modules/BlockOptimization.java b/src/main/java/me/xginko/villageroptimizer/modules/BlockOptimization.java index c0e76c4..aac358a 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/BlockOptimization.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/BlockOptimization.java @@ -1,29 +1,43 @@ package me.xginko.villageroptimizer.modules; -import io.papermc.paper.event.entity.EntityMoveEvent; import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.config.Config; import me.xginko.villageroptimizer.enums.OptimizationType; import me.xginko.villageroptimizer.models.VillagerCache; import me.xginko.villageroptimizer.models.WrappedVillager; +import me.xginko.villageroptimizer.utils.CommonUtils; +import net.kyori.adventure.text.TextReplacementConfig; import org.bukkit.Location; +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; public class BlockOptimization implements VillagerOptimizerModule, Listener { private final VillagerCache cache; private final Config config; - private final boolean shouldLog; + private final boolean shouldLog, shouldNotifyPlayer; protected BlockOptimization() { this.cache = VillagerOptimizer.getVillagerCache(); this.config = VillagerOptimizer.getConfiguration(); + this.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. + """); this.shouldLog = config.getBoolean("optimization.methods.by-specific-block.log", false); + this.shouldNotifyPlayer = config.getBoolean("optimization.methods.by-specific-block.notify-player", true); } @Override @@ -43,24 +57,91 @@ public class BlockOptimization implements VillagerOptimizerModule, Listener { } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - private void onEntityMove(EntityMoveEvent event) { - if (!event.getEntityType().equals(EntityType.VILLAGER)) return; + private void onBlockPlace(BlockPlaceEvent event) { + Block placed = event.getBlock(); + if (!config.blocks_that_disable.contains(placed.getType())) return; + + placed.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,0.5,0.5).forEach(entity -> { + if (entity.getType().equals(EntityType.VILLAGER)) { + WrappedVillager wVillager = cache.get((Villager) entity); + if (!wVillager.isOptimized()) { + if (wVillager.setOptimization(OptimizationType.BLOCK)) { + if (shouldNotifyPlayer) { + Player player = event.getPlayer(); + VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(player::sendMessage); + } + if (shouldLog) + VillagerOptimizer.getLog().info("Villager was optimized by block at "+wVillager.villager().getLocation()); + } else { + if (shouldNotifyPlayer) { + Player player = event.getPlayer(); + VillagerOptimizer.getLang(player.locale()).block_on_optimize_cooldown.forEach(line -> player.sendMessage(line + .replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(wVillager.getOptimizeCooldown())).build()))); + } + } + } + } + }); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + private void onBlockBreak(BlockBreakEvent event) { + Block broken = event.getBlock(); + if (!config.blocks_that_disable.contains(broken.getType())) return; + + broken.getRelative(BlockFace.UP).getLocation().getNearbyEntities(0.5,0.5,0.5).forEach(entity -> { + if (entity.getType().equals(EntityType.VILLAGER)) { + WrappedVillager wVillager = cache.get((Villager) entity); + if (wVillager.getOptimizationType().equals(OptimizationType.BLOCK)) { + wVillager.setOptimization(OptimizationType.OFF); + if (shouldNotifyPlayer) { + Player player = event.getPlayer(); + VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(player::sendMessage); + } + 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; + + WrappedVillager wVillager = cache.get((Villager) interacted); + final Location entityLegs = interacted.getLocation(); - final Location entityLegs = event.getEntity().getLocation(); if ( - config.blocks_that_disable.contains(entityLegs.getBlock().getType()) + config.blocks_that_disable.contains(entityLegs.getBlock().getType()) // for slabs and sink in blocks || config.blocks_that_disable.contains(entityLegs.clone().subtract(0,1,0).getBlock().getType()) ) { - WrappedVillager wVillager = cache.get((Villager) event.getEntity()); if (!wVillager.isOptimized()) { - wVillager.setOptimization(OptimizationType.BLOCK); - if (shouldLog) VillagerOptimizer.getLog().info("Villager moved onto an optimization block at "+wVillager.villager().getLocation()); + if (wVillager.setOptimization(OptimizationType.BLOCK)) { + if (shouldNotifyPlayer) { + Player player = event.getPlayer(); + VillagerOptimizer.getLang(player.locale()).block_optimize_success.forEach(player::sendMessage); + } + if (shouldLog) + VillagerOptimizer.getLog().info("Villager was optimized by block at "+wVillager.villager().getLocation()); + } else { + if (shouldNotifyPlayer) { + Player player = event.getPlayer(); + VillagerOptimizer.getLang(player.locale()).block_on_optimize_cooldown.forEach(line -> player.sendMessage(line + .replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(wVillager.getOptimizeCooldown())).build()))); + } + } } } else { - WrappedVillager wVillager = cache.get((Villager) event.getEntity()); - if (wVillager.isOptimized()) { + if (wVillager.getOptimizationType().equals(OptimizationType.BLOCK)) { wVillager.setOptimization(OptimizationType.OFF); - if (shouldLog) VillagerOptimizer.getLog().info("Villager moved away from an optimization block at "+wVillager.villager().getLocation()); + if (shouldNotifyPlayer) { + Player player = event.getPlayer(); + VillagerOptimizer.getLang(player.locale()).block_unoptimize_success.forEach(player::sendMessage); + } + if (shouldLog) + VillagerOptimizer.getLog().info("Villager unoptimized because no longer standing on optimization block at "+wVillager.villager().getLocation()); } } } diff --git a/src/main/java/me/xginko/villageroptimizer/modules/ChunkLimit.java b/src/main/java/me/xginko/villageroptimizer/modules/ChunkLimit.java index 90b09fa..4a4fc9c 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/ChunkLimit.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/ChunkLimit.java @@ -115,6 +115,6 @@ public class ChunkLimit implements VillagerOptimizerModule, Listener { private int getProfessionPriority(Villager villager) { Villager.Profession profession = villager.getProfession(); - return removalPriority.contains(profession) && !WrappedVillager.fromVillager(villager).isOptimized() ? removalPriority.indexOf(profession) : Integer.MAX_VALUE; + return removalPriority.contains(profession) && !WrappedVillager.fromCache(villager).isOptimized() ? removalPriority.indexOf(profession) : Integer.MAX_VALUE; } } diff --git a/src/main/java/me/xginko/villageroptimizer/modules/NametagOptimization.java b/src/main/java/me/xginko/villageroptimizer/modules/NametagOptimization.java index b40b555..cdd210c 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/NametagOptimization.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/NametagOptimization.java @@ -27,6 +27,12 @@ public class NametagOptimization implements VillagerOptimizerModule, Listener { protected NametagOptimization() { this.cache = VillagerOptimizer.getVillagerCache(); this.config = VillagerOptimizer.getConfiguration(); + this.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.shouldLog = config.getBoolean("optimization.methods.by-nametag.log", false); this.shouldNotifyPlayer = config.getBoolean("optimization.methods.by-nametag.notify-player", true); } @@ -48,7 +54,7 @@ public class NametagOptimization implements VillagerOptimizerModule, Listener { } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - private void onNametag(PlayerNameEntityEvent event) { + private void onPlayerNameEntity(PlayerNameEntityEvent event) { if (!event.getEntity().getType().equals(EntityType.VILLAGER)) return; Component name = event.getName(); if (name == null) return; @@ -68,14 +74,13 @@ public class NametagOptimization implements VillagerOptimizerModule, Listener { } else { if (shouldNotifyPlayer) { Player player = event.getPlayer(); - VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> - player.sendMessage(line.replaceText(TextReplacementConfig.builder().matchLiteral("%time").replacement(CommonUtils.formatTime(wVillager.getOptimizeCooldown())).build())) - ); + VillagerOptimizer.getLang(player.locale()).nametag_on_optimize_cooldown.forEach(line -> player.sendMessage(line + .replaceText(TextReplacementConfig.builder().matchLiteral("%time%").replacement(CommonUtils.formatTime(wVillager.getOptimizeCooldown())).build()))); } } } } else { - if (wVillager.isOptimized()) { + if (wVillager.getOptimizationType().equals(OptimizationType.NAMETAG)) { wVillager.setOptimization(OptimizationType.OFF); if (shouldNotifyPlayer) { Player player = event.getPlayer(); diff --git a/src/main/java/me/xginko/villageroptimizer/modules/RestockOptimized.java b/src/main/java/me/xginko/villageroptimizer/modules/RestockOptimized.java index fc2b45b..fd3216f 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/RestockOptimized.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/RestockOptimized.java @@ -2,6 +2,7 @@ package me.xginko.villageroptimizer.modules; import me.xginko.villageroptimizer.VillagerOptimizer; import me.xginko.villageroptimizer.config.Config; +import me.xginko.villageroptimizer.models.VillagerCache; import me.xginko.villageroptimizer.models.WrappedVillager; import org.bukkit.entity.EntityType; import org.bukkit.entity.Villager; @@ -13,12 +14,19 @@ import org.bukkit.event.player.PlayerInteractEntityEvent; public class RestockOptimized implements VillagerOptimizerModule, Listener { + private final VillagerCache cache; private final long restock_delay; private final boolean shouldLog; public RestockOptimized() { + this.cache = VillagerOptimizer.getVillagerCache(); Config config = VillagerOptimizer.getConfiguration(); - this.restock_delay = config.getInt("") + config.addComment("optimization.trade-restocking.enable", """ + This is for automatic restocking of trades for optimized villagers. Optimized Villagers\s + Don't have enough AI to do trade restocks themselves, so this needs to always be enabled. + """); + this.restock_delay = config.getInt("optimization.trade-restocking.delay-in-ticks", 1200); + this.shouldLog = config.getBoolean("optimization.trade-restocking.log", false); } @Override @@ -34,15 +42,15 @@ public class RestockOptimized implements VillagerOptimizerModule, Listener { @Override public boolean shouldEnable() { - return true; + return VillagerOptimizer.getConfiguration().getBoolean("optimization.trade-restocking.enable", true); } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) private void onInteract(PlayerInteractEntityEvent event) { if (!event.getRightClicked().getType().equals(EntityType.VILLAGER)) return; - WrappedVillager wrappedVillager = new WrappedVillager((Villager) event.getRightClicked()); - if (!wrappedVillager.isOptimized()) return; + WrappedVillager wVillager = cache.get((Villager) event.getRightClicked()); + if (!wVillager.isOptimized()) return; + - if (wrappedVillager.getSavedWorldTime() >) } } diff --git a/src/main/java/me/xginko/villageroptimizer/modules/WorkstationOptimization.java b/src/main/java/me/xginko/villageroptimizer/modules/WorkstationOptimization.java index f4d9898..4b39569 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/WorkstationOptimization.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/WorkstationOptimization.java @@ -14,12 +14,17 @@ public class WorkstationOptimization implements VillagerOptimizerModule, Listene private final VillagerCache cache; private final Config config; - private final boolean shouldLog; + private final boolean shouldLog, shouldNotifyPlayer; protected WorkstationOptimization() { this.cache = VillagerOptimizer.getVillagerCache(); this.config = VillagerOptimizer.getConfiguration(); + this.config.addComment("optimization.methods.by-workstation.enable", """ + When enabled, villagers near a configured radius to a workstation specific to their profession\s + will be optimized. + """); this.shouldLog = config.getBoolean("optimization.methods.by-workstation.log", false); + this.shouldNotifyPlayer = config.getBoolean("optimization.methods.by-workstation.notify-player", true); } @Override diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index e8b0226..6b5333d 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,4 +1,5 @@ name: VillagerOptimizer version: '${project.version}' main: me.xginko.villageroptimizer.VillagerOptimizer -api-version: '1.20' +api-version: '1.19' +folia-supported: true \ No newline at end of file