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.14 (Latest)

  • 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