Content generated with AI — it may contain mistakes.

Getting started

Installation

Dependencies, the first boot, every file it writes, the database, Folia and the reload.

Before you start

The server needs Paper 1.21 or newer and Java 21. Two plugins have to be in plugins/ beside the jar; two more are optional and each buys something specific.

PluginDeclared asWhat for
ExyliaLibsoftdependRequired. Configuration, messages, menus, text, the palette, the database, tasks and placeholders all come from it. The plugin does not run without it.
packeteventsdependRequired. The server refuses to load the plugin without it; ExyliaLib runs its packet features through it.
PlaceholderAPIsoftdependOptional. Bridges %exyliachatcosmetics_<name>% into every other plugin, which is how a chat plugin draws the tag and the coloured name.
LuckPermssoftdependOptional. Rank colours are unusable without it, and permission changes apply within the tick rather than on relog.
Why the required one is a softdepend

softdepend is a load-order rule, not a statement about how optional something is. The comment in plugin.yml says it plainly:

plugin.yml
# Soft on purpose: a hard depend stops this plugin from loading when
# ExyliaLib is absent, and the loader that would install ExyliaLib is
# exactly what has to load for the install to happen.
softdepend: [ExyliaLib, PlaceholderAPI, LuckPerms]

A hard depend would refuse to load the plugin before the library could ever arrive, so soft here means "load me after it", not "you may leave it out".

What each optional plugin buys

PlaceholderAPI. ExyliaLib registers every placeholder under %exyliachatcosmetics_<name>% and bridges them into PAPI automatically. Inside ExyliaLib's own text they work regardless; PAPI is what makes them resolve in someone else's chat format, scoreboard or tab list. If the built-in chat module is doing the delivering, nothing needs the round trip and PAPI buys you nothing.

LuckPerms. A rank colour repaints the prefix and suffix your permission plugin already writes, and it reads them from LuckPerms' cached data. Without LuckPerms {prefix} and {suffix} draw nothing and the whole rank-colour screen is empty. LuckPerms also carries the groups an entry's groups list names, and its UserDataRecalculateEvent drops the memoised permissions and the render cache, so a rank that ran out takes its cosmetics off without a relog. Ownership by permission still works through Bukkit without it — the memo simply refreshes on join, on reload and on the 30-second sweep instead.

Installing

Drop in the jar

Copy ExyliaChatCosmetics.jar into plugins/, next to ExyliaLib.

Start the server

The first boot writes the configuration, the eight catalogue files, the six menus and the database tables. It may take longer than usual: plugin.yml declares the same driver list ExyliaLib does, and the server's own library loader fetches each one once and shares it, so the very first start needs an internet connection.

Check the console

You are looking for the ASCII banner with the plugin name, followed by Version: v1.0.0. If it never appears, ExyliaLib is missing.

Open the menu

/cc in game. exyliachatcosmetics.command.cosmetics is declared default: true, so every player already has it. See First steps.

Nothing about your chat changes yet

chat.module.enabled is false out of the box, so the plugin delivers no messages, owns no format and registers no chat commands. Whatever runs your chat today keeps running it. All the plugin does until you decide otherwise is style the message body through the hook and answer placeholders — and even that is only what the hook mode you pick allows. Turning the module on is a deliberate step, and it lives on the chat page.

What the first boot writes

Everything lives under plugins/ExyliaChatCosmetics/. Each file is copied out once and never overwritten, so your edits survive an update.

FileContents
config.ymlGenerated from the Settings record: the chat hook, the tag format, rank-colour repainting, the custom-tag and custom-colour rules, the loadout limit, the animation tick, the preview line, the menu strings, expiry and the placeholder fallbacks.
messages.ymlEvery player-facing line, generated from the CosmeticMessages record, plus the prefix that %prefix% expands to.
database.ymlExyliaLib's. Engine, credentials and pool size. H2 by default.

cosmetics/

The catalogue. These are not schema-managed on purpose — a catalogue is curated by hand and a schema writer keeps none of its comments — so what ships is a commented file you can read top to bottom.

FileHolds
cosmetics/tags.yml180 tags in seven tabs: symbols, animated, particles, icons, interface, flags, premium
cosmetics/nick-colors.yml90 nick colours in red, blue, yellow, green, advanced, premium
cosmetics/chat-colors.yml96 chat colours in the same six tabs
cosmetics/shadow-colors.yml90 shadow colours in natural, red, blue, yellow, green, premium
cosmetics/rank-colors.yml77 rank colours in solid, metal, gems, gradient, animated, staff
cosmetics/fonts.yml18 fonts in classic, typeset, modern, script
cosmetics/modifiers.ymlbold, italic, underlined, strikethrough and obfuscated, in one style tab
cosmetics/animations.yml18 animations, referenced by id from any of the files above

Six ExyliaLib menu files, compiled on load and recompiled by /cca reload.

FileOpens from
menus/main.yml/cc
menus/identity.ymlthe IDENTITY button — tags, nick colours, rank colours
menus/message.ymlthe MESSAGE button — chat colours, shadow colours, fonts, modifiers
menus/browser.ymlevery grid of cosmetics, one screen reused for every type
menus/favorites.ymlthe FAVORITES button
menus/loadouts.ymlthe LOADOUTS button

The chat/ tree

None of it is written on the first boot. The chat module builds its files the first time it starts, which is the first load or reload after chat.module.enabled: true.

FileHolds
chat/config.ymlThe module's own settings, generated from a record like config.yml is.
chat/messages.ymlThe module's player-facing lines.
chat/channels.ymlThe channels and their ranges, permissions and formats.
chat/formats/default.ymlThe line a message is drawn as.
chat/formats/msg.ymlThe same for private messages. Any other .yml you add to this folder is read too.
chat/filter/config.ymlCooldowns, similarity, flood, caps and the point budget.
chat/filter/rules.ymlWhat is blocked, and what each rule costs.
chat/filter/whitelist.ymlWords a rule must not match.
chat/filter/leet.ymlThe letter substitutions folded away before a rule looks at a message.
chat/filter/punishments.ymlWhat happens at each point total.
chat/text/emojis.yml:name: to the glyph it draws.
chat/text/grammar.ymlThe capitalisation and punctuation tidy-ups.
chat/announcements.ymlThe announcer's rotation.
A server upgrading from the loose layout

The filter used to be five loose files directly in chat/, beside emojis.yml and grammar.yml. They live in chat/filter/ and chat/text/ now, and the module moves each one into place on the first start after the change — an edited file keeps its edits, and anything already in the new place is left alone.

Database

By default the plugin uses H2: a file inside the plugin folder, with no server, no installation and no maintenance. Tables are created on first use and widened automatically when a column needs more room.

TableKeyHolds
chatcosmetics_profilesuuidname, equipped, favorites, active_loadout, created_at, updated_at
chatcosmetics_entitlementsgenerated idplayer_uuid, cosmetic_key, source, source_ref, granted_by, granted_at, expires_at, revoked_at, note
chatcosmetics_customgenerated idplayer_uuid, type, text, color, animation, created_at
chatcosmetics_loadoutsgenerated idplayer_uuid, name, slots, created_at
chatcosmetics_tokensgenerated idplayer_uuid, type, kind, amount, updated_at — one row per player, per kind of custom cosmetic, per create/edit balance

A sixth table, chatcosmetics_chat_players, belongs to the chat module and is created only once that module runs. It carries a player's channel, their ignore list and their moderation state.

To share cosmetics across several servers, point them at the same engine:

plugins/ExyliaChatCosmetics/database.yml
database:
  type: mysql
  settings:
    max-pool-size: 0
  mysql:
    host: 127.0.0.1
    port: 3306
    database: minecraft
    username: root
    password: ""

Supported engines: h2, mysql, mariadb, postgresql and mongodb. Only the block matching type is read, and an unrecognised value falls back to h2 and says so. The file is ExyliaLib's; its full reference is in the library's database page.

A profile is read once on join — the row, the grants, what the player made and their loadouts — and held in memory while they are online. Menu clicks mark the row dirty and a flush runs every five seconds, on quit and on shutdown. Drawing a chat line never touches the database.

Folia

plugin.yml declares folia-supported: true, and everything thread-bound goes through ExyliaLib's Tasks: profile writes and event firing land on the player's own region, the animation clock and the expiry sweep are asynchronous, and rendering a line or answering a placeholder reads memory only. Nothing extra to configure.

Updating

Replace the jar and restart. Your catalogue files, menus and chat files are kept untouched, and everything a player owns lives in the database.

config.yml and messages.yml are generated from records, so they are handled differently from the files that are merely copied: a key a new release adds appears with its default and its comment, and a key no record declares any more is removed on load and reported once as UNKNOWN_KEY. That is one-way — a typo in a key name is deleted rather than warned about for ever, so read the console after an update if you have been editing by hand.

Reloading

/cca reload

Permission exyliachatcosmetics.admin. It runs five steps in order, and a step that fails is reported while the rest still run:

StepRe-reads
configsconfig.yml and messages.yml, and the message prefix with them
catalogscosmetics/animations.yml and every catalogue file
menusall six menus/*.yml
chatturns the built-in module on or off to match chat.module.enabled, and reinstalls the chat hook
hookstries LuckPerms again, in case it registered its service after this plugin started

/exylialib reload is separate: it reloads the palette, and this plugin drops its render cache in response, so recolouring the server recolours tags and names without touching anything here.

Where to go next

Something missing on this page? Tell us on Discord