EndCore Framework

en-admin

The F10 admin panel for players, vehicles, world and self tools, plus the Build tab for placing traders, garages, quests, spawns and zones in game.

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 onen-core, en-ui
Also needsThe en-inventory files present (the server reads @en-inventory.data.items)
Optionalen-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 tablesNone of its own. Build tab content is stored by en-core's content registry
Config filesconfig.lua
Key bindsenadmin_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.

cfg
# server.cfg: make someone an admin
add_principal identifier.license:xxxxxxxxxxxxxxxxxxxxxxxx group.admin

With 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

KeyAction
ECopy the hit position as vec3
GCopy { model, coords = vec4 }
DeleteDelete the entity (never players)
BackspaceExit 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.

KindResource
traderen-shops
dealeren-garages
garageen-garages
questnpcen-quests
questen-quests
benchen-crafting
barberen-clothing
spawnen-respawn
zoneen-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:

ExportReturns
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:

KeyDefaultWhat 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/sSpeeds cycled with the mouse wheel
Config.noclip.defaultSpeed3Index into speeds
Config.noclip.fastMultiplier / slowMultiplier3.0 / 0.25Shift and Ctrl multipliers
Config.laserDistance250.0Laser reach (m)
Config.logtruePrint every admin action to the server console
Config.vehicles33 quick picks such as rebel, technical, insurgent2, mule, sanchez, frogger, dusterVehicles tab, each { model, label, class }
Config.weatherEXTRASUNNY, CLEAR, CLOUDS, SMOG, FOGGY, OVERCAST, RAIN, THUNDER, CLEARING, SNOWLIGHT, SNOW, BLIZZARD, XMAS, HALLOWEENWeather choices; must be types en-weathersync knows
Config.builderthe nine kinds aboveBuild tab kinds, each { kind, label, singular, resource }
lua
-- 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']:

ExportArgumentsReturns
IsAdminsourceboolean (console source 0 is always admin)

Commands

All restricted to Config.principal.

CommandArgumentsWhat it does
/adminOpen the panel
/noclipToggle noclip
/coords [format]vec3, vec4, ped, heading, table or json (default vec4)Copy your coordinates
/laserToggle the entity laser
/tpmTeleport to your waypoint
/tp <x> <y> [z]NumbersTeleport; finds the ground when z is left out
/goto <id>Player idTeleport to a player (joins their routing bucket)
/bring <id>Player idBring a player to you
/heal [id]Player id, default youHeal and restore survival stats
/revive [id]Player id, default youRevive (clears isdead and inlaststand)
/giveitem <id> <item> [count]Count 1-10000Give an item through encore.inventory.addItem
/car <model>Model nameSpawn a vehicle and get in
/dvDelete the vehicle you are in or next to
/announce <message>Up to 300 charactersNotify everyone for 10 seconds
Note

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:

EventPayload
en-admin:client:open
en-admin:client:noclip
en-admin:client:laser
en-admin:client:copyCoordsformat
en-admin:client:waypoint
en-admin:client:teleport{ x, y, z, heading?, findGround? }
en-admin:client:healbyOther
en-admin:client:revivebyOther
en-admin:client:frozenboolean
en-admin:client:spawnVehiclemodel
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:

lua
-- 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:

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:

lua
-- 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)