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