Skip to content
Developer Guide

Developer Guide

Gradle Setup

repositories {
    maven { url 'https://jitpack.io' }
}
dependencies {
    compileOnly 'com.github.Xyness:XCore:1.1'
}

Addon Structure

addon.yml

name: MyAddon
version: '1.0.0'
author: 'YourName'
main: com.example.myaddon.MyAddonAddon
description: What this addon does
depend: []
soft-depend: []

Main Class

public class MyAddon extends XAddon {

    @Override
    public boolean onEnable() {
        saveDefaultConfig();
        // Register listeners, commands, tables...
        return true;
    }

    @Override
    public void onDisable() { }

    @Override
    public void onReload() { }
}

Available APIs

MethodDescription
api()XCore API (database, cache, sync, GUI, vault, web)
core()XCore JavaPlugin instance (for Bukkit registrations)
scheduler()Folia-compatible SchedulerAdapter
logger()Addon-scoped logger
lang()Addon language namespace (MiniMessage)
guiRegistry()GUI definition registry (loaded from YAML)
guiUtils()Item building, sounds, heads, click handling
delivery()Give an item or money to someone offline
leaderboards()Declare a ranking once, read it from memory
network()Which servers are up, and where a player is
ranks()Primary group and numbered permission levels (LuckPerms or Vault)
discord()The shared webhook sender
cooldowns()Per-player cooldowns for this addon
placeholders()Publish placeholders without writing an expansion
registerListener(Listener)Bukkit listener, unregistered automatically on disable
getConfig()Addon config.yml
updateConfigWithDefaults(protected...)Add the settings a new version introduces
loadLanguage(codes...)Extract the bundled translations and load the chosen one
getDataFolder()plugins/XCore/addons/<name>/

Two thread pools

api().getExecutor() is for logic, api().getDbExecutor() for anything that opens a connection. Never wait on a task belonging to the pool you are already running on: with a single pool, a thread ends up waiting for work queued behind itself.

SchedulerAdapter (Folia)

Never use Bukkit.getScheduler(). Use scheduler() for Folia compatibility.

MethodUsage
runGlobalTask(Runnable)Sync main thread
runGlobalTaskTimer(Runnable, start, period)Repeating sync
runGlobalTaskLater(Runnable, delay)Delayed sync
runEntityTask(Player|Entity, Runnable)Entity region thread
runEntityTaskLater(Player|Entity, Runnable, delay)Delayed entity task
runEntityTaskTimer(Entity, Runnable, start, period)Repeating, follows the entity across regions
runLocationTask(Runnable, Location)The region owning that location, also runLocationTaskLater
runChunkTask(Runnable, World, x, z)The region owning that chunk, also runChunkTaskLater
runAsyncTask(Runnable)Async thread
runAsyncTaskTimer(Runnable, start, period)Repeating async
runAsyncTaskLater(Runnable, delay)Delayed async
teleportAsync(Player, Location)Teleport with the destination chunk loaded off the main thread
getChunkAtAsync(World, x, z)Load a chunk without blocking
cancelTask(Object)Cancel a task handle

The state of an entity — setAware, remove, effects — must be changed on the thread of its own region: runEntityTask(entity, ...), not runGlobalTask.

GUI Framework

XCore provides a YAML-driven GUI system. Place YAML files in src/main/resources/guis/.

gui-title: "gui-title-key"        # Lang key for inventory title
rows: 6
slots: [0,1,2,...,44]             # Slots for paginated content
slots-sound: "minecraft:ui.button.click"

items:
  BackPage:
    slot: 48
    material: ARROW
    target-title: "previous-title"
    target-lore: "previous-lore"
    target-button-on: "previous-button-on"
    target-button-off: "previous-button-off"
    permission: "myaddon.gui.navigate"
    sound: "minecraft:ui.button.click"
    custom_model_data_value: 0
    item_model_key: "my_pack:my_item"
    actions:
      left:
        - "command:mycommand"
      right:
        - "message:<green>Hello!"

GuiUtils Methods

MethodDescription
createItem(Material, Component, List<Component>)Create item with name, lore, all ItemFlags
createItemFromDef(GuiItem, Component, List<Component>)Create from YAML definition (handles heads, model data)
updateGuiItem(Inventory, slot, Component, List<Component>)Update existing item's name/lore in-place
buildNavLore(LangNamespace, loreKey, offKey, onKey, check, ...)Build nav button lore with blink state
playSound(Player, String)Play sound from namespaced key
handleCommonFeatures(Player, slot, click, def)Sound, configured actions and permission of the clicked item, in one call
blinkBarItem(BlinkCache, lang, itemDef, on, viewer, ...)Render a bar button and cache both of its faces

Paginated screens

PagedGui covers what a list screen always needs: page maths, the bar at slots 48 / 49 / 50, the blink task and its cancellation, and click routing. XCore listens for these screens itself, so nothing has to be registered.

public class WarpGui extends PagedGui<Warp> {

    public WarpGui(MyAddon addon) {
        super(addon.scheduler(), addon.guiUtils(), addon.lang(),
              addon.guiRegistry().get("warps"));
    }

    protected List<Warp> items(Player viewer) { return manager.warps(); }

    protected ItemStack render(Warp warp, Player viewer, boolean blinkOn) {
        return guiUtils().createItemFromDef(itemDef, title(warp), lore(warp), viewer);
    }

    protected void onItemClick(Warp warp, Player viewer, ClickType click) {
        manager.teleport(viewer, warp);
    }
}

items() runs off the server thread, on every open and every page change, so reading the database in it is the expected thing to do.

Entries are drawn once per page. itemsBlink() returns true for a list whose entries have two faces; they then go through the blink cache, which is dropped every itemCacheTicks() so a remaining time in a lore still moves.

A list too large to hold in memory — every account a server has ever seen, for one — pages in the database instead: override totalItems(viewer) with the count and loadPage(viewer, page, perPage) with that one page. items() is then never called.

Never modify the ItemStack returned by inv.getItem(slot) — it is a live mirror of what the server is holding. Build a new one, or use updateGuiItem, which clones it for you.