This commit is contained in:
2026-05-26 23:16:33 -04:00
commit 3b023d3c6b
32 changed files with 2931 additions and 0 deletions
+70
View File
@@ -0,0 +1,70 @@
# Overview
## Architecture
```
RanksPlugin (entry)
+-- RanksPermissionProvider implements Hytale's PermissionProvider SPI
| +-- RankRepository SQL: ranks, ranks_perms
| +-- GrantRepository SQL: ranks_grants
| +-- UserPermRepository SQL: ranks_user_perms
| +-- RanksCache in-memory per-user effective perms + group memberships
+-- RankService high-level business API used by commands
+-- ChatPrefixListener PlayerChatEvent -> rewrites formatter with primary rank prefix
+-- Commands: /rank, /grant, /tempgrant, /revoke, /primary, /ranks
```
External:
- **NetworkCore.getDatabase()** for the MySQL pool
- **NetworkCore.getMessageBus()** for cross-server cache invalidation
- **PermissionsModule.get()** for `addProvider`/`removeProvider`/`getProviders`
## Mental model: rank vs group
In Hytale's permission API the unit is a **group**. We call ours a **rank** in user-facing speak but they're the same thing under the hood. A `Rank` row in the DB IS a Hytale group. Every method on `PermissionProvider` operates on group IDs - we just pass `Rank.id()` through.
A player can hold multiple ranks at once via `Grant` rows. Hytale's `getGroupsForUser(uuid)` returns the set of active rank IDs (not revoked, not expired). The "primary" flag is OUR extension - Hytale doesn't have a notion of primary. We use it for chat prefix selection.
## Lifecycle
- `setup()` - load config.
- `start()`:
1. Require NetworkCore's DB + bus.
2. Bootstrap schema; seed Hytale's built-in groups (`hytale:Adventurer`, `hytale:Admin`, etc.) if absent.
3. Build cache + provider.
4. If `provider.replace_default`: remove every existing provider from `PermissionsModule`, remember them, add ours.
5. Register the chat prefix listener and all commands.
- `shutdown()`:
1. Remove our provider.
2. Re-add any providers we removed at start (so we leave the module in the state we found it).
3. Stop the cache (closes the bus subscription).
## File map
| File | Responsibility |
|---|---|
| [RanksPlugin.java](../src/main/java/net/kewwbec/ranks/RanksPlugin.java) | Entry point. Lifecycle, provider registration. |
| [RanksPermissionsNodes.java](../src/main/java/net/kewwbec/ranks/RanksPermissionsNodes.java) | String constants for `networkranks.*` nodes. |
| [config/](../src/main/java/net/kewwbec/ranks/config/) | Config record + JSON loader. |
| [model/Rank.java](../src/main/java/net/kewwbec/ranks/model/Rank.java) | `(id, parentId, displayName, prefix, priority, builtIn)`. |
| [model/Grant.java](../src/main/java/net/kewwbec/ranks/model/Grant.java) | One row of `ranks_grants`. |
| [db/SchemaBootstrap.java](../src/main/java/net/kewwbec/ranks/db/SchemaBootstrap.java) | `CREATE TABLE IF NOT EXISTS` + seeds built-in groups. |
| [db/RankRepository.java](../src/main/java/net/kewwbec/ranks/db/RankRepository.java) | CRUD on `ranks` + `ranks_perms`, includes inheritance walk. |
| [db/GrantRepository.java](../src/main/java/net/kewwbec/ranks/db/GrantRepository.java) | CRUD on `ranks_grants`. |
| [db/UserPermRepository.java](../src/main/java/net/kewwbec/ranks/db/UserPermRepository.java) | CRUD on `ranks_user_perms`. |
| [provider/RanksPermissionProvider.java](../src/main/java/net/kewwbec/ranks/provider/RanksPermissionProvider.java) | Implements `PermissionProvider`. Reads cache, writes through to MySQL. |
| [service/RanksCache.java](../src/main/java/net/kewwbec/ranks/service/RanksCache.java) | In-memory cache + cross-server invalidation publisher/subscriber. |
| [service/InvalidationPayload.java](../src/main/java/net/kewwbec/ranks/service/InvalidationPayload.java) | Wire payload on the invalidation channel. |
| [service/RankService.java](../src/main/java/net/kewwbec/ranks/service/RankService.java) | High-level business API used by commands. |
| [listener/ChatPrefixListener.java](../src/main/java/net/kewwbec/ranks/listener/ChatPrefixListener.java) | Sets `PlayerChatEvent` formatter using primary rank's prefix. |
| [command/rank/RankCommand.java](../src/main/java/net/kewwbec/ranks/command/rank/RankCommand.java) | `/rank create|delete|addperm|removeperm|setparent|setprefix|list|info`. |
| [command/GrantCommands.java](../src/main/java/net/kewwbec/ranks/command/GrantCommands.java) | `/grant`, `/tempgrant`, `/revoke`, `/primary`, `/ranks`. |
| [util/DurationParser.java](../src/main/java/net/kewwbec/ranks/util/DurationParser.java) | Parses `30d`, `1mo`, `1y`, etc. |
| [util/TargetResolver.java](../src/main/java/net/kewwbec/ranks/util/TargetResolver.java) | Resolves a name (online players only) or UUID string to a UUID. |
## Why this shape
- **One provider per server, single source of truth**: replacing Hytale's default removes the risk of two providers giving conflicting answers.
- **Cache around the provider**: hasPermission gets called many times per tick (chat enforcement, command parsing, world actions). Hitting MySQL each time would be a disaster; caching by UUID with bus-based invalidation is the standard pattern.
- **Repositories return models, the service orchestrates**: keeps SQL out of the provider and command handlers.
- **`built_in` flag on ranks**: stops `/rank delete hytale:Admin` from breaking the world.