Punishment History GUI
The paper module's whole purpose is /history — a staff GUI over a player's full punishment record, with an in-menu pardon flow. It's built entirely on AstralCore's YAML blueprint menu framework: no hand-written Java menu classes, three blueprints under menus/punishments/, one custom action verb ([pardon-punishment]), and a MenuService that owns the shared MenuContainer and open/fetch/filter logic for both the command and the pardon action.
Flow
HistoryCommand#onHistory parses the optional [duration] filter (see Commands → duration syntax) and calls MenuService#openHistory(viewer, target, filter), which:
Fetches every punishment ever recorded against the target (
PunishmentService#findHistory→PunishmentRepository#findByTarget) — active, expired, and pardoned alike.If
filteris set, keeps only rows withcreatedAt >= now - filter(a recency window, not a type filter).Sorts newest-first and wraps each row in a
PunishmentPlaceholder.If the resulting list is empty, replies
no-historyand opens nothing.Otherwise opens the
punishment-historymenu viaMenuContainer#computeAndOpen, passing two open parameters:punishments(the wrapped list, as anItemProvider) andtarget(MinecraftPlayerPlaceholder).
punishment-history
54-slot paginated list. The punishments layout (taint: "punishments", provider: "%parameters_punishments%") fills 28 content slots with one punishment item per row, each copy-from the type's display.yml icon with reason/operator/date/duration/status appended as lore. Left-clicking a row opens the detail menu for that specific punishment:
Standard prev/next navigation (slots 46/52) is gated on %layouts_punishments_hasPreviousPage%/%layouts_punishments_hasNextPage%; a close button sits at slot 49.
punishment-detail
54-slot single-punishment view, opened with punishment, target, and punishments (the last carried forward purely so Back can reopen the list without re-fetching). Shows the full info block (reason/operator/date/duration/status) plus two conditional elements:
Retrait (revocation) info — reason/pardoner/date — shown only when
%parameters_punishment_active% == false.Pardon button — shown only when
%parameters_punishment_pardonable% == true(active, not expired, and type isBAN/IPBAN/MUTE) — opens the confirmation menu, forwarding the same three parameters.
Back reopens punishment-history with the original target/punishments parameters (no re-fetch, no filter re-applied — if a duration filter narrowed the original list, going back preserves that narrowed view since the same punishments list is passed through unchanged).
punishment-confirm-pardon
54-slot confirm/cancel step. Cancel returns to punishment-detail unchanged. Confirm fires the custom action:
[pardon-punishment] action
Backed by PardonPunishmentAction (paper/action/PardonPunishmentAction), registered as registerAction("pardon-punishment", PardonPunishmentAction.class).
Arg | Type | Description |
|---|---|---|
|
| The punishment to pardon. |
|
| The punishment's target, used only to re-open the history menu afterward. |
Behavior, in order:
Loads the punishment by id (
PunishmentService#findById). Missing or a lookup failure →error-occurred, logged via SLF4J.If it's already inactive (someone else pardoned it in the meantime) →
already-pardoned.Maps its type to the permission node required to lift it:
punishments.unbanforBAN/IPBAN,punishments.unmuteforMUTE.KICK/WARNhave no mapping →cannot-pardon(defensive; the button is already hidden for these types viapunishment_pardonable).If the clicking player lacks that node →
no-permission.Otherwise pardons it (
PunishmentService#pardon→PunishmentRepository#pardon, no reason — GUI pardons always persistremovedReason = null, unlike/unban//unmutewhich accept one), repliespardon-success, and re-openspunishment-historyfor the target (MenuService#openHistory(player, targetId)— the unfiltered overload, so any duration filter from the original/historycall is dropped on this refresh).
Pardoning here reuses the exact same PunishmentRepository#pardon path as /unban//unmute — it flips active, records the revocation fields, and broadcasts a PunishmentIssuedPacket on punishments.updates, so the lift is visible at the velocity login gate immediately. See Overview → Cross-server sync.
See also
Commands →
/history— the command that opens this flow.Placeholders — the full
punishment_*reference used throughout these three menus.Configuration →
display.yml— the per-type icons used as each row's base item.