Skip to content

Generated reference. This page mirrors Code/Admin/README.md, this module's source-of-truth documentation (Standards/02_DOCUMENTATION_STANDARDS.md §1). Edit it there, not here -- this file is regenerated on every docs build and any local edit is silently overwritten.

Admin

Purpose. Moderation commands (/grant, /revoke, /kick, /ban, /unban, /mute, /unmute), Milestone 11's admin movement commands (/goto, /bring, /tp, /noclip, /spectate, /unspectate), and the interactive admin panel/dev menu UI that fronts the Milestone 10 half. Milestone 10's answer to Documentation/Guides/04_SERVER_ADMIN_GUIDE.md's previous "no such command exists yet" gap. See Documentation/Architecture/11_UI_ARCHITECTURE.md §7 for the on-demand-panel pattern this module introduces, and Documentation/Architecture/22_PLAYER_PRESENCE_AND_ADMIN_TOOLS.md §3 for the movement commands.

Responsibilities. - AdminModule — registers /grant <characterId> <permission>, /revoke <characterId> <permission>, /ban <characterId> <reason...> [durationMinutes], /unban <characterId>, /mute <characterId> [durationMinutes], and /unmute <characterId>, all gated by admin.moderate. GrantAsync/RevokeAsync/BanAsync/ UnbanAsync/MuteAsync/UnmuteAsync/ResolveTarget are internal static, taking ICharacterService (and, for ban/mute/unban/unmute, IAccountService/IClock) as parameters, the same InventoryModule.GiveAsync shape — testable with no module-boot machinery. Target resolution is always a CharacterId guid from ICharacterService.GetAllActive(), never a typed player name. ICharacterService. FindActiveByName (Milestone 12, Code/Characters/ICharacterService.cs) now exists and could resolve a typed name for these commands too, but ResolveTarget wasn't switched to use it in that pass — Chat's /pm was its first wired consumer, not Admin's commands; this remains real, available future work, not something still unbuilt. /ban//mute can therefore only target an account behind a currently-connected character, not an offline SteamId directly. /ban's <reason...> is free text with an optional trailing <durationMinutes> (omitted = permanent); a reason that itself ends in a number will misparse as a duration — named, not solved, in AdminModule.cs's own header comment. /mute takes no reason, matching 22_PLAYER_PRESENCE_AND_ADMIN_TOOLS.md §6's own shape, so it has no such ambiguity. /unban//unmute both reset AccountModeration.Status back to None (sharing one private ClearModerationAsync body) rather than erasing the prior record, the same way /revoke doesn't erase that a permission was ever granted. AccountModeration.Status is a single field, not independent ban/mute flags — muting an already-banned account (or the reverse) silently replaces the older record; there is no "banned and muted at once" state. Real, named limitation of the shape 22_PLAYER_PRESENCE_AND_ADMIN_TOOLS.md §6 specifies, not solved here. - AdminKickModule — registers /kick <characterId>, also gated by admin.moderate, as its own sibling module rather than a third command on AdminModule. It needs the real S&box Connection type to actually disconnect someone, which OfflineTests deliberately doesn't fake, and MSBuild can only include or exclude a whole file — splitting it out is what lets AdminModule.cs's tested logic and this module's untested disconnect step both compile in the environments that need them. - AdminMovementModule (Milestone 11) — registers /goto//bring//tp (admin.teleport), /noclip (admin.noclip), and /spectate//unspectate (admin.spectate), each targeting a live PawnComponent through IPawnLocator (Applejack.Movement, ADR-0007). A sibling module to AdminModule for the same MSBuild whole-file reason as AdminKickModule — every Execute body here touches an engine-facing PawnComponent/AdminSpectateComponent, which OfflineTests doesn't fake. Reuses AdminModule.ResolveTarget, not a second copy. - AdminToolsRootComponent — toggles AdminPanel/DevMenuPanel/PlayerListPanel visibility on their own keybinds (admin_panel, dev_menu, player_list), gated on the caller's own permission for UX only. Placed on PlayerPawn.prefab's root alongside the three panels (each its own child GameObject, starting disabled) and a new CommandDispatchComponent sibling the panels' own Commands/Viewer properties reference cross-GameObject — see Assets/Prefabs/README.md (issue #36). - AdminSpectateComponent (Milestone 11) — the host-authoritative SpectatingCharacterId flag /spectate//unspectate set, plus the local-client camera follow that acts on it. Sits alongside PawnComponent on the caller's own pawn, discovered through IPawnLocator rather than a second registry service. See 22_PLAYER_PRESENCE_AND_ADMIN_TOOLS.md §3.1. - AdminPanel.razor — grants/revokes one of this codebase's known permission strings, or kicks, per currently-connected character. - DevMenuPanel.razor — browses items/jobs with a spawn/join button, shows ApplejackBootstrap.BootOrder and every IPluginCatalog.Plugins entry (state + reason, per 23_PLUGIN_SYSTEM.md §6), reflects generically over every IConfigurationInspector-registered config, and tails IGameLogHistory. - PlayerListPanel.razor (Milestone 11) — a sortable list (name, distance from the viewing admin's own pawn, raw position) of every IPawnLocator.All entry, joined against ICharacterService.GetAllActive() by CharacterId. No textured minimap — no per-map coordinate-calibration convention exists anywhere in this codebase yet to build one against. Gated by admin.moderate, the same permission as AdminPanel (an oversight tool, not a content-authoring one like DevMenuPanel's admin.give). See 22_PLAYER_PRESENCE_AND_ADMIN_TOOLS.md §4.

Not responsible for. - A textured 2D minimap. PlayerListPanel above is a sortable list, not a map — see its own bullet for why. - Enforcing the mute itself. That's Chat's job — /mute here only writes the moderation record; ChatRouter.SendAsync (Code/Chat/README.md) is what actually refuses a muted sender's messages. - Banning or muting an offline account directly. /ban//mute's target is always a currently-connected character (same ResolveTarget every other command here uses) — there is no way to moderate a SteamId that isn't connected right now. Milestone 12 added ICharacterService.FindActiveByName (Code/Characters/ICharacterService.cs), but it only ever searches the in-memory active roster — it doesn't help here, since an offline account by definition isn't on that roster. Fixable once a persisted name index (or a raw SteamId argument path) exists, neither of which does today. - Telling the calling player the outcome. Same limitation every command has (Code/Commands/README.md "Not responsible for") — a failed grant/revoke/ban/unban/mute/unmute/kick/goto/bring/tp/noclip/spectate is only visible in the server log. - A typed player name. Every target is a CharacterId guid the panel already has from its own live roster or a hand-typed command's argument. ICharacterService.FindActiveByName (Milestone 12) makes resolving "kick PlayerName" possible now, but wiring it into AdminModule's/AdminKickModule's commands is not done in this pass — see "Responsibilities" above.

Dependencies. Core (IServiceRegistry, IServiceResolver, IClock — for /ban's issued/expiry timestamps), Character (ICharacterService, IAccountService, Account, AccountModeration, Character, PermissionSet), Commands (ICommandRegistry, CommandDefinition, CommandDispatchComponent), Movement (IPawnLocator, PawnComponent, for AdminMovementModule/AdminSpectateComponent), Items (IItemRegistry, for the dev menu's item browser), Jobs (IJobRegistry, for the dev menu's job browser), the S&box engine (Component, Input, PanelComponent, Connection for /kick only, a camera component for AdminSpectateComponent).

Public API. - AdminModule.ResolveTarget, .GrantAsync, .RevokeAsync, .BanAsync, .UnbanAsync, .MuteAsync, .UnmuteAsyncinternal static, for direct testability. - AdminModule, AdminKickModule, AdminMovementModule — registered in ApplejackBootstrap.Modules.

Events published. None.

Persistence. None of its own — grant/revoke mutate and save the target Character through the existing ICharacterService; ban/unban/mute/unmute mutate and save the target's Account through IAccountService (accounts/{steamId}); kick, the movement commands, and PlayerListPanel have no persistence at all (IPawnLocator's pawn positions are ephemeral, per ADR-0007).

Future improvements. - A textured 2D minimap for PlayerListPanel, once a per-map coordinate-calibration convention exists anywhere in this codebase to build one against. - Banning or muting an offline account directly, once a persisted name index or a raw SteamId argument path exists — ICharacterService.FindActiveByName (Milestone 12) doesn't help here, since it only searches the in-memory active roster; see "Not responsible for" above. - Wiring AdminModule.ResolveTarget/AdminKickModule to accept a typed name via ICharacterService.FindActiveByName (Milestone 12) instead of only a CharacterId guid — the mechanism exists; these commands weren't switched to use it in this pass. - Independent ban/mute state, if a real need for "banned and muted at the same time" ever shows up — AccountModeration is a single-status record today, see "Responsibilities" above. - A reply-to-caller RPC so a failed grant/revoke/ban/unban/mute/unmute/kick/goto/bring/tp/ noclip/spectate reaches the admin who ran it, once Chat builds one for every command. - A rebindable keybind for admin_panel/dev_menu/player_list, once any panel needs configurable input — the action names themselves are already the only thing an operator needs to remap through S&box's own input-binding settings.

Not unit-tested. AdminKickModule's disconnect step (needs a real Connection), AdminMovementModule's Execute bodies (each resolves a live PawnComponent/ AdminSpectateComponent through IPawnLocator, an engine type OfflineTests doesn't fake — ResolveTarget/existence/permission gating it reuses are already covered by AdminModuleTests/CommandDispatchComponent's own tests), and every file under Ui/ (Component, Input, PanelComponent, razor markup, AdminSpectateComponent's camera follow, PlayerListPanel's sort/distance logic) — reviewed by reading, excluded from OfflineTests where they're .cs (razor files are never compiled by that project at all), the same treatment MoneyDisplay/InventoryPanel and CommandDispatchComponent already get. See Documentation/Standards/03_TESTING_STANDARDS.md §1.