In-game spawn editor: /addspawn, /delspawn, /savespawns store points per map and override player spawns via SetOrigin.
csb_map_spawn_editor.sma v1.0.0
1
/*
2
* CSB Map Spawn Editor
3
* Copyright (C) 2026 counter-strike-boost.com
4
*
5
* In-game spawn editor for admins. /addspawn stores your current origin and
6
* angles, /delspawn removes the closest saved point, /savespawns writes them,
7
* and /listspawns reports the count. Spawns are kept per map in the AMXX data
8
* directory. On respawn the plugin teleports the player to a saved point with
9
* engfunc(EngFunc_SetOrigin) - useful for DM and CSDM style maps.
10
*
11
* Inspired by the CSDM spawn editor by BAILOPAN. Independent GPL
12
* re-implementation.
13
*
14
* This program is free software: you can redistribute it and/or modify it under
15
* the terms of the GNU General Public License as published by the Free Software
16
* Foundation, either version 3 of the License, or (at your option) any later
17
* version. It is distributed in the hope that it will be useful, but WITHOUT
18
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19
* FOR A PARTICULAR PURPOSE. See <https://www.gnu.org/licenses/> for details.
20
*/
21
22
#include <amxmodx>
23
#include <amxmisc>
24
#include <fakemeta>
25
#include <hamsandwich>
26
27
#define MAX_SPAWNS 64
28
29
new const PLUGIN[] = "CSB Map Spawn Editor"
30
new const VERSION[] = "1.0.0"
31
new const AUTHOR[] = "counter-strike-boost.com"
32
33
new Float:g_fSpawn[MAX_SPAWNS][6]
34
new g_iSpawnCount = 0
35
36
new g_pEnabled, g_pOverride
37
new g_szFile[192]
38
39
public plugin_init()
40
{
41
register_plugin(PLUGIN, VERSION, AUTHOR)
42
43
g_pEnabled = register_cvar("csb_spawn_enabled", "1")
44
g_pOverride = register_cvar("csb_spawn_override", "1")
45
46
register_clcmd("say /addspawn", "cmdAdd")
47
register_clcmd("say /delspawn", "cmdDel")
48
register_clcmd("say /savespawns", "cmdSave")
49
register_clcmd("say /listspawns", "cmdList")
50
51
RegisterHam(Ham_Spawn, "player", "fwdSpawnPost", 1)
52
}
53
54
public plugin_cfg()
55
{
56
new dir[128]
57
get_localinfo("amxx_datadir", dir, charsmax(dir))
58
59
new map[40]
60
get_mapname(map, charsmax(map))
61
62
formatex(g_szFile, charsmax(g_szFile), "%s/csb_spawns_%s.txt", dir, map)
63
64
loadSpawns()
65
}
66
67
loadSpawns()
68
{
69
g_iSpawnCount = 0
70
71
if (!file_exists(g_szFile))
72
return
73
74
new f = fopen(g_szFile, "rt")
75
76
if (!f)
77
return
78
79
new line[128], p[6][16]
80
81
while (!feof(f) && g_iSpawnCount < MAX_SPAWNS)
82
{
83
fgets(f, line, charsmax(line))
84
trim(line)
85
86
if (!line[0] || line[0] == ';')
87
continue
88
89
if (parse(line, p[0], 15, p[1], 15, p[2], 15, p[3], 15, p[4], 15, p[5], 15) < 6)
90
continue
91
92
for (new j = 0; j < 6; j++)
93
g_fSpawn[g_iSpawnCount][j] = str_to_float(p[j])
94
95
g_iSpawnCount++
96
}
97
98
fclose(f)
99
100
log_amx("[CSB Spawn] loaded %d spawn(s) for this map.", g_iSpawnCount)
101
}
102
103
bool:hasAccess(id)
104
{
105
if (get_user_flags(id) & ADMIN_MAP)
106
return true
107
108
client_print(id, print_chat, "[CSB] You need the map admin flag (e) to edit spawns.")
109
return false
110
}
111
112
public cmdAdd(id)
113
{
114
if (!hasAccess(id))
115
return PLUGIN_HANDLED
116
117
if (g_iSpawnCount >= MAX_SPAWNS)
118
{
119
client_print(id, print_chat, "[CSB] Spawn list is full (%d).", MAX_SPAWNS)
120
return PLUGIN_HANDLED
121
}
122
123
new Float:origin[3], Float:angles[3]
124
pev(id, pev_origin, origin)
125
pev(id, pev_v_angle, angles)
126
127
g_fSpawn[g_iSpawnCount][0] = origin[0]
128
g_fSpawn[g_iSpawnCount][1] = origin[1]
129
g_fSpawn[g_iSpawnCount][2] = origin[2]
130
g_fSpawn[g_iSpawnCount][3] = angles[0]
131
g_fSpawn[g_iSpawnCount][4] = angles[1]
132
g_fSpawn[g_iSpawnCount][5] = angles[2]
133
134
g_iSpawnCount++
135
136
client_print(id, print_chat, "[CSB] Spawn #%d saved at your position. Use /savespawns to write to disk.", g_iSpawnCount)
137
return PLUGIN_HANDLED
138
}
139
140
public cmdDel(id)
141
{
142
if (!hasAccess(id))
143
return PLUGIN_HANDLED
144
145
if (!g_iSpawnCount)
146
{
147
client_print(id, print_chat, "[CSB] No spawns to delete.")
148
return PLUGIN_HANDLED
149
}
150
151
new Float:origin[3]
152
pev(id, pev_origin, origin)
153
154
new closest = -1
155
new Float:best = 999999.0
156
157
for (new i = 0; i < g_iSpawnCount; i++)
158
{
159
new Float:pt[3]
160
pt[0] = g_fSpawn[i][0]
161
pt[1] = g_fSpawn[i][1]
162
pt[2] = g_fSpawn[i][2]
163
164
new Float:d = get_distance_f(origin, pt)
165
166
if (d < best)
167
{
168
best = d
169
closest = i
170
}
171
}
172
173
if (closest < 0)
174
return PLUGIN_HANDLED
175
176
/* shift the rest down */
177
for (new i = closest; i < g_iSpawnCount - 1; i++)
178
{
179
for (new j = 0; j < 6; j++)
180
g_fSpawn[i][j] = g_fSpawn[i + 1][j]
181
}
182
183
g_iSpawnCount--
184
185
client_print(id, print_chat, "[CSB] Removed the closest spawn (%.0f units away). %d left.", best, g_iSpawnCount)
186
return PLUGIN_HANDLED
187
}
188
189
public cmdSave(id)
190
{
191
if (!hasAccess(id))
192
return PLUGIN_HANDLED
193
194
new f = fopen(g_szFile, "wt")
195
196
if (!f)
197
{
198
client_print(id, print_chat, "[CSB] Could not open the spawn file for writing.")
199
return PLUGIN_HANDLED
200
}
201
202
fprintf(f, "; CSB Map Spawn Editor - x y z pitch yaw roll^n")
203
204
for (new i = 0; i < g_iSpawnCount; i++)
205
{
206
fprintf(f, "%.2f %.2f %.2f %.2f %.2f %.2f^n",
207
g_fSpawn[i][0], g_fSpawn[i][1], g_fSpawn[i][2],
208
g_fSpawn[i][3], g_fSpawn[i][4], g_fSpawn[i][5])
209
}
210
211
fclose(f)
212
213
client_print(id, print_chat, "[CSB] Wrote %d spawn(s) to disk.", g_iSpawnCount)
214
log_amx("[CSB Spawn] admin saved %d spawn(s).", g_iSpawnCount)
215
return PLUGIN_HANDLED
216
}
217
218
public cmdList(id)
219
{
220
if (!hasAccess(id))
221
return PLUGIN_HANDLED
222
223
client_print(id, print_chat, "[CSB] %d custom spawn(s) loaded for this map.", g_iSpawnCount)
224
return PLUGIN_HANDLED
225
}
226
227
public fwdSpawnPost(id)
228
{
229
if (!get_pcvar_num(g_pEnabled) || !get_pcvar_num(g_pOverride))
230
return
231
232
if (!g_iSpawnCount || !is_user_alive(id))
233
return
234
235
set_task(0.1, "taskTeleport", id)
236
}
237
238
public taskTeleport(id)
239
{
240
if (!is_user_alive(id) || !g_iSpawnCount)
241
return
242
243
new pick = random_num(0, g_iSpawnCount - 1)
244
245
new Float:origin[3], Float:angles[3]
246
origin[0] = g_fSpawn[pick][0]
247
origin[1] = g_fSpawn[pick][1]
248
origin[2] = g_fSpawn[pick][2]
249
angles[0] = g_fSpawn[pick][3]
250
angles[1] = g_fSpawn[pick][4]
251
angles[2] = g_fSpawn[pick][5]
252
253
engfunc(EngFunc_SetOrigin, id, origin)
254
255
set_pev(id, pev_angles, angles)
256
set_pev(id, pev_v_angle, angles)
257
set_pev(id, pev_fixangle, 1)
258
259
/* stop any residual momentum from the map's default spawn */
260
new Float:zero[3]
261
set_pev(id, pev_velocity, zero)
262
}
263









