Payer Stats Sync icon

Payer Stats Sync -----

Real-time Minecraft statistics sync to MySQL, plus secure WordPress account linking out of the box.



Documentation

Configuration

Database
Code (Text):

database:
  host: "localhost"
  port: 3306
  name: "minecraft"
  username: "root"
  password: "password"
  table-prefix: "sbsync_"
 
Key Default Description
host localhost MySQL server hostname or IP
port 3306 MySQL server port
name minecraft Database name
username root Database user
password Database password
table-prefix sbsync_ Prefix for all created tables


Sync Interval
Code (Text):

sync-interval-minutes: 20
 
How often the periodic sync runs for all online players. Set to 0 to disable periodic sync entirely and rely only on join/quit triggers.

Statistics
Each stat entry syncs a Minecraft internal statistic into its own database table named <prefix>stat_<key>.

Code (Text):

statistics:
  deaths:
    enabled: true
    display-name: "Deaths"
    statistic: DEATHS
    sync-on-join: true
    sync-on-quit: true
    sync-periodically: true
    conflict-resolution: additive

  open_chest:
    enabled: true
    display-name: "Chests Opened"
    statistic: CUSTOM
    custom_key: open_chest
    sync-on-join: false
    sync-on-quit: true
    sync-periodically: true
    conflict-resolution: additive

  kill_entity:
    enabled: true
    display-name: "Mob Kills"
    statistic: KILL_ENTITY
    sync-on-join: false
    sync-on-quit: true
    sync-periodically: false
    conflict-resolution: additive
    entities:
      - ZOMBIE
      - CREEPER
      - SKELETON
 
Key Required Description
enabled No (default: true) Whether this stat is active
display-name No Human-readable name for display purposes
statistic Yes Bukkit Statistic enum value, or CUSTOM for minecraft:custom stats
custom_key Only if statistic: CUSTOM The minecraft:custom key, e.g. open_chest
sync-on-join No (default: true) Sync when player joins
sync-on-quit No (default: true) Sync when player quits
sync-periodically No (default: true) Include in periodic sync
conflict-resolution No (default: last-write-wins) How to handle existing DB values
materials Only for BLOCK/ITEM stats List of Material enum values
entities Only for ENTITY stats List of EntityType enum values — creates one table per entity


Supported statistic types:

Type Example Notes
UNTYPED DEATHS, PLAY_ONE_MINUTE Simple counter, no sub-type
BLOCK MINE_BLOCK Requires materials list
ITEM USE_ITEM Requires materials list
ENTITY KILL_ENTITY Requires entities list — creates one table per entity
CUSTOM open_chest minecraft:custom namespace stats, requires custom_key


Note on wall-clock stats: PLAY_ONE_MINUTE and TIME_SINCE_DEATH are relative to the current session and do not represent meaningful cross-server totals. Use them with conflict-resolution: additive only if you understand what you are summing.

Objectives
Each objective syncs a scoreboard value into its own table named <prefix>obj_<key>.

Code (Text):

objectives:
  money:
    enabled: true
    display-name: "Balance"
    sync-on-join: true
    sync-on-quit: true
    sync-periodically: true
    conflict-resolution: last-write-wins

  kills:
    enabled: true
    display-name: "Player Kills"
    sync-on-join: true
    sync-on-quit: true
    sync-periodically: true
    conflict-resolution: additive
 
Key Required Description
enabled No (default: true) Whether this objective is active
display-name No Human-readable name
sync-on-join No (default: true) Sync when player joins
sync-on-quit No (default: true) Sync when player quits
sync-periodically No (default: true) Include in periodic sync
conflict-resolution No (default: last-write-wins) How to handle existing DB values


Conflict Resolution
Controls what happens when a value already exists in the database for a player.

Strategy SQL Best for
last-write-wins value = VALUES(value) Balances, settings, anything that can go up or down
additive value = GREATEST(value, VALUES(value)) Kills, deaths, blocks broken — monotonic counters
max value = GREATEST(value, VALUES(value)) Highscores, personal bests
min value = LEAST(value, VALUES(value)) Speed-run times, lowest death count


Why additive uses GREATEST and not +: Minecraft stats are monotonically increasing — they only ever go up. GREATEST ensures that a stale read from one server never overwrites a higher value written by another. True additive ( +) would double-count on every sync.

Verify (Website Linking)
Code (Text):

verify:
  enabled: true
  wordpress-url: "https://your-domain.com/?rest_route=/msp/v1/verify"
  hmac-secret: "your-secret-here"
  website-url: "https://your-domain.com/stats"
 
Key Description
enabled Enable or disable the /verify command
wordpress-url Full URL to the WordPress REST endpoint. Use ?rest_route= format if permalinks are set to Plain
hmac-secret Shared secret for HMAC-SHA256 request signing. Must match the WordPress plugin setting exactly
website-url Shown to players in the /verify usage hint


Update Checker
Code (Text):

updater:
  enabled: true        # Set to false to disable entirely
  notify-on-join: true # Set to false to suppress join notifications
 
State Log on startup /pss version output
Up to date Plugin is up to date (2.0.0) ✔ Up to date! (green)
Update available A new version is available: 2.1.0 ⚠ Update available! (yellow) + clickable links
Dev build Running a development version: 2.1.0-dev ⚡ Dev build — newer than latest release. (purple)


Commands

/pss reload
Reloads config.yml, re-registers all stat and objective entries, and ensures all configured tables exist in the database. Safe to run at any time without restarting the server.

/pss sync [target]
Forces an immediate sync for one or more players. The target argument is optional — if omitted, all online players are synced.

Format Example Result
(none) /pss sync All online players
Player name /pss sync Shin_Jin_Jin Single online player
@a /pss sync @a All online players
@p /pss sync @p Nearest player to the command sender
@r /pss sync @r One random online player
@e[...] /pss sync @a[gamemode=survival] All survival-mode players


Tab completion suggests @a, @r, @p, @e and all online player names.

/pss status
Displays current database connection status, sync interval, table prefix, and a full list of all active objectives and stats with their table names and sync trigger configuration.

/pss version
Shows the currently installed version and checks for updates. If the update check is still running when the command is executed, the result is sent automatically to chat as soon as it completes — no need to re-run the command.

  • Up to date — green confirmation message
  • Update available — yellow warning with clickable links to SpigotMC and GitHub Release
  • Dev build — purple notice when the installed version is newer than the latest release

/verify <code>
Links the executing player's Minecraft account to their website profile. The player must first request a 6-digit code on the website, then type /verify <code> in-game within the configured time window (default: 10 minutes). The request is signed with HMAC-SHA256 — the server never sends plaintext credentials.

Permissions

Permission Default Description
playerstatssync.admin op Access to /pss reload, /pss sync, /pss status, /pss version
playerstatssync.verify true Access to /verify


Developer API

Add PlayerStatsSync as a dependency in your plugin.yml:
Code (Text):

softdepend:
  - PlayerStatsSync
 
All API methods are in de.plugin.playerstatssync.api.PlayerStatsSyncAPI.

getStat(UUID uuid, String statKey)
Returns the synced value for a stat table key.
Code (Text):

long deaths       = PlayerStatsSyncAPI.getStat(uuid, "deaths");
long creeperKills = PlayerStatsSyncAPI.getStat(uuid, "kill_entity_creeper");
long playtime     = PlayerStatsSyncAPI.getStat(uuid, "playtime");
 
The statKey corresponds to the key defined in config.yml under statistics. For ENTITY stats, append the entity name: kill_entity_zombie, kill_entity_creeper, etc.

getObjective(UUID uuid, String objectiveName)
Returns the synced score for a scoreboard objective.
Code (Text):

long balance = PlayerStatsSyncAPI.getObjective(uuid, "money");
long kills   = PlayerStatsSyncAPI.getObjective(uuid, "kills");
 
getPlayerName(UUID uuid)
Returns the last known player name from the database, or null if the player has no entry.
Code (Text):

String name = PlayerStatsSyncAPI.getPlayerName(uuid);
 
isPlayerTracked(UUID uuid)
Returns true if the player has any entry in the database.
Code (Text):

if (!PlayerStatsSyncAPI.isPlayerTracked(uuid)) {
    // player has never synced
}
 
Return values and error handling
All methods return PlayerStatsSyncAPI.NOT_FOUND ( Long.MIN_VALUE) when the player or stat does not exist. Always check before using a value:
Code (Text):

long value = PlayerStatsSyncAPI.getStat(uuid, "deaths");
if (value == PlayerStatsSyncAPI.NOT_FOUND) {
    // no data yet
} else {
    // use value
}
 
All methods are synchronous database reads. Call them off the main thread to avoid TPS impact:
Code (Text):

Bukkit.getScheduler().runTaskAsynchronously(yourPlugin, () -> {
    long deaths = PlayerStatsSyncAPI.getStat(uuid, "deaths");
    // process result...
});
 
PlaceholderAPI

If PlaceholderAPI is installed, the following placeholders are registered automatically on startup. No configuration required.

Placeholder Returns
%playerstatssync_stat_<key>% Synced stat value for the given key
%playerstatssync_stat_kill_entity_<mob>% Kill count for a specific mob
%playerstatssync_obj_<name>% Synced scoreboard objective value
%playerstatssync_player_name% Last known player name from the database


Examples:
Code (Text):

%playerstatssync_stat_deaths%
%playerstatssync_stat_playtime%
%playerstatssync_stat_kill_entity_creeper%
%playerstatssync_obj_money%
%playerstatssync_player_name%
 
Placeholders return an empty string if the player has no data in the database yet.

Database Schema

All tables are created automatically on first sync. No manual SQL setup is required.

<prefix>players
Central player registry. All stat and objective tables reference this via player_id.

Column Type Description
id BIGINT AUTO_INCREMENT Internal player ID
uuid VARCHAR(36) Player UUID
name VARCHAR(50) Last known player name
last_seen DATETIME Last sync timestamp


<prefix>stat_<key>
One table per configured stat entry.

Column Type Description
id BIGINT AUTO_INCREMENT Row ID
player_id BIGINT Foreign key to players.id
value BIGINT Synced stat value
last_sync DATETIME Last sync timestamp


<prefix>obj_<name>
One table per configured objective.

Column Type Description
id BIGINT AUTO_INCREMENT Row ID
player_id BIGINT Foreign key to players.id
score INT Scoreboard score
last_sync DATETIME Last sync timestamp
Resource Information
Author:
----------
Total Downloads: 11
First Release: Apr 26, 2026
Last Update: Apr 30, 2026
Category: ---------------
All-Time Rating:
0 ratings
Version -----
Released: --------------------
Downloads: ------
Version Rating:
----------------------
-- ratings