Romania XPlayZM.CSBlackDevil.COM # Zombie Plague UltimateX [ Respawn + FDL + VIP 22-12PM + POINTS] ® 5/32
France -= OLT.LaLeagane.Ro 18+=- 3/32
France NGNW.PL [ONLY DD2][CLASSIC][SVIP 23-8][WSKRZESZANIE][MONETY][STEAM VIP][LOSOWANIE SVIP][SKINY] 4/32
Germany FRAGSYNDICATE.NET » ZOMBIE PLAGUE RETRO [LVLS] 2/32
Promocionar servidor
Daemon666 / CSB Map Chooser Público

End-of-map vote with random maps from the map cycle, a HUD countdown, recent-map exclusion and an optional extend option.

v1.0.0 179 descargas GPL-3.0 Última actualización Sep 13, 2026
csb_mapchooser.sma v1.0.0 418 líneas · 10.1 KB Sin formato
1 /*
2 * CSB Map Chooser
3 * Copyright (C) 2026 counter-strike-boost.com
4 *
5 * End-of-map vote. A short time before the time limit runs out the plugin opens
6 * a menu with a handful of random maps taken from the map cycle (excluding the
7 * current map and the last few that were played, which are tracked in a file),
8 * counts the votes with a HUD countdown and sets amx_nextmap to the winner. If
9 * enabled, players can instead vote to extend the current map, up to a capped
10 * number of extensions.
11 *
12 * Inspired by the original AMX Mod X MapChooser. This is an independent GPL
13 * re-implementation; no original code is reused.
14 *
15 * This program is free software: you can redistribute it and/or modify it under
16 * the terms of the GNU General Public License as published by the Free Software
17 * Foundation, either version 3 of the License, or (at your option) any later
18 * version. It is distributed in the hope that it will be useful, but WITHOUT
19 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20 * FOR A PARTICULAR PURPOSE. See <https://www.gnu.org/licenses/> for details.
21 */
22
23 #include <amxmodx>
24
25 new const PLUGIN[] = "CSB Map Chooser"
26 new const VERSION[] = "1.0.0"
27 new const AUTHOR[] = "counter-strike-boost.com"
28
29 #define MAX_CHOICES 6
30 #define EXTEND_SLOT MAX_CHOICES /* votes[] index used by the extend option */
31
32 new g_pEnabled, g_pStart, g_pChoices, g_pDuration
33 new g_pExtend, g_pExtendStep, g_pExtendMax, g_pRecent
34
35 new g_szChoice[MAX_CHOICES][32]
36 new g_iVotes[MAX_CHOICES + 1]
37 new g_iNumChoices
38 new bool:g_bExtendOffered
39
40 new bool:g_bVoted[33]
41 new bool:g_bVoteActive
42 new bool:g_bDecided
43 new g_iExtendsDone
44 new g_iSecondsLeft
45 new g_menu = -1
46
47 public plugin_init()
48 {
49 register_plugin(PLUGIN, VERSION, AUTHOR)
50
51 g_pEnabled = register_cvar("csb_mc_enabled", "1")
52 g_pStart = register_cvar("csb_mc_start", "90") /* seconds before end to open the vote */
53 g_pChoices = register_cvar("csb_mc_choices", "5")
54 g_pDuration = register_cvar("csb_mc_duration", "20")
55 g_pExtend = register_cvar("csb_mc_extend", "1")
56 g_pExtendStep = register_cvar("csb_mc_extendstep", "15") /* minutes added per extend */
57 g_pExtendMax = register_cvar("csb_mc_extendmax", "2")
58 g_pRecent = register_cvar("csb_mc_recent", "3")
59
60 register_clcmd("say /nextmap", "cmdNextmap")
61
62 set_task(15.0, "checkTime", 0, _, _, "b")
63 }
64
65 public cmdNextmap(id)
66 {
67 new nextmap[32]
68 get_cvar_string("amx_nextmap", nextmap, charsmax(nextmap))
69 client_print(id, print_chat, "[CSB] Next map: %s", nextmap[0] ? nextmap : "not decided yet")
70 return PLUGIN_HANDLED
71 }
72
73 public checkTime()
74 {
75 if (!get_pcvar_num(g_pEnabled) || g_bVoteActive || g_bDecided)
76 return
77
78 new left = get_timeleft()
79
80 /* no timelimit, or too early */
81 if (left <= 10 || left > get_pcvar_num(g_pStart))
82 return
83
84 startVote()
85 }
86
87 startVote()
88 {
89 new pool[64][32], poolCount = 0
90 gatherMaps(pool, poolCount)
91
92 if (poolCount < 2)
93 {
94 log_amx("[CSB MapChooser] not enough eligible maps to hold a vote.")
95 g_bDecided = true
96 return
97 }
98
99 new want = get_pcvar_num(g_pChoices)
100 if (want < 2) want = 2
101 if (want > MAX_CHOICES) want = MAX_CHOICES
102 if (want > poolCount) want = poolCount
103
104 g_iNumChoices = 0
105 for (new i = 0; i < want; i++)
106 {
107 new pick = random_num(0, poolCount - 1)
108 copy(g_szChoice[g_iNumChoices], charsmax(g_szChoice[]), pool[pick])
109 g_iNumChoices++
110
111 /* remove the chosen map so it cannot be picked twice */
112 pool[pick] = pool[poolCount - 1]
113 poolCount--
114 }
115
116 for (new i = 0; i <= MAX_CHOICES; i++)
117 g_iVotes[i] = 0
118
119 for (new id = 1; id <= 32; id++)
120 g_bVoted[id] = false
121
122 g_bExtendOffered = (get_pcvar_num(g_pExtend) != 0 && g_iExtendsDone < get_pcvar_num(g_pExtendMax))
123
124 buildMenu()
125
126 new players[32], num
127 get_players(players, num, "ch")
128 for (new i = 0; i < num; i++)
129 menu_display(players[i], g_menu, 0)
130
131 g_bVoteActive = true
132
133 new dur = get_pcvar_num(g_pDuration)
134 if (dur < 5) dur = 5
135 g_iSecondsLeft = dur
136
137 set_task(1.0, "voteTick", 0, _, _, "a", dur)
138 set_task(float(dur), "endVote")
139
140 client_print_color(0, print_team_default, "^x04[CSB]^x03 Vote for the next map!^x01 Check the menu.")
141 }
142
143 buildMenu()
144 {
145 if (g_menu != -1)
146 {
147 menu_destroy(g_menu)
148 g_menu = -1
149 }
150
151 g_menu = menu_create("\yVote for the next map\w", "voteHandler")
152
153 new item[48]
154 for (new i = 0; i < g_iNumChoices; i++)
155 {
156 formatex(item, charsmax(item), "%s", g_szChoice[i])
157 menu_additem(g_menu, item)
158 }
159
160 if (g_bExtendOffered)
161 {
162 formatex(item, charsmax(item), "\rExtend the current map (+%d min)", get_pcvar_num(g_pExtendStep))
163 menu_additem(g_menu, item)
164 }
165
166 menu_setprop(g_menu, MPROP_EXIT, MEXIT_ALL)
167 }
168
169 public voteHandler(id, menu, item)
170 {
171 if (item == MENU_EXIT || !g_bVoteActive)
172 return PLUGIN_HANDLED
173
174 if (g_bVoted[id])
175 return PLUGIN_HANDLED
176
177 /* extend option sits right after the map items */
178 if (g_bExtendOffered && item == g_iNumChoices)
179 {
180 g_iVotes[EXTEND_SLOT]++
181 g_bVoted[id] = true
182 client_print(id, print_chat, "[CSB] You voted to extend the current map.")
183 return PLUGIN_HANDLED
184 }
185
186 if (item >= 0 && item < g_iNumChoices)
187 {
188 g_iVotes[item]++
189 g_bVoted[id] = true
190 client_print(id, print_chat, "[CSB] You voted for %s.", g_szChoice[item])
191 }
192
193 return PLUGIN_HANDLED
194 }
195
196 public voteTick()
197 {
198 if (!g_bVoteActive)
199 return
200
201 if (g_iSecondsLeft > 0)
202 g_iSecondsLeft--
203
204 set_hudmessage(255, 200, 0, -1.0, 0.20, 0, 0.0, 1.1, 0.0, 0.0, -1)
205 show_hudmessage(0, "Map vote: %d second(s) left", g_iSecondsLeft)
206 }
207
208 public endVote()
209 {
210 g_bVoteActive = false
211
212 new best = -1, bestVotes = -1
213
214 for (new i = 0; i < g_iNumChoices; i++)
215 {
216 if (g_iVotes[i] > bestVotes)
217 {
218 bestVotes = g_iVotes[i]
219 best = i
220 }
221 }
222
223 new bool:extendWon = false
224 if (g_bExtendOffered && g_iVotes[EXTEND_SLOT] > bestVotes)
225 {
226 extendWon = true
227 bestVotes = g_iVotes[EXTEND_SLOT]
228 }
229
230 if (extendWon)
231 {
232 new step = get_pcvar_num(g_pExtendStep)
233 new Float:cur = get_cvar_float("mp_timelimit")
234 set_cvar_float("mp_timelimit", cur + float(step))
235 g_iExtendsDone++
236 g_bDecided = false /* allow another vote later */
237
238 client_print_color(0, print_team_default,
239 "^x04[CSB]^x03 The current map was extended by^x04 %d^x01 minute(s) (%d vote(s)).",
240 step, bestVotes)
241 }
242 else if (best != -1)
243 {
244 set_cvar_string("amx_nextmap", g_szChoice[best])
245 g_bDecided = true
246 rememberCurrentMap()
247
248 client_print_color(0, print_team_default,
249 "^x04[CSB]^x03 Next map will be^x04 %s^x01 (%d vote(s)).",
250 g_szChoice[best], bestVotes)
251 }
252 else
253 {
254 g_bDecided = true
255 }
256
257 if (g_menu != -1)
258 {
259 menu_destroy(g_menu)
260 g_menu = -1
261 }
262 }
263
264 /* ---------- map pool ---------- */
265
266 gatherMaps(pool[][], &count)
267 {
268 count = 0
269
270 new cur[32]
271 get_mapname(cur, charsmax(cur))
272
273 new recent[16][32], recentNum = 0
274 loadRecent(recent, recentNum)
275
276 new cyclefile[64]
277 get_cvar_string("mapcyclefile", cyclefile, charsmax(cyclefile))
278 if (!cyclefile[0])
279 copy(cyclefile, charsmax(cyclefile), "mapcycle.txt")
280
281 new fp = fopen(cyclefile, "rt")
282 if (!fp)
283 return
284
285 new line[64], map[32]
286
287 while (!feof(fp) && count < 64)
288 {
289 fgets(fp, line, charsmax(line))
290 trim(line)
291
292 if (!line[0] || line[0] == ';' || (line[0] == '/' && line[1] == '/'))
293 continue
294
295 /* first token is the map name */
296 new i = 0, j = 0
297 while (line[i] && line[i] != ' ' && line[i] != '^t' && j < charsmax(map))
298 map[j++] = line[i++]
299 map[j] = 0
300
301 if (!map[0] || equali(map, cur) || !is_map_valid(map))
302 continue
303
304 if (inList(map, recent, recentNum) || inList(map, pool, count))
305 continue
306
307 copy(pool[count], 31, map)
308 count++
309 }
310
311 fclose(fp)
312 }
313
314 bool:inList(const map[], const list[][], num)
315 {
316 for (new i = 0; i < num; i++)
317 if (equali(list[i], map))
318 return true
319 return false
320 }
321
322 loadRecent(recent[][], &num)
323 {
324 num = 0
325
326 new path[160]
327 recentPath(path, charsmax(path))
328
329 if (!file_exists(path))
330 return
331
332 new fp = fopen(path, "rt")
333 if (!fp)
334 return
335
336 new line[32]
337 new keep = get_pcvar_num(g_pRecent)
338
339 while (!feof(fp) && num < 16)
340 {
341 fgets(fp, line, charsmax(line))
342 trim(line)
343
344 if (!line[0])
345 continue
346
347 copy(recent[num], 31, line)
348 num++
349 }
350
351 fclose(fp)
352
353 /* only the last 'keep' entries actually count */
354 if (keep >= 0 && num > keep)
355 {
356 new drop = num - keep
357 for (new i = 0; i < keep; i++)
358 copy(recent[i], 31, recent[i + drop])
359 num = keep
360 }
361 }
362
363 rememberCurrentMap()
364 {
365 new cur[32]
366 get_mapname(cur, charsmax(cur))
367
368 new path[160]
369 recentPath(path, charsmax(path))
370
371 new lines[16][32], num = 0
372
373 if (file_exists(path))
374 {
375 new fp = fopen(path, "rt")
376 if (fp)
377 {
378 new line[32]
379 while (!feof(fp) && num < 16)
380 {
381 fgets(fp, line, charsmax(line))
382 trim(line)
383 if (line[0])
384 {
385 copy(lines[num], 31, line)
386 num++
387 }
388 }
389 fclose(fp)
390 }
391 }
392
393 /* keep the most recent 15, then append the current map */
394 new start = 0
395 if (num >= 15)
396 start = num - 14
397
398 new fw = fopen(path, "wt")
399 if (!fw)
400 return
401
402 for (new i = start; i < num; i++)
403 {
404 fputs(fw, lines[i])
405 fputs(fw, "^n")
406 }
407 fputs(fw, cur)
408 fputs(fw, "^n")
409 fclose(fw)
410 }
411
412 recentPath(out[], len)
413 {
414 new dir[128]
415 get_localinfo("amxx_configsdir", dir, charsmax(dir))
416 formatex(out, len, "%s/csb_mc_recent.ini", dir)
417 }
418