Promote server
csb-admin-spectate-tools / addons / amxmodx / scripting / csb_admin_spectate_tools.sma
241 lines 6.4 KiB Pawn Raw Permalink History
/*
 * CSB Spectate Tools
 * Copyright (C) 2026 counter-strike-boost.com
 *
 * Admin-only spectator helpers. amx_spectate opens a menu to jump straight into
 * any player's first-person view (pev_iuser1 = in-eye, pev_iuser2 = target); a
 * HUD shows the watched player's HP, armor and current weapon.
 * amx_specme moves the admin to spectator while remembering the team, and
 * amx_specback puts them back on it.
 *
 * 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 <https://www.gnu.org/licenses/> for details.
 */

#include <amxmodx>
#include <amxmisc>
#include <fakemeta>
#include <cstrike>

#define OBS_IN_EYE 4

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

new g_pEnabled
new g_iSyncHud
new g_iWatch[33]
new g_iSavedTeam[33]
new bool:g_bSpecMe[33]

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

    g_pEnabled = register_cvar("csb_spec_enabled", "1")

    register_concmd("amx_spectate", "cmdSpectate", ADMIN_SLAY, "- menu to watch a player in first person")
    register_concmd("amx_specme",   "cmdSpecMe",   ADMIN_SLAY, "- move yourself to spectator, keeping your team")
    register_concmd("amx_specback", "cmdSpecBack", ADMIN_SLAY, "- return to the team you had before amx_specme")

    g_iSyncHud = CreateHudSyncObj()

    set_task(1.0, "taskHud", 0, _, _, "b")
}

public client_disconnected(id)
{
    g_iWatch[id] = 0
    g_iSavedTeam[id] = 0
    g_bSpecMe[id] = false
}

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

    if (!get_pcvar_num(g_pEnabled))
    {
        console_print(id, "[CSB] Spectate tools are disabled (csb_spec_enabled 0).")
        return PLUGIN_HANDLED
    }

    showSpecMenu(id)
    return PLUGIN_HANDLED
}

showSpecMenu(id)
{
    new menu = menu_create("\ySpectate Tools^n\wPick a player to watch:", "specMenuHandler")

    new players[32], num, pid, name[32], info[8], line[64]
    get_players(players, num, "a")

    for (new i = 0; i < num; i++)
    {
        pid = players[i]
        get_user_name(pid, name, charsmax(name))
        num_to_str(pid, info, charsmax(info))
        formatex(line, charsmax(line), "%s \r[%d hp]", name, get_user_health(pid))
        menu_additem(menu, line, info, 0)
    }

    if (!num)
        menu_additem(menu, "\rNo alive players right now", "-1", 0)

    menu_setprop(menu, MPROP_TITLE, "\ySpectate Tools")
    menu_display(id, menu, 0)
}

public specMenuHandler(id, menu, item)
{
    if (item == MENU_EXIT || item < 0)
    {
        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)

    new target = str_to_num(info)

    if (target < 1 || !is_user_connected(target))
    {
        client_print(id, print_chat, "[CSB] That player is no longer available.")
        return PLUGIN_HANDLED
    }

    watchPlayer(id, target)
    return PLUGIN_HANDLED
}

watchPlayer(id, target)
{
    /* make sure the admin is a spectator first */
    new CsTeams:t = cs_get_user_team(id)

    if (t != CS_TEAM_SPECTATOR)
    {
        if (t == CS_TEAM_T || t == CS_TEAM_CT)
            g_iSavedTeam[id] = _:t

        cs_set_user_team(id, CS_TEAM_SPECTATOR)
        g_bSpecMe[id] = true
    }

    g_iWatch[id] = target

    new tname[32]
    get_user_name(target, tname, charsmax(tname))
    client_print(id, print_chat, "[CSB] Watching %s. Use amx_specback to return to your team.", tname)

    set_task(0.2, "taskApplyView", id)
}

public taskApplyView(id)
{
    if (!is_user_connected(id))
        return

    new target = g_iWatch[id]

    if (target < 1 || !is_user_connected(target))
        return

    set_pev(id, pev_iuser1, OBS_IN_EYE)
    set_pev(id, pev_iuser2, target)
}

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

    new CsTeams:t = cs_get_user_team(id)

    if (t == CS_TEAM_SPECTATOR)
    {
        client_print(id, print_chat, "[CSB] You are already a spectator.")
        return PLUGIN_HANDLED
    }

    if (t == CS_TEAM_T || t == CS_TEAM_CT)
        g_iSavedTeam[id] = _:t

    cs_set_user_team(id, CS_TEAM_SPECTATOR)
    g_bSpecMe[id] = true

    client_print(id, print_chat, "[CSB] Moved to spectator. amx_specback returns you to your team.")
    return PLUGIN_HANDLED
}

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

    if (!g_bSpecMe[id] || g_iSavedTeam[id] == 0)
    {
        client_print(id, print_chat, "[CSB] No saved team to return to.")
        return PLUGIN_HANDLED
    }

    cs_set_user_team(id, CsTeams:g_iSavedTeam[id])

    g_bSpecMe[id] = false
    g_iSavedTeam[id] = 0
    g_iWatch[id] = 0

    client_print(id, print_chat, "[CSB] Returned to your team. You will respawn next round.")
    return PLUGIN_HANDLED
}

public taskHud()
{
    new players[32], num, id, target
    get_players(players, num)

    for (new i = 0; i < num; i++)
    {
        id = players[i]
        target = g_iWatch[id]

        if (target < 1 || !is_user_connected(target) || cs_get_user_team(id) != CS_TEAM_SPECTATOR)
            continue

        if (!is_user_alive(target))
        {
            set_hudmessage(255, 60, 60, 0.02, 0.25, 0, 0.0, 1.1, 0.0, 0.0, -1)
            ShowSyncHudMsg(id, g_iSyncHud, "Watching: (dead)")
            continue
        }

        new tname[32], wname[32]
        get_user_name(target, tname, charsmax(tname))

        new wid = get_user_weapon(target)
        if (wid > 0)
            get_weaponname(wid, wname, charsmax(wname))
        else
            copy(wname, charsmax(wname), "none")

        replace(wname, charsmax(wname), "weapon_", "")

        new armor = get_user_armor(target)

        new hud[160]
        formatex(hud, charsmax(hud), "%s^nHP %d  AP %d^nWeapon: %s",
            tname, get_user_health(target), armor, wname)

        set_hudmessage(60, 220, 255, 0.02, 0.25, 0, 0.0, 1.1, 0.0, 0.0, -1)
        ShowSyncHudMsg(id, g_iSyncHud, hud)
    }
}