actually bruh
This commit is contained in:
+9
-6
@@ -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
|
||||
|
||||
@@ -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.<command>`) 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 <staff-member-name>` 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 <staff-member-name>` from a staff account. If you see the lookup output, the perms are wired.
|
||||
|
||||
## Environment variables
|
||||
|
||||
|
||||
+43
-5
@@ -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 <message>` 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.<command>` 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. |
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
1. `/networkcore status` - confirm "Database: ok" and "Network: connected".
|
||||
2. Connect a staff alt. Run `/lookup <your-main>`. You should see your own UUID and online status.
|
||||
3. Connect a non-staff alt. Have a staff member run `/warn <alt> 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 <theirname>` - 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.<command>")`. 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
|
||||
|
||||
|
||||
+132
@@ -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 = """
|
||||
<div class="page-overlay">
|
||||
<div class="container" data-hyui-title="Title">
|
||||
<div class="container-contents">
|
||||
<button id="someBtn">Do thing</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
""";
|
||||
|
||||
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<EntityStore> 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)
|
||||
|
||||
- `<div class="page-overlay">` - root, dims background, captures input
|
||||
- `<div class="container" data-hyui-title="...">` - main framed window with header
|
||||
- `<div class="container-contents">` - inner content area
|
||||
- `<button id="...">Text</button>` - clickable button (use `class="back-button"` for back-themed)
|
||||
- `<input type="text" id="..." value="..." placeholder="...">` - text input
|
||||
- `<p>` / `<label>` - text
|
||||
|
||||
The full HYUIML reference is in `extras to look at/HyUI-Docs/hyuiml-htmlish-in-hytale.md`.
|
||||
|
||||
## Adding a new page
|
||||
|
||||
1. Create `src/main/java/net/kewwbec/staff/ui/MyPage.java` following the skeleton above.
|
||||
2. Use HTMLish in the `fromHtml` block. Stick to alphanumeric IDs (HyUI sanitizes them, but bugs hide in non-trivial chars).
|
||||
3. Wire listeners with `page.addEventListener(id, eventType, handler)`.
|
||||
4. To navigate to it, call `new MyPage(...).open(store)` from another page's handler.
|
||||
5. To open it from a command, do the same thing in the command's `execute` method.
|
||||
|
||||
## Things to be aware of
|
||||
|
||||
- **HyUI must be on the classpath at runtime.** This is handled by the shade plugin in [pom.xml](../pom.xml), which packs HyUI into the Staff jar. Don't drop the shade plugin without a replacement.
|
||||
- **Sanitized IDs.** Use plain alphanumeric ids in HYUIML. Use the same string in `addEventListener` - HyUI translates internally.
|
||||
- **Escape user input** when embedding into HTML strings. We have a tiny `escape(String)` helper in each page; use it for any value that came from a player.
|
||||
- **`value` attribute is what carries text-field state across re-renders.** When you rebuild a form page with an error, set `value="..."` on each input so the user doesn't lose what they typed.
|
||||
- **No `<script>`, no full CSS.** Logic is in Java; layout is via Hytale's `LayoutMode` and `flex-weight` via `style="..."` on `<div>`s if needed.
|
||||
|
||||
## What this UI deliberately doesn't do
|
||||
|
||||
- **Network-wide active punishments view.** Could be added as a Hub button if needed.
|
||||
- **Bulk actions.** No "ban everyone in this list" - intentional.
|
||||
- **In-place updates.** Every state change rebuilds the page. Simpler reasoning, slightly more network chatter.
|
||||
- **Custom themes.** We rely on HyUI's default Container/Button styling. Theming via `<style>` blocks is possible later.
|
||||
@@ -6,3 +6,4 @@
|
||||
4. [04_Schema.md](04_Schema.md) - MySQL tables
|
||||
5. [05_Cross_Server_Propagation.md](05_Cross_Server_Propagation.md) - how bans/mutes propagate
|
||||
6. [06_Operations.md](06_Operations.md) - ops scenarios, debugging
|
||||
7. [07_UI.md](07_UI.md) - the /staffui hub and the page system
|
||||
|
||||
Reference in New Issue
Block a user