What is ServerWrapped?
ServerWrapped is a lightweight Paper plugin that turns the statistics your server already collects into funny, personal, shareable recaps — like Spotify Wrapped, but for your SMP. Every player can run /wrapped and instantly get a framed chat card with their playtime, mining stats, deaths, a personality archetype, a generated title, unlocked achievements, and a "top X% on this server" rank.
It needs no database, no bot, no other plugins, and no client mods. Everything works out of the box; Discord posting is a single config line.
✦ JUSTIN'S SERVER WRAPPED ✦
Season: June 2026
──────────────────────────────────────────────────
▸ You played for 42.3 hours.
▸ You mined 12,442 blocks, mostly Stone.
▸ You placed 8,901 blocks.
▸ You traveled 118.4 km.
▸ You defeated 312 mobs, mostly Zombie.
▸ You died 37 times. The respawn screen knows you personally.
▸ Totems saved you 3 times.
▸ Your natural predator was: Entity Attack Creeper.
▸ You're in the top 5% of miners on this server.
▸ You unlocked 6 achievements. (hover)
▸ Your personality type: Cave Goblin.
▸ Your server title: Minister of Questionable Mining Decisions.
──────────────────────────────────────────────────
[ ✦ Share in Chat ] [ Open as Book ] [ Copy for Discord ] [ ▶ Play Reveal ]
Quick start for server owners
- Download the jar and drop it into your server's
plugins/folder. - Restart the server. The data folder and
config.ymlare created automatically. - Done — players can use
/wrappedimmediately (lifetime stats).
Two optional upgrades:
- Seasons: run
/wrapped season start june-2026 June 2026so recaps cover only the current season/month. See the seasons guide. - Discord: paste a channel webhook URL into
config.ymlso shares and season recaps get posted automatically. See Discord integration.
Requirements: Paper 26.2 or compatible Java 26 No dependencies
Your Wrapped card Players
Run /wrapped to see your personal card. Every line describes one part of your journey during the current season (or your whole lifetime on the server, if no season is active). Hover over the lines — many of them hide extra details:
| Card line | What it shows | Hover detail |
|---|---|---|
| You played for … | Total time spent on the server | How many days of your life that is |
| You mined … | Total blocks broken and your most-mined block | Total blocks touched + diamond ore mined |
| You placed … | Total blocks placed | — |
| You traveled … | Total distance across all modes of travel | Breakdown: on foot / elytra / swimming / riding |
| You defeated … | Mob kills and your most-killed mob | Player kills, damage dealt, damage taken |
| You died … | Death count with a roast comment (severity is configurable) | Your top 3 death causes |
| Totems saved you … | Totem of Undying pops (only shown if > 0) | — |
| Your natural predator | The thing that killed you most often | — |
| You're in the top X% … | Your best server rank | All your ranks (miner, builder, explorer, fighter, most active) |
| You unlocked X achievements | Achievement count | Up to 10 achievement names with descriptions |
| Your personality type | Your archetype | The archetype's flavor text |
| Your server title | A generated title based on your archetype (stable per player) | — |
The footer has three clickable buttons: [ ✦ Share in Chat ] posts a compact recap publicly, [ Open as Book ] opens the book version, and [ Copy for Discord ] copies an emoji share card to your clipboard.
The book version Players
/wrapped book opens your recap as a written book — one theme per page, like flipping through Wrapped stories:
- Title page — your name and the season
- Time played — hours and days of your life
- Mining & building — mined/placed totals, top block, diamond ore
- Adventure — travel distance breakdown and jumps
- Survival — deaths, natural predator, mob kills, totems
- Odd jobs — fishing, breeding, trading, enchanting, raids, sleep
- Achievements — up to 8 unlocked achievements
- Your type — archetype and server title
- Outro — that's a wrap!
The reveal show Players
/wrapped reveal plays your recap as a dramatic slideshow instead of showing everything at once — the same suspense as Spotify's Wrapped stories. One stat appears every few seconds with a sound effect, the death line lands with an anvil clang, and after a cliffhanger ("And your personality type this season is…") your archetype is revealed with a full-screen title and the challenge-complete fanfare.
The whole show takes about 20 seconds and ends with the usual share buttons. You can also start it from the [ ▶ Play Reveal ] button on your card. It's the perfect way to experience Wrapped Day together in a voice call — everyone runs it at the same time and reacts to their reveals live.
Sharing your Wrapped Players
In-game: /wrapped share
Posts a one-line compact recap to public chat, visible to everyone online:
Justin's June 2026 Wrapped: 42h played | 12.4k mined | 37 deaths | 6 achievements | top 5% miner | Cave Goblin
There is a per-player cooldown (60 seconds by default) so chat doesn't get flooded. If the server has Discord integration enabled, your share is also posted to the server's Discord channel as a rich embed with your player head.
Anywhere else: the "Copy for Discord" button
Click [ Copy for Discord ] on your card and a full emoji version is placed on your clipboard — paste it into Discord, WhatsApp, or anywhere else:
✨ Justin's June 2026 Wrapped ✨
⏰ 42 hours played
⛏️ 12,442 blocks mined (mostly Stone)
💀 37 deaths — natural predator: Entity Attack Creeper
🎖️ 6 achievements unlocked
📊 Top 5% miner on this server
🏆 Cave Goblin — "Minister of Questionable Mining Decisions"
Head-to-head compare Players
/wrapped compare <player> puts your wrapped next to another player's, category by category: playtime, blocks mined and placed, distance, mob kills, fish, totem saves, achievements, and deaths (where fewer wins). The better value in each row is highlighted with a gold ✦, and a verdict line declares the overall winner:
Bermint vs SirLagsalot (June 2026)
▸ Playtime: 112 hours ✦ vs 61 hours
▸ Blocks mined: 42,000 ✦ vs 8,120
▸ Deaths: 37 ✦ vs 94
…
Verdict: Bermint takes it, 7–2.
During a season the comparison uses season stats, so a rematch next season starts from zero. From console (or to compare two other players) use /wrapped compare <player> <player>. Categories neither player has touched count for nobody.
Server ranks — "top X%" Players
Just like Spotify's "top 0.05% of listeners", ServerWrapped ranks you against everyone who has played on the server, in five categories:
| Rank | Based on |
|---|---|
| Most active | Playtime |
| Miner | Blocks mined |
| Builder | Blocks placed |
| Explorer | Total distance traveled (all modes) |
| Fighter | Mob kills + player kills |
Your card shows your best rank; hover over the line to see all five. The math: if you rank 3rd of 60 players in mining, you're in the top 5%. Ranks only appear once the server knows at least 3 players, and only for categories where your value is above zero. During a season, ranks compare season stats, not lifetime stats. Server owners can disable ranks entirely (percentiles.enabled).
All 31 achievements Players
Achievements are computed from your stats for the current wrapped period — during a season, only what you did that season counts, so every season is a fresh hunt. They appear on your card (hover the achievements line), in the book, and in your share texts.
Tiered achievements
These come in pairs — you always get the strongest tier you qualify for, never both:
| Achievement | How to unlock |
|---|---|
| 🏅 Marathon Gamer | Play 100+ hours (replaces The Regular) |
| 🏅 The Regular | Play 24+ hours |
| 💎 Diamond Hands | Mine 64+ diamond ore — a full stack (replaces Lucky Miner) |
| 💎 Lucky Miner | Mine 10+ diamond ore |
| 🛡 Nine Lives | Get saved by totems 9+ times (replaces Clutch Master) |
| 🛡 Clutch Master | Get saved by a totem at least once |
| ⚔ Menace to Society | 10+ player kills (replaces First Blood) |
| ⚔ First Blood | At least 1 player kill |
| 🗡 Exterminator | 500+ mob kills (replaces Monster Hunter) |
| 🗡 Monster Hunter | 100+ mob kills |
Mining, building & time
| Achievement | How to unlock |
|---|---|
| Demolition Expert | Break 10,000+ blocks |
| Master Builder | Place 10,000+ blocks |
| Terraformer | Break + place 50,000+ blocks combined |
Survival & combat
| Achievement | How to unlock |
|---|---|
| Untouchable | Zero deaths with at least 5 hours of playtime |
| Gravity's Favorite | Die to fall damage 5+ times |
| Extra Crispy | Burn to death (lava/fire) 3+ times |
| Creeper Magnet | Get blown up by creepers 3+ times |
| Berserker | Deal 50,000+ damage points |
| Human Shield | Absorb 50,000+ damage points |
Travel
| Achievement | How to unlock |
|---|---|
| Globetrotter | Travel 500+ km in total |
| Ace Pilot | Fly 50+ km with an elytra |
| Road Tripper | Ride 20+ km by boat, horse, or minecart |
| Part-Time Fish | Swim 5+ km |
| Bunny Hopper | Jump 50,000+ times |
Odd jobs
| Achievement | How to unlock |
|---|---|
| Rod Enthusiast | Catch 50+ fish |
| Barnyard Cupid | Breed 50+ animals |
| Shopaholic | Trade with villagers 50+ times |
| Bookworm | Enchant 25+ items |
| Hero of the Village | Win at least one raid |
| Well Rested | Sleep through 50+ nights |
| Sweet Tooth | Eat 7+ cake slices — a whole cake |
All 17 archetypes & titles Players
Your archetype is your Wrapped personality — chosen by comparing every part of your playstyle against thresholds and picking the trait where you stand out the most (the stat that exceeds its threshold by the largest factor wins). Each archetype has three possible server titles; which one you get is stable for your account, so your title doesn't change every time you look.
| Archetype | Typical trigger | Flavor | Possible titles |
|---|---|---|---|
| Cave Goblin | 5,000+ blocks mined and at least twice as much mined as placed | Sees a mountain, sees a personal problem. | Minister of Questionable Mining Decisions · Undersecretary of the Deepslate Department · Chief Executive of Digging Straight Down |
| Professional Victim | 20+ deaths | The respawn button has muscle memory now. | Director of Repeated Learning Experiences · Five-Star Reviewer of the Respawn Screen · Loyal Customer of the Death Message |
| Certified Menace | 5+ player kills | PvP logs remember your name. | Local Threat With a Sword · Head of Unprovoked Diplomacy · Regional Generator of PvP Complaints |
| Sleep Deprived Builder | 4,000+ blocks placed | One more block. Always one more block. | Chairperson of Placing One More Block · Licensed Scaffolding Abuser · Architect of 3 AM Decisions |
| The Tourist | 20+ km total travel distance | Has seen more of the map than the actual base. | Cartographer of Places Nobody Asked About · Long-Distance Nowhere Enthusiast · Honorary Citizen of the World Border |
| Walking Insurance Claim | 3+ "accident" deaths (falls, lava, explosions, creepers) making up a big share of all deaths | Gravity and lava are not your friends. | Chief Executive of Gravity Testing · Preferred Customer of Lava Insurance · Field Researcher of Fall Damage |
| Mob Accountant | 100+ mob kills | Keeps the local mob population in check, aggressively. | Hostile Mob Performance Reviewer · Regional Manager of Pest Control · Auditor of the Local Zombie Census |
| Frequent Flyer | 10+ km elytra flight | Owns a bed but lives in the sky. | Platinum Member of the Firework Airline · Air Traffic Control's Biggest Problem · Chief Officer of Unscheduled Landings |
| Master Angler | 100+ fish caught | The fish fear the sound of your boots. | Supreme Commander of the Fishing Rod · Negotiator With the Local Cod Union · Part-Time Ocean Vacuum |
| Animal Matchmaker | 100+ animals bred | Runs the server's most successful dating service. | Chief Cupid of the Cow Pen · Director of Suspicious Wheat Distribution · Head of Barnyard Human Resources |
| Emerald Capitalist | 75+ villager trades | Has personally destabilized the villager economy. | Chairman of the Emerald Reserve · Founder and Sole Shareholder of Big Trade · Chief Exploiter of Librarian Labor |
| Enchantment Gremlin | 50+ items enchanted | Hoards XP like it's a personality trait. | Keeper of Suspicious Glowing Books · Level 30 and Emotionally Attached · Grand Curator of Lapis Expenses |
| Village Hero | 3+ raids won | Shows up whenever the bell rings. | Honorary Employee of the Month, Every Month · Chief of Volunteer Pillager Removal · Bearer of the Discount Crown |
| Professional Sleeper | 100+ nights slept | Speedruns the night, every night. | Regional Champion of Skipping the Night · Chief Officer of Immediate Bedtime · Certified Phantom Prevention Specialist |
| Second Chance Collector | 10+ totem saves | Death keeps sending invoices, totems keep paying. | Chief Beneficiary of Totem Insurance · Collector of Refunded Deaths · Preferred Client of the Evoker Estate |
| The Unkillable | Zero deaths with 10+ hours playtime | The death screen is just a rumor to you. | Certified Immortal, Terms and Conditions Apply · The Respawn Screen's Greatest Mystery · Undefeated Champion of Staying Alive |
| Peaceful Farmer | Fallback when nothing stands out | Just here for the vibes and the crops. | Reliable Citizen With Suspiciously Normal Stats · Keeper of the Quiet Life · Chief Vibes Officer |
Leaderboards Players
/wrapped top <category> shows the server's top players (5 by default). During a season, leaderboards rank season stats. Available categories and their aliases:
| Category | Aliases | Measures |
|---|---|---|
deaths | death | Deaths |
playtime | time | Time played |
mined | mine | Blocks broken |
placed | place | Blocks placed |
kills | kill | Mob + player kills |
distance | walk | Total distance (all travel modes) |
damage_taken | damage | Damage absorbed |
damage_dealt | dealt | Damage dealt |
elytra | fly, aviate | Elytra distance |
fish | fishing, fish_caught | Fish caught |
bred | breeding, animals_bred | Animals bred |
trades | trading, traded | Villager trades |
jumps | jump | Jumps |
totems | totem | Totem saves |
There's also /wrapped server — the whole server's combined recap: total playtime, blocks, distance, deaths with the most common cause, and the most active player.
Wrapped Day & /wrapped recap Players
When a season ends, that's Wrapped Day:
- Everyone online gets a ✦ WRAPPED DAY ✦ announcement with a clickable [ Open your Wrapped ] button (plus a celebration sound).
- Your stats for that season are frozen at the moment it ends — anything you do afterwards doesn't change your recap.
/wrapped recapshows your final season recap for the next 7 days (configurable). The card is labeled "(Final)".- If you weren't online for the drop, you get a one-time clickable reminder the next time you join.
- If Discord integration is on, the server-wide recap is posted to Discord automatically.
Installation Owners
- Requirements: a Paper 26.2-compatible server running Java 26. Spigot is not supported (the plugin uses Paper's Adventure API).
- Place
ServerWrapped-1.0.jarinplugins/and restart (a reload also works, but restart is recommended). - On first start the plugin creates
plugins/ServerWrapped/config.ymland the data folders. No further setup is required.
Building from source: ./gradlew build produces build/libs/ServerWrapped-1.0.jar; ./gradlew test runs the unit tests; ./gradlew runServer spins up a local test server with the plugin installed.
Commands Owners
| Command | What it does | Permission | Who |
|---|---|---|---|
/wrapped | Your personal wrapped card | serverwrapped.use | Players |
/wrapped book | Your wrapped as a flip-through book | serverwrapped.use | Players |
/wrapped reveal | Plays your recap as a dramatic slideshow with sounds and titles | serverwrapped.use | Players |
/wrapped share | Posts a compact recap to chat (and Discord, if configured); respects the cooldown | serverwrapped.share | Players |
/wrapped compare <player> [player] | Head-to-head comparison; one name compares against yourself, two names compare any two players | serverwrapped.compare | Anyone |
/wrapped top <category> | Leaderboard for one of 14 categories | serverwrapped.top | Anyone |
/wrapped server | The whole server's combined recap | serverwrapped.top | Anyone |
/wrapped recap | Your final recap of the last ended season (within the recap window) | serverwrapped.use | Players |
/wrapped <player> | Another player's wrapped card (works from console too) | serverwrapped.view.other | Staff |
/wrapped season | Shows the current season's state | serverwrapped.admin | Admins |
/wrapped season start <id> <name…> | Starts a season and snapshots all known players as the baseline | serverwrapped.admin | Admins |
/wrapped season end | Ends the season: freezes final stats, announces Wrapped Day, posts the Discord recap | serverwrapped.admin | Admins |
/wrapped reload | Reloads config.yml | serverwrapped.admin | Admins |
/wrapped help | Lists the commands you have access to | — | Anyone |
/serverwrapped is an alias for /wrapped. Full tab completion is included, including known player names and season ID suggestions.
Permissions Owners
| Permission | Default | Grants |
|---|---|---|
serverwrapped.use | everyone | /wrapped, /wrapped book, /wrapped recap |
serverwrapped.share | everyone | /wrapped share |
serverwrapped.compare | everyone | /wrapped compare |
serverwrapped.top | everyone | /wrapped top, /wrapped server |
serverwrapped.view.other | ops | /wrapped <player> |
serverwrapped.admin | ops | Season management and /wrapped reload |
Configuration reference Owners
Everything lives in plugins/ServerWrapped/config.yml. Apply changes with /wrapped reload. Invalid values fall back to safe defaults — the plugin never crashes on bad config.
prefix: "<gold><bold>ServerWrapped</bold></gold> <dark_gray>></dark_gray> "
season-mode: false
current-season-id: ""
last-ended-season-id: ""
leaderboard-size: 5
server-name: ""
leaderboard-cache-seconds: 30
stats-cache-refresh-minutes: 5
share:
enabled: true
cooldown-seconds: 60
percentiles:
enabled: true
recap:
window-days: 7
join-reminder: true
discord:
webhook-url: ""
share-to-discord: true
post-season-recap: true
avatar-thumbnails: true
image-cards: true
post-milestones: true
milestones:
enabled: true
check-minutes: 10
roast-mode:
enabled: true
intensity: 2
| Key | Default | Range | Meaning |
|---|---|---|---|
prefix | gold "ServerWrapped >" | MiniMessage | Prefix for all plugin chat messages, in MiniMessage format. |
season-mode | false | bool | Whether wrapped cards use the active season baseline. Managed automatically by /wrapped season start/end — you normally never touch it. |
current-season-id | "" | — | ID of the active season. Managed automatically. |
last-ended-season-id | "" | — | Set when a season ends; drives /wrapped recap and join reminders. Managed automatically. |
leaderboard-size | 5 | 1–20 | Entries shown by /wrapped top. |
server-name | "" | text | Server name shown in share texts and Discord embeds ("Top 5% miner on PlayTown"). Empty = "this server". |
leaderboard-cache-seconds | 30 | 0–3600 | How long leaderboards and the percentile distribution stay cached. 0 disables caching (recomputed on every command). |
stats-cache-refresh-minutes | 5 | 1–60 | How often online players' stats are written to the offline stats cache. |
share.enabled | true | bool | Master switch for /wrapped share. |
share.cooldown-seconds | 60 | ≥ 0 | Per-player share cooldown. 0 disables the cooldown. |
percentiles.enabled | true | bool | Enables the "top X%" server ranks. Ranks need at least 3 known players either way. |
recap.window-days | 7 | 0–90 | How long /wrapped recap stays available after a season ends. 0 disables recaps and reminders. |
recap.join-reminder | true | bool | One-time clickable "your Wrapped is ready" message when players join during the recap window. |
discord.webhook-url | "" | URL | Discord channel webhook. Empty disables all Discord posting. Must be a real discord.com/api/webhooks/… URL — anything else is rejected with a console warning. |
discord.share-to-discord | true | bool | Whether /wrapped share also posts an embed to Discord. |
discord.post-season-recap | true | bool | Whether ending a season posts the server-wide recap to Discord. |
discord.avatar-thumbnails | true | bool | Whether shares include the player's head from the public mc-heads.net service. For text embeds Discord fetches the image; for image cards your server downloads it once per share while rendering. |
discord.image-cards | true | bool | Renders /wrapped share posts as a PNG image card instead of a text embed. Falls back to the text embed automatically if rendering or the upload fails. |
discord.post-milestones | true | bool | Whether server milestones are also posted to the webhook. |
milestones.enabled | true | bool | Master switch for server milestone broadcasts. |
milestones.check-minutes | 10 | 1–1440 | How often server totals are checked for freshly crossed milestones. |
roast-mode.enabled | true | bool | Enables the joke comments on the death line. |
roast-mode.intensity | 2 | 0–5 | How savage the death comments get: 0 none, 1 mild, up to 5 merciless. |
Seasons guide Owners
Seasons let you run monthly or SMP-season recaps without resetting anyone's statistics. The trick is baseline snapshots: seasonValue = currentValue − baselineValue.
Lifecycle
- Start:
/wrapped season start june-2026 June 2026june-2026is the internal ID (lowercase letters, numbers,-,_); "June 2026" is the display name shown on cards.- The plugin snapshots the current stats of every known player as the season baseline — including death reasons and totem counts.
- From now on, all cards, leaderboards, ranks, achievements, and archetypes are computed from season deltas.
- Players who join mid-season simply have a zero baseline — their whole activity counts.
- During:
/wrapped seasonshows ID, name, status, and start time. - End:
/wrapped season end— this is the Wrapped Day moment:- Final stats of every known player are frozen in a second snapshot, so the recap can never be polluted by post-season play.
- All online players get the ✦ WRAPPED DAY ✦ broadcast with a clickable button.
- The server-wide recap is posted to Discord (if configured).
/wrapped recapworks forrecap.window-daysdays; players who missed the drop get a one-time reminder on join.- Regular
/wrappedswitches back to lifetime stats (or start the next season right away).
june-2026 / "June 2026") create a predictable Wrapped Day your players will look forward to — end one season and immediately start the next.Discord integration Owners
ServerWrapped posts to Discord through a plain channel webhook — no bot account, no bot token, no extra plugin, no library.
Setup (2 minutes)
- In Discord, open the target channel → Edit Channel → Integrations → Webhooks → New Webhook.
- Give it a name (the plugin posts as "ServerWrapped" regardless) and Copy Webhook URL.
- Paste it into
config.yml:discord: webhook-url: "https://discord.com/api/webhooks/1234567890/AbCdEf..." - Run
/wrapped reload. Test it with/wrapped share.
What gets posted
- On
/wrapped share(ifshare-to-discordis on): by default a rendered PNG image card — player head, season, big stat numbers, "TOP X%" badge, and the archetype with its title on an archetype-colored background. Real images get shown around, screenshotted, and reposted far more than text ever does. Setdiscord.image-cards: falseto post the classic text embed with the emoji stat card instead. The in-game cooldown also throttles Discord posts, so the webhook rate limit (30/min) is never an issue. - On
/wrapped season end(ifpost-season-recapis on): a gold embed with the server-wide recap — players, combined playtime, blocks, distance, deaths, top death cause, and the most active player. - On a server milestone (if
post-milestonesis on): a short celebration embed, e.g. "⛏️ This server just mined its 1,000,000th block!".
How image cards work
- The card is rendered on your server with Java's built-in 2D graphics — no external image service, no extra dependency.
- Rendering and uploading happen entirely on a background thread; the main thread is never touched.
- With
avatar-thumbnails: truethe player's head is downloaded frommc-heads.netonce per share (5-second timeout); if that fails, the card simply renders without a head. - If rendering or the upload fails for any reason, the plugin automatically falls back to the text embed and logs a console warning — a share never silently disappears.
Safety & failure behavior
- Requests are sent fully asynchronously — a slow or down Discord can never lag the server.
- Failures (bad URL, deleted webhook, HTTP errors) only produce a console warning; gameplay is unaffected.
- The URL is validated to be a real Discord webhook before anything is sent.
Server milestones Owners
Milestones celebrate the whole community: whenever the server's lifetime totals cross a big round number, everyone online gets a broadcast (with the challenge-complete sound), the console logs it, and — if configured — the Discord webhook posts it too.
This server just mined its 1,000,000th block!
| Metric | First milestone | Example message |
|---|---|---|
| Blocks mined | 10,000 | "This server just mined its 250,000th block!" |
| Blocks placed | 10,000 | "This server just placed its 100,000th block!" |
| Combined playtime | 100 hours | "This server just passed 2,500 hours of combined playtime!" |
| Distance traveled | 100 km | "This server has now traveled over 5,000 km combined!" |
| Mobs defeated | 1,000 | "This server just defeated its 50,000th mob!" |
| Deaths | 100 | "This server just suffered its 1,000th death. Press F." |
Details worth knowing:
- Milestones follow a 1 / 2.5 / 5 ladder per power of ten (10,000 → 25,000 → 50,000 → 100,000 → …), so they stay rare and meaningful as the server grows.
- They always use lifetime totals — starting or ending a season never makes milestones jump or repeat.
- The first check after installing baselines silently: a mature server does not get flooded with milestones it passed years ago. Only totals crossed after installation are celebrated.
- At most one milestone per check is announced; if several are crossed at once they trickle out over the following checks instead of spamming chat.
- The last announced value per metric is persisted in
milestones.yml, so restarts never re-announce.
Data storage Owners
Everything is plain YAML in the plugin data folder — no database, trivially backupable, human-readable:
plugins/ServerWrapped/
config.yml
milestones.yml # last announced value per milestone metric
players/
<uuid>.yml # one file per player
seasons/
<season-id>.yml # display name, start/end timestamps
A player file contains:
| Section | Contents |
|---|---|
name, firstSeen, lastSeen | Identity, updated on join/quit |
deathReasons | Death cause counters, e.g. ENTITY_ATTACK:CREEPER: 6 — includes the killer mob, or the shooter for projectiles |
customStats.totemUses | Totem pops (tracked by the plugin, since vanilla has no reliable stat) |
snapshots.<season-id> | Season baseline: all stat values, death reasons, and totems at season start |
snapshots.<season-id>__final | Final stats frozen at season end (powers /wrapped recap) |
statsCache | Latest known Bukkit stats + top mined block / top killed mob, so offline players can be served instantly |
recapSeen | Which season recap the player has already opened (stops the join reminder) |
Distances are stored in centimeters (100,000 cm = 1 km), playtime in ticks (72,000 ticks = 1 hour), damage in Minecraft's internal damage points (10 = 1 heart).
Performance & internals Owners
- Write-behind cache: player files are held in memory; events (deaths, totems, joins) only mark data dirty. A background task writes dirty files asynchronously every 20 seconds — events never block on disk I/O. Everything is flushed on player quit and plugin shutdown.
- Offline stats cache: reading Bukkit statistics for offline players is expensive (per-material disk reads). ServerWrapped therefore persists each player's stats on quit, every
stats-cache-refresh-minuteswhile online, and at shutdown — recaps and leaderboards for offline players are served from this cache and never trigger the expensive scan. - Result caching: leaderboards and the percentile distribution are cached for
leaderboard-cache-seconds, so command spam can't cause recomputation storms. - No movement listeners: all distance/mining/combat numbers come from Bukkit's built-in statistics. The plugin only listens to joins, quits, deaths, and totem resurrections.
- Async networking & rendering: Discord webhook calls, avatar downloads, and PNG card rendering all run on background threads with timeouts.
- No NMS, no reflection into server internals, no external runtime dependencies — version bumps are low-risk.
FAQ & troubleshooting Owners
Players see no "top X%" line
Ranks need at least 3 known players with recorded stats, percentiles.enabled: true, and a non-zero value in at least one category. On a fresh server, ranks appear once three people have played.
Nothing arrives in Discord
- Check the console for a warning — a malformed URL is reported there.
- The URL must start with
https://discord.com/api/webhooks/(or theptb./canary.variants). - Make sure the webhook still exists in Discord (deleting the channel deletes its webhooks) and run
/wrapped reloadafter editing the config.
The Discord post is text instead of an image card
That's the automatic fallback. Check the console for a warning around the time of the share — typical causes are the JVM missing fonts (very minimal container images) or Discord rejecting the upload. Rendering works headless out of the box on normal JVMs; no display server is needed.
An offline player's card looks outdated
Offline players are served from the stats cache, which is refreshed every stats-cache-refresh-minutes while they play and once more when they quit. A card can therefore lag a few minutes behind their last session at most.
/wrapped recap says there's nothing to recap
Recaps exist only after a season was ended with /wrapped season end, and only for recap.window-days days. Seasons ended before updating to this version have no frozen final stats; recaps work from the first season you end on this version.
Can I edit or reset a player's data?
Yes — stop the server (or wait for a flush), edit players/<uuid>.yml, and start again. Deleting a player's file resets their plugin-tracked data (death reasons, totems, snapshots); their Bukkit statistics are untouched, since those belong to the vanilla world save.
Does it work on Spigot / Folia / older versions?
No — ServerWrapped targets Paper's Adventure API on Paper 26.2+. Folia is not supported.
Is any data sent anywhere?
If you configure a Discord webhook, share/recap/milestone posts go to that webhook. Player heads come from mc-heads.net: for text embeds Discord fetches them, for image cards your server downloads them while rendering (disable either with discord.avatar-thumbnails: false). Beyond that, the plugin reports anonymous usage statistics (server count, player count, versions) to bStats, like most public plugins — opt out globally in plugins/bStats/config.yml. No player names, UUIDs, or stats ever leave your server.