Files

6.5 KiB

Overview

Architecture in one diagram

StaffPlugin (entry)
  +-- StaffPermissions       wraps Hytale PermissionsModule, checks groups from config
  +-- PunishmentService      issue / revoke / lookup, broadcasts on MessageBus
  |     +-- PunishmentRepository    SQL CRUD
  |     +-- MuteCache               in-memory map of active mutes for online players
  +-- StaffChatService       cross-server staff chat over MessageBus
  +-- 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; routes to staff chat if toggle is on

External dependencies:

  • NetworkCore.getDatabase() - MySQL pool (HikariCP). Required.
  • NetworkCore.getMessageBus() - Redis pub/sub. Required.
  • NetworkCore.getServerId() - identifies which server an action was taken on.
  • PermissionsModule.get() - Hytale's built-in group/permission API.

What lives where

File Responsibility
StaffPlugin.java Plugin entry. Reads NetworkCore services, bootstraps schema, registers everything.
config/StaffConfig.java Default config shape.
util/StaffPermissions.java Thin wrapper for PermissionsModule.get().hasPermission(uuid, node). Just has(ref, node).
StaffPermissionsNodes.java String constants for every networkstaff.* node.
util/PlayerResolver.java Name -> UUID, looks at online players first, then players_seen.
util/DurationParser.java Parse "30m", "2h" etc.; format milliseconds back to human strings.
model/Punishment.java, PunishmentType.java, PlayerSeen.java Record types.
db/SchemaBootstrap.java CREATE TABLE IF NOT EXISTS for both tables. Run on start().
db/PunishmentRepository.java SQL for punishments.
db/PlayerSeenRepository.java SQL for player presence.
service/PunishmentService.java Issuing/revoking + cross-server broadcast + applying remote actions locally.
service/MuteCache.java Per-server hot cache of active mutes for online players.
service/StaffChatService.java Cross-server staff chat over MessageBus.
service/StaffChatToggleService.java In-memory set of players whose chat is currently being routed to staff chat.
service/PunishmentBroadcast.java, StaffChatPayload.java Wire payloads.
listener/BanEnforcer.java PlayerSetupConnectEvent -> cancel + set reason if banned.
listener/ChatEnforcer.java PlayerChatEvent -> cancel if muted; otherwise route to staff chat if toggle is on.
listener/PlayerSeenTracker.java Connect/disconnect -> update players_seen, load/clear mute cache, clear staff-chat toggle.
listener/StaffPresenceListener.java Connect/disconnect of staff -> announce on staff chat when they actually join/leave the network (suppresses server transfers via NetworkCore PlayerPresence + a configurable delay).
command/punish/PunishmentCommands.java All 8 punishment commands as nested classes.
command/chat/StaffChatCommand.java /sc - toggles staff-chat-mode for the caller.
command/lookup/LookupCommand.java, HistoryCommand.java /lookup, /history.

Why the design is this shape

  • One service per concern. Punishments and staff chat are independent. Each owns its own MessageBus subscription and can be modified without touching the other.
  • Repositories return models, services orchestrate. Repositories never call services; services call repositories. This keeps SQL out of the punishment-issuing logic.
  • MuteCache is per-server, not network-wide. Mute state is loaded on join for that one player and cleared on leave. Network-wide propagation happens via the MessageBus, which updates other servers' caches when their player base intersects.
  • BanEnforcer talks to MySQL on connect. No cache. Connects are infrequent enough that a single indexed query is fine; the data is authoritative and we don't want stale-cache surprises letting a banned player in.
  • Permission checks live in the command, not in services. That way a future console source can call the same service without re-checking permissions.

Lifecycle

Standard Hytale plugin lifecycle:

  • setup() - load config, build StaffPermissions and MuteCache.
  • start() - require NetworkCore's database + bus, bootstrap schema, build services and repositories, mark this server's stale online flags as offline (crash recovery), register events and commands.
  • shutdown() - stop service subscriptions. The database pool is owned by NetworkCore and closes on its own shutdown.

If MySQL isn't healthy at start(), this plugin logs a severe error and returns. The server keeps running; this plugin just does nothing. Restart once MySQL is back.