Files
core-ranks/docs/01_Overview.md
T
2026-05-26 23:16:33 -04:00

5.1 KiB

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 Entry point. Lifecycle, provider registration.
RanksPermissionsNodes.java String constants for networkranks.* nodes.
config/ Config record + JSON loader.
model/Rank.java (id, parentId, displayName, prefix, priority, builtIn).
model/Grant.java One row of ranks_grants.
db/SchemaBootstrap.java CREATE TABLE IF NOT EXISTS + seeds built-in groups.
db/RankRepository.java CRUD on ranks + ranks_perms, includes inheritance walk.
db/GrantRepository.java CRUD on ranks_grants.
db/UserPermRepository.java CRUD on ranks_user_perms.
provider/RanksPermissionProvider.java Implements PermissionProvider. Reads cache, writes through to MySQL.
service/RanksCache.java In-memory cache + cross-server invalidation publisher/subscriber.
service/InvalidationPayload.java Wire payload on the invalidation channel.
service/RankService.java High-level business API used by commands.
listener/ChatPrefixListener.java Sets PlayerChatEvent formatter using primary rank's prefix.
command/rank/RankCommand.java `/rank create
command/GrantCommands.java /grant, /tempgrant, /revoke, /primary, /ranks.
util/DurationParser.java Parses 30d, 1mo, 1y, etc.
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.