/*
* CSB Parachute
* Copyright (C) 2026 counter-strike-boost.com
*
* Hold +use while falling to deploy a parachute. In PlayerPreThink the plugin
* clamps the downward velocity to a configurable speed and shows a parachute
* model attached above the player (pev_aiment / MOVETYPE_FOLLOW); it is removed
* the moment the player lands or lets go of +use. Cvars control the fall speed,
* a VIP-only flag and a per-round use limit.
*
* Inspired by the well-known "Parachute" plugin by KRoTaL and later VEN. This is
* an independent GPL re-implementation; no original code is reused.
*
* 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
new const PLUGIN[] = "CSB Parachute"
new const VERSION[] = "1.0.0"
new const AUTHOR[] = "counter-strike-boost.com"
#define PARA_MODEL "models/csb_parachute.mdl"
new g_iChute[33]
new g_iUsed[33]
new bool:g_bModelOk
new g_pEnabled, g_pFallSpeed, g_pVipFlag, g_pMaxPerRound
public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)
g_pEnabled = register_cvar("csb_para_enabled", "1")
g_pFallSpeed = register_cvar("csb_para_fallspeed", "100")
g_pVipFlag = register_cvar("csb_para_vipflag", "")
g_pMaxPerRound = register_cvar("csb_para_maxperround", "0") /* 0 = unlimited */
register_forward(FM_PlayerPreThink, "fwPreThink")
register_event("ResetHUD", "evSpawn", "b")
register_event("HLTV", "evNewRound", "a", "1=0", "2=0")
}
public plugin_precache()
{
if (file_exists(PARA_MODEL))
{
precache_model(PARA_MODEL)
g_bModelOk = true
}
else
{
log_amx("[CSB Parachute] %s not found - the parachute will slow the fall but show no model.", PARA_MODEL)
}
}
public client_putinserver(id)
{
g_iChute[id] = 0
g_iUsed[id] = 0
}
public client_disconnected(id)
{
removeChute(id)
g_iChute[id] = 0
}
public evSpawn(id)
{
removeChute(id)
g_iUsed[id] = 0
}
public evNewRound()
{
for (new i = 1; i <= 32; i++)
g_iUsed[i] = 0
}
vipAllowed(id)
{
new flagStr[8]
get_pcvar_string(g_pVipFlag, flagStr, charsmax(flagStr))
if (!flagStr[0])
return 1
new need = read_flags(flagStr)
return (get_user_flags(id) & need) ? 1 : 0
}
public fwPreThink(id)
{
if (!get_pcvar_num(g_pEnabled) || !is_user_alive(id))
{
if (g_iChute[id])
removeChute(id)
return
}
new flags = pev(id, pev_flags)
if (flags & FL_ONGROUND)
{
if (g_iChute[id])
removeChute(id)
return
}
new Float:vel[3]
pev(id, pev_velocity, vel)
new button = pev(id, pev_button)
/* deploy only while +use is held and the player is actually descending */
if ((button & IN_USE) && vel[2] < 0.0 && vipAllowed(id))
{
new maxUse = get_pcvar_num(g_pMaxPerRound)
if (!g_iChute[id])
{
if (maxUse > 0 && g_iUsed[id] >= maxUse)
return
deployChute(id)
g_iUsed[id]++
}
new Float:fall = get_pcvar_float(g_pFallSpeed)
if (fall < 10.0)
fall = 10.0
vel[2] = -fall
set_pev(id, pev_velocity, vel)
}
else if (g_iChute[id])
{
removeChute(id)
}
}
deployChute(id)
{
if (!g_bModelOk)
{
g_iChute[id] = -1 /* marker: active but no visible model */
return
}
new ent = engfunc(EngFunc_CreateNamedEntity, engfunc(EngFunc_AllocString, "info_target"))
if (!pev_valid(ent))
{
g_iChute[id] = -1
return
}
engfunc(EngFunc_SetModel, ent, PARA_MODEL)
set_pev(ent, pev_movetype, MOVETYPE_FOLLOW)
set_pev(ent, pev_aiment, id)
set_pev(ent, pev_owner, id)
set_pev(ent, pev_sequence, 0)
set_pev(ent, pev_frame, 0.0)
set_pev(ent, pev_framerate, 1.0)
set_pev(ent, pev_animtime, get_gametime())
new Float:origin[3]
pev(id, pev_origin, origin)
set_pev(ent, pev_origin, origin)
g_iChute[id] = ent
}
removeChute(id)
{
new ent = g_iChute[id]
if (ent > 0 && pev_valid(ent))
engfunc(EngFunc_RemoveEntity, ent)
g_iChute[id] = 0
}