Files
ScoreKeeper/docs/plugin-integration-and-event-wiring.md
2026-10-10 22:57:27 -07:00

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.