Prodvigat server
csb-admin-menu / addons / amxmodx / scripting / csb_admin_menu.sma
501 lines 13.1 KiB Pawn Raw Permalink History
/*
 * CSB Admin Menu
 * Copyright (C) 2026 counter-strike-boost.com
 *
 * A central, flag-gated administration menu for AMX Mod X.
 * Inspired by the classic "Admin Commands / amxmodmenu" plugin shipped with
 * AMX Mod X by the AMX Mod X Development Team. This is an independent GPL
 * re-implementation, not a copy of their source.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version. It is distributed in the hope that it will be
 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details: <https://www.gnu.org/licenses/>.
 */

#include <amxmodx>
#include <amxmisc>
#include <cstrike>
#include <fun>

new const PLUGIN[] = "CSB Admin Menu"
new const VERSION[] = "1.0.0"
new const AUTHOR[]  = "counter-strike-boost.com"

#define ACT_KICK    1
#define ACT_BAN     2
#define ACT_SLAY    3
#define ACT_SLAP    4
#define ACT_MOVE_T  5
#define ACT_MOVE_CT 6

new g_pEnabled, g_pBanTime, g_pSlapDamage
new g_iTargetUserid[33]

new const g_szCvarName[][] =
{
    "mp_friendlyfire",
    "mp_freezetime",
    "mp_roundtime",
    "sv_gravity",
    "mp_autoteambalance",
    "sv_alltalk"
}

new const g_szCvarValues[][] =
{
    "0 1",
    "0 3 6 10",
    "1.75 2.5 5",
    "400 600 800 1000",
    "0 1",
    "0 1"
}

public plugin_init()
{
    register_plugin(PLUGIN, VERSION, AUTHOR)

    g_pEnabled    = register_cvar("csb_adminmenu_enabled", "1")
    g_pBanTime    = register_cvar("csb_adminmenu_bantime", "30")
    g_pSlapDamage = register_cvar("csb_adminmenu_slapdmg", "5")

    register_clcmd("amx_csbmenu", "cmdMenu", ADMIN_MENU, "- opens the CSB admin menu")
    register_clcmd("say /admin", "cmdMenu", ADMIN_MENU, "- opens the CSB admin menu")
    register_clcmd("say_team /admin", "cmdMenu", ADMIN_MENU, "- opens the CSB admin menu")
}

public cmdMenu(id, level, cid)
{
    if (!cmd_access(id, level, cid, 1))
        return PLUGIN_HANDLED

    if (!get_pcvar_num(g_pEnabled))
    {
        client_print(id, print_chat, "[CSB] The admin menu is disabled (csb_adminmenu_enabled 0).")
        return PLUGIN_HANDLED
    }

    showMainMenu(id)
    return PLUGIN_HANDLED
}

showMainMenu(id)
{
    new menu = menu_create("\yCSB Admin Menu^n\wPick a category", "handlerMain")

    menu_additem(menu, "Player commands", "1", 0)
    menu_additem(menu, "Change map", "2", 0)
    menu_additem(menu, "Server cvars", "3", 0)
    menu_additem(menu, "Swap teams", "4", 0)
    menu_additem(menu, "Restart round", "5", 0)

    menu_setprop(menu, MPROP_EXITNAME, "Close")
    menu_display(id, menu, 0)
}

public handlerMain(id, menu, item)
{
    if (item == MENU_EXIT)
    {
        menu_destroy(menu)
        return PLUGIN_HANDLED
    }

    new info[8], name[32], access, callback
    menu_item_getinfo(menu, item, access, info, charsmax(info), name, charsmax(name), callback)
    menu_destroy(menu)

    switch (str_to_num(info))
    {
        case 1: showPlayerMenu(id)
        case 2: showMapMenu(id)
        case 3: showCvarMenu(id)
        case 4:
        {
            if (!(get_user_flags(id) & ADMIN_LEVEL_A))
            {
                client_print(id, print_chat, "[CSB] You do not have access to team swapping.")
                return PLUGIN_HANDLED
            }
            swapTeams(id)
        }
        case 5:
        {
            if (!(get_user_flags(id) & ADMIN_CVAR))
            {
                client_print(id, print_chat, "[CSB] You do not have access to round control.")
                return PLUGIN_HANDLED
            }
            server_cmd("sv_restartround 1")
            announce(id, "restarted the round")
        }
    }

    return PLUGIN_HANDLED
}

showPlayerMenu(id)
{
    new menu = menu_create("\yCSB Admin Menu^n\wSelect a player", "handlerPlayer")
    new players[32], num, pid, pname[32], info[8], line[64]

    get_players(players, num, "ch")

    for (new i = 0; i < num; i++)
    {
        pid = players[i]
        get_user_name(pid, pname, charsmax(pname))
        formatex(info, charsmax(info), "%d", get_user_userid(pid))
        formatex(line, charsmax(line), "%s \r[%s]", pname, is_user_alive(pid) ? "alive" : "dead")
        menu_additem(menu, line, info, 0)
    }

    menu_setprop(menu, MPROP_EXITNAME, "Back")
    menu_display(id, menu, 0)
}

public handlerPlayer(id, menu, item)
{
    if (item == MENU_EXIT)
    {
        menu_destroy(menu)
        showMainMenu(id)
        return PLUGIN_HANDLED
    }

    new info[8], name[32], access, callback
    menu_item_getinfo(menu, item, access, info, charsmax(info), name, charsmax(name), callback)
    menu_destroy(menu)

    g_iTargetUserid[id] = str_to_num(info)
    showActionMenu(id)
    return PLUGIN_HANDLED
}

showActionMenu(id)
{
    new target = find_player("k", g_iTargetUserid[id])

    if (!target)
    {
        client_print(id, print_chat, "[CSB] That player already left the server.")
        showPlayerMenu(id)
        return
    }

    new pname[32], title[96]
    get_user_name(target, pname, charsmax(pname))
    formatex(title, charsmax(title), "\yCSB Admin Menu^n\wTarget: \r%s", pname)

    new menu = menu_create(title, "handlerAction")

    menu_additem(menu, "Kick",       "1", 0)
    menu_additem(menu, "Ban",        "2", 0)
    menu_additem(menu, "Slay",       "3", 0)
    menu_additem(menu, "Slap",       "4", 0)
    menu_additem(menu, "Move to T",  "5", 0)
    menu_additem(menu, "Move to CT", "6", 0)

    menu_setprop(menu, MPROP_EXITNAME, "Back")
    menu_display(id, menu, 0)
}

public handlerAction(id, menu, item)
{
    if (item == MENU_EXIT)
    {
        menu_destroy(menu)
        showPlayerMenu(id)
        return PLUGIN_HANDLED
    }

    new info[8], name[32], access, callback
    menu_item_getinfo(menu, item, access, info, charsmax(info), name, charsmax(name), callback)
    menu_destroy(menu)

    new action = str_to_num(info)
    new target = find_player("k", g_iTargetUserid[id])

    if (!target)
    {
        client_print(id, print_chat, "[CSB] That player already left the server.")
        return PLUGIN_HANDLED
    }

    if (get_user_flags(target) & ADMIN_IMMUNITY)
    {
        client_print(id, print_chat, "[CSB] That player is immune.")
        return PLUGIN_HANDLED
    }

    new flags = get_user_flags(id)
    new tname[32], what[64]
    get_user_name(target, tname, charsmax(tname))

    switch (action)
    {
        case ACT_KICK:
        {
            if (!(flags & ADMIN_KICK)) { noAccess(id); return PLUGIN_HANDLED; }
            server_cmd("kick #%d ^"Kicked by an admin^"", get_user_userid(target))
            formatex(what, charsmax(what), "kicked %s", tname)
        }
        case ACT_BAN:
        {
            if (!(flags & ADMIN_BAN)) { noAccess(id); return PLUGIN_HANDLED; }
            new mins = get_pcvar_num(g_pBanTime)
            server_cmd("banid %d #%d kick", mins, get_user_userid(target))
            server_cmd("writeid")
            formatex(what, charsmax(what), "banned %s for %d minutes", tname, mins)
        }
        case ACT_SLAY:
        {
            if (!(flags & ADMIN_SLAY)) { noAccess(id); return PLUGIN_HANDLED; }
            if (!is_user_alive(target))
            {
                client_print(id, print_chat, "[CSB] %s is already dead.", tname)
                return PLUGIN_HANDLED
            }
            user_kill(target, 1)
            formatex(what, charsmax(what), "slayed %s", tname)
        }
        case ACT_SLAP:
        {
            if (!(flags & ADMIN_SLAY)) { noAccess(id); return PLUGIN_HANDLED; }
            if (!is_user_alive(target))
            {
                client_print(id, print_chat, "[CSB] %s is already dead.", tname)
                return PLUGIN_HANDLED
            }
            user_slap(target, get_pcvar_num(g_pSlapDamage))
            formatex(what, charsmax(what), "slapped %s", tname)
        }
        case ACT_MOVE_T, ACT_MOVE_CT:
        {
            if (!(flags & ADMIN_LEVEL_A)) { noAccess(id); return PLUGIN_HANDLED; }
            new CsTeams:team = (action == ACT_MOVE_T) ? CS_TEAM_T : CS_TEAM_CT
            if (is_user_alive(target))
                user_kill(target, 1)
            cs_set_user_team(target, team)
            formatex(what, charsmax(what), "moved %s to %s", tname, (action == ACT_MOVE_T) ? "Terrorists" : "Counter-Terrorists")
        }
    }

    announce(id, what)
    showActionMenu(id)
    return PLUGIN_HANDLED
}

showMapMenu(id)
{
    if (!(get_user_flags(id) & ADMIN_MAP))
    {
        noAccess(id)
        return
    }

    new menu = menu_create("\yCSB Admin Menu^n\wChange map", "handlerMap")
    new line[64], txtlen, added = 0

    for (new i = 0; i < 128; i++)
    {
        if (!read_file("mapcycle.txt", i, line, charsmax(line), txtlen))
            break

        trim(line)

        if (!line[0] || line[0] == ';' || line[0] == '/')
            continue

        if (!is_map_valid(line))
            continue

        menu_additem(menu, line, line, 0)
        added++
    }

    if (!added)
    {
        menu_destroy(menu)
        client_print(id, print_chat, "[CSB] mapcycle.txt is empty or unreadable.")
        return
    }

    menu_setprop(menu, MPROP_EXITNAME, "Back")
    menu_display(id, menu, 0)
}

public handlerMap(id, menu, item)
{
    if (item == MENU_EXIT)
    {
        menu_destroy(menu)
        showMainMenu(id)
        return PLUGIN_HANDLED
    }

    new map[32], name[32], access, callback
    menu_item_getinfo(menu, item, access, map, charsmax(map), name, charsmax(name), callback)
    menu_destroy(menu)

    if (!is_map_valid(map))
    {
        client_print(id, print_chat, "[CSB] %s is not a valid map on this server.", map)
        return PLUGIN_HANDLED
    }

    new what[64]
    formatex(what, charsmax(what), "changed the map to %s", map)
    announce(id, what)

    set_task(3.0, "taskChangeLevel", 0, map, strlen(map) + 1)
    return PLUGIN_HANDLED
}

public taskChangeLevel(map[])
{
    server_cmd("changelevel %s", map)
}

showCvarMenu(id)
{
    if (!(get_user_flags(id) & ADMIN_CVAR))
    {
        noAccess(id)
        return
    }

    new menu = menu_create("\yCSB Admin Menu^n\wServer cvars (click to cycle)", "handlerCvar")
    new line[64], value[16], info[8]

    for (new i = 0; i < sizeof(g_szCvarName); i++)
    {
        get_cvar_string(g_szCvarName[i], value, charsmax(value))
        formatex(line, charsmax(line), "%s \r[%s]", g_szCvarName[i], value)
        formatex(info, charsmax(info), "%d", i)
        menu_additem(menu, line, info, 0)
    }

    menu_setprop(menu, MPROP_EXITNAME, "Back")
    menu_display(id, menu, 0)
}

public handlerCvar(id, menu, item)
{
    if (item == MENU_EXIT)
    {
        menu_destroy(menu)
        showMainMenu(id)
        return PLUGIN_HANDLED
    }

    new info[8], name[64], access, callback
    menu_item_getinfo(menu, item, access, info, charsmax(info), name, charsmax(name), callback)
    menu_destroy(menu)

    new idx = str_to_num(info)

    if (idx < 0 || idx >= sizeof(g_szCvarName))
        return PLUGIN_HANDLED

    new cur[16], next[16], what[96]
    get_cvar_string(g_szCvarName[idx], cur, charsmax(cur))
    nextValue(g_szCvarValues[idx], cur, next, charsmax(next))
    set_cvar_string(g_szCvarName[idx], next)

    formatex(what, charsmax(what), "set %s to %s", g_szCvarName[idx], next)
    announce(id, what)

    showCvarMenu(id)
    return PLUGIN_HANDLED
}

swapTeams(id)
{
    new players[32], num, pid
    get_players(players, num, "ch")

    for (new i = 0; i < num; i++)
    {
        pid = players[i]
        new CsTeams:team = cs_get_user_team(pid)

        if (team == CS_TEAM_T)
            cs_set_user_team(pid, CS_TEAM_CT)
        else if (team == CS_TEAM_CT)
            cs_set_user_team(pid, CS_TEAM_T)
    }

    server_cmd("sv_restartround 1")
    announce(id, "swapped the teams")
}

nextValue(const list[], const cur[], out[], len)
{
    new piece[16], first[16], pos = 0, i, found = 0
    new listlen = strlen(list)

    first[0] = 0

    while (pos < listlen)
    {
        i = 0

        while (pos < listlen && list[pos] != ' ')
        {
            if (i < charsmax(piece))
                piece[i++] = list[pos]
            pos++
        }

        piece[i] = 0

        while (pos < listlen && list[pos] == ' ')
            pos++

        if (!first[0])
            copy(first, charsmax(first), piece)

        if (found)
        {
            copy(out, len, piece)
            return
        }

        if (equal(piece, cur))
            found = 1
    }

    copy(out, len, first)
}

announce(id, const what[])
{
    new aname[32], authid[35], buf[192]
    get_user_name(id, aname, charsmax(aname))
    get_user_authid(id, authid, charsmax(authid))

    formatex(buf, charsmax(buf), "^x04[CSB]^x03 %s^x01 %s.", aname, what)

    new players[32], num, msgid = get_user_msgid("SayText")
    get_players(players, num, "ch")

    for (new i = 0; i < num; i++)
    {
        message_begin(MSG_ONE, msgid, _, players[i])
        write_byte(players[i])
        write_string(buf)
        message_end()
    }

    log_amx("[CSB Admin Menu] %s <%s> %s", aname, authid, what)
}

noAccess(id)
{
    client_print(id, print_chat, "[CSB] You do not have the required access flag for that action.")
}