/*
* CSB C4 Timer
* Copyright (C) 2026 counter-strike-boost.com
*
* Once the bomb is planted this shows a live HUD countdown of the exact seconds
* left, based on mp_c4timer and the moment of the plant. The colour ramps from
* green to red as the fuse burns down, and each CT is told whether they can
* still reach and defuse it in time given their defuse kit.
*
* Inspired by the classic "C4 Timer" plugins for AMX Mod X. 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 the GNU General
* Public License for more details: .
*/
#include
#include
new const PLUGIN[] = "CSB C4 Timer"
new const VERSION[] = "1.0.0"
new const AUTHOR[] = "counter-strike-boost.com"
/* stock defuse timings without / with a defuse kit */
#define DEFUSE_NOKIT 10.0
#define DEFUSE_KIT 5.0
new g_pEnabled, g_pBeep
new g_pC4Timer
new bool:g_bPlanted = false
new Float:g_flExplodeAt
new g_iSync
public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)
g_pEnabled = register_cvar("csb_c4timer_enabled", "1")
g_pBeep = register_cvar("csb_c4timer_beep", "1")
g_pC4Timer = get_cvar_pointer("mp_c4timer")
g_iSync = CreateHudSyncObj()
register_logevent("evPlanted", 3, "2=Planted_The_Bomb")
register_logevent("evDefused", 3, "2=Defused_The_Bomb")
/* new round resets everything (HLTV new-round message) */
register_event("HLTV", "evNewRound", "a", "1=0", "2=0")
register_event("TextMsg", "evRestart", "a", "2=#Game_will_restart_in")
}
public evPlanted()
{
if (!get_pcvar_num(g_pEnabled))
return
new Float:c4timer = g_pC4Timer ? get_pcvar_float(g_pC4Timer) : 45.0
if (c4timer <= 0.0)
c4timer = 45.0
g_bPlanted = true
g_flExplodeAt = get_gametime() + c4timer
set_task(0.2, "taskTick", 0, _, _, "b")
}
public evDefused()
{
stopTimer()
}
public evNewRound()
{
stopTimer()
}
public evRestart()
{
stopTimer()
}
stopTimer()
{
if (!g_bPlanted)
return
g_bPlanted = false
remove_task(0)
ClearSyncHud(0, g_iSync)
}
public taskTick()
{
if (!g_bPlanted)
{
remove_task(0)
return
}
new Float:remain = g_flExplodeAt - get_gametime()
if (remain <= 0.0)
{
stopTimer()
return
}
new r, g, b
fuseColor(remain, r, g, b)
/* the bomb is a shared threat - draw it to everyone, then add the
personal defuse note only for living CTs */
new players[32], num, pid
get_players(players, num, "ch")
new bars[16]
beepBars(remain, bars, charsmax(bars))
for (new i = 0; i < num; i++)
{
pid = players[i]
new note[64]
note[0] = 0
if (is_user_alive(pid) && cs_get_user_team(pid) == CS_TEAM_CT)
defuseNote(pid, remain, note, charsmax(note))
set_hudmessage(r, g, b, 0.02, 0.20, 0, 0.0, 0.3, 0.0, 0.0, -1)
if (note[0])
ShowSyncHudMsg(pid, g_iSync, "C4: %.1f %s^n%s", remain, bars, note)
else
ShowSyncHudMsg(pid, g_iSync, "C4: %.1f %s", remain, bars)
}
}
fuseColor(Float:remain, &r, &g, &b)
{
if (remain <= 5.0) { r = 255; g = 0; b = 0; }
else if (remain <= 15.0) { r = 255; g = 140; b = 0; }
else { r = 0; g = 220; b = 0; }
}
beepBars(Float:remain, out[], len)
{
if (!get_pcvar_num(g_pBeep))
{
out[0] = 0
return
}
/* more filled bars the closer the explosion is */
new filled
if (remain <= 5.0) filled = 5
else if (remain <= 10.0) filled = 4
else if (remain <= 20.0) filled = 3
else if (remain <= 30.0) filled = 2
else filled = 1
new p = 0
for (new i = 0; i < 5 && p < len - 1; i++)
out[p++] = (i < filled) ? '|' : '.'
out[p] = 0
}
defuseNote(id, Float:remain, out[], len)
{
new Float:need = cs_get_user_defuse(id) ? DEFUSE_KIT : DEFUSE_NOKIT
if (remain >= need)
formatex(out, len, "Defusable (%.0fs needed)", need)
else
formatex(out, len, "TOO LATE to defuse")
}