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, .UnmuteAsync — internal 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.