diff --git a/pom.xml b/pom.xml index c3385e2..88e04d9 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ me.xginko.VillagerOptimizer VillagerOptimizer - 1.3.0 + 1.4.0 jar VillagerOptimizer diff --git a/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java b/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java index 3e6689d..43a94cb 100644 --- a/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java +++ b/src/main/java/me/xginko/villageroptimizer/modules/VillagerOptimizerModule.java @@ -1,6 +1,5 @@ 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; @@ -31,6 +30,7 @@ public interface VillagerOptimizerModule { modules.add(new PreventUnoptimizedTrading()); modules.add(new PreventOptimizedTargeting()); modules.add(new PreventOptimizedDamage()); + modules.add(new EnableLeashingVillagers()); modules.add(new VillagerChunkLimit()); diff --git a/src/main/java/me/xginko/villageroptimizer/modules/gameplay/EnableLeashingVillagers.java b/src/main/java/me/xginko/villageroptimizer/modules/gameplay/EnableLeashingVillagers.java new file mode 100644 index 0000000..0d3b804 --- /dev/null +++ b/src/main/java/me/xginko/villageroptimizer/modules/gameplay/EnableLeashingVillagers.java @@ -0,0 +1,96 @@ +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.ExpiringSet; +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.entity.PlayerLeashEntityEvent; +import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.player.PlayerInteractEntityEvent; + +import java.time.Duration; +import java.util.UUID; + +public class EnableLeashingVillagers implements VillagerOptimizerModule, Listener { + + private final VillagerCache villagerCache; + private final ExpiringSet villagersGettingLeashed; + private final boolean only_optimized; + + public EnableLeashingVillagers() { + shouldEnable(); + this.villagerCache = VillagerOptimizer.getCache(); + this.villagersGettingLeashed = new ExpiringSet<>(Duration.ofSeconds(1)); + Config config = VillagerOptimizer.getConfiguration(); + config.master().addComment("gameplay.villagers-can-be-leashed.enable", """ + Enable leashing of villagers, enabling players to easily move villagers to where they want them to be."""); + this.only_optimized = config.getBoolean("gameplay.villagers-can-be-leashed.only-optimized", false, + "If set to true, only optimized villagers can be leashed."); + } + + @Override + public void enable() { + VillagerOptimizer plugin = VillagerOptimizer.getInstance(); + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + @Override + public void disable() { + HandlerList.unregisterAll(this); + } + + @Override + public boolean shouldEnable() { + return VillagerOptimizer.getConfiguration().getBoolean("gameplay.villagers-can-be-leashed.enable", false); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onLeash(PlayerInteractEntityEvent event) { + if (!event.getRightClicked().getType().equals(EntityType.VILLAGER)) return; + + Player player = event.getPlayer(); + if (!player.getInventory().getItem(event.getHand()).getType().equals(Material.LEAD)) return; + + Villager villager = (Villager) event.getRightClicked(); + if (villager.isLeashed()) return; + if (only_optimized && !villagerCache.getOrAdd(villager).isOptimized()) return; + + // Call event for compatibility with plugins listening to leashing, constructing non deprecated if available. + PlayerLeashEntityEvent leashEvent; + try { + leashEvent = new PlayerLeashEntityEvent(villager, player, player, event.getHand()); + } catch (Exception e) { + leashEvent = new PlayerLeashEntityEvent(villager, player, player); + } + + if (!leashEvent.callEvent()) return; + + VillagerOptimizer.getFoliaLib().getImpl().runAtEntity(villager, leash -> { + // Legitimate like this since values in PlayerLeashEntityEvent are final and can therefore never be changed by a plugin + if (villager.setLeashHolder(player)) { + villagersGettingLeashed.add(villager.getUniqueId()); + } + }); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + private void onInventoryOpen(InventoryOpenEvent event) { + if ( + event.getInventory().getType().equals(InventoryType.MERCHANT) + && event.getInventory().getHolder() instanceof Villager villager + && villagersGettingLeashed.contains(villager.getUniqueId()) + ) { + event.setCancelled(true); + } + } +} diff --git a/src/main/java/me/xginko/villageroptimizer/utils/ExpiringSet.java b/src/main/java/me/xginko/villageroptimizer/utils/ExpiringSet.java new file mode 100644 index 0000000..0848a7f --- /dev/null +++ b/src/main/java/me/xginko/villageroptimizer/utils/ExpiringSet.java @@ -0,0 +1,29 @@ +package me.xginko.villageroptimizer.utils; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +public final class ExpiringSet { + + private final Cache cache; + private static final Object PRESENT = new Object(); // Dummy value to associate with an Object in the backing Cache + + public ExpiringSet(long duration, TimeUnit unit) { + this.cache = Caffeine.newBuilder().expireAfterWrite(duration, unit).build(); + } + + public ExpiringSet(Duration duration) { + this.cache = Caffeine.newBuilder().expireAfterWrite(duration).build(); + } + + public void add(E item) { + this.cache.put(item, PRESENT); + } + + public boolean contains(E item) { + return this.cache.getIfPresent(item) != null; + } +} \ No newline at end of file