TDXDevelopment
TDX VotePlus

Changelog

Version history and release notes for TDX VotePlus.

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


v1.3.5 (Latest)

  • Servers sharing one set of tables (MySQL without mysql.server-id): fixed votes being overwritten by another server's old copy. Every server kept each player it had ever seen in memory for its whole uptime, and saved that copy again whenever the player quit, changed or not. Somebody who voted or claimed on one server and later only visited another had their totals, streak and claimed milestones written back to what that server last remembered. A join now starts from the database, and a quit no longer saves a record that has not changed. Single servers and networks with a server-id per backend were not affected
  • Players who are offline and fully saved are dropped from memory after each save, so memory no longer grows for the whole uptime
  • A vote's milestone claims are now saved together with the vote. They could miss that save and wait for the next one, which on a crash in between paid the milestone again

v1.3.4

  • MySQL: fixed one unsaveable player record stopping every player's votes from being saved. Dirty players are written as one transaction, so a single row the database rejected (the classic case is a name longer than 16 characters, from a typo on a vote site or in /voteplus setvotes) rolled back the whole batch, which was then retried every 15 seconds for good. Nothing was saved from that moment until a restart, and the final save at shutdown failed the same way. A rejected batch is now saved row by row, and only the bad row is dropped, with a warning
  • Votes for a name longer than 16 characters are ignored with a warning. No such player can exist. /voteplus give and /voteplus setvotes refuse such a name up front
  • MySQL: an overlong service name or address is cut to fit its column instead of failing the whole vote log batch
  • Networks using mysql.server-id: every server now runs its own month-end. The "this month is done" markers were shared by the whole database, so the first server to notice the new month closed it out and all the others skipped their archive, payout and monthly reset. Months missed on those servers can be closed with /voteplus archive <period> confirm. Single servers are unaffected
  • /voteplus monthly no longer pays out the month that is still running. With every finished month already closed it used to pay the current standings early, and the markers it left made the real month-end skip its payout and its archive. It now says there is nothing to catch up on
  • /voteplus setvotes <player> streak <n> now sticks. Streaks are worked out from the recorded vote days, so the number used to be thrown away by the player's next vote. It now rewrites those days, so the streak reads n and the next vote carries it on. Capped at 3650
  • /voteplus reset now clears the recorded vote days as well. They were left behind, so a reset player's old streak and best streak came back with their next vote. It also drops anything still waiting in the write buffer, which could put the record back a few seconds after the reset
  • tdxvoteplus.admin now includes every tdxvoteplus.admin.* node. Ops never noticed, but a non-op staff group given only tdxvoteplus.admin was refused every /voteplus subcommand
  • The week is now pinned to Sunday to Saturday instead of being left to the JVM's defaults. This is what current Java versions already did, so no counter moves
  • Corrected comments in the shipped files: broadcast-to-online in vote-party.yml switches the party rewards, not the announcement. Menu text does not resolve PlaceholderAPI tokens. The reminder text is reminders.nudge. %month% is a key such as 2026-04. The top-N placeholder is %tdxvoteplus_top_<period>_name_<n>%. /voteplus usage lists every subcommand

v1.3.3

  • Fixed the Daily and Weekly leaderboards showing players from earlier days and weeks. A player's period counts only roll over when they next vote, so a board listed everybody's last voting day until each of them voted again. The boards now only count votes from the current day, week or month
  • The same fix applies everywhere a count is shown: /vote stats, the main and My Votes menus, milestone progress, the %tdxvoteplus_daily%, _weekly% and _monthly% placeholders and the API. Yesterday's votes no longer show as today's
  • A streak now shows 0 once a full day has passed without a vote, instead of keeping the old number until the next vote
  • Fixed queued offline votes being payable twice. They were all paid out first and only marked as claimed afterwards, so clicking Claim All again while the first run was still going, or claiming while the join auto-claim was running, paid the same votes again. Each vote is now claimed in the database before it is paid, which also holds across servers sharing one database
  • /voteplus setvotes with daily, weekly or monthly now stamps the current period, so the value shows up on the boards and survives the player's next vote

v1.3.2

  • Fixed repeating MONTHLY milestones never becoming claimable again. The month rollover stamped the new month on every player, which skipped the per-player rollover that clears those claims, so anybody who was not online at rollover kept them claimed forever
  • The monthly reset no longer zeroes players who already voted in the new month. That happened when it ran some time after the month flipped, or when /voteplus archive was used mid-month
  • MySQL 8: rank is a reserved word from MySQL 8.0.2, so the monthly_history table could not be created there and the plugin disabled itself on startup. The column is now quoted everywhere. MariaDB was not affected
  • Folia: the vote sound and particles now run on the voter's own region, and queued monthly rewards run their console commands on the global region
  • Fixed queued monthly rewards being lost when the winner logged out in the moment between the rewards being claimed and being run. They are put back for the next join
  • Fixed sounds with an underscore in their key never playing, including ENTITY_EXPERIENCE_ORB_PICKUP and BLOCK_NOTE_BLOCK_PLING, both of which effects.yml suggests

v1.3.1

  • Fixed votes being lost when two votes for the same player arrived at the same moment, which is normal for somebody working through several vote sites. Each vote runs on its own async thread and the counter update is a read-modify-write, so one increment could overwrite the other and the streak's day set could be corrupted. Votes for one player are now applied one at a time
  • The same lock covers the milestone check, so two votes crossing a threshold together can no longer pay the same milestone twice, and a /voteplus setvotes edit can no longer be overwritten by a vote landing in the middle of it
  • Fixed a small window in the autosave flush where a player marked dirty between the copy and the clear was dropped without being saved. That vote's counter update was lost if the server stopped before the player's next vote
  • Fixed the first vote party never firing on a fresh database. With no party.total_parties row yet, the startup snapshot never ran, so the first real party was consumed as that snapshot and nothing happened. A missing row now counts as 0, and the server that wins the claim always fires
  • MySQL: the first increment of a counter returned a stale LAST_INSERT_ID() instead of the stored value, which could mute later parties on that server until a restart. The value is now read back on that path

v1.3.0

  • Folia and CanvasMC support. All scheduling now goes through the threaded-regions scheduler: anything touching a player runs on that player's region, broadcasts and console commands run on the global region, and database and HTTP work stays async. The same jar runs on Paper
  • Requires Minecraft 26.1 or newer. plugin.yml now declares api-version: '26.1' and folia-supported: true, so an older server refuses to load it. Stay on v1.2.16 for 1.20.x to 1.21.x
  • Streaks are now worked out from the set of days a player voted on instead of a single "last vote" marker. An offline vote claimed days later counts for the day it was cast, so a late claim can repair a streak instead of breaking it
  • New vote_days table, created automatically. The first time a player votes after the upgrade, their existing streak is carried over as it was, so nobody's streak resets and no milestone fires a second time
  • A replayed offline vote only adds to the daily, weekly and monthly counters when it was cast in the current period. Claiming a three day old vote no longer inflates today's daily count. The total always counts

v1.2.16

  • Fixed vote parties only firing on one server in a shared-DB cluster. The original v1.2.6 design used an atomic compare-and-subtract so exactly one server wins the threshold-crossing race, but never wired the cross-server propagation that was supposed to follow, peer servers' refresh task only synced the counter value, never fired trigger() locally. Result: only the winning server's online players got the broadcast + rewards. If MySQL latency was asymmetric, one backend could lose the race every time (matches the "kiwi never triggers" report)
  • total_parties is now the cross-server fire signal. When any server's refresh task detects the counter going up since its last local fire, it runs trigger() so its players see the broadcast + get rewards too
  • Per-server CAS dedup via lastFiredTotalParties AtomicLong so the winning server doesn't double-fire (once from increment, once from its own refresh)
  • Startup snapshot, first refresh after enable seeds lastFiredTotalParties without replaying historical parties as fresh celebrations
  • Lag-collapse, if multiple parties happened between refreshes, players see one celebration rather than a flurry; the underlying total_parties counter is still accurate for placeholders / leaderboards

v1.2.15

  • Fixed /voteplus archive <period> confirm refusing to run when monthly-rewards.enabled: false. Archive (writing history + bulk-resetting the leaderboard) is independent of reward distribution, a tracking-only server should still be able to close out past periods cleanly. The check is now decoupled: archive + bulk reset always run; rewards distribution + winner broadcast only fire when rewards are enabled
  • The auto-rollover hourly check still respects enabled: false (so users who deliberately disabled the system don't get surprise side effects)

v1.2.14

  • Fixed the Vote Leaderboard not rolling over to the new month. The per-vote rollover in VoteService.applyCounterUpdate only resets a single player's monthly_votes on their first vote of the new period, so the leaderboard kept showing last-month's standings for any player who hadn't voted yet
  • New DataManager.resetAllMonthlyVotes(newMonthKey) runs once per period change and bulk-resets every row's monthly_votes to 0 + stamps last_monthly_key with the new period
  • True past-period standings are now reconstructed from the append-only votes table via aggregateMonthlyTopFromVotes, not the live players.monthly_votes column. So archive + rewards work correctly even days after rollover, when fast voters have already triggered their per-row reset
  • Per-period idempotency markers (monthly.archived_<period>, monthly.rewards_distributed_<period>) prevent double-distribution if the rollover is re-run after a crash or a duplicate /voteplus archive invocation
  • New /voteplus archive <period> confirm admin command for catch-up. Reconstructs the period's top-N from votes, archives to history, distributes rewards (online + offline-queued), bulk-resets monthly counters
  • Offline winners are no longer skipped. Each command-type reward is queued in the new pending_rewards table and replayed on the winner's next join. Non-command rewards (items, messages, fireworks) are still skipped for offline winners, they need a live Player at apply time
  • New pending_rewards table with a uuid index. SQLite serialises drains through its lock; MySQL uses SELECT … FOR UPDATE so two backends sharing one DB can't both claim the same queue when a player joins both at once

v1.2.13

  • Renamed the "Next Monthly Rewards" card in the Vote Leaderboard GUI to "Top Monthly Voters" with shorter "Reset in" lore

v1.2.12

  • Fixed repeating milestones (one-time: false) firing on every vote past the threshold instead of once per period
  • Daily / weekly / monthly key changes and streak breaks now clear the matching claims so repeating milestones fire again on the next period
  • one-time: true milestones still stay claimed across rollovers as configured

v1.2.11

  • Cleaned up vote-rewards.yml comment text (configs-only, no code change)

v1.2.10

  • Suppressed the public vote.broadcast for replayed offline votes by default, so a multi-day offline voter rejoining no longer dumps a wall of "Player just voted!" lines into chat
  • New offline-queue.broadcast-on-claim key in vote-rewards.yml (default false; set true to broadcast every replayed vote like real-time votes)
  • Real-time votes still honour the top-level broadcast switch
  • Personal reward messages, counters, streaks, milestones, and vote-party progress on replayed votes are unaffected

v1.2.9

  • New /voteplus offline [player] admin subcommand for diagnosing missing votes; permission tdxvoteplus.admin.debug
  • Always-on [VotifierHook] Received vote: log line on every incoming vote
  • Debug-gated [VoteService] pipeline trace covering UUID resolution, online vs offline path, and the target table on queue insert
  • "Queued offline vote for …" log line no longer requires debug mode
  • New DatabaseProvider diagnostics: dumpOfflineQueue, countAllOfflineVotes, offlineQueueTableName

v1.2.8

  • Fixed offline votes silently lost when a player's name had fallen out of the server's usercache.json
  • VoteService.resolveUuid now consults the players table by name first, so new rows store under the player's real Mojang UUID
  • PlayerJoinListener and ClaimMenu repair existing orphaned rows on next login via a two-pronged UUID/name lookup
  • storage.yml now documents the per-server-id vs Votifier-forwarding interaction

v1.2.7

  • New optional mysql.server-id field for per-server player data isolation when multiple servers share one MySQL database
  • Scoped tables: players, votes, offline_queue, monthly_history
  • Shared table: metadata, so the vote-party counter stays global across the cluster
  • Startup banner shows (shared tables) or (server-id 'survival') so the mode is visible at a glance
  • Config-version bumped 7 to 8, auto-merged into existing installs

v1.2.6

  • Fixed the main menu "Next Milestone" card showing a streak milestone while the Milestones GUI showed a non-streak one
  • Fixed vote-party counter drift across servers sharing one MySQL DB; the counter now uses DB-side atomic increment with compare-and-subtract for threshold crossing
  • 5-second background refresh task so peer servers see the shared counter move between votes

v1.2.5

  • Fixed IncompatibleClassChangeError on Paper 1.20.4 caused by InventoryView being a class in 1.20.x but an interface in 1.21+; routed click handling through event.getInventory() instead of event.getView().getTopInventory()

v1.2.4

  • Filtered streak rewards out of the Milestones GUI
  • Made milestone item lore configurable via lore: field on each milestone entry
  • Fixed duplicate-clock bug in Streaks GUI
  • Hidden enchant glints and attribute modifiers via ItemFlag.values()
  • Renamed gui_menus/ to guis/ (with one-time folder migration)

v1.2.3

  • Leaderboard hopper filter
  • Merged Milestones and Milestone Rewards GUIs into one screen
  • Dropped material / slot from vote-sites.yml
  • Fixed 1.20.x prefix shadow tags