164 lines
		
	
	
	
		
			5.3 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			164 lines
		
	
	
	
		
			5.3 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
--[[
 | 
						|
    https://github.com/overextended/ox_lib
 | 
						|
 | 
						|
    This file is licensed under LGPL-3.0 or higher <https://www.gnu.org/licenses/lgpl-3.0.en.html>
 | 
						|
 | 
						|
    Copyright © 2025 Linden <https://github.com/thelindat>
 | 
						|
]]
 | 
						|
 | 
						|
---@class OxCommandParams
 | 
						|
---@field name string
 | 
						|
---@field help? string
 | 
						|
---@field type? 'number' | 'playerId' | 'string' | 'longString'
 | 
						|
---@field optional? boolean
 | 
						|
 | 
						|
---@class OxCommandProperties
 | 
						|
---@field help string?
 | 
						|
---@field params OxCommandParams[]?
 | 
						|
---@field restricted boolean | string | string[]?
 | 
						|
 | 
						|
---@type OxCommandProperties[]
 | 
						|
local registeredCommands = {}
 | 
						|
local shouldSendCommands = false
 | 
						|
 | 
						|
SetTimeout(1000, function()
 | 
						|
    shouldSendCommands = true
 | 
						|
    TriggerClientEvent('chat:addSuggestions', -1, registeredCommands)
 | 
						|
end)
 | 
						|
 | 
						|
AddEventHandler('playerJoining', function()
 | 
						|
    TriggerClientEvent('chat:addSuggestions', source, registeredCommands)
 | 
						|
end)
 | 
						|
 | 
						|
---@param source number
 | 
						|
---@param args table
 | 
						|
---@param raw string
 | 
						|
---@param params OxCommandParams[]?
 | 
						|
---@return table?
 | 
						|
local function parseArguments(source, args, raw, params)
 | 
						|
    if not params then return args end
 | 
						|
 | 
						|
    local paramsNum = #params
 | 
						|
    for i = 1, paramsNum do
 | 
						|
        local arg, param = args[i], params[i]
 | 
						|
        local value
 | 
						|
 | 
						|
        if param.type == 'number' then
 | 
						|
            value = tonumber(arg)
 | 
						|
        elseif param.type == 'string' then
 | 
						|
            value = not tonumber(arg) and arg
 | 
						|
        elseif param.type == 'playerId' then
 | 
						|
            value = arg == 'me' and source or tonumber(arg)
 | 
						|
 | 
						|
            if not value or not DoesPlayerExist(value--[[@as string]]) then
 | 
						|
                value = false
 | 
						|
            end
 | 
						|
        elseif param.type == 'longString' and i == paramsNum then
 | 
						|
            if arg then
 | 
						|
                local start = raw:find(arg, 1, true)
 | 
						|
                value = start and raw:sub(start)
 | 
						|
            else
 | 
						|
                value = nil
 | 
						|
            end
 | 
						|
        else
 | 
						|
            value = arg
 | 
						|
        end
 | 
						|
 | 
						|
        if not value and (not param.optional or param.optional and arg) then
 | 
						|
            return Citizen.Trace(("^1command '%s' received an invalid %s for argument %s (%s), received '%s'^0\n"):format(string.strsplit(' ', raw) or raw, param.type, i, param.name, arg))
 | 
						|
        end
 | 
						|
 | 
						|
        arg = value
 | 
						|
 | 
						|
        args[param.name] = arg
 | 
						|
        args[i] = nil
 | 
						|
    end
 | 
						|
 | 
						|
    return args
 | 
						|
end
 | 
						|
 | 
						|
---@param commandName string | string[]
 | 
						|
---@param properties OxCommandProperties | false
 | 
						|
---@param cb fun(source: number, args: table, raw: string)
 | 
						|
---@param ... any
 | 
						|
function lib.addCommand(commandName, properties, cb, ...)
 | 
						|
    -- Try to handle backwards-compatibility with the old addCommand syntax (prior to v3.0)
 | 
						|
    local restricted, params
 | 
						|
 | 
						|
    if properties then
 | 
						|
        if ... or table.type(properties) ~= 'hash' then
 | 
						|
            local _commandName = type(properties) == 'table' and properties[1] or properties
 | 
						|
            local info = debug.getinfo(2, 'Sl')
 | 
						|
 | 
						|
            warn(("command '%s' is using deprecated syntax for lib.addCommand\nupdate the command or use lib.__addCommand to ignore this warning\n> source ^0(^5%s^0:%d)"):format(_commandName, info.short_src, info.currentline))
 | 
						|
            ---@diagnostic disable-next-line: deprecated
 | 
						|
            return lib.__addCommand(commandName, properties, cb, ...)
 | 
						|
        end
 | 
						|
 | 
						|
        restricted = properties.restricted
 | 
						|
        params = properties.params
 | 
						|
    end
 | 
						|
 | 
						|
    if params then
 | 
						|
        for i = 1, #params do
 | 
						|
            local param = params[i]
 | 
						|
 | 
						|
            if param.type then
 | 
						|
                param.help = param.help and ('%s (type: %s)'):format(param.help, param.type) or ('(type: %s)'):format(param.type)
 | 
						|
            end
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    local commands = type(commandName) ~= 'table' and { commandName } or commandName
 | 
						|
    local numCommands = #commands
 | 
						|
    local totalCommands = #registeredCommands
 | 
						|
 | 
						|
    local function commandHandler(source, args, raw)
 | 
						|
        args = parseArguments(source, args, raw, params)
 | 
						|
 | 
						|
        if not args then return end
 | 
						|
 | 
						|
        local success, resp = pcall(cb, source, args, raw)
 | 
						|
 | 
						|
        if not success then
 | 
						|
            Citizen.Trace(("^1command '%s' failed to execute!\n%s"):format(string.strsplit(' ', raw) or raw, resp))
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    for i = 1, numCommands do
 | 
						|
        totalCommands += 1
 | 
						|
        commandName = commands[i]
 | 
						|
 | 
						|
        RegisterCommand(commandName, commandHandler, restricted and true)
 | 
						|
 | 
						|
        if restricted then
 | 
						|
            local ace = ('command.%s'):format(commandName)
 | 
						|
            local restrictedType = type(restricted)
 | 
						|
 | 
						|
            if restrictedType == 'string' and not IsPrincipalAceAllowed(restricted, ace) then
 | 
						|
                lib.addAce(restricted, ace)
 | 
						|
            elseif restrictedType == 'table' then
 | 
						|
                for j = 1, #restricted do
 | 
						|
                    if not IsPrincipalAceAllowed(restricted[j], ace) then
 | 
						|
                        lib.addAce(restricted[j], ace)
 | 
						|
                    end
 | 
						|
                end
 | 
						|
            end
 | 
						|
        end
 | 
						|
 | 
						|
        if properties then
 | 
						|
            ---@diagnostic disable-next-line: inject-field
 | 
						|
            properties.name = ('/%s'):format(commandName)
 | 
						|
            properties.restricted = nil
 | 
						|
            registeredCommands[totalCommands] = properties
 | 
						|
 | 
						|
            if i ~= numCommands and numCommands ~= 1 then
 | 
						|
                properties = table.clone(properties)
 | 
						|
            end
 | 
						|
 | 
						|
            if shouldSendCommands then TriggerClientEvent('chat:addSuggestions', -1, properties) end
 | 
						|
        end
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
return lib.addCommand
 |