Documentation for developers working with the GriefPrevention3D API. Please feel free to help add to this page.
ClaimYInfo)ClaimCommandAddon InterfaceClaimCommandContextGriefPrevention3D is hosted on JitPack, which exposes the repo as a public Maven artifact on demand.
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependency>
<groupId>com.github.castledking</groupId>
<artifactId>GriefPrevention3D</artifactId>
<version>17.3.6</version>
<scope>provided</scope>
</dependency>
Replace
17.3.6with the release tag you want to build against, or with a short commit hash for snapshot-style builds. See the JitPack page for the full list of buildable versions.
repositories {
maven { url 'https://jitpack.io' }
}
dependencies {
compileOnly 'com.github.castledking:GriefPrevention3D:17.3.6'
}
repositories {
maven("https://jitpack.io")
}
dependencies {
compileOnly("com.github.castledking:GriefPrevention3D:17.3.6")
}
See the JitPack page for the project: https://jitpack.io/#castledking/GriefPrevention3D/
Always declare GriefPrevention as either a depend or softdepend in your addon's plugin.yml:
softdepend: [GriefPrevention]
Use depend only if your plugin cannot load without it.
These operations all exist on upstream GriefPrevention too, and work the same way on GP3D. The notable GP3D-specific additions are the 3D subdivision API below.
import me.ryanhamshire.GriefPrevention.Claim;
import me.ryanhamshire.GriefPrevention.GriefPrevention;
Claim claim = GriefPrevention.instance.dataStore.getClaimAt(
location,
/* ignoreHeight = */ false,
/* cachedClaim = */ null);
ignoreHeight = false makes Y matter (needed for 3D subdivisions).Claim as cachedClaim to speed up repeated lookups in the same area.null if the location is in wilderness.Permissions are expressed as ClaimPermission values:
import me.ryanhamshire.GriefPrevention.ClaimPermission;
import java.util.function.Supplier;
Supplier<String> denialReason = claim.checkPermission(player, ClaimPermission.Build, event);
if (denialReason != null) {
player.sendMessage(denialReason.get());
event.setCancelled(true);
}
ClaimPermission.Edit > Manage > Build > Container > Access — higher levels grant all lower levels.
For unified location-based permission checks (the same logic GriefPrevention uses internally), prefer:
import com.griefprevention.protection.ProtectionHelper;
Supplier<String> denial = ProtectionHelper.checkPermission(
player, location, ClaimPermission.Build, event);
GriefPrevention.instance.dataStore.createClaim(
world,
x1, z1, x2, z2, // corners
minY, maxY, // Y bounds (use world min/max for 2D)
ownerUuid, // null for admin claims
/* parent = */ null,
/* id = */ null,
/* creatingPlayer = */ player);
Use parent = someTopLevelClaim to create a subdivision. Pass id = null to let the data store assign one.
GriefPrevention.instance.dataStore.resizeClaim(
claim,
newX1, newZ1, newX2, newZ2,
newMinY, newMaxY,
resizingPlayer);
Claim-block math is applied automatically for the owner.
GriefPrevention.instance.dataStore.extendClaim(claim, newMinY);
Useful for plugins that want to auto-extend a claim when players build below it.
GriefPrevention.instance.dataStore.changeClaimOwner(claim, newOwnerUuid);
Pass null as the new owner to convert the claim to an administrative claim.
Long id = claim.getID();
Claim sameClaim = GriefPrevention.instance.dataStore.getClaim(id);
Claim IDs are stable across reloads.
import me.ryanhamshire.GriefPrevention.PlayerData;
PlayerData data = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId());
int total = data.getAccruedClaimBlocks() + data.getBonusClaimBlocks();
// Save after changes:
GriefPrevention.instance.dataStore.savePlayerData(player.getUniqueId(), data);
GP3D extends the Claim object with Y-aware accessors so addons can query vertical boundaries uniformly, whether the claim is a full-height 2D claim or a Y-bounded 3D subdivision.
int minY = GriefPrevention.getWorldMinY(world); // e.g., -64 in 1.18+
int maxY = GriefPrevention.getWorldMaxY(world); // e.g., 320 in 1.21
These exist so you don't have to care about the 1.17→1.18 world-height change.
int minY = claim.getMinY(); // lowest Y the claim protects
int maxY = claim.getMaxY(); // highest Y the claim protects
int height = claim.getYHeight(); // maxY - minY, in blocks
boolean is3D = claim.is3D(); // true if this claim has custom Y bounds
boolean inside = claim.containsY(y);
Behavior by claim type:
| Claim type | Y range |
|---|---|
| Top-level (basic) claim | Full world min → max Y |
| Admin claim | Full world min → max Y |
| 2D subdivision | Inherits parent's (world) Y range |
| 3D subdivision | Own defined Y bounds |
ClaimYInfo)For a richer description of the claim's Y shape, use ClaimYInfo:
Claim.ClaimYInfo yInfo = claim.getYInfo();
yInfo.getMinY(); // int
yInfo.getMaxY(); // int
yInfo.getHeight(); // int
yInfo.is3D(); // boolean
yInfo.isSubdivision(); // boolean, true if this has a parent
yInfo.isAdminClaim(); // boolean
yInfo.getClaimType(); // human-readable, e.g. "3D Subdivision"
getClaimType() returns one of:
"Main Claim""Admin Claim""2D Subdivision""3D Subdivision""Admin 2D Subdivision""Admin 3D Subdivision"toString() is implemented for quick debugging:
ClaimYInfo{type=3D Subdivision, minY=60, maxY=80, height=21}
if (claim.containsY(block.getY())) {
// y is inside this claim's vertical range
}
For non-3D claims this always returns true. For 3D claims it enforces the actual Y bounds.
GP3D exposes a narrow addon seam that lets other plugins extend /claim and /aclaim tab completions and add their own subcommands — without having to fight the core for command ownership.
The relevant classes live in com.griefprevention.api:
ClaimCommandAddon — the interface your addon implements.ClaimCommandAddonRegistry — static entry point for registering addons.ClaimCommandContext — the command-execution context passed to your handler.ClaimCommandAddon Interfacepackage com.griefprevention.api;
public interface ClaimCommandAddon {
/**
* Additional tab completions for a known subcommand.
* Merged additively with GP3D's native completions.
*/
List<String> getTabCompletions(
CommandSender sender,
String rootCommand, // "claim" or "aclaim"
String subcommand, // canonical name, e.g. "trust"
String[] args); // args AFTER the subcommand
/**
* Additional subcommand names shown when the user types just `/claim `.
* Use this to surface addon-provided subcommands in tab completion.
*/
default List<String> getSubcommandCompletions(
CommandSender sender, String rootCommand) {
return List.of();
}
/**
* Handle an addon-defined subcommand.
* Only called if GP3D does NOT already own the subcommand — core always wins.
*
* @return true if your addon handled the command; false to fall through.
*/
default boolean handleSubcommand(ClaimCommandContext context) {
return false;
}
}
ClaimCommandContextPassed to handleSubcommand. Provides:
context.getSender(); // CommandSender (usually Player)
context.getRootCommand(); // "claim" or "aclaim"
context.getSubcommand(); // the subcommand the user typed
context.getArgs(); // args AFTER the subcommand (defensive copy)
context.getSelectedOrCurrentClaim(); // @Nullable Claim — selected claim or the
// claim the player is standing in
getSelectedOrCurrentClaim() is the key piece — GP3D has already done the "selected claim, fallback to current claim" resolution for you, so your addon can just operate on that claim if it's non-null.
import com.griefprevention.api.ClaimCommandAddonRegistry;
public final class MyAddon extends JavaPlugin {
private final MyClaimAddon hook = new MyClaimAddon();
@Override
public void onEnable() {
ClaimCommandAddonRegistry.register(hook);
}
@Override
public void onDisable() {
ClaimCommandAddonRegistry.unregister(hook);
}
}
register() is idempotent — registering the same instance twice is a no-op. Always unregister() on disable to keep reloads clean.
A minimal /claim sell <price> addon:
import com.griefprevention.api.ClaimCommandAddon;
import com.griefprevention.api.ClaimCommandAddonRegistry;
import com.griefprevention.api.ClaimCommandContext;
import me.ryanhamshire.GriefPrevention.Claim;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.List;
import java.util.Objects;
public final class SellClaimAddon implements ClaimCommandAddon {
@Override
public List<String> getSubcommandCompletions(CommandSender sender, String rootCommand) {
// Show "sell" in `/claim <TAB>`
return rootCommand.equals("claim") ? List.of("sell") : List.of();
}
@Override
public List<String> getTabCompletions(
CommandSender sender, String rootCommand, String subcommand, String[] args) {
if (!"sell".equalsIgnoreCase(subcommand)) return List.of();
if (args.length == 0) {
return List.of("100", "500", "1000");
}
return List.of();
}
@Override
public boolean handleSubcommand(ClaimCommandContext context) {
if (!"sell".equalsIgnoreCase(context.getSubcommand())) {
return false; // not ours
}
if (!(context.getSender() instanceof Player player)) {
context.getSender().sendMessage("Run this as a player.");
return true;
}
Claim claim = context.getSelectedOrCurrentClaim();
if (claim == null) {
player.sendMessage(ChatColor.RED + "No selected or current claim.");
return true;
}
if (!Objects.equals(claim.getOwnerID(), player.getUniqueId())) {
player.sendMessage(ChatColor.RED + "Only the owner can list this claim for sale.");
return true;
}
String[] args = context.getArgs();
if (args.length < 1) {
player.sendMessage(ChatColor.RED + "Usage: /claim sell <price>");
return true;
}
long price;
try {
price = Long.parseLong(args[0]);
} catch (NumberFormatException ex) {
player.sendMessage(ChatColor.RED + "Price must be a number.");
return true;
}
// ... store the listing, charge a fee, etc.
player.sendMessage(ChatColor.GREEN + "Claim listed for sale at " + price);
return true;
}
}
Register it in your plugin's onEnable:
ClaimCommandAddonRegistry.register(new SellClaimAddon());
That's it — /claim sell 100 now runs your handler with the claim already resolved.
trust, abandon, expand, etc. cannot be overridden by an addon.handleAddonSubcommand returns on the first addon that reports true, so order of registration matters if two addons claim the same subcommand.GriefPrevention fires a number of custom Bukkit events you can listen to. They live under me.ryanhamshire.GriefPrevention.events:
| Event | When it fires | Cancellable |
|---|---|---|
ClaimCreatedEvent |
A new claim is about to be created | Yes |
ClaimDeletedEvent |
A claim is deleted | No |
ClaimResizeEvent |
A claim is being resized | Yes (via ClaimChangeEvent) |
ClaimExtendEvent |
A claim is auto-extended downward | Yes (via ClaimChangeEvent) |
ClaimModifiedEvent |
A generic "claim shape changed" event | Yes (via ClaimChangeEvent) |
ClaimTransferEvent |
Claim ownership changes | Yes |
ClaimExpirationEvent |
A claim is about to expire | Yes |
ClaimPermissionCheckEvent |
Permission check runs against a claim — override to alter the result | Yes |
ClaimInspectionEvent |
Player right-clicks with the investigation tool | Yes |
TrustChangedEvent |
Trust list changes for one or more claims | Yes |
AccrueClaimBlocksEvent |
A player is about to accrue claim blocks | Yes |
PreventBlockBreakEvent |
A block break is being prevented | Yes |
PreventPvPEvent |
PvP is being prevented | Yes |
ProtectDeathDropsEvent |
Death drops are about to be protected | Yes |
SaveTrappedPlayerEvent |
/trapped is about to teleport a player |
Yes |
PlayerKickBanEvent |
GP is about to kick/ban a player | Yes |
BoundaryVisualizationEvent |
Claim borders are about to be visualized | Yes |
VisualizationEvent |
(legacy) Visualization is about to run | Yes |
Listen to them like any Bukkit event:
@EventHandler
public void onCreate(ClaimCreatedEvent event) {
Claim created = event.getClaim();
// ...
}
See the Javadoc on each class for the exact getters and setters.