// vim: set ts=4 sw=4 tw=99 noet: // // AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO"). // Copyright (C) The AMX Mod X Development Team. // // This software is licensed under the GNU General Public License, version 3 or higher. // Additional exceptions apply. For full license details, see LICENSE.txt or visit: // https://alliedmods.net/amxmodx-license // // Plugin Cvar and Command Menu // #include #include new g_disabled_callback; new g_enabled_callback; // pcvar that the client is currently modifying new g_current_cvar[MAX_PLAYERS + 1]; // Name of the cvar being modified new g_current_cvar_name[MAX_PLAYERS + 1][32]; // Plugin ID that the client is modifying new g_current_player_id[MAX_PLAYERS + 1]; // Page that the client is currently on new g_current_page[MAX_PLAYERS + 1]; // Menu function ID that the client is in new g_current_menu_function[MAX_PLAYERS + 1] = { -1, ... }; new g_current_command[MAX_PLAYERS + 1][32]; new g_cvarmenu_cmdid; new g_cmdmenu_cmdid; new g_explicit_plugin[MAX_PLAYERS + 1]; public plugin_init() { register_plugin("Plugin Menu", AMXX_VERSION_STR, "AMXX Dev Team"); register_dictionary("admincmd.txt"); //Needed for CVAR_CHANGED register_dictionary("common.txt"); register_dictionary("pausecfg.txt"); // Needed for PAUSE_COULDNT_FIND register_dictionary("pluginmenu.txt"); g_cvarmenu_cmdid = register_clcmd("amx_plugincvarmenu", "CvarMenuCommand", ADMIN_CVAR, "PLUGINCVARMENU_DESC", .info_ml = true); g_cmdmenu_cmdid = register_clcmd("amx_plugincmdmenu", "CommandMenuCommand", ADMIN_MENU, "PLUGINCMDMENU_DESC", .info_ml = true); register_clcmd("amx_changecvar", "CommandChangeCvar"); register_clcmd("amx_executecmd", "CommandExecuteCommand"); // Register global menu callbacks. g_disabled_callback = menu_makecallback("AlwaysDisableCallback"); g_enabled_callback = menu_makecallback("AlwaysEnableCallback"); } // Add these menus to the amxmodmenu public plugin_cfg() { set_task(0.1, "addToMenuFront"); } public addToMenuFront() { new plugin_filename[64], cmd[32], garbage[1], cvarflags, cmdflags; get_plugin(-1, plugin_filename, charsmax(plugin_filename)); get_concmd(g_cmdmenu_cmdid, cmd, charsmax(cmd), cmdflags, garbage, charsmax(garbage), -1); if (strcmp(cmd, "amx_plugincmdmenu") != 0) { // this should never happen, but just incase! cmdflags = ADMIN_MENU; } get_concmd(g_cvarmenu_cmdid, cmd, charsmax(cmd), cvarflags, garbage, charsmax(garbage), -1); if (strcmp(cmd, "amx_plugincvarmenu") != 0) { // this should never happen, but just incase! cvarflags = ADMIN_CVAR; } AddMenuItem("Plugin Cvars", "amx_plugincvarmenu", cvarflags, plugin_filename); AddMenuItem("Plugin Commands", "amx_plugincmdmenu", cmdflags, plugin_filename); } // Reset all fields for each client as they connect. public client_connect(id) { g_current_cvar[id] = 0; g_current_player_id[id] = 0; g_current_menu_function[id] = -1; g_current_cvar_name[id][0] = 0; g_current_command[id][0] = 0; g_explicit_plugin[id] = -1; } /** * Creates a plugin list menu. * * @param menu_lang The text to display as the title. * @param Handler The function to call when an item is selected. * @param Command The function to pass to the handler. It will be passed as "PLID Command". * @param Callback Function to call for each plugin to be listed. Displays a number next to it (how many cvars, etc.) */ stock DisplayPluginMenu(id, const menu_lang[], const Handler[], const Command[], const Callback[]) { new plugin_name[64], plugincmd[64], menu_text[64], plugin_state[32], tally; new menu = menu_create(fmt("%L", id, menu_lang), Handler); new func = get_func_id(Callback); for (new i = 0, max = get_pluginsnum(); i < max; i++) { if (callfunc_begin_i(func, -1) == 1) { callfunc_push_int(i); // push the plid if ((tally = callfunc_end()) > 0) { get_plugin(i, _, _, plugin_name, charsmax(plugin_name), _, _, _, _, plugin_state, charsmax(plugin_state)); // Command syntax is: "# Function", # being plugin ID, function being public function to call. formatex(plugincmd, charsmax(plugincmd), "%d %s", i, Command); formatex(menu_text, charsmax(menu_text), "%s - %d", plugin_name, tally); // If the plugin is running, add this as an activated menu item. if (strcmp(plugin_state, "running", true) == 0 || strcmp(plugin_state, "debug", true) == 0) { menu_additem(menu, menu_text, plugincmd, g_enabled_callback); } else { menu_additem(menu, menu_text, "", _, g_disabled_callback); } } } } menu_setprop(menu, MPROP_NUMBER_COLOR, "\y"); menu_setprop(menu, MPROP_EXIT, MEXIT_ALL); menu_display(id, menu, 0); } /** * Byrefs the plugin id of a target plugin (passed by argv(1)), but only if it's valid. * * @param id id of the display messages to upon failure. * @param plid Variable to byref the plugin id. * @return True on successful lookup, false on failure. */ stock bool:GetPlidForValidPlugins(id, &plid) { // If arguments have been passed, then we were given // a specific plugin to examine. if (read_argc() > 1) { // Yes, we were provided a plugin. new buffer_name[64], buffer_file[64] ,buffer_state[64], target_plugin[64]; read_argv(1, target_plugin, charsmax(target_plugin)); // Scan for the plugin ID. for (new i = 0, max = get_pluginsnum(); i < max; i++) { get_plugin(i, buffer_file, charsmax(buffer_file), buffer_name, charsmax(buffer_name), _, _, _, _, buffer_state, charsmax(buffer_state)); if (strcmp(buffer_file, target_plugin, true) != 0 || strcmp(buffer_name, target_plugin, true) != 0) { // We have a match. // Check the status of the plugin. If it's anything other than "running" or "debug" fail. if (strcmp(buffer_state, "running") != 0 && strcmp(buffer_state, "debug") != 0) { // TODO: ML This console_print(id, "[AMXX] %l", "PLUGIN_NOT_RUNNING", buffer_file); // Return a failed state. return false; } plid = i; break; } } // If the plugin was not found, then tell them there was an error. if (plid == -1) { console_print(id, "%l", "PAUSE_COULDNT_FIND", target_plugin); // return a failure state return false; } } return true; } /** * Returns the number of cvars available for a plugin by plid. (Callback for the plugin menu.) * * @return number of cvars in the plugin. */ public GetNumberOfCvarsForPlid(plid) { new cvar_plid, count; for (new i = 0, max = get_plugins_cvarsnum(); i < max; i++) { get_plugins_cvar(i, "", 0, _, cvar_plid, _); if (cvar_plid == plid) { count++; } } return count; } /** * Returns the number of commands available for a plugin by plid. (Callback for the plugin menu.) * * @return Number of valid commands in the plugin. */ public GetNumberOfCmdsForPlid(plid) { new count; for (new i = 0, max = get_concmdsnum(-1,-1); i < max; i++) { if (get_concmd_plid(i, -1, -1) == plid) { count++; } } return count; } /** * Whether or not the client has access to modify this cvar. * * @param id The admin id. * @param cvar The name of the cvar to be checked. * @return True if the client has access, false otherwise. */ stock bool:CanIModifyCvar(id, const cvar[]) { new user_flags = get_user_flags(id); // If the user has rcon access don't bother checking anything. if (user_flags & ADMIN_RCON) { return true; } // If the cvar is "sv_password" (somehow), then check access. if (equali(cvar, "sv_password") && user_flags & ADMIN_PASSWORD) { return true; } // Check to see if the cvar is flagged as protected. if (get_cvar_flags(cvar) & FCVAR_PROTECTED) { // non-rcon user trying to modify a protected cvar. return false; } // All known checks done, they can change this cvar if they // were able to open the menu. return true; } /** * Simple function to ensure that a menu item is always disabled. * * All parameters are dummy, nothing is used. */ public AlwaysDisableCallback(playerid, menuid, itemid) { return ITEM_DISABLED; } /** * Simple function to ensure that a menu item is always enabled. * * All parameters are dummy, nothing is used. */ public AlwaysEnableCallback(playerid, menuid, itemid) { return ITEM_ENABLED; } /** * Handler for the plugin menu. * * @param id The client selecting an item. * @param menu The menu handle. * @param item The item number that was selected. */ public PluginMenuSelection(id, menu, item) { if (item == MENU_EXIT) { menu_destroy(menu); } if (item < 0) { return PLUGIN_HANDLED; } // All of the commands set for each item is the public // function that we want to call after the item is selected. // The parameters are: function(idPlayer,itemnumber) // Note the menu is destroyed BEFORE the command // gets executed. // The command retrieved is in the format: "PLID Command" new command[64]; menu_item_getinfo(menu, item, _, command, charsmax(command)); new function[32], plid = str_to_num(command); for (new i = 0; i < charsmax(command); i++) { if (command[i] == ' ') { // we're at the break. move up one space. i++; copy(function, charsmax(function), command[i]); break; } } menu_destroy(menu); new funcid = get_func_id(function); if (funcid != -1 && callfunc_begin_i(funcid) == 1) { g_current_page[id] = 0; g_current_player_id[id] = plid; g_current_menu_function[id] = funcid; callfunc_push_int(id); callfunc_push_int(plid); callfunc_push_int(0); callfunc_end(); } return PLUGIN_HANDLED; } /** * The command to change a cvar has been called. * * @param id The client who is changing the cvar. */ public CommandChangeCvar(id) { // All access checks are done before this command is called. // So if the client has no pcvar pointer in his array slot // then just ignore the command. if (!g_current_cvar[id]) { return PLUGIN_CONTINUE; } new args[256]; read_args(args, charsmax(args)); remove_quotes(args); if (equali(args, "!cancel", 7)) { // The client didn't want to change this cvar. client_print(id, print_chat, "[AMXX] %l", "CVAR_NOT_CHANGED"); } else { // Changed to set_cvar_* for 1.76 tests new pointer = g_current_cvar[id]; set_pcvar_string(g_current_cvar[id], args); client_print(id, print_chat, "%l", "CVAR_CHANGED", g_current_cvar_name[id], args); log_amx("Cmd: ^"%N^" set cvar (name ^"%s^") (value ^"%s^")", id, g_current_cvar_name[id], args); new cvar_val[64], players[MAX_PLAYERS], pnum; get_players_ex(players, pnum, GetPlayers_ExcludeBots); for (new player, i; i < pnum; i++) { player = players[i]; if (get_pcvar_flags(pointer) & FCVAR_PROTECTED || equali(args, "rcon_password")) { formatex(cvar_val, charsmax(cvar_val), "*** %L ***", player, "PROTECTED"); } else { copy(cvar_val, charsmax(cvar_val), args); } show_activity_id(player, id, fmt("%n", id), "%L", player, "SET_CVAR_TO", "", g_current_cvar_name[id], cvar_val); } console_print(id, "[AMXX] %l", "CVAR_CHANGED", g_current_cvar_name[id], args); } // Now redraw the menu for the client if (g_current_menu_function[id] != -1 && callfunc_begin_i(g_current_menu_function[id]) == 1) { callfunc_push_int(id); callfunc_push_int(g_current_player_id[id]); callfunc_push_int(g_current_page[id]); callfunc_end(); } return PLUGIN_HANDLED; } /** * Process a selection from the cvar menu. * * @param id The client who chose an item. * @param menu The menu handle. * @param item The item that has been selected. */ public CvarMenuSelection(id, menu, item) { if (item == MENU_EXIT) { menu_destroy(menu); if (g_explicit_plugin[id] == -1) { DisplayPluginMenuDefault(id); } } else if (item == MENU_BACK) { --g_current_page[id]; client_print(id, print_chat, "MENU_BACK"); } else if (item == MENU_MORE) { ++g_current_page[id]; client_print(id, print_chat, "MENU_MORE"); } else { new cvar_name[64], command[32]; // pcvar pointer is stored in command, extract the name of the cvar from the name field. menu_item_getinfo(menu, item, _, command, charsmax(command), cvar_name, charsmax(cvar_name)); g_current_cvar[id] = str_to_num(command); if (g_current_cvar[id] == 0) // This should never happen, but just incase.. { client_print(id, print_chat, "%l", "CVAR_PTR_ERROR", cvar_name); return PLUGIN_HANDLED; } // TODO: ML this // Scan up "cvar_name" and stop at the first space for (new i = 0; i < charsmax(cvar_name); i++) { if (cvar_name[i] == ' ') { cvar_name[i]= '^0'; break; } } copy(g_current_cvar_name[id], charsmax(g_current_cvar_name[]), cvar_name); client_print(id, print_chat, "%l", "CVAR_TYPE_NEW_VALUE", cvar_name); client_cmd(id, "messagemode amx_changecvar"); menu_destroy(menu); } return PLUGIN_HANDLED; } /** * Displays the cvar menu to a client. * * @param id id of the client. * @param plid Plugin ID to display cvars from. * @param page Page of the menu to start at. */ public DisplayCvarMenu(id, plid, page) { new menu_title[64], plugin_name[32]; get_plugin(plid, _, _, plugin_name, charsmax(plugin_name)); formatex(menu_title, charsmax(menu_title), "%s %L:", plugin_name, id, "CVARS"); new menu = menu_create(menu_title, "CvarMenuSelection"); new cvar[64], cvar_text[64], cvar_data[32], cvar_plid, cvar_pointer; for (new i = 0, max = get_plugins_cvarsnum(); i < max; i++) { get_plugins_cvar(i, cvar, charsmax(cvar), _, cvar_plid, cvar_pointer); if (cvar_plid == plid) { if (CanIModifyCvar(id, cvar)) { get_pcvar_string(cvar_pointer, cvar_data, charsmax(cvar_data)); formatex(cvar_text, charsmax(cvar_text), "%s - %s", cvar, cvar_data); // Now store the pcvar data in cvar num_to_str(cvar_pointer, cvar, charsmax(cvar)); menu_additem(menu, cvar_text, cvar, _ , g_enabled_callback); } else { menu_additem(menu, cvar, "", _, g_disabled_callback); } } } menu_setprop(menu, MPROP_EXIT, MEXIT_ALL); menu_setprop(menu, MPROP_NUMBER_COLOR, "\y"); menu_display(id, menu, page); } /** * Process the "amx_plugincvarmenu" command. * * @param id id of the client that is calling the command. * @param level Access level required by the command. * @param cid Command ID. */ public CvarMenuCommand(id, level, cid) { if (!cmd_access(id, level, cid, 0)) { return PLUGIN_HANDLED; } // This is which plugin to display. -1 means display all plugins in a list. new plid = -1; if (GetPlidForValidPlugins(id, plid) != true) { // If GetPlidForValidPlugins returns false then it failed to find the plugin. return PLUGIN_HANDLED; } // Check if we were passed a specific plugin to display or not. if (plid == -1) { g_explicit_plugin[id] = -1; // We need to display a list of the plugins, instead of a specific plugin. DisplayPluginMenu(id, "PLUGIN_CVAR_MENU", "PluginMenuSelection", "DisplayCvarMenu", "GetNumberOfCvarsForPlid"); } else { g_explicit_plugin[id] = plid; g_current_player_id[id] = plid; g_current_page[id] = 0; DisplayCvarMenu(id, plid, 0); } return PLUGIN_HANDLED; } /** * Handler for the menu that displays a single command ("Execute with no params", etc). * * @param id Id of the client. * @param menu Menu handle. * @param item Item that was selected. */ public SpecificCommandHandler(id, menu, item) { // Exit was called, return to the previous menu. if (item < 0) { if (g_current_menu_function[id] != -1 && callfunc_begin_i(g_current_menu_function[id]) == 1) { callfunc_push_int(id); callfunc_push_int(g_current_player_id[id]); callfunc_push_int(g_current_page[id]); callfunc_end(); } menu_destroy(menu); return PLUGIN_HANDLED; } if (item == 0) // "With params" { menu_item_getinfo(menu, item, _, g_current_command[id], charsmax(g_current_command[])); if (g_current_command[id][0] == 0) // This should never happen, but just incase.. { client_print(id, print_chat, "%l", "CMD_NAME_ERROR"); return PLUGIN_HANDLED; } // TODO: ML this client_print(id, print_chat, "%l", "CMD_TYPE_PARAMS", g_current_command[id]); client_cmd(id, "messagemode amx_executecmd"); menu_destroy(menu); return PLUGIN_HANDLED; // Don't return to original menu immediately! } else if (item == 1) // "No params" { menu_item_getinfo(menu, item, _, g_current_command[id], charsmax(g_current_command[])); if (g_current_command[id][0] == 0) // This should never happen, but just incase.. { client_print(id, print_chat, "%l", "CMD_NAME_ERROR"); return PLUGIN_HANDLED; } // TODO: ML this // Now redraw the menu for the client BEFORE the command is executed, incase // that menu brings up a menu of its own. if (g_current_menu_function[id] != -1 && callfunc_begin_i(g_current_menu_function[id]) == 1) { callfunc_push_int(id); callfunc_push_int(g_current_player_id[id]); callfunc_push_int(g_current_page[id]); callfunc_end(); } menu_destroy(menu); client_cmd(id, "%s", g_current_command[id]); client_print(id, print_chat, "%l", "CMD_EXEC_NO_PARAMS", g_current_command[id]); return PLUGIN_HANDLED; } // We should never get here, but just incase.. menu_destroy(menu); return PLUGIN_HANDLED; } /** * Generates and displays a menu to the client for a specific command. * * @param id The client to display the menu to. * @param cid The command id to display. */ stock DisplaySpecificCommand(id, cid) { new command_title[256], command_desc[128], command_name[64], command_access, menu; get_concmd(cid, command_name, charsmax(command_name), command_access, command_desc, charsmax(command_desc), -1, -1); if (command_desc[0] != '^0') { formatex(command_title, charsmax(command_title), "%s^n%s", command_name, command_desc); menu = menu_create(command_title, "SpecificCommandHandler"); } else { menu = menu_create(command_name, "SpecificCommandHandler"); } menu_additem(menu, fmt("%L", id, "EXEC_WITH_PARAMS"), command_name, _, g_enabled_callback); menu_additem(menu, fmt("%L", id, "EXEC_WITHOUT_PARAMS"), command_name, _, g_enabled_callback); menu_setprop(menu, MPROP_NUMBER_COLOR, "\y"); menu_display(id, menu, 0); } /** * Handles the executed command (via "amx_executecmd"). * * @param id The id of the client who executed this. */ public CommandExecuteCommand(id) { // If they had no command stored, then just ignore it entirely. if (g_current_command[id][0] == '^0') { return PLUGIN_CONTINUE; } new args[256]; read_args(args, charsmax(args)); remove_quotes(args); if (equali(args, "!cancel", 7)) { // The client didn't want to execute this command. client_print(id, print_chat, "%l", "CMD_NOT_EXECUTED"); // Now redraw the menu for the client if (g_current_menu_function[id] != -1 && callfunc_begin_i(g_current_menu_function[id]) == 1) { callfunc_push_int(id); callfunc_push_int(g_current_player_id[id]); callfunc_push_int(g_current_page[id]); callfunc_end(); } } else { // TODO: ML client_print(id, print_chat, "%l", "CMD_EXECUTED", g_current_command[id], args); // Now redraw the menu for the client if (g_current_menu_function[id] != -1 && callfunc_begin_i(g_current_menu_function[id]) == 1) { callfunc_push_int(id); callfunc_push_int(g_current_player_id[id]); callfunc_push_int(g_current_page[id]); callfunc_end(); } // Execute the command on the client. client_cmd(id, "%s %s", g_current_command[id], args); } return PLUGIN_HANDLED; } /** * Handle a specific selection from the command menu. * * @param id id of the client who made the selection. * @param menu The menu handle. * @param item The item that was selected. */ public CommandMenuSelection(id, menu, item) { if (item == MENU_EXIT) { menu_destroy(menu); // If the player did not explicitly specify a plugin, return them to the // plugin selection menu. if (g_explicit_plugin[id] == -1) { client_cmd(id, "amx_plugincmdmenu"); } } else if (item == MENU_BACK) { --g_current_page[id]; client_print(id, print_chat, "MENU_BACK"); } else if (item == MENU_MORE) { ++g_current_page[id]; client_print(id, print_chat, "MENU_MORE"); } else { new command[32]; // pcvar pointer is stored in command, extract the name of the cvar from the name field. menu_item_getinfo(menu, item, _, command, charsmax(command)); menu_destroy(menu); DisplaySpecificCommand(id, str_to_num(command)); } return PLUGIN_HANDLED; } /** * This blocks "say" and "say_team" commands. * Other commands that shouldn't be displayed (eg: amxauth) should be filtered out already. * * @param command The command that is being checked. */ stock bool:IsDisplayableCmd(const command[]) { // Block "say" and "say_team" if (equal(command, "say", 3)) { return false; } return true; } /** * Displays a command list for the specified plugin. * * @param id Id of the client that's being displayed to. * @param plid Plugin ID of the plugin that is to be displayed. * @param page The page to start at. */ public DisplayCmdMenu(id, plid, page) { new menu_title[64], command[64], plugin_name[32], cid_string[32], command_access; get_plugin(plid, _, _, plugin_name, charsmax(plugin_name)); formatex(menu_title, charsmax(menu_title), "%s %L:", plugin_name, id, "COMMANDS"); new menu = menu_create(menu_title, "CommandMenuSelection"); new userflags = get_user_flags(id); new bool:isadmin = bool:is_user_admin(id); for (new i = 0, max = get_concmdsnum(-1, -1); i < max; i++) { if (get_concmd_plid(i, -1, -1) == plid) { get_concmd(i, command, charsmax(command), command_access, "", 0, -1, -1); if (IsDisplayableCmd(command)) { if (userflags & command_access || (command_access == ADMIN_ADMIN && isadmin) || command_access == ADMIN_USER || command_access == ADMIN_ALL) { num_to_str(i, cid_string, charsmax(cid_string)); menu_additem(menu, command, cid_string, 0, g_enabled_callback); } else { menu_additem(menu, command, "", 0, g_disabled_callback); } } } } menu_setprop(menu, MPROP_NUMBER_COLOR, "\y"); menu_display(id, menu, page); } /** * Handles the "amx_plugincmdmenu" command. * * @param id Id of the client that's being checked. * @param level Access level of the command. * @param cid Command ID of the command that was executed. */ public CommandMenuCommand(id, level, cid) { if (!cmd_access(id, level, cid, 0)) { return PLUGIN_HANDLED; } // This is which plugin to display. -1 means display all plugins in a list. new plid = -1; if (GetPlidForValidPlugins(id, plid) != true) { // If GetPlidForValidPlugins returns false then it failed to find the plugin. return PLUGIN_HANDLED; } // Check if we were passed a specific plugin to display or not. if (plid == -1) { // We need to display a list of the plugins, instead of a specific plugin. g_explicit_plugin[id] = -1; DisplayPluginMenuDefault(id); } else { g_explicit_plugin[id] = plid; g_current_player_id[id] = plid; g_current_page[id] = 0; DisplayCmdMenu(id, plid, 0); } return PLUGIN_HANDLED; } DisplayPluginMenuDefault(id) { DisplayPluginMenu(id, "PLUGIN_CMD_MENU", "PluginMenuSelection", "DisplayCmdMenu", "GetNumberOfCmdsForPlid"); }