amxmodx/plugins/cstrike/restmenu.sma
Vincent Herbet 8e7eb94e50 Overhaul Restrict Weapons plugin - part 1 (#347)
* Restmenu: Use CS_OnBuyAttempt forward to detect any buy attempt - part 1

- Removed any useless code which won't be needed anymore with forward. A lot!

* Restmenu: Use CS_OnBuyAttempt forward to detect any buy attempt - part 2

- Added the actual forward
- Because of forward passing a CSI_* constant, it's needed to refactor how is saved a blocked item. Not fully implemented everywhere because menu is going to be refactored later as well.

* Restmenu: Replace findAliasId() by cs_get_item_id()

* Restmenu: Use cvar pointers

* Restmenu: Refactor menu - part 1

Purpose:
  - Convert old menus to new menus.
  - Instead of having one menu with several pages, having one main menu (item types) and sub-menus (items list). More readable, more easy to naviguate and no pagination.
  - Move hardcoded strings to ML

* Restmenu: Refactor menu - part 2

Purpose:
  - Convert old menus to new menus.
  - Instead of having one menu with several pages, having one main menu (item types) and sub-menus (items list). More readable, more easy to naviguate and no pagination.
  - Move hardcoded strings to ML
  - Minor visual improvements

* Restmenu: Refactor podbot stuff

- Factorized code
- Used a more direct way to set restrictions

* Restmenu: Refactor load/save config file

- Used new file natives
- Moved harcoded message header to ML
- QoL: saved restricted items will be grouped per type
- Cleaned up few things

* Restmenu: Refactor amx_restrict command

- Minor improvements on how are retrieved/handled arguments from command.
- Same as menu, it displays first classes list, then it lists associated items. No more pagination.
- Minor visual changes.

* Restmenu: Remove unused stuffs

* Restmenu: Move more harcoded messages to ML

* Restmenu: Move MAPSETTINGS define to cvar

- Don't forget me in changelog
- There is room for improvements but for now keep the same behavior

* Restmenu: Add some consistency, no code change

- Renamed some variables
- Changed functions order
- Removed extra spaces
- Fixed leading tabs

* Restmenu: Use inline formatting at various places

* Restmenu: Make command descriptions multilingual for player

* Restmenu: Refresh opened menus if the command is used to save/load/set.

Also, I've removed the renaming of "exit" item because it was misleading as it returns MENU_EXIT when it's named with "back" and handling the action wiht that doesn't make much sense.

* Restmenu: Adjust minor things (consistency, readability, safety)

* Restmenu: Show the current category when items are listed through the command

* Restmenu: Display admin activity on loading/saving

+ load: reset blocked items only if file has been loaded
+ load: ignored if no arguments
+ save: added a silly fix because of the cvars
2017-08-05 10:45:29 +02:00

676 lines
17 KiB
SourcePawn
Executable File

// 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
//
// Restrict Weapons Plugin
//
#include <amxmodx>
#include <amxmisc>
#include <cstrike>
new const PluginName[] = "Restrict Weapons";
const MaxAliasNameLength = 16;
const MaxItemNameLength = 32;
const MaxMenuTitleLength = 48;
const MaxCommandAliasLength = 12;
const MaxConfigFileLength = 48;
const MaxConsoleLength = 128;
const MaxMapLength = 32;
new bool:BlockedItems[CSI_MAX_COUNT];
new bool:ModifiedItem;
new bool:ConfigsExecuted;
new MenuPosition[MAX_PLAYERS + 1];
new MenuHandle [MAX_PLAYERS + 1] = { -1, ... };
new ConfigFilePath[PLATFORM_MAX_PATH];
new RestrictedBotWeapons[] = "00000000000000000000000000";
new RestrictedBotEquipAmmos[] = "000000000";
new CvarPointerAllowMapSettings;
new CvarPointerRestrictedWeapons;
new CvarPointerRestrictedEquipAmmos;
enum MenuTitle
{
m_Title[MaxMenuTitleLength],
m_Alias[MaxCommandAliasLength],
};
#define TITLE(%0) "MENU_TITLE_" + #%0
new const MenuInfos[][MenuTitle] =
{
{ TITLE(HANDGUNS) , "pistol" },
{ TITLE(SHOTGUNS) , "shotgun" },
{ TITLE(SUBMACHINES) , "sub" },
{ TITLE(RIFLES) , "rifle" },
{ TITLE(SNIPERS) , "sniper" },
{ TITLE(MACHINE) , "machine" },
{ TITLE(EQUIPMENT) , "equip" },
{ TITLE(AMMUNITION) , "ammo" },
}
enum MenuItem
{
m_Index,
m_Name[MaxItemNameLength],
};
#define ITEM(%0) { CSI_%0, "MENU_ITEM_" + #%0 }
#define ITEM_NONE { CSI_NONE, "" }
new const ItemsInfos[][][MenuItem] =
{
{ ITEM(USP) , ITEM(GLOCK18) , ITEM(DEAGLE) , ITEM(P228) , ITEM(ELITE) , ITEM(FIVESEVEN), ITEM_NONE , ITEM_NONE },
{ ITEM(M3) , ITEM(XM1014) , ITEM_NONE , ITEM_NONE , ITEM_NONE , ITEM_NONE , ITEM_NONE , ITEM_NONE },
{ ITEM(MP5NAVY), ITEM(TMP) , ITEM(P90) , ITEM(MAC10) , ITEM(UMP45) , ITEM_NONE , ITEM_NONE , ITEM_NONE },
{ ITEM(AK47) , ITEM(SG552) , ITEM(M4A1) , ITEM(GALIL) , ITEM(FAMAS) , ITEM(AUG) , ITEM_NONE , ITEM_NONE },
{ ITEM(SCOUT) , ITEM(AWP) , ITEM(G3SG1) , ITEM(SG550) , ITEM_NONE , ITEM_NONE , ITEM_NONE , ITEM_NONE },
{ ITEM(M249) , ITEM_NONE , ITEM_NONE , ITEM_NONE , ITEM_NONE , ITEM_NONE , ITEM_NONE , ITEM_NONE },
{ ITEM(VEST) , ITEM(VESTHELM), ITEM(FLASHBANG), ITEM(HEGRENADE), ITEM(SMOKEGRENADE), ITEM(DEFUSER) , ITEM(NVGS), ITEM(SHIELD) },
{ ITEM(PRIAMMO), ITEM(SECAMMO) , ITEM_NONE , ITEM_NONE , ITEM_NONE , ITEM_NONE , ITEM_NONE , ITEM_NONE },
};
public plugin_init()
{
register_plugin(PluginName, AMXX_VERSION_STR, "AMXX Dev Team");
register_dictionary("restmenu.txt");
register_dictionary("common.txt");
register_clcmd( "amx_restmenu", "@ClientCommand_MainMenu" , ADMIN_CFG, .info = "REG_CMD_MENU", .info_ml = true);
register_concmd("amx_restrict", "@ConsoleCommand_Restrict", ADMIN_CFG, .info = "REG_CMD_REST", .info_ml = true);
CvarPointerAllowMapSettings = register_cvar("amx_restrmapsettings", "0");
CvarPointerRestrictedWeapons = register_cvar("amx_restrweapons" , RestrictedBotWeapons);
CvarPointerRestrictedEquipAmmos = register_cvar("amx_restrequipammo" , RestrictedBotEquipAmmos);
}
public OnConfigsExecuted()
{
new const configFile[] = "weaprest";
new const configFileExt[] = "ini";
new configsDir[PLATFORM_MAX_PATH];
get_configsdir(configsDir, charsmax(configsDir));
if (get_pcvar_bool(CvarPointerAllowMapSettings))
{
new mapName[MaxMapLength];
get_mapname(mapName, charsmax(mapName));
formatex(ConfigFilePath, charsmax(ConfigFilePath), "%s/%s_%s.%s", configsDir, configFile, mapName, configFileExt);
}
else
{
formatex(ConfigFilePath, charsmax(ConfigFilePath), "%s/%s.%s", configsDir, configFile, configFileExt);
}
loadSettings(ConfigFilePath);
ConfigsExecuted = true;
}
public CS_OnBuyAttempt(player, itemid)
{
if (BlockedItems[itemid])
{
return blockcommand(player);
}
return PLUGIN_CONTINUE;
}
public blockcommand(const id) // Might be used by others plugins, so keep this for backward compatibility.
{
client_print(id, print_center, "%l", "RESTRICTED_ITEM");
return PLUGIN_HANDLED;
}
@ClientCommand_MainMenu(const id, const level, const cid)
{
if (cmd_access(id, level, cid, 1))
{
displayMenu(id, MenuPosition[id] = -1);
}
return PLUGIN_HANDLED;
}
@ConsoleCommand_Restrict(const id, const level, const cid)
{
if (!cmd_access(id, level, cid, 1))
{
return PLUGIN_HANDLED;
}
new const argumentsCount = read_argc();
if (argumentsCount <= 1) // Main command only, no arguments.
{
goto usage;
}
new action[8];
new argumentIndex;
new const actionLength = read_argv(++argumentIndex, action, charsmax(action)) - trim(action);
if (!actionLength || !isalpha(action[0])) // Empty argument or first character is not a letter.
{
goto usage;
}
new const ch1 = char_to_lower(action[0]);
new const ch2 = char_to_lower(action[1]);
if (ch1 == 'o' && (ch2 == 'n' || ch2 == 'f')) // [on]/[of]f
{
new const bool:restricted = (ch2 == 'n');
new bool:valid;
if (argumentsCount <= argumentIndex + 1) // No arguments, all items are concerned.
{
arrayset(BlockedItems, restricted, sizeof BlockedItems);
console_print(id, "%l", restricted ? "EQ_WE_RES" : "EQ_WE_UNRES");
ModifiedItem = valid = true;
refreshMenus(level);
}
else // Either item type or specific alias
{
new commands[MaxConsoleLength];
new itemName[MaxItemNameLength];
new argument[MaxAliasNameLength];
new position, class;
new itemid, slot;
new commandLength;
while (argumentIndex < argumentsCount)
{
// Ignore if the argument is empty or the first character is not a letter.
if ((commandLength = read_argv(++argumentIndex, commands, charsmax(commands)) - trim(commands)) <= 0 || !isalpha(commands[0]))
{
continue;
}
strtolower(commands);
position = 0;
// In case argument contains several input between quotes.
while (position != commandLength && (position = argparse(commands, position, argument, charsmax(argument))) != -1)
{
if ((class = findMenuAliasId(argument)) != -1)
{
for (slot = 0; slot < sizeof ItemsInfos[] && (itemid = ItemsInfos[class][slot][m_Index]) != CSI_NONE; ++slot)
{
BlockedItems[itemid] = restricted;
}
console_print(id, "%l %l %l", MenuInfos[class], (class < 6) ? "HAVE_BEEN" : "HAS_BEEN", restricted ? "RESTRICTED" : "UNRESTRICTED");
ModifiedItem = valid = true;
}
else if ((itemid = cs_get_item_id(argument)) != CSI_NONE)
{
BlockedItems[itemid] = restricted;
findItemFullName(itemid, itemName, charsmax(itemName));
console_print(id, "%l %l %l", itemName, "HAS_BEEN", restricted ? "RESTRICTED" : "UNRESTRICTED");
ModifiedItem = valid = true;
}
}
}
if (!valid)
{
console_print(id, "%l", "NO_EQ_WE");
}
else
{
refreshMenus(level);
}
}
if (ConfigsExecuted && valid)
{
show_activity_key("ADMIN_UPD_RES_1", "ADMIN_UPD_RES_2", fmt("%n", id));
log_amx("%L", LANG_SERVER, "ADMIN_CMD_UPDATEDCFG", id);
}
}
else if (ch1 == 'l' && ch2 == 'i') // [li]st
{
// Items list.
if (argumentsCount > argumentIndex + 1) // Available arguments.
{
new const selection = read_argv_int(++argumentIndex) - 1; // Index starts from 0.
if (0 <= selection <= charsmax(ItemsInfos))
{
console_print(id, "^n----- %l: %l -----^n", "WEAP_RES", MenuInfos[selection][m_Title]);
SetGlobalTransTarget(id);
new alias[MaxAliasNameLength];
new itemid;
console_print(id, " %-32.31s %-10.9s %-9.8s", fmt("%l", "NAME"), fmt("%l", "VALUE"), fmt("%l", "STATUS"));
console_print(id, "");
for (new slot = 0; slot < sizeof ItemsInfos[] && (itemid = ItemsInfos[selection][slot][m_Index]) != CSI_NONE; ++slot)
{
cs_get_item_alias(itemid, alias, charsmax(alias));
console_print(id, " %-32.31s %-10.9s %-9.8s", fmt("%l", ItemsInfos[selection][slot][m_Name]), alias
, fmt("%l", BlockedItems[itemid] ? "ON" : "OFF"));
}
console_print(id, "");
return PLUGIN_HANDLED;
}
}
console_print(id, "^n----- %l -----^n", "WEAP_RES");
// Item types list.
for (new class = 0; class < sizeof MenuInfos; ++class)
{
console_print(id, "%3d: %l", class + 1, MenuInfos[class][m_Title]);
}
console_print(id, "^n----- %l -----^n", "REST_USE_HOW");
}
else if (ch1 == 's') // [s]ave
{
// If 'save' is used in a per-map config file, the plugin config file is not yet known as it depends on
// amx_restrmapsettings cvar value read after per-map configs are processed. Postponing the saving a little.
if (!ConfigsExecuted)
{
const taskId = 424242;
if (!task_exists(taskId))
{
set_task(0.1, "@Task_SaveConfig", taskId);
}
return PLUGIN_HANDLED;
}
new bool:saved = saveSettings(ConfigFilePath);
if (saved)
{
ModifiedItem = false;
refreshMenus(level, .displaySaveMessage = true);
if (ConfigsExecuted)
{
log_amx("%L", LANG_SERVER, "ADMIN_CMD_SAVEDCFG", id, ConfigFilePath);
}
}
console_print(id, "%l^n", saved ? "REST_CONF_SAVED" : "REST_COULDNT_SAVE", ConfigFilePath);
}
else if (ch1 == 'l' && ch2 == 'o') // [lo]ad
{
if (argumentsCount <= argumentIndex + 1) // No argument
{
goto usage;
}
new argument[MaxConfigFileLength];
read_argv(++argumentIndex, argument, charsmax(argument)) - trim(argument);
new filepath[PLATFORM_MAX_PATH];
new length = get_configsdir(filepath, charsmax(filepath));
formatex(filepath[length], charsmax(filepath) - length, "/%s", argument);
new bool:loaded = loadSettings(filepath);
if (loaded)
{
arrayset(BlockedItems, false, sizeof BlockedItems);
ModifiedItem = true;
refreshMenus(level);
if (ConfigsExecuted)
{
show_activity_key("ADMIN_UPD_RES_1", "ADMIN_UPD_RES_2", fmt("%n", id));
log_amx("%L", LANG_SERVER, "ADMIN_CMD_LOADEDCFG", id, ConfigFilePath);
}
}
console_print(id, "%l^n", loaded ? "REST_CONF_LOADED" : "REST_COULDNT_LOAD", filepath);
}
else
{
usage:
console_print(id, "%l", "COM_REST_USAGE");
console_print(id, "^n%l", "COM_REST_COMMANDS");
console_print(id, "%l", "COM_REST_ON");
console_print(id, "%l", "COM_REST_OFF");
console_print(id, "%l", "COM_REST_ONV");
console_print(id, "%l", "COM_REST_OFFV");
console_print(id, "%l", "COM_REST_LIST");
console_print(id, "%l", "COM_REST_SAVE");
console_print(id, "%l^n", "COM_REST_LOAD");
console_print(id, "%l^n", "COM_REST_VALUES");
console_print(id, "%l^n", "COM_REST_TYPE");
}
return PLUGIN_HANDLED;
}
@Task_SaveConfig()
{
server_cmd("amx_restrict save");
}
displayMenu(const id, const position)
{
SetGlobalTransTarget(id);
new menuTitle[MaxMenuTitleLength * 2];
formatex(menuTitle, charsmax(menuTitle), " \y%l", "REST_WEAP");
new const menu = MenuHandle[id] = menu_create(menuTitle, "@OnMenuAction");
if (position < 0) // Main menu
{
for (new class = 0; class < sizeof MenuInfos; ++class)
{
menu_additem(menu, fmt("%l", MenuInfos[class][m_Title]));
}
}
else // Sub-menus
{
menu_setprop(menu, MPROP_TITLE, fmt("%s > \d%l", menuTitle, MenuInfos[position][m_Title]));
for (new slot = 0, data[MenuItem], index; slot < sizeof ItemsInfos[]; ++slot)
{
data = ItemsInfos[position][slot];
if ((index = data[m_Index]))
{
menu_additem(menu, fmt("%l\R%s%l", data[m_Name], BlockedItems[index] ? "\y" : "\r", BlockedItems[index] ? "ON" : "OFF"));
continue;
}
menu_addblank2(menu);
}
}
menu_addblank(menu, .slot = false);
menu_additem(menu, fmt("%s%l \y\R%s", ModifiedItem ? "\y" : "\d", "SAVE_SET", ModifiedItem ? "*" : ""));
if (position >= 0) // Inside a sub-menu
{
menu_addblank(menu, .slot = false);
menu_additem(menu, fmt("%l", "BACK"));
}
else // Main menu
{
menu_setprop(menu, MPROP_EXITNAME, fmt("%l", "EXIT"));
menu_setprop(menu, MPROP_EXIT, MEXIT_FORCE); // Force an EXIT item since pagination is disabled.
}
menu_setprop(menu, MPROP_PERPAGE, 0); // Disable pagination.
menu_setprop(menu, MPROP_NUMBER_COLOR, " \r"); // Small QoL change to avoid menu overlapping with left icons.
menu_display(id, menu);
return menu;
}
@OnMenuAction(const id, const menu, const key)
{
new position = MenuPosition[id];
if (key >= 0)
{
switch (key + 1)
{
case 1 .. sizeof ItemsInfos[]:
{
if (position < 0) // We are right now in the main menu, go to sub-menu.
{
position = key;
}
else // We are in a sub-menu.
{
ModifiedItem = true;
new const itemid = ItemsInfos[any:position][key][m_Index];
BlockedItems[itemid] = !BlockedItems[itemid];
restrictPodbotItem(itemid, .toggle = true);
updatePodbotCvars();
}
}
case sizeof ItemsInfos[] + 1: // Save option.
{
if (saveSettings(ConfigFilePath))
{
show_activity_key("ADMIN_UPD_RES_1", "ADMIN_UPD_RES_2", fmt("%n", id));
log_amx("%L", LANG_SERVER, "ADMIN_MENU_SAVEDCFG", id ,ConfigFilePath);
ModifiedItem = false;
}
client_print(id, print_chat, "* %l", ModifiedItem ? "CONF_SAV_FAIL" : "CONF_SAV_SUC");
}
default:
{
position = -1; // Back to main menu.
}
}
}
MenuHandle[id] = -1;
menu_destroy(menu);
if (position != MenuPosition[id] || key >= 0)
{
displayMenu(id, MenuPosition[id] = position);
}
return PLUGIN_HANDLED;
}
findAdminsWithMenu(playersList[MAX_PLAYERS], &playersCount, const commandLevel = -1)
{
new player, adminsCount;
new menu, newmenu;
get_players(playersList, playersCount, "ch");
for (new i = 0; i < playersCount, (player = playersList[i]); ++i)
{
if (player_menu_info(player, menu, newmenu) && newmenu != -1 && newmenu == MenuHandle[player])
{
if (commandLevel == -1 || access(player, commandLevel)) // extra safety
{
playersList[adminsCount++] = player;
}
}
}
playersCount = adminsCount;
}
refreshMenus(const commandLevel = 0, const bool:displaySaveMessage = false)
{
new playersList[MAX_PLAYERS], playersCount;
findAdminsWithMenu(playersList, playersCount, commandLevel);
if (!playersCount)
{
return;
}
for (new i = 0, player; i < playersCount, (player = playersList[i]); ++i)
{
MenuHandle[player] = displayMenu(player, MenuPosition[player]);
if (displaySaveMessage)
{
client_print(playersList[i], print_chat, "* %l (amx_restrict)", "CONF_SAV_SUC");
}
}
}
bool:saveSettings(const filename[])
{
new const fp = fopen(filename, "wt");
if (!fp)
{
return false;
}
fprintf(fp, "%L", LANG_SERVER, "CONFIG_FILE_HEADER", PluginName);
new alias[MaxAliasNameLength];
new itemid;
new bool:showCategory;
for (new class = 0, slot; class < sizeof ItemsInfos; ++class)
{
showCategory = true;
for (slot = 0; slot < sizeof ItemsInfos[]; ++slot)
{
if ((itemid = ItemsInfos[class][slot][m_Index]) == CSI_NONE)
{
break;
}
if (BlockedItems[itemid])
{
if (showCategory)
{
showCategory = false;
fprintf(fp, "^n; %l^n; -^n", MenuInfos[class][m_Title]);
}
cs_get_item_alias(itemid, alias, charsmax(alias));
fprintf(fp, "%-16.15s ; %L^n", alias, LANG_SERVER, ItemsInfos[class][slot][m_Name]);
}
}
}
fclose(fp);
return true;
}
bool:loadSettings(const filename[])
{
new const fp = fopen(filename, "rt");
if (!fp)
{
return false;
}
new lineRead[MaxAliasNameLength], alias[MaxAliasNameLength];
new itemid, ch;
arrayset(RestrictedBotEquipAmmos, '0', charsmax(RestrictedBotEquipAmmos));
arrayset(RestrictedBotWeapons, '0', charsmax(RestrictedBotWeapons));
while (!feof(fp))
{
if (fgets(fp, lineRead, charsmax(lineRead)) - trim(lineRead) <= 0)
{
continue;
}
if ((ch = lineRead[0]) == ';' || ch == '/' || ch == '#')
{
continue;
}
if (parse(lineRead, alias, charsmax(alias)) == 1 && (itemid = cs_get_item_id(alias)) != CSI_NONE)
{
BlockedItems[itemid] = true;
restrictPodbotItem(itemid);
}
}
fclose (fp);
updatePodbotCvars();
return true;
}
findMenuAliasId(const name[])
{
for (new i = 0; i < sizeof MenuInfos; ++i)
{
if (equal(name, MenuInfos[i][m_Alias]))
{
return i;
}
}
return -1;
}
findItemFullName(const itemid, name[], const maxlen)
{
for (new class = 0, slot; class < sizeof ItemsInfos; ++class)
{
for (slot = 0; slot < sizeof ItemsInfos[]; ++slot)
{
if (ItemsInfos[class][slot][m_Index] == itemid)
{
copy(name, maxlen, ItemsInfos[class][slot][m_Name]);
return;
}
}
}
}
restrictPodbotItem(const itemid, const bool:toggle = false)
{
new const translatedItems[CSI_MAX_COUNT] =
{
// CSI ids -> string indexes of pb_restrweapons and pb_restrequipammo cvars. See podbot.cfg.
-1, 4, -1, 20, 3, 8, -1, 12, 19, 4, 5, 6, 13, 23, 17, 18, 1, 2, 21, 9, 24, 7, 16, 10, 22, 2, 3, 15, 14, 0, 11, 0, 1, 5, 6, 25, 7, 8
};
new const index = translatedItems[itemid];
if (index >= 0)
{
if ((itemid <= CSI_LAST_WEAPON && !(1 << itemid & CSI_ALL_GRENADES)) || itemid == CSI_SHIELD)
{
RestrictedBotWeapons[index] = toggle && RestrictedBotWeapons[index] == '1' ? '0' : '1';
}
else
{
RestrictedBotEquipAmmos[index] = toggle && RestrictedBotEquipAmmos[index] == '1' ? '0' : '1';
}
}
}
updatePodbotCvars()
{
set_pcvar_string(CvarPointerRestrictedWeapons, RestrictedBotWeapons);
set_pcvar_string(CvarPointerRestrictedEquipAmmos, RestrictedBotEquipAmmos);
}