add experimental regional optimization
This commit is contained in:
parent
4019afe89b
commit
15fcd14e16
@ -20,6 +20,7 @@ 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;
|
||||||
@ -67,6 +68,10 @@ public class LanguageCache {
|
|||||||
"<gray>You need to wait %time% until you can optimize this villager again.");
|
"<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%.");
|
"<green>Successfully unoptimized %villagertype% villager by removing workstation block %blocktype%.");
|
||||||
|
// 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.");
|
"<green>Successfully optimized %amount% villager(s) in a radius of %radius% blocks.");
|
||||||
|
@ -0,0 +1,240 @@
|
|||||||
|
package me.xginko.villageroptimizer.modules.optimization;
|
||||||
|
|
||||||
|
import com.cryptomorin.xseries.XEntityType;
|
||||||
|
import com.destroystokyo.paper.event.entity.EntityPathfindEvent;
|
||||||
|
import com.github.benmanes.caffeine.cache.Cache;
|
||||||
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import me.xginko.villageroptimizer.modules.VillagerOptimizerModule;
|
||||||
|
import me.xginko.villageroptimizer.struct.enums.OptimizationType;
|
||||||
|
import me.xginko.villageroptimizer.struct.models.BlockRegion2D;
|
||||||
|
import me.xginko.villageroptimizer.wrapper.WrappedVillager;
|
||||||
|
import net.kyori.adventure.text.TextReplacementConfig;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
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.EntityInteractEvent;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class OptimizeByActivity extends VillagerOptimizerModule implements Listener {
|
||||||
|
|
||||||
|
protected static class RegionData {
|
||||||
|
|
||||||
|
public final BlockRegion2D region;
|
||||||
|
public final AtomicInteger pathfindCount, entityInteractCount;
|
||||||
|
public final AtomicBoolean regionBusy;
|
||||||
|
|
||||||
|
public RegionData(BlockRegion2D region) {
|
||||||
|
this.region = region;
|
||||||
|
this.pathfindCount = new AtomicInteger();
|
||||||
|
this.entityInteractCount = new AtomicInteger();
|
||||||
|
this.regionBusy = new AtomicBoolean(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Cache<BlockRegion2D, RegionData> regionDataCache;
|
||||||
|
private final double checkRadius;
|
||||||
|
private final int pathfindLimit, entityInteractLimit;
|
||||||
|
private final boolean notifyPlayers, doLogging;
|
||||||
|
|
||||||
|
public OptimizeByActivity() {
|
||||||
|
super("optimization-methods.regional-activity");
|
||||||
|
config.master().addComment(configPath + ".enable",
|
||||||
|
"Enable optimization by naming villagers to one of the names configured below.\n" +
|
||||||
|
"Nametag optimized villagers will be unoptimized again when they are renamed to something else.");
|
||||||
|
|
||||||
|
this.checkRadius = config.getDouble(configPath + ".check-radius-blocks", 500.0,
|
||||||
|
"The radius in blocks in which activity will be grouped together and measured.");
|
||||||
|
this.regionDataCache = Caffeine.newBuilder().expireAfterWrite(Duration.ofMillis(
|
||||||
|
config.getInt(configPath + ".data-keep-time-millis", 10000,
|
||||||
|
"The time in milliseconds before a region and its data will be expired\n" +
|
||||||
|
"if no activity has been detected.\n" +
|
||||||
|
"For proper functionality, needs to be at least as long as your pause time."))).build();
|
||||||
|
|
||||||
|
this.pathfindLimit = config.getInt(configPath + ".limits.pathfind-event", 150);
|
||||||
|
this.entityInteractLimit = config.getInt(configPath + ".limits.interact-event", 50);
|
||||||
|
|
||||||
|
this.notifyPlayers = config.getBoolean(configPath + ".notify-players", true,
|
||||||
|
"Sends players a message to any player near an auto-optimized villager.");
|
||||||
|
this.doLogging = 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NotNull RegionData getRegionData(Location location) {
|
||||||
|
return regionDataCache.get(getRegion(location), RegionData::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NotNull BlockRegion2D getRegion(Location location) {
|
||||||
|
// Find and return region containing this location
|
||||||
|
for (Map.Entry<BlockRegion2D, RegionData> regionDataEntry : regionDataCache.asMap().entrySet()) {
|
||||||
|
if (regionDataEntry.getKey().contains(location)) {
|
||||||
|
return regionDataEntry.getKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and cache region if none exists
|
||||||
|
BlockRegion2D region = BlockRegion2D.of(location.getWorld(), location.getX(), location.getZ(), checkRadius);
|
||||||
|
regionDataCache.put(region, new RegionData(region));
|
||||||
|
return region;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
private void onEntityPathfind(EntityPathfindEvent event) {
|
||||||
|
if (event.getEntityType() != XEntityType.VILLAGER.get()) return;
|
||||||
|
|
||||||
|
Location location = event.getEntity().getLocation();
|
||||||
|
BlockRegion2D region2D = getRegion(location);
|
||||||
|
RegionData regionData = getRegionData(location);
|
||||||
|
|
||||||
|
if (regionData.regionBusy.get() || regionData.pathfindCount.incrementAndGet() <= pathfindLimit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
regionData.regionBusy.set(true);
|
||||||
|
|
||||||
|
AtomicInteger optimizeCount = new AtomicInteger();
|
||||||
|
Set<Player> playersWithinArea = new CopyOnWriteArraySet<>();
|
||||||
|
|
||||||
|
region2D.getEntities()
|
||||||
|
.thenAccept(entities -> {
|
||||||
|
for (Entity entity : entities) {
|
||||||
|
scheduling.entitySpecificScheduler(entity).run(() -> {
|
||||||
|
if (entity.getType() == XEntityType.VILLAGER.get()) {
|
||||||
|
WrappedVillager wrappedVillager = wrapperCache.get((Villager) entity, WrappedVillager::new);
|
||||||
|
|
||||||
|
if (wrappedVillager.isOptimized()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wrappedVillager.setOptimizationType(OptimizationType.REGIONAL_ACTIVITY);
|
||||||
|
optimizeCount.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notifyPlayers && entity.getType() == XEntityType.PLAYER.get()) {
|
||||||
|
playersWithinArea.add((Player) entity);
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.thenRun(() -> {
|
||||||
|
if (notifyPlayers) {
|
||||||
|
TextReplacementConfig amount = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%amount%")
|
||||||
|
.replacement(optimizeCount.toString())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
for (Player player : playersWithinArea) {
|
||||||
|
VillagerOptimizer.scheduling().entitySpecificScheduler(player).run(() ->
|
||||||
|
VillagerOptimizer.getLang(player.locale()).activity_optimize_success
|
||||||
|
.forEach(line -> player.sendMessage(line.replaceText(amount))),
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
playersWithinArea.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doLogging) {
|
||||||
|
info( "Optimized " + optimizeCount.get() + " villagers in a radius of " + checkRadius +
|
||||||
|
" blocks from center at x=" + regionData.region.getCenterX() + ", z=" + regionData.region.getCenterZ() +
|
||||||
|
" in world " + location.getWorld().getName() +
|
||||||
|
"because of too high pathfinding activity within the configured timeframe: " +
|
||||||
|
regionData.pathfindCount + " (limit: " + pathfindLimit + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
regionDataCache.invalidate(region2D);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
private void onEntityInteract(EntityInteractEvent event) {
|
||||||
|
if (event.getEntityType() != XEntityType.VILLAGER.get()) return;
|
||||||
|
|
||||||
|
Location location = event.getEntity().getLocation();
|
||||||
|
BlockRegion2D region2D = getRegion(location);
|
||||||
|
RegionData regionData = getRegionData(location);
|
||||||
|
|
||||||
|
if (regionData.regionBusy.get() || regionData.entityInteractCount.incrementAndGet() <= entityInteractLimit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
regionData.regionBusy.set(true);
|
||||||
|
|
||||||
|
AtomicInteger optimizeCount = new AtomicInteger();
|
||||||
|
Set<Player> playersWithinArea = new CopyOnWriteArraySet<>();
|
||||||
|
|
||||||
|
region2D.getEntities()
|
||||||
|
.thenAccept(entities -> {
|
||||||
|
for (Entity entity : entities) {
|
||||||
|
scheduling.entitySpecificScheduler(entity).run(() -> {
|
||||||
|
if (entity.getType() == XEntityType.VILLAGER.get()) {
|
||||||
|
WrappedVillager wrappedVillager = wrapperCache.get((Villager) entity, WrappedVillager::new);
|
||||||
|
|
||||||
|
if (wrappedVillager.isOptimized()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wrappedVillager.setOptimizationType(OptimizationType.REGIONAL_ACTIVITY);
|
||||||
|
optimizeCount.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notifyPlayers && entity.getType() == XEntityType.PLAYER.get()) {
|
||||||
|
playersWithinArea.add((Player) entity);
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.thenRun(() -> {
|
||||||
|
if (notifyPlayers) {
|
||||||
|
TextReplacementConfig amount = TextReplacementConfig.builder()
|
||||||
|
.matchLiteral("%amount%")
|
||||||
|
.replacement(optimizeCount.toString())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
for (Player player : playersWithinArea) {
|
||||||
|
VillagerOptimizer.scheduling().entitySpecificScheduler(player).run(() ->
|
||||||
|
VillagerOptimizer.getLang(player.locale()).activity_optimize_success
|
||||||
|
.forEach(line -> player.sendMessage(line.replaceText(amount))),
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
playersWithinArea.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doLogging) {
|
||||||
|
info( "Optimized " + optimizeCount.get() + " villagers in a radius of " + checkRadius +
|
||||||
|
" blocks from center at x=" + regionData.region.getCenterX() + ", z=" + regionData.region.getCenterZ() +
|
||||||
|
" in world " + location.getWorld().getName() +
|
||||||
|
"because of too many villagers interacting with objects within the configured timeframe: " +
|
||||||
|
regionData.pathfindCount + " (limit: " + pathfindLimit + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
regionDataCache.invalidate(region2D);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -99,13 +99,13 @@ public class OptimizeByNametag extends VillagerOptimizerModule implements Listen
|
|||||||
|
|
||||||
if (!optimizeEvent.callEvent()) return;
|
if (!optimizeEvent.callEvent()) return;
|
||||||
|
|
||||||
|
wrapped.setOptimizationType(optimizeEvent.getOptimizationType());
|
||||||
|
wrapped.saveOptimizeTime();
|
||||||
|
|
||||||
if (!consume_nametag && player.getGameMode() == GameMode.SURVIVAL) {
|
if (!consume_nametag && player.getGameMode() == GameMode.SURVIVAL) {
|
||||||
player.getInventory().addItem(usedItem.asOne());
|
player.getInventory().addItem(usedItem.asOne());
|
||||||
}
|
}
|
||||||
|
|
||||||
wrapped.setOptimizationType(optimizeEvent.getOptimizationType());
|
|
||||||
wrapped.saveOptimizeTime();
|
|
||||||
|
|
||||||
if (notify_player) {
|
if (notify_player) {
|
||||||
VillagerOptimizer.getLang(player.locale()).nametag_optimize_success
|
VillagerOptimizer.getLang(player.locale()).nametag_optimize_success
|
||||||
.forEach(line -> KyoriUtil.sendMessage(player, line));
|
.forEach(line -> KyoriUtil.sendMessage(player, line));
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package me.xginko.villageroptimizer.struct.enums;
|
package me.xginko.villageroptimizer.struct.enums;
|
||||||
|
|
||||||
public enum OptimizationType {
|
public enum OptimizationType {
|
||||||
|
CHUNK_LIMIT,
|
||||||
|
REGIONAL_ACTIVITY,
|
||||||
COMMAND,
|
COMMAND,
|
||||||
NAMETAG,
|
NAMETAG,
|
||||||
WORKSTATION,
|
WORKSTATION,
|
||||||
|
@ -0,0 +1,131 @@
|
|||||||
|
package me.xginko.villageroptimizer.struct.models;
|
||||||
|
|
||||||
|
import me.xginko.villageroptimizer.VillagerOptimizer;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
public class BlockRegion2D {
|
||||||
|
|
||||||
|
private final UUID worldUID;
|
||||||
|
private final double halfSideLength, centerX, centerZ;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A square region on a minecraft world map.
|
||||||
|
*
|
||||||
|
* @param worldUID The UUID of the world this region is in.
|
||||||
|
* @param centerX The X-axis of the center location on the map.
|
||||||
|
* @param centerZ The Z-axis of the center location on the map.
|
||||||
|
* @param halfSideLength Half the length of the square's side. Acts like a radius would on circular regions.
|
||||||
|
*/
|
||||||
|
public BlockRegion2D(UUID worldUID, double centerX, double centerZ, double halfSideLength) {
|
||||||
|
this.worldUID = worldUID;
|
||||||
|
this.centerX = centerX;
|
||||||
|
this.centerZ = centerZ;
|
||||||
|
this.halfSideLength = halfSideLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a square region on a minecraft world map.
|
||||||
|
*
|
||||||
|
* @param worldUID The UUID of the world this region is in.
|
||||||
|
* @param centerX The X-axis of the center location on the map.
|
||||||
|
* @param centerZ The Z-axis of the center location on the map.
|
||||||
|
* @param halfSideLength Half the length of the square's side. Acts like a radius would on circular regions.
|
||||||
|
*/
|
||||||
|
public static BlockRegion2D of(UUID worldUID, double centerX, double centerZ, double halfSideLength) {
|
||||||
|
return new BlockRegion2D(worldUID, centerX, centerZ, halfSideLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a square region on a minecraft world map.
|
||||||
|
*
|
||||||
|
* @param world The world this region is in.
|
||||||
|
* @param centerX The X-axis of the center location on the map.
|
||||||
|
* @param centerZ The Z-axis of the center location on the map.
|
||||||
|
* @param halfSideLength Half the length of the square's side. Acts like a radius would on circular regions.
|
||||||
|
*/
|
||||||
|
public static BlockRegion2D of(World world, double centerX, double centerZ, double halfSideLength) {
|
||||||
|
return BlockRegion2D.of(world.getUID(), centerX, centerZ, halfSideLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getWorldUID() {
|
||||||
|
return this.worldUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getHalfSideLength() {
|
||||||
|
return this.halfSideLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getCenterX() {
|
||||||
|
return this.centerX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getCenterZ() {
|
||||||
|
return this.centerZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(Location location) {
|
||||||
|
if (!location.getWorld().getUID().equals(this.worldUID)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return location.getX() >= this.centerX - this.halfSideLength
|
||||||
|
&& location.getX() <= this.centerX + this.halfSideLength
|
||||||
|
&& location.getZ() >= this.centerZ - this.halfSideLength
|
||||||
|
&& location.getZ() <= this.centerZ + this.halfSideLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<Collection<Entity>> getEntities() {
|
||||||
|
World world = Bukkit.getWorld(worldUID);
|
||||||
|
|
||||||
|
if (world == null) {
|
||||||
|
// Only way I can imagine this happening would be if the server is using a world manager plugin and unloads
|
||||||
|
// the world during an operation.
|
||||||
|
// Since these plugins are rather common though, we will silently complete with an empty set instead of exceptionally.
|
||||||
|
return CompletableFuture.completedFuture(Collections.emptySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<Collection<Entity>> future = new CompletableFuture<>();
|
||||||
|
Location centerLoc = new Location(world, centerX, world.getMinHeight(), centerZ);
|
||||||
|
|
||||||
|
VillagerOptimizer.scheduling().regionSpecificScheduler(centerLoc).run(() -> future.complete(
|
||||||
|
centerLoc.getNearbyEntities(
|
||||||
|
halfSideLength,
|
||||||
|
Math.abs(world.getMaxHeight()) + Math.abs(world.getMinHeight()), // World y can be between -64 and 320, we want everything from top to bottom
|
||||||
|
halfSideLength
|
||||||
|
)));
|
||||||
|
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (null == obj || obj.getClass() != BlockRegion2D.class)
|
||||||
|
return false;
|
||||||
|
BlockRegion2D blockRegion2D = (BlockRegion2D)obj;
|
||||||
|
return blockRegion2D.worldUID.equals(this.worldUID) && blockRegion2D.centerX == this.centerX && blockRegion2D.centerZ == this.centerZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(this.worldUID, this.centerX, this.centerZ, this.halfSideLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "BlockRegion2D{" +
|
||||||
|
" radius(half side length)=" + halfSideLength +
|
||||||
|
", centerX=" + centerX +
|
||||||
|
", centerZ=" + centerZ +
|
||||||
|
", worldUID=" + worldUID +
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ import org.bukkit.Location;
|
|||||||
import org.bukkit.Sound;
|
import org.bukkit.Sound;
|
||||||
import org.bukkit.entity.Villager;
|
import org.bukkit.entity.Villager;
|
||||||
import org.bukkit.entity.memory.MemoryKey;
|
import org.bukkit.entity.memory.MemoryKey;
|
||||||
|
import org.bukkit.event.entity.VillagerReplenishTradeEvent;
|
||||||
import org.bukkit.inventory.MerchantRecipe;
|
import org.bukkit.inventory.MerchantRecipe;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@ -41,8 +42,11 @@ public class WrappedVillager extends PDCWrapper {
|
|||||||
*/
|
*/
|
||||||
public void restock() {
|
public void restock() {
|
||||||
VillagerOptimizer.scheduling().entitySpecificScheduler(villager).run(() -> {
|
VillagerOptimizer.scheduling().entitySpecificScheduler(villager).run(() -> {
|
||||||
for (MerchantRecipe recipe : villager.getRecipes()) {
|
for (MerchantRecipe merchantRecipe : villager.getRecipes()) {
|
||||||
recipe.setUses(0);
|
VillagerReplenishTradeEvent restockRecipeEvent = new VillagerReplenishTradeEvent(villager, merchantRecipe);
|
||||||
|
if (restockRecipeEvent.callEvent()) {
|
||||||
|
restockRecipeEvent.getRecipe().setUses(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, null);
|
}, null);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user