From 0c3cf627981cd619765da0b7c0804a7670bd07c4 Mon Sep 17 00:00:00 2001 From: adabugra <57899270+adabugra@users.noreply.github.com> Date: Sat, 25 Jan 2025 23:30:58 +0300 Subject: [PATCH 1/7] Optimize handleMove with chunk caching and movement tick skipping --- .../npclib/bukkit/BukkitActionController.java | 61 ++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java b/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java index 82ad6e2..1194298 100644 --- a/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java +++ b/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java @@ -38,9 +38,14 @@ import com.github.juliarn.npclib.bukkit.util.BukkitPlatformUtil; import com.github.juliarn.npclib.common.CommonNpcActionController; import com.github.juliarn.npclib.common.flag.CommonNpcFlaggedBuilder; +import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; @@ -52,6 +57,8 @@ import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerToggleSneakEvent; +import org.bukkit.event.world.ChunkLoadEvent; +import org.bukkit.event.world.ChunkUnloadEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.NotNull; @@ -59,6 +66,9 @@ public final class BukkitActionController extends CommonNpcActionController implements Listener { private final NpcTracker npcTracker; + private final Map> loadedChunks = new HashMap<>(); + private final Map playerCooldowns = new HashMap<>(); + private static final int COOLDOWN_TICKS = 10; // based on the given flags private final int spawnDistance; @@ -135,14 +145,31 @@ public void handleMove(@NotNull PlayerMoveEvent event) { boolean changedWorld = !Objects.equals(from.getWorld(), to.getWorld()); boolean changedOrientation = from.getYaw() != to.getYaw() || from.getPitch() != to.getPitch(); boolean changedPosition = from.getX() != to.getX() || from.getY() != to.getY() || from.getZ() != to.getZ(); + boolean significantMovement = Math.abs(to.getX() - from.getX()) > 0.5 || Math.abs(to.getY() - from.getY()) > 0.5 || Math.abs(to.getZ() - from.getZ()) > 0.5; + + if (!significantMovement) return; + + Player player = event.getPlayer(); + UUID playerId = player.getUniqueId(); + long currentTick = System.currentTimeMillis() / 50; // Convert current time to ticks + + // Cooldown check + Long lastProcessedTick = playerCooldowns.get(playerId); + if (lastProcessedTick != null && currentTick - lastProcessedTick < COOLDOWN_TICKS) { + return; // Skip processing if still within the cooldown period + } + playerCooldowns.put(playerId, currentTick); // check if any movement happened (event is also called when standing still) if (changedPosition || changedOrientation || changedWorld) { - Player player = event.getPlayer(); for (Npc npc : this.npcTracker.trackedNpcs()) { // check if the player is still in the same world as the npc Position pos = npc.position(); - if (!npc.world().equals(player.getWorld()) || !npc.world().isChunkLoaded(pos.chunkX(), pos.chunkZ())) { + World npcWorld = npc.world(); + + // Use cached chunk data to check if the chunk is loaded + Set loadedChunksInWorld = loadedChunks.get(npcWorld); + if (loadedChunksInWorld == null || !loadedChunksInWorld.contains(chunkKey(pos.chunkX(), pos.chunkZ()))) { // if the player is tracked by the npc, stop that npc.stopTrackingPlayer(player); continue; @@ -170,6 +197,32 @@ public void handleMove(@NotNull PlayerMoveEvent event) { } } + @EventHandler + public void onChunkLoad(ChunkLoadEvent event) { + World world = event.getWorld(); + Chunk chunk = event.getChunk(); + + // Add the chunk to the cache + loadedChunks + .computeIfAbsent(world, w -> new HashSet<>()) + .add(chunkKey(chunk.getX(), chunk.getZ())); + } + + @EventHandler + public void onChunkUnload(ChunkUnloadEvent event) { + World world = event.getWorld(); + Chunk chunk = event.getChunk(); + + // Remove the chunk from the cache + Set chunks = loadedChunks.get(world); + if (chunks != null) { + chunks.remove(chunkKey(chunk.getX(), chunk.getZ())); + if (chunks.isEmpty()) { + loadedChunks.remove(world); // Clean up if no chunks are left + } + } + } + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void handleSneak(@NotNull PlayerToggleSneakEvent event) { Player player = event.getPlayer(); @@ -216,6 +269,10 @@ public void handleQuit(@NotNull PlayerQuitEvent event) { } } + private String chunkKey(int chunkX, int chunkZ) { + return chunkX + "," + chunkZ; + } + private static final class BukkitActionControllerBuilder extends CommonNpcFlaggedBuilder implements NpcActionController.Builder { From 4efe6b9cf83d9038938809ef82a254095c03c548 Mon Sep 17 00:00:00 2001 From: adabugra <57899270+adabugra@users.noreply.github.com> Date: Wed, 19 Feb 2025 01:42:26 +0300 Subject: [PATCH 2/7] Use Chunk#getChunkKey --- .../npclib/bukkit/BukkitActionController.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java b/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java index ae3869f..0e9c28f 100644 --- a/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java +++ b/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java @@ -66,7 +66,7 @@ public final class BukkitActionController extends CommonNpcActionController implements Listener { private final NpcTracker npcTracker; - private final Map> loadedChunks = new HashMap<>(); + private final Map> loadedChunks = new HashMap<>(); private final Map playerCooldowns = new HashMap<>(); private static final int COOLDOWN_TICKS = 10; @@ -168,8 +168,9 @@ public void handleMove(@NotNull PlayerMoveEvent event) { World npcWorld = npc.world(); // Use cached chunk data to check if the chunk is loaded - Set loadedChunksInWorld = loadedChunks.get(npcWorld); - if (loadedChunksInWorld == null || !loadedChunksInWorld.contains(chunkKey(pos.chunkX(), pos.chunkZ()))) { + Set loadedChunksInWorld = loadedChunks.get(npcWorld); + long chunkKey = Chunk.getChunkKey(pos.chunkX(), pos.chunkZ()); + if (loadedChunksInWorld == null || !loadedChunksInWorld.contains(chunkKey)) { // if the player is tracked by the npc, stop that npc.stopTrackingPlayer(player); continue; @@ -205,7 +206,7 @@ public void onChunkLoad(ChunkLoadEvent event) { // Add the chunk to the cache loadedChunks .computeIfAbsent(world, w -> new HashSet<>()) - .add(chunkKey(chunk.getX(), chunk.getZ())); + .add(chunk.getChunkKey()); } @EventHandler @@ -214,9 +215,9 @@ public void onChunkUnload(ChunkUnloadEvent event) { Chunk chunk = event.getChunk(); // Remove the chunk from the cache - Set chunks = loadedChunks.get(world); + Set chunks = loadedChunks.get(world); if (chunks != null) { - chunks.remove(chunkKey(chunk.getX(), chunk.getZ())); + chunks.remove(chunk.getChunkKey()); if (chunks.isEmpty()) { loadedChunks.remove(world); // Clean up if no chunks are left } From f08036f6b1b6a771ea83a2c232cb24ead32538a9 Mon Sep 17 00:00:00 2001 From: WhiteProject1 Date: Wed, 19 Feb 2025 01:59:24 +0300 Subject: [PATCH 3/7] More Optimized Movement --- .../npclib/bukkit/BukkitActionController.java | 295 ++++++++++-------- 1 file changed, 168 insertions(+), 127 deletions(-) diff --git a/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java b/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java index 7164fe9..f1c6739 100644 --- a/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java +++ b/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java @@ -22,8 +22,33 @@ * THE SOFTWARE. */ +/* + * This file is part of npc-lib, licensed under the MIT License (MIT). + * + * Copyright (c) 2022-2023 Julian M., Pasqual K. and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.github.juliarn.npclib.bukkit; +import static com.comphenix.protocol.ProtocolLibrary.getPlugin; + import com.github.juliarn.npclib.api.Npc; import com.github.juliarn.npclib.api.NpcActionController; import com.github.juliarn.npclib.api.NpcTracker; @@ -32,188 +57,200 @@ import com.github.juliarn.npclib.api.event.ShowNpcEvent; import com.github.juliarn.npclib.api.event.manager.NpcEventManager; import com.github.juliarn.npclib.api.flag.NpcFlag; -import com.github.juliarn.npclib.api.protocol.enums.EntityAnimation; -import com.github.juliarn.npclib.api.protocol.enums.PlayerInfoAction; import com.github.juliarn.npclib.api.protocol.meta.EntityMetadataFactory; -import com.github.juliarn.npclib.bukkit.util.BukkitPlatformUtil; import com.github.juliarn.npclib.common.CommonNpcActionController; import com.github.juliarn.npclib.common.flag.CommonNpcFlaggedBuilder; + import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import org.bukkit.Bukkit; +import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerToggleSneakEvent; +import org.bukkit.event.world.ChunkLoadEvent; +import org.bukkit.event.world.ChunkUnloadEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.NotNull; public final class BukkitActionController extends CommonNpcActionController implements Listener { + private final Plugin plugin; private final NpcTracker npcTracker; + private final Map> loadedChunks = new ConcurrentHashMap<>(); + private final Map playerCooldowns = new ConcurrentHashMap<>(); + private final NpcEventManager eventManager; + private final PlatformVersionAccessor versionAccessor; - // based on the given flags + private static final int COOLDOWN_TICKS = 10; + private static final long COOLDOWN_MILLIS = COOLDOWN_TICKS * 50L; + private static final double MOVEMENT_THRESHOLD = 0.5; private final int spawnDistance; - private final int imitateDistance; public BukkitActionController( @NotNull Map, Optional> flags, @NotNull Plugin plugin, @NotNull NpcEventManager eventManager, @NotNull PlatformVersionAccessor versionAccessor, - @NotNull NpcTracker tracker - ) { + @NotNull NpcTracker npcTracker) { super(flags); - this.npcTracker = tracker; - - // add all listeners - plugin.getServer().getPluginManager().registerEvents(this, plugin); - - // register a listener for the post spawn event if we need to send out an update to remove the spawned player - if (!versionAccessor.atLeast(1, 19, 3)) { - eventManager.registerEventHandler(ShowNpcEvent.Post.class, event -> { - // remove the npc from the tab list after the given amount of time (never smaller than 0 because of validation) - int tabRemovalTicks = this.flagValueOrDefault(TAB_REMOVAL_TICKS); - plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, () -> { - // schedule the removal of the player from the tab list, can be done async - Player player = event.player(); - event.npc().platform().packetFactory() - .createPlayerInfoPacket(PlayerInfoAction.REMOVE_PLAYER) - .schedule(player, event.npc()); - }, tabRemovalTicks); - }); - } - - // pre-calculate flag values - int spawnDistance = this.flagValueOrDefault(SPAWN_DISTANCE); - this.spawnDistance = spawnDistance * spawnDistance; - - int imitateDistance = this.flagValueOrDefault(IMITATE_DISTANCE); - this.imitateDistance = imitateDistance * imitateDistance; - - // register listener to update the npc rotation after it is tracked - if (this.flagValueOrDefault(NpcActionController.AUTO_SYNC_POSITION_ON_SPAWN)) { - eventManager.registerEventHandler(ShowNpcEvent.Post.class, event -> { - Player player = event.player(); - Location to = player.getLocation(); - - double distance = BukkitPlatformUtil.distance(event.npc(), to); - if (distance <= this.imitateDistance && event.npc().flagValueOrDefault(Npc.LOOK_AT_PLAYER)) { - event.npc().lookAt(BukkitPlatformUtil.positionFromBukkitLegacy(to)).schedule(player); - } - }); - } + this.plugin = plugin; + this.npcTracker = npcTracker; + this.eventManager = eventManager; + this.versionAccessor = versionAccessor; + this.spawnDistance = 50; + scheduleCleanup(); } - static @NotNull NpcActionController.Builder actionControllerBuilder( + public static @NotNull NpcActionController.Builder actionControllerBuilder( @NotNull Plugin plugin, @NotNull NpcEventManager eventManager, @NotNull PlatformVersionAccessor versionAccessor, - @NotNull NpcTracker npcTracker - ) { - Objects.requireNonNull(plugin, "plugin"); - Objects.requireNonNull(eventManager, "eventManager"); - Objects.requireNonNull(npcTracker, "npcTracker"); - Objects.requireNonNull(versionAccessor, "versionAccessor"); - + @NotNull NpcTracker npcTracker) { return new BukkitActionControllerBuilder(plugin, eventManager, versionAccessor, npcTracker); } - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + private void scheduleCleanup() { + Bukkit.getScheduler().runTaskTimer(getPlugin(), () -> { + long currentTime = System.currentTimeMillis(); + playerCooldowns.entrySet().removeIf(entry -> currentTime - entry.getValue() > COOLDOWN_MILLIS * 2); + }, 6000L, 6000L); + } + + private long chunkKey(int x, int z) { + return ((long) x << 32) | (z & 0xFFFFFFFFL); + } + + @EventHandler public void handleMove(@NotNull PlayerMoveEvent event) { - Location to = event.getTo(); Location from = event.getFrom(); + Location to = event.getTo(); + if (to == null) return; - boolean changedWorld = !Objects.equals(from.getWorld(), to.getWorld()); - boolean changedOrientation = from.getYaw() != to.getYaw() || from.getPitch() != to.getPitch(); - boolean changedPosition = from.getX() != to.getX() || from.getY() != to.getY() || from.getZ() != to.getZ(); - - // check if any movement happened (event is also called when standing still) - if (changedPosition || changedOrientation || changedWorld) { - Player player = event.getPlayer(); - for (Npc npc : this.npcTracker.trackedNpcs()) { - // check if the player is still in the same world as the npc - Position pos = npc.position(); - if (!npc.world().equals(player.getWorld()) || !npc.world().isChunkLoaded(pos.chunkX(), pos.chunkZ())) { - // if the player is tracked by the npc, stop that - npc.stopTrackingPlayer(player); - continue; - } + double dx = to.getX() - from.getX(); + double dy = to.getY() - from.getY(); + double dz = to.getZ() - from.getZ(); + + if (Math.abs(dx) <= MOVEMENT_THRESHOLD && + Math.abs(dy) <= MOVEMENT_THRESHOLD && + Math.abs(dz) <= MOVEMENT_THRESHOLD) return; + + Player player = event.getPlayer(); + UUID playerId = player.getUniqueId(); + long currentTime = System.currentTimeMillis(); + + Long lastProcessed = playerCooldowns.get(playerId); + if (lastProcessed != null && currentTime - lastProcessed < COOLDOWN_MILLIS) return; + playerCooldowns.put(playerId, currentTime); + + World playerWorld = player.getWorld(); + Location playerLoc = player.getLocation(); - // check if the player moved in / out of any npc tracking distance - double distance = BukkitPlatformUtil.distance(npc, to); - if (distance > this.spawnDistance) { - // this will only do something if the player is already tracked by the npc + for (Npc npc : npcTracker.trackedNpcs()) { + World npcWorld = npc.world(); + if (!npcWorld.equals(playerWorld)) { + npc.stopTrackingPlayer(player); + continue; + } + + Position pos = npc.position(); + Set worldChunks = loadedChunks.get(npcWorld); + if (worldChunks == null || !worldChunks.contains(chunkKey(pos.chunkX(), pos.chunkZ()))) { + npc.stopTrackingPlayer(player); + continue; + } + + double distX = playerLoc.getX() - pos.x(); + double distY = playerLoc.getY() - pos.y(); + double distZ = playerLoc.getZ() - pos.z(); + double distanceSquared = distX * distX + distY * distY + distZ * distZ; + + if (npc.trackedPlayers().contains(player)) { + if (distanceSquared > spawnDistance * spawnDistance) { npc.stopTrackingPlayer(player); - continue; - } else { - // this will only do something if the player is not already tracked by the npc - npc.trackPlayer(player); } + } else if (distanceSquared <= spawnDistance * spawnDistance) { + ShowNpcEvent.Pre showEvent = new ShowNpcEvent.Pre() { + private boolean isCancelled = false; + + @Override + public boolean cancelled() { + return isCancelled; + } + + @Override + public void cancelled(boolean cancelled) { + this.isCancelled = cancelled; + } + + @Override + @SuppressWarnings("unchecked") + public

P player() { + return (P) player; + } + + @Override + public Npc npc() { + return npc; + } + }; - // check if we should rotate the npc towards the player - if (changedPosition - && npc.tracksPlayer(player) - && distance <= this.imitateDistance - && npc.flagValueOrDefault(Npc.LOOK_AT_PLAYER)) { - npc.lookAt(BukkitPlatformUtil.positionFromBukkitLegacy(to)).schedule(player); + eventManager.post(showEvent); + + if (!showEvent.cancelled()) { + npc.trackPlayer(player); } } } } + @EventHandler + public void onChunkLoad(ChunkLoadEvent event) { + World world = event.getWorld(); + Chunk chunk = event.getChunk(); + loadedChunks.computeIfAbsent(world, k -> ConcurrentHashMap.newKeySet()) + .add(chunkKey(chunk.getX(), chunk.getZ())); + } + + @EventHandler + public void onChunkUnload(ChunkUnloadEvent event) { + World world = event.getWorld(); + Set chunks = loadedChunks.get(world); + if (chunks != null) { + chunks.remove(chunkKey(event.getChunk().getX(), event.getChunk().getZ())); + if (chunks.isEmpty()) loadedChunks.remove(world); + } + } + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void handleSneak(@NotNull PlayerToggleSneakEvent event) { Player player = event.getPlayer(); - for (Npc npc : this.npcTracker.trackedNpcs()) { - double distance = BukkitPlatformUtil.distance(npc, player.getLocation()); - - // check if we should imitate the action - if (npc.world().equals(player.getWorld()) - && npc.tracksPlayer(player) - && distance <= this.imitateDistance - && npc.flagValueOrDefault(Npc.SNEAK_WHEN_PLAYER_SNEAKS)) { - // let the npc sneak as well - npc.platform().packetFactory() - .createEntityMetaPacket(EntityMetadataFactory.sneakingMetaFactory(), event.isSneaking()) - .schedule(player, npc); - } - } - } + boolean isSneaking = event.isSneaking(); - @EventHandler(priority = EventPriority.MONITOR) - public void handleLeftClick(@NotNull PlayerInteractEvent event) { - if (event.getAction() == Action.LEFT_CLICK_AIR || event.getAction() == Action.LEFT_CLICK_BLOCK) { - Player player = event.getPlayer(); - for (Npc npc : this.npcTracker.trackedNpcs()) { - double distance = BukkitPlatformUtil.distance(npc, player.getLocation()); - - // check if we should imitate the action - if (npc.world().equals(player.getWorld()) - && npc.tracksPlayer(player) - && distance <= this.imitateDistance - && npc.flagValueOrDefault(Npc.HIT_WHEN_PLAYER_HITS)) { - // let the npc left click as well - npc.platform().packetFactory().createAnimationPacket(EntityAnimation.SWING_MAIN_ARM).schedule(player, npc); - } - } - } + npcTracker.trackedNpcs().stream() + .filter(npc -> npc.trackedPlayers().contains(player)) + .forEach(npc -> { + npc.changeMetadata(EntityMetadataFactory.sneakingMetaFactory(), isSneaking); + }); } @EventHandler(priority = EventPriority.MONITOR) public void handleQuit(@NotNull PlayerQuitEvent event) { - for (Npc npc : this.npcTracker.trackedNpcs()) { - // check if the npc tracks the player which disconnected and stop tracking him if so - npc.stopTrackingPlayer(event.getPlayer()); - } + Player player = event.getPlayer(); + playerCooldowns.remove(player.getUniqueId()); + npcTracker.trackedNpcs().forEach(npc -> npc.stopTrackingPlayer(player)); } private static final class BukkitActionControllerBuilder @@ -229,18 +266,22 @@ public BukkitActionControllerBuilder( @NotNull Plugin plugin, @NotNull NpcEventManager eventManager, @NotNull PlatformVersionAccessor versionAccessor, - @NotNull NpcTracker npcTracker - ) { + @NotNull NpcTracker npcTracker) { + Objects.requireNonNull(plugin, "plugin"); + Objects.requireNonNull(eventManager, "eventManager"); + Objects.requireNonNull(versionAccessor, "versionAccessor"); + Objects.requireNonNull(npcTracker, "npcTracker"); + this.plugin = plugin; this.eventManager = eventManager; - this.npcTracker = npcTracker; this.versionAccessor = versionAccessor; + this.npcTracker = npcTracker; } @Override public @NotNull NpcActionController build() { return new BukkitActionController( - this.flags, + (Map, Optional>) this.flags, this.plugin, this.eventManager, this.versionAccessor, From c17e12dd774094ff1db34916014eeb1cb5c5eeb5 Mon Sep 17 00:00:00 2001 From: WhiteProject1 Date: Wed, 19 Feb 2025 02:59:40 +0300 Subject: [PATCH 4/7] Optimization and Modify --- .../npclib/bukkit/BukkitActionController.java | 265 ++++++++++-------- 1 file changed, 141 insertions(+), 124 deletions(-) diff --git a/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java b/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java index 4292f9f..0e9c28f 100644 --- a/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java +++ b/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java @@ -22,33 +22,8 @@ * THE SOFTWARE. */ -/* - * This file is part of npc-lib, licensed under the MIT License (MIT). - * - * Copyright (c) 2022-2023 Julian M., Pasqual K. and contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ package com.github.juliarn.npclib.bukkit; -import static com.comphenix.protocol.ProtocolLibrary.getPlugin; - import com.github.juliarn.npclib.api.Npc; import com.github.juliarn.npclib.api.NpcActionController; import com.github.juliarn.npclib.api.NpcTracker; @@ -57,18 +32,28 @@ import com.github.juliarn.npclib.api.event.ShowNpcEvent; import com.github.juliarn.npclib.api.event.manager.NpcEventManager; import com.github.juliarn.npclib.api.flag.NpcFlag; +import com.github.juliarn.npclib.api.protocol.enums.EntityAnimation; +import com.github.juliarn.npclib.api.protocol.enums.PlayerInfoAction; import com.github.juliarn.npclib.api.protocol.meta.EntityMetadataFactory; +import com.github.juliarn.npclib.bukkit.util.BukkitPlatformUtil; import com.github.juliarn.npclib.common.CommonNpcActionController; import com.github.juliarn.npclib.common.flag.CommonNpcFlaggedBuilder; +import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerToggleSneakEvent; @@ -80,127 +65,136 @@ public final class BukkitActionController extends CommonNpcActionController implements Listener { - private final Plugin plugin; private final NpcTracker npcTracker; - + private final Map> loadedChunks = new HashMap<>(); + private final Map playerCooldowns = new HashMap<>(); private static final int COOLDOWN_TICKS = 10; - private static final long COOLDOWN_MILLIS = COOLDOWN_TICKS * 50L; - private static final double MOVEMENT_THRESHOLD = 0.5; + + // based on the given flags private final int spawnDistance; + private final int imitateDistance; public BukkitActionController( @NotNull Map, Optional> flags, @NotNull Plugin plugin, @NotNull NpcEventManager eventManager, @NotNull PlatformVersionAccessor versionAccessor, - @NotNull NpcTracker npcTracker) { + @NotNull NpcTracker tracker + ) { super(flags); - this.plugin = plugin; - this.npcTracker = npcTracker; - this.eventManager = eventManager; - this.versionAccessor = versionAccessor; - this.spawnDistance = 50; - scheduleCleanup(); + this.npcTracker = tracker; + + // add all listeners + plugin.getServer().getPluginManager().registerEvents(this, plugin); + + // register a listener for the post spawn event if we need to send out an update to remove the spawned player + if (!versionAccessor.atLeast(1, 19, 3)) { + eventManager.registerEventHandler(ShowNpcEvent.Post.class, event -> { + // remove the npc from the tab list after the given amount of time (never smaller than 0 because of validation) + int tabRemovalTicks = this.flagValueOrDefault(TAB_REMOVAL_TICKS); + plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, () -> { + // schedule the removal of the player from the tab list, can be done async + Player player = event.player(); + event.npc().platform().packetFactory() + .createPlayerInfoPacket(PlayerInfoAction.REMOVE_PLAYER) + .schedule(player, event.npc()); + }, tabRemovalTicks); + }); + } + + // pre-calculate flag values + int spawnDistance = this.flagValueOrDefault(SPAWN_DISTANCE); + this.spawnDistance = spawnDistance * spawnDistance; + + int imitateDistance = this.flagValueOrDefault(IMITATE_DISTANCE); + this.imitateDistance = imitateDistance * imitateDistance; + + // register listener to update the npc rotation after it is tracked + if (this.flagValueOrDefault(NpcActionController.AUTO_SYNC_POSITION_ON_SPAWN)) { + eventManager.registerEventHandler(ShowNpcEvent.Post.class, event -> { + Player player = event.player(); + Location to = player.getLocation(); + + double distance = BukkitPlatformUtil.distance(event.npc(), to); + if (distance <= this.imitateDistance && event.npc().flagValueOrDefault(Npc.LOOK_AT_PLAYER)) { + event.npc().lookAt(BukkitPlatformUtil.positionFromBukkitLegacy(to)).schedule(player); + } + }); + } } - public static @NotNull NpcActionController.Builder actionControllerBuilder( + static @NotNull NpcActionController.Builder actionControllerBuilder( @NotNull Plugin plugin, @NotNull NpcEventManager eventManager, @NotNull PlatformVersionAccessor versionAccessor, - @NotNull NpcTracker npcTracker) { - return new BukkitActionControllerBuilder(plugin, eventManager, versionAccessor, npcTracker); - } + @NotNull NpcTracker npcTracker + ) { + Objects.requireNonNull(plugin, "plugin"); + Objects.requireNonNull(eventManager, "eventManager"); + Objects.requireNonNull(npcTracker, "npcTracker"); + Objects.requireNonNull(versionAccessor, "versionAccessor"); - private void scheduleCleanup() { - Bukkit.getScheduler().runTaskTimer(getPlugin(), () -> { - long currentTime = System.currentTimeMillis(); - playerCooldowns.entrySet().removeIf(entry -> currentTime - entry.getValue() > COOLDOWN_MILLIS * 2); - }, 6000L, 6000L); - } - - private long chunkKey(int x, int z) { - return ((long) x << 32) | (z & 0xFFFFFFFFL); + return new BukkitActionControllerBuilder(plugin, eventManager, versionAccessor, npcTracker); } - @EventHandler + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void handleMove(@NotNull PlayerMoveEvent event) { + Location to = event.getTo(); Location from = event.getFrom(); boolean changedWorld = !Objects.equals(from.getWorld(), to.getWorld()); boolean changedOrientation = from.getYaw() != to.getYaw() || from.getPitch() != to.getPitch(); boolean changedPosition = from.getX() != to.getX() || from.getY() != to.getY() || from.getZ() != to.getZ(); + boolean significantMovement = Math.abs(to.getX() - from.getX()) > 0.5 || Math.abs(to.getY() - from.getY()) > 0.5 || Math.abs(to.getZ() - from.getZ()) > 0.5; + + if (!significantMovement) return; + + Player player = event.getPlayer(); + UUID playerId = player.getUniqueId(); + long currentTick = System.currentTimeMillis() / 50; // Convert current time to ticks + + // Cooldown check + Long lastProcessedTick = playerCooldowns.get(playerId); + if (lastProcessedTick != null && currentTick - lastProcessedTick < COOLDOWN_TICKS) { + return; // Skip processing if still within the cooldown period + } + playerCooldowns.put(playerId, currentTick); // check if any movement happened (event is also called when standing still) if (changedPosition || changedOrientation || changedWorld) { - Player player = event.getPlayer(); for (Npc npc : this.npcTracker.trackedNpcs()) { // check if the player is still in the same world as the npc Position pos = npc.position(); - if (!npc.world().equals(player.getWorld()) || !npc.world().isChunkLoaded(pos.chunkX(), pos.chunkZ())) { + World npcWorld = npc.world(); + + // Use cached chunk data to check if the chunk is loaded + Set loadedChunksInWorld = loadedChunks.get(npcWorld); + long chunkKey = Chunk.getChunkKey(pos.chunkX(), pos.chunkZ()); + if (loadedChunksInWorld == null || !loadedChunksInWorld.contains(chunkKey)) { // if the player is tracked by the npc, stop that npc.stopTrackingPlayer(player); continue; } - double distX = playerLoc.getX() - pos.x(); - double distY = playerLoc.getY() - pos.y(); - double distZ = playerLoc.getZ() - pos.z(); - double distanceSquared = distX * distX + distY * distY + distZ * distZ; - - if (npc.trackedPlayers().contains(player)) { - if (distanceSquared > spawnDistance * spawnDistance) { + // check if the player moved in / out of any npc tracking distance + double distance = BukkitPlatformUtil.distance(npc, to); + if (distance > this.spawnDistance) { + // this will only do something if the player is already tracked by the npc npc.stopTrackingPlayer(player); - } - } else if (distanceSquared <= spawnDistance * spawnDistance) { - ShowNpcEvent.Pre showEvent = new ShowNpcEvent.Pre() { - private boolean isCancelled = false; - - @Override - public boolean cancelled() { - return isCancelled; - } - - @Override - public void cancelled(boolean cancelled) { - this.isCancelled = cancelled; - } - - @Override - @SuppressWarnings("unchecked") - public

P player() { - return (P) player; - } - - @Override - public Npc npc() { - return npc; - } - }; - - eventManager.post(showEvent); - - if (!showEvent.cancelled()) { + continue; + } else { + // this will only do something if the player is not already tracked by the npc npc.trackPlayer(player); } - } - } - } - - @EventHandler - public void onChunkLoad(ChunkLoadEvent event) { - World world = event.getWorld(); - Chunk chunk = event.getChunk(); - loadedChunks.computeIfAbsent(world, k -> ConcurrentHashMap.newKeySet()) - .add(chunkKey(chunk.getX(), chunk.getZ())); - } - @EventHandler - public void onChunkUnload(ChunkUnloadEvent event) { - World world = event.getWorld(); - Set chunks = loadedChunks.get(world); - if (chunks != null) { - chunks.remove(chunkKey(event.getChunk().getX(), event.getChunk().getZ())); - if (chunks.isEmpty()) loadedChunks.remove(world); + // check if we should rotate the npc towards the player + if (changedPosition + && npc.tracksPlayer(player) + && distance <= this.imitateDistance + && npc.flagValueOrDefault(Npc.LOOK_AT_PLAYER)) { + npc.lookAt(BukkitPlatformUtil.positionFromBukkitLegacy(to)).schedule(player); + } + } } } @@ -233,20 +227,47 @@ public void onChunkUnload(ChunkUnloadEvent event) { @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void handleSneak(@NotNull PlayerToggleSneakEvent event) { Player player = event.getPlayer(); - boolean isSneaking = event.isSneaking(); + for (Npc npc : this.npcTracker.trackedNpcs()) { + double distance = BukkitPlatformUtil.distance(npc, player.getLocation()); + + // check if we should imitate the action + if (npc.world().equals(player.getWorld()) + && npc.tracksPlayer(player) + && distance <= this.imitateDistance + && npc.flagValueOrDefault(Npc.SNEAK_WHEN_PLAYER_SNEAKS)) { + // let the npc sneak as well + npc.platform().packetFactory() + .createEntityMetaPacket(EntityMetadataFactory.sneakingMetaFactory(), event.isSneaking()) + .schedule(player, npc); + } + } + } - npcTracker.trackedNpcs().stream() - .filter(npc -> npc.trackedPlayers().contains(player)) - .forEach(npc -> { - npc.changeMetadata(EntityMetadataFactory.sneakingMetaFactory(), isSneaking); - }); + @EventHandler(priority = EventPriority.MONITOR) + public void handleLeftClick(@NotNull PlayerInteractEvent event) { + if (event.getAction() == Action.LEFT_CLICK_AIR || event.getAction() == Action.LEFT_CLICK_BLOCK) { + Player player = event.getPlayer(); + for (Npc npc : this.npcTracker.trackedNpcs()) { + double distance = BukkitPlatformUtil.distance(npc, player.getLocation()); + + // check if we should imitate the action + if (npc.world().equals(player.getWorld()) + && npc.tracksPlayer(player) + && distance <= this.imitateDistance + && npc.flagValueOrDefault(Npc.HIT_WHEN_PLAYER_HITS)) { + // let the npc left click as well + npc.platform().packetFactory().createAnimationPacket(EntityAnimation.SWING_MAIN_ARM).schedule(player, npc); + } + } + } } @EventHandler(priority = EventPriority.MONITOR) public void handleQuit(@NotNull PlayerQuitEvent event) { - Player player = event.getPlayer(); - playerCooldowns.remove(player.getUniqueId()); - npcTracker.trackedNpcs().forEach(npc -> npc.stopTrackingPlayer(player)); + for (Npc npc : this.npcTracker.trackedNpcs()) { + // check if the npc tracks the player which disconnected and stop tracking him if so + npc.stopTrackingPlayer(event.getPlayer()); + } } private String chunkKey(int chunkX, int chunkZ) { @@ -266,22 +287,18 @@ public BukkitActionControllerBuilder( @NotNull Plugin plugin, @NotNull NpcEventManager eventManager, @NotNull PlatformVersionAccessor versionAccessor, - @NotNull NpcTracker npcTracker) { - Objects.requireNonNull(plugin, "plugin"); - Objects.requireNonNull(eventManager, "eventManager"); - Objects.requireNonNull(versionAccessor, "versionAccessor"); - Objects.requireNonNull(npcTracker, "npcTracker"); - + @NotNull NpcTracker npcTracker + ) { this.plugin = plugin; this.eventManager = eventManager; - this.versionAccessor = versionAccessor; this.npcTracker = npcTracker; + this.versionAccessor = versionAccessor; } @Override public @NotNull NpcActionController build() { return new BukkitActionController( - (Map, Optional>) this.flags, + this.flags, this.plugin, this.eventManager, this.versionAccessor, From d55170fa89bde6d9946879995744406ab1aa1ab6 Mon Sep 17 00:00:00 2001 From: WhiteProject1 Date: Wed, 19 Feb 2025 03:03:57 +0300 Subject: [PATCH 5/7] Revert "Optimization and Modify" This reverts commit c17e12dd774094ff1db34916014eeb1cb5c5eeb5. --- .../npclib/bukkit/BukkitActionController.java | 265 ++++++++---------- 1 file changed, 124 insertions(+), 141 deletions(-) diff --git a/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java b/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java index 0e9c28f..4292f9f 100644 --- a/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java +++ b/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java @@ -22,8 +22,33 @@ * THE SOFTWARE. */ +/* + * This file is part of npc-lib, licensed under the MIT License (MIT). + * + * Copyright (c) 2022-2023 Julian M., Pasqual K. and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.github.juliarn.npclib.bukkit; +import static com.comphenix.protocol.ProtocolLibrary.getPlugin; + import com.github.juliarn.npclib.api.Npc; import com.github.juliarn.npclib.api.NpcActionController; import com.github.juliarn.npclib.api.NpcTracker; @@ -32,28 +57,18 @@ import com.github.juliarn.npclib.api.event.ShowNpcEvent; import com.github.juliarn.npclib.api.event.manager.NpcEventManager; import com.github.juliarn.npclib.api.flag.NpcFlag; -import com.github.juliarn.npclib.api.protocol.enums.EntityAnimation; -import com.github.juliarn.npclib.api.protocol.enums.PlayerInfoAction; import com.github.juliarn.npclib.api.protocol.meta.EntityMetadataFactory; -import com.github.juliarn.npclib.bukkit.util.BukkitPlatformUtil; import com.github.juliarn.npclib.common.CommonNpcActionController; import com.github.juliarn.npclib.common.flag.CommonNpcFlaggedBuilder; -import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.Set; -import java.util.UUID; -import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerToggleSneakEvent; @@ -65,139 +80,130 @@ public final class BukkitActionController extends CommonNpcActionController implements Listener { + private final Plugin plugin; private final NpcTracker npcTracker; - private final Map> loadedChunks = new HashMap<>(); - private final Map playerCooldowns = new HashMap<>(); - private static final int COOLDOWN_TICKS = 10; - // based on the given flags + private static final int COOLDOWN_TICKS = 10; + private static final long COOLDOWN_MILLIS = COOLDOWN_TICKS * 50L; + private static final double MOVEMENT_THRESHOLD = 0.5; private final int spawnDistance; - private final int imitateDistance; public BukkitActionController( @NotNull Map, Optional> flags, @NotNull Plugin plugin, @NotNull NpcEventManager eventManager, @NotNull PlatformVersionAccessor versionAccessor, - @NotNull NpcTracker tracker - ) { + @NotNull NpcTracker npcTracker) { super(flags); - this.npcTracker = tracker; - - // add all listeners - plugin.getServer().getPluginManager().registerEvents(this, plugin); - - // register a listener for the post spawn event if we need to send out an update to remove the spawned player - if (!versionAccessor.atLeast(1, 19, 3)) { - eventManager.registerEventHandler(ShowNpcEvent.Post.class, event -> { - // remove the npc from the tab list after the given amount of time (never smaller than 0 because of validation) - int tabRemovalTicks = this.flagValueOrDefault(TAB_REMOVAL_TICKS); - plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, () -> { - // schedule the removal of the player from the tab list, can be done async - Player player = event.player(); - event.npc().platform().packetFactory() - .createPlayerInfoPacket(PlayerInfoAction.REMOVE_PLAYER) - .schedule(player, event.npc()); - }, tabRemovalTicks); - }); - } - - // pre-calculate flag values - int spawnDistance = this.flagValueOrDefault(SPAWN_DISTANCE); - this.spawnDistance = spawnDistance * spawnDistance; - - int imitateDistance = this.flagValueOrDefault(IMITATE_DISTANCE); - this.imitateDistance = imitateDistance * imitateDistance; - - // register listener to update the npc rotation after it is tracked - if (this.flagValueOrDefault(NpcActionController.AUTO_SYNC_POSITION_ON_SPAWN)) { - eventManager.registerEventHandler(ShowNpcEvent.Post.class, event -> { - Player player = event.player(); - Location to = player.getLocation(); - - double distance = BukkitPlatformUtil.distance(event.npc(), to); - if (distance <= this.imitateDistance && event.npc().flagValueOrDefault(Npc.LOOK_AT_PLAYER)) { - event.npc().lookAt(BukkitPlatformUtil.positionFromBukkitLegacy(to)).schedule(player); - } - }); - } + this.plugin = plugin; + this.npcTracker = npcTracker; + this.eventManager = eventManager; + this.versionAccessor = versionAccessor; + this.spawnDistance = 50; + scheduleCleanup(); } - static @NotNull NpcActionController.Builder actionControllerBuilder( + public static @NotNull NpcActionController.Builder actionControllerBuilder( @NotNull Plugin plugin, @NotNull NpcEventManager eventManager, @NotNull PlatformVersionAccessor versionAccessor, - @NotNull NpcTracker npcTracker - ) { - Objects.requireNonNull(plugin, "plugin"); - Objects.requireNonNull(eventManager, "eventManager"); - Objects.requireNonNull(npcTracker, "npcTracker"); - Objects.requireNonNull(versionAccessor, "versionAccessor"); - + @NotNull NpcTracker npcTracker) { return new BukkitActionControllerBuilder(plugin, eventManager, versionAccessor, npcTracker); } - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + private void scheduleCleanup() { + Bukkit.getScheduler().runTaskTimer(getPlugin(), () -> { + long currentTime = System.currentTimeMillis(); + playerCooldowns.entrySet().removeIf(entry -> currentTime - entry.getValue() > COOLDOWN_MILLIS * 2); + }, 6000L, 6000L); + } + + private long chunkKey(int x, int z) { + return ((long) x << 32) | (z & 0xFFFFFFFFL); + } + + @EventHandler public void handleMove(@NotNull PlayerMoveEvent event) { - Location to = event.getTo(); Location from = event.getFrom(); boolean changedWorld = !Objects.equals(from.getWorld(), to.getWorld()); boolean changedOrientation = from.getYaw() != to.getYaw() || from.getPitch() != to.getPitch(); boolean changedPosition = from.getX() != to.getX() || from.getY() != to.getY() || from.getZ() != to.getZ(); - boolean significantMovement = Math.abs(to.getX() - from.getX()) > 0.5 || Math.abs(to.getY() - from.getY()) > 0.5 || Math.abs(to.getZ() - from.getZ()) > 0.5; - - if (!significantMovement) return; - - Player player = event.getPlayer(); - UUID playerId = player.getUniqueId(); - long currentTick = System.currentTimeMillis() / 50; // Convert current time to ticks - - // Cooldown check - Long lastProcessedTick = playerCooldowns.get(playerId); - if (lastProcessedTick != null && currentTick - lastProcessedTick < COOLDOWN_TICKS) { - return; // Skip processing if still within the cooldown period - } - playerCooldowns.put(playerId, currentTick); // check if any movement happened (event is also called when standing still) if (changedPosition || changedOrientation || changedWorld) { + Player player = event.getPlayer(); for (Npc npc : this.npcTracker.trackedNpcs()) { // check if the player is still in the same world as the npc Position pos = npc.position(); - World npcWorld = npc.world(); - - // Use cached chunk data to check if the chunk is loaded - Set loadedChunksInWorld = loadedChunks.get(npcWorld); - long chunkKey = Chunk.getChunkKey(pos.chunkX(), pos.chunkZ()); - if (loadedChunksInWorld == null || !loadedChunksInWorld.contains(chunkKey)) { + if (!npc.world().equals(player.getWorld()) || !npc.world().isChunkLoaded(pos.chunkX(), pos.chunkZ())) { // if the player is tracked by the npc, stop that npc.stopTrackingPlayer(player); continue; } - // check if the player moved in / out of any npc tracking distance - double distance = BukkitPlatformUtil.distance(npc, to); - if (distance > this.spawnDistance) { - // this will only do something if the player is already tracked by the npc + double distX = playerLoc.getX() - pos.x(); + double distY = playerLoc.getY() - pos.y(); + double distZ = playerLoc.getZ() - pos.z(); + double distanceSquared = distX * distX + distY * distY + distZ * distZ; + + if (npc.trackedPlayers().contains(player)) { + if (distanceSquared > spawnDistance * spawnDistance) { npc.stopTrackingPlayer(player); - continue; - } else { - // this will only do something if the player is not already tracked by the npc - npc.trackPlayer(player); } - - // check if we should rotate the npc towards the player - if (changedPosition - && npc.tracksPlayer(player) - && distance <= this.imitateDistance - && npc.flagValueOrDefault(Npc.LOOK_AT_PLAYER)) { - npc.lookAt(BukkitPlatformUtil.positionFromBukkitLegacy(to)).schedule(player); + } else if (distanceSquared <= spawnDistance * spawnDistance) { + ShowNpcEvent.Pre showEvent = new ShowNpcEvent.Pre() { + private boolean isCancelled = false; + + @Override + public boolean cancelled() { + return isCancelled; + } + + @Override + public void cancelled(boolean cancelled) { + this.isCancelled = cancelled; + } + + @Override + @SuppressWarnings("unchecked") + public

P player() { + return (P) player; + } + + @Override + public Npc npc() { + return npc; + } + }; + + eventManager.post(showEvent); + + if (!showEvent.cancelled()) { + npc.trackPlayer(player); } } } } + @EventHandler + public void onChunkLoad(ChunkLoadEvent event) { + World world = event.getWorld(); + Chunk chunk = event.getChunk(); + loadedChunks.computeIfAbsent(world, k -> ConcurrentHashMap.newKeySet()) + .add(chunkKey(chunk.getX(), chunk.getZ())); + } + + @EventHandler + public void onChunkUnload(ChunkUnloadEvent event) { + World world = event.getWorld(); + Set chunks = loadedChunks.get(world); + if (chunks != null) { + chunks.remove(chunkKey(event.getChunk().getX(), event.getChunk().getZ())); + if (chunks.isEmpty()) loadedChunks.remove(world); + } + } + @EventHandler public void onChunkLoad(ChunkLoadEvent event) { World world = event.getWorld(); @@ -227,47 +233,20 @@ public void onChunkUnload(ChunkUnloadEvent event) { @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void handleSneak(@NotNull PlayerToggleSneakEvent event) { Player player = event.getPlayer(); - for (Npc npc : this.npcTracker.trackedNpcs()) { - double distance = BukkitPlatformUtil.distance(npc, player.getLocation()); - - // check if we should imitate the action - if (npc.world().equals(player.getWorld()) - && npc.tracksPlayer(player) - && distance <= this.imitateDistance - && npc.flagValueOrDefault(Npc.SNEAK_WHEN_PLAYER_SNEAKS)) { - // let the npc sneak as well - npc.platform().packetFactory() - .createEntityMetaPacket(EntityMetadataFactory.sneakingMetaFactory(), event.isSneaking()) - .schedule(player, npc); - } - } - } + boolean isSneaking = event.isSneaking(); - @EventHandler(priority = EventPriority.MONITOR) - public void handleLeftClick(@NotNull PlayerInteractEvent event) { - if (event.getAction() == Action.LEFT_CLICK_AIR || event.getAction() == Action.LEFT_CLICK_BLOCK) { - Player player = event.getPlayer(); - for (Npc npc : this.npcTracker.trackedNpcs()) { - double distance = BukkitPlatformUtil.distance(npc, player.getLocation()); - - // check if we should imitate the action - if (npc.world().equals(player.getWorld()) - && npc.tracksPlayer(player) - && distance <= this.imitateDistance - && npc.flagValueOrDefault(Npc.HIT_WHEN_PLAYER_HITS)) { - // let the npc left click as well - npc.platform().packetFactory().createAnimationPacket(EntityAnimation.SWING_MAIN_ARM).schedule(player, npc); - } - } - } + npcTracker.trackedNpcs().stream() + .filter(npc -> npc.trackedPlayers().contains(player)) + .forEach(npc -> { + npc.changeMetadata(EntityMetadataFactory.sneakingMetaFactory(), isSneaking); + }); } @EventHandler(priority = EventPriority.MONITOR) public void handleQuit(@NotNull PlayerQuitEvent event) { - for (Npc npc : this.npcTracker.trackedNpcs()) { - // check if the npc tracks the player which disconnected and stop tracking him if so - npc.stopTrackingPlayer(event.getPlayer()); - } + Player player = event.getPlayer(); + playerCooldowns.remove(player.getUniqueId()); + npcTracker.trackedNpcs().forEach(npc -> npc.stopTrackingPlayer(player)); } private String chunkKey(int chunkX, int chunkZ) { @@ -287,18 +266,22 @@ public BukkitActionControllerBuilder( @NotNull Plugin plugin, @NotNull NpcEventManager eventManager, @NotNull PlatformVersionAccessor versionAccessor, - @NotNull NpcTracker npcTracker - ) { + @NotNull NpcTracker npcTracker) { + Objects.requireNonNull(plugin, "plugin"); + Objects.requireNonNull(eventManager, "eventManager"); + Objects.requireNonNull(versionAccessor, "versionAccessor"); + Objects.requireNonNull(npcTracker, "npcTracker"); + this.plugin = plugin; this.eventManager = eventManager; - this.npcTracker = npcTracker; this.versionAccessor = versionAccessor; + this.npcTracker = npcTracker; } @Override public @NotNull NpcActionController build() { return new BukkitActionController( - this.flags, + (Map, Optional>) this.flags, this.plugin, this.eventManager, this.versionAccessor, From 59e6d26e1b841181d4abbba4cd67ea83266cc06d Mon Sep 17 00:00:00 2001 From: WhiteProject1 Date: Wed, 19 Feb 2025 03:15:52 +0300 Subject: [PATCH 6/7] Optimization and Modify --- .../npclib/bukkit/BukkitActionController.java | 106 +++++++++--------- 1 file changed, 54 insertions(+), 52 deletions(-) diff --git a/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java b/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java index 4292f9f..a23de5a 100644 --- a/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java +++ b/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java @@ -63,6 +63,11 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; @@ -82,6 +87,10 @@ public final class BukkitActionController extends CommonNpcActionController impl private final Plugin plugin; private final NpcTracker npcTracker; + private final Map> loadedChunks = new ConcurrentHashMap<>(); + private final Map playerCooldowns = new ConcurrentHashMap<>(); + private final NpcEventManager eventManager; + private final PlatformVersionAccessor versionAccessor; private static final int COOLDOWN_TICKS = 10; private static final long COOLDOWN_MILLIS = COOLDOWN_TICKS * 50L; @@ -103,18 +112,11 @@ public BukkitActionController( scheduleCleanup(); } - public static @NotNull NpcActionController.Builder actionControllerBuilder( - @NotNull Plugin plugin, - @NotNull NpcEventManager eventManager, - @NotNull PlatformVersionAccessor versionAccessor, - @NotNull NpcTracker npcTracker) { - return new BukkitActionControllerBuilder(plugin, eventManager, versionAccessor, npcTracker); - } - private void scheduleCleanup() { - Bukkit.getScheduler().runTaskTimer(getPlugin(), () -> { + Bukkit.getScheduler().runTaskTimer(plugin, () -> { long currentTime = System.currentTimeMillis(); - playerCooldowns.entrySet().removeIf(entry -> currentTime - entry.getValue() > COOLDOWN_MILLIS * 2); + playerCooldowns.entrySet().removeIf(entry -> + currentTime - entry.getValue() > COOLDOWN_MILLIS * 2); }, 6000L, 6000L); } @@ -125,22 +127,41 @@ private long chunkKey(int x, int z) { @EventHandler public void handleMove(@NotNull PlayerMoveEvent event) { Location from = event.getFrom(); + Location to = event.getTo(); + if (to == null) return; - boolean changedWorld = !Objects.equals(from.getWorld(), to.getWorld()); - boolean changedOrientation = from.getYaw() != to.getYaw() || from.getPitch() != to.getPitch(); - boolean changedPosition = from.getX() != to.getX() || from.getY() != to.getY() || from.getZ() != to.getZ(); - - // check if any movement happened (event is also called when standing still) - if (changedPosition || changedOrientation || changedWorld) { - Player player = event.getPlayer(); - for (Npc npc : this.npcTracker.trackedNpcs()) { - // check if the player is still in the same world as the npc - Position pos = npc.position(); - if (!npc.world().equals(player.getWorld()) || !npc.world().isChunkLoaded(pos.chunkX(), pos.chunkZ())) { - // if the player is tracked by the npc, stop that - npc.stopTrackingPlayer(player); - continue; - } + double dx = to.getX() - from.getX(); + double dy = to.getY() - from.getY(); + double dz = to.getZ() - from.getZ(); + + if (Math.abs(dx) <= MOVEMENT_THRESHOLD && + Math.abs(dy) <= MOVEMENT_THRESHOLD && + Math.abs(dz) <= MOVEMENT_THRESHOLD) return; + + Player player = event.getPlayer(); + UUID playerId = player.getUniqueId(); + long currentTime = System.currentTimeMillis(); + + Long lastProcessed = playerCooldowns.get(playerId); + if (lastProcessed != null && currentTime - lastProcessed < COOLDOWN_MILLIS) return; + playerCooldowns.put(playerId, currentTime); + + World playerWorld = player.getWorld(); + Location playerLoc = player.getLocation(); + + for (Npc npc : npcTracker.trackedNpcs()) { + World npcWorld = npc.world(); + if (!npcWorld.equals(playerWorld)) { + npc.stopTrackingPlayer(player); + continue; + } + + Position pos = npc.position(); + Set worldChunks = loadedChunks.get(npcWorld); + if (worldChunks == null || !worldChunks.contains(chunkKey(pos.chunkX(), pos.chunkZ()))) { + npc.stopTrackingPlayer(player); + continue; + } double distX = playerLoc.getX() - pos.x(); double distY = playerLoc.getY() - pos.y(); @@ -200,32 +221,8 @@ public void onChunkUnload(ChunkUnloadEvent event) { Set chunks = loadedChunks.get(world); if (chunks != null) { chunks.remove(chunkKey(event.getChunk().getX(), event.getChunk().getZ())); - if (chunks.isEmpty()) loadedChunks.remove(world); - } - } - - @EventHandler - public void onChunkLoad(ChunkLoadEvent event) { - World world = event.getWorld(); - Chunk chunk = event.getChunk(); - - // Add the chunk to the cache - loadedChunks - .computeIfAbsent(world, w -> new HashSet<>()) - .add(chunk.getChunkKey()); - } - - @EventHandler - public void onChunkUnload(ChunkUnloadEvent event) { - World world = event.getWorld(); - Chunk chunk = event.getChunk(); - - // Remove the chunk from the cache - Set chunks = loadedChunks.get(world); - if (chunks != null) { - chunks.remove(chunk.getChunkKey()); if (chunks.isEmpty()) { - loadedChunks.remove(world); // Clean up if no chunks are left + loadedChunks.remove(world); } } } @@ -249,8 +246,13 @@ public void handleQuit(@NotNull PlayerQuitEvent event) { npcTracker.trackedNpcs().forEach(npc -> npc.stopTrackingPlayer(player)); } - private String chunkKey(int chunkX, int chunkZ) { - return chunkX + "," + chunkZ; + // Static factory method for builder + public static @NotNull NpcActionController.Builder actionControllerBuilder( + @NotNull Plugin plugin, + @NotNull NpcEventManager eventManager, + @NotNull PlatformVersionAccessor versionAccessor, + @NotNull NpcTracker npcTracker) { + return new BukkitActionControllerBuilder(plugin, eventManager, versionAccessor, npcTracker); } private static final class BukkitActionControllerBuilder From 5aa635dae456e824571d5c553a0c1423964aec78 Mon Sep 17 00:00:00 2001 From: Batuhan <103301275+WhiteProject1@users.noreply.github.com> Date: Wed, 19 Feb 2025 03:17:56 +0300 Subject: [PATCH 7/7] Delete bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java --- .../npclib/bukkit/BukkitActionController.java | 293 ------------------ 1 file changed, 293 deletions(-) delete mode 100644 bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java diff --git a/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java b/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java deleted file mode 100644 index a23de5a..0000000 --- a/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * This file is part of npc-lib, licensed under the MIT License (MIT). - * - * Copyright (c) 2022-2023 Julian M., Pasqual K. and contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/* - * This file is part of npc-lib, licensed under the MIT License (MIT). - * - * Copyright (c) 2022-2023 Julian M., Pasqual K. and contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.github.juliarn.npclib.bukkit; - -import static com.comphenix.protocol.ProtocolLibrary.getPlugin; - -import com.github.juliarn.npclib.api.Npc; -import com.github.juliarn.npclib.api.NpcActionController; -import com.github.juliarn.npclib.api.NpcTracker; -import com.github.juliarn.npclib.api.PlatformVersionAccessor; -import com.github.juliarn.npclib.api.Position; -import com.github.juliarn.npclib.api.event.ShowNpcEvent; -import com.github.juliarn.npclib.api.event.manager.NpcEventManager; -import com.github.juliarn.npclib.api.flag.NpcFlag; -import com.github.juliarn.npclib.api.protocol.meta.EntityMetadataFactory; -import com.github.juliarn.npclib.common.CommonNpcActionController; -import com.github.juliarn.npclib.common.flag.CommonNpcFlaggedBuilder; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import org.bukkit.Bukkit; -import org.bukkit.Chunk; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.player.PlayerToggleSneakEvent; -import org.bukkit.event.world.ChunkLoadEvent; -import org.bukkit.event.world.ChunkUnloadEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.plugin.Plugin; -import org.jetbrains.annotations.NotNull; - -public final class BukkitActionController extends CommonNpcActionController implements Listener { - - private final Plugin plugin; - private final NpcTracker npcTracker; - private final Map> loadedChunks = new ConcurrentHashMap<>(); - private final Map playerCooldowns = new ConcurrentHashMap<>(); - private final NpcEventManager eventManager; - private final PlatformVersionAccessor versionAccessor; - - private static final int COOLDOWN_TICKS = 10; - private static final long COOLDOWN_MILLIS = COOLDOWN_TICKS * 50L; - private static final double MOVEMENT_THRESHOLD = 0.5; - private final int spawnDistance; - - public BukkitActionController( - @NotNull Map, Optional> flags, - @NotNull Plugin plugin, - @NotNull NpcEventManager eventManager, - @NotNull PlatformVersionAccessor versionAccessor, - @NotNull NpcTracker npcTracker) { - super(flags); - this.plugin = plugin; - this.npcTracker = npcTracker; - this.eventManager = eventManager; - this.versionAccessor = versionAccessor; - this.spawnDistance = 50; - scheduleCleanup(); - } - - private void scheduleCleanup() { - Bukkit.getScheduler().runTaskTimer(plugin, () -> { - long currentTime = System.currentTimeMillis(); - playerCooldowns.entrySet().removeIf(entry -> - currentTime - entry.getValue() > COOLDOWN_MILLIS * 2); - }, 6000L, 6000L); - } - - private long chunkKey(int x, int z) { - return ((long) x << 32) | (z & 0xFFFFFFFFL); - } - - @EventHandler - public void handleMove(@NotNull PlayerMoveEvent event) { - Location from = event.getFrom(); - Location to = event.getTo(); - if (to == null) return; - - double dx = to.getX() - from.getX(); - double dy = to.getY() - from.getY(); - double dz = to.getZ() - from.getZ(); - - if (Math.abs(dx) <= MOVEMENT_THRESHOLD && - Math.abs(dy) <= MOVEMENT_THRESHOLD && - Math.abs(dz) <= MOVEMENT_THRESHOLD) return; - - Player player = event.getPlayer(); - UUID playerId = player.getUniqueId(); - long currentTime = System.currentTimeMillis(); - - Long lastProcessed = playerCooldowns.get(playerId); - if (lastProcessed != null && currentTime - lastProcessed < COOLDOWN_MILLIS) return; - playerCooldowns.put(playerId, currentTime); - - World playerWorld = player.getWorld(); - Location playerLoc = player.getLocation(); - - for (Npc npc : npcTracker.trackedNpcs()) { - World npcWorld = npc.world(); - if (!npcWorld.equals(playerWorld)) { - npc.stopTrackingPlayer(player); - continue; - } - - Position pos = npc.position(); - Set worldChunks = loadedChunks.get(npcWorld); - if (worldChunks == null || !worldChunks.contains(chunkKey(pos.chunkX(), pos.chunkZ()))) { - npc.stopTrackingPlayer(player); - continue; - } - - double distX = playerLoc.getX() - pos.x(); - double distY = playerLoc.getY() - pos.y(); - double distZ = playerLoc.getZ() - pos.z(); - double distanceSquared = distX * distX + distY * distY + distZ * distZ; - - if (npc.trackedPlayers().contains(player)) { - if (distanceSquared > spawnDistance * spawnDistance) { - npc.stopTrackingPlayer(player); - } - } else if (distanceSquared <= spawnDistance * spawnDistance) { - ShowNpcEvent.Pre showEvent = new ShowNpcEvent.Pre() { - private boolean isCancelled = false; - - @Override - public boolean cancelled() { - return isCancelled; - } - - @Override - public void cancelled(boolean cancelled) { - this.isCancelled = cancelled; - } - - @Override - @SuppressWarnings("unchecked") - public

P player() { - return (P) player; - } - - @Override - public Npc npc() { - return npc; - } - }; - - eventManager.post(showEvent); - - if (!showEvent.cancelled()) { - npc.trackPlayer(player); - } - } - } - } - - @EventHandler - public void onChunkLoad(ChunkLoadEvent event) { - World world = event.getWorld(); - Chunk chunk = event.getChunk(); - loadedChunks.computeIfAbsent(world, k -> ConcurrentHashMap.newKeySet()) - .add(chunkKey(chunk.getX(), chunk.getZ())); - } - - @EventHandler - public void onChunkUnload(ChunkUnloadEvent event) { - World world = event.getWorld(); - Set chunks = loadedChunks.get(world); - if (chunks != null) { - chunks.remove(chunkKey(event.getChunk().getX(), event.getChunk().getZ())); - if (chunks.isEmpty()) { - loadedChunks.remove(world); - } - } - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void handleSneak(@NotNull PlayerToggleSneakEvent event) { - Player player = event.getPlayer(); - boolean isSneaking = event.isSneaking(); - - npcTracker.trackedNpcs().stream() - .filter(npc -> npc.trackedPlayers().contains(player)) - .forEach(npc -> { - npc.changeMetadata(EntityMetadataFactory.sneakingMetaFactory(), isSneaking); - }); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void handleQuit(@NotNull PlayerQuitEvent event) { - Player player = event.getPlayer(); - playerCooldowns.remove(player.getUniqueId()); - npcTracker.trackedNpcs().forEach(npc -> npc.stopTrackingPlayer(player)); - } - - // Static factory method for builder - public static @NotNull NpcActionController.Builder actionControllerBuilder( - @NotNull Plugin plugin, - @NotNull NpcEventManager eventManager, - @NotNull PlatformVersionAccessor versionAccessor, - @NotNull NpcTracker npcTracker) { - return new BukkitActionControllerBuilder(plugin, eventManager, versionAccessor, npcTracker); - } - - private static final class BukkitActionControllerBuilder - extends CommonNpcFlaggedBuilder - implements NpcActionController.Builder { - - private final Plugin plugin; - private final NpcEventManager eventManager; - private final PlatformVersionAccessor versionAccessor; - private final NpcTracker npcTracker; - - public BukkitActionControllerBuilder( - @NotNull Plugin plugin, - @NotNull NpcEventManager eventManager, - @NotNull PlatformVersionAccessor versionAccessor, - @NotNull NpcTracker npcTracker) { - Objects.requireNonNull(plugin, "plugin"); - Objects.requireNonNull(eventManager, "eventManager"); - Objects.requireNonNull(versionAccessor, "versionAccessor"); - Objects.requireNonNull(npcTracker, "npcTracker"); - - this.plugin = plugin; - this.eventManager = eventManager; - this.versionAccessor = versionAccessor; - this.npcTracker = npcTracker; - } - - @Override - public @NotNull NpcActionController build() { - return new BukkitActionController( - (Map, Optional>) this.flags, - this.plugin, - this.eventManager, - this.versionAccessor, - this.npcTracker); - } - } -}