Players¶
The Players module is a player browser, profile viewer, and admin-action
toolkit built entirely on the existing Module/GUI/Action/Audit/Settings
infrastructure - no shortcuts specific to a built-in module (see docs/development/architecture-rules.md's
"Built-in Modules Stay Extension-Friendly"). Every mutation is an
Action run through ActionExecutor, so it is permission-checked and
audited the same way regardless of whether it's clicked in the GUI or (in
the future) invoked from a command.
Open via /admin → Players (needs universaladmin.players.view).
Player Browser¶
The Players button of the main menu opens the online players. The three lists are tabs in the header - Online, Offline and Recently seen; the open one is white:
- Online Players - currently connected players, with cycling World
and Gamemode filter buttons (only meaningful for online players - an
offline player's world/gamemode isn't available without loading their
data, see "Offline limitations" below) and a name sort. In
network mode the list holds every player of the
network with the server they are on, plus a Server filter and
sorting by name or by server. Players on another server open the same
profile: it says where they are and offers Teleport to player,
Bring to me and Send to server...; what needs them on this server
(live inventory, ender chest, effects) is left out. Vanished players stay
hidden from staff without
universaladmin.bypass.vanish; players on servers without UniversalAdmin have no profile and are only in the Network menu. - Offline Players - every player who has ever joined, backed by a small
indexed table (
player_profiles) rather than Bukkit's own (documented slow)getOfflinePlayers()scan. Has a name-search box and a sort toggle (name / last seen). - Recently Seen - the same offline list, fixed to last-seen-first.
Every list is capped at players.gui.max-results (default 200, see
"Settings" below) profiles per query - a deliberate bound, not a bug: the
underlying query is ORDER BY ... LIMIT ? at the database layer, so raising
the cap doesn't change what's findable (search still works), just how many
rows one unfiltered browse shows at once.
Player Profile¶
Clicking a player opens their profile - the place for everything about them.
On the left, what is known about them, in four items (hover for the
details): the player (online status, UUID), where (server, world,
coordinates, respawn location), playtime (first and last join, session,
total) and condition (gamemode, health, food and saturation, level and
XP, ping, locale, active effects, and the IP address if the viewer holds
universaladmin.players.ip). A field that genuinely isn't available
(everything live-state-only, for an offline target) is omitted, not
shown blank - see "Offline limitations".
On the right, one row per purpose, each behind a coloured label:
| Row | Buttons |
|---|---|
| Moderate (red) | the Moderate page, warn, temp mute, kick, temp ban, freeze (unfreeze while frozen) |
| View (blue) | inventory, ender chest, effects, punishment history, reports, staff notes, alts |
| Help & teleport (green) | teleport to them, bring them, heal, feed, flight, all actions |
| More (purple) | the nick, voice chat and other extensions - and what did not fit into its row |
Buttons appear only with their permission, and only while the player is online when they need the live player. A punishment started here asks the same questions as on the Moderate page and comes back to the profile.
At the bottom, the previous and next online player (by name) open their profile in place of this one - "back" still leads to the list - and the UUID can be copied into the chat.
IP address is never attached to the profile data at all (not just hidden in
the GUI) unless the viewer holds the permission - see
GetPlayerIpAddressAction's javadoc. Viewing a profile or an IP address is
not audited (read-only, .notAudited()); every field below the profile
that changes something is.
Actions¶
All gated on their own permission node (see "Permissions"), all routed
through ActionExecutor, all fail cleanly with a "player is no longer
online" message if the target disconnected between the GUI rendering and
the click landing (a real race, not a hypothetical one).
| Action | Node |
|---|---|
| Teleport Admin → Player, Bring Player → Admin, Player → another Player, Teleport to World Spawn, Teleport to Bed/Respawn, Teleport to Coordinates | players.teleport |
| Heal | players.heal |
| Feed | players.feed |
| Extinguish | players.extinguish |
| Clear Effects | players.effects.clear |
| Add Effect | players.effects.add |
| Remove Effect | players.effects.remove |
| Set Health | players.set.health |
| Set Food | players.set.food |
| Set XP (progress) | players.set.xp |
| Set Level | players.set.level |
| Toggle Fly | players.fly.toggle |
| Set Fly Speed | players.fly.speed |
| Set Walk Speed | players.walk-speed |
| Toggle Glow | players.glow |
| Toggle Gravity | players.gravity |
| Toggle Collision | players.collision |
| Gamemode (Survival/Creative/Adventure/Spectator) | players.gamemode |
Health, food, XP and the two speeds are set with a slider that starts at the player's current value; the level stays a number field, and Teleport to Coordinates is a form with X, Y, Z and the world as a list.
All six teleport variants share one action/permission (players.teleport)
- they're the same risk tier and the same GUI section; the audit summary
still distinguishes which variant ran. Every other action above got its own
node deliberately, so e.g. a moderator role can be granted Heal/Feed without
also getting arbitrary Set Health/Set Food access.
Every action here is online-only. There is no "Kill" action - use Set Health to a low value if that's the intent.
Inventory & Ender Chest¶
Reachable from the profile page only when the target is online (hidden, not shown disabled, per the "hide what can't work right now" rule the whole GUI framework follows) - editing an offline player's inventory would need NMS-level player-data-file access, which this project doesn't use (see docs/development/architecture-rules.md's "Security" section on packet/NMS hacks).
- Inventory (
PlayerInventoryPage) - laid out like the player's own screen: main storage in the top three rows, the hotbar below it, then helmet, chestplate, leggings, boots and offhand in the bottom row. The book in the bottom row explains the layout. - Ender Chest (
PlayerEnderChestPage) - the 27 ender chest slots; the rest of the grid is locked.
Both are live in both directions: whatever the player does (picking up,
dropping, eating, moving items) shows up in the editor within one tick, and
with ...inventory.edit/...enderchest.edit every change made in the
editor lands in the player's inventory within one tick - no Save button. A
viewer with only ...view sees the same live view but cannot move
anything. If the player logs off, the editor closes with a notice. The
Staff Mode Inventory/Ender Chest Inspector tools (see
staff-tools.md) open exactly these pages.
How the live sync works. The editor is a normal UniversalAdmin
GuiView (so GuiListener still owns every click, including the
read-only mode), and LiveInventoryService merges it with the real
inventory once per tick, slot by slot: whichever side changed since the
previous tick is copied to the other. If both changed the same slot in the
same tick, the player's inventory wins - copying the editor's stale copy
back could otherwise bring back an item the player had just used or
dropped. Every slot that is not part of the inventory is locked, so an item
can never end up stranded in the menu.
Audit. One entry per editing session, not one per click: when the
editor closes, players.inventory.edit / players.enderchest.edit runs
through ActionExecutor (permission check, audit, action event) and the
staff member gets a summary ("Inventory of Steve edited: 3 slot(s)
changed"). The entry records how many slots changed and a coarse
before/after occupancy ("12/41 slots occupied"), never the items
themselves - see InventoryChangeSummary; item contents are exactly the
kind of sensitive detail the audit log keeps out. Opening an editor
without changing anything records nothing.
Clear Inventory is a dangerous action (clears main storage, armor, and
offhand - matching vanilla /clear, never the ender chest) behind a
confirmation dialog (skipped if gui.confirmations is off), gated on
...inventory.edit.
Offline limitations¶
An offline player has no live entity state. What's still available without touching NMS or a Mojang API call:
- Name, UUID, First Join, Last Seen, Total Playtime (from Bukkit's own
OfflinePlayerstats/usercache - read off the main thread since it's file-backed, seePlayerService#snapshot) - Respawn/bed location (
OfflinePlayer#getBedSpawnLocation())
Not available offline: World, Coordinates, Gamemode, Health, Food, Saturation, XP, Level, Ping, Locale, Active Effects, IP address, Inventory, Ender Chest. The profile page omits these fields entirely for an offline target rather than showing them blank; every action that needs live state fails with a "player is no longer online" message instead of silently doing nothing.
Permissions¶
| Node | Default | Meaning |
|---|---|---|
universaladmin.players.view |
op | Open the player browser and profile pages |
universaladmin.players.ip |
op | View a player's IP address |
universaladmin.players.teleport |
op | All teleport variants |
universaladmin.players.heal |
op | Heal |
universaladmin.players.feed |
op | Feed |
universaladmin.players.extinguish |
op | Extinguish |
universaladmin.players.effects.clear |
op | Clear potion effects |
universaladmin.players.effects.add |
op | Add a potion effect |
universaladmin.players.effects.remove |
op | Remove one potion effect |
universaladmin.players.set.health |
op | Set health |
universaladmin.players.set.food |
op | Set food level |
universaladmin.players.set.xp |
op | Set XP progress |
universaladmin.players.set.level |
op | Set level |
universaladmin.players.fly.toggle |
op | Toggle fly |
universaladmin.players.fly.speed |
op | Set fly speed |
universaladmin.players.walk-speed |
op | Set walk speed |
universaladmin.players.glow |
op | Toggle glowing |
universaladmin.players.gravity |
op | Toggle gravity |
universaladmin.players.collision |
op | Toggle collision |
universaladmin.players.gamemode |
op | Change gamemode |
universaladmin.players.inventory.view |
op | View main inventory, armor, offhand |
universaladmin.players.inventory.edit |
op | Edit/clear main inventory, armor, offhand |
universaladmin.players.enderchest.view |
op | View ender chest |
universaladmin.players.enderchest.edit |
op | Edit ender chest |
See docs/user/permissions.md for how these fit into
the platform-wide list, and docs/architecture/actions.md
for how a node on ActionDefinition.Builder#permission(...) gets enforced.
Settings¶
| Setting | Default | Meaning |
|---|---|---|
players.gui.max-results |
200 | Upper bound on one Offline Players/Search/Recently Seen query |
Registered under the module's own players settings namespace (see
docs/development/settings.md) - the first
built-in module to actually do so.
Database¶
player_profiles (id, last_known_name, first_join, last_seen) is an index
for the Offline/Search/Recently-Seen lists, not the source of truth for any
live field - see the "Player Browser" section above. Created by
PlayerProfileMigration (version 1000); PlayerProfileIndexMigration
(version 1001) adds indexes on last_known_name and last_seen for the
sort/order-by paths. Name search is a case-insensitive substring match
(LIKE '%term%'), which can't use an index - accepted at the expected scale
of "unique players who ever joined a server", not a huge event log.
Commands¶
/admin player, info (/seen), heal, feed, extinguish, fly,
gamemode (/gm), speed, tp (/tpto), tphere, invsee, endersee,
clearinv - see commands.md.