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
| Method | Description |
|---|---|
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.
| Method | Usage |
|---|---|
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
| Method | Description |
|---|---|
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.