org.avaje
@@ -102,7 +102,7 @@
org.projectlombok
lombok
- 1.18.20
+ 1.18.22
provided
@@ -136,8 +136,6 @@
3.1
-
- eclipse
true
diff --git a/TacoSpigot-API/src/main/java/net/techcable/tacospigot/event/entity/ChunkSnapshot.java b/TacoSpigot-API/src/main/java/net/techcable/tacospigot/event/entity/ChunkSnapshot.java
new file mode 100644
index 0000000..4d947dc
--- /dev/null
+++ b/TacoSpigot-API/src/main/java/net/techcable/tacospigot/event/entity/ChunkSnapshot.java
@@ -0,0 +1,5 @@
+package net.techcable.tacospigot.event.entity;
+
+public interface ChunkSnapshot {
+
+}
diff --git a/TacoSpigot-API/src/main/java/org/bukkit/Chunk.java b/TacoSpigot-API/src/main/java/org/bukkit/Chunk.java
index 0510151..436a518 100644
--- a/TacoSpigot-API/src/main/java/org/bukkit/Chunk.java
+++ b/TacoSpigot-API/src/main/java/org/bukkit/Chunk.java
@@ -121,4 +121,8 @@ public interface Chunk {
* @return true if the chunk has unloaded successfully, otherwise false
*/
boolean unload();
+
+ net.techcable.tacospigot.event.entity.ChunkSnapshot takeSnapshot();
+
+ void restoreSnapshot(net.techcable.tacospigot.event.entity.ChunkSnapshot snapshot);
}
diff --git a/TacoSpigot-API/src/main/java/org/bukkit/World.java b/TacoSpigot-API/src/main/java/org/bukkit/World.java
index 325d65a..a12daa1 100644
--- a/TacoSpigot-API/src/main/java/org/bukkit/World.java
+++ b/TacoSpigot-API/src/main/java/org/bukkit/World.java
@@ -494,6 +494,23 @@ public interface World extends PluginMessageRecipient, Metadatable {
*/
public boolean setSpawnLocation(int x, int y, int z);
+ /**
+ * Sets the spawn location of the world
+ *
+ * @param x X coordinate
+ * @param y Y coordinate
+ * @param z Z coordinate
+ * @return True if it was successfully set.
+ */
+ public boolean setSpawnLocation(double x, double y, double z, float yaw, float pitch);
+ /**
+ * Sets the spawn location of the world
+ *
+ * @param location Location of the spawn
+ * @return True if it was successfully set.
+ */
+ public boolean setSpawnLocation(Location location);
+
/**
* Gets the relative in-game time of this world.
*
diff --git a/TacoSpigot-Server/pom.xml b/TacoSpigot-Server/pom.xml
index 614566c..d771e17 100644
--- a/TacoSpigot-Server/pom.xml
+++ b/TacoSpigot-Server/pom.xml
@@ -29,6 +29,12 @@
+
+ org.projectlombok
+ lombok
+ 1.18.22
+ provided
+
io.netty
netty-all
@@ -105,18 +111,23 @@
mockito-core
1.10.19
+
+ it.unimi.dsi
+ fastutil
+ 8.1.0
+
+
+ net.jafama
+ jafama
+ 2.3.2
+ compile
+
org.hamcrest
hamcrest-library
1.3
test
-
- org.projectlombok
- lombok
- 1.18.20
- provided
-
com.velocitypowered
velocity-native
@@ -228,31 +239,29 @@
-
- net.md-5
- specialsource-maven-plugin
- 1.2.1
-
-
- package
-
- remap
-
-
- ${project.basedir}/deprecation-mappings.csrg
- ${project.basedir}/deprecation-mappings.at
-
-
-
-
+
org.apache.maven.plugins
maven-compiler-plugin
3.8.1
-
- eclipse
true
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/chunk/ChunkSectionSnapshot.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/chunk/ChunkSectionSnapshot.java
new file mode 100644
index 0000000..56a367b
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/chunk/ChunkSectionSnapshot.java
@@ -0,0 +1,49 @@
+package com.elevatemc.spigot.chunk;
+
+
+import net.minecraft.server.NibbleArray;
+
+public class ChunkSectionSnapshot {
+
+ private final int nonEmptyBlockCount;
+ private final int tickingBlockCount;
+ private final char[] blockIds;
+ private final NibbleArray emittedLight;
+ private final NibbleArray skyLight;
+
+ public ChunkSectionSnapshot(
+ int nonEmptyBlockCount,
+ int tickingBlockCount,
+ char[] blockIds,
+ NibbleArray emittedLight,
+ NibbleArray skyLight
+ ) {
+ this.nonEmptyBlockCount = nonEmptyBlockCount;
+ this.tickingBlockCount = tickingBlockCount;
+ this.blockIds = blockIds;
+ this.emittedLight = emittedLight;
+ this.skyLight = skyLight;
+ }
+
+ public final int getNonEmptyBlockCount() {
+ return nonEmptyBlockCount;
+ }
+
+ public final int getTickingBlockCount() {
+ return tickingBlockCount;
+ }
+
+ public final char[] getBlockIds() {
+ return blockIds;
+ }
+
+ public final NibbleArray getEmittedLight() {
+ return emittedLight;
+ }
+
+ public final NibbleArray getSkyLight() {
+ return skyLight;
+ }
+
+}
+
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/chunk/CraftChunkSnapshot.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/chunk/CraftChunkSnapshot.java
new file mode 100644
index 0000000..7793bf1
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/chunk/CraftChunkSnapshot.java
@@ -0,0 +1,22 @@
+package com.elevatemc.spigot.chunk;
+
+import net.minecraft.server.NBTTagCompound;
+import net.techcable.tacospigot.event.entity.ChunkSnapshot;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CraftChunkSnapshot implements ChunkSnapshot {
+
+ private final ChunkSectionSnapshot[] sections = new ChunkSectionSnapshot[16];
+ private final List tileEntities = new ArrayList<>();
+
+ public ChunkSectionSnapshot[] getSections() {
+ return this.sections;
+ }
+
+ public List getTileEntities() {
+ return this.tileEntities;
+ }
+}
+
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/chunk/WeakChunkCache.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/chunk/WeakChunkCache.java
new file mode 100644
index 0000000..9a8ae28
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/chunk/WeakChunkCache.java
@@ -0,0 +1,119 @@
+package com.elevatemc.spigot.chunk;
+
+import net.minecraft.server.*;
+
+public class WeakChunkCache implements IBlockAccess {
+
+ private final int lowerChunkX;
+ private final int lowerChunkZ;
+ private final Chunk[][] chunks;
+ private boolean ownArray = false;
+
+ public WeakChunkCache(Chunk[][] chunks, int lowerChunkX, int lowerChunkZ) {
+ this.chunks = chunks;
+ this.lowerChunkX = lowerChunkX;
+ this.lowerChunkZ = lowerChunkZ;
+ }
+
+ public WeakChunkCache(World world, int chunkX, int chunkZ, int chunkRadius) {
+ this(world, chunkX - chunkRadius, chunkZ - chunkRadius, chunkX + chunkRadius, chunkZ + chunkRadius);
+ }
+
+ public WeakChunkCache(World world, int lowerChunkX, int lowerChunkZ, int upperChunkX, int upperChunkZ) {
+ this.lowerChunkX = lowerChunkX;
+ this.lowerChunkZ = lowerChunkZ;
+
+ this.chunks = new Chunk[upperChunkX - this.lowerChunkX + 1][upperChunkZ - this.lowerChunkZ + 1];
+ this.ownArray = true;
+
+ for (int chunkX = this.lowerChunkX; chunkX <= upperChunkX; ++chunkX) {
+ for (int chunkZ = this.lowerChunkZ; chunkZ <= upperChunkZ; ++chunkZ) {
+ this.chunks[chunkX - this.lowerChunkX][chunkZ - this.lowerChunkZ] = world.getChunkIfLoaded(chunkX, chunkZ);
+ }
+ }
+ }
+
+ public WeakChunkCache(World world, int lowerBlockX, int lowerBlockY, int lowerBlockZ, int upperBlockX, int upperBlockY, int upperBlockZ, int blockRadius) {
+ this(world, (lowerBlockX - blockRadius) >> 4,
+ (lowerBlockZ - blockRadius) >> 4,
+ (upperBlockX + blockRadius) >> 4,
+ (upperBlockZ + blockRadius) >> 4);
+ }
+
+ public IBlockData getData(BlockPosition blockPosition) {
+ if (blockPosition.getY() >= 0 && blockPosition.getY() < 256) {
+ int indexX = (blockPosition.getX() >> 4) - this.lowerChunkX;
+ int indexZ = (blockPosition.getZ() >> 4) - this.lowerChunkZ;
+ if (indexX >= 0 && indexX < this.chunks.length && indexZ >= 0 && indexZ < this.chunks[indexX].length) {
+ Chunk chunk = this.chunks[indexX][indexZ];
+ if (chunk != null) {
+ return chunk.getBlockData(blockPosition);
+ }
+ }
+ }
+ return null;
+ }
+
+ public boolean isLoaded(int x, int y, int z) {
+ int indexX = (x >> 4) - this.lowerChunkX;
+ int indexZ = (z >> 4) - this.lowerChunkZ;
+ if (indexX >= 0 && indexX < this.chunks.length && indexZ >= 0 && indexZ < this.chunks[indexX].length) {
+ return this.chunks[indexX][indexZ] != null;
+ }
+ return false;
+ }
+
+ public void clear() {
+ if(this.ownArray) {
+ for(int i = 0; i < this.chunks.length; i++) {
+ for(int j = 0; j < this.chunks[i].length; j++) {
+ this.chunks[i][j] = null;
+ }
+ }
+ }
+ }
+
+ @Override
+ public TileEntity getTileEntity(BlockPosition blockPosition) {
+ int indexX = (blockPosition.getX() >> 4) - this.lowerChunkX;
+ int indexZ = (blockPosition.getZ() >> 4) - this.lowerChunkZ;
+
+ Chunk chunk = this.chunks[indexX][indexZ];
+ if (chunk != null) {
+ return chunk.i(blockPosition);
+ }
+
+ return null;
+ }
+
+ @Override
+ public IBlockData getType(BlockPosition blockPosition) {
+ Block block = Blocks.AIR;
+
+ if (blockPosition.getY() >= 0 && blockPosition.getY() < 256) {
+ int indexX = (blockPosition.getX() >> 4) - this.lowerChunkX;
+ int indexZ = (blockPosition.getZ() >> 4) - this.lowerChunkZ;
+
+ if (indexX >= 0 && indexX < this.chunks.length && indexZ >= 0 && indexZ < this.chunks[indexX].length) {
+ Chunk chunk = this.chunks[indexX][indexZ];
+ if (chunk != null) {
+ block = chunk.getType(blockPosition);
+ }
+ }
+ }
+
+ return block.getBlockData();
+ }
+
+ @Override
+ public boolean isEmpty(BlockPosition blockPosition) {
+ return getType(blockPosition) != Blocks.AIR.getBlockData();
+ }
+
+ @Override
+ public int getBlockPower(BlockPosition blockposition, EnumDirection enumdirection) {
+ IBlockData iblockdata = this.getType(blockposition);
+ return iblockdata.getBlock().b(this, blockposition, iblockdata, enumdirection);
+ }
+
+}
\ No newline at end of file
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/command/TicksPerSecondCommand.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/command/TicksPerSecondCommand.java
index b3f50ed..59b0576 100644
--- a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/command/TicksPerSecondCommand.java
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/command/TicksPerSecondCommand.java
@@ -16,6 +16,7 @@ import java.lang.management.ManagementFactory;
import java.math.RoundingMode;
import java.text.DecimalFormat;
+
public class TicksPerSecondCommand extends Command {
public TicksPerSecondCommand() {
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/eSpigot.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/eSpigot.java
index 555ec1f..8d799b7 100644
--- a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/eSpigot.java
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/eSpigot.java
@@ -2,18 +2,26 @@ package com.elevatemc.spigot;
import com.elevatemc.spigot.command.KnockbackCommand;
import com.elevatemc.spigot.command.TicksPerSecondCommand;
+import com.elevatemc.spigot.handler.MovementHandler;
+import com.elevatemc.spigot.handler.PacketHandler;
import com.elevatemc.spigot.util.YamlConfig;
import net.minecraft.server.MinecraftServer;
+import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import com.elevatemc.spigot.knockback.KnockbackHandler;
import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class eSpigot {
+
+ private final Set packetHandlers = new HashSet<>();
+ private final Set movementHandlers = new HashSet<>();
public static final ScheduledExecutorService EXECUTOR_SERVICE =
Executors.newSingleThreadScheduledExecutor();
@@ -45,6 +53,25 @@ public class eSpigot {
EXECUTOR_SERVICE.scheduleAtFixedRate(() -> MinecraftServer.getServer().aq().processFastPackets(), 5L, 5L, TimeUnit.MILLISECONDS);
}
+ public void addPacketHandler(PacketHandler handler) {
+ Bukkit.getLogger().info("Adding packet handler: " + handler.getClass().getPackage().getName() + "." + handler.getClass().getSimpleName());
+ this.packetHandlers.add(handler);
+ }
+
+ public void addMovementHandler(MovementHandler handler) {
+ Bukkit.getLogger().info("Adding movement handler: " + handler.getClass().getPackage().getName() + "." + handler.getClass().getSimpleName());
+ this.movementHandlers.add(handler);
+ }
+
+ public Set getMovementHandlers() {
+ return this.movementHandlers;
+ }
+
+ public Set getPacketHandlers() {
+ return this.packetHandlers;
+ }
+
+
public YamlConfig getConfig() {
return config;
}
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/event/BlockDropItemsEvent.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/event/BlockDropItemsEvent.java
new file mode 100644
index 0000000..feb3c76
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/event/BlockDropItemsEvent.java
@@ -0,0 +1,56 @@
+package com.elevatemc.spigot.event;
+
+import org.bukkit.block.Block;
+import org.bukkit.entity.Item;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Cancellable;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+
+import java.util.List;
+
+public class BlockDropItemsEvent extends Event implements Cancellable {
+
+ private static final HandlerList handlers = new HandlerList();
+ private Block block;
+ private Player player;
+ private List- toDrop;
+ private boolean cancelled = false;
+
+ public BlockDropItemsEvent(Block block, Player player, List
- toDrop) {
+ this.block = block;
+ this.player = player;
+ this.toDrop = toDrop;
+ }
+
+ public Block getBlock() {
+ return this.block;
+ }
+
+ public Player getPlayer() {
+ return this.player;
+ }
+
+ public List
- getToDrop() {
+ return this.toDrop;
+ }
+
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return this.cancelled;
+ }
+
+ @Override
+ public void setCancelled(boolean cancel) {
+ this.cancelled = cancel;
+ }
+}
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/event/PlayerHealthChangeEvent.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/event/PlayerHealthChangeEvent.java
new file mode 100644
index 0000000..39faa33
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/event/PlayerHealthChangeEvent.java
@@ -0,0 +1,38 @@
+package com.elevatemc.spigot.event;
+
+import org.bukkit.entity.Player;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.player.PlayerEvent;
+
+public class PlayerHealthChangeEvent extends PlayerEvent {
+
+ private static final HandlerList handlers = new HandlerList();
+
+ private final double previousHealth;
+ private final double newHealth;
+
+ public PlayerHealthChangeEvent(Player who, double previousHealth, double newHealth) {
+ super(who);
+
+ this.previousHealth = previousHealth;
+ this.newHealth = newHealth;
+ }
+
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
+ public double getPreviousHealth() {
+ return previousHealth;
+ }
+
+ public double getNewHealth() {
+ return newHealth;
+ }
+}
+
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/event/PlayerPearlRefundEvent.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/event/PlayerPearlRefundEvent.java
new file mode 100644
index 0000000..d11afc0
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/event/PlayerPearlRefundEvent.java
@@ -0,0 +1,22 @@
+package com.elevatemc.spigot.event;
+
+import org.bukkit.entity.Player;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.player.PlayerEvent;
+
+public class PlayerPearlRefundEvent extends PlayerEvent {
+ private static final HandlerList handlers = new HandlerList();
+
+ public PlayerPearlRefundEvent(final Player player) {
+ super(player);
+ }
+
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+}
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/handler/MovementHandler.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/handler/MovementHandler.java
new file mode 100644
index 0000000..f0c7374
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/handler/MovementHandler.java
@@ -0,0 +1,13 @@
+package com.elevatemc.spigot.handler;
+
+import net.minecraft.server.PacketPlayInFlying;
+import org.bukkit.Location;
+import org.bukkit.entity.Player;
+
+public interface MovementHandler {
+
+ void handleUpdateLocation(Player player, Location to, Location from, PacketPlayInFlying packet);
+
+ void handleUpdateRotation(Player player, Location to, Location from, PacketPlayInFlying packet);
+
+}
\ No newline at end of file
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/handler/PacketHandler.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/handler/PacketHandler.java
new file mode 100644
index 0000000..4775d68
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/handler/PacketHandler.java
@@ -0,0 +1,17 @@
+package com.elevatemc.spigot.handler;
+
+import net.minecraft.server.Packet;
+import net.minecraft.server.PlayerConnection;
+
+public interface PacketHandler {
+
+ default void handleReceivedPacket(PlayerConnection connection, Packet> packet) {}
+
+ default void handleSentPacket(PlayerConnection connection, Packet> packet) {}
+
+ default boolean handleSentPacketCancellable(PlayerConnection connection, Packet> packet) {
+ return true;
+ }
+
+}
+
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/AsyncNavigation.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/AsyncNavigation.java
new file mode 100644
index 0000000..08c28af
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/AsyncNavigation.java
@@ -0,0 +1,200 @@
+package com.elevatemc.spigot.pathsearch;
+
+import com.elevatemc.spigot.pathsearch.cache.SearchCacheEntry;
+import com.elevatemc.spigot.pathsearch.cache.SearchCacheEntryEntity;
+import com.elevatemc.spigot.pathsearch.cache.SearchCacheEntryPosition;
+import com.elevatemc.spigot.pathsearch.jobs.PathSearchJob;
+import com.elevatemc.spigot.pathsearch.jobs.PathSearchJobNavigationEntity;
+import com.elevatemc.spigot.pathsearch.jobs.PathSearchJobNavigationPosition;
+import com.elevatemc.spigot.pathsearch.jobs.PathSearchQueuingManager;
+import net.minecraft.server.*;
+import org.bukkit.util.BlockVector;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.UUID;
+
+public class AsyncNavigation extends Navigation {
+
+ private HashMap searchCache;
+ private HashMap positionSearchCache;
+ private static double minimumDistanceForOffloadingSquared = 0.0D;
+ private int cleanUpDelay = 0;
+ private PathSearchQueuingManager queuingManager;
+
+ public AsyncNavigation(EntityInsentient entityInsentient, World world) {
+ super(entityInsentient, world);
+
+ this.searchCache = new HashMap<>();
+ this.positionSearchCache = new HashMap<>();
+ this.queuingManager = new PathSearchQueuingManager();
+ }
+
+ private BlockVector createBlockVectorForPosition(int x, int y, int z) {
+ return new BlockVector(x, y, z);
+ }
+
+ private void issueSearch(Entity target, float range, boolean j, boolean k, boolean l, boolean m) {
+ this.queuingManager.queueSearch(new PathSearchJobNavigationEntity(this.b, target, range, j, k, l, m));
+ }
+
+ private void issueSearch(PositionPathSearchType type, BlockPosition blockPosition, float range, boolean j, boolean k, boolean l, boolean m) {
+ this.queuingManager.queueSearch(new PathSearchJobNavigationPosition(type, this.b, blockPosition, range, j, k, l, m));
+ }
+
+ @Override
+ public void cancelSearch(PathSearchJob pathSearch) {
+ this.queuingManager.checkLastSearchResult(pathSearch);
+ pathSearch.cleanup();
+ }
+
+ @Override
+ public void setSearchResult(PathSearchJobNavigationEntity pathSearch) {
+ this.queuingManager.checkLastSearchResult(pathSearch);
+ SearchCacheEntry entry = pathSearch.getCacheEntryValue();
+ if (entry != null && entry.didSearchSucceed()) {
+ synchronized (this.searchCache) {
+ UUID key = pathSearch.getCacheEntryKey();
+ this.searchCache.put(key, entry);
+ }
+ }
+ }
+
+ @Override
+ public void setSearchResult(PathSearchJobNavigationPosition pathSearch) {
+ this.queuingManager.checkLastSearchResult(pathSearch);
+ SearchCacheEntryPosition entry = pathSearch.getCacheEntryValue();
+ if (entry != null && entry.didSearchSucceed()) {
+ synchronized (this.positionSearchCache) {
+ PositionPathSearchType key = pathSearch.getCacheEntryKey();
+ this.positionSearchCache.put(key, entry);
+ }
+ }
+ }
+
+ @Override
+ public PathEntity a(BlockPosition blockPosition) {
+ return this.a(PositionPathSearchType.ANYOTHER, blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
+ }
+
+ @Override
+ public boolean a(PositionPathSearchType type, double d0, double d1, double d2, double d3) {
+ PathEntity pathentity = this.a(type, (double) MathHelper.floor(d0), (double) ((int) d1), (double) MathHelper.floor(d2));
+ return this.a(pathentity, d3);
+ }
+
+ @Override
+ public PathEntity a(Entity entity) {
+ if (!this.offloadSearches() || this.b.h(entity) < minimumDistanceForOffloadingSquared) {
+ return super.a(entity);
+ }
+
+ if (!this.b()) {
+ return null;
+ }
+
+ SearchCacheEntry entry = null;
+ UUID id = entity.getUniqueID();
+ synchronized (this.searchCache) {
+ if (this.searchCache.containsKey(id)) {
+ entry = this.searchCache.get(id);
+ }
+ }
+
+ PathEntity resultPath = null;
+ if (entry != null) {
+ resultPath = entry.getAdjustedPathEntity();
+ if (!entry.isStillValid()) {
+ this.issueSearch(entity, this.i(), this.a.getB(), this.a.getC(), this.a.getD(), this.a.getE());
+ }
+ }
+ if (entry == null && !this.queuingManager.hasAsyncSearchIssued()) {
+ resultPath = super.a(entity);
+ if (resultPath != null) {
+ entry = new SearchCacheEntryEntity(this.b, entity, resultPath);
+ synchronized (this.searchCache) {
+ SearchCacheEntry oldEntry = this.searchCache.put(id, entry);
+ if (oldEntry != null) {
+ oldEntry.cleanup();
+ }
+ }
+ }
+ }
+ return resultPath;
+ }
+
+ @Override
+ public PathEntity a(PositionPathSearchType type, double d0, double d1, double d2) {
+ if (!this.offloadSearches() || this.b.e(d0, d1, d2) < minimumDistanceForOffloadingSquared) {
+ return super.a(d0, d1, d2);
+ }
+
+ if (!this.b()) {
+ return null;
+ }
+
+ int x = MathHelper.floor(d0);
+ int y = (int) d1;
+ int z = MathHelper.floor(d2);
+
+ SearchCacheEntryPosition entry = null;
+ synchronized (this.positionSearchCache) {
+ if (this.positionSearchCache.containsKey(type)) {
+ entry = this.positionSearchCache.get(type);
+ }
+ }
+
+ PathEntity resultPath = null;
+ if (entry != null) {
+ resultPath = entry.getAdjustedPathEntity();
+ if (!entry.isStillValid()) {
+ this.issueSearch(type, new BlockPosition(x, y, z), this.i(), this.a.getB(), this.a.getC(), this.a.getD(), this.a.getE());
+ }
+ }
+
+ if (entry == null && !this.queuingManager.hasAsyncSearchIssued()) {
+ resultPath = super.a(d0, d1, d2);
+ if (resultPath != null) {
+ entry = new SearchCacheEntryPosition(this.b, x, y, z, resultPath);
+ synchronized (this.positionSearchCache) {
+ SearchCacheEntry oldEntry = this.positionSearchCache.put(type, entry);
+ if (oldEntry != null) {
+ oldEntry.cleanup();
+ }
+ }
+ }
+ }
+ return resultPath;
+ }
+
+ @Override
+ public void cleanUpExpiredSearches() {
+ if (++this.cleanUpDelay > 100) {
+ this.cleanUpDelay = 0;
+ synchronized (this.searchCache) {
+ Iterator> iter = this.searchCache.entrySet().iterator();
+ while (iter.hasNext()) {
+ Map.Entry entry = iter.next();
+ if (entry.getValue().hasExpired()) {
+ iter.remove();
+ }
+ }
+ }
+ synchronized (this.positionSearchCache) {
+ Iterator> iter2 = this.positionSearchCache.entrySet().iterator();
+ while (iter2.hasNext()) {
+ Map.Entry entry = iter2.next();
+ if (entry.getValue().hasExpired()) {
+ iter2.remove();
+ }
+ }
+ }
+ }
+ }
+
+ private boolean offloadSearches() {
+ return true;
+ }
+
+}
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/AsyncPathfinder.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/AsyncPathfinder.java
new file mode 100644
index 0000000..c857ab1
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/AsyncPathfinder.java
@@ -0,0 +1,25 @@
+package com.elevatemc.spigot.pathsearch;
+
+import net.minecraft.server.*;
+
+public class AsyncPathfinder extends Pathfinder {
+
+ private IBlockAccess iblockaccess;
+
+ public AsyncPathfinder(IBlockAccess iblockaccess) {
+ super(new AsyncPathfinderNormal(iblockaccess));
+
+ this.iblockaccess = iblockaccess;
+ }
+
+ @Override
+ public PathEntity a(IBlockAccess var1, Entity var2, Entity var3, float var4) {
+ return super.a(iblockaccess, var2, var3, var4);
+ }
+
+ @Override
+ public PathEntity a(IBlockAccess var1, Entity var2, BlockPosition var3, float var4) {
+ return super.a(iblockaccess, var2, var3, var4);
+ }
+
+}
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/AsyncPathfinderNormal.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/AsyncPathfinderNormal.java
new file mode 100644
index 0000000..b14acbf
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/AsyncPathfinderNormal.java
@@ -0,0 +1,20 @@
+package com.elevatemc.spigot.pathsearch;
+
+import net.minecraft.server.Entity;
+import net.minecraft.server.IBlockAccess;
+import net.minecraft.server.PathfinderNormal;
+
+public class AsyncPathfinderNormal extends PathfinderNormal {
+
+ private IBlockAccess iblockaccess;
+
+ public AsyncPathfinderNormal(IBlockAccess iblockaccess) {
+ this.iblockaccess = iblockaccess;
+ }
+
+ @Override
+ public void a(IBlockAccess iblockaccess, Entity entity) {
+ super.a(this.iblockaccess, entity);
+ }
+
+}
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/PathSearchThrottlerThread.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/PathSearchThrottlerThread.java
new file mode 100644
index 0000000..ad2cb8c
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/PathSearchThrottlerThread.java
@@ -0,0 +1,101 @@
+package com.elevatemc.spigot.pathsearch;
+
+import com.elevatemc.spigot.pathsearch.jobs.PathSearchJob;
+import com.elevatemc.spigot.threading.NamePriorityThreadFactory;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.*;
+
+public class PathSearchThrottlerThread extends ThreadPoolExecutor {
+
+ private int queueLimit;
+ private LinkedHashMap filter;
+ private HashSet activeSearchHashes;
+ private static PathSearchThrottlerThread instance;
+
+ public PathSearchThrottlerThread(int poolSize) {
+ super(poolSize, poolSize, 1L, TimeUnit.MINUTES, new LinkedBlockingQueue(), new NamePriorityThreadFactory(Thread.MIN_PRIORITY, "eSpigot_PathFinder"));
+ instance = this;
+ adjustPoolSize(poolSize);
+ this.filter = new LinkedHashMap<>();
+ this.activeSearchHashes = new HashSet<>();
+ }
+
+ public boolean queuePathSearch(PathSearchJob newJob) {
+ boolean jobHasBeenQueued = false;
+ if(newJob != null) {
+ synchronized(this.filter) {
+ if(this.filter.containsKey(newJob) || this.filter.size() < 1000) {
+ jobHasBeenQueued = true;
+ PathSearchJob previousJob = this.filter.put(newJob, newJob);
+ if(previousJob != null) {
+ previousJob.cancel();
+ }
+ }
+ }
+ if(!jobHasBeenQueued) {
+ newJob.cancel();
+ }
+ }
+ PathSearchJob jobToExecute = null;
+ synchronized(this.filter) {
+ Iterator> iter = this.filter.entrySet().iterator();
+ while(iter.hasNext() && this.getQueue().size() < this.queueLimit) {
+ jobToExecute = iter.next().getValue();
+ if(!this.activeSearchHashes.contains(jobToExecute.getSearchHash())) {
+ iter.remove();
+ if(jobToExecute != null) {
+ this.activeSearchHashes.add(jobToExecute.getSearchHash());
+ this.submit(jobToExecute);
+ }
+ if(newJob != null) {
+ break;
+ }
+ }
+ }
+ }
+ return jobHasBeenQueued;
+ }
+
+ @Override
+ public void shutdown() {
+ this.getQueue().clear();
+ super.shutdown();
+ }
+
+ @Override
+ protected void afterExecute(Runnable runnable, Throwable throwable) {
+ super.afterExecute(runnable, throwable);
+ if(runnable instanceof FutureTask) {
+ FutureTask task = (FutureTask) runnable;
+ PathSearchJob job = null;
+ try {
+ job = task.get();
+ } catch (InterruptedException e) {
+ } catch (ExecutionException e) {
+ }
+ if(job != null) {
+ synchronized(this.filter) {
+ this.activeSearchHashes.remove(job.getSearchHash());
+ }
+ }
+ }
+ this.queuePathSearch(null);
+ }
+
+ public static void adjustPoolSize(int size) {
+ if(instance != null) {
+ if(size > instance.getMaximumPoolSize()) {
+ instance.setMaximumPoolSize(size);
+ instance.setCorePoolSize(size);
+ } else if(size < instance.getMaximumPoolSize()) {
+ instance.setCorePoolSize(size);
+ instance.setMaximumPoolSize(size);
+ }
+ instance.queueLimit = size * 8;
+ }
+ }
+}
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/PositionPathSearchType.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/PositionPathSearchType.java
new file mode 100644
index 0000000..e78c211
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/PositionPathSearchType.java
@@ -0,0 +1,18 @@
+package com.elevatemc.spigot.pathsearch;
+
+
+public enum PositionPathSearchType {
+
+ ANYOTHER,
+ AVOIDPLAYER,
+ FLEESUN,
+ JUMPONBLOCK,
+ MOVEINDOORS,
+ MOVETHROUGHVILLAGE,
+ MOVETOWARDSRESTRICTION,
+ MOVETOWARDSTARGET,
+ PANIC,
+ PLAY,
+ RANDOMSTROLL,
+ TAME;
+}
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/cache/SearchCacheEntry.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/cache/SearchCacheEntry.java
new file mode 100644
index 0000000..4843ca5
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/cache/SearchCacheEntry.java
@@ -0,0 +1,78 @@
+package com.elevatemc.spigot.pathsearch.cache;
+
+import net.minecraft.server.*;
+import org.bukkit.util.BlockVector;
+
+public class SearchCacheEntry {
+
+ protected long tick;
+ protected BlockVector positionStart;
+ protected BlockVector positionTarget;
+ protected EntityInsentient entity;
+ private PathEntity path;
+
+ public SearchCacheEntry(EntityInsentient entity, PathEntity path) {
+ this.entity = entity;
+ this.positionStart = this.getEntityPosition(this.entity);
+ this.path = path;
+ this.tick = this.getCurrentTick();
+ }
+
+ protected int getCurrentTick() {
+ return MinecraftServer.currentTick;
+ }
+
+ protected BlockVector getEntityPosition(Entity entity) {
+ return new BlockVector(entity.locX, entity.locY, entity.locZ);
+ }
+
+ protected BlockVector getTargetPosition(int x, int y, int z) {
+ return new BlockVector(x, y, z);
+ }
+
+ public boolean isStillValid() {
+ return false;
+ }
+
+ public PathEntity getPathEntity() {
+ return this.path;
+ }
+
+ public boolean hasExpired() {
+ return !this.entity.isAlive() || (this.getCurrentTick() - this.tick) > 200;
+ }
+
+ public boolean didSearchSucceed() {
+ return this.path != null;
+ }
+
+ public boolean shouldBeRefreshed() {
+ return (this.getCurrentTick() - this.tick) > 5;
+ }
+
+ public PathEntity getAdjustedPathEntity() {
+ if(this.path != null && (this.path.e() < this.path.d() - 1)) {
+ PathPoint pathpoint = this.path.a(this.path.e());
+ double currentDist = this.entity.e(pathpoint.a, pathpoint.b, pathpoint.c);
+ while(this.path.e() < this.path.d() - 1) {
+ pathpoint = this.path.a(this.path.e() + 1);
+ double nextDist = this.entity.e(pathpoint.a, pathpoint.b, pathpoint.c);
+ if(nextDist < currentDist) {
+ currentDist = nextDist;
+ this.path.a();
+ } else {
+ break;
+ }
+ }
+ }
+ return this.path;
+ }
+
+ public void cleanup() {
+ this.positionStart = null;
+ this.positionTarget = null;
+ this.entity = null;
+ this.path = null;
+ }
+
+}
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/cache/SearchCacheEntryEntity.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/cache/SearchCacheEntryEntity.java
new file mode 100644
index 0000000..5fe8ff4
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/cache/SearchCacheEntryEntity.java
@@ -0,0 +1,38 @@
+package com.elevatemc.spigot.pathsearch.cache;
+
+import net.minecraft.server.Entity;
+import net.minecraft.server.EntityInsentient;
+import net.minecraft.server.PathEntity;
+import org.bukkit.util.BlockVector;
+
+public class SearchCacheEntryEntity extends SearchCacheEntry {
+
+ private Entity target;
+
+ public SearchCacheEntryEntity(EntityInsentient entity, Entity target, PathEntity path) {
+ super(entity, path);
+
+ this.target = target;
+ this.positionTarget = this.getEntityPosition(this.target);
+ }
+
+ @Override
+ public boolean isStillValid() {
+ if (this.getCurrentTick() - this.tick > 20) {
+ return false;
+ }
+
+ BlockVector bvStart = this.getEntityPosition(this.entity);
+ BlockVector bvTarget = this.getEntityPosition(this.target);
+
+ return bvStart.equals(this.positionStart) && bvTarget.equals(this.positionTarget);
+ }
+
+ @Override
+ public void cleanup() {
+ super.cleanup();
+
+ this.target = null;
+ }
+
+}
\ No newline at end of file
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/cache/SearchCacheEntryPosition.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/cache/SearchCacheEntryPosition.java
new file mode 100644
index 0000000..33bdbfe
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/cache/SearchCacheEntryPosition.java
@@ -0,0 +1,29 @@
+package com.elevatemc.spigot.pathsearch.cache;
+
+import net.minecraft.server.EntityInsentient;
+import net.minecraft.server.PathEntity;
+import org.bukkit.util.BlockVector;
+
+public class SearchCacheEntryPosition extends SearchCacheEntry {
+
+ public SearchCacheEntryPosition(EntityInsentient entity, int x, int y, int z, PathEntity path) {
+ super(entity, path);
+
+ this.positionTarget = this.getTargetPosition(x, y, z);
+ }
+
+ @Override
+ public boolean isStillValid() {
+ if (this.getCurrentTick() - this.tick > 20) {
+ return false;
+ }
+
+ BlockVector bvStart = this.getEntityPosition(this.entity);
+ return bvStart.equals(this.positionStart);
+ }
+
+ public boolean targetEquals(BlockVector bv) {
+ return this.positionTarget.equals(bv);
+ }
+
+}
\ No newline at end of file
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/jobs/PathSearchJob.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/jobs/PathSearchJob.java
new file mode 100644
index 0000000..6139949
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/jobs/PathSearchJob.java
@@ -0,0 +1,73 @@
+package com.elevatemc.spigot.pathsearch.jobs;
+
+import com.elevatemc.spigot.chunk.WeakChunkCache;
+import com.elevatemc.spigot.pathsearch.cache.SearchCacheEntry;
+import net.minecraft.server.EntityInsentient;
+import net.minecraft.server.MathHelper;
+import net.minecraft.server.PathEntity;
+
+import java.util.concurrent.Callable;
+
+public abstract class PathSearchJob implements Callable {
+
+ protected EntityInsentient entity;
+ protected WeakChunkCache chunkCache;
+ protected boolean issued;
+ protected float range;
+ protected boolean b1, b2, b3, b4;
+ protected PathEntity pathEntity;
+ protected int hash;
+
+ public PathSearchJob(EntityInsentient entity, float range, boolean b1, boolean b2, boolean b3, boolean b4) {
+ this.entity = entity;
+ this.range = range;
+ this.b1 = b1;
+ this.b2 = b2;
+ this.b3 = b3;
+ this.b4 = b4;
+ this.issued = false;
+ this.hash = entity.getUniqueID().hashCode();
+ this.createChunkCache();
+ }
+
+ private void createChunkCache() {
+ int x = MathHelper.floor(this.entity.locX);
+ int y = MathHelper.floor(this.entity.locY);
+ int z = MathHelper.floor(this.entity.locZ);
+ int radius = (int) (this.range + 8.0F);
+ int xMinor = x - radius;
+ int yMinor = y - radius;
+ int zMinor = z - radius;
+ int xMajor = x + radius;
+ int yMajor = y + radius;
+ int zMajor = z + radius;
+ this.chunkCache = new WeakChunkCache(this.entity.world, xMinor, yMinor, zMinor, xMajor, yMajor, zMajor, 0);
+ }
+
+ public void cleanup() {
+ this.entity = null;
+ this.chunkCache = null;
+ this.pathEntity = null;
+ }
+
+ public int getSearchHash() {
+ return this.hash;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.hash;
+ }
+
+ public abstract Object getCacheEntryKey();
+
+ public abstract SearchCacheEntry getCacheEntryValue();
+
+ public abstract void cancel();
+
+ protected boolean isEntityStillValid() {
+ return this.entity != null && this.entity.valid && this.entity.isAlive();
+ }
+
+}
+
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/jobs/PathSearchJobEntity.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/jobs/PathSearchJobEntity.java
new file mode 100644
index 0000000..5e1e62d
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/jobs/PathSearchJobEntity.java
@@ -0,0 +1,61 @@
+package com.elevatemc.spigot.pathsearch.jobs;
+
+import com.elevatemc.spigot.pathsearch.AsyncPathfinder;
+import com.elevatemc.spigot.pathsearch.cache.SearchCacheEntry;
+import com.elevatemc.spigot.pathsearch.cache.SearchCacheEntryEntity;
+import net.minecraft.server.Entity;
+import net.minecraft.server.EntityCreature;
+
+public class PathSearchJobEntity extends PathSearchJob {
+
+ private Entity target;
+
+ public PathSearchJobEntity(EntityCreature entity, Entity target, float range, boolean b1, boolean b2, boolean b3, boolean b4) {
+ super(entity, range, b1, b2, b3, b4);
+
+ this.target = target;
+ }
+
+ @Override
+ public PathSearchJob call() throws Exception {
+ if(!this.isEntityStillValid()) {
+ this.cancel();
+ } else if(!this.issued) {
+ this.issued = true;
+ this.pathEntity = (new AsyncPathfinder(this.chunkCache)).a(this.chunkCache, entity, this.target, this.range);
+ ((EntityCreature) this.entity).setSearchResult(this, this.target, this.pathEntity);
+ this.cleanup();
+ }
+ return this;
+ }
+
+ @Override
+ public void cleanup() {
+ super.cleanup();
+ this.target = null;
+ }
+
+ @Override
+ public Object getCacheEntryKey() {
+ return this.entity.getUniqueID();
+ }
+
+ @Override
+ public SearchCacheEntry getCacheEntryValue() {
+ return new SearchCacheEntryEntity(this.entity, this.target, this.pathEntity);
+ }
+
+ @Override
+ public void cancel() {
+ ((EntityCreature) this.entity).cancelSearch(this);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof PathSearchJobEntity)) {
+ return false;
+ }
+
+ return this.getSearchHash() == ((PathSearchJobEntity)o).getSearchHash();
+ }
+}
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/jobs/PathSearchJobNavigationEntity.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/jobs/PathSearchJobNavigationEntity.java
new file mode 100644
index 0000000..9df435a
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/jobs/PathSearchJobNavigationEntity.java
@@ -0,0 +1,62 @@
+package com.elevatemc.spigot.pathsearch.jobs;
+
+import com.elevatemc.spigot.pathsearch.AsyncPathfinder;
+import com.elevatemc.spigot.pathsearch.cache.SearchCacheEntry;
+import com.elevatemc.spigot.pathsearch.cache.SearchCacheEntryEntity;
+import net.minecraft.server.Entity;
+import net.minecraft.server.EntityInsentient;
+
+import java.util.UUID;
+
+public class PathSearchJobNavigationEntity extends PathSearchJob {
+
+ private Entity target;
+
+ public PathSearchJobNavigationEntity(EntityInsentient entity, Entity target, float range, boolean b1, boolean b2, boolean b3, boolean b4) {
+ super(entity, range, b1, b2, b3, b4);
+
+ this.target = target;
+ }
+
+ @Override
+ public PathSearchJob call() throws Exception {
+ if (!this.isEntityStillValid()) {
+ this.cancel();
+ } else if (!this.issued) {
+ this.issued = true;
+ this.pathEntity = (new AsyncPathfinder(this.chunkCache)).a(this.chunkCache, entity, this.target, this.range);
+ this.entity.getNavigation().setSearchResult(this);
+ this.cleanup();
+ }
+
+ return this;
+ }
+
+ @Override
+ public void cleanup() {
+ super.cleanup();
+ this.target = null;
+ }
+
+ public UUID getCacheEntryKey() {
+ return this.target.getUniqueID();
+ }
+
+ public SearchCacheEntry getCacheEntryValue() {
+ return new SearchCacheEntryEntity(this.entity, this.target, this.pathEntity);
+ }
+
+ @Override
+ public void cancel() {
+ this.entity.getNavigation().cancelSearch(this);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || !(o instanceof PathSearchJobNavigationEntity)) {
+ return false;
+ }
+ return this.getSearchHash() == ((PathSearchJobNavigationEntity) o).getSearchHash();
+ }
+
+}
\ No newline at end of file
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/jobs/PathSearchJobNavigationPosition.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/jobs/PathSearchJobNavigationPosition.java
new file mode 100644
index 0000000..4a0adb7
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/jobs/PathSearchJobNavigationPosition.java
@@ -0,0 +1,65 @@
+package com.elevatemc.spigot.pathsearch.jobs;
+
+import com.elevatemc.spigot.pathsearch.AsyncPathfinder;
+import com.elevatemc.spigot.pathsearch.PositionPathSearchType;
+import com.elevatemc.spigot.pathsearch.cache.SearchCacheEntryPosition;
+import net.minecraft.server.BlockPosition;
+import net.minecraft.server.EntityInsentient;
+
+public class PathSearchJobNavigationPosition extends PathSearchJob {
+
+ private BlockPosition blockPosition;
+ private PositionPathSearchType type;
+
+ public PathSearchJobNavigationPosition(PositionPathSearchType type, EntityInsentient entity, BlockPosition blockPosition, float range, boolean b1, boolean b2, boolean b3, boolean b4) {
+ super(entity, range, b1, b2, b3, b4);
+
+ this.type = type;
+ this.blockPosition = blockPosition;
+ }
+
+ @Override
+ public PathSearchJob call() throws Exception {
+ if(!this.isEntityStillValid()) {
+ this.cancel();
+ } else if(!this.issued) {
+ this.issued = true;
+ this.pathEntity = (new AsyncPathfinder(this.chunkCache)).a(this.chunkCache, entity, blockPosition, range);
+ this.entity.getNavigation().setSearchResult(this);
+ this.cleanup();
+ }
+ return this;
+ }
+
+ public PositionPathSearchType getCacheEntryKey() {
+ return this.type;
+ }
+
+ public SearchCacheEntryPosition getCacheEntryValue() {
+ return new SearchCacheEntryPosition(this.entity, blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), this.pathEntity);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.type.hashCode() ^
+ (this.getSearchHash() << 4);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if(o == null || !(o instanceof PathSearchJobNavigationPosition)) {
+ return false;
+ }
+ PathSearchJobNavigationPosition other = (PathSearchJobNavigationPosition) o;
+
+ return this.type.equals(
+ other.type) &&
+ this.getSearchHash() ==
+ other.getSearchHash();
+ }
+
+ @Override
+ public void cancel() {
+ this.entity.getNavigation().cancelSearch(this);
+ }
+}
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/jobs/PathSearchJobPosition.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/jobs/PathSearchJobPosition.java
new file mode 100644
index 0000000..5b75bd7
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/jobs/PathSearchJobPosition.java
@@ -0,0 +1,66 @@
+package com.elevatemc.spigot.pathsearch.jobs;
+
+import com.elevatemc.spigot.pathsearch.AsyncPathfinder;
+import com.elevatemc.spigot.pathsearch.PositionPathSearchType;
+import com.elevatemc.spigot.pathsearch.cache.SearchCacheEntry;
+import com.elevatemc.spigot.pathsearch.cache.SearchCacheEntryPosition;
+import net.minecraft.server.BlockPosition;
+import net.minecraft.server.EntityCreature;
+
+public class PathSearchJobPosition extends PathSearchJob {
+
+ private BlockPosition blockPosition;
+ private PositionPathSearchType type;
+
+ public PathSearchJobPosition(EntityCreature entity, BlockPosition blockPosition, float range, boolean b1, boolean b2, boolean b3, boolean b4) {
+ super(entity, range, b1, b2, b3, b4);
+
+ this.type = PositionPathSearchType.ANYOTHER;
+ this.blockPosition = blockPosition;
+ }
+
+ @Override
+ public PathSearchJob call() throws Exception {
+ if(!this.isEntityStillValid()) {
+ this.cancel();
+ } else if(!this.issued) {
+ this.issued = true;
+ this.pathEntity = (new AsyncPathfinder(this.chunkCache)).a(this.chunkCache, entity, blockPosition, range);
+ ((EntityCreature)this.entity).setSearchResult(this, this.pathEntity);
+ this.cleanup();
+ }
+ return this;
+ }
+
+ @Override
+ public Object getCacheEntryKey() {
+ return this.type;
+ }
+
+ @Override
+ public SearchCacheEntry getCacheEntryValue() {
+ return new SearchCacheEntryPosition(this.entity, this.blockPosition.getX(), this.blockPosition.getY(), this.blockPosition.getZ(), this.pathEntity);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.type.hashCode() ^ (this.getSearchHash() << 4);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if(o == null || !(o instanceof PathSearchJobPosition)) {
+ return false;
+ }
+ PathSearchJobPosition other = (PathSearchJobPosition) o;
+
+ return this.type.equals(
+ other.type) &&
+ this.getSearchHash() == other.getSearchHash();
+ }
+
+ @Override
+ public void cancel() {
+ ((EntityCreature) this.entity).cancelSearch(this);
+ }
+}
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/jobs/PathSearchQueuingManager.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/jobs/PathSearchQueuingManager.java
new file mode 100644
index 0000000..ec2fe4d
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/pathsearch/jobs/PathSearchQueuingManager.java
@@ -0,0 +1,30 @@
+package com.elevatemc.spigot.pathsearch.jobs;
+
+
+import com.elevatemc.spigot.threading.ThreadingManager;
+
+public class PathSearchQueuingManager {
+
+ private PathSearchJob lastQueuedJob;
+
+ public PathSearchQueuingManager() {
+ this.lastQueuedJob = null;
+ }
+
+ public synchronized boolean hasAsyncSearchIssued() {
+ return this.lastQueuedJob != null;
+ }
+
+ public synchronized void queueSearch(PathSearchJob job) {
+ if (ThreadingManager.queuePathSearch(job)) {
+ this.lastQueuedJob = job;
+ }
+ }
+
+ public synchronized void checkLastSearchResult(PathSearchJob pathSearch) {
+ if (this.lastQueuedJob == pathSearch) {
+ this.lastQueuedJob = null;
+ }
+ }
+
+}
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/threading/NamePriorityThreadFactory.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/threading/NamePriorityThreadFactory.java
new file mode 100644
index 0000000..0e4044c
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/threading/NamePriorityThreadFactory.java
@@ -0,0 +1,85 @@
+package com.elevatemc.spigot.threading;
+
+import net.jafama.FastMath;
+
+import java.lang.ref.WeakReference;
+import java.util.Iterator;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+public class NamePriorityThreadFactory implements ThreadFactory {
+
+ private final int priority;
+ private int idCounter = 0;
+ private String name = "gSpigotThread";
+ private boolean isDaemon = false;
+ private Queue> createdThreadList;
+
+ public NamePriorityThreadFactory(int priority) {
+ this.priority = FastMath.min(FastMath.max(priority, Thread.MIN_PRIORITY), Thread.MAX_PRIORITY);
+ }
+
+ public NamePriorityThreadFactory(int priority, String name) {
+ this(priority);
+ this.name = name;
+ }
+
+ public NamePriorityThreadFactory(String name) {
+ this(Thread.NORM_PRIORITY);
+ this.name = name;
+ }
+
+ public NamePriorityThreadFactory setDaemon(boolean daemon) {
+ this.isDaemon = daemon;
+ return this;
+ }
+
+ public NamePriorityThreadFactory setLogThreads(boolean log) {
+ if (log) {
+ if (this.createdThreadList == null) {
+ this.createdThreadList = new ConcurrentLinkedQueue>();
+ }
+ } else {
+ this.createdThreadList = null;
+ }
+ return this;
+ }
+
+ @Override
+ public Thread newThread(Runnable runnable) {
+ Thread thread = Executors.defaultThreadFactory().newThread(runnable);
+ thread.setPriority(this.priority);
+ thread.setName(this.name + "-" + idCounter);
+ thread.setDaemon(this.isDaemon);
+ if (this.createdThreadList != null) {
+ this.createdThreadList.add(new WeakReference<>(thread));
+ }
+ idCounter++;
+ return thread;
+ }
+
+ public int getActiveCount() {
+ if (this.createdThreadList != null) {
+ Iterator> iter = this.createdThreadList.iterator();
+ int count = 0;
+ while (iter.hasNext()) {
+ WeakReference ref = iter.next();
+ Thread t = ref.get();
+ if (t == null) {
+ iter.remove();
+ } else if (t.isAlive()) {
+ count++;
+ }
+ }
+ return count;
+ }
+ return -1;
+ }
+
+ public Queue> getThreadList() {
+ return this.createdThreadList;
+ }
+
+}
diff --git a/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/threading/ThreadingManager.java b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/threading/ThreadingManager.java
new file mode 100644
index 0000000..6452f04
--- /dev/null
+++ b/TacoSpigot-Server/src/main/java/com/elevatemc/spigot/threading/ThreadingManager.java
@@ -0,0 +1,281 @@
+package com.elevatemc.spigot.threading;
+
+import com.elevatemc.spigot.pathsearch.PathSearchThrottlerThread;
+import com.elevatemc.spigot.pathsearch.jobs.PathSearchJob;
+import net.minecraft.server.NBTCompressedStreamTools;
+import net.minecraft.server.NBTTagCompound;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.ref.WeakReference;
+import java.util.ArrayDeque;
+import java.util.Iterator;
+import java.util.Queue;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class ThreadingManager {
+
+ private static ThreadingManager instance;
+
+ private final Logger log = LogManager.getLogger();
+ private PathSearchThrottlerThread pathSearchThrottler;
+ private ScheduledExecutorService timerService = Executors.newScheduledThreadPool(1, new NamePriorityThreadFactory(Thread.NORM_PRIORITY + 2, "eSpigot_TimerService"));
+ private TickCounter tickCounter = new TickCounter();
+ private NamePriorityThreadFactory cachedThreadPoolFactory;
+ private ExecutorService cachedThreadPool;
+
+ private ScheduledFuture