Plugin API

Advanced

Plugin API

Overview

SCS2 ships a public Java API for other plugins to integrate with the claim system — read claim data, listen to events, drive admin actions, and (since 2.5.0) declare custom flags and role-permissions.

The API is published as a separate artifact SimpleClaimSystem-API via JitPack. Add it as a provided/compileOnly dependency in your build file:

<repositories>
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
</repositories>

<dependency>
    <groupId>com.github.Xyness</groupId>
    <artifactId>SimpleClaimSystem-API</artifactId>
    <version>v2.5.0</version>
    <scope>provided</scope>
</dependency>

Gradle (Groovy): compileOnly 'com.github.Xyness:SimpleClaimSystem-API:v2.5.0' with the JitPack repo maven { url 'https://jitpack.io' }.

Declare SimpleClaimSystem in your plugin.yml so it loads first:

depend: [SimpleClaimSystem]   # or softdepend: [SimpleClaimSystem]

This page documents the whole API surface. The authoritative reference is the JitPack-generated Javadoc; the source lives in the SimpleClaimSystem-API repository.

Getting the API instance

Fetch the API once SCS2 is enabled. Always guard with isRegistered() so your plugin still loads when SCS2 is absent:

import fr.xyness.SimpleClaimSystem.API.SCS_API;
import fr.xyness.SimpleClaimSystem.API.SCS_API_Provider;

if (SCS_API_Provider.isRegistered()) {
    SCS_API api = SCS_API_Provider.get();
    // ... use the API
}

Every example below is called on that api instance. Types live under fr.xyness.SimpleClaimSystem.Types (Claim, PlayerData, ChunkKey) and ...Enums (ClaimRole, WorldMode).

Reading claims & players

From a chunk or location

Use the synchronous getters on hot paths inside listeners (the chunk is loaded and the claim is almost certainly cached). Use the async ones from commands, GUIs or scheduled tasks, where a cold chunk could otherwise hit the database on the calling thread.

Optional<Claim> claim = api.getClaim(player.getLocation().getChunk());
claim.ifPresent(c -> {
    String owner = c.getOwnerName();
    String name  = c.getClaimName();
});

// Async — preferred outside listeners
api.getClaimAsync(chunk).thenAccept(opt -> opt.ifPresent(c -> { /* ... */ }));

// Batch several chunks at once
Map<ChunkKey, Optional<Claim>> claims = api.getClaims(keys);

Quick checks (no Claim object needed)

boolean claimed  = api.isClaimed(player.getLocation());   // ChunkKey / Chunk / Location
boolean canBuild = api.hasPermission(chunk, playerId, "place_block");
boolean banned   = api.isBanned(chunk, playerId);
boolean member   = api.isMember(chunk, playerId);
String  role     = api.getRole(chunk, playerId);          // "VISITOR" if not a member
Boolean pvp      = api.getFlag(chunk, "pvp");

Players & world mode

Optional<PlayerData> data = api.getPlayer(player.getUniqueId());
data.ifPresent(pd -> { String name = pd.getName(); });
// Async: getPlayerAsync, getPlayerByNameAsync, getPlayersAsync, getPlayerNamesAsync

WorldMode mode = api.getWorldMode(world);   // SURVIVAL, SURVIVAL_REQUIRING_CLAIMS, PROTECTED, DISABLED

Roles & permissions

Roles are plain Strings. The four built-ins are VISITOR, MEMBER, MODERATOR and OWNER; claim owners can also create custom roles. Permissions and flags resolve the same way for both.

String  role      = claim.getRole(playerId);          // built-in or custom, "VISITOR" if none
boolean canBuild  = claim.getPermission(role, "place_block");
boolean isDefault = ClaimRole.isDefault(role);        // true for VISITOR/MEMBER/MODERATOR/OWNER

List<String> all    = claim.getAllRoles();            // defaults + custom
List<String> custom = claim.getCustomRoles();

boolean explosions = claim.getFlag("creeper_explosions");

Enforce a permission in your own listener — owners are always allowed, and honour the staff bypass node:

String role = claim.getRole(player.getUniqueId());
if (!claim.getPermission(role, "my_perm")
        && !player.hasPermission("scs.bypass.my_perm")) {
    event.setCancelled(true);
}

Modifying claims

Two layers. Claim helpers mutate the in-memory object only — no database write, no events — handy while assembling a claim. SCS_API methods persist to the database, refresh caches and fire the matching events; most return a CompletableFuture.

// ❌ Maps from getMembers() / getFlags() / getPermissions() are unmodifiable
claim.getMembers().put(uuid, "MEMBER");           // throws UnsupportedOperationException

// ✅ In-memory only
claim.addMember(uuid, "MEMBER");
claim.setFlag("pvp", false);
claim.setPermission("MEMBER", "place_block", true);

// ✅ Persist + caches + events
api.addMember(claim, uuid, "MEMBER");
api.setMemberRole(claim, uuid, "MODERATOR");
api.removeMember(claim, uuid);
api.banPlayer(claim, uuid, LocalDateTime.now().plusDays(7));
api.unbanPlayer(claim, uuid);
api.setFlag(claim, "pvp", false);
api.setPermission(claim, "MEMBER", "place_block", true);

Bulk member ops (Factions / Kingdoms style)

api.addMemberToAllClaims(leaderUuid, newMemberUuid, "MEMBER");
api.removeMemberFromAllClaims(leaderUuid, leavingMemberUuid);

Player limits & stats

int    maxClaims  = api.getMaxClaims(player);
int    maxChunks  = api.getMaxChunks(player);
int    maxRadius  = api.getMaxRadius(player);
int    maxMembers = api.getMaxMembers(player);
double cost       = api.getChunkCost(player);
double multiplier = api.getCostMultiplier(player);

int claims = api.getClaimCount(playerId);   // current totals
int chunks = api.getChunkCount(playerId);

Querying claims by owner

List<Claim>  claims = api.getClaimsByOwner(ownerUuid);     // also getClaimsByOwnerAsync
List<String> names  = api.getClaimNamesByOwner(ownerUuid);
Optional<Claim> one  = api.getClaimByOwnerAndName(ownerUuid, "My Base");

Map<UUID, String>        members = api.getClaimMembers(ownerUuid, "My Base");
Map<UUID, LocalDateTime> banned  = api.getClaimBanned(ownerUuid, "My Base");

Favourites

boolean added   = api.addFavorite(playerUuid, claim);
boolean removed = api.removeFavorite(playerUuid, claim);
boolean fav     = api.isFavorite(playerUuid, claim.getId());

List<Integer> ids  = api.getFavoriteClaimIds(playerUuid);
List<Claim>   live = api.getFavoriteClaims(playerUuid);   // ids that no longer resolve are dropped

React to changes with ClaimFavoriteEventgetAction() returns FAVORITE or UNFAVORITE.

Events

Every event extends ClaimEvent (getClaim()) and fires before the database write, so cancelling a cancellable event vetoes the whole operation — no DB change, no cache update. Bulk actions fire one event per claim.

Available events

EventKey dataCancel
ClaimCreateEvent / ClaimDeleteEventgetPlayerId()
ClaimExpireEventgetOwnerUuid()
ClaimEnterEvent / ClaimLeaveEventgetPlayer()Enter only
ClaimMemberEventgetMemberId(), getAction(), getRoleName()
ClaimOwnerTransferEventgetOldOwner(), getNewOwner()
ClaimSaleEventgetAction(), getPrice(), getPlayerId()
ClaimChunkEventgetChunkKey(), getAction()
ClaimMergeEventgetMergedClaims()
ClaimFlagChangeEventgetFlag(), getValue()
ClaimPermissionChangeEventgetRoleName(), getPermission(), getValue()
ClaimRenameEventgetOldName(), getNewName()
ClaimDescriptionChangeEventgetOldDescription(), getNewDescription()
ClaimSpawnChangeEventgetNewSpawn()
ClaimWarpToggleEventgetNewState()
ClaimVisitEventgetVisitorId()
ClaimFavoriteEventgetPlayerId(), getAction()

Action enums

  • ClaimMemberEvent.Action — ADD, REMOVE, PROMOTE, DEMOTE, KICK, BAN, UNBAN, ROLE_CHANGE
  • ClaimSaleEvent.Action — LISTED, CANCELLED, BOUGHT
  • ClaimChunkEvent.Action — ADD, REMOVE
  • ClaimFavoriteEvent.Action — FAVORITE, UNFAVORITE

Listening

import fr.xyness.SimpleClaimSystem.Events.*;

@EventHandler
public void onCreate(ClaimCreateEvent event) {
    Claim claim = event.getClaim();
    getLogger().info(claim.getOwnerName() + " created " + claim.getClaimName());
}

// Veto a deletion from your own protection logic
@EventHandler
public void onDelete(ClaimDeleteEvent event) {
    if (event.getClaim().getClaimName().equalsIgnoreCase("spawn")) {
        event.setCancelled(true);
    }
}

@EventHandler
public void onMember(ClaimMemberEvent event) {
    if (event.getAction() == ClaimMemberEvent.Action.ADD) {
        getLogger().info("Added as " + event.getRoleName());
    }
}

Custom flags & permissions (2.5.0)

External plugins can declare their own claim flags and role-permissions. Data-only: SCS2 stores the per-claim value and exposes it through claim.getFlag(key) / claim.getPermission(role, key). You write the @EventHandler that checks the value and cancels events — SCS does NOT invoke any handler tied to your definition.

Registering a custom flag

Register in your plugin's onLoad() so SCS's startup migration sees your flag and seeds the default value on existing claims. Runtime registration (in onEnable or later) also works — SCS backfills cached claims and persists to DB — but claims that loaded before the registration are missing the key until the next plugin reload.

import fr.xyness.SimpleClaimSystem.API.FlagDefinition;
import fr.xyness.SimpleClaimSystem.API.SCS_FlagRegistry;

public class MyPlugin extends JavaPlugin {

    @Override
    public void onLoad() {
        SCS_FlagRegistry.registerFlag(FlagDefinition.builder("my_flag")
            .defaultValue(true)
            // Optional: separate defaults for PROTECTED / SURVIVAL_REQUIRING_CLAIMS world modes
            .protectedModeDefault(false)
            .survivalRequiringClaimsModeDefault(true)
            // Optional metadata used by the GUI (you provide the lang strings)
            .titleKey("my_flag-title")
            .loreKey("my_flag-lore")
            .iconMaterial("DIAMOND")
            // Used in diagnostics + SCS_FlagRegistry.unregisterAllOwnedBy(pluginName)
            .owningPluginName(getName())
            .build());
    }

    @Override
    public void onDisable() {
        // Optional cleanup on plugin reload
        SCS_FlagRegistry.unregisterAllOwnedBy(getName());
    }

    // Your own listener checks the flag and cancels the event
    @EventHandler
    public void onSomething(SomeBukkitEvent event) {
        SCS_API api = SCS_API_Provider.get();
        api.getClaim(event.getLocation().getChunk()).ifPresent(claim -> {
            if (!claim.getFlag("my_flag")) event.setCancelled(true);
        });
    }
}

Registering a custom role-permission

Same shape, but with per-role defaults. Role names are matched case-insensitively (uppercase-normalized internally). Roles not explicitly listed fall back to fallbackDefault — important for custom roles created by claim owners.

import fr.xyness.SimpleClaimSystem.API.PermissionDefinition;
import fr.xyness.SimpleClaimSystem.API.SCS_FlagRegistry;

@Override
public void onLoad() {
    SCS_FlagRegistry.registerPermission(PermissionDefinition.builder("my_perm")
        .defaultPerRole("VISITOR", false)
        .defaultPerRole("MEMBER", true)
        .defaultPerRole("MODERATOR", true)
        .fallbackDefault(false)  // used by custom claim roles
        .titleKey("my_perm-title")
        .loreKey("my_perm-lore")
        .iconMaterial("DIAMOND")
        .owningPluginName(getName())
        .build());
}

@EventHandler
public void onSomething(SomeBukkitEvent event) {
    Player player = event.getPlayer();
    SCS_API api = SCS_API_Provider.get();
    api.getClaim(event.getLocation().getChunk()).ifPresent(claim -> {
        String role = claim.getRole(player.getUniqueId());
        if (!claim.getPermission(role, "my_perm")
                && !player.hasPermission("scs.bypass.my_perm")) {
            event.setCancelled(true);
        }
    });
}

Auto-registered Bukkit permissions

When you register a custom key, SCS automatically declares the matching Bukkit permissions via PluginManager.addPermission:

Permission nodeDefaultPurpose
scs.bypass.<key>opBypass the per-claim check (you read it in your own listener).
scs.flag.<key> (flags only)opAllow toggling this flag in the GUI.
scs.permission.<key> (permissions only)opAllow toggling this permission row in the GUI.

You don't need to declare these in your own plugin.yml. They're cleaned up on unregister.

Runtime registration via SCS_API

The same operations are mirrored on the runtime SCS_API:

SCS_API api = SCS_API_Provider.get();
api.registerCustomFlag(FlagDefinition.builder("dynamic").build());
api.unregisterCustomFlag("dynamic");
api.registerCustomPermission(PermissionDefinition.builder("dynamic_perm").build());
api.unregisterCustomPermission("dynamic_perm");

Runtime registration triggers an immediate backfill on every cached claim (default value written to claims that don't yet have the key, batched into a single DB write). Runtime unregistration strips the key from cached claims.

Caveats

  • Default value source-of-truth: the defaultValue in the builder is used to seed claims that don't have your key yet. After that, the per-claim stored value wins — changing the builder default later won't update existing claims.
  • No automatic gating: SCS doesn't call your code on any event. The flag is a stored boolean; you write the listener.
  • Definition not persisted: only the per-claim value is stored in the DB. If your plugin is uninstalled and SCS restarts, the unknown key is stripped at the next startup migration (same behavior as a removed built-in flag).

Still need help?

Ask our team on Discord or browse the source.