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