Documentation
Configuration
Database
Code (Text):
database:
host: "localhost"
port: 3306
name: "minecraft"
username: "root"
password: "password"
table-prefix: "sbsync_"
| 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
| 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:
| 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
| 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.
| 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"
| 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
| 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.
| (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
| 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.
| %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.
| 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.
| 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.
| id |
BIGINT AUTO_INCREMENT |
Row ID |
| player_id |
BIGINT |
Foreign key to players.id |
| score |
INT |
Scoreboard score |
| last_sync |
DATETIME |
Last sync timestamp |