AstralVote Overview
AstralVote is the network's Votifier replacement: a standalone Votifier v1/v2 protocol server (master) that receives votes from voting sites and fans them out over RabbitMQ to every Paper server, plus a Paper plugin (paper) that tracks each player's unclaimed votes, enforces a per-service cooldown, sends reminder messages, and runs configurable reward actions when a player claims their votes. It depends on AstralCore (database, messaging, actions, placeholders) and AstralSync (cross-server vote-reminder preference) — both are hard dependencies in plugin.yml.
Everything documented on the following pages is the Paper plugin's surface — the master module is an internal Netty/Votifier server with its own MySQL connection and no player-facing configuration.
Data flow
master persists each vote (id, username, ip, service, protocol, processed, created_at) to a shared MySQL votes table and publishes a VoteReceivedPacket carrying only the vote's id. Every Paper server that receives the packet re-fetches the full row through its own CrudRepository<VoteModel> — the packet is a change notification, not a data carrier.
Unclaimed votes and cooldowns
VoteService keeps two in-memory, per-player caches, both populated on join (ConnectionListener) and cleared on quit:
Unclaimed votes (
cachedUnclaimedVotes) — everyvotesrow for the player whereprocessed = false. Backs the%votes_unclaimed%placeholder and theclaim-voteaction.Weekly vote count (
cachedWeeklyVoteCounts) — a CaffeineAsyncLoadingCachecounting the player's votes since the most recent Monday (Europe/Paristimezone), refreshed every 30 minutes and invalidated entirely at midnight Paris time by a repeating task. Backs%votes_weekly%.
Each configured vote service has its own cooldown (a Duration, e.g. "1h30" or "24h"). VoteService tracks the last vote time and last reminder time per service per player (PlayerVoteTime, in-memory only) and, every 5 seconds, checks whether any service's cooldown has elapsed since the player's last vote for it; if so and the player hasn't already been notified for that vote, it sends the VOTE_AVAILABLE message. A vote received while the player is online (VoteReceivedPacket handling) also immediately sends NEW_VOTE_RECEIVED and refreshes the caches.
A player's service is matched by comparing the vote's service string against a configured service's id or name, case-insensitively — see services.
Claiming votes
The claim-vote action calls VoteService#claimVote, which:
Guards against concurrent claims for the same player (a second click while one is in flight is silently ignored) and optimistically empties the player's cached unclaimed-vote list.
Re-fetches unclaimed votes from the database; if none, sends
NO_PENDING_VOTESand rolls the guard back.Marks every fetched vote as
processedand batch-updates them.On success, runs the configured
claim-actionslist once per claimed vote — claiming 3 unclaimed votes at once runs the wholeclaim-actionslist 3 times — then sendsVOTE_CLAIMEDwith%amount%set to the number of votes claimed.On any database failure, both the vote rows and the in-memory cache are rolled back to their pre-claim state and
UNEXPECTED_ERRORis sent.
Vote reminders
Whether a player receives VOTE_AVAILABLE/VOTE_REMINDER messages at all is gated by a per-player voteRemindersEnabled flag, stored in VotePlayerData and synced across servers by AstralSync (default true for a new player). The toggle-votes-reminders action flips it. See Developer API for the sync adapter.
Where to go next
Configuration —
config.yml(services, cooldowns, claim actions) andmessages.yml.Actions —
claim-vote,toggle-votes-reminders.Placeholders —
votes_*and theservicechain placeholder.Developer API —
PlayerVoteEventand the AstralSyncVotePlayerDataadapter.