/* * CSB Longjump Trainer * Copyright (C) 2026 counter-strike-boost.com * * A KZ training tool. In PlayerPreThink it samples velocity and origin between * takeoff and landing to measure each long jump: distance covered, pre-strafe * speed at takeoff, number of jump inputs (bhop taps) and a strafe-sync * percentage (fraction of airborne frames where the player was gaining speed). * The result is shown in a HUD panel and the personal best per map is stored in * an nVault keyed by SteamID. * * Inspired by LJ Stats / KZ trainer plugins. Independent GPL re-implementation. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. It is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See for details. */ #include #include #include new const PLUGIN[] = "CSB Longjump Trainer" new const VERSION[] = "1.0.0" new const AUTHOR[] = "counter-strike-boost.com" #if !defined FL_ONGROUND #define FL_ONGROUND (1<<9) #endif #if !defined IN_JUMP #define IN_JUMP 2 #endif new g_pEnabled, g_pMinDist new bool:g_bInAir[33] new Float:g_fTakeoff[33][3] new Float:g_fPreSpeed[33] new Float:g_fLastSpeed[33] new g_iJumps[33] new g_iSyncGain[33] new g_iSyncTotal[33] new bool:g_bLastJump[33] new g_vault new g_szMap[32] public plugin_init() { register_plugin(PLUGIN, VERSION, AUTHOR) g_pEnabled = register_cvar("csb_lj_enabled", "1") g_pMinDist = register_cvar("csb_lj_mindist", "200") register_forward(FM_PlayerPreThink, "fwPreThink") register_clcmd("say /ljbest", "cmdBest") register_clcmd("say_team /ljbest", "cmdBest") get_mapname(g_szMap, charsmax(g_szMap)) g_vault = nvault_open("csb_ljtrainer") } public plugin_end() { nvault_close(g_vault) } public client_putinserver(id) { resetJump(id) } resetJump(id) { g_bInAir[id] = false g_iJumps[id] = 0 g_iSyncGain[id] = 0 g_iSyncTotal[id] = 0 g_fPreSpeed[id] = 0.0 g_fLastSpeed[id] = 0.0 g_bLastJump[id] = false } public fwPreThink(id) { if (!get_pcvar_num(g_pEnabled) || !is_user_alive(id)) return FMRES_IGNORED new flags = pev(id, pev_flags) new bool:onground = (flags & FL_ONGROUND) != 0 new btn = pev(id, pev_button) static Float:fVel[3] pev(id, pev_velocity, fVel) new Float:hspeed = floatsqroot(fVel[0] * fVel[0] + fVel[1] * fVel[1]) if (onground) { /* remember the last grounded speed as the pre-strafe speed */ g_fPreSpeed[id] = hspeed if (g_bInAir[id]) { g_bInAir[id] = false landed(id) } } else { if (!g_bInAir[id]) { g_bInAir[id] = true pev(id, pev_origin, g_fTakeoff[id]) g_iJumps[id] = 1 g_iSyncGain[id] = 0 g_iSyncTotal[id] = 0 g_fLastSpeed[id] = hspeed } else { if ((btn & IN_JUMP) && !g_bLastJump[id]) g_iJumps[id]++ g_iSyncTotal[id]++ if (hspeed > g_fLastSpeed[id]) g_iSyncGain[id]++ g_fLastSpeed[id] = hspeed } } g_bLastJump[id] = (btn & IN_JUMP) != 0 return FMRES_IGNORED } landed(id) { static Float:fNow[3] pev(id, pev_origin, fNow) new Float:dx = fNow[0] - g_fTakeoff[id][0] new Float:dy = fNow[1] - g_fTakeoff[id][1] new Float:dist = floatsqroot(dx * dx + dy * dy) if (dist < get_pcvar_float(g_pMinDist)) return new sync = 0 if (g_iSyncTotal[id] > 0) sync = (g_iSyncGain[id] * 100) / g_iSyncTotal[id] new Float:best = getBest(id) new bool:isPB = false if (dist > best) { isPB = true setBest(id, dist) } set_hudmessage(0, 255, 100, -1.0, 0.62, 0, 0.0, 4.0, 0.1, 0.3, -1) show_hudmessage(id, "Distance: %.1f u^nPrestrafe: %.0f u/s^nJumps: %d Sync: %d%%^n%s", dist, g_fPreSpeed[id], g_iJumps[id], sync, isPB ? "*** PERSONAL BEST ***" : " ") } Float:getBest(id) { new auth[35] get_user_authid(id, auth, charsmax(auth)) new key[80] formatex(key, charsmax(key), "%s_%s", g_szMap, auth) new data[16] if (nvault_get(g_vault, key, data, charsmax(data))) return str_to_float(data) return 0.0 } setBest(id, Float:dist) { new auth[35] get_user_authid(id, auth, charsmax(auth)) new key[80] formatex(key, charsmax(key), "%s_%s", g_szMap, auth) new data[16] formatex(data, charsmax(data), "%f", dist) nvault_set(g_vault, key, data) } public cmdBest(id) { new Float:best = getBest(id) if (best <= 0.0) client_print(id, print_chat, "^x04[CSB]^x01 No recorded long jump yet on %s.", g_szMap) else client_print(id, print_chat, "^x04[CSB]^x01 Your best long jump on %s: %.1f units.", g_szMap, best) return PLUGIN_HANDLED }