Prodvigat server
csb-random-spawns / addons / amxmodx / scripting / csb_random_spawns.sma
141 lines 4 KiB Pawn Raw Permalink History
/*
 * CSB Random Spawns
 * Copyright (C) 2026 counter-strike-boost.com
 *
 * Overrides the map's normal spawn assignment with a randomized pick from all
 * info_player_deathmatch / info_player_start / info_vip_start entities. Before
 * a point is used it is validated with a hull trace (must not start in solid)
 * and a distance check against living players, so nobody spawns inside a
 * teammate or in front of an enemy's crosshair. Designed for deathmatch and
 * gungame servers to reduce spawn camping.
 *
 * 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 <hamsandwich>
#include <fakemeta>
#include <engine>

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

#define MAX_SPAWNS 128

new g_iSpawnCount
new Float:g_fSpawnOrigin[MAX_SPAWNS][3]
new Float:g_fSpawnAngles[MAX_SPAWNS][3]

new g_pEnabled, g_pMinDist

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

    g_pEnabled = register_cvar("csb_rspawns_enabled", "1")
    g_pMinDist = register_cvar("csb_rspawns_mindist", "70.0")

    RegisterHam(Ham_Spawn, "player", "OnPlayerSpawn", 1)

    collectSpawns("info_player_deathmatch")
    collectSpawns("info_player_start")
    collectSpawns("info_vip_start")

    log_amx("[CSB] Random Spawns: collected %d spawn point(s).", g_iSpawnCount)
}

collectSpawns(const className[])
{
    new ent = -1

    while ((ent = find_ent_by_class(ent, className)) > 0)
    {
        if (g_iSpawnCount >= MAX_SPAWNS)
            break

        new Float:orig[3], Float:ang[3]
        pev(ent, pev_origin, orig)
        pev(ent, pev_angles, ang)

        /* lift slightly so the hull never clips the floor */
        orig[2] += 2.0

        g_fSpawnOrigin[g_iSpawnCount][0] = orig[0]
        g_fSpawnOrigin[g_iSpawnCount][1] = orig[1]
        g_fSpawnOrigin[g_iSpawnCount][2] = orig[2]
        g_fSpawnAngles[g_iSpawnCount][0] = ang[0]
        g_fSpawnAngles[g_iSpawnCount][1] = ang[1]
        g_fSpawnAngles[g_iSpawnCount][2] = ang[2]
        g_iSpawnCount++
    }
}

public OnPlayerSpawn(id)
{
    if (!get_pcvar_num(g_pEnabled) || !is_user_alive(id) || g_iSpawnCount < 1)
        return

    new team = get_user_team(id)
    if (team != 1 && team != 2)
        return

    new start = random_num(0, g_iSpawnCount - 1)
    new Float:minDist = get_pcvar_float(g_pMinDist)

    for (new i = 0; i < g_iSpawnCount; i++)
    {
        new idx = (start + i) % g_iSpawnCount

        if (!spawnFree(idx, id, minDist))
            continue

        new Float:orig[3], Float:ang[3]
        orig[0] = g_fSpawnOrigin[idx][0]
        orig[1] = g_fSpawnOrigin[idx][1]
        orig[2] = g_fSpawnOrigin[idx][2]
        ang[0]  = g_fSpawnAngles[idx][0]
        ang[1]  = g_fSpawnAngles[idx][1]
        ang[2]  = g_fSpawnAngles[idx][2]

        set_pev(id, pev_origin, orig)
        set_pev(id, pev_angles, ang)
        set_pev(id, pev_v_angle, ang)
        set_pev(id, pev_fixangle, 1)
        return
    }
}

bool:spawnFree(idx, id, Float:minDist)
{
    static Float:orig[3]
    orig[0] = g_fSpawnOrigin[idx][0]
    orig[1] = g_fSpawnOrigin[idx][1]
    orig[2] = g_fSpawnOrigin[idx][2]

    /* bit 1 (StartSolid) means a player hull does not fit here */
    if (trace_hull(orig, HULL_HUMAN, id) & 1)
        return false

    static players[32], num
    get_players(players, num, "a")

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

        static Float:o2[3]
        pev(players[i], pev_origin, o2)

        if (get_distance_f(orig, o2) < minDist)
            return false
    }

    return true
}