Advancements
Challenge and achievement system with 9 tabbed categories, 36 trigger types, tree-positioned GUIs, toasts, sounds, points, console rewards, and per-player progress stored in the database. Everything is defined in advancement.yml.
How Players Use It
- Open the GUI:
/advancements(aliases:/adv,/achievements,/sdadv). Permission:shyamduels.advancements.use. - Pick a category tab (Duels, Ranked, Bed Fight, Bridge, FFA, Kits, Party, Challenges, Secrets) and click through the tree.
- Play normally. Progress counters fill automatically (for example
Win 10 duelsshows4/10). - On completion the player gets a toast popup, a chat message, and a sound. Big milestones also broadcast server-wide.
How To Make An Advancement
Work in plugins/ShyamDuels/advancement.yml. The file is versioned (config-version: 2) and missing keys are merged from the built-in defaults on load, so editing never wipes your entries.
- Decide the category. Use one of the 9 shipped categories or add your own block under
categories:(see the category table below). Note its key, for exampleDUELS. - Add an entry under
advancements:with a unique id, for examplemy_first_win:. - Wire it into the tree with
category:andparent:. Useparent: nullfor a root node, or the id of the previous advancement to chain it. The GUI position is computed automatically unless you setcoords.x/coords.y. - Describe it under
display:(title, description lines, icon material, frame). See the display table below. - Pick a difficulty (
EASYtoSECRET). It sets the label color and the default point value whenrewards.pointsis omitted. - Pick a trigger under
trigger:withtype:andamount:(how many times the event must fire). Full trigger list is in the trigger table below. - Narrow it with conditions (optional) under
trigger.conditions:, for exampleranked: trueorkit: "nethpot". Full condition list is in the condition table below. - Set rewards under
rewards:(points, console commands, extra message lines). Override announcements per advancement underannouncements:if needed. - Reload and test: run
/advancements reload(or/shyamduels reload-advancements), then/advancements listto confirm your id appears. Grant it to yourself with/advancements grant <player> <id>to preview the toast and rewards. Turn onsettings.debug: trueand reload if an entry does not show up; unknown trigger types log a warning and never complete.
Minimal Example (Copy And Edit)
yamladvancements:
my_bridge_debut:
category: BRIDGE
parent: "bridge_root"
display:
title: "<green>My Bridge Debut"
description:
- "<gray>Score your first Bridge goal."
icon:
material: "END_PORTAL_FRAME"
frame: TASK
difficulty: EASY
trigger:
type: BRIDGE_GOAL
amount: 1
rewards:
points: 25
commands:
- "give %player% diamond 3"
Placeholders usable in messages, commands, and lore: %player%, %uuid%, %advancement%, %advancement_id%, %category%, %difficulty%, %progress%, %required%, %percentage%, %rewards%.
settings: (7 Keys)
| Key | Default | What It Does |
|---|---|---|
settings.enabled | true | Master switch. When false the whole subsystem stays off. |
settings.debug | false | Logs load counts and warnings (for example unknown trigger types) to console. Turn on while building advancements. |
settings.disable-vanilla-advancements | false | Disables vanilla Minecraft advancements through the embedded advancement library. |
settings.suppress-vanilla-in-arena | true | Cancels vanilla advancement toast packets while a player is inside an arena world. |
settings.sound-volume | 1.0 | Fallback volume for completion sounds when an entry does not set its own. |
settings.sound-pitch | 1.0 | Fallback pitch for completion sounds when an entry does not set its own. |
settings.broadcast-prefix | [ACHIEVEMENT] gradient | Prefix prepended to server-wide broadcast lines. |
defaults: (Global Fallbacks)
| Key | Default | What It Does |
|---|---|---|
defaults.frame | TASK | Frame used when an advancement omits display.frame. |
defaults.show_toast | true | Toast popup used when an entry omits announcements.toast.enabled. |
defaults.progress.enabled | true | Shows current/required counters. Disable per entry with progress.enabled: false. |
defaults.announcements.chat.enabled | true | Sends the private completion message to the earning player. |
defaults.announcements.chat.message | checkmark line with [%advancement%] | Default private message lines. Supports all placeholders above. |
defaults.announcements.toast.enabled | true | Shows the vanilla-style toast popup. |
defaults.announcements.sound.enabled / .type / .volume / .pitch | true / UI_TOAST_CHALLENGE_COMPLETE / 1.0 / 1.0 | Completion sound. Volume and pitch fall back to settings.sound-volume / sound-pitch when omitted here. |
defaults.announcements.broadcast.enabled / .message | false / %player% unlocked [%advancement%] | Server-wide broadcast. Off by default; big milestones enable it per entry. |
defaults.how_to_get.enabled / .header | true / How to unlock: | Shows the hint section in the GUI. |
categories: (9 Shipped Tabs)
| Key | What It Does |
|---|---|
categories.<ID>.enabled | Shows or hides the whole tab. |
categories.<ID>.title | Tab title (MiniMessage, gradients allowed). |
categories.<ID>.description | String or list of lines shown on the tab icon. |
categories.<ID>.icon.material / .amount | Tab icon item (any Bukkit material name, MINECRAFT: prefix optional; EYE_OF_ENDER is accepted as ENDER_EYE). |
categories.<ID>.background | Vanilla texture path drawn behind the tree. |
categories.<ID>.root | Advancement id used as the tree root. If missing, the first entry with parent: null is used. |
Shipped tabs and roots: DUELS (duels_root), RANKED (ranked_root), BEDFIGHT (bedfight_root), BRIDGE (bridge_root), FFA (ffa_root), KITS (kits_root), PARTY (party_root), CHALLENGES (challenges_root), SECRETS (secrets_root). Category keys are uppercased on load. An advancement whose category does not exist falls back to the first loaded category.
advancements: (Per-Entry Keys)
| Key | What It Does |
|---|---|
enabled | Default true. Disabled entries are skipped entirely at load. |
category | Category key (default DUELS). Case insensitive. |
parent | Parent advancement id, or null for a root. Use quotes around ids. A missing parent still loads (positioned by the tree generator). |
display.title | Name shown in the GUI (MiniMessage). Falls back to the entry id. |
display.description | String or list of lore lines under the title. |
display.lore | Extra lore lines appended after the description. |
display.icon.material | Item material (default DIAMOND_SWORD). Also accepts legacy icon.material / icon paths. |
display.icon.amount | Stack size 1-64 (default 1). |
display.icon.skull.owner | Player name, %player%, or {player} for player-head icons. |
display.icon.custom_model_data | CustomModelData int for resource-pack icons (optional). |
display.frame | TASK, GOAL, or CHALLENGE (default from defaults.frame). |
display.background | Optional per-advancement background texture override. |
display.hidden (alias hidden) | Secret entry. Hidden nodes stay invisible until the player has progress on them, then appear. Used by the whole SECRETS tab. |
difficulty | EASY, NORMAL, HARD, VERY_HARD, EXTREME, LEGENDARY, SECRET (default NORMAL). Unknown values fall back to NORMAL. |
trigger.type | One of the 36 trigger types below (default DUEL_WIN). Unknown values become CUSTOM, log a warning, and never complete, so check spelling. |
trigger.amount (aliases required_amount, amount) | How many matching events are needed (minimum 1). Progress accumulates across matches. |
trigger.conditions (alias conditions) | Map of extra requirements. See the condition table below. Supports all: / any: / not: groups and typed type: / field: / value: rules. |
progress.enabled | Default from defaults.progress.enabled. Shows the counter on multi-step advancements. |
coords.x / coords.y (aliases x, y) | Optional fixed tree position. When omitted, the layout generator places the node (leaf-span with parent centering, normalized at zero or above). |
display.how_to_get / how_to_get.lines / how_to_get | Hint lines. how_to_get.enabled: false hides them for that entry. |
rewards.* / announcements.* | See the rewards table below. Per-entry announcements.chat/toast/sound/broadcast blocks override the global defaults. |
Difficulty Tiers And Default Points
When rewards.points is omitted, the tier value is used.
| Difficulty | Default Points | Used For |
|---|---|---|
EASY | 10 | First steps (first duel, first kill, first goal). |
NORMAL | 25 | Regular milestones (10 wins, first bed break). |
HARD | 50 | Demanding goals (50 wins, 100 FFA kills). |
VERY_HARD | 100 | Long grinds (100 wins, 500 FFA kills). |
EXTREME | 250 | Elite feats (500 wins, flawless duels). |
LEGENDARY | 500 | Legendary grinds (1,000 wins, 100 winstreak). |
SECRET | 1000 | Hidden easter eggs (whole SECRETS tab). |
Trigger Types (All 36)
amount counts matching events. Pair a counter trigger with a condition to scope it (for example DUEL_WIN plus ranked: true).
| Trigger | Fires When | Typical Amount |
|---|---|---|
DUEL_PLAY | A duel starts (any mode, add ranked: true or kit: to scope). | 1+ |
DUEL_WIN | The player wins a duel. | 1, 10, 50, 100, 500, 1000, 2000 |
DUEL_KILL | The player gets a duel kill (add weapon conditions below to scope). | 1+ |
DUEL_DEATH | The player dies in a duel. | 1+ |
WIN_STREAK | Winstreak reaches the amount (any mode unless ranked: true). | 5, 10, 15, 25, 30, 50, 100 |
KILL_STREAK | Killstreak reaches the amount. | 1+ |
RANK_TIER | Post-win Elo check passes elo_min (always amount: 1). | 1 |
ELO_GAIN | Post-win Elo gain event (ranked wins only). | 1+ |
BED_BREAK | An enemy bed is destroyed. | 1, 10, 25, 50, 100 |
BEDFIGHT_VOID_SURVIVE | A Bed Fight void fall is survived. | 1 |
BEDFIGHT_FIREBALL_LAUNCH | A Bed Fight fireball is launched. | 1 |
BEDFIGHT_FIREBALL_KILL | A Bed Fight kill comes from fireball impact or knockback. | 1 |
BEDFIGHT_TNT_JUMP | A Bed Fight TNT jump succeeds. | 1 |
BEDFIGHT_FINAL_KILL | An opponent with no bed left is eliminated. | 1 |
BRIDGE_GOAL | A Bridge goal is scored. | 1, 10, 25, 50, 100 |
BRIDGE_SHUTOUT | A Bridge shutout or clean match completes. | 1, 5 |
BRIDGE_PORTAL_JUMP | A Bridge portal jump or fast goal happens. | 1 |
BRIDGE_BLOCK_CLUTCH | A Bridge block clutch is executed. | 1 |
FFA_KILL | An FFA kill happens (add bounty, multikill, or iron_man to scope). | 1, 25, 100, 250, 500, 1000 |
FFA_KILLSTREAK | FFA killstreak reaches the amount. | 5, 10, 25, 50 |
FFA_SURVIVE | An FFA survival tick completes. | 1+ |
BOXING_COMBO | Hit combo reaches the amount without taking a hit back (mace kits). | 20, 50 |
SUMO_RINGOUT | A sumo-style ringout happens. | 1 |
FLAWLESS_WIN | A duel is won with zero damage taken (pair with flawless: true). | 1, 5 |
LOW_HEALTH_WIN | A duel is won at low health (pair with health_max: 2.0 or 1.0). | 1 |
COMEBACK_WIN | A match is won after losing early rounds (pair with comeback: true or clutch_win: true). | 1, 5 |
KIT_CUSTOMIZE | A kit layout is customized and saved in the Kit Editor. | 1 |
KIT_SHARE | A customized kit layout is shared with another player. | 1 |
PARTY_PLAY | A party match is played (add fight_type, split_win, ffa_win, or pvp_win to scope). | 1, 10, 25, 50 |
PARTY_SIZE | A party session starts (pair with party_size_min: 8 or 16). | 1 |
PLAYTIME | Fighting-time check passes playtime_hours (always amount: 1). | 1 |
STAT_TOTAL_KILLS | Lifetime kills reach the amount. | 1000 |
STAT_TOTAL_WINS | Lifetime wins reach the amount. | 500, 1000 |
SECRET_TRIGGER | A secret interaction happens (pair with secret_id). | 1, 25 |
CUSTOM | Never fired by gameplay. If you see this, the trigger name is misspelled. Fix the spelling. | - |
Conditions (Every Key)
Conditions live under trigger.conditions: (or conditions:). Flat keys combine with AND. Use all:, any:, and not: lists for logic, and typed rules (type: plus field: plus value:) for full control. Any other key falls back to generic payload matching: booleans must equal, numbers must be greater than or equal, text must equal ignoring case.
| Condition Key | Value | What It Checks |
|---|---|---|
kit (alias kit_name) | kit name | Only this kit, for example kit: "nethpot", kit: "bedwars", kit: "bridge", kit: "Stickfight", kit: "macepvp". |
ranked | true / false | Ranked-only or unranked-only matches. |
mode | mode name | Queue mode string from the match payload. |
arena (alias arena_name) | arena name | Only this arena. |
flawless (alias no_damage_taken) | true | No damage taken in the match. |
combo (alias combo_hits_min) | number | Minimum combo hits. |
health_max (alias health_lte) | health number | Health at or below the value (20.0 is full). Example: health_max: 2.0 is 1 heart, 1.0 is half a heart. |
health_min (alias health_gte) | health number | Health at or above the value. |
distance_min (alias distance_gte) | blocks number | Kill distance at or above the value, for example distance_min: 30.0 for longbow kills. |
winstreak_min (alias min_winstreak) | number | Minimum winstreak. |
killstreak_min (alias min_killstreak) | number | Minimum killstreak. |
elo_min (alias min_elo) | elo number | Elo at or above the value, for example elo_min: 1050 up to 2200. |
duration_max_seconds (alias time_max_seconds) | seconds | Match at or under this length, for example 10 for blitz wins or 60 for fast shutouts. |
duration_min_seconds (alias time_min_seconds) | seconds | Match at or over this length, for example 300 for 5-minute marathons. |
mace | true | Mace-based combat payload. |
shutout | true | Shutout payload (Bridge defense). |
void_clutch | true | Void-clutch payload. |
fireball_kill | true | Fireball-kill payload. |
tnt_jump | true | TNT-jump payload. |
comeback (alias reverse_sweep) | true | Comeback-win payload. |
party_size_min | number | Party at or above this size (8, 16). |
friends_min | number | Minimum friends count in the payload. |
bow_kill | true | Lethal hit was a bow shot (generic match). |
crossbow_kill | true | Lethal hit was a crossbow shot (generic match). |
sword_kill / axe_kill | true | Lethal hit used a sword or an axe (generic match). |
shield_broken | true | Enemy shield was disabled with an axe (generic match). |
fist_only | true | Won with bare hands only (generic match). |
wood_sword | true | Wooden-sword kill (generic match). |
final_round | true | Kill happened in the final round (generic match). |
rounds_won | number | Rounds won in the series, for example 2 for Best-of-3 or 3 for Best-of-5. |
fight_type | PARTY_SPLIT etc. | Party fight type, for example fight_type: "PARTY_SPLIT". |
split_win / ffa_win / pvp_win | true | Party split win, party FFA win, or party-vs-party win. |
speed_demon | true | Fast bed break (within 15 seconds of match start). |
clutch_win | true | Bed Fight win after your own bed was destroyed. |
fast_goal | true | Bridge goal within 8 seconds of round start. |
hat_trick | true | 3 consecutive Bridge goals in one match. |
bounty | true | Victim had a killstreak of 10 or higher (FFA). |
multikill | true | 2 kills within 4 seconds (FFA). |
iron_man | true | Kill while below 2 hearts (FFA). |
secret_id | id string | Secret interaction id, for example secret_good_game or secret_about_command. |
playtime_hours | hours | Minimum fighting hours (10, 50, 100). |
Logic Groups And Typed Rules
yamltrigger:
type: BOXING_COMBO
amount: 20
conditions:
any: # OR: macepvp kit OR mace kit OR mace payload
- kit: "macepvp"
- kit: "mace"
- mace: true
yamltrigger:
type: DUEL_KILL
amount: 1
conditions:
all: # AND group
- bow_kill: true
- ranked: true
not: # NOT group
- kit: "bridge"
Typed rules use type: plus field: plus value:: KIT_EQUALS (kit name equals), NUMERIC_GTE (payload number greater than or equal), NUMERIC_LTE (payload number less than or equal), BOOLEAN_IS (payload flag equals), STRING_EQUALS (payload text equals ignoring case). Example: {type: NUMERIC_GTE, field: elo, value: 2000}.
rewards: (Every Key)
| Key | Default | What It Does |
|---|---|---|
rewards.points | difficulty default (10 to 1000) | Point score for leaderboards and progress totals. |
rewards.currency | 0.0 | Economy amount granted on completion. |
rewards.commands | empty list | Console commands run on completion. Supports all placeholders above. |
rewards.message.enabled | true | Sends the extra reward lines below. |
rewards.message.lines (aliases rewards.message) | empty | Extra private message lines about the reward. |
announcements.sound.enabled / .type / .volume / .pitch (aliases sound.*) | global defaults | Per-entry sound override. |
announcements.toast.enabled (alias display.show_toast) | global default | Per-entry toast override. |
announcements.chat.enabled / .message | global defaults | Per-entry private message override. |
announcements.broadcast.enabled / .message | global defaults (off) | Per-entry server broadcast override. Enable for legendary milestones. |
Admin Commands And Permissions
| Command | Permission | What It Does |
|---|---|---|
/advancements (/adv, /achievements, /sdadv) | shyamduels.advancements.use | Opens the advancement GUI. |
/advancements list | shyamduels.advancements.admin | Lists loaded advancement ids. |
/advancements grant <player> <id> | shyamduels.advancements.admin | Grants an advancement (fires AdvancementGrantEvent, runs rewards). |
/advancements revoke <player> <id> | shyamduels.advancements.admin | Revokes an advancement and its progress. |
/advancements reset <player> | shyamduels.advancements.admin | Resets one player's progress. |
/advancements resetall | shyamduels.advancements.admin | Resets progress for all players. |
/advancements reload (also /shyamduels reload-advancements) | shyamduels.admin | Reloads advancement.yml and rebuilds the registry and tree layout. |
Behind The Scenes
- Storage: progress lives in the
player_advancementstable (progression, completed flag, timestamp) with a 60-second completed-count cache. Join loads are async; gameplay triggers run on the main thread; saves go through a per-player ordered write queue. - Events: completion fires
AdvancementGrantEvent(player plus advancement, informational, not cancellable). See Events Reference. - High-frequency guard: rapid triggers such as boxing combos are checked on a throttled path (every 5th event) to protect performance.
- Vanilla: the embedded advancement library renders tabs, nodes, progress bars, and toasts from its own SQLite file (
data/advancements.db).disable-vanilla-advancementsandsuppress-vanilla-in-arenacontrol vanilla behavior. - Placeholders:
%shyamduels_advancements_completed%and relatedadvancements_*variables expose totals. See PlaceholderAPI.
Entry missing from the GUI: check enabled: true, a valid category, and run /advancements reload with settings.debug: true. Advancement never completes: the trigger name is probably misspelled (it becomes CUSTOM and logs a warning). Progress not counting: the condition is too strict (for example a wrong kit name). Tree looks wrong: set explicit coords.x / coords.y or fix the parent chain and the category root.