From ade5f069e30afcbc1a91192180a5447c8b65e62e Mon Sep 17 00:00:00 2001 From: ehko Date: Tue, 26 May 2026 13:31:21 -0400 Subject: [PATCH] actually bruh --- README.md | 2 +- docs/01_Overview.md | 15 +- docs/02_Configuration.md | 27 +-- docs/03_Commands.md | 48 +++++- docs/06_Operations.md | 8 +- docs/07_UI.md | 132 +++++++++++++++ docs/README.md | 1 + pom.xml | 50 ++++++ .../kewwbec/staff/StaffPermissionsNodes.java | 23 +++ .../java/net/kewwbec/staff/StaffPlugin.java | 36 ++-- .../kewwbec/staff/command/StaffUICommand.java | 36 ++++ .../staff/command/chat/StaffChatCommand.java | 34 ++-- .../staff/command/lookup/HistoryCommand.java | 14 +- .../staff/command/lookup/LookupCommand.java | 14 +- .../command/punish/PunishmentCommands.java | 80 ++++----- .../net/kewwbec/staff/config/StaffConfig.java | 9 - .../staff/config/StaffConfigLoader.java | 1 - .../kewwbec/staff/listener/BanEnforcer.java | 3 +- .../kewwbec/staff/listener/ChatEnforcer.java | 36 ++-- .../staff/listener/PlayerSeenTracker.java | 5 + .../staff/service/PunishmentService.java | 2 +- .../staff/service/StaffChatService.java | 3 +- .../staff/service/StaffChatToggleService.java | 34 ++++ .../net/kewwbec/staff/ui/HistoryPage.java | 105 ++++++++++++ .../kewwbec/staff/ui/PlayerDetailPage.java | 160 ++++++++++++++++++ .../kewwbec/staff/ui/PlayerLookupPage.java | 145 ++++++++++++++++ .../net/kewwbec/staff/ui/PunishFormPage.java | 144 ++++++++++++++++ .../net/kewwbec/staff/ui/StaffHubPage.java | 51 ++++++ .../net/kewwbec/staff/ui/StaffUIContext.java | 34 ++++ .../kewwbec/staff/util/StaffPermissions.java | 34 +--- src/main/resources/manifest.json | 2 +- 31 files changed, 1109 insertions(+), 179 deletions(-) create mode 100644 docs/07_UI.md create mode 100644 src/main/java/net/kewwbec/staff/StaffPermissionsNodes.java create mode 100644 src/main/java/net/kewwbec/staff/command/StaffUICommand.java create mode 100644 src/main/java/net/kewwbec/staff/service/StaffChatToggleService.java create mode 100644 src/main/java/net/kewwbec/staff/ui/HistoryPage.java create mode 100644 src/main/java/net/kewwbec/staff/ui/PlayerDetailPage.java create mode 100644 src/main/java/net/kewwbec/staff/ui/PlayerLookupPage.java create mode 100644 src/main/java/net/kewwbec/staff/ui/PunishFormPage.java create mode 100644 src/main/java/net/kewwbec/staff/ui/StaffHubPage.java create mode 100644 src/main/java/net/kewwbec/staff/ui/StaffUIContext.java diff --git a/README.md b/README.md index 8617130..10f4b3b 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Staff tools for KweebecNet: punishments, history, cross-server staff chat, playe - **NetworkCore** with `mysql.enabled: true` and a healthy MySQL connection - A working `network.auth_token` configured the same way as for NetworkCore (since this plugin uses the cross-server bus) -- The built-in Hytale `PermissionsModule` populated with at least one of the configured staff groups (defaults: `OP`, `ADMIN`, `MOD`, `HELPER`) +- Hytale permission nodes assigned to your staff group(s). Grant `networkstaff.*` for full access, or pick specific nodes - see [docs/03_Commands.md](docs/03_Commands.md#permissions). If MySQL isn't configured in NetworkCore, Staff refuses to start and logs a clear error. Fix NetworkCore first, then restart. diff --git a/docs/01_Overview.md b/docs/01_Overview.md index 61119f5..2dfd8df 100644 --- a/docs/01_Overview.md +++ b/docs/01_Overview.md @@ -9,10 +9,11 @@ StaffPlugin (entry) | +-- PunishmentRepository SQL CRUD | +-- MuteCache in-memory map of active mutes for online players +-- StaffChatService cross-server staff chat over MessageBus - +-- PlayerSeenTracker writes to players_seen on join/leave + +-- StaffChatToggleService in-memory toggle of which players have chat-to-staff mode on + +-- PlayerSeenTracker writes to players_seen on join/leave; clears toggle on disconnect | +-- PlayerSeenRepository SQL CRUD +-- BanEnforcer cancels PlayerSetupConnectEvent if banned - +-- ChatEnforcer cancels PlayerChatEvent if muted + +-- ChatEnforcer cancels PlayerChatEvent if muted; routes to staff chat if toggle is on ``` External dependencies: @@ -28,7 +29,8 @@ External dependencies: |---|---| | [StaffPlugin.java](../src/main/java/net/kewwbec/staff/StaffPlugin.java) | Plugin entry. Reads NetworkCore services, bootstraps schema, registers everything. | | [config/StaffConfig.java](../src/main/java/net/kewwbec/staff/config/StaffConfig.java) | Default config shape. | -| [util/StaffPermissions.java](../src/main/java/net/kewwbec/staff/util/StaffPermissions.java) | `isStaff(uuid)` / `isAdmin(uuid)` checks. | +| [util/StaffPermissions.java](../src/main/java/net/kewwbec/staff/util/StaffPermissions.java) | Thin wrapper for `PermissionsModule.get().hasPermission(uuid, node)`. Just `has(ref, node)`. | +| [StaffPermissionsNodes.java](../src/main/java/net/kewwbec/staff/StaffPermissionsNodes.java) | String constants for every `networkstaff.*` node. | | [util/PlayerResolver.java](../src/main/java/net/kewwbec/staff/util/PlayerResolver.java) | Name -> UUID, looks at online players first, then players_seen. | | [util/DurationParser.java](../src/main/java/net/kewwbec/staff/util/DurationParser.java) | Parse "30m", "2h" etc.; format milliseconds back to human strings. | | [model/Punishment.java](../src/main/java/net/kewwbec/staff/model/Punishment.java), [PunishmentType.java](../src/main/java/net/kewwbec/staff/model/PunishmentType.java), [PlayerSeen.java](../src/main/java/net/kewwbec/staff/model/PlayerSeen.java) | Record types. | @@ -38,12 +40,13 @@ External dependencies: | [service/PunishmentService.java](../src/main/java/net/kewwbec/staff/service/PunishmentService.java) | Issuing/revoking + cross-server broadcast + applying remote actions locally. | | [service/MuteCache.java](../src/main/java/net/kewwbec/staff/service/MuteCache.java) | Per-server hot cache of active mutes for online players. | | [service/StaffChatService.java](../src/main/java/net/kewwbec/staff/service/StaffChatService.java) | Cross-server staff chat over MessageBus. | +| [service/StaffChatToggleService.java](../src/main/java/net/kewwbec/staff/service/StaffChatToggleService.java) | In-memory set of players whose chat is currently being routed to staff chat. | | [service/PunishmentBroadcast.java](../src/main/java/net/kewwbec/staff/service/PunishmentBroadcast.java), [StaffChatPayload.java](../src/main/java/net/kewwbec/staff/service/StaffChatPayload.java) | Wire payloads. | | [listener/BanEnforcer.java](../src/main/java/net/kewwbec/staff/listener/BanEnforcer.java) | `PlayerSetupConnectEvent` -> cancel + set reason if banned. | -| [listener/ChatEnforcer.java](../src/main/java/net/kewwbec/staff/listener/ChatEnforcer.java) | `PlayerChatEvent` -> cancel if muted. | -| [listener/PlayerSeenTracker.java](../src/main/java/net/kewwbec/staff/listener/PlayerSeenTracker.java) | Connect/disconnect -> update players_seen, load/clear mute cache. | +| [listener/ChatEnforcer.java](../src/main/java/net/kewwbec/staff/listener/ChatEnforcer.java) | `PlayerChatEvent` -> cancel if muted; otherwise route to staff chat if toggle is on. | +| [listener/PlayerSeenTracker.java](../src/main/java/net/kewwbec/staff/listener/PlayerSeenTracker.java) | Connect/disconnect -> update players_seen, load/clear mute cache, clear staff-chat toggle. | | [command/punish/PunishmentCommands.java](../src/main/java/net/kewwbec/staff/command/punish/PunishmentCommands.java) | All 8 punishment commands as nested classes. | -| [command/chat/StaffChatCommand.java](../src/main/java/net/kewwbec/staff/command/chat/StaffChatCommand.java) | `/sc`. | +| [command/chat/StaffChatCommand.java](../src/main/java/net/kewwbec/staff/command/chat/StaffChatCommand.java) | `/sc` - toggles staff-chat-mode for the caller. | | [command/lookup/LookupCommand.java](../src/main/java/net/kewwbec/staff/command/lookup/LookupCommand.java), [HistoryCommand.java](../src/main/java/net/kewwbec/staff/command/lookup/HistoryCommand.java) | `/lookup`, `/history`. | ## Why the design is this shape diff --git a/docs/02_Configuration.md b/docs/02_Configuration.md index 06d5724..57133f2 100644 --- a/docs/02_Configuration.md +++ b/docs/02_Configuration.md @@ -8,10 +8,6 @@ ```json { - "permissions": { - "staff_groups": ["OP", "ADMIN", "MOD", "HELPER"], - "admin_groups": ["OP", "ADMIN"] - }, "chat": { "channel": "staff.chat", "prefix": "[Staff]" @@ -23,17 +19,10 @@ } ``` +Permission gating no longer lives here. Each command checks a permission node (`networkstaff.`) via Hytale's `PermissionsModule`. Configure those in the Hytale server's own permission config - see [03_Commands.md](03_Commands.md#permissions) for the full list of nodes. + ## Fields -### permissions - -| Field | Default | Meaning | -|---|---|---| -| `staff_groups` | `["OP", "ADMIN", "MOD", "HELPER"]` | Any Hytale group name in this list grants access to staff commands. | -| `admin_groups` | `["OP", "ADMIN"]` | Subset that the plugin treats as "admin". v1 doesn't gate anything on this, but it's available for future features that want to require admin (e.g. unban after a permaban). | - -Group names come from `PermissionsModule.get().getGroupsForUser(uuid)`. Set up your groups in the Hytale server's own configuration first; this plugin just reads them. - ### chat | Field | Default | Meaning | @@ -48,13 +37,13 @@ Group names come from `PermissionsModule.get().getGroupsForUser(uuid)`. Set up y | `punishments` | `"staff_punishments"` | Table name. Change only if you really need to and you're prepared to migrate data. | | `players_seen` | `"staff_players_seen"` | Same. | -## Setting up staff groups +## Setting up staff permissions -In your Hytale server config, define groups and assign players: - -1. Make sure the group names you assign match one of `staff_groups` exactly. The check is case-sensitive (groups in Hytale tend to be uppercase, e.g. `OP`). -2. After assigning, the player needs to relog for the change to take effect (`PermissionsModule.get().getGroupsForUser` is queried on each command, but Hytale generally requires a reconnect for group changes to apply on the player side). -3. Test with `/lookup ` from a staff account. If you see the lookup output, you have staff perms. +1. In your Hytale server's permission config, create a `staff` group (or whatever you want to call it). +2. Grant it `networkstaff.*` (or pick specific nodes - see [03_Commands.md](03_Commands.md#permissions) for the list). +3. Assign your staff members to that group. +4. Players need to reconnect for newly-granted permissions to apply. +5. Test with `/lookup ` from a staff account. If you see the lookup output, the perms are wired. ## Environment variables diff --git a/docs/03_Commands.md b/docs/03_Commands.md index 1acc864..9527701 100644 --- a/docs/03_Commands.md +++ b/docs/03_Commands.md @@ -1,6 +1,29 @@ # Commands -All commands require the caller to be in one of the `staff_groups` from config. Player-only (no console support yet). +Player-only (no console support yet). Every command is gated by a Hytale permission node. + +## Permissions + +| Command | Required permission | +|---|---| +| `/ban` | `networkstaff.ban` | +| `/tempban` | `networkstaff.tempban` | +| `/mute` | `networkstaff.mute` | +| `/tempmute` | `networkstaff.tempmute` | +| `/kick` | `networkstaff.kick` | +| `/warn` | `networkstaff.warn` | +| `/unban` | `networkstaff.unban` | +| `/unmute` | `networkstaff.unmute` | +| `/sc` | `networkstaff.chat` (also required to *receive* staff chat) | +| `/lookup` | `networkstaff.lookup` | +| `/history` | `networkstaff.history` | +| `/staffui` | `networkstaff.ui` | + +Grant `networkstaff.*` to give a group everything. Without the relevant node, Hytale's command system blocks the call before our handler runs - the user sees the standard `You do not have permission for this command.` message. + +Permissions are wired via `AbstractCommand.requirePermission(node)` in each command's constructor. We also override `canGeneratePermission()` to return false so Hytale doesn't auto-generate a different permission name and gate on that instead. + +Receiving staff chat also requires `networkstaff.chat`, so a moderator who can read it can also send to it. If you want separate read/send permissions later, split the check in [StaffChatService.java](../src/main/java/net/kewwbec/staff/service/StaffChatService.java) into two nodes. ## Punishments @@ -38,11 +61,20 @@ Same as unban but for mutes. Also clears the entry from the mute cache on any se ## Staff chat -### /sc <message> +### /sc -Broadcasts on the cross-server staff chat channel. Visible only to other players whose `getGroupsForUser` overlaps with `staff_groups`. +Toggles **staff chat mode** for you. No arguments. (Hytale command args are single-token, so a multi-word `/sc ` doesn't work; the toggle is the workaround.) -System events (a ban being issued, an unban) also post a line on this channel automatically. +When ON: +- Anything you type into world chat is intercepted, the regular chat event is cancelled, and the message is broadcast on the cross-server staff chat channel. +- Other players who have `networkstaff.chat` see it; nobody else does. +- Run `/sc` again to turn it off. + +When OFF: regular chat behaves normally. + +System events (a ban issued, an unban) also post a line on this channel automatically. + +The toggle state lives in memory on this server only and is cleared when you disconnect. If you log into a different server, you'll need to re-toggle. ## Lookup @@ -61,11 +93,17 @@ If the player has never connected, only the name/UUID lookup happens via online Last 20 punishment entries for the player, most recent first. Each entry shows id, state (`ACTIVE` / `EXPIRED` / `REVOKED`), type, who issued it, when, and the reason. +## UI + +### /staffui + +Opens the Staff Hub page. From there: Player Lookup -> select player -> Detail page with action buttons. See [07_UI.md](07_UI.md) for the page flow and how to add new screens. + ## Common error cases | Error message | Cause | |---|---| -| "You do not have permission to use this command." | Caller isn't in any of `staff_groups`. | +| "You do not have permission for this command." | Caller lacks the relevant `networkstaff.` node. Add it (or `networkstaff.*`) to their group. Hytale's command system enforces this before our handler runs. | | "No player found with that name (online or in history)." | Name doesn't match any online player or any name in `staff_players_seen`. | | "Database error: ..." | MySQL query failed. Check NetworkCore connection (`/networkcore status`). | | "Bad duration. Use 30s, 5m, 2h, 7d, 1w." | Duration string didn't parse. Last char must be `s`/`m`/`h`/`d`/`w`, prefix must be a positive integer. | diff --git a/docs/06_Operations.md b/docs/06_Operations.md index d01ebdd..2181010 100644 --- a/docs/06_Operations.md +++ b/docs/06_Operations.md @@ -5,7 +5,7 @@ 1. `/networkcore status` - confirm "Database: ok" and "Network: connected". 2. Connect a staff alt. Run `/lookup `. You should see your own UUID and online status. 3. Connect a non-staff alt. Have a staff member run `/warn test`. The alt receives a chat message; nothing crashes. -4. `/sc test` from staff. Every other online staff member sees it. No non-staff sees it. +4. `/sc` from a staff alt to toggle staff-chat-mode ON, then type a message in chat. Every other online staff member sees it; no non-staff sees it. Run `/sc` again to turn off. 5. Look in MySQL: ```sql SELECT * FROM staff_punishments ORDER BY issued_at DESC LIMIT 5; @@ -14,9 +14,9 @@ ## A staff member can't run staff commands -1. Check what groups Hytale shows for them. There's no direct command for this in Staff; have them run `/lookup ` - if it works, they're staff. If it doesn't, the group is wrong. -2. `PermissionsModule.get().getGroupsForUser(uuid)` is the source. Confirm the group name they're assigned matches one in `permissions.staff_groups` in `config.json` (case-sensitive). -3. Group changes typically require the player to reconnect. +1. The plugin checks `PermissionsModule.get().hasPermission(uuid, "networkstaff.")`. The error message includes the exact node that was checked - read it. +2. Confirm their group has either the specific node or `networkstaff.*`. See [03_Commands.md](03_Commands.md#permissions) for the full list. +3. Permission changes typically require the player to reconnect. ## A ban isn't propagating diff --git a/docs/07_UI.md b/docs/07_UI.md new file mode 100644 index 0000000..9db7d38 --- /dev/null +++ b/docs/07_UI.md @@ -0,0 +1,132 @@ +# UI + +Built on top of [HyUI](https://github.com/Elliesaur/HyUI), a third-party library that wraps Hytale's native Custom UI API in an HTML-like markup (HYUIML). HyUI is bundled into the Staff jar via the shade plugin so you don't need it installed separately. + +## Reading the source + +Each page lives in [src/main/java/net/kewwbec/staff/ui/](../src/main/java/net/kewwbec/staff/ui/). Pages don't extend the SDK's `InteractiveCustomUIPage` anymore - they're plain classes that build a `PageBuilder` and expose an `open(store)` method. Navigation between pages is `new OtherPage(...).open(store)`. + +## Page flow + +``` +/staffui + | + v +[StaffHubPage] + | + +-> Player Lookup -> [PlayerLookupPage] (text search, click result for detail) + | + v + [PlayerDetailPage] + | + +-> Ban / TempBan / Mute / TempMute / Kick / Warn --> [PunishFormPage] + +-> Unban / Unmute (applied inline, page re-opens) + +-> History --> [HistoryPage] +``` + +Every non-hub page has a Back button. ESC always closes (lifetime is `CanDismiss`). + +## Page reference + +| Page | Purpose | +|---|---| +| [StaffHubPage](../src/main/java/net/kewwbec/staff/ui/StaffHubPage.java) | Top-level menu. Currently just routes to Player Lookup; add buttons here for new sections. | +| [PlayerLookupPage](../src/main/java/net/kewwbec/staff/ui/PlayerLookupPage.java) | Text input + Search button. Online players are listed first; if you type a name not online, the players_seen table is consulted. Click a result to open detail. | +| [PlayerDetailPage](../src/main/java/net/kewwbec/staff/ui/PlayerDetailPage.java) | Player info + active ban/mute lines + action buttons. Unban / Unmute fire immediately (no form); the page reopens with refreshed data. | +| [PunishFormPage](../src/main/java/net/kewwbec/staff/ui/PunishFormPage.java) | Reason field, plus duration field for timed types. Validation errors re-open the same page with the entered values and a red error line. | +| [HistoryPage](../src/main/java/net/kewwbec/staff/ui/HistoryPage.java) | Last 50 entries for the target. Read-only. | + +## HyUI patterns we use + +All pages follow the same skeleton: + +```java +public final class SomePage { + private final PlayerRef playerRef; + private final StaffUIContext sCtx; + private final PageBuilder page; + + public SomePage(PlayerRef playerRef, StaffUIContext sCtx, ...args) { + this.playerRef = playerRef; + this.sCtx = sCtx; + + // Build the HYUIML based on constructor state + String html = """ +
+
+
+ +
+
+
+ """; + + this.page = PageBuilder.pageForPlayer(playerRef) + .withLifetime(CustomPageLifetime.CanDismiss) + .fromHtml(html); + + // Wire listeners by element id + page.addEventListener("someBtn", CustomUIEventBindingType.Activating, (ignored, pCtx) -> { + // pCtx.getValue("inputId", String.class).orElse("") to read input values + // Navigate by opening a new page instance + }); + } + + public void open(Store store) { page.open(store); } +} +``` + +### Reading input values + +Inside an event listener, the second argument is the HyUI page context, which exposes `getValue(id, type)`: + +```java +page.addEventListener("submitBtn", CustomUIEventBindingType.Activating, (ignored, pCtx) -> { + String reason = pCtx.getValue("reasonInput", String.class).orElse("").trim(); + ... +}); +``` + +### Re-rendering after state changes + +We do *not* mutate the page in place. Instead, we open a fresh page instance with new state: + +```java +new PunishFormPage(playerRef, sCtx, uuid, name, type, durationFromForm, reasonFromForm, "Error: ...").open(store); +``` + +This is simpler than HyUI's `updatePage(true)` re-render dance and keeps each page render side-effect-free. The cost is rebuilding the HTML each navigation - cheap compared to anything else we do. + +### HYUIML class reference (the bits we use) + +- `
` - root, dims background, captures input +- `
` - main framed window with header +- `
` - inner content area +- `` - clickable button (use `class="back-button"` for back-themed) +- `` - text input +- `

` / `