Reads mapcycle.txt (or maps.ini) into a paginated menu; picking a map confirms, then changes level now or at round end. Includes amx_mapsearch.
csb_admin_map_menu.sma v1.0.0
1
/*
2
* CSB Admin Map Menu
3
* Copyright (C) 2026 counter-strike-boost.com
4
*
5
* Reads mapcycle.txt (or addons/amxmodx/configs/maps.ini) into a paginated menu.
6
* Picking a map asks for confirmation, then changes level immediately or waits
7
* for the end of the round. amx_mapsearch <name> finds a map by substring, and
8
* every candidate is checked with is_map_valid before it is offered.
9
*
10
* Inspired by the AMX Mod X maps menu (AMX Mod X Development Team). Independent
11
* GPL re-implementation.
12
*
13
* This program is free software: you can redistribute it and/or modify it under
14
* the terms of the GNU General Public License as published by the Free Software
15
* Foundation, either version 3 of the License, or (at your option) any later
16
* version. It is distributed in the hope that it will be useful, but WITHOUT
17
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18
* FOR A PARTICULAR PURPOSE. See <https://www.gnu.org/licenses/> for details.
19
*/
20
21
#include <amxmodx>
22
#include <amxmisc>
23
24
new const PLUGIN[] = "CSB Admin Map Menu"
25
new const VERSION[] = "1.0.0"
26
new const AUTHOR[] = "counter-strike-boost.com"
27
28
#define MAX_MAPS 128
29
30
new g_szMaps[MAX_MAPS][32]
31
new g_iMapCount = 0
32
33
new g_szPending[33][32] /* map chosen, awaiting confirm */
34
new bool:g_bChangePending = false
35
new g_szNextMap[32]
36
37
public plugin_init()
38
{
39
register_plugin(PLUGIN, VERSION, AUTHOR)
40
41
register_concmd("amx_mapmenu", "cmdMapMenu", ADMIN_MAP, "- open the map change menu")
42
register_concmd("amx_mapsearch", "cmdSearch", ADMIN_MAP, "<part of name> - search maps and open a menu")
43
44
register_logevent("eventRoundEnd", 2, "1=Round_End")
45
}
46
47
public plugin_cfg()
48
{
49
loadMaps()
50
}
51
52
loadMaps()
53
{
54
g_iMapCount = 0
55
56
if (readMapFile("mapcycle.txt"))
57
return
58
59
readMapFile("addons/amxmodx/configs/maps.ini")
60
}
61
62
bool:readMapFile(const path[])
63
{
64
new line[64], name[32], len, i = 0
65
66
while (g_iMapCount < MAX_MAPS && read_file(path, i, line, charsmax(line), len))
67
{
68
i++
69
trim(line)
70
71
if (!line[0] || line[0] == ';' || line[0] == '/' || line[0] == '#')
72
continue
73
74
/* mapcycle lines can be "de_dust2" or "de_dust2 { ... }" */
75
strtok(line, name, charsmax(name), line, charsmax(line), ' ')
76
trim(name)
77
78
if (!name[0] || !is_map_valid(name))
79
continue
80
81
copy(g_szMaps[g_iMapCount], charsmax(g_szMaps[]), name)
82
g_iMapCount++
83
}
84
85
return g_iMapCount > 0
86
}
87
88
public cmdMapMenu(id, level, cid)
89
{
90
if (!cmd_access(id, level, cid, 1))
91
return PLUGIN_HANDLED
92
93
if (!g_iMapCount)
94
{
95
console_print(id, "[CSB] No maps loaded (mapcycle.txt / maps.ini empty).")
96
return PLUGIN_HANDLED
97
}
98
99
buildMenu(id, "")
100
return PLUGIN_HANDLED
101
}
102
103
public cmdSearch(id, level, cid)
104
{
105
if (!cmd_access(id, level, cid, 1))
106
return PLUGIN_HANDLED
107
108
new needle[32]
109
read_argv(1, needle, charsmax(needle))
110
111
if (!needle[0])
112
{
113
console_print(id, "[CSB] Usage: amx_mapsearch <part of name>")
114
return PLUGIN_HANDLED
115
}
116
117
buildMenu(id, needle)
118
return PLUGIN_HANDLED
119
}
120
121
buildMenu(id, const needle[])
122
{
123
new title[64]
124
formatex(title, charsmax(title), "\yCSB Map Menu\w^nPick a map to change to")
125
126
new menu = menu_create(title, "menuPick")
127
new added = 0
128
129
for (new i = 0; i < g_iMapCount; i++)
130
{
131
if (needle[0] && containi(g_szMaps[i], needle) == -1)
132
continue
133
134
menu_additem(menu, g_szMaps[i], g_szMaps[i], 0)
135
added++
136
}
137
138
if (!added)
139
{
140
menu_destroy(menu)
141
console_print(id, "[CSB] No maps matched '%s'.", needle)
142
return
143
}
144
145
menu_display(id, menu, 0)
146
}
147
148
public menuPick(id, menu, item)
149
{
150
if (item == MENU_EXIT || item < 0)
151
{
152
menu_destroy(menu)
153
return PLUGIN_HANDLED
154
}
155
156
new info[32], name[32], access, callback
157
menu_item_getinfo(menu, item, access, info, charsmax(info), name, charsmax(name), callback)
158
menu_destroy(menu)
159
160
copy(g_szPending[id], charsmax(g_szPending[]), info)
161
confirmMenu(id)
162
return PLUGIN_HANDLED
163
}
164
165
confirmMenu(id)
166
{
167
new title[96]
168
formatex(title, charsmax(title), "\yChange to %s?\w", g_szPending[id])
169
170
new menu = menu_create(title, "menuConfirm")
171
menu_additem(menu, "\rChange now", "now", 0)
172
menu_additem(menu, "\yChange at round end", "end", 0)
173
menu_additem(menu, "\wCancel", "cancel", 0)
174
menu_setprop(menu, MPROP_EXIT, MEXIT_ALL)
175
menu_display(id, menu, 0)
176
}
177
178
public menuConfirm(id, menu, item)
179
{
180
if (item == MENU_EXIT || item < 0)
181
{
182
menu_destroy(menu)
183
return PLUGIN_HANDLED
184
}
185
186
new info[8], name[32], access, callback
187
menu_item_getinfo(menu, item, access, info, charsmax(info), name, charsmax(name), callback)
188
menu_destroy(menu)
189
190
if (equal(info, "cancel") || !g_szPending[id][0])
191
return PLUGIN_HANDLED
192
193
if (!is_map_valid(g_szPending[id]))
194
{
195
client_print(id, print_chat, "[CSB] %s is no longer valid.", g_szPending[id])
196
return PLUGIN_HANDLED
197
}
198
199
new aname[32]
200
getName(id, aname, charsmax(aname))
201
202
if (equal(info, "now"))
203
{
204
client_print(0, print_chat, "[CSB] %s is changing the map to %s...", aname, g_szPending[id])
205
log_amx("[CSB Maps] %s changed the map to %s", aname, g_szPending[id])
206
new mp[32]
207
copy(mp, charsmax(mp), g_szPending[id])
208
set_task(2.0, "taskChange", 0, mp, charsmax(mp))
209
}
210
else
211
{
212
copy(g_szNextMap, charsmax(g_szNextMap), g_szPending[id])
213
g_bChangePending = true
214
client_print(0, print_chat, "[CSB] %s scheduled %s for the end of this round.", aname, g_szNextMap)
215
log_amx("[CSB Maps] %s scheduled %s at round end", aname, g_szNextMap)
216
}
217
218
return PLUGIN_HANDLED
219
}
220
221
public taskChange(const map[])
222
{
223
server_cmd("changelevel %s", map)
224
}
225
226
public eventRoundEnd()
227
{
228
if (!g_bChangePending)
229
return
230
231
g_bChangePending = false
232
server_cmd("changelevel %s", g_szNextMap)
233
}
234
235
getName(id, name[], len)
236
{
237
if (id)
238
get_user_name(id, name, len)
239
else
240
copy(name, len, "CONSOLE")
241
}
242









