/* * CSB Map Spawn Editor * Copyright (C) 2026 counter-strike-boost.com * * In-game spawn editor for admins. /addspawn stores your current origin and * angles, /delspawn removes the closest saved point, /savespawns writes them, * and /listspawns reports the count. Spawns are kept per map in the AMXX data * directory. On respawn the plugin teleports the player to a saved point with * engfunc(EngFunc_SetOrigin) - useful for DM and CSDM style maps. * * Inspired by the CSDM spawn editor by BAILOPAN. Independent GPL * re-implementation. * * 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 #define MAX_SPAWNS 64 new const PLUGIN[] = "CSB Map Spawn Editor" new const VERSION[] = "1.0.0" new const AUTHOR[] = "counter-strike-boost.com" new Float:g_fSpawn[MAX_SPAWNS][6] new g_iSpawnCount = 0 new g_pEnabled, g_pOverride new g_szFile[192] public plugin_init() { register_plugin(PLUGIN, VERSION, AUTHOR) g_pEnabled = register_cvar("csb_spawn_enabled", "1") g_pOverride = register_cvar("csb_spawn_override", "1") register_clcmd("say /addspawn", "cmdAdd") register_clcmd("say /delspawn", "cmdDel") register_clcmd("say /savespawns", "cmdSave") register_clcmd("say /listspawns", "cmdList") RegisterHam(Ham_Spawn, "player", "fwdSpawnPost", 1) } public plugin_cfg() { new dir[128] get_localinfo("amxx_datadir", dir, charsmax(dir)) new map[40] get_mapname(map, charsmax(map)) formatex(g_szFile, charsmax(g_szFile), "%s/csb_spawns_%s.txt", dir, map) loadSpawns() } loadSpawns() { g_iSpawnCount = 0 if (!file_exists(g_szFile)) return new f = fopen(g_szFile, "rt") if (!f) return new line[128], p[6][16] while (!feof(f) && g_iSpawnCount < MAX_SPAWNS) { fgets(f, line, charsmax(line)) trim(line) if (!line[0] || line[0] == ';') continue if (parse(line, p[0], 15, p[1], 15, p[2], 15, p[3], 15, p[4], 15, p[5], 15) < 6) continue for (new j = 0; j < 6; j++) g_fSpawn[g_iSpawnCount][j] = str_to_float(p[j]) g_iSpawnCount++ } fclose(f) log_amx("[CSB Spawn] loaded %d spawn(s) for this map.", g_iSpawnCount) } bool:hasAccess(id) { if (get_user_flags(id) & ADMIN_MAP) return true client_print(id, print_chat, "[CSB] You need the map admin flag (e) to edit spawns.") return false } public cmdAdd(id) { if (!hasAccess(id)) return PLUGIN_HANDLED if (g_iSpawnCount >= MAX_SPAWNS) { client_print(id, print_chat, "[CSB] Spawn list is full (%d).", MAX_SPAWNS) return PLUGIN_HANDLED } new Float:origin[3], Float:angles[3] pev(id, pev_origin, origin) pev(id, pev_v_angle, angles) g_fSpawn[g_iSpawnCount][0] = origin[0] g_fSpawn[g_iSpawnCount][1] = origin[1] g_fSpawn[g_iSpawnCount][2] = origin[2] g_fSpawn[g_iSpawnCount][3] = angles[0] g_fSpawn[g_iSpawnCount][4] = angles[1] g_fSpawn[g_iSpawnCount][5] = angles[2] g_iSpawnCount++ client_print(id, print_chat, "[CSB] Spawn #%d saved at your position. Use /savespawns to write to disk.", g_iSpawnCount) return PLUGIN_HANDLED } public cmdDel(id) { if (!hasAccess(id)) return PLUGIN_HANDLED if (!g_iSpawnCount) { client_print(id, print_chat, "[CSB] No spawns to delete.") return PLUGIN_HANDLED } new Float:origin[3] pev(id, pev_origin, origin) new closest = -1 new Float:best = 999999.0 for (new i = 0; i < g_iSpawnCount; i++) { new Float:pt[3] pt[0] = g_fSpawn[i][0] pt[1] = g_fSpawn[i][1] pt[2] = g_fSpawn[i][2] new Float:d = get_distance_f(origin, pt) if (d < best) { best = d closest = i } } if (closest < 0) return PLUGIN_HANDLED /* shift the rest down */ for (new i = closest; i < g_iSpawnCount - 1; i++) { for (new j = 0; j < 6; j++) g_fSpawn[i][j] = g_fSpawn[i + 1][j] } g_iSpawnCount-- client_print(id, print_chat, "[CSB] Removed the closest spawn (%.0f units away). %d left.", best, g_iSpawnCount) return PLUGIN_HANDLED } public cmdSave(id) { if (!hasAccess(id)) return PLUGIN_HANDLED new f = fopen(g_szFile, "wt") if (!f) { client_print(id, print_chat, "[CSB] Could not open the spawn file for writing.") return PLUGIN_HANDLED } fprintf(f, "; CSB Map Spawn Editor - x y z pitch yaw roll^n") for (new i = 0; i < g_iSpawnCount; i++) { fprintf(f, "%.2f %.2f %.2f %.2f %.2f %.2f^n", g_fSpawn[i][0], g_fSpawn[i][1], g_fSpawn[i][2], g_fSpawn[i][3], g_fSpawn[i][4], g_fSpawn[i][5]) } fclose(f) client_print(id, print_chat, "[CSB] Wrote %d spawn(s) to disk.", g_iSpawnCount) log_amx("[CSB Spawn] admin saved %d spawn(s).", g_iSpawnCount) return PLUGIN_HANDLED } public cmdList(id) { if (!hasAccess(id)) return PLUGIN_HANDLED client_print(id, print_chat, "[CSB] %d custom spawn(s) loaded for this map.", g_iSpawnCount) return PLUGIN_HANDLED } public fwdSpawnPost(id) { if (!get_pcvar_num(g_pEnabled) || !get_pcvar_num(g_pOverride)) return if (!g_iSpawnCount || !is_user_alive(id)) return set_task(0.1, "taskTeleport", id) } public taskTeleport(id) { if (!is_user_alive(id) || !g_iSpawnCount) return new pick = random_num(0, g_iSpawnCount - 1) new Float:origin[3], Float:angles[3] origin[0] = g_fSpawn[pick][0] origin[1] = g_fSpawn[pick][1] origin[2] = g_fSpawn[pick][2] angles[0] = g_fSpawn[pick][3] angles[1] = g_fSpawn[pick][4] angles[2] = g_fSpawn[pick][5] engfunc(EngFunc_SetOrigin, id, origin) set_pev(id, pev_angles, angles) set_pev(id, pev_v_angle, angles) set_pev(id, pev_fixangle, 1) /* stop any residual momentum from the map's default spawn */ new Float:zero[3] set_pev(id, pev_velocity, zero) }