/*
* 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
}