Pathetic is a high-performance A* pathfinding library for Minecraft servers, specifically designed for Bukkit, Spigot, and Paper. It provides a robust and flexible API for computing efficient paths within the Minecraft world.
- High-performance A* pathfinding implementation with asynchronous computation
- Configurable pathfinding parameters and customizable cost calculation
- Native chunk loading support via NavigationPointProvider
- Extensive validation and cost processing system
- Built-in fallback strategies for complex pathfinding scenarios
- Minimal performance impact with configurable iteration limits
Paper and Spigot are explicitly supported. Other server implementations may work but are not officially tested.
This repository was previously part of the Pathetic main repository.
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.github.bsommerfeld.pathetic-bukkit</groupId>
<artifactId>core</artifactId>
<version>VERSION</version>
</dependency>
</dependencies>repositories {
maven { url 'https://jitpack.io' }
}
dependencies {
implementation 'com.github.bsommerfeld.pathetic-bukkit:core:VERSION'
}Relocation is strongly recommended to avoid conflicts with other plugins using Pathetic.
Maven Shade Relocation
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<relocations>
<relocation>
<pattern>de.bsommerfeld.pathetic</pattern>
<shadedPattern>your.package.pathetic</shadedPattern>
</relocation>
</relocations>
</configuration>
</plugin>Gradle Shadow Relocation
shadowJar {
relocate 'de.bsommerfeld.pathetic', 'your.package.pathetic'
}import de.bsommerfeld.pathetic.bukkit.PatheticBukkit;
import org.bukkit.plugin.java.JavaPlugin;
public class MyPlugin extends JavaPlugin {
@Override
public void onEnable() {
// Initialize Pathetic with this plugin instance
PatheticBukkit.initialize(this);
}
}import de.bsommerfeld.pathetic.api.factory.PathfinderFactory;
import de.bsommerfeld.pathetic.api.factory.PathfinderInitializer;
import de.bsommerfeld.pathetic.api.pathing.Pathfinder;
import de.bsommerfeld.pathetic.api.pathing.configuration.PathfinderConfiguration;
import de.bsommerfeld.pathetic.bukkit.initializer.BukkitPathfinderInitializer;
import de.bsommerfeld.pathetic.bukkit.provider.LoadingNavigationPointProvider;
import de.bsommerfeld.pathetic.engine.factory.AStarPathfinderFactory;
// Create the PathfinderFactory
PathfinderFactory factory = new AStarPathfinderFactory();
// Configure the pathfinder
PathfinderConfiguration configuration = PathfinderConfiguration.builder()
.provider(new LoadingNavigationPointProvider())
.async(true)
.maxIterations(100_000_000)
.build();
// Create the pathfinder instance
Pathfinder pathfinder = factory.createPathfinder(configuration);import de.bsommerfeld.pathetic.api.wrapper.PathPosition;
import de.bsommerfeld.pathetic.bukkit.context.BukkitEnvironmentContext;
import de.bsommerfeld.pathetic.bukkit.mapper.BukkitMapper;
import org.bukkit.Location;
class PathfindingExample {
private final Pathfinder pathfinder;
public PathfindingExample(Pathfinder pathfinder) {
this.pathfinder = pathfinder;
}
private void findPath(Location start, Location target) {
PathPosition startPos = BukkitMapper.toPathPosition(start);
PathPosition targetPos = BukkitMapper.toPathPosition(target);
pathfinder.findPath(startPos, targetPos, new BukkitEnvironmentContext(world))
.ifPresent(result -> {
// We have an usable result since it either found the path, or fallen back.
result.getPath.forEach(position -> {
Location location = BukkitMapper.toLocation(position, world);
// Do something with it.
});
}).orElse(_ -> {
// Handle no path found scenario
System.out.println("No path found between start and target positions.");
}).exceptionally(ex -> System.err.println("An exception occurred -> " + ex));
}
}Implement the CostProcessor interface to define custom movement costs:
import de.bsommerfeld.pathetic.api.pathing.processing.Cost;
import de.bsommerfeld.pathetic.api.pathing.processing.CostProcessor;
import de.bsommerfeld.pathetic.api.pathing.processing.context.EvaluationContext;
import de.bsommerfeld.pathetic.api.provider.NavigationPointProvider;
import de.bsommerfeld.pathetic.bukkit.provider.BukkitNavigationPoint;
import org.bukkit.Material;
public class CustomCostProcessor implements CostProcessor {
@Override
public Cost calculateCostContribution(EvaluationContext context) {
NavigationPointProvider provider = context.getNavigationPointProvider();
BukkitNavigationPoint beneath = (BukkitNavigationPoint)
provider.getNavigationPoint(
context.getCurrentPathPosition().subtract(0, 1, 0),
context.getEnvironmentContext()
);
if (beneath.getMaterial() == Material.STONE) {
return Cost.of(20);
}
return Cost.ZERO;
}
}Add the processor to your configuration:
PathfinderConfiguration configuration = PathfinderConfiguration.builder()
.provider(new LoadingNavigationPointProvider())
.costProcessors(List.of(new CustomCostProcessor()))
.build();Control which nodes are valid for pathfinding:
import de.bsommerfeld.pathetic.api.pathing.processing.ValidationProcessor;
import de.bsommerfeld.pathetic.api.pathing.processing.context.EvaluationContext;
public class CustomValidationProcessor implements ValidationProcessor {
@Override
public boolean validate(EvaluationContext context) {
// Return true if the node is valid, false otherwise
return true;
}
}Add to configuration:
PathfinderConfiguration configuration = PathfinderConfiguration.builder()
.provider(new LoadingNavigationPointProvider())
.nodeValidationProcessors(List.of(new CustomValidationProcessor()))
.build();PathfinderConfiguration configuration = PathfinderConfiguration.builder()
.provider(new LoadingNavigationPointProvider())
.async(true) // Enable async pathfinding
.fallback(true) // Enable fallback strategies
.maxIterations(100_000_000) // Maximum nodes to evaluate
.heuristicStrategy(HeuristicStrategies.SQUARED) // Heuristic calculation
.costProcessors(List.of(...)) // Custom cost processors
.
nodeValidationProcessors(List.of(...)) // Custom validation processors
.
build();Pathetic provides two built-in providers:
LoadingNavigationPointProvider: Loads chunks automatically if needed (recommended)FailingNavigationPointProvider: Fails if chunks are not loaded
// With automatic chunk loading
.provider(new LoadingNavigationPointProvider())
// Without automatic chunk loading
.
provider(new FailingNavigationPointProvider())- Always use asynchronous pathfinding to avoid blocking the main thread
- Set appropriate
maxIterationslimits based on your use case - Use validation processors to eliminate invalid nodes early
- Consider using fallback strategies for complex scenarios
- Relocate the library to prevent conflicts with other plugins
See the example module for complete working implementations, including:
- Basic pathfinding setup
- Custom cost and validation processors
- Integration with Bukkit commands
- Chunk invalidation handling
Complete JavaDoc documentation is available in the releases.
This project is licensed under the MIT License - see the LICENSE file for details.
For issues and feature requests, please use the GitHub Issues page.