Compatibility
What the server has to be, what has to be installed beside it, where the data lives, and what this plugin deliberately does not do.
A bot is a real entity on a real server thread, not a packet illusion. That decides most of what this page says: what the server has to be, what has to load before it, and where a player's bot is written down.
What the server has to be
| Requirement | Detail |
|---|---|
| Minecraft 1.21.9 or newer | Every bot is an org.bukkit.entity.Mannequin, an entity type that only exists from 1.21.9, and the jar is compiled against paper-api:1.21.9. |
| Paper, or a Paper fork | The plugin builds against the Paper API and uses Paper-only surfaces: the knockback event, async teleports, profile creation for skins, and suppressing a death message. Plain Spigot is not enough. |
| Java 21 | The jar is compiled to release 21. |
plugin.yml declares 1.21 because that is the API generation the plugin targets. It is not the
version you can run it on: a 1.21.4 server loads the jar and then has no mannequin to spawn. The real
minimum is 1.21.9.
Folia
plugin.yml declares folia-supported: true, and for this plugin that claim is about scheduling, not
a compatibility badge.
Nothing here runs on a global tick loop. Work is scheduled where the thing it touches lives:
- A bot's own tick is scheduled at the bot, on the mannequin's entity scheduler, once per tick.
- A spawn is scheduled at the destination, not at the player who asked for it. Spawning an entity has to happen on the thread that owns the region it appears in, and a duel can put the bot at the far end of an arena.
- A respawn is scheduled at the owner, because that is who has to be checked for still being online and still being in an allowed world.
- A bot's health is republished every tick into a field anything can read, precisely because another plugin drawing a health bar cannot safely touch the entity from its own thread.
One consequence a server owner sees: a bot holds its own chunk open for as long as it exists. An entity only runs its scheduled work while its chunk is ticking, and a bot in a chunk nobody is ticking would stand there naked and still. The ticket is released the moment the bot is removed.
Because a bot's death happens on the bot's own thread, PracticeBotDeathEvent is fired as async on a
threaded server — which it genuinely is. Anything listening to it must treat it that way. See
API.
What has to be installed
| Plugin | Role |
|---|---|
packetevents | Hard dependency. depend: [packetevents] — the server will not load this plugin without it. Nothing in this plugin's own code calls it: PacketEvents is what ExyliaLib uses for effects, displays, holograms and NPCs. The hard depend is a load-order guarantee, not a sign that this plugin talks to packets itself. |
ExyliaLib | Soft dependency, on purpose. Everything the plugin is made of comes from it: configuration and migrations, messages and colour tokens, menus, actions, commands, tasks, reloads, the database, and the practice bot API contract itself. |
It reads backwards, and the reason is in plugin.yml's own comment. ExyliaLib is the dependency this
plugin genuinely cannot work without — which is exactly why it is soft. A hard depend would stop the
plugin loading on a server that does not have the library yet, and the loader that installs ExyliaLib
is precisely what has to load for that install to happen.
The plugin also declares the same runtime libraries list ExyliaLib does — the command framework, the
cache, the connection pool and every database driver — in the same order and the same versions. The
server's library loader fetches each one once and shares it, so naming a driver the plugin never opens
itself costs nothing, while missing one that ExyliaLib later needs is a server that starts and then
cannot write.
The database
Every player's bot is one row in practicebot_players, written through ExyliaLib's database module:
Databases.of(plugin).repository(PlayerData.class)Which means it follows whatever engine the library is configured with. This plugin names no engine of
its own and holds no connection of its own. H2, MySQL, MariaDB, PostgreSQL and MongoDB drivers are all
declared in the libraries block for the shared loader; the one that is actually used is the one
ExyliaLib's database configuration names.
The row is loaded when a player joins, saved and unloaded when they leave, and holds every setting a player can change: mode, difficulty, the two switches, the four armour pieces and their enchantments, both hands, knockback, strength, speed, fire resistance, and every number the advanced menu can nudge.
If you read the table directly: the difficulty column holds the combat mode, and the skill
column holds the difficulty. This is the 1.0 schema carried on purpose — the difficulty column
is what has always actually held the mode, and renaming it would cost every existing row its kit. The
four armour columns and their enchantment columns likewise keep their 1.0 names.
Servers sharing a database share bot profiles, and /bot export and /bot import <filename> move
them: an export writes a dump into plugins/ExyliaPracticeBotV3/dumps/, and an import reads a file
back out of the same folder. That is how you move between engines without touching SQL. Both are
exyliapracticebot.admin. See Commands.
What is not here
- No PlaceholderAPI placeholders. The plugin registers none. The
%...%names in the menu files are menu-scoped and resolved by the plugin itself; see Menus. - No world generation and no arenas. A bot is spawned where you are standing, in one of the worlds
listed under
worlds— which ships as Exylia's five sandbox worlds, not as an empty list — and nothing else about the world is this plugin's business. - No cross-server bots. A shared database shares the settings a bot is built from. The bot itself is an entity on one server.
Something missing on this page? Tell us on Discord