TDXDevelopment
TDX Leaderboards

Changelog

Version history and release notes for TDX Leaderboards.

All notable changes to TDX Leaderboards are documented here. Versions are listed newest-first.


v1.9.20 (Latest)

  • DELTA boards can show a player's lifetime total as this period's progress when the baseline under it is wrong. Two causes are now handled
  • A baseline read as 0 before the player's data had loaded. The join baseline is now taken five seconds after a join instead of five ticks, and a value of 0 is only stored for a player who first joined this period or has been online for two minutes. Anyone left without a baseline gets one on the next update cycle. Existing baselines are never overwritten
  • A lifetime value that jumped after the baseline was taken, because player statistics or another plugin's data were restored, merged or imported mid-period. The plugin cannot prevent that, but it can now show it and clear it
  • New /lbadmin baselines. It reports, per DELTA board, how many baselines there are, how many zero baselines belong to players who joined before the period began, and how many players show more playtime than has passed this period, which is the sure sign of a jump
  • /lbadmin baselines repair confirm removes only those zero baselines. /lbadmin baselines reset <board|all> confirm removes every baseline of one DELTA board or of all of them. Both also clear the cached standings of the boards they touch. The players affected start again from the value they have the next time they are online. Their earlier progress this period cannot be recovered, because the right starting value was never recorded
  • Boards the plugin counts itself (ACCUMULATE) have no baselines and are never touched by these commands

v1.9.19

  • Fixed the reset countdown showing the wrong time. Since v1.9.8 the reset happens when the calendar period changes (midnight on the 1st, midnight on Monday, or midnight on every second Monday), but the countdown in the GUI and %tdxleaderboards_reset_time% still counted down to reset-day and reset-hour. With anything but the default values it showed a moment nothing happened at. It now counts down to the real reset
  • reset-day and reset-hour are documented as unused in config.yml. They stay in the file so existing configs keep loading, and changing them does nothing
  • Period keys are unchanged, so no stored data moves

v1.9.18

  • Fixed &x&r&r&g&g&b&b colours. The converter dropped the first digit of every colour and produced a five digit tag, so the colour never applied
  • Fixed sounds with an underscore in their key never playing. ENTITY_EXPERIENCE_ORB_PICKUP and more than half of all sounds resolved to nothing, for a board's sound, the GUI open and close sounds and the milestone sound
  • Fixed the Discord results post showing the wrong standings. It read the live boards, which already hold the new period by the time a reset runs, so a normal rollover posted a nearly empty embed and /lbadmin archive posted the current month under the old period's name. It now posts the standings of the period that ended
  • Fixed /lbadmin setstat on an offline player corrupting a DELTA board. It stored a negative starting point, and the next time the player joined their value became their lifetime stat plus the number typed. For an offline player it now only changes their row on the board, which lasts until they next join, and the command says so
  • Fixed reward positions being taken by stale totals. A player who had dropped off every board kept their old points total in storage, and the period reset pays out from those rows. A total is now cleared once the player holds no points, and the reset ignores anybody on 0
  • Fixed queued rewards being lost when a winner logged out in the moment between the rewards being claimed and being run. They are put back for the next join

v1.9.17

  • New board types counted by the plugin itself: CUSTOM_BLOCKS_BROKEN (blocks broken), CUSTOM_WOOD (natural logs, stems and stripped logs chopped), CUSTOM_POTIONS_BREWED (bottles brewed, credited to whoever used the brewing stand last) and CUSTOM_TAMED (animals tamed)
  • CUSTOM_BLOCKS_BROKEN and CUSTOM_WOOD skip blocks the player placed first, so nobody can farm a board by placing and breaking the same block. The plugin remembers the most recently placed blocks, sized by custom-tracking.blocks-broken.placed-block-memory (default 100000, about 10 MB of memory, 0 turns it off). ignore-instant-breaks leaves out grass, flowers, torches and crops
  • New CUSTOM_COUNTER board type that only moves when a command adds to it. Put /lbadmin addstat <player> <board-id> <amount> in another plugin's reward commands to feed a board from outside, for example event wins. It uses the same tdxleaderboards.admin.setstat permission as setstat
  • New per-board hidden: true option for a board that only feeds placeholders, such as a spawn hologram. A hidden board has no menu tile, gives no points or rewards and is never announced. Read it with %tdxleaderboards_board_<id>_name_<1-10>% and %tdxleaderboards_board_<id>_value_<1-10>%
  • New general.hide-banned-players (default true). A board only refreshes a player while they are online, so somebody banned while sitting at #1 used to stay there all month. Banned players now drop off within one update cycle. Uses LiteBans when it is installed, otherwise the vanilla ban list

v1.9.16

  • New CUSTOM_DAMAGE_DEALT board type. Damage is counted by the plugin on the same scale as the vanilla statistic (10 per heart), but only for health actually taken off the target, so overkill does not count
  • custom-tracking.damage-dealt.exclude-spawner-mobs (default true) ignores damage done to mobs that came out of a spawner, vanilla or RoseStacker, which keeps mob grinders off the combat board. count-player-damage (default true) decides whether damage to other players counts
  • The bundled damage-dealt preset now uses CUSTOM_DAMAGE_DEALT with tracking: ACCUMULATE. An existing config keeps STATISTIC with DAMAGE_DEALT until you change it
  • New CUSTOM_QUESTS board type that counts quests completed on the AxQuestBoard board. Needs the AxQuestBoard plugin, and counts from the moment the board is set up
  • This version was never published as its own download. v1.9.17 contains everything listed here

v1.9.15

  • Fixed the distance board being farmable by falling. CUSTOM_DISTANCE counted vertical movement too, so a player could dive into the void, score a block for every Y level on the way down, get teleported back to spawn and repeat. Only horizontal movement counts now, which is what "blocks traveled" always meant

v1.9.14

  • Fixed the personal "You" line on each board showing 0 while the top-N rows showed the correct value. The async pre-fetch in openMainGui was calling Player.getStatistic() from a background thread (unreliable on Paper) and storing the resulting 0 into a per-viewer cache; the top entries were rescued by the board_cache merge in updateBoard but the personal lookup had no merge fallback, so the two values disagreed
  • placeBoardItems and placePointsItem now read the viewer's value straight from the in-memory leaderboard (board.getAllEntries() / pointsManager.getPointsLeaderboard()), same source the top rows render from, so the values can never disagree again
  • Removed the viewerData cache, ViewerData record, async pre-fetch in openMainGui, and the clearViewerData close-handler call. Restored the simple 1-tick delayed re-fill for ResetVault interference defense
  • Trade-off: viewer's row is now refreshed by the periodic updateBoard cycle (default 30s) rather than per-open. Net win: GUI opens are sync-fast and never disagree with themselves

v1.9.13

  • Fixed /lbadmin reset confirm leaving DELTA boards empty until each online player relogged. The command was clearing monthly_snapshots (along with the rest of the period's data) but never re-establishing baselines, so getMonthlyValue returned 0 for every online player and updateBoard silently skipped them. Boards stayed frozen empty until the natural per-join snapshot path re-populated rows
  • /lbadmin reset confirm now: clears month data → wipes in-memory leaderboard caches → calls forceSnapshotAllPlayers to take fresh DELTA baselines (idempotent IF-ABSENT, can't clobber existing) → kicks off an async updateAllBoards so the GUI catches up immediately
  • New /lbadmin snapshot confirm recovery command. Takes fresh snapshots for online players without clearing any data, use this if you already ran the broken /lbadmin reset on a pre-v1.9.13 install and lost DELTA-board tracking; ACCUMULATE-board progress is untouched

v1.9.12

  • Fixed the new History GUI getting stuck on "Loading…" forever. player.openInventory() was being called from inside the InventoryClickEvent that triggered the open, Bukkit's behaviour for opening a new inventory mid-click is unreliable, and the title rendered while later mutations from the async refill failed to sync to the client view
  • Both openInventory and the chained async DB read now defer to the next tick via runTask. The async fetch is scheduled inside the deferred open so the order is guaranteed
  • Async DB reads are wrapped in try/catch with a visible failure card ("Failed to load, see server console for details") instead of an indefinite spinner. Console gets the stacktrace
  • Same deferral applied to back-button navigation from the period list to the main GUI

v1.9.11

  • Fixed /lbadmin archive <past period> wiping current-period progress for online DELTA-board players. forceSnapshotAllPlayers was overwriting existing new-period snapshots with the player's current stat, which zeroed the monthly value of anyone online at the moment of the archive
  • forceSnapshotAllPlayers now uses saveSnapshotsIfAbsentBatch (INSERT … DO NOTHING / INSERT IGNORE), same idempotent path the join handler already uses. Live rollovers behave identically (no existing rows → every write succeeds); catch-up archives leave the new period's already-established baselines alone
  • Symptom this fixes: large negative %rank_change% lore indicators (-114, -186, etc.) appearing on boards right after running /lbadmin archive

v1.9.10

  • Per-board period history GUI. New "Past Periods" button on the main /lb GUI opens a three-tier flow: pick a past period → pick a board (or "Points overall") → see that period's archived top 10 with player heads
  • The chat-only /lb history <period> <board> command is unchanged for power users
  • Reads period_archive and points_archive async with a "Loading…" placeholder, so opening the GUI never blocks the main thread
  • New HistoryGuiManager, HistoryHolder (with type/period/board state + slot mappings), and history button entry in main-leaderboard.yml
  • Config-merge friendly: existing installs get the history button on next reload; legacy GUIs without the items.history block silently skip placement

v1.9.9

  • Reward delivery is now offline-aware. When a winner is offline at period rollover, the reward command is queued in a new pending_rewards table and replayed when they next join, give %player% diamond and other online-only commands no longer silently disappear
  • New pending_rewards table with a uuid index. SQLite uses its global lock for serialised drain; MySQL uses SELECT … FOR UPDATE inside a transaction so two backends sharing one DB can't both claim the same queue when a player joins both at once
  • Per-period idempotency markers (rewards_distributed_<period>, archived_<period>) in the metadata table. A mid-reset crash, or a second /lbadmin archive 2026-04 confirm invocation, no longer re-runs distribution and double-rewards winners
  • 2-second delay before draining the offline queue so the player's chunks and inventory finish loading before give-style commands fire
  • Removed dead RewardManager.distributeRewards() legacy code path that read from in-memory caches (replaced by distributeRewardsFor reading the period's frozen DB caches)

v1.9.8

  • Fixed monthly reset silently failing to archive + distribute rewards. Three compounding bugs: lastResetPeriod defaulted to the current period on every startup (so missed crossings became invisible after a restart), the field was in-memory only (so a successful reset was forgotten across restarts), and isResetTime required getHour() == resetHour exactly, which the hourly tick frequently missed
  • New metadata k/v table. The last successfully reset period is persisted there, so reboots no longer mask missed resets
  • Reset is now triggered by period-key change, not exact-hour matching. Check interval lowered to 5 minutes; the reset fires once when currentPeriodKey() != lastResetPeriod
  • Reset reads final standings from the period's frozen board_cache / points_cache rows, so catch-up resets (period rollover happened while server was offline) work the same as live resets
  • New /lbadmin archive <period> confirm admin command to retroactively archive + distribute rewards for a past period, use this to fix April after upgrading

v1.9.7

  • Fixed two more main-thread SQLite freezes the v1.9.6 batch fix didn't cover
  • /lb GUI open now pre-fetches the viewer's per-board monthly value and points total on an async worker, then refills the inventory from a per-viewer cache, no DB read happens on the main thread while rendering. Refills triggered by category-tab clicks and ResetVault interference reuse the same cache
  • PlayerJoinListener's "show points on join" message now reads getPlayerPoints async and hops back to main thread only to send the chat line
  • Per-viewer cache is dropped on inventory close so it doesn't grow

v1.9.6

  • Fixed server-thread freezes on player join when DELTA boards were configured. The join handler called snapshotAllPlayers() synchronously, iterating every online player × every DELTA board with a locked SQLite write per row, so a single join could stall the tick loop for 30+ seconds while contending with the async board-update cycle for the same ReentrantLock
  • New BoardManager.snapshotPlayerAsync(Player) only snapshots the joining player and dispatches the writes off-thread
  • New DatabaseProvider.saveSnapshotsIfAbsentBatch(...) writes all rows in a single transaction using INSERT … ON CONFLICT DO NOTHING (SQLite) / INSERT IGNORE (MySQL), one fsync per join instead of one per row, no read-then-write roundtrip

v1.9.5

  • Compatibility with Paper 1.20.x through 1.21.11+
  • MySQL / MariaDB storage backend alongside SQLite
  • DecentHolograms integration for physical leaderboard displays
  • 16 board types including PlaceholderAPI-driven boards
  • Points-from-position scoring across all boards
  • Milestone alerts with configurable thresholds
  • Per-board reward overrides at period end
  • Weekly and biweekly reset schedules (in addition to monthly)
  • Config-version auto-merge so upgrades preserve your customisations

v1.0, Initial Release

Date: 2026-02-26

Features

  • 11 default leaderboard boards: Balance, Player Kills, Playtime, Votes, Skills XP, Monsters Killed, Animals Bred, Fish Caught, Crops Harvested, Blocks Placed, Ores Mined
  • Monthly tracking with automatic resets
  • Points system: awards 10 for 1st, 9 for 2nd down to 1 for 10th across all boards
  • Automatic rewards: configurable commands at month end based on points ranking
  • Customisable GUI: MiniMessage-formatted chest GUI with configurable layout, materials, and custom model data
  • Built-in event tracking for ores mined, crops harvested, and blocks placed
  • Vault integration for balance leaderboard
  • PlaceholderAPI support with expansions for all boards, points, and rankings
  • SQLite storage with WAL mode for performance

Board Types

  • STATISTIC, Minecraft vanilla statistics (delta tracking)
  • CUSTOM_ORES / CUSTOM_CROPS / CUSTOM_BLOCKS, Plugin event tracking
  • VAULT_BALANCE, Vault economy (current value)
  • PLACEHOLDER, Any PlaceholderAPI placeholder