/*
* CSB Killstreak Announcer
* Copyright (C) 2026 counter-strike-boost.com
*
* Counts consecutive kills without dying and, at configurable thresholds,
* plays a Quake-style sound and shows a HUD message to everyone. The streak
* resets on death or on a new round. Thresholds, sounds and text are read
* from addons/amxmodx/configs/csb_killstreak.ini (a sane default table is
* built in if the file is missing).
*
* Inspired by the classic "Killing Streak" plugin by KaOs. 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. See for details.
*/
#include
new const PLUGIN[] = "CSB Killstreak Announcer"
new const VERSION[] = "1.0.0"
new const AUTHOR[] = "counter-strike-boost.com"
#define MAX_TIERS 16
#define INI_FILE "csb_killstreak.ini"
new g_pEnabled, g_pHudR, g_pHudG, g_pHudB
new g_iStreak[33]
new g_iTierCount = 0
new g_iTierKills[MAX_TIERS]
new g_szTierSound[MAX_TIERS][64]
new g_szTierText[MAX_TIERS][48]
public plugin_precache()
{
loadTiers()
for (new i = 0; i < g_iTierCount; i++)
if (g_szTierSound[i][0])
precache_sound(g_szTierSound[i])
}
public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)
g_pEnabled = register_cvar("csb_killstreak_enabled", "1")
g_pHudR = register_cvar("csb_killstreak_r", "255")
g_pHudG = register_cvar("csb_killstreak_g", "160")
g_pHudB = register_cvar("csb_killstreak_b", "0")
register_event("DeathMsg", "evDeath", "a")
register_logevent("evRoundStart", 2, "1=Round_Start")
}
/* Build the default tier table, then let the ini override it if present. */
loadTiers()
{
g_iTierCount = 0
addTier(3, "misc/csb_streak3.wav", "TRIPLE KILL")
addTier(5, "misc/csb_streak5.wav", "RAMPAGE")
addTier(7, "misc/csb_streak7.wav", "DOMINATING")
addTier(10, "misc/csb_streak10.wav", "UNSTOPPABLE")
addTier(15, "misc/csb_streak15.wav", "GODLIKE")
/* read_file / file_exists are relative to the mod (cstrike) directory */
new path[128]
formatex(path, charsmax(path), "addons/amxmodx/configs/%s", INI_FILE)
if (!file_exists(path))
return
/* file present: replace the built-in table with its contents */
g_iTierCount = 0
new line[160], kills[8], sound[64], text[48], txtlen
new i = 0
while (read_file(path, i++, line, charsmax(line), txtlen))
{
trim(line)
if (!line[0] || line[0] == ';' || line[0] == '#')
continue
/* format: */
new pos = 0
pos = argParse(line, pos, kills, charsmax(kills))
pos = argParse(line, pos, sound, charsmax(sound))
/* the rest of the line is the message */
while (line[pos] == ' ') pos++
copy(text, charsmax(text), line[pos])
trim(text)
new k = str_to_num(kills)
if (k > 0 && sound[0])
addTier(k, sound, text[0] ? text : "KILLING SPREE")
}
}
/* Read one whitespace-separated token starting at 'start'; return new index. */
argParse(const src[], start, out[], len)
{
new i = start, j = 0
while (src[i] == ' ') i++
while (src[i] && src[i] != ' ' && j < len)
out[j++] = src[i++]
out[j] = 0
return i
}
addTier(kills, const sound[], const text[])
{
if (g_iTierCount >= MAX_TIERS)
return
g_iTierKills[g_iTierCount] = kills
copy(g_szTierSound[g_iTierCount], charsmax(g_szTierSound[]), sound)
copy(g_szTierText[g_iTierCount], charsmax(g_szTierText[]), text)
g_iTierCount++
}
public evDeath()
{
new killer = read_data(1)
new victim = read_data(2)
if (victim >= 1 && victim <= 32)
g_iStreak[victim] = 0
if (!get_pcvar_num(g_pEnabled))
return
/* ignore suicides and team-independent self-kills */
if (killer < 1 || killer > 32 || killer == victim)
return
if (!is_user_connected(killer))
return
g_iStreak[killer]++
for (new i = 0; i < g_iTierCount; i++)
{
if (g_iStreak[killer] == g_iTierKills[i])
{
announce(killer, i)
break
}
}
}
public evRoundStart()
{
for (new id = 1; id <= 32; id++)
g_iStreak[id] = 0
}
public client_disconnected(id)
{
g_iStreak[id] = 0
}
announce(killer, tier)
{
new name[32]
get_user_name(killer, name, charsmax(name))
if (g_szTierSound[tier][0])
{
new players[32], num
get_players(players, num, "ch")
for (new i = 0; i < num; i++)
emit_sound(players[i], CHAN_AUTO, g_szTierSound[tier], 1.0, ATTN_NORM, 0, PITCH_NORM)
}
new r = get_pcvar_num(g_pHudR)
new g = get_pcvar_num(g_pHudG)
new b = get_pcvar_num(g_pHudB)
set_hudmessage(r, g, b, -1.0, 0.25, 0, 0.0, 3.0, 0.1, 0.1, -1)
show_hudmessage(0, "%s - %s (%d kills)!", name, g_szTierText[tier], g_iStreak[killer])
client_print_color(0, print_team_default,
"^x04[CSB]^x03 %s^x01 is on a^x04 %s^x01 (^x04%d^x01 kills)!",
name, g_szTierText[tier], g_iStreak[killer])
}