Content generated with AI — it may contain mistakes.

Systems

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

RequirementDetail
Minecraft 1.21.9 or newerEvery 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 forkThe 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 21The jar is compiled to release 21.
`api-version: '1.21'` is a floor, not a promise

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

PluginRole
packeteventsHard 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.
ExyliaLibSoft 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.
The hard one is not the one it needs most

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.

Two columns are named for the opposite field

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