Promuj serwer

Initial release 1.0.0

Browse Source
1.0.0 - Initial release.
Daemon666 committed commit 7fca6b33cc8bc00b7e3f684983c8a0228635de56
4 changed files with 281 additions and 0 deletions
+3 -0 CHANGELOG.md A
@@ -0,0 +1,3 @@
1 +## 1.0.0 (2026-04-01)
2 +
3 +1.0.0 - Initial release.
+1 -0 README.md A
@@ -0,0 +1 @@
1 +<p><strong>CSB Admin Map Menu</strong> turns your map list into a point-and-click change. On load it reads <code>mapcycle.txt</code> (falling back to <code>addons/amxmodx/configs/maps.ini</code>), validates every entry with <code>is_map_valid</code> so dead maps never appear, and builds a paginated menu. Pick a map and it asks whether to change <em>now</em> or <em>at the end of the round</em>.</p><p>Inspired by the AMX Mod X maps menu. Independent GPL re-implementation.</p><h2>Features</h2><ul><li><code>amx_mapmenu</code> opens the paginated, validated map list.</li><li>Confirmation step: change immediately or schedule for round end.</li><li><code>amx_mapsearch &lt;part of name&gt;</code> filters the list by substring and opens a menu of the matches.</li><li>Handles both plain and <code>{ ... }</code>-suffixed mapcycle lines, and skips comments.</li><li>The map change is announced to everyone and written to the AMXX log.</li></ul><h2>Installation</h2><ol><li>Compile <code>csb_admin_map_menu.sma</code> and copy it to <code>addons/amxmodx/plugins/</code>.</li><li>Add it to <code>plugins.ini</code> and change the map.</li></ol><p>Both commands need the <code>ADMIN_MAP</code> flag (<code>e</code>). If <code>mapcycle.txt</code> is empty the plugin tries <code>maps.ini</code>; if both are empty the menu reports that no maps were loaded.</p>
+241 -0 addons/amxmodx/scripting/csb_admin_map_menu.sma A
@@ -0,0 +1,241 @@
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 +}
+36 -0 csb.json A
@@ -0,0 +1,36 @@
1 +{
2 + "name": "CSB Admin Map Menu",
3 + "category": "admin",
4 + "tags": "maps, changelevel, menu",
5 + "image": "",
6 + "original_author": "AMX Mod X Development Team (Maps Menu)",
7 + "original_url": "",
8 + "license": "GPL-3.0",
9 + "title": "CSB Admin Map Menu",
10 + "short_desc": "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.",
11 + "version": "1.0.0",
12 + "amxx_min": "1.9",
13 + "amxx_compile_default": "1.9",
14 + "rehlds_ok": true,
15 + "hlds_ok": true,
16 + "regamedll_required": false,
17 + "metamod_flavor": "any",
18 + "reapi_required": false,
19 + "modules": "",
20 + "install_notes": "1. Put csb_admin_map_menu.amxx in addons/amxmodx/plugins/\n2. Add it to addons/amxmodx/configs/plugins.ini\n3. Change map",
21 + "known_conflicts": "Other map-vote / nextmap plugins that also call changelevel at round end can race this one.",
22 + "cvars": [],
23 + "commands": [
24 + {
25 + "name": "amx_mapmenu",
26 + "access": "e (ADMIN_MAP)",
27 + "description": "Open the map change menu."
28 + },
29 + {
30 + "name": "amx_mapsearch",
31 + "access": "e (ADMIN_MAP)",
32 + "description": "amx_mapsearch <part of name> - search maps and open a menu."
33 + }
34 + ],
35 + "main": "addons/amxmodx/scripting/csb_admin_map_menu.sma"
36 +}
0 37