/* * CSB Cluster Grenade * Copyright (C) 2026 counter-strike-boost.com * * Upgrades the HE grenade into a cluster bomb: when the parent grenade goes off * it scatters N smaller bomblets with random velocities; each bomblet arms for a * short fuse and then explodes on its own, dealing scaled blast damage in a * radius. Great for chaos rounds and fun modes. * * Inspired by Cluster Grenade by VEN. 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 for details. */ #include #include #include new const PLUGIN[] = "CSB Cluster Grenade" new const VERSION[] = "1.0.0" new const AUTHOR[] = "counter-strike-boost.com" #define TE_EXPLOSION 3 #define BOMBLET_CLASS "csb_bomblet" new const g_szHeModel[] = "models/w_hegrenade.mdl" new const g_szBombletModel[] = "models/w_hegrenade.mdl" new g_pEnabled, g_pCount, g_pDamage, g_pRadius, g_pFuse new g_iSprExplode new g_iMaxPlayers public plugin_init() { register_plugin(PLUGIN, VERSION, AUTHOR) g_pEnabled = register_cvar("csb_cluster_enabled", "1") g_pCount = register_cvar("csb_cluster_count", "6") g_pDamage = register_cvar("csb_cluster_damage", "60") g_pRadius = register_cvar("csb_cluster_radius", "160") g_pFuse = register_cvar("csb_cluster_fuse", "1.0") register_forward(FM_SetModel, "fwSetModel") register_forward(FM_Think, "fwThink") g_iMaxPlayers = get_maxplayers() } public plugin_precache() { g_iSprExplode = precache_model("sprites/zerogxplode.spr") } public fwSetModel(ent, const model[]) { if (!get_pcvar_num(g_pEnabled) || !pev_valid(ent)) return FMRES_IGNORED if (!equal(model, g_szHeModel)) return FMRES_IGNORED new Float:fDmgTime pev(ent, pev_dmgtime, fDmgTime) /* fire the cluster split just as the parent detonates */ new Float:delay = fDmgTime - get_gametime() - 0.05 if (delay < 0.2) delay = 0.2 set_task(delay, "taskSplit", ent) return FMRES_IGNORED } public taskSplit(ent) { if (!pev_valid(ent)) return static Float:origin[3] pev(ent, pev_origin, origin) new owner = pev(ent, pev_owner) new count = get_pcvar_num(g_pCount) new Float:fuse = get_pcvar_float(g_pFuse) for (new i = 0; i < count; i++) spawnBomblet(origin, owner, fuse) } spawnBomblet(const Float:origin[3], owner, Float:fuse) { new ent = engfunc(EngFunc_CreateNamedEntity, engfunc(EngFunc_AllocString, "info_target")) if (!pev_valid(ent)) return set_pev(ent, pev_classname, BOMBLET_CLASS) engfunc(EngFunc_SetModel, ent, g_szBombletModel) set_pev(ent, pev_movetype, MOVETYPE_BOUNCE) set_pev(ent, pev_solid, SOLID_BBOX) set_pev(ent, pev_owner, owner) set_pev(ent, pev_gravity, 1.0) static Float:mins[3], Float:maxs[3] mins[0] = -2.0; mins[1] = -2.0; mins[2] = -2.0 maxs[0] = 2.0; maxs[1] = 2.0; maxs[2] = 2.0 engfunc(EngFunc_SetSize, ent, mins, maxs) static Float:spawn[3] spawn[0] = origin[0] spawn[1] = origin[1] spawn[2] = origin[2] + 8.0 engfunc(EngFunc_SetOrigin, ent, spawn) static Float:vel[3] vel[0] = float(random_num(-220, 220)) vel[1] = float(random_num(-220, 220)) vel[2] = float(random_num(150, 350)) set_pev(ent, pev_velocity, vel) set_pev(ent, pev_nextthink, get_gametime() + fuse + random_float(0.0, 0.4)) } public fwThink(ent) { if (!pev_valid(ent)) return FMRES_IGNORED static cls[32] pev(ent, pev_classname, cls, charsmax(cls)) if (!equal(cls, BOMBLET_CLASS)) return FMRES_IGNORED static origin[3], Float:fOrigin[3] pev(ent, pev_origin, fOrigin) origin[0] = floatround(fOrigin[0]) origin[1] = floatround(fOrigin[1]) origin[2] = floatround(fOrigin[2]) new owner = pev(ent, pev_owner) message_begin_f(MSG_PVS, SVC_TEMPENTITY, fOrigin) write_byte(TE_EXPLOSION) write_coord(origin[0]) write_coord(origin[1]) write_coord(origin[2]) write_short(g_iSprExplode) write_byte(10) write_byte(18) write_byte(0) message_end() blastDamage(fOrigin, owner) engfunc(EngFunc_RemoveEntity, ent) return FMRES_IGNORED } blastDamage(const Float:origin[3], owner) { new Float:radius = get_pcvar_float(g_pRadius) new Float:maxDmg = get_pcvar_float(g_pDamage) new attacker = (owner >= 1 && owner <= g_iMaxPlayers) ? owner : 0 for (new i = 1; i <= g_iMaxPlayers; i++) { if (!is_user_alive(i)) continue static Float:pOrigin[3] pev(i, pev_origin, pOrigin) new Float:dist = get_distance_f(origin, pOrigin) if (dist > radius) continue new Float:scale = 1.0 - (dist / radius) new Float:dmg = maxDmg * scale if (dmg < 1.0) continue new inflictor = (attacker > 0) ? attacker : i ExecuteHamB(Ham_TakeDamage, i, inflictor, inflictor, dmg, DMG_BLAST) } }