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

137 lines
7.6 KiB
Markdown

# Score Keeper - Plugin Integration and Event Wiring
New Score Keeper contributors should start with
[CONTRIBUTING.md](../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](domain-score-tracking.md)
describes state and [README.md](../README.md) gives full command syntax.
## Paper lifecycle
The entry point is
[ScoreKeeperPlugin](../src/main/java/com/majinnaibu/minecraft/plugins/scorekeeper/ScoreKeeperPlugin.java),
declared in [plugin.yml](../src/main/resources/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/](../src/main/java/com/majinnaibu/minecraft/plugins/scorekeeper/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:
```java
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`:
```java
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](human-tests.md). Do not assume a successful compilation verifies
companion load order or runtime API compatibility.