/* * CSB Player Trails * Copyright (C) 2026 counter-strike-boost.com * * Attaches a coloured beam-follow trail to a player. A colour is picked from a * menu (VIP/shop gated) and the TE_BEAMFOLLOW temp entity is re-sent on a task * so the trail persists as the player moves. Trails are cleared on death and at * round start so the arena does not fill up with streaks. * * 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 new const PLUGIN[] = "CSB Player Trails" new const VERSION[] = "1.0.0" new const AUTHOR[] = "counter-strike-boost.com" #define ACCESS_FLAG ADMIN_LEVEL_H new const g_szColorName[][] = { "Off", "Red", "Green", "Blue", "Yellow", "Cyan", "Purple", "White" } new const g_iColor[][3] = { { 0, 0, 0 }, { 255, 0, 0 }, { 0, 255, 0 }, { 40, 90, 255 }, { 255, 255, 0 }, { 0, 255, 255 }, { 200, 0, 255 }, { 255, 255, 255 } } new g_pEnabled new g_iTrailSpr new g_iChoice[33] public plugin_init() { register_plugin(PLUGIN, VERSION, AUTHOR) g_pEnabled = register_cvar("csb_trails_enabled", "1") register_clcmd("say /trail", "cmdMenu") register_clcmd("say_team /trail", "cmdMenu") register_event("DeathMsg", "eventDeath", "a", "1>0") register_logevent("eventRoundStart", 2, "1=Round_Start") set_task(1.0, "taskTrails", 0, _, _, "b") } public plugin_precache() { g_iTrailSpr = precache_model("sprites/laserbeam.spr") } public client_putinserver(id) { g_iChoice[id] = 0 } bool:hasAccess(id) { return (get_user_flags(id) & ACCESS_FLAG) != 0 } public cmdMenu(id) { if (!get_pcvar_num(g_pEnabled)) return PLUGIN_HANDLED if (!hasAccess(id)) { client_print(id, print_chat, "[CSB] Trails are a VIP feature.") return PLUGIN_HANDLED } new menu = menu_create("\yCSB Trail^n\wPick a colour:", "menuHandler") new info[8] for (new i = 0; i < sizeof(g_szColorName); i++) { num_to_str(i, info, charsmax(info)) menu_additem(menu, g_szColorName[i], info, 0) } menu_setprop(menu, MPROP_EXIT, MEXIT_ALL) menu_display(id, menu, 0) return PLUGIN_HANDLED } public menuHandler(id, menu, item) { if (item == MENU_EXIT) { menu_destroy(menu) return PLUGIN_HANDLED } new info[8], name[32], access, callback menu_item_getinfo(menu, item, access, info, charsmax(info), name, charsmax(name), callback) menu_destroy(menu) g_iChoice[id] = clamp(str_to_num(info), 0, sizeof(g_szColorName) - 1) killTrail(id) if (g_iChoice[id] == 0) client_print(id, print_chat, "[CSB] Trail removed.") else { client_print(id, print_chat, "[CSB] Trail set to %s.", g_szColorName[g_iChoice[id]]) if (is_user_alive(id)) sendTrail(id) } return PLUGIN_HANDLED } public eventDeath() { new victim = read_data(2) if (victim >= 1 && victim <= 32) killTrail(victim) } public eventRoundStart() { new players[32], num get_players(players, num, "c") for (new i = 0; i < num; i++) killTrail(players[i]) } sendTrail(id) { if (!is_user_alive(id) || g_iChoice[id] == 0) return new c = g_iChoice[id] message_begin(MSG_BROADCAST, SVC_TEMPENTITY) write_byte(TE_BEAMFOLLOW) write_short(id) write_short(g_iTrailSpr) write_byte(20) /* life in 0.1s */ write_byte(6) /* width */ write_byte(g_iColor[c][0]) write_byte(g_iColor[c][1]) write_byte(g_iColor[c][2]) write_byte(180) /* brightness */ message_end() } killTrail(id) { /* a zero-life beam follow on the entity clears any active one */ message_begin(MSG_BROADCAST, SVC_TEMPENTITY) write_byte(TE_KILLBEAM) write_short(id) message_end() } public taskTrails() { new players[32], num get_players(players, num, "a") for (new i = 0; i < num; i++) { new id = players[i] if (g_iChoice[id] > 0) sendTrail(id) } }