-- name: BetterCommands API -- description: API for simplified command development by enforcing a unified standard, enhancing argument processing, and enabling automated, full-featured error reporting, among other features\n\nAuthor: \\#b85469\\CrazyDany CMD_ARG_BOOLEAN = 0 CMD_ARG_INTEGER = 1 CMD_ARG_NUMBER = 2 CMD_ARG_STRING = 3 CMD_ARG_PLAYER = 4 --- @alias CommandArgTypes integer --- | `CMD_ARG_BOOLEAN` --- | `CMD_ARG_INTEGER` --- | `CMD_ARG_NUMBER` --- | `CMD_ARG_STRING` --- | `CMD_ARG_PLAYER` --- Returns a string representation of a command argument type. --- @param arg_type CommandArgTypes - one of the CMD_ARG_* constants --- @return string - the name of the type (e.g., "bool", "int", "player") local function cmd_arg_type_name(arg_type) local names = { [CMD_ARG_BOOLEAN] = 'bool', [CMD_ARG_INTEGER] = 'int', [CMD_ARG_NUMBER] = 'number', [CMD_ARG_STRING] = 'string', [CMD_ARG_PLAYER] = 'player' } return names[arg_type] or 'undefined' end --- Registers a chat command with a unified interface. --- @param command string --- @param description string --- @param args table --- @param func function(table) function hook_better_chat_command(command, description, args, func) local arg_parts = {} for arg_name, arg_type in pairs(args) do table.insert(arg_parts, string.format("%s: %s", arg_name, cmd_arg_type_name(arg_type))) end table.sort(arg_parts) local args_str = table.concat(arg_parts, ", ") local full_desc if #arg_parts > 0 then full_desc = string.format(" - <%s> - %s", args_str, description) else full_desc = string.format(" - %s", description) end local arg_list = {} for name, typ in pairs(args) do table.insert(arg_list, { name = name, type = typ }) end local function process_command(msg) chat_create_log('/' .. command .. ' ' .. msg) if #arg_list == 0 then if msg and msg:match("%S") then chat_create_error("Command takes no arguments.") return true end func({}) return true end local tokens = {} for token in string.gmatch(msg or "", "([^,]+)") do table.insert(tokens, token) end if #tokens < #arg_list then chat_create_error(string.format("Not enough arguments. Expected %d, got %d.", #arg_list, #tokens)) return true end if #tokens > #arg_list then chat_create_error(string.format("Too many arguments. Expected %d, got %d.", #arg_list, #tokens)) return true end local result = {} for i, arg_info in ipairs(arg_list) do local name = arg_info.name local typ = arg_info.type local token = tokens[i] token = token:match("^%s*(.-)%s*$") or "" local success, value = false, nil if typ == CMD_ARG_BOOLEAN then if token == "true" then success, value = true, true elseif token == "false" then success, value = true, false else success = false end elseif typ == CMD_ARG_INTEGER then local num = tonumber(token) if num and math.floor(num) == num then success, value = true, num else success = false end elseif typ == CMD_ARG_NUMBER then local num = tonumber(token) if num then success, value = true, num else success = false end elseif typ == CMD_ARG_STRING then success, value = true, token elseif typ == CMD_ARG_PLAYER then local index = tonumber(token) if index and math.floor(index) == index then if index >= 0 and index < MAX_PLAYERS and gMarioStates[index] and gNetworkPlayers[index].connected then success, value = true, gMarioStates[index] else success = false end else local found = false for j = 0, MAX_PLAYERS - 1 do if gNetworkPlayers[j] and gNetworkPlayers[j].name and gNetworkPlayers[j].connected then if string.lower(gNetworkPlayers[j].name) == string.lower(token) then if gMarioStates[j] then success, value = true, gMarioStates[j] found = true break end end end end if not found then success = false end end if not success then chat_create_error(string.format("Invalid argument '%s': player not found (by index or name).", name)) return true end else chat_create_error("Unknown argument type.") return true end if not success then local type_name = cmd_arg_type_name(typ) chat_create_error(string.format("Invalid argument '%s': expected %s, got '%s'", name, type_name, token)) return true end result[name] = value end func(result) return true end hook_chat_command(command, full_desc, process_command) end --- Prints an error message (red) with a sound. --- @param msg string function chat_create_error(msg) djui_chat_message_create("\\#ff4444\\" .. msg) play_sound(SOUND_MENU_CAMERA_BUZZ, gGlobalSoundSource) end --- Prints a success message (green) with a sound. --- @param msg string function chat_create_success(msg) djui_chat_message_create("\\#00ff00\\" .. msg) play_sound(SOUND_MENU_MARIO_CASTLE_WARP2, gGlobalSoundSource) end --- Prints a warning message (orange) without sound. --- @param msg string function chat_create_warning(msg) djui_chat_message_create("\\#ffaa00\\" .. msg) end --- Prints a log message (grey) without sound. --- @param msg string function chat_create_log(msg) djui_chat_message_create("\\#8aa3ab\\" .. msg) end hook_chat_command('bc-help', '- Show help for using BetterCommands', function(msg) local help = "\\#00ff00\\BetterCommands Help\\#ffffff\\\n" .. "Args separated by \\#ffff00\\commas (,)\\#ffffff\\\n" .. "Types:\n" .. " \\#ffff00\\bool\\#ffffff\\ - true/false\n" .. " \\#ffff00\\int\\#ffffff\\ - integer number\n" .. " \\#ffff00\\number\\#ffffff\\ - any number\n" .. " \\#ffff00\\string\\#ffffff\\ - text (no commas)\n" .. " \\#ffff00\\player\\#ffffff\\ - index or name\n" .. "Invalid args show error" djui_chat_message_create(help) return true end ) -- API EXPORT _G.CMD_ARG_BOOLEAN = CMD_ARG_BOOLEAN _G.CMD_ARG_INTEGER = CMD_ARG_INTEGER _G.CMD_ARG_NUMBER = CMD_ARG_NUMBER _G.CMD_ARG_STRING = CMD_ARG_STRING _G.CMD_ARG_PLAYER = CMD_ARG_PLAYER _G.hook_better_chat_command = hook_better_chat_command _G.chat_create_error = chat_create_error _G.chat_create_success = chat_create_success _G.chat_create_warning = chat_create_warning _G.chat_create_log = chat_create_log log_to_console('BetterCommands API loaded!')