Developer API
Package: wtf.wrongstudios.wrongpunish. For integrations developed separately, use the supplied JAR as a compile-only dependency and declare a platform dependency on WrongPunish. Wait for its readiness before obtaining the API; database initialization is asynchronous and failed startup keeps admission checks closed.
WrongPunishAPI api = WrongPunish.getAPI();
api.getActiveBan(uuid).thenAccept(optionalBan -> {
// Perform data-only work here; completion occurs on an asynchronous worker.
});
api.punishTemplate(uuid, Actor.consoleActor(), "Spam").thenAccept(punishment -> {
// The record is committed. Network delivery follows through the outbox.
});For a custom staff command, capture an Actor on that staff member's owning thread, including only permissions actually granted, inherited groups and hierarchy weight. The platform adapters already do this through LuckPerms when available. Actor.consoleActor() explicitly represents privileged automation, not an ordinary player. Server plugins are trusted code and must not accept fabricated actor grants from clients.
Methods:
getActiveBan(UUID),getActiveMute(UUID): optional active UUID/applicable IP punishments in this node's scope.getPunishments(UUID, page, limit): paginated history, limit 1–100. The one-argument overload returns the latest 100 entries.getWarnings(UUID, page, limit): paginated warning history, including inactive records. Pages are one-based and limits are 1–100.countPunishments(UUID),countWarnings(UUID): total row counts for pagination, without fetching the history.getPunishment(long): optional record by internal numeric ID.ban,mute,warn,kick, orpunish(UUID, Actor, PunishmentType, durationMillis, reason): validated mutations. Zero duration means permanent; warning calls use configured warning expiry.unban,unmute: validated removals with ownership/reason rules.isBanned,isMuted: asynchronous predicates.getTemplate(String): immutable policy catalog lookup.getTemplateStatus(UUID, String): persisted current/next offense, last punishment, next policy and reset timestamp. An empty counter has offense zero. The next offense can exceed the final configured level; the final policy is reused.punishTemplate(UUID, Actor, String): atomic offense resolution and issuance. The two-argument convenience overload is privileged console automation.
Never call .join() or .get() on these futures from a Minecraft game/region thread. Schedule entity changes with the player's entity scheduler. Expected command errors use UserError.key() for localization; storage failures complete exceptionally. Punishment records are immutable, including statistics maps.
Punishment.status(now) resolves ACTIVE, EXPIRED, REVOKED, SUPERSEDED, COMPLETED or HIDDEN. Superseded records retain supersededBy/supersededAt; hidden records retain hiddenAt, hiddenByUuid, hiddenBy and hiddenReason. deletedHistory() classifies removed/hidden rows. Trusted API history queries still return all historical states. The GUI uses HistoryService.page(UUID, HistoryType, deleted, page, limit) and bans(page, limit) for bounded rows and matching counts. Template counters, supersession and the new record commit atomically; default durations begin at issuance.
Paper also registers WrongPunishAPI in ServicesManager. Static registry lookup by WrongPunish.getAPI(serverName) is available if multiple nodes unusually share a JVM; the no-argument registry method requires exactly one active instance.
Events
Paper events reside in wtf.wrongstudios.wrongpunish.event:
| Event | Contract |
|---|---|
PlayerPunishEvent | Asynchronous, cancellable pre-event with validated request; fired inside the mutation transaction before insertion |
PunishmentRevokeEvent | Asynchronous, cancellable pre-revocation event with punishment, target, staff, reason and timestamp; cancellation preserves the record and rolls back the mutation |
PunishmentLadderAdvanceEvent | Asynchronous post-commit event with player, template ID, previous/new offense levels and previous/new punishment records |
PlayerBanEvent, PlayerMuteEvent, PlayerWarnEvent, PlayerKickPunishmentEvent | Asynchronous post-commit events with immutable punishment |
PlayerUnbanEvent, PlayerUnmuteEvent, PlayerUnwarnEvent | Asynchronous post-commit removal notifications |
PunishmentExpireEvent | Asynchronous post-commit cleanup notification |
PunishmentModifyEvent | Asynchronous post-commit modification notification |
Events are data events, not region/entity events. No Bukkit world/entity operations are safe merely because the target UUID is in the event. Schedule such operations on the appropriate scheduler. Pre-event listeners must return quickly and must not wait for another API mutation while the database transaction is open, which could deadlock on the outbox lock. Cancellation rolls back the transaction and does not consume a ladder offense.
Events from synchronized applicable scopes also fire locally. They may occur after the initiating API future completes. Observing a post-event does not grant permission to alter the punishment; use the service/API with the intended actor.
Velocity publishes api.PunishmentChange and api.LadderAdvance; Bungee publishes platform.bungee.BungeePunishmentEvent and BungeeLadderAdvanceEvent through their event managers. Proxy pre-events are not exposed; validated mutations still follow identical permission/policy rules. Supersession is a SUPERSEDE change and must not be interpreted as a fresh ban or manual revocation.
Import and GUI extensions
PunishmentImporter defines a format identifier and asynchronous import entry point. A format implementation owns its parsing and passes validated mutations through the API. No importer is bundled. Historical data-preserving importers may require a dedicated migration package rather than pretending normal console issuance reproduces historical timestamps.
TemplateService exposes immutable sorted templates, lookup and groups. The bundled GUI previews permitted next steps and passes its expected preview back to the mutation service, which rechecks permissions, target protections and policy inside the transaction. A stale offense preview is rejected. Custom template selection must also route to PunishmentService.template to resolve offense numbers atomically; never implement escalation by reading history and issuing a raw punishment afterward.
GUI revocations use PunishmentService.revokeById with the expected target UUID and screen permission context. This rechecks state, actor permissions, target/issuer hierarchy and required reasons. Inventory clicks alone never authorize a mutation. GUI configuration and locale files are described in GUI.md.
Administrative pruning now hides inactive records with a bulk update and retains history/counters/audit. A HIDE_HISTORY outbox notification refreshes views; it is not a new punishment or revocation event. Rich message placeholders accept trusted configuration components only. Keep player-controlled names/reasons in the literal string-value map.