/*
* CSB Checkpoints
* Copyright (C) 2026 counter-strike-boost.com
*
* A lightweight KZ-style checkpoint system. /cp saves the player's origin,
* angles and velocity, /tp teleports back to the last saved point with
* set_pev + EngFunc_SetOrigin, and /gocheck cycles through every point saved on
* the current map. A cvar can force saving only while on the ground, and a HUD
* counter shows how many points are stored.
*
* Inspired by KZ / climb checkpoint 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
new const PLUGIN[] = "CSB Checkpoints"
new const VERSION[] = "1.0.0"
new const AUTHOR[] = "counter-strike-boost.com"
#define MAX_CP 8
#define FL_ONGROUND (1<<9)
new g_pEnabled, g_pGroundOnly
new Float:g_cpOrigin[33][MAX_CP][3]
new Float:g_cpAngles[33][MAX_CP][3]
new Float:g_cpVel[33][MAX_CP][3]
new g_cpCount[33]
new g_cpCycle[33]
public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)
g_pEnabled = register_cvar("csb_cp_enabled", "1")
g_pGroundOnly = register_cvar("csb_cp_groundonly", "1")
register_clcmd("say /cp", "cmdSave")
register_clcmd("say_team /cp", "cmdSave")
register_clcmd("say /tp", "cmdRestore")
register_clcmd("say_team /tp", "cmdRestore")
register_clcmd("say /gocheck", "cmdCycle")
register_clcmd("say_team /gocheck", "cmdCycle")
register_clcmd("say /clearcp", "cmdClear")
}
public client_putinserver(id)
{
g_cpCount[id] = 0
g_cpCycle[id] = 0
}
public cmdSave(id)
{
if (!get_pcvar_num(g_pEnabled) || !is_user_alive(id))
return PLUGIN_HANDLED
if (get_pcvar_num(g_pGroundOnly) && !(pev(id, pev_flags) & FL_ONGROUND))
{
client_print(id, print_chat, "[CSB] You must be on the ground to save a checkpoint.")
return PLUGIN_HANDLED
}
if (g_cpCount[id] >= MAX_CP)
{
/* drop the oldest by shifting down */
for (new i = 1; i < MAX_CP; i++)
{
copyVec(g_cpOrigin[id][i - 1], g_cpOrigin[id][i])
copyVec(g_cpAngles[id][i - 1], g_cpAngles[id][i])
copyVec(g_cpVel[id][i - 1], g_cpVel[id][i])
}
g_cpCount[id] = MAX_CP - 1
}
new slot = g_cpCount[id]
pev(id, pev_origin, g_cpOrigin[id][slot])
pev(id, pev_v_angle, g_cpAngles[id][slot])
pev(id, pev_velocity, g_cpVel[id][slot])
g_cpCount[id]++
g_cpCycle[id] = g_cpCount[id] - 1
showCount(id, "Checkpoint saved")
return PLUGIN_HANDLED
}
public cmdRestore(id)
{
if (!get_pcvar_num(g_pEnabled) || !is_user_alive(id))
return PLUGIN_HANDLED
if (g_cpCount[id] <= 0)
{
client_print(id, print_chat, "[CSB] No checkpoint saved yet. Use /cp first.")
return PLUGIN_HANDLED
}
teleport(id, g_cpCount[id] - 1)
showCount(id, "Teleported to last checkpoint")
return PLUGIN_HANDLED
}
public cmdCycle(id)
{
if (!get_pcvar_num(g_pEnabled) || !is_user_alive(id))
return PLUGIN_HANDLED
if (g_cpCount[id] <= 0)
{
client_print(id, print_chat, "[CSB] No checkpoints saved.")
return PLUGIN_HANDLED
}
g_cpCycle[id] = (g_cpCycle[id] + 1) % g_cpCount[id]
teleport(id, g_cpCycle[id])
client_print(id, print_chat, "[CSB] Checkpoint %d / %d.", g_cpCycle[id] + 1, g_cpCount[id])
return PLUGIN_HANDLED
}
public cmdClear(id)
{
g_cpCount[id] = 0
g_cpCycle[id] = 0
client_print(id, print_chat, "[CSB] Checkpoints cleared.")
return PLUGIN_HANDLED
}
teleport(id, slot)
{
if (slot < 0 || slot >= g_cpCount[id])
return
engfunc(EngFunc_SetOrigin, id, g_cpOrigin[id][slot])
set_pev(id, pev_angles, g_cpAngles[id][slot])
set_pev(id, pev_v_angle, g_cpAngles[id][slot])
set_pev(id, pev_fixangle, 1)
set_pev(id, pev_velocity, g_cpVel[id][slot])
}
copyVec(Float:dest[3], const Float:src[3])
{
dest[0] = src[0]
dest[1] = src[1]
dest[2] = src[2]
}
showCount(id, const what[])
{
client_print(id, print_chat, "[CSB] %s (%d stored).", what, g_cpCount[id])
}