Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -52,13 +57,18 @@
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 NpcTracker<World, Player, ItemStack, Plugin> npcTracker;
private final Map<World, Set<Long>> loadedChunks = new HashMap<>();
private final Map<UUID, Long> playerCooldowns = new HashMap<>();
private static final int COOLDOWN_TICKS = 10;

// based on the given flags
private final int spawnDistance;
Expand Down Expand Up @@ -135,14 +145,32 @@ 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<World, Player, ItemStack, Plugin> 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())) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In all honestly, I think a better call here would be for NPCs to store state as to whether they are in a loaded chunk or not, rather than rely on every single small player movement to check if they are in a loaded chunk.

World npcWorld = npc.world();

// Use cached chunk data to check if the chunk is loaded
Set<Long> 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;
Expand Down Expand Up @@ -170,6 +198,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(chunk.getChunkKey());
}

@EventHandler
public void onChunkUnload(ChunkUnloadEvent event) {
World world = event.getWorld();
Chunk chunk = event.getChunk();

// Remove the chunk from the cache
Set<Long> chunks = loadedChunks.get(world);
if (chunks != null) {
chunks.remove(chunk.getChunkKey());
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();
Expand Down Expand Up @@ -216,6 +270,10 @@ public void handleQuit(@NotNull PlayerQuitEvent event) {
}
}

private String chunkKey(int chunkX, int chunkZ) {
return chunkX + "," + chunkZ;
}

private static final class BukkitActionControllerBuilder
extends CommonNpcFlaggedBuilder<NpcActionController.Builder>
implements NpcActionController.Builder {
Expand Down