EndCore Framework

Minigames and radial menu

Ask for skill checks, lockpicking, hotwiring and sequences with encore.minigame, and add entries to the F1 radial menu with encore.radial.

Two client-side interfaces for things players do with their hands. encore.minigame asks en-minigames to play a short game and tells you whether the player succeeded. encore.radial adds your own entries to the hold-to-open radial menu from en-radialmenu.

Minigames

Every call yields until the game ends and returns whether it succeeded.

FunctionReturnsStand-in without en-minigames
encore.minigame.isAvailable()boolean
encore.minigame.skillCheck(difficulty?, keys?, options?)boolean"Concentrating..." for 2.5 s
encore.minigame.lockpick(options?)success, broke"Picking the lock..." for 6 s. broke is always false
encore.minigame.hotwire(options?)boolean"Hotwiring..." for 8 s
encore.minigame.sequence(options?)boolean"Working..." for 4 s
encore.minigame.isActive()booleanfalse

Options

  • skillCheck: difficulty is one difficulty or a list, and each entry is one round. It defaults to 'medium'. keys is the list of keys the check may ask for, and defaults to E.
  • lockpick, hotwire and sequence take either a difficulty string or a table:
KeyApplies toMeaning
difficultyall'easy', 'medium' or 'hard'. Default 'medium'
bonusall0 to 1. Makes the game easier. Pass skill bonuses here
titleallHeading shown during the game
pinslockpickNumber of pins
lengthsequenceNumber of steps

Fallback behaviour

  • When en-minigames isn't running, a cancellable progress bar stands in, and en-minigames is not running; minigames fall back to progress bars is logged once. Finishing the bar counts as success.
  • The stand-in bar uses UI services, so if en-ui isn't running either, every minigame returns false.
  • If an en-minigames export errors, the call returns false and a warning is logged once.
  • To stop a running game from code, call exports['en-minigames']:Cancel().

Security

A minigame result is decided on the player's client. When success unlocks something on the server, check the rest there: that the player has the lockpick, is next to the door or vehicle, and isn't sending successes faster than the game can be played.

Examples

lua
-- lockpicking a door, eased by the player's skill
local bonus = encore.skills.getBonus('lockpick')
local picked, broke = encore.minigame.lockpick({ difficulty = 'hard', bonus = bonus })

if broke then TriggerServerEvent('my-doors:pickBroke') end
if picked then TriggerServerEvent('my-doors:unlock', doorId) end
lua
-- two rounds of skill check, then a short sequence
if not encore.minigame.skillCheck({ 'easy', 'medium' }) then return end

if encore.minigame.sequence({ difficulty = 'easy', length = 5, title = 'Rewire the panel' }) then
    TriggerServerEvent('my-generator:repaired', generatorId)
end
lua
-- hotwiring
if encore.minigame.hotwire({ difficulty = 'medium', bonus = encore.skills.getBonus('hotwire') }) then
    TriggerServerEvent('my-vehicles:hotwired', NetworkGetNetworkIdFromEntity(vehicle))
end

Contract

To replace en-minigames, a resource named en-minigames must provide these client exports:

ExportReturns
SkillCheck(difficulty, keys, options)boolean
Lockpick(options)success, broke (two values)
Hotwire(options)boolean
Sequence(options)boolean
IsActive()boolean

Results are compared with == true, so return exactly true for success. The same two approaches as the inventory interface apply: ship it under that name, or run an adapter named en-minigames.

Radial menu

Players hold F1 (rebindable) to open the radial menu, point at an entry and release to pick it. EndCore ships four submenus that hide themselves when irrelevant: 'survivor', 'vehicle', 'base' and 'party'. Your resource can add entries to the top ring, to those submenus, or add submenus of its own.

FunctionNotes
encore.radial.isAvailable()Whether en-radialmenu is started
encore.radial.addItem(item)Adds or replaces an entry. Raises an error unless item.id is a string
encore.radial.removeItem(id)Forgets the entry and removes it if en-radialmenu is running

Item fields

FieldDefaultMeaning
idrequiredUnique id. Prefix it with your resource
labelidText shown on the entry
iconnoneAn icon name
parenttop ringThe id of the submenu this entry belongs in
order100Lower numbers come first
submenufalsetrue makes this entry a submenu. A submenu with no visible children is hidden
canShowalwaysfunction() returning boolean, checked each time the menu opens. Keep it quick
onSelectnoneRuns in a new thread in your resource, so it may wait
keepOpenfalsetrue keeps the menu open after this entry is picked

Behaviour

  • Items are copied and remembered in your resource. They are applied now if en-radialmenu is running, and applied again each time it starts, so start order doesn't matter.
  • Unlike target options, removeItem forgets the entry, so it doesn't come back after a restart.
  • en-radialmenu drops a resource's entries when that resource stops.
  • Without en-radialmenu, nothing is shown and nothing errors.

Examples

lua
-- a submenu of your own, with an entry inside it
encore.radial.addItem({ id = 'my-radio:menu', label = 'Radio', icon = 'radio', submenu = true, order = 60 })

encore.radial.addItem({
    id = 'my-radio:toggle',
    parent = 'my-radio:menu',
    label = 'Power',
    icon = 'radio',
    onSelect = function() ExecuteCommand('radio') end,
})
lua
-- an entry in the built-in Survivor submenu, only while carrying a radio
encore.radial.addItem({
    id = 'my-radio:quick',
    parent = 'survivor',
    label = 'Radio',
    icon = 'radio',
    order = 50,
    canShow = function()
        return encore.inventory.getItemCount('radio') > 0
    end,
    onSelect = function()
        local values = encore.inputDialog('Tune radio', {
            { type = 'number', label = 'Channel', required = true, min = 1, max = 999 },
        })
        if values then TriggerServerEvent('my-radio:join', values[1]) end
    end,
})

Contract

A replacement named en-radialmenu must export AddItem(item) and RemoveItem(id) on the client. item.onSelect arrives already wrapped in a thread. Re-adding an item with the same id should replace it.