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