/*
* 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 for details.
*/
#include
#include
#include
#include
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
}