/*
* CSB Bomb Status
* Copyright (C) 2026 counter-strike-boost.com
*
* Announces and shows, in a HUD line, which bombsite the C4 was planted at
* (worked out from the planted bomb origin versus the map's func_bomb_target
* entities), the live countdown against mp_c4timer, and whether the bomb is
* being defused (with or without a kit) and by whom. Everything is worked out
* server-side from the planted entity and the standard bomb log events.
*
* 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
#include
new const PLUGIN[] = "CSB Bomb Status"
new const VERSION[] = "1.0.0"
new const AUTHOR[] = "counter-strike-boost.com"
#define MAX_SITES 8
new g_pEnabled, g_pC4Timer
new bool:g_bPlanted
new Float:g_fPlantTime
new g_szSite[8]
new Float:g_fSiteOrigin[MAX_SITES][3]
new g_szSiteLabel[MAX_SITES][4]
new g_iSiteCount
new g_iSync
public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)
g_pEnabled = register_cvar("csb_bomb_status", "1")
g_pC4Timer = get_cvar_pointer("mp_c4timer")
if (g_pC4Timer == 0)
g_pC4Timer = register_cvar("mp_c4timer", "45")
register_logevent("logPlanted", 3, "2=Planted_The_Bomb")
register_logevent("logDefusedOk", 3, "2=Defused_The_Bomb")
register_logevent("logDefuseKit", 3, "2=Begin_Bomb_Defuse_With_A_Defuse_Kit")
register_logevent("logDefuseNoKit", 3, "2=Begin_Bomb_Defuse_Without_Kit")
register_logevent("logRoundStart", 2, "1=Round_Start")
register_event("HLTV", "eventNewRound", "a", "1=0", "2=0")
g_iSync = CreateHudSyncObj()
set_task(0.5, "taskHud", 71, _, _, "b")
}
public plugin_cfg()
{
indexSites()
}
/* collect func_bomb_target origins once and label them A / B / ... by X order */
indexSites()
{
g_iSiteCount = 0
new ent = -1
while ((ent = engfunc(EngFunc_FindEntityByString, ent, "classname", "func_bomb_target")) > 0)
{
if (g_iSiteCount >= MAX_SITES)
break
static Float:fMin[3], Float:fMax[3]
pev(ent, pev_absmin, fMin)
pev(ent, pev_absmax, fMax)
g_fSiteOrigin[g_iSiteCount][0] = (fMin[0] + fMax[0]) * 0.5
g_fSiteOrigin[g_iSiteCount][1] = (fMin[1] + fMax[1]) * 0.5
g_fSiteOrigin[g_iSiteCount][2] = (fMin[2] + fMax[2]) * 0.5
g_iSiteCount++
}
/* simple insertion sort by X so the westmost site becomes A */
for (new i = 1; i < g_iSiteCount; i++)
{
for (new j = i; j > 0; j--)
{
if (g_fSiteOrigin[j][0] < g_fSiteOrigin[j - 1][0])
{
static Float:tmp[3]
tmp[0] = g_fSiteOrigin[j][0]
tmp[1] = g_fSiteOrigin[j][1]
tmp[2] = g_fSiteOrigin[j][2]
g_fSiteOrigin[j][0] = g_fSiteOrigin[j - 1][0]
g_fSiteOrigin[j][1] = g_fSiteOrigin[j - 1][1]
g_fSiteOrigin[j][2] = g_fSiteOrigin[j - 1][2]
g_fSiteOrigin[j - 1][0] = tmp[0]
g_fSiteOrigin[j - 1][1] = tmp[1]
g_fSiteOrigin[j - 1][2] = tmp[2]
}
else
{
break
}
}
}
for (new i = 0; i < g_iSiteCount; i++)
formatex(g_szSiteLabel[i], charsmax(g_szSiteLabel[]), "%c", 'A' + i)
}
findBombOrigin(Float:out[3])
{
new ent = -1
while ((ent = engfunc(EngFunc_FindEntityByString, ent, "classname", "grenade")) > 0)
{
new mdl[48]
pev(ent, pev_model, mdl, charsmax(mdl))
if (containi(mdl, "c4") != -1)
{
pev(ent, pev_origin, out)
return 1
}
}
return 0
}
public logPlanted()
{
if (!get_pcvar_num(g_pEnabled))
return
g_bPlanted = true
g_fPlantTime = get_gametime()
static Float:fBomb[3]
if (findBombOrigin(fBomb) && g_iSiteCount > 0)
{
new best = 0
new Float:bestDist = 999999.0
for (new i = 0; i < g_iSiteCount; i++)
{
new Float:d = vector_distance(fBomb, g_fSiteOrigin[i])
if (d < bestDist)
{
bestDist = d
best = i
}
}
copy(g_szSite, charsmax(g_szSite), g_szSiteLabel[best])
}
else
{
copy(g_szSite, charsmax(g_szSite), "?")
}
client_print(0, print_chat, "[CSB] The bomb has been planted at bombsite %s!", g_szSite)
}
public logDefuseKit()
{
announceDefuse(true)
}
public logDefuseNoKit()
{
announceDefuse(false)
}
announceDefuse(bool:kit)
{
if (!g_bPlanted)
return
new defuser = nearestDefuser()
new need = kit ? 5 : 10
new left = timeLeft()
if (defuser)
{
new name[32]
get_user_name(defuser, name, charsmax(name))
client_print(0, print_chat, "[CSB] %s is defusing (%s) - needs %ds, %ds on the timer.",
name, kit ? "kit" : "no kit", need, left)
}
else
{
client_print(0, print_chat, "[CSB] Someone is defusing (%s) - needs %ds, %ds on the timer.",
kit ? "kit" : "no kit", need, left)
}
}
nearestDefuser()
{
static Float:fBomb[3]
if (!findBombOrigin(fBomb))
return 0
new players[32], num
get_players(players, num, "aeh", "CT")
new best = 0
new Float:bestDist = 200.0
for (new i = 0; i < num; i++)
{
static Float:fP[3]
pev(players[i], pev_origin, fP)
new Float:d = vector_distance(fP, fBomb)
if (d < bestDist)
{
bestDist = d
best = players[i]
}
}
return best
}
timeLeft()
{
new Float:total = get_pcvar_float(g_pC4Timer)
new Float:left = total - (get_gametime() - g_fPlantTime)
if (left < 0.0)
left = 0.0
return floatround(left, floatround_ceil)
}
public logDefusedOk()
{
if (!g_bPlanted)
return
g_bPlanted = false
client_print(0, print_chat, "[CSB] The bomb at site %s has been defused.", g_szSite)
}
public logRoundStart()
{
g_bPlanted = false
}
public eventNewRound()
{
g_bPlanted = false
}
public taskHud()
{
if (!get_pcvar_num(g_pEnabled) || !g_bPlanted)
return
new left = timeLeft()
if (left <= 0)
{
g_bPlanted = false
return
}
set_hudmessage(255, 60, 60, -1.0, 0.15, 0, 0.0, 0.6, 0.0, 0.0, -1)
ShowSyncHudMsg(0, g_iSync, "BOMB @ %s %d s", g_szSite, left)
}