6 releases, newest first.
notifications.challenge-reminder.interval-minutes (default 20), counted per player. A fresh joiner is left alone for join-delay-seconds (default 60); enabled: false stops it, live on /battlepass reload.notifications.challenge-reminder in lang/messages_en.yml, with {challenges} (ready) and {slots} (total). Emptying BOTH title and subtitle now sends no title at all instead of a blank one - that is how you make the reminder chat-only. MiniMessage works there, so the line can be made clickable with <click:run_command:'/battlepass challenges'>.%battlepass_challenges_ready%: the same count the reminder nudges with, for scoreboards.No command, permission or schema change. New config and language keys merge in on boot.
{boost} and {boost_factor} on the xp-info tile of guis/main.yml, and %battlepass_boost% / %battlepass_boost_factor% in the PlaceholderAPI family: the passive farming-XP boost the player's pets grant right now, in percentage points at one decimal (48.0 for x1.48, 0.4 for a level-1 pet, 0.0 with nothing equipped) and as the raw multiplier (1.48). Both read the exact value the farming drain pays with and answer from the first tick, before the player's pass data is loaded. Until now the only visible number was the per-tool XP rate, which a level-1 pet moves from 0.5 to 0.502.xp-info tile prints Pet boost: +{boost}%. A server updating from 2.3.0 keeps its own lore - add the line by hand.SnPets detected: farming-XP boost active line is now printed only when the boost really is wired. At runtime a boost lookup that throws is reported ONCE with the exception, then goes to /battlepass debug, so a repeating fault cannot flood the console at farm rate.integrations.sn-pets.* still needs a restart.pets challenge now counts SnPets box openings, read from that plugin's public API
(PetBoxRewardEvent) instead of the item's NBT. Nothing to configure: the listener registers
itself when SnPets is on the server.integrations.sn-pets band in config.yml: enabled, pet-id-prefix (default Pase_), percent-per-level, max-percent-base, max-percent-per-tier. SnLib merges it into existing installs on boot.min(level x percent-per-level, max-percent-base + (rarity - 1) x max-percent-per-tier), where the rarity is the number the pet id ends with. Each pet is capped on its own and the results are added, so a level-100 Pase_3 pays 24% -> x1.24.integrations.rival-pets.*, the new keys need a restart rather than a reload.-alt.yml layout, and presentation.alternate-gui-placeholder decided per viewer, on every open, which of the two they saw. The whole mechanism is gone: each menu now has exactly one layout that every player sees.guis/main-alt.yml, guis/challenges-alt.yml and guis/confirm-purchase-alt.yml.presentation.alternate-gui-placeholder config key. presentation.main is unchanged.Nothing to do, but two leftovers are worth knowing about. The removed key stays in your config.yml and the three *-alt.yml files stay in your guis/ folder - the updater never deletes what it no longer ships. Nothing reads either one, so they are harmless; delete them whenever you like.
If you had customised the -alt layouts, those edits are no longer used by anything. Move whatever you want to keep into the matching main.yml, challenges.yml or confirm-purchase.yml.
Permanent battle pass for Paper 1.20.x and 1.21.x. Players climb 54 tiers by farming and by completing challenges, every tier can pay a free reward and a premium one, and the premium track opens with a pass they buy in-game, receive from your store, or hold through a permission.
Battle Pass XP comes from two places. Farming blocks with EdTools pays passively at a configurable rate, with per-tool overrides so a late-game drill is worth more than a starter pickaxe. Challenges pay the rest.
The tier curve is base plus increment per tier, so the ladder is two numbers rather than a table you maintain by hand. XP, tier and pass all survive a relog and a restart, and the pass is permanent: there is no season to reset and nothing expires underneath a player.
rewards.yml holds one entry per tier with a free lane and a premium lane, and each lane is a list of console commands. Anything a plugin can give from the console can be a reward, so items, currency, crate keys, ranks and permissions all work with no extra wiring.
The premium lane is retroactive by design. Buying a pass does not write anything into a player's claim history; it changes which lane the gate lets them open, so every premium reward on a tier they already passed becomes claimable the moment they buy. Claiming is per lane and per tier, and a claim-all button takes everything currently open in one click.
Two of them, Gold and Diamond, priced in config.yml. The plugin never talks to an economy API directly: it reads a balance by expanding a PlaceholderAPI placeholder you configure and charges by running a console command you configure, so Vault, tokens, gems, points or any custom currency work unchanged. A balance that cannot be read is a distinct outcome from a balance that is too small, and economy.deny-on-unreadable-balance decides whether that refuses the purchase or lets the charge command have the final word.
Set passes.allow-purchase: false and the in-game purchase closes: the menu says the pass is sold elsewhere, and the direct purchase route refuses too, so a web store stays the only way in. snbattlepass.pass.gold and snbattlepass.pass.diamond grant a pass for as long as the permission is held and are never persisted, which makes a pass attached to a rank disappear cleanly when the rank does.
Five rotating slots per player, rolled from a weighted pool in challenges.yml. Eight types ship: crate keys (SnCrates), generator upgrades and repairs (SnGens), farming with a specific EdTools tool, playtime, pet boxes, offhand boxes, and envoy claims (SnEnvoys). Each pool entry carries its own weight, target range, duration, reward percentage and icon, and an entry you delete stays deleted.
Targets are ranges rather than fixed numbers, so the same entry rolls "mine 1,200 blocks" for one player and "mine 1,800" for another. Rewards are a percentage of the current tier requirement, which keeps a challenge worth roughly the same fraction of a tier at level 3 and at level 50.
Lower challenges.slots and the challenges already running in the removed slots freeze rather than vanish: they stop earning and stop paying, their wall-clock deadline keeps running, and raising the count back either resumes them or expires them on the next sweep.
Three of them - the pass, the challenges and the purchase confirmation - and each is an ordinary guis/*.yml with a title, rows, a character grid you lay out and items you can move, restyle or remove. Each also has an -alt.yml twin, and presentation.alternate-gui-placeholder picks between them per viewer, so a different layout can follow a rank, a world or anything else a placeholder can answer.
SQLite out of the box with nothing to configure, or MySQL by setting database.type and filling the connection block. Every read and write happens off the server thread.
RivalPets is optional: when installed, a pet's buff boosts farming XP. PlaceholderAPI is optional too, and exposes progress, tier, XP, pass, playtime and per-slot challenge state, so holograms, scoreboards and Discord bridges can read a player's pass without this plugin knowing they exist.
/battlepass opens the pass, /battlepass challenges opens the challenges. Aliases are bp and pass, and they are re-read on reload.
Admin side: setpass, removepass, settier, addtier, addxp, setxp, reset, resetall confirm, bypass, givechallenge and reroll. Everything that edits a player also works on offline players, reading their real stored row rather than a blank one. /battlepass reload, help and debug come with the framework, and every argument tab-completes.