en-admin puts developer and admin tools in one panel, opened with F10:
- Players: goto, bring, heal, revive, freeze, kick, give item, give cash.
- Vehicles: spawn from quick picks or any model, and give owned vehicles through en-garages.
- World: weather, time, time freeze and blackout (through en-weathersync), and clearing zombies.
- Announcements to the whole server.
- Self tools: noclip, god mode, invisibility, teleport to your waypoint or to coordinates, copy coordinates in several formats, and an "entity laser" to inspect, copy or delete entities.
It also hosts the Build tab, an in-game content editor. Admins place and edit traders, vehicle dealers, garages, quest NPCs, quests, crafting benches, barbers, spawn points and zombie zones without touching config files. Each owning resource describes its own form and validates the data; en-core's content registry stores it and makes it live.
At a glance
| Depends on | en-core, en-ui |
| Also needs | The en-inventory files present (the server reads @en-inventory.data.items) |
| Optional | en-weathersync, en-zombies, en-garages, en-vehiclekeys, en-shops, en-quests, en-crafting, en-clothing, en-respawn. Each feature is shown only when its resource is started |
| Database tables | None of its own. Build tab content is stored by en-core's content registry |
| Config files | config.lua |
| Key binds | enadmin_panel (F10), enadmin_noclip (F9) |
How it works
Permissions
Commands are granted to Config.principal (default group.admin). Every panel action is checked on the server against the ACE command.admin, and the server console (source 0) always counts as admin. Other resources can reuse the same check with the IsAdmin export.
# server.cfg: make someone an admin
add_principal identifier.license:xxxxxxxxxxxxxxxxxxxxxxxx group.adminWith Config.log on, every admin action is printed to the server console.
Noclip
- WASD moves, E goes up, Q goes down.
- Shift is fast (x3), Ctrl is slow (x0.25), and the mouse wheel cycles speeds.
- Noclip moves your vehicle when you are driving.
- You are invisible and have no collision while active, with a few seconds of fall damage grace when you leave it.
Entity laser
| Key | Action |
|---|---|
| E | Copy the hit position as vec3 |
| G | Copy { model, coords = vec4 } |
| Delete | Delete the entity (never players) |
| Backspace | Exit the laser |
Spawned vehicles
/car and the Vehicles tab spawn the car and put you in it. Your previous admin car is deleted if it is empty. Spawned cars get a plate like ADMINnnn, full fuel (Entity(vehicle).state.fuel = 100.0) and are hotwired through en-vehiclekeys when it is running.
The Build tab
Each kind in Config.builder belongs to a resource. Kinds whose resource isn't started are hidden.
| Kind | Resource |
|---|---|
trader | en-shops |
dealer | en-garages |
garage | en-garages |
questnpc | en-quests |
quest | en-quests |
bench | en-crafting |
barber | en-clothing |
spawn | en-respawn |
zone | en-zombies |
When an admin saves, en-admin calls the owning resource's BuilderValidate, then encore.content.save(kind, id, data, author). Delete calls encore.content.remove(kind, id). If a placed entry was overriding a config entry with the same id, deleting it brings the config version back. The owning resource picks up changes with encore.content.watch(kind, onAll, onChange). See Content registry and Content API.
Builder contract
To make your own kind editable, the owning resource exports these on the server:
| Export | Returns |
|---|---|
BuilderList(kind) | { id, label, subtitle?, coords?, placed, inConfig }[] |
BuilderGet(kind, id) | Editable plain-table data, or nil |
BuilderValidate(kind, id, data) | ok, errorMessage? |
BuilderSchema(kind) | Form fields: { key, type, label, min?, max?, step?, default?, required?, options?, help? }[] |
BuilderTemplate(kind) | Default data for "New" |
In a schema field, key is a dotted path into the data. Field types used by the shipped resources include 'text', 'select', 'coords' and 'number'.
Configuration
config.lua:
| Key | Default | What it does |
|---|---|---|
Config.principal | 'group.admin' | Principal granted the admin commands |
Config.keys.panel / noclip | 'F10' / 'F9' | Default keys |
Config.noclip.speeds | { 1.0, 4.0, 10.0, 25.0, 60.0, 150.0 } m/s | Speeds cycled with the mouse wheel |
Config.noclip.defaultSpeed | 3 | Index into speeds |
Config.noclip.fastMultiplier / slowMultiplier | 3.0 / 0.25 | Shift and Ctrl multipliers |
Config.laserDistance | 250.0 | Laser reach (m) |
Config.log | true | Print every admin action to the server console |
Config.vehicles | 33 quick picks such as rebel, technical, insurgent2, mule, sanchez, frogger, duster | Vehicles tab, each { model, label, class } |
Config.weather | EXTRASUNNY, CLEAR, CLOUDS, SMOG, FOGGY, OVERCAST, RAIN, THUNDER, CLEARING, SNOWLIGHT, SNOW, BLIZZARD, XMAS, HALLOWEEN | Weather choices; must be types en-weathersync knows |
Config.builder | the nine kinds above | Build tab kinds, each { kind, label, singular, resource } |
-- config.lua
Config.principal = 'group.admin'
Config.log = true
Config.vehicles = {
{ model = 'rebel', label = 'Rebel', class = 'Off-road' },
{ model = 'technical', label = 'Technical', class = 'Off-road' },
{ model = 'mule', label = 'Mule', class = 'Commercial' },
}Exports
Server exports on exports['en-admin']:
| Export | Arguments | Returns |
|---|---|---|
IsAdmin | source | boolean (console source 0 is always admin) |
Commands
All restricted to Config.principal.
| Command | Arguments | What it does |
|---|---|---|
/admin | Open the panel | |
/noclip | Toggle noclip | |
/coords [format] | vec3, vec4, ped, heading, table or json (default vec4) | Copy your coordinates |
/laser | Toggle the entity laser | |
/tpm | Teleport to your waypoint | |
/tp <x> <y> [z] | Numbers | Teleport; finds the ground when z is left out |
/goto <id> | Player id | Teleport to a player (joins their routing bucket) |
/bring <id> | Player id | Bring a player to you |
/heal [id] | Player id, default you | Heal and restore survival stats |
/revive [id] | Player id, default you | Revive (clears isdead and inlaststand) |
/giveitem <id> <item> [count] | Count 1-10000 | Give an item through encore.inventory.addItem |
/car <model> | Model name | Spawn a vehicle and get in |
/dv | Delete the vehicle you are in or next to | |
/announce <message> | Up to 300 characters | Notify everyone for 10 seconds |
en-inventory also registers /giveitem with the same arguments. Both give the item through the inventory, so it works the same either way.
Events
Client net events the server sends to an admin or a target player:
| Event | Payload |
|---|---|
en-admin:client:open | |
en-admin:client:noclip | |
en-admin:client:laser | |
en-admin:client:copyCoords | format |
en-admin:client:waypoint | |
en-admin:client:teleport | { x, y, z, heading?, findGround? } |
en-admin:client:heal | byOther |
en-admin:client:revive | byOther |
en-admin:client:frozen | boolean |
en-admin:client:spawnVehicle | model |
en-admin:client:deleteVehicle |
The panel sends its actions through the en-admin:action callback: goto, bring, heal, revive, freeze, kick, giveItem, giveCash, spawnVehicle, giveVehicle, fixVehicle, deleteEntity, setWeather, setTime, freezeTime, blackout, clearZombies, announce, and the builder actions builderList, builderGet, builderNew, builderSave, builderDelete.
Examples
Gate your own admin feature on en-admin's permission check:
-- server
RegisterNetEvent('my-res:wipeLoot', function()
local src = source
if not exports['en-admin']:IsAdmin(src) then return end
-- ...
end)Make your own resource's content editable in the Build tab. First add the kind to en-admin's config.lua:
-- en-admin/config.lua, inside Config.builder
{ kind = 'cache', label = 'Supply caches', singular = 'supply cache', resource = 'my-caches' },Then export the builder contract from my-caches:
-- my-caches/server.lua
exports('BuilderSchema', function(kind)
if kind ~= 'cache' then return nil end
return {
{ key = 'label', type = 'text', label = 'Name', max = 60, required = true },
{ key = 'coords', type = 'coords', label = 'Position', required = true },
{ key = 'respawn', type = 'number', label = 'Respawn (min)', min = 1, max = 240, default = 30 },
}
end)
exports('BuilderTemplate', function(kind)
return kind == 'cache' and { label = 'New Cache', respawn = 30 } or nil
end)
exports('BuilderValidate', function(kind, id, data)
return data.coords ~= nil, 'A cache needs a position.'
end)
-- plus BuilderList and BuilderGet, and pick up saved caches with
-- encore.content.watch('cache', onAll, onChange)