Nexal Duels
Competitive 1v1 dueling with a ranked ladder. Arenas are saved as WorldEdit schematics and pasted into their own void world for every duel. Players challenge each other through kit and arena menus, join a casual queue, or climb the ranked ladder from Iron to Challenger across seasons. Duels can carry a Vault bet held in escrow, every match is recorded, and crash recovery gives players their inventory back if the server stops mid-duel.
Features
- Schematic-Based Arenas - Arenas are saved as WorldEdit schematics and pasted into a void world (
nexal_duel_arenas) per duel, then cleared afterwards - In-Game Arena Editor - Hotbar tools for region selection, spawn points and a settings menu; arenas are enabled, edited and deleted from the admin menu
- Kit System - Admins save their inventory as a kit, with every enchantment, name and potion kept; players pick a kit before each duel
- Challenge System - Challenge any player with
/duel \<player>, pick a kit and arena in menus, accept or decline with clickable chat buttons - Queue Matchmaking - A per-kit FIFO queue that matches players automatically on a random arena
- Ranked Ladder - Five placement games, then LP (League Points) through Iron, Bronze, Silver, Gold, Platinum and Diamond (four divisions each), Master, and Challenger for the top Master players
- MMR Matchmaking - The ranked queue pairs players on the same kit with similar hidden MMR, widening the search the longer they wait
- Seasons - Create, start and end seasons by command or menu; each season has its own profiles and leaderboard
- Vault Betting - Bet money on a duel: both stakes are held in escrow, the winner takes the pot minus a configurable tax, and every cancelled or drawn duel is refunded
- Spectator Mode - Watch any duel in spectator mode and get your location, inventory and game mode back afterwards
- Stats and Match History - Wins, losses, win rate, K/D, streaks and betting totals, with paginated history menus for casual and ranked matches, and optional limits on how long and how many matches the history keeps
- Leaderboards - Casual and ranked leaderboard menus, and a leaderboard hologram placed with
/duels admin hologram set - Crash Recovery - Inventories and bets, including bets held for a challenge, are backed up to disk and restored when the player next joins; kit items cannot leave a duel through an ender chest
- Admin Dashboard -
/duels adminshows active duels and queue sizes, and opens arena, kit, ranked, configuration and data management - PlaceholderAPI Integration - 33 placeholders for stats, ranked standing, queues and leaderboards
- Dual Storage - JSON file or MySQL/MariaDB, with export and import to move between them
- Customizable Messages - Every message in
messages.yml, with MiniMessage gradients, sounds and titles
Technologies
- Paper API 1.20.1+ - Minecraft server API (Spigot compatible)
- Utilizer - Configuration, messages, menus, holograms, tasks
- ACF - Command framework
- nexal-core - JSON and SQL persistence (MariaDB/MySQL), arena cloning, help menus
- WorldEdit or FastAsyncWorldEdit - Arena schematics (required)
- Vault - Betting (optional)
- PlaceholderAPI - Placeholder integration (optional)
Commands
/duel, /d, /duels and /nexalduels are the same command: every subcommand below works with any of them.
Player Commands
| Command | Description | Permission |
|---|---|---|
/duels help [page] | Show the help | None |
/duel \<player> | Challenge a player (opens the kit and arena menus) | nexalduels.duel |
/duel challenge \<player> | Challenge a player | nexalduels.duel |
/duel bet \<player> \<amount> | Challenge with a money bet | nexalduels.bet |
/duel accept | Accept a challenge | nexalduels.duel |
/duel decline | Decline a challenge | nexalduels.duel |
/duels queue \<kit> | Join the queue for a kit | nexalduels.queue |
/duels leave | Leave the queue | nexalduels.queue |
/duels ranked \<kit> | Join the ranked queue for a kit | nexalduels.ranked |
/duels ranked queue \<kit> | Join the ranked queue for a kit | nexalduels.ranked |
/duels ranked leave | Leave the ranked queue | nexalduels.ranked |
/duels ranked | Your ranked stats | nexalduels.ranked |
/duels ranked stats [player] | Ranked stats menu | nexalduels.ranked |
/duels ranked top | Ranked leaderboard | nexalduels.ranked |
/duels ranked history [player] | Ranked match history | nexalduels.ranked |
/duels ranked seasons | All seasons and their leaderboards | nexalduels.ranked |
/duels stats [player] | Stats menu | nexalduels.stats |
/duels top | Leaderboard menu | nexalduels.stats |
/duels history [player] | Match history menu | nexalduels.stats |
/duels spectate \<player> | Spectate a player's duel | nexalduels.spectate |
/duels spectate leave | Stop spectating | nexalduels.spectate |
/duels kits | List the available kits | nexalduels.duel |
Stats, history and ranked commands also work for offline players who have played before.
Admin Commands
| Command | Description | Permission |
|---|---|---|
/duels admin | Open the admin dashboard (admin help from the console) | nexalduels.admin |
/duels admin help [page] | Admin help | nexalduels.admin |
/duels admin arena create \<name> | Create an arena and enter the editor | nexalduels.admin.arena |
/duels admin arena edit \<name> | Edit an arena in the editor | nexalduels.admin.arena |
/duels admin arena delete \<name> | Delete an arena and its schematic | nexalduels.admin.arena |
/duels admin arena list | List all arenas | nexalduels.admin.arena |
/duels admin kit create \<name> | Save your inventory as a kit | nexalduels.admin.kit |
/duels admin kit update \<name> | Replace a kit with your inventory | nexalduels.admin.kit |
/duels admin kit delete \<name> | Delete a kit | nexalduels.admin.kit |
/duels admin kit seticon \<name> \<material> | Set a kit's menu icon | nexalduels.admin.kit |
/duels admin kit enable \<name> | Enable a kit | nexalduels.admin.kit |
/duels admin kit disable \<name> | Disable a kit | nexalduels.admin.kit |
/duels admin kit preview \<name> | Preview a kit | nexalduels.admin.kit |
/duels admin kit list | List all kits | nexalduels.admin.kit |
/duels admin forcematch \<p1> \<p2> \<kit> | Start a duel between two players | nexalduels.admin.forcematch |
/duels admin forceend | End every active duel (bets refunded) | nexalduels.admin.forceend |
/duels admin hologram set | Place the leaderboard hologram where you stand | nexalduels.admin.hologram |
/duels admin hologram remove | Remove the leaderboard hologram | nexalduels.admin.hologram |
/duels admin reload | Reload config.yml and messages.yml | nexalduels.admin.reload |
/duels admin export | Export all data to the export/ folder | nexalduels.admin.export |
/duels admin import | Import data from the export/ folder | nexalduels.admin.import |
/duels admin ranked | Open the ranked admin menu | nexalduels.admin.ranked |
/duels admin ranked season create \<id> \<name> | Create a season | nexalduels.admin.ranked |
/duels admin ranked season start \<id> | Start a season (ends the current one) | nexalduels.admin.ranked |
/duels admin ranked season end | End the current season | nexalduels.admin.ranked |
/duels admin ranked setrating \<player> \<mmr> | Set a player's MMR | nexalduels.admin.ranked |
/duels admin ranked reset \<player> | Reset a player's ranked profile | nexalduels.admin.ranked |
The dashboard buttons check the same permissions as their commands; its Configuration screen needs nexalduels.config. Kit, arena, season and tier IDs are 1 to 32 letters, digits, _ or -.
Permissions
| Permission | Description | Default |
|---|---|---|
nexalduels.duel | Challenge, accept and decline duels | true |
nexalduels.bet | Bet on duels | true |
nexalduels.queue | Join the duel queue | true |
nexalduels.ranked | Join the ranked queue and view ranked stats | true |
nexalduels.stats | View stats, history and leaderboards | true |
nexalduels.spectate | Spectate duels | true |
nexalduels.admin | Open the admin dashboard and admin help | op |
nexalduels.admin.arena | Manage arenas | op |
nexalduels.admin.kit | Manage kits | op |
nexalduels.admin.forcematch | Force matches | op |
nexalduels.admin.forceend | Force end all duels | op |
nexalduels.admin.hologram | Place and remove the leaderboard hologram | op |
nexalduels.admin.reload | Reload the configuration | op |
nexalduels.admin.export | Export data | op |
nexalduels.admin.import | Import data | op |
nexalduels.admin.ranked | Manage seasons, tiers, ratings and profiles | op |
nexalduels.config | Edit settings in the admin menu | op |
Placeholders
Requires PlaceholderAPI.
| Placeholder | Description |
|---|---|
%nexalduels_wins% | Wins |
%nexalduels_losses% | Losses |
%nexalduels_winrate% | Win rate, e.g. 62.5% |
%nexalduels_win_streak% | Current win streak |
%nexalduels_best_win_streak% | Best win streak |
%nexalduels_kills% | Kills |
%nexalduels_deaths% | Deaths |
%nexalduels_kdr% | Kill/death ratio |
%nexalduels_matches% | Matches played |
%nexalduels_bets_won% | Total money won from bets |
%nexalduels_bets_lost% | Total money lost on bets |
%nexalduels_bets_profit% | Betting profit |
%nexalduels_in_duel% | true when in a duel |
%nexalduels_in_queue% | true when in the queue |
%nexalduels_in_ranked_queue% | true when in the ranked queue |
%nexalduels_opponent% | Current opponent's name |
%nexalduels_active_duels% | Duels in progress on the server |
%nexalduels_ranked_queue_size% | Players in the ranked queue |
%nexalduels_ranked_rank% | Rank this season, e.g. Gold II (Unranked in placements) |
%nexalduels_ranked_tier% | Tier name this season |
%nexalduels_ranked_lp% | LP this season |
%nexalduels_ranked_mmr% | Hidden MMR this season |
%nexalduels_ranked_wins% | Ranked wins this season |
%nexalduels_ranked_losses% | Ranked losses this season |
%nexalduels_ranked_placement_games% | Placement games played |
%nexalduels_ranked_placed% | true once placements are done |
%nexalduels_top_wins_\<pos>_name% | Name at a position of the wins leaderboard (1 to 10) |
%nexalduels_top_wins_\<pos>_value% | Wins at that position |
%nexalduels_top_streak_\<pos>_name% | Name at a position of the best streak leaderboard |
%nexalduels_top_streak_\<pos>_value% | Best win streak at that position |
%nexalduels_top_ranked_\<pos>_name% | Name at a position of this season's ranked leaderboard |
%nexalduels_top_ranked_\<pos>_rank% | Rank at that position |
%nexalduels_top_ranked_\<pos>_lp% | LP at that position |
Configuration
config.yml
# Storage settings
storage:
type: "json" # "json" or "sql"
sql:
host: "localhost"
port: 3306
database: "minecraft"
username: "root"
password: ""
history: # match history retention; player stats are never removed
max-age-days: 0 # delete matches older than this (0 = keep forever)
max-matches: 0 # keep the newest N casual and N ranked matches (0 = unlimited)
duel:
countdown: 5 # seconds before the fight starts
ending-duration: 2 # seconds before players are sent back
request-timeout: 30 # seconds to accept a challenge
max-duration: 300 # time limit, then a draw with bets refunded (0 = none)
allow-block-break: false
allow-block-place: true # placed blocks are cleared with the arena
allow-item-drop: false
enable-hunger: false
block-commands: true # only /duel commands and the whitelist work in a duel
command-whitelist: ["msg", "r", "tell"]
queue:
match-interval: 40 # ticks between matchmaking checks
betting:
enabled: true
min-bet: 10.0
max-bet: 10000.0 # 0 = no limit
tax-percent: 5.0 # share of the pot the server keeps
spectator:
enabled: true
hologram:
enabled: true # place it with /duels admin hologram set
refresh-interval: 30 # seconds
entries: 10
# location: written by /duels admin hologram set
fireworks:
enabled: true
count: 3
ranked:
enabled: true
starting-mmr: 1000
min-mmr: 100
placement-games: 5
placement-multiplier: 2.0 # placement games move MMR this many times faster
demotion-shield-games: 3 # games protected from demotion after a promotion
base-lp: 20
min-lp-change: 10
max-lp-change: 35
lp-per-division: 100
demotion-lp: 75 # LP kept after dropping a division
k-factor: # how fast MMR moves: new, medium, veteran players
new-player-games: 10
medium-player-games: 30
k-new: 40
k-medium: 30
k-veteran: 20
tiers: # lowest to highest; edit in-game under /duels admin ranked
iron: { display-name: "Iron", color: "&8", divisions: 4, min-mmr: 0, max-mmr: 799 }
bronze: { display-name: "Bronze", color: "&6", divisions: 4, min-mmr: 800, max-mmr: 949 }
silver: { display-name: "Silver", color: "&7", divisions: 4, min-mmr: 950, max-mmr: 1099 }
gold: { display-name: "Gold", color: "&e", divisions: 4, min-mmr: 1100, max-mmr: 1249 }
platinum: { display-name: "Platinum", color: "&b", divisions: 4, min-mmr: 1250, max-mmr: 1399 }
diamond: { display-name: "Diamond", color: "&9", divisions: 4, min-mmr: 1400, max-mmr: 1599 }
master: { display-name: "Master", color: "&5", divisions: 0, min-mmr: 1600, max-mmr: 99998 }
challenger: { display-name: "Challenger", color: "&c", divisions: 0, min-mmr: 99999, max-mmr: 99999 }
matchmaking:
match-interval: 40
base-range: 100 # starting MMR search range
expansion-interval: 10 # seconds between widenings
expansion-step: 50
max-range: 500 # players further apart are never matched
queue-display:
type: "bossbar" # or "actionbar"
bossbar:
title: "<gradient:#9B59B6:#A569BD>\<bold>RANKED\</bold>\</gradient> \<gray>|\</gray> \<yellow>%time%\</yellow>"
color: "PURPLE"
style: "SOLID"
actionbar:
message: "<gradient:#9B59B6:#A569BD>\<bold>RANKED\</bold>\</gradient> \<gray>|\</gray> \<yellow>%time%\</yellow>"
top-tier: # Challenger: the top Master players
enabled: true
recalculate-interval: 300 # seconds
promote-from: "master"
promote-to: "challenger"
min-lp: 0 # 0 = use top-player-count
top-player-count: 10
Most settings can also be changed in-game from /duels admin, under Configuration and Ranked. /duels admin reload applies every file change, the matchmaking and Challenger intervals included.
Match history retention
Every finished duel is recorded, so on a busy server the history keeps growing. Both limits are off by default. With max-age-days set, matches that ended longer ago are deleted; with max-matches set, only the newest that many casual matches and that many ranked matches are kept. Old records are removed at startup and after every finished duel, in the background, with JSON and SQL storage alike. Wins, losses, streaks, betting totals and ranked LP, rank and MMR are stored separately and never change. Both limits are also under Configuration in /duels admin.
Ranked in short
A player's first five ranked matches each season are placement games: they move the hidden MMR (twice as fast as normal) and then place the player in the tier whose MMR range holds it. From there each win or loss moves LP, scaled by how likely the result was; 100 LP moves a player up a division, and dropping below 0 moves them down one. Master has no divisions and no LP cap, and the top ten Master players by LP are made Challenger every five minutes.
messages.yml
All messages are customizable with MiniMessage gradients, sounds and titles, including the clickable accept and decline buttons (request-buttons) and the leaderboard hologram (leaderboard-hologram). Messages added in an update are taken from the bundled file until you add them to yours.
Database Schema
When using SQL storage, the plugin creates the following tables. JSON storage keeps the same records in data.json.
duel_arena_templates
| Column | Type | Key | Description |
|---|---|---|---|
id | VARCHAR(64) | PK | Arena ID |
type | VARCHAR(32) | Always SCHEMATIC | |
world_name | VARCHAR(255) | Unused for schematic arenas | |
schematic_path | VARCHAR(255) | Schematic file, relative to the plugin folder | |
target_world | VARCHAR(255) | Void world the arena is pasted into | |
metadata | TEXT | JSON: origin, spawns (spawn1, spawn2, spectator), bounds | |
enabled | BOOLEAN | Whether duels can use the arena | |
display_name | VARCHAR(64) | Name shown in menus | |
icon_material | VARCHAR(64) | Menu icon |
duel_kits
| Column | Type | Key | Description |
|---|---|---|---|
id | VARCHAR(64) | PK | Kit ID |
display_name | VARCHAR(64) | Name shown in menus | |
icon_material | VARCHAR(64) | Menu icon | |
contents_base64 | TEXT | Inventory items, Bukkit-serialized and Base64-encoded | |
armor_base64 | TEXT | Armor items | |
offhand_base64 | TEXT | Offhand item | |
enabled | BOOLEAN | Whether players can pick the kit | |
slot_order | INT | Position in the kit menu |
duel_players
| Column | Type | Key | Description |
|---|---|---|---|
id | VARCHAR(36) | PK | Player UUID |
player_name | VARCHAR(32) | Last known name | |
wins | INT | Wins | |
losses | INT | Losses | |
win_streak | INT | Current win streak | |
best_win_streak | INT | Best win streak | |
kills | INT | Kills | |
deaths | INT | Deaths | |
total_bets_won | DOUBLE | Money won from bets | |
total_bets_lost | DOUBLE | Money lost on bets | |
last_played | BIGINT | Last duel timestamp | |
created_at | BIGINT | First duel timestamp |
duel_matches
| Column | Type | Key | Description |
|---|---|---|---|
id | VARCHAR(36) | PK | Match ID |
player1_id | VARCHAR(36) | First player (the challenger) UUID | |
player2_id | VARCHAR(36) | Second player UUID | |
winner_id | VARCHAR(36) | Winner UUID (empty for a draw) | |
arena_id | VARCHAR(64) | FK → duel_arena_templates.id | Arena |
kit_id | VARCHAR(64) | FK → duel_kits.id | Kit |
bet_amount | DOUBLE | Bet per player | |
duration_seconds | INT | Fight length | |
start_time | BIGINT | Start timestamp | |
end_time | BIGINT | End timestamp |
duel_ranked_seasons
| Column | Type | Key | Description |
|---|---|---|---|
id | VARCHAR(64) | PK | Season ID |
display_name | VARCHAR(64) | Season name | |
start_time | BIGINT | Start timestamp | |
end_time | BIGINT | End timestamp (0 while running) | |
active | BOOLEAN | Whether this is the current season |
duel_ranked_profiles
| Column | Type | Key | Description |
|---|---|---|---|
id | VARCHAR(100) | PK | \<player UUID>:\<season ID> |
player_id | VARCHAR(36) | Player UUID | |
player_name | VARCHAR(32) | Last known name | |
season_id | VARCHAR(64) | FK → duel_ranked_seasons.id | Season |
mmr | INT | Hidden matchmaking rating | |
lp | INT | League Points | |
tier | VARCHAR(32) | Tier ID | |
division | INT | Division (1 = I, highest) | |
demotion_shield | INT | Protected games left | |
placement_games | INT | Placement games played | |
placement_wins | INT | Placement games won | |
placed | BOOLEAN | Placements done | |
peak_tier | VARCHAR(32) | Highest tier reached | |
peak_division | INT | Highest division reached | |
peak_lp | INT | LP at the peak | |
ranked_wins | INT | Ranked wins | |
ranked_losses | INT | Ranked losses | |
ranked_win_streak | INT | Current ranked win streak | |
ranked_best_win_streak | INT | Best ranked win streak | |
ranked_kills | INT | Ranked kills | |
ranked_deaths | INT | Ranked deaths | |
last_lp_change | INT | LP change of the last match | |
last_played | BIGINT | Last ranked match timestamp |