Daemon666 / CSB Admin Map Menu Public

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.

v1.0.0 122 descărcări GPL-3.0 Ultima actualizare Aug 24, 2026
csb_admin_map_menu.sma v1.0.0 242 linii · 6.0 KB Brut
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