Promoveaza serverul
csb-checkpoints / addons / amxmodx / scripting / csb_checkpoints.sma
162 lines 4.4 KiB Pawn Raw Permalink History
/*
 * 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 <https://www.gnu.org/licenses/> for details.
 */

#include <amxmodx>
#include <fakemeta>

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