Nexal Boat Racing
A competitive boat racing minigame for Minecraft servers. Build tracks with a hotbar editor, run several races at once, and let players race laps through checkpoints with live positions, a per-player scoreboard, spectating, stats, leaderboards and reward commands for the podium.
Features
- Multiple Concurrent Arenas - Run different races simultaneously across multiple arenas
- Checkpoint Anti-Cheat - Players must pass checkpoints in order; crossing a later one first sends them back to the last one they passed
- Real-Time Position Tracking - Positions from laps, checkpoints and the distance to the next checkpoint, on a flicker-free per-player sidebar that only racers and spectators see
- Finish Line Detection - Line-segment intersection catches fast boats crossing between ticks
- Boat Freezing Warmup - Boats frozen on armor stands during the final countdown for fair starts
- Configurable Races - Laps, player counts, lobby countdown, warmup and finish timeout per arena, with server-wide defaults for new arenas
- Reward Commands - Console commands per finish position (economy rewards, items)
- Spectator Mode - Finished racers watch the rest of the race; anyone can watch with
/br spectate - Player Statistics GUI - Totals plus per-arena best times, best laps, races, wins and win rate
- Leaderboard GUI - Global wins and per-arena best times with tab switching and pagination
- Admin Menu -
/br adminopens a hub with the plugin configuration, the arenas, and export/import - Arena Settings GUI - Every race setting, the schedule, rewards, enabling and race control from one screen
- Arena Editor Mode - Hotbar tools for lobby, start positions, finish line and checkpoints, with a live action bar
- Crash-Safe Inventories - Racers', spectators' and editors' items are backed up on disk and given back after a crash
- Cron Scheduling - Open race lobbies automatically on a schedule
- Checkpoint Holograms - Numbered holograms above checkpoints, no hologram plugin needed
- PlaceholderAPI Integration - 37 placeholders for scoreboards, tab and chat
- Dual Storage - JSON file or MySQL/MariaDB, with export and import to move between them
Technologies
- Paper/Spigot API 1.20.1+ - Minecraft server API
- Utilizer - Configuration, messages, menus, tasks, holograms
- ACF - Command framework
- nexal-core - JSON and SQL persistence (MariaDB/MySQL)
- cron4j - Race schedules
- PlaceholderAPI - Placeholder integration (optional)
Commands
/boatracing also answers to /br and /racing.
Player Commands
| Command | Description | Permission |
|---|---|---|
/br or /br help [page] | Show help | nexalboatracing.use |
/br join [arena] | Join a race. Without an arena: joins the only open lobby, or opens the arena browser | nexalboatracing.join |
/br leave (or /br quit) | Leave your race or stop spectating; your items come back | nexalboatracing.join |
/br gui (or /br menu) | Open the arena browser | nexalboatracing.join |
/br spectate \<arena> | Watch a running race | nexalboatracing.spectate |
/br stats [player] | View player stats (GUI) | nexalboatracing.stats |
/br leaderboard [arena] (or lb, top) | Global wins, or the arena's best times (GUI) | nexalboatracing.stats |
Admin Commands
Admin commands are hidden from players without the nexalboatracing.admin permission.
| Command | Description | Permission |
|---|---|---|
/br admin | Open the admin menu (console: the admin help) | nexalboatracing.admin |
/br admin help [page] | Show the admin commands | nexalboatracing.admin |
/br admin create \<name> | Create an arena with the defaults from config.yml | nexalboatracing.admin |
/br admin delete \<arena> | Delete an arena (stops its race) | nexalboatracing.admin |
/br admin edit \<arena> | Enter arena editor mode | nexalboatracing.admin |
/br admin cancel | Leave arena editor mode | nexalboatracing.admin |
/br admin info \<arena> | Open the arena settings (GUI; console: text) | nexalboatracing.admin |
/br admin list | Arena management screen (GUI; console: text) | nexalboatracing.admin |
/br admin setcron \<arena> [cron] | Set the race schedule; without a schedule (or none) clears it | nexalboatracing.admin |
/br admin enable \<arena> | Enable an arena (it must be fully set up) | nexalboatracing.admin |
/br admin disable \<arena> | Disable an arena (stops its race) | nexalboatracing.admin |
/br admin start \<arena> | Open a race lobby | nexalboatracing.admin |
/br admin forcestart \<arena> | Start an open lobby's countdown below the minimum players | nexalboatracing.admin |
/br admin stop \<arena> | Stop the running race without results | nexalboatracing.admin |
/br admin reload | Reload configs (they also reload by themselves when the files change) | nexalboatracing.admin |
/br admin export | Export all data to plugins/NexalBoatRacing/export.json | nexalboatracing.admin |
/br admin import | Import plugins/NexalBoatRacing/export.json into the current storage | nexalboatracing.admin |
/br export and /br import, the paths up to 1.0.5, still work.
Permissions
| Permission | Description | Default |
|---|---|---|
nexalboatracing.use | /br and /br help | true |
nexalboatracing.join | Join and leave races, open the arena browser | true |
nexalboatracing.spectate | Watch a running race with /br spectate or from the arena browser | true |
nexalboatracing.stats | View stats and leaderboards | true |
nexalboatracing.admin | Admin commands and menus | op |
nexalboatracing.bypass.full | Join a race at its player limit, while a start position is free | op |
nexalboatracing.bypass.commands | Use any command during a race | op |
Arena Setup
From the admin menu
/br admin> Arenas > Create Arena, and type the name in chat- In the arena settings, click the lobby, start positions, finish line or checkpoints to open the editor
- Set laps, players, countdowns, the schedule and rewards on the same screen
- Click Disabled to enable the arena once it is ready
Using Editor Mode
- Create the arena:
/br admin create myarena - Enter editor mode:
/br admin edit myarena(your inventory is stored and given back when you leave) - Use the hotbar tools:
- Lime Bed (slot 1) - Right-click to set lobby at your position
- Armor Stand (slot 2) - Right-click to set start positions (auto-increments, up to 8). Shift-right to clear all
- Fence Gate (slot 3) - Right-click then left-click 2 blocks for finish line
- Ender Pearl (slot 4) - Right-click then left-click 2 blocks for checkpoint zones. Shift-right to clear all
- Paper (slot 5) - Shows live arena setup status
- Comparator (slot 8) - Opens the settings GUI for laps, players, rewards
- Barrier (slot 9) - Exit editor mode (restores inventory)
- Open settings GUI from editor or via
/br admin info myarena - Enable the arena from the settings GUI or via
/br admin enable myarena
An arena is ready once it has a lobby, a finish line and at least as many start positions as its minimum players. Checkpoints are optional. At most as many players can race as the arena has start positions, and an arena takes 2 to 8 players.
An arena cannot be edited while its race runs (stop it first), and no lobby opens on an arena an admin has open in the editor.
Without checkpoints (or with anti-cheat.require-checkpoints: false), a lap counts when the racer
crosses the finish line further than they ever did: driving back and forth over the line does not
add laps.
How a Race Runs
- Lobby - A lobby opens when a player joins an idle arena, an admin starts one, or its schedule fires. Joining stores your inventory, experience, game mode and location, and clears your inventory.
- Countdown - Once the minimum players have joined (or an admin force starts), the lobby countdown runs.
- Warmup - Racers are teleported to their start positions in frozen boats; the last five seconds are counted down on screen.
- Racing - Boats are released. Pass every checkpoint in order and cross the finish line to complete a lap. Crossing a later checkpoint before the next one sends the racer back to the last checkpoint they passed (
anti-cheat.respawn-on-skip). Positions rank laps first, then checkpoints, then the distance to the next checkpoint (or to the finish line once all are passed). - Finishing - After the first finisher, the others have the finish timeout to cross the line. Finished racers spectate the rest of the race when
spectator.enabledis on. - End - Results are broadcast, stats saved, everyone gets their state back, then the reward commands run.
Leaving with /br leave or disconnecting after the start counts as a DNF; the player gets their
items back at once and is free to play elsewhere. If the server stops mid-race (or while an
admin is in the editor), the items are given back when the player next joins.
Configuration
Most settings can be changed in game: /br admin > Configuration.
config.yml
# Storage type: "json" or "sql"
# To move your data: /br admin export, change the type, restart, /br admin import
storage:
type: json
sql:
host: localhost
port: 3306
database: minecraft
username: root
password: ""
# Settings a new arena starts with. Each arena changes its own in /br admin info \<arena>.
defaults:
min-players: 4
max-players: 8 # At most 8: an arena has up to 8 start positions
laps: 3
lobby-countdown: 30 # Seconds to wait before teleporting to start
warmup-countdown: 10 # Seconds boats are frozen before race starts
finish-timeout: 30 # Seconds to wait for remaining racers after first finishes
boat:
type: OAK # OAK, SPRUCE, BIRCH, JUNGLE, ACACIA, DARK_OAK, MANGROVE, CHERRY, BAMBOO, PALE_OAK (1.21.4+)
invincible: true # Boats can't break during race
step-height: 1.5 # How high boats can climb (1.0 = one block)
spectator:
enabled: true # Finished racers watch the rest of the race instead of waiting in their boat
allow-join: true # Anyone can watch a running race with /br spectate \<arena>
gamemode: SPECTATOR # SPECTATOR, or ADVENTURE (flying and invisible)
fireworks:
enabled: true
amount: 5 # Fireworks per podium finisher
# Colour names (YELLOW, ORANGE, WHITE, SILVER, RED, LIME, AQUA...) or hex (#CD7F32)
colors:
first: [YELLOW, ORANGE]
second: [WHITE, SILVER]
third: ["#CD7F32", ORANGE]
other: [WHITE]
broadcast:
race-start: true # Broadcast when race lobby opens
race-end: true # Broadcast race results
scope: GLOBAL # Race results: GLOBAL (everyone) or LOCAL (only racers and spectators)
# Scoreboard (the lines are in messages.yml, under scoreboard)
scoreboard:
enabled: true
title: "<#5DADE2>\<bold>BOAT RACING"
update-interval: 10 # Ticks between updates (20 ticks = 1 second)
anti-cheat:
require-checkpoints: true # A lap only counts after every checkpoint, in order
respawn-on-skip: true # Crossing a later checkpoint than the next one sends the racer back to the last one passed
# Whole command names racers may use during a race, besides /br leave (without the /)
command-whitelist: []
checkpoint-holograms:
enabled: true
height-above-ground: 4.0
lines:
- "<gradient:#5DADE2:#73B9E8>\<bold>Checkpoint #%number%\</bold>\</gradient>"
- "\<gray>%number% / %total%\</gray>"
The sidebar's lines are updated in place, so it does not flicker. Racers see a sidebar of their own for the race: a player on the server's main scoreboard gets a private copy of it (with its teams) until the race ends, so other players never see the race sidebar. A player another plugin gave a scoreboard keeps it, with the sidebar added.
messages.yml
Every message, the scoreboard lines, and the player menus (arena browser, leaderboard, stats)
are in messages.yml, in MiniMessage format with sounds and titles. Keys added in an update fall
back to the plugin's defaults until you add them to your file.
PlaceholderAPI Placeholders
Arena ids are matched ignoring case and may contain underscores. Placeholders asked for off the main thread (async chat, tab plugins) return the value worked out on the main thread a moment before.
Player Statistics
| Placeholder | Description |
|---|---|
%boatracing_wins% | Total wins |
%boatracing_races% | Total races |
%boatracing_finished% | Races finished (not DNF) |
%boatracing_podiums% | Total podium finishes |
%boatracing_dnfs% | Total DNFs |
%boatracing_winrate% | Win percentage |
%boatracing_finish_rate% | Finish percentage |
%boatracing_wins_\<arena>% | Wins in an arena |
%boatracing_races_\<arena>% | Races in an arena |
%boatracing_best_\<arena>% | Best race time in an arena |
%boatracing_best_lap_\<arena>% | Best lap time in an arena |
Current Race
| Placeholder | Description |
|---|---|
%boatracing_in_race% | "yes" when racing, else "no" |
%boatracing_spectating% | "yes" when spectating, else "no" |
%boatracing_current_arena% | Arena ID of the race you are in or watching |
%boatracing_current_state% | LOBBY, COUNTDOWN, WARMUP, RACING, FINISHING |
%boatracing_current_position% | Current position |
%boatracing_current_racers% | Racers in the race |
%boatracing_current_lap% | Current lap number |
%boatracing_current_laps% | Laps of the race |
%boatracing_current_checkpoint% | Last checkpoint passed this lap |
%boatracing_current_checkpoints% | Checkpoints per lap |
%boatracing_current_time% | Elapsed race time |
%boatracing_current_best_lap% | Your best lap this race |
Leaderboards
| Placeholder | Description |
|---|---|
%boatracing_top_wins_\<pos>_name% | Player name at position |
%boatracing_top_wins_\<pos>_count% | Win count at position |
%boatracing_top_\<arena>_\<pos>_name% | Player name in arena leaderboard |
%boatracing_top_\<arena>_\<pos>_time% | Best time in arena leaderboard |
%boatracing_top_\<arena>_\<pos>_wins% | Wins in arena leaderboard |
Global Info
| Placeholder | Description |
|---|---|
%boatracing_arena_count% | Total arena count |
%boatracing_active_races% | Currently active races |
%boatracing_arena_\<id>_status% | IDLE or the race state |
%boatracing_arena_\<id>_players% | Players in the arena's race |
%boatracing_arena_\<id>_max% | Arena max players |
%boatracing_arena_\<id>_min% | Arena min players |
%boatracing_arena_\<id>_laps% | Arena laps |
%boatracing_arena_\<id>_name% | Arena display name |
%boatracing_arena_\<id>_enabled% | "yes" or "no" |
Database Schema
With storage.type: json, everything is in plugins/NexalBoatRacing/data.json. With SQL, the
plugin creates these tables; list and map columns hold JSON.
boatracing_arenas
| Column | Type | Key | Description |
|---|---|---|---|
id | VARCHAR(64) | PK | Arena identifier |
displayName | VARCHAR(64) | Display name | |
world | VARCHAR(64) | World name | |
lobbyX, lobbyY, lobbyZ | DOUBLE | Lobby location | |
lobbyYaw, lobbyPitch | FLOAT | Lobby rotation | |
startPositions | TEXT | Start positions (JSON) | |
finishX1, finishY1, finishZ1 | DOUBLE | Finish line point 1 | |
finishX2, finishY2, finishZ2 | DOUBLE | Finish line point 2 | |
finishLineSet | BOOLEAN | Finish line defined | |
checkpoints | TEXT | Checkpoints (JSON) | |
laps | INT | Lap count | |
minPlayers | INT | Minimum players | |
maxPlayers | INT | Maximum players | |
lobbyCountdown | INT | Lobby countdown seconds | |
warmupCountdown | INT | Warmup countdown seconds | |
finishTimeout | INT | Finish timeout seconds | |
cronSchedule | VARCHAR(64) | Cron schedule | |
positionCommands | TEXT | Reward commands by position (JSON) | |
enabled | BOOLEAN | Arena enabled |
boatracing_races
| Column | Type | Key | Description |
|---|---|---|---|
id | VARCHAR(36) | PK | Race UUID |
arena_id | VARCHAR(64) | Arena identifier | |
start_time | BIGINT | Race start timestamp | |
end_time | BIGINT | Race end timestamp | |
laps | INT | Laps configured | |
participants | TEXT | Participants with results and lap times (JSON) | |
completed | BOOLEAN | Race completed |
boatracing_player_stats
| Column | Type | Key | Description |
|---|---|---|---|
playerId | VARCHAR(36) | PK | Player UUID |
player_name | VARCHAR(16) | Last known name | |
totalRaces | INT | Total races | |
wins | INT | Total wins | |
podiums | INT | Podium finishes | |
dnfs | INT | Did not finish | |
bestTimes | TEXT | Best times per arena (JSON) | |
bestLapTimes | TEXT | Best lap times per arena (JSON) | |
updated_at | BIGINT | Last update timestamp |
Key Legend: PK = Primary Key
Cron Schedule Examples
| Schedule | Description |
|---|---|
0 * * * * | Every hour on the hour |
0 12 * * * | Every day at noon |
0 18 * * sat,sun | Weekends at 6 PM |
*/30 * * * * | Every 30 minutes |
Installation
- Download the plugin JAR
- Place it in your server's
pluginsfolder - Start or restart the server
- Optionally install PlaceholderAPI for placeholders
- Set the storage in
plugins/NexalBoatRacing/config.yml(JSON works out of the box) - Create your first arena with
/br admin
Dependencies
- Paper or Spigot 1.20.1+
- PlaceholderAPI (optional, for placeholders)