Configuration
General
Four settings that do not belong to any one subsystem.
# Verbose cache, database and API messages. It also turns on the tick-thread
# watchdog: any addon querying the database from a tick thread is named in the
# console, once per call site.
debug: false
# Measure how long each addon spends in each event handler, read with /xcore profile.
# Addons run under XCore's plugin identity, so a timings report blames XCore for all
# of them. This is what tells you which addon is actually costing tick time.
profiling: false
# Recognise a returning player whose UUID changed.
migrate-uuid-changes: true
# Answer /plugins with Paper's own list plus a section for the addons.
plugins-command:
override: true
Profiling
Costs one boolean read per event while off, so leaving it off is free. /xcore profile on turns it on for a session without editing the file, and /xcore profile prints what it has gathered.
UUID changes
server_uuid is derived from the name in offline mode and comes from Mojang in online mode. Switching that setting, or moving the server behind a proxy that changes it, makes every player look brand new. With this on, the existing row is moved onto the new UUID instead of a second one being created, so balances, per-addon columns and history follow the player. Matching is by Mojang UUID first, and by name only for accounts that have no Mojang UUID at all.
An addon that stores its own data by UUID has to do the same for its own tables. XCore only moves the rows it owns.
The /plugins command
Addons are loaded by XCore rather than by the server, so /plugins does not know they exist. With the override on, the command answers with Paper's own list plus a third section for the addons, and /bukkit:plugins and /bukkit:pl answer the same way so the two forms cannot disagree. Turn it off to hand the command back to the server exactly as it was.
Database
XCore supports SQLite (default, zero config), MySQL, and PostgreSQL.
database-type: sqlite # sqlite, mysql, postgresql
database:
host: localhost
port: 3306
name: xcore
username: root
password: ""
pool-size: 10
Use MySQL or PostgreSQL for multi-server setups. SQLite is single-server only and doesn't support cross-server sync.
Cross-Server
The cross-server block controls Redis (L2 cache + instant Pub/Sub sync) and database polling (fallback sync). Both are configured together.
server-name: "default" # at the root of config.yml
cross-server:
enabled: false
redis:
enabled: false
host: localhost
port: 6379
password: ""
database: 0
ttl: 3600
heartbeat-seconds: 10 # how often a server announces itself to the others
sync:
poll-interval-seconds: 3 # Database polling interval (if no Redis)
retention-seconds: 300 # How long to keep DB sync rows
Redis is the preferred transport (instant delivery). Database polling is the fallback with a configurable delay.
server-name identifies this server instance. It is used by addons (e.g. per-server economy balances) and sync channels. Each addon registers named channels. XCore multiplexes them over a single Redis channel (xcore:sync) or database table (xcore_sync).
When a player's row changes, the server that wrote it tells the others so they drop their copy. Without that, a balance changed on one server stayed wrong everywhere else until the entry fell out of the cache.
The heartbeat is what feeds the server list, "which server is this player on", and messages addressed to one server in particular.
Addon toggles and sync-addon toggles are configured in a separate addons.yml file, not in config.yml.
Economy
XCore includes a full economy system with multi-currency support, Vault integration, and currency exchange.
economy:
enabled: true
per-server-balances: false # Uses cross-server.server-name
currencies:
coins:
symbol: "$"
symbol-position: BEFORE # BEFORE or AFTER
decimals: 2
starting-balance: 0.00
max-balance: 1000000000
vault: true # Primary Vault currency
gems:
symbol: "✦"
symbol-position: AFTER
decimals: 0
starting-balance: 0
max-balance: 0 # 0 = unlimited
vault: false
exchange:
enabled: true
rates:
coins-to-gems: 100 # 100 coins = 1 gem
gems-to-coins: 80 # 1 gem = 80 coins
The currency marked with vault: true becomes the primary Vault provider. All other plugins (shops, auction house, etc.) see this currency through Vault.
Set per-server-balances: true to maintain separate balances per server. The server identity comes from server-name at the root of the main config.
Web Dashboard
web-dashboard:
enabled: false
port: 8085
session-ttl-hours: 24 # how long a /xcore dashboard link stays valid, 0 = until revoked
metrics-public: true
cors-origin: "*" # set your panel URL if the dashboard is reachable from the internet
public-url: "" # what /xcore dashboard links to, behind a proxy or a domain
The dashboard serves a vanilla JS single-page application. Each addon registers a web module that declares its own pages, so the interface of an addon lives with the addon rather than in the core.
Logging in
There is no token to configure. Run /xcore dashboard in game and you get a link that opens the dashboard already authenticated. The page takes the token out of the address bar on arrival, so it does not linger in the browser history or in a screenshot.
Sessions are stored as SHA-256 hashes in web-sessions.json — a copy of that file lets nobody in. /xcore dashboard revoke closes every session you opened, and links expire after session-ttl-hours.
A script that needs API access gets its token the same way: run the command, take the token out of the link, revoke it when the script is retired.
Authentication is rate limited: ten failed attempts from the same address within a minute lock that source out. All requests are limited to 120 per minute per address.
Deliveries
Anything an addon owes a player but cannot hand over on the spot: a reward with a full inventory, a payment made while they were offline. It is stored and given on their next join, oldest first.
delivery:
on-join: true # hand everything over when the player joins
per-join-limit: 20 # so a long backlog does not slow a login down
Money always goes through. An item is only marked as taken once it is really in the inventory, so a full inventory postpones it instead of losing it.
The message a player sees comes from the delivery-received key of your language file.
Discord
Every addon that posts to Discord goes through one queue. A webhook that answers "too many requests" is left alone for as long as Discord asks, and the message is sent afterwards rather than dropped.
integrations:
discord:
username: "XCore" # name messages are posted under
avatar-url: ""
Only the identity is shared. Each addon keeps its own webhook address, its own events and its own wording in its own config.