293 lines
		
	
	
		
			No EOL
		
	
	
		
			10 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			293 lines
		
	
	
		
			No EOL
		
	
	
		
			10 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
----------------------- [ MenuV ] -----------------------
 | 
						|
-- GitHub: https://github.com/ThymonA/menuv/
 | 
						|
-- License: GNU General Public License v3.0
 | 
						|
--          https://choosealicense.com/licenses/gpl-3.0/
 | 
						|
-- Author: Thymon Arens <contact@arens.io>
 | 
						|
-- Name: MenuV
 | 
						|
-- Version: 1.4.1
 | 
						|
-- Description: FiveM menu library for creating menu's
 | 
						|
----------------------- [ MenuV ] -----------------------
 | 
						|
local assert = assert
 | 
						|
local load = assert(load)
 | 
						|
local xpcall = assert(xpcall)
 | 
						|
local lower = assert(string.lower)
 | 
						|
local upper = assert(string.upper)
 | 
						|
local rawget = assert(rawget)
 | 
						|
local rawset = assert(rawset)
 | 
						|
local traceback = assert(debug.traceback)
 | 
						|
local setmetatable = assert(setmetatable)
 | 
						|
 | 
						|
--- FiveM globals
 | 
						|
local GetInvokingResource = assert(GetInvokingResource)
 | 
						|
local LoadResourceFile = assert(LoadResourceFile)
 | 
						|
local RegisterKeyMapping = assert(RegisterKeyMapping)
 | 
						|
local RegisterCommand = assert(RegisterCommand)
 | 
						|
local SendNUIMessage = assert(SendNUIMessage)
 | 
						|
local RegisterNUICallback = assert(RegisterNUICallback)
 | 
						|
local IsScreenFadedOut = assert(IsScreenFadedOut)
 | 
						|
local IsPauseMenuActive = assert(IsPauseMenuActive)
 | 
						|
local PlaySoundFrontend = assert(PlaySoundFrontend)
 | 
						|
local CreateThread = assert(Citizen.CreateThread)
 | 
						|
local Wait = assert(Citizen.Wait)
 | 
						|
local exports = assert(exports)
 | 
						|
 | 
						|
--- MenuV globals
 | 
						|
---@type Utilities
 | 
						|
local Utilities = assert(Utilities)
 | 
						|
 | 
						|
--- Load a file from `menuv`
 | 
						|
---@param path string Path in `menuv`
 | 
						|
---@return any|nil Results of nil
 | 
						|
local function load_file(path)
 | 
						|
    if (path == nil or type(path) ~= 'string') then return nil end
 | 
						|
 | 
						|
    local raw_file = LoadResourceFile('menuv', path)
 | 
						|
 | 
						|
    if (raw_file) then
 | 
						|
        local raw_func, _ = load(raw_file, ('menuv/%s'):format(path), 't', _ENV)
 | 
						|
 | 
						|
        if (raw_func) then
 | 
						|
            local ok, result = xpcall(raw_func, traceback)
 | 
						|
 | 
						|
            if (ok) then
 | 
						|
                return result
 | 
						|
            end
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    return nil
 | 
						|
end
 | 
						|
 | 
						|
load_file('menuv/components/translations.lua')
 | 
						|
 | 
						|
local MenuV = setmetatable({
 | 
						|
    ---@type string
 | 
						|
    __class = 'MenuV',
 | 
						|
    ---@type string
 | 
						|
    __type = 'MenuV',
 | 
						|
    ---@type boolean
 | 
						|
    Loaded = false,
 | 
						|
    ---@type number
 | 
						|
    ThreadWait = Utilities:Ensure((Config or {}).HideInterval, 250),
 | 
						|
    ---@type table<string, string>
 | 
						|
    Translations = translations or {},
 | 
						|
    ---@type table<string, table>
 | 
						|
    Sounds = Utilities:Ensure((Config or {}).Sounds, {}),
 | 
						|
    ---@type boolean
 | 
						|
    Hidden = false
 | 
						|
}, {})
 | 
						|
 | 
						|
MenuV.Keys = setmetatable({ data = {}, __class = 'MenuVKeys', __type = 'keys' }, {
 | 
						|
    __index = function(t, k)
 | 
						|
        return rawget(t.data, k)
 | 
						|
    end,
 | 
						|
    __newindex = function(t, k, v)
 | 
						|
        k = Utilities:Ensure(k, 'unknown')
 | 
						|
 | 
						|
        if (k == 'unknown') then return end
 | 
						|
 | 
						|
        local rawKey = rawget(t.data, k)
 | 
						|
        local keyExists = rawKey ~= nil
 | 
						|
        local prevState = Utilities:Ensure((rawKey or {}).status, false)
 | 
						|
        local newState = Utilities:Ensure(v, false)
 | 
						|
 | 
						|
        if (keyExists and not MenuV.Hidden) then
 | 
						|
            rawset(t.data[k], 'status', newState)
 | 
						|
 | 
						|
            if (prevState ~= newState) then
 | 
						|
                local action = newState and not prevState and 'KEY_PRESSED' or 'KEY_RELEASED'
 | 
						|
                local key = Utilities:Ensure(rawKey.action, 'UNKNOWN')
 | 
						|
 | 
						|
                SendNUIMessage({ action = action, key = key })
 | 
						|
            end
 | 
						|
        end
 | 
						|
    end,
 | 
						|
    __call = function(t, k, a, inputType)
 | 
						|
        k = Utilities:Ensure(k, 'unknown')
 | 
						|
        a = Utilities:Ensure(a, 'UNKNOWN')
 | 
						|
        inputType = Utilities:Ensure(inputType, 0)
 | 
						|
 | 
						|
        if (k == 'unknown') then return end
 | 
						|
 | 
						|
        local rawKey = rawget(t.data, k)
 | 
						|
        local keyExists = rawKey ~= nil
 | 
						|
 | 
						|
        if (keyExists) then
 | 
						|
            if not rawKey.inputTypes[inputType] then
 | 
						|
                rawKey.inputTypes[inputType] = true
 | 
						|
            end
 | 
						|
 | 
						|
            return
 | 
						|
        end
 | 
						|
 | 
						|
        rawset(t.data, k, { status = false, action = a, inputTypes = { [inputType] = true } })
 | 
						|
    end
 | 
						|
})
 | 
						|
 | 
						|
--- Register a `action` with custom keybind
 | 
						|
---@param action string Action like: UP, DOWN, LEFT...
 | 
						|
---@param description string Description of keybind
 | 
						|
---@param defaultType string Type like: keyboard, mouse etc.
 | 
						|
---@param defaultKey string Default key for this keybind
 | 
						|
function MenuV:RegisterKey(action, description, defaultType, defaultKey)
 | 
						|
    action = Utilities:Ensure(action, 'UNKNOWN')
 | 
						|
    description = Utilities:Ensure(description, 'unknown')
 | 
						|
    defaultType = Utilities:Ensure(defaultType, 'KEYBOARD')
 | 
						|
    defaultType = upper(defaultType)
 | 
						|
    defaultKey = Utilities:Ensure(defaultKey, 'F12')
 | 
						|
 | 
						|
    action = Utilities:Replace(action, ' ', '_')
 | 
						|
    action = upper(action)
 | 
						|
 | 
						|
    local typeGroup = Utilities:GetInputTypeGroup(defaultType)
 | 
						|
 | 
						|
    if (self.Keys[action] and self.Keys[action].inputTypes[typeGroup]) then return end
 | 
						|
 | 
						|
    self.Keys(action, action, typeGroup)
 | 
						|
 | 
						|
    local k = lower(action)
 | 
						|
 | 
						|
    if typeGroup > 0 then
 | 
						|
        local inputGroupName = Utilities:GetInputGroupName(typeGroup)
 | 
						|
        k = ('%s_%s'):format(lower(inputGroupName), k)
 | 
						|
    end
 | 
						|
 | 
						|
    k = ('menuv_%s'):format(k)
 | 
						|
 | 
						|
    RegisterKeyMapping(('+%s'):format(k), description, defaultType, defaultKey)
 | 
						|
    RegisterCommand(('+%s'):format(k), function() MenuV.Keys[action] = true end)
 | 
						|
    RegisterCommand(('-%s'):format(k), function() MenuV.Keys[action] = false end)
 | 
						|
end
 | 
						|
 | 
						|
--- Load translation
 | 
						|
---@param k string Translation key
 | 
						|
---@return string Translation or 'MISSING TRANSLATION'
 | 
						|
local function T(k)
 | 
						|
    k = Utilities:Ensure(k, 'unknown')
 | 
						|
 | 
						|
    return Utilities:Ensure(MenuV.Translations[k], 'MISSING TRANSLATION')
 | 
						|
end
 | 
						|
 | 
						|
RegisterNUICallback('loaded', function(_, cb)
 | 
						|
    MenuV.Loaded = true
 | 
						|
    cb('ok')
 | 
						|
end)
 | 
						|
 | 
						|
RegisterNUICallback('sound', function(info, cb)
 | 
						|
    local key = upper(Utilities:Ensure(info.key, 'UNKNOWN'))
 | 
						|
 | 
						|
    if (MenuV.Sounds == nil and MenuV.Sounds[key] == nil) then cb('ok') return end
 | 
						|
 | 
						|
    local sound = Utilities:Ensure(MenuV.Sounds[key], {})
 | 
						|
    local soundType = lower(Utilities:Ensure(sound.type, 'unknown'))
 | 
						|
 | 
						|
    if (soundType == 'native') then
 | 
						|
        local name = Utilities:Ensure(sound.name, 'UNKNOWN')
 | 
						|
        local library = Utilities:Ensure(sound.library, 'UNKNOWN')
 | 
						|
 | 
						|
        PlaySoundFrontend(-1, name, library, true)
 | 
						|
    end
 | 
						|
 | 
						|
    cb('ok')
 | 
						|
end)
 | 
						|
 | 
						|
--- Trigger the NUICallback for the right resource
 | 
						|
---@param name string Name of callback
 | 
						|
---@param info table Info returns from callback
 | 
						|
---@param cb function Trigger this when callback is done
 | 
						|
local function TriggerResourceCallback(name, info, cb)
 | 
						|
    local r = Utilities:Ensure(info.r, 'menuv')
 | 
						|
 | 
						|
    if (r == 'menuv') then cb('ok') return end
 | 
						|
 | 
						|
    local resource = exports[r] or nil
 | 
						|
 | 
						|
    if (resource == nil) then cb('ok') return end
 | 
						|
 | 
						|
    local nuiCallback = resource['NUICallback'] or nil
 | 
						|
 | 
						|
    if (nuiCallback == nil) then cb('ok') return end
 | 
						|
 | 
						|
    exports[r]:NUICallback(name, info, cb)
 | 
						|
end
 | 
						|
 | 
						|
RegisterNUICallback('submit', function(info, cb) TriggerResourceCallback('submit', info, cb) end)
 | 
						|
RegisterNUICallback('close', function(info, cb) TriggerResourceCallback('close', info, cb) end)
 | 
						|
RegisterNUICallback('switch', function(info, cb) TriggerResourceCallback('switch', info, cb) end)
 | 
						|
RegisterNUICallback('update', function(info, cb) TriggerResourceCallback('update', info, cb) end)
 | 
						|
RegisterNUICallback('open', function(info, cb) TriggerResourceCallback('open', info, cb) end)
 | 
						|
RegisterNUICallback('opened', function(info, cb) TriggerResourceCallback('opened', info, cb) end)
 | 
						|
RegisterNUICallback('close_all', function(info, cb) TriggerResourceCallback('close_all', info, cb) end)
 | 
						|
 | 
						|
--- MenuV exports
 | 
						|
exports('IsLoaded', function(cb)
 | 
						|
    cb = Utilities:Ensure(cb, function() end)
 | 
						|
 | 
						|
    if (MenuV.Loaded) then
 | 
						|
        cb()
 | 
						|
        return
 | 
						|
    end
 | 
						|
 | 
						|
    CreateThread(function()
 | 
						|
        local callback = cb
 | 
						|
 | 
						|
        repeat Wait(0) until MenuV.Loaded
 | 
						|
 | 
						|
        callback()
 | 
						|
    end)
 | 
						|
end)
 | 
						|
 | 
						|
exports('SendNUIMessage', function(input)
 | 
						|
    local r = Utilities:Ensure(GetInvokingResource(), 'menuv')
 | 
						|
 | 
						|
    if (Utilities:Typeof(input) == 'table') then
 | 
						|
        if (input.menu) then
 | 
						|
            rawset(input.menu, 'resource', r)
 | 
						|
            rawset(input.menu, 'defaultSounds', MenuV.Sounds)
 | 
						|
            rawset(input.menu, 'hidden', MenuV.Hidden)
 | 
						|
        end
 | 
						|
 | 
						|
        SendNUIMessage(input)
 | 
						|
    end
 | 
						|
end)
 | 
						|
 | 
						|
--- Register `MenuV` keybinds
 | 
						|
MenuV:RegisterKey('UP', T('keybind_key_up'), 'KEYBOARD', 'UP')
 | 
						|
MenuV:RegisterKey('DOWN', T('keybind_key_down'), 'KEYBOARD', 'DOWN')
 | 
						|
MenuV:RegisterKey('LEFT', T('keybind_key_left'), 'KEYBOARD', 'LEFT')
 | 
						|
MenuV:RegisterKey('RIGHT', T('keybind_key_right'), 'KEYBOARD', 'RIGHT')
 | 
						|
MenuV:RegisterKey('ENTER', T('keybind_key_enter'), 'KEYBOARD', 'RETURN')
 | 
						|
MenuV:RegisterKey('CLOSE', T('keybind_key_close'), 'KEYBOARD', 'BACK')
 | 
						|
MenuV:RegisterKey('CLOSE_ALL', T('keybind_key_close_all'), 'KEYBOARD', 'PLUS')
 | 
						|
 | 
						|
MenuV:RegisterKey('UP', ('%s - %s'):format(T('controller'), T('keybind_key_up')), 'PAD_ANALOGBUTTON', 'LUP_INDEX')
 | 
						|
MenuV:RegisterKey('DOWN', ('%s - %s'):format(T('controller'), T('keybind_key_down')), 'PAD_ANALOGBUTTON', 'LDOWN_INDEX')
 | 
						|
MenuV:RegisterKey('LEFT', ('%s - %s'):format(T('controller'), T('keybind_key_left')), 'PAD_ANALOGBUTTON', 'LLEFT_INDEX')
 | 
						|
MenuV:RegisterKey('RIGHT', ('%s - %s'):format(T('controller'), T('keybind_key_right')), 'PAD_ANALOGBUTTON', 'LRIGHT_INDEX')
 | 
						|
MenuV:RegisterKey('ENTER', ('%s - %s'):format(T('controller'), T('keybind_key_enter')), 'PAD_ANALOGBUTTON', 'RDOWN_INDEX')
 | 
						|
MenuV:RegisterKey('CLOSE', ('%s - %s'):format(T('controller'), T('keybind_key_close')), 'PAD_ANALOGBUTTON', 'RRIGHT_INDEX')
 | 
						|
MenuV:RegisterKey('CLOSE_ALL', ('%s - %s'):format(T('controller'), T('keybind_key_close_all')), 'PAD_ANALOGBUTTON', 'R3_INDEX')
 | 
						|
 | 
						|
--- Hide menu when screen is faded out or pause menu ia active
 | 
						|
CreateThread(function()
 | 
						|
    MenuV.Hidden = false
 | 
						|
 | 
						|
    while true do
 | 
						|
        repeat Wait(0) until MenuV.Loaded
 | 
						|
 | 
						|
        local new_state = IsScreenFadedOut() or IsPauseMenuActive()
 | 
						|
 | 
						|
        if (MenuV.Hidden ~= new_state) then
 | 
						|
            SendNUIMessage({ action = 'UPDATE_STATUS', status = not new_state })
 | 
						|
        end
 | 
						|
 | 
						|
        MenuV.Hidden = new_state
 | 
						|
 | 
						|
        Wait(MenuV.ThreadWait)
 | 
						|
    end
 | 
						|
end)
 | 
						|
 | 
						|
--- When resource is stopped
 | 
						|
AddEventHandler('onResourceStop', function(resourceName)
 | 
						|
    SendNUIMessage({ action = 'RESOURCE_STOPPED', resource = resourceName })
 | 
						|
end) |