Promocionar servidor

Initial release 1.0.0

Browse Source
1.0.0 — Initial release.
Daemon666 committed commit 28822d6460059dc2187ceb0ae7689a6ee9fa0f0d
4 changed files with 452 additions and 0 deletions
+3 -0 CHANGELOG.md A
@@ -0,0 +1,3 @@
1 +## 1.0.0 (2025-12-17)
2 +
3 +1.0.0 — Initial release.
+1 -0 README.md A
@@ -0,0 +1 @@
1 +<p>A <strong>Kreedz (climb) timer</strong>. The run starts when a player presses a start button and stops on the end/counter button — detected by the button's <code>targetname</code> containing <code>start</code> or <code>end</code>/<code>finish</code>/<code>counter</code>, which is how most climb maps are built. A live HUD shows elapsed time and jump count, checkpoints let you practice, and finish times are stored per map in nVault.</p><p>Independent GPL re-implementation inspired by the classic <strong>KZ Timer / xJ Timer</strong> plugins.</p><h2>Features</h2><ul><li>Automatic start/stop via <code>Ham_Use</code> on <code>func_button</code> target names.</li><li>Live HUD timer (mm:ss.mmm) and jump counter.</li><li>Checkpoints: <code>/cp</code> to save, <code>/tp</code> to teleport back, <code>/start</code> to reset.</li><li>Personal best (<code>/pb</code>) and a top-ten (<code>/top</code>), persisted per map in nVault.</li></ul><h2>Installation</h2><ol><li>Put <code>csb_kz_timer.amxx</code> in <code>addons/amxmodx/plugins/</code>.</li><li>Add it to <code>plugins.ini</code>.</li><li>Make sure the <code>nvault</code> module is enabled in <code>modules.ini</code> (it is by default).</li><li>Change to a <code>kz_</code>/<code>climb_</code> map.</li></ol>
+391 -0 addons/amxmodx/scripting/csb_kz_timer.sma A
@@ -0,0 +1,391 @@
1 +/*
2 + * CSB KZ Timer
3 + * Copyright (C) 2026 counter-strike-boost.com
4 + *
5 + * A Kreedz (climb) timer. The run starts when a player presses a start button
6 + * (targetname contains "start") and stops on the end/counter button. A live
7 + * HUD shows the elapsed time and jump count, checkpoints let you save/teleport
8 + * (/cp, /tp), and finish times are stored per map in nVault. /pb shows your
9 + * personal best, /top shows the map's ten best runs.
10 + *
11 + * Inspired by the classic KZ Timer / xJ Timer plugins. Independent GPL
12 + * re-implementation.
13 + *
14 + * This program is free software: you can redistribute it and/or modify it
15 + * under the terms of the GNU General Public License as published by the Free
16 + * Software Foundation, either version 3 of the License, or (at your option)
17 + * any later version. Distributed WITHOUT ANY WARRANTY. See the GNU General
18 + * Public License for more details: <https://www.gnu.org/licenses/>.
19 + */
20 +
21 +#include <amxmodx>
22 +#include <fakemeta>
23 +#include <hamsandwich>
24 +#include <fun>
25 +#include <nvault>
26 +
27 +new const PLUGIN[] = "CSB KZ Timer"
28 +new const VERSION[] = "1.0.0"
29 +new const AUTHOR[] = "counter-strike-boost.com"
30 +
31 +#define TOP_MAX 10
32 +
33 +new g_pEnabled
34 +
35 +new bool:g_bRunning[33]
36 +new Float:g_fStart[33]
37 +new g_iJumps[33]
38 +new bool:g_bHasCp[33]
39 +new Float:g_fCp[33][3]
40 +new Float:g_fCpAngles[33][3]
41 +
42 +new g_iVault
43 +new g_szMap[32]
44 +
45 +// in-memory top table for the current map
46 +new Float:g_fTopTime[TOP_MAX]
47 +new g_szTopName[TOP_MAX][32]
48 +new g_iTopCount
49 +
50 +public plugin_init()
51 +{
52 + register_plugin(PLUGIN, VERSION, AUTHOR)
53 +
54 + g_pEnabled = register_cvar("csb_kz_enabled", "1")
55 +
56 + RegisterHam(Ham_Use, "func_button", "fwButtonUse", 1)
57 +
58 + register_forward(FM_PlayerPreThink, "fwPreThink")
59 +
60 + register_clcmd("say /cp", "cmdSaveCp")
61 + register_clcmd("say /tp", "cmdTeleCp")
62 + register_clcmd("say /start", "cmdRestart")
63 + register_clcmd("say /pb", "cmdPersonalBest")
64 + register_clcmd("say /top", "cmdTop")
65 +
66 + get_mapname(g_szMap, charsmax(g_szMap))
67 + g_iVault = nvault_open("csb_kz_timer")
68 +
69 + loadTop()
70 +
71 + set_task(0.1, "taskHud", _, _, _, "b")
72 +}
73 +
74 +public plugin_end()
75 +{
76 + if (g_iVault != INVALID_HANDLE)
77 + nvault_close(g_iVault)
78 +}
79 +
80 +public client_putinserver(id)
81 +{
82 + resetRun(id)
83 + g_bHasCp[id] = false
84 +}
85 +
86 +resetRun(id)
87 +{
88 + g_bRunning[id] = false
89 + g_fStart[id] = 0.0
90 + g_iJumps[id] = 0
91 +}
92 +
93 +public fwButtonUse(ent, idcaller, idactivator, use_type, Float:value)
94 +{
95 + if (!get_pcvar_num(g_pEnabled))
96 + return HAM_IGNORED
97 +
98 + if (idactivator < 1 || idactivator > 32 || !is_user_alive(idactivator))
99 + return HAM_IGNORED
100 +
101 + new tname[64]
102 + pev(ent, pev_targetname, tname, charsmax(tname))
103 + strtolower(tname)
104 +
105 + if (contain(tname, "start") != -1)
106 + {
107 + startRun(idactivator)
108 + }
109 + else if (contain(tname, "end") != -1 || contain(tname, "finish") != -1 || contain(tname, "counter") != -1)
110 + {
111 + stopRun(idactivator)
112 + }
113 +
114 + return HAM_IGNORED
115 +}
116 +
117 +startRun(id)
118 +{
119 + g_bRunning[id] = true
120 + g_fStart[id] = get_gametime()
121 + g_iJumps[id] = 0
122 + client_print(id, print_center, "Timer started -- go!")
123 +}
124 +
125 +stopRun(id)
126 +{
127 + if (!g_bRunning[id])
128 + return
129 +
130 + new Float:elapsed = get_gametime() - g_fStart[id]
131 + g_bRunning[id] = false
132 +
133 + new name[32]
134 + get_user_name(id, name, charsmax(name))
135 +
136 + new tbuf[16]
137 + formatTime(elapsed, tbuf, charsmax(tbuf))
138 +
139 + client_print(0, print_chat, "[KZ] %s finished %s in %s (%d jumps).", name, g_szMap, tbuf, g_iJumps[id])
140 +
141 + savePersonal(id, elapsed)
142 + tryInsertTop(name, elapsed)
143 +}
144 +
145 +public fwPreThink(id)
146 +{
147 + if (!is_user_alive(id))
148 + return
149 +
150 + new button = pev(id, pev_button)
151 + new oldbutton = pev(id, pev_oldbuttons)
152 + new flags = pev(id, pev_flags)
153 +
154 + // count a take-off: jump pressed this frame, not last, while on ground
155 + if ((button & IN_JUMP) && !(oldbutton & IN_JUMP) && (flags & FL_ONGROUND))
156 + {
157 + if (g_bRunning[id])
158 + g_iJumps[id]++
159 + }
160 +}
161 +
162 +public cmdSaveCp(id)
163 +{
164 + if (!is_user_alive(id))
165 + return PLUGIN_HANDLED
166 +
167 + pev(id, pev_origin, g_fCp[id])
168 + pev(id, pev_v_angle, g_fCpAngles[id])
169 + g_bHasCp[id] = true
170 + client_print(id, print_center, "Checkpoint saved.")
171 + return PLUGIN_HANDLED
172 +}
173 +
174 +public cmdTeleCp(id)
175 +{
176 + if (!is_user_alive(id))
177 + return PLUGIN_HANDLED
178 +
179 + if (!g_bHasCp[id])
180 + {
181 + client_print(id, print_chat, "[KZ] No checkpoint saved yet. Use /cp first.")
182 + return PLUGIN_HANDLED
183 + }
184 +
185 + set_pev(id, pev_origin, g_fCp[id])
186 + set_pev(id, pev_velocity, Float:{0.0, 0.0, 0.0})
187 + set_pev(id, pev_v_angle, g_fCpAngles[id])
188 + set_pev(id, pev_fixangle, 1)
189 + client_print(id, print_center, "Teleported to checkpoint.")
190 + return PLUGIN_HANDLED
191 +}
192 +
193 +public cmdRestart(id)
194 +{
195 + resetRun(id)
196 + client_print(id, print_center, "Run reset. Hit the start button to begin.")
197 + return PLUGIN_HANDLED
198 +}
199 +
200 +public cmdPersonalBest(id)
201 +{
202 + new authid[35], key[80], data[16], timestamp
203 + get_user_authid(id, authid, charsmax(authid))
204 + formatex(key, charsmax(key), "%s#%s", g_szMap, authid)
205 +
206 + if (nvault_lookup(g_iVault, key, data, charsmax(data), timestamp))
207 + {
208 + new Float:best = str_to_float(data)
209 + new tbuf[16]
210 + formatTime(best, tbuf, charsmax(tbuf))
211 + client_print(id, print_chat, "[KZ] Your personal best on %s: %s", g_szMap, tbuf)
212 + }
213 + else
214 + {
215 + client_print(id, print_chat, "[KZ] You have no recorded time on %s yet.", g_szMap)
216 + }
217 + return PLUGIN_HANDLED
218 +}
219 +
220 +public cmdTop(id)
221 +{
222 + if (g_iTopCount == 0)
223 + {
224 + client_print(id, print_chat, "[KZ] No records on %s yet -- be the first!", g_szMap)
225 + return PLUGIN_HANDLED
226 + }
227 +
228 + client_print(id, print_chat, "[KZ] Top times on %s:", g_szMap)
229 + for (new i = 0; i < g_iTopCount; i++)
230 + {
231 + new tbuf[16]
232 + formatTime(g_fTopTime[i], tbuf, charsmax(tbuf))
233 + client_print(id, print_chat, " %d. %s -- %s", i + 1, g_szTopName[i], tbuf)
234 + }
235 + return PLUGIN_HANDLED
236 +}
237 +
238 +savePersonal(id, Float:elapsed)
239 +{
240 + new authid[35], key[80], data[16], stored[16], timestamp
241 + get_user_authid(id, authid, charsmax(authid))
242 + formatex(key, charsmax(key), "%s#%s", g_szMap, authid)
243 +
244 + new bool:save = true
245 + if (nvault_lookup(g_iVault, key, stored, charsmax(stored), timestamp))
246 + {
247 + if (str_to_float(stored) <= elapsed)
248 + save = false
249 + }
250 +
251 + if (save)
252 + {
253 + formatex(data, charsmax(data), "%f", elapsed)
254 + nvault_set(g_iVault, key, data)
255 + client_print(id, print_chat, "[KZ] New personal best saved!")
256 + }
257 +}
258 +
259 +tryInsertTop(const name[], Float:elapsed)
260 +{
261 + // find insertion slot (ascending time)
262 + new pos = -1
263 + for (new i = 0; i < g_iTopCount; i++)
264 + {
265 + if (elapsed < g_fTopTime[i])
266 + {
267 + pos = i
268 + break
269 + }
270 + }
271 +
272 + if (pos == -1)
273 + {
274 + if (g_iTopCount >= TOP_MAX)
275 + return
276 + pos = g_iTopCount
277 + }
278 +
279 + if (g_iTopCount < TOP_MAX)
280 + g_iTopCount++
281 +
282 + // shift down from the end
283 + for (new i = g_iTopCount - 1; i > pos; i--)
284 + {
285 + g_fTopTime[i] = g_fTopTime[i - 1]
286 + copy(g_szTopName[i], charsmax(g_szTopName[]), g_szTopName[i - 1])
287 + }
288 +
289 + g_fTopTime[pos] = elapsed
290 + copy(g_szTopName[pos], charsmax(g_szTopName[]), name)
291 +
292 + saveTop()
293 +}
294 +
295 +saveTop()
296 +{
297 + // serialise as "time|name;time|name;..."
298 + new blob[512], piece[80]
299 + blob[0] = 0
300 +
301 + for (new i = 0; i < g_iTopCount; i++)
302 + {
303 + formatex(piece, charsmax(piece), "%f|%s;", g_fTopTime[i], g_szTopName[i])
304 + add(blob, charsmax(blob), piece)
305 + }
306 +
307 + new key[48]
308 + formatex(key, charsmax(key), "TOP#%s", g_szMap)
309 + nvault_set(g_iVault, key, blob)
310 +}
311 +
312 +loadTop()
313 +{
314 + g_iTopCount = 0
315 +
316 + new key[48], blob[512], timestamp
317 + formatex(key, charsmax(key), "TOP#%s", g_szMap)
318 +
319 + if (!nvault_lookup(g_iVault, key, blob, charsmax(blob), timestamp))
320 + return
321 +
322 + new record[96], pos = 0
323 + while (g_iTopCount < TOP_MAX && (pos = kzNext(blob, pos, ';', record, charsmax(record))))
324 + {
325 + if (!record[0])
326 + continue
327 +
328 + new bar = contain(record, "|")
329 + if (bar < 1)
330 + continue
331 +
332 + new stime[24], sname[32]
333 + new copylen = bar < charsmax(stime) ? bar : charsmax(stime)
334 + copy(stime, copylen, record)
335 + copy(sname, charsmax(sname), record[bar + 1])
336 +
337 + g_fTopTime[g_iTopCount] = str_to_float(stime)
338 + copy(g_szTopName[g_iTopCount], charsmax(g_szTopName[]), sname)
339 + g_iTopCount++
340 + }
341 +}
342 +
343 +// walk a delimited blob; returns new position after the delimiter, 0 at end
344 +kzNext(const source[], start, delim, dest[], len)
345 +{
346 + new slen = strlen(source)
347 + if (start >= slen)
348 + return 0
349 +
350 + new i = 0
351 + while (start < slen && source[start] != delim)
352 + {
353 + if (i < len)
354 + dest[i++] = source[start]
355 + start++
356 + }
357 + dest[i] = 0
358 +
359 + if (start < slen && source[start] == delim)
360 + start++
361 +
362 + return start
363 +}
364 +
365 +public taskHud()
366 +{
367 + if (!get_pcvar_num(g_pEnabled))
368 + return
369 +
370 + for (new id = 1; id <= 32; id++)
371 + {
372 + if (!is_user_alive(id) || !g_bRunning[id])
373 + continue
374 +
375 + new Float:elapsed = get_gametime() - g_fStart[id]
376 + new tbuf[16]
377 + formatTime(elapsed, tbuf, charsmax(tbuf))
378 +
379 + set_hudmessage(0, 255, 100, 0.02, 0.25, 0, 0.0, 0.15, 0.0, 0.0, -1)
380 + show_hudmessage(id, "Time: %s^nJumps: %d", tbuf, g_iJumps[id])
381 + }
382 +}
383 +
384 +formatTime(Float:seconds, out[], len)
385 +{
386 + new total_ms = floatround(seconds * 1000.0)
387 + new mins = total_ms / 60000
388 + new secs = (total_ms / 1000) % 60
389 + new ms = total_ms % 1000
390 + formatex(out, len, "%02d:%02d.%03d", mins, secs, ms)
391 +}
+57 -0 csb.json A
@@ -0,0 +1,57 @@
1 +{
2 + "name": "CSB KZ Timer",
3 + "category": "movement",
4 + "tags": "kz, climb, timer",
5 + "image": "",
6 + "original_author": "KZ Timer / xJ Timer authors",
7 + "original_url": "",
8 + "license": "GPL-3.0",
9 + "title": "CSB KZ Timer",
10 + "short_desc": "Kreedz climb timer: start/end button detection, live HUD, checkpoints and per-map records in nVault.",
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": "fakemeta,hamsandwich,fun,nvault",
20 + "install_notes": "1. Put csb_kz_timer.amxx in addons/amxmodx/plugins/\n2. Add it to plugins.ini\n3. Ensure the nvault module is loaded\n4. Change to a kz_/climb_ map",
21 + "known_conflicts": "Other timer/bhop plugins that also teleport or hook func_button use.",
22 + "cvars": [
23 + {
24 + "name": "csb_kz_enabled",
25 + "default": "1",
26 + "description": "Master switch for the KZ timer."
27 + }
28 + ],
29 + "commands": [
30 + {
31 + "name": "say /cp",
32 + "access": "",
33 + "description": "Save a checkpoint at your position."
34 + },
35 + {
36 + "name": "say /tp",
37 + "access": "",
38 + "description": "Teleport to your saved checkpoint."
39 + },
40 + {
41 + "name": "say /start",
42 + "access": "",
43 + "description": "Reset your run."
44 + },
45 + {
46 + "name": "say /pb",
47 + "access": "",
48 + "description": "Show your personal best for this map."
49 + },
50 + {
51 + "name": "say /top",
52 + "access": "",
53 + "description": "Show the map's top-ten times."
54 + }
55 + ],
56 + "main": "addons/amxmodx/scripting/csb_kz_timer.sma"
57 +}
0 58