Skip to content
Configuration

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.