7.6 KiB
Score Keeper - Plugin Integration and Event Wiring
New Score Keeper contributors should start with CONTRIBUTING.md, which covers architecture, setup, and validation. This is an optional detailed reference for runtime wiring and companion-plugin authors, not a second onboarding guide. domain-score-tracking.md describes state and README.md gives full command syntax.
Paper lifecycle
The entry point is
ScoreKeeperPlugin,
declared in plugin.yml under the plugin ID
ScoreKeeper. Compilation targets Paper 26.2.build.129-stable on Java 25;
supported server versions are Paper 26.2 and 26.3.
| Trigger | Handler / effect |
|---|---|
| Plugin enable | Register eight executors, initialize points, load scores/archives, check existing runs, schedule synchronous checks every 20 ticks |
| Scheduled check | checkHighScoreRuns: finish expired duration runs or runs whose target is reached |
| Plugin disable | Serialize bucket definitions, scores, assignments, active runs/snapshots, and archives to two YAML files |
| Minecraft join/quit/death/kill | No built-in listeners or automatic score awards |
| Companion-plugin API call | Perform requested score/bucket/archive/run operation |
There are no Listener implementations, @EventHandler methods, custom scoring
events, or event registrations. The scheduler manages runs, not gameplay scoring.
Expired saved runs finish during enable; until-disabled runs persist and resume.
Command wiring
Each manifest command is explicitly wired in onEnable using getCommand and
setExecutor. A missing declaration fails startup via Objects.requireNonNull.
Handlers are in commands/.
| Command | Executor | Delegation / effect | Permission |
|---|---|---|---|
score-get |
ScoreGetCommand |
getScore, active bucket, lazy entry creation |
None |
score-add |
ScoreAddCommand |
addScore, signed integer addition |
None |
score-subtract |
ScoreSubtractCommand |
subtractScore, signed integer subtraction |
None |
score-reset |
ScoreResetCommand |
resetScore, active bucket initial value |
None |
score-archive |
ScoreArchiveCommand |
archivePlayerScore, archiveBucketScores, or archiveAllScores |
scorekeeper.admin |
score-archive-list |
ScoreArchiveListCommand |
getScoreArchives or getScoreArchive |
None |
score-bucket |
ScoreBucketCommand |
View/select own bucket, create bucket, switch another/all players | Admin for create/other/all; none for self |
score-run |
ScoreRunCommand |
Start target/timed/manual run, or stop it | scorekeeper.admin |
scorekeeper.admin defaults to operators in the manifest. Guards are implemented
inside selected executors, not per-command manifest permission fields. Public API
calls do not enforce sender permissions; the caller must enforce its own policy.
Named player targets use Paper's getPlayerExact and must be online. Players
may omit the optional name to target themselves; non-player senders have no
self context. Console/RCON can use archive all/bucket forms without a player.
Bucket IDs are case-sensitive even though command keywords such as all are not.
Parse/validation failures are returned as Adventure usage/error messages.
Handlers consume commands by returning true. Score mutation commands rely on
bucket/run reporting instead of unconditional success messages; the default
none policy can make a successful mutation silent.
Companion-plugin integration
Score Keeper declares no plugin dependencies of its own. A companion that requires
it should compile against the matching jar as a provided/compile-only dependency,
and declare depend: [ScoreKeeper] in its own Bukkit-style manifest so it loads
after Score Keeper. For optional integration, use softdepend and handle absence
or disabled state explicitly.
There is no ServicesManager registration or standalone API module. Obtain the enabled plugin by ID and check its type, for example during the companion's enable:
Plugin candidate = getServer().getPluginManager().getPlugin("ScoreKeeper");
if (!(candidate instanceof ScoreKeeperPlugin scoreKeeper) || !candidate.isEnabled()) {
getLogger().severe("Score Keeper is required but is not enabled.");
getServer().getPluginManager().disablePlugin(this);
return;
}
Imports for this example are org.bukkit.plugin.Plugin and
com.majinnaibu.minecraft.plugins.scorekeeper.ScoreKeeperPlugin. Keep the
reference for your integration lifecycle, not as a global singleton.
Do not shade a second copy of Score Keeper into the companion jar.
Public operations
| Area | Methods |
|---|---|
| Bucket definition | createBucket(id, singular, plural, initialValue[, ScoreReporting]), getBucket(id), getBuckets() |
| Active selection | getPlayerBucket(Player), switchPlayerBucket(Player, id), switchAllPlayers(id) |
| Score CRUD | getScore(Player[, id]), setScore(Player[, id], score), addScore(Player[, id], amount), subtractScore(Player[, id], amount), resetScore(Player[, id]) |
| Archives | archivePlayerScore(Player), archiveBucketScores(id), archiveAllScores(), getScoreArchives(), getScoreArchive(archiveId) |
| Compatibility wrapper | archiveScore(Player) delegates to player archive creation but discards the returned ID |
| Runs | startTargetScoreRun(id, topN, resetScores, targetScore), startTimedScoreRun(id, topN, resetScores, Duration), startUntilDisabledScoreRun(id, topN, resetScores), stopHighScoreRun(id), hasActiveHighScoreRun(id) |
| Messaging/logging | sendMessage(CommandSender, Component), logInfo, logWarning, logError |
The bracket notation above describes overloads, not literal Java syntax.
getBucket and getScoreArchive return null when unknown; mutating/score methods
validate bucket existence and throw IllegalArgumentException for invalid
operations. Do not swallow errors or report success after a failure.
Bucket maps and archive snapshots exposed by getters are immutable copies/views.
Example explicit-bucket award, with imports for ScoreReporting and Player:
if (scoreKeeper.getBucket("event") == null) {
scoreKeeper.createBucket("event", "point", "points", 0, ScoreReporting.PLAYER);
}
scoreKeeper.addScore(player, "event", 10);
Only create a shared bucket if its existing definition meets your integration's requirements; duplicate creation throws. Explicit-bucket scoring does not switch the player's active bucket.
Threading and ownership
All calls touching state, players, or reporting must execute on Paper's main
server thread. The plugin's LinkedHashMap state and read-modify-write operations
are not synchronized. Marshal asynchronous event/work results to a synchronous
Paper task before calling this API. Score Keeper does not provide Folia/region
scheduler support.
Do not mutate private maps or write the YAML files from another plugin. Use the API so reporting, validation, target completion, and snapshots remain consistent. API calls change in-memory state; durability is deferred until Score Keeper disables successfully.
Verification
There is currently no automated plugin harness or real-server test task. Validate load ordering, command dispatch, messages, run scheduler behavior, and clean-restart persistence on disposable Paper servers using human-tests.md. Do not assume a successful compilation verifies companion load order or runtime API compatibility.