/* * CSB Chat Manager * Copyright (C) 2026 counter-strike-boost.com * * Full chat formatter. Every say / say_team line is rebuilt from a configurable * template made of tokens ({rank} {country} {vip} {admin} {team} {dead} {name} * {msg}) and re-broadcast as a coloured SayText message. The template is loaded * from a small ini in the amxx data dir (falling back to a sane default), rank * is derived from frags, country from the geoip module, and a swear-word filter * masks blacklisted words before the line goes out. * * Inspired by the classic Chat Manager (AMXX). Independent GPL re-implementation. * * 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 new const PLUGIN[] = "CSB Chat Manager" new const VERSION[] = "1.0.0" new const AUTHOR[] = "counter-strike-boost.com" #define VIP_FLAG ADMIN_LEVEL_H #define ADMIN_FLAG ADMIN_KICK new const g_szDefaultFmt[] = "{dead}{country} {vip}{admin}[{rank}] {name}: {msg}" new const g_szBadWords[][] = { "fuck", "shit", "bitch", "asshole", "nigger", "faggot", "cunt" } new g_pEnabled, g_pFilter, g_pVipTag, g_pAdminTag new g_szFmt[192] new g_iSayText public plugin_init() { register_plugin(PLUGIN, VERSION, AUTHOR) g_pEnabled = register_cvar("csb_chat_enabled", "1") g_pFilter = register_cvar("csb_chat_filter", "1") g_pVipTag = register_cvar("csb_chat_viptag", "*VIP*") g_pAdminTag = register_cvar("csb_chat_admintag", "@ADMIN") register_clcmd("say", "cmdSay") register_clcmd("say_team", "cmdSayTeam") g_iSayText = get_user_msgid("SayText") loadFormat() } loadFormat() { new dir[128] get_localinfo("amxx_datadir", dir, charsmax(dir)) new path[160] formatex(path, charsmax(path), "%s/csb_chatformat.ini", dir) if (file_exists(path)) { new line[192], len new f = fopen(path, "rt") while (f && !feof(f)) { fgets(f, line, charsmax(line)) trim(line) if (line[0] && line[0] != ';' && line[0] != '#') { copy(g_szFmt, charsmax(g_szFmt), line) len = 1 break } } if (f) fclose(f) if (len) return } copy(g_szFmt, charsmax(g_szFmt), g_szDefaultFmt) } public cmdSay(id) { return handle(id, false) } public cmdSayTeam(id) { return handle(id, true) } handle(id, bool:team) { if (!get_pcvar_num(g_pEnabled) || !is_user_connected(id)) return PLUGIN_CONTINUE new msg[160] read_args(msg, charsmax(msg)) remove_quotes(msg) trim(msg) if (!msg[0]) return PLUGIN_HANDLED if (get_pcvar_num(g_pFilter)) censor(msg) new out[192] build(id, msg, out, charsmax(out)) sendColored(id, out, team) return PLUGIN_HANDLED } build(id, const msg[], out[], len) { copy(out, len, g_szFmt) /* dead marker */ replace_string(out, len, "{dead}", is_user_alive(id) ? "" : "^x03*DEAD* ") /* rank from frags */ new frags = get_user_frags(id) new rank[16] if (frags >= 100) copy(rank, charsmax(rank), "Elite") else if (frags >= 40) copy(rank, charsmax(rank), "Veteran") else if (frags >= 10) copy(rank, charsmax(rank), "Regular") else copy(rank, charsmax(rank), "Rookie") replace_string(out, len, "{rank}", rank) /* country tag from geoip */ new ip[32], ccode[3] get_user_ip(id, ip, charsmax(ip), 1) ccode[0] = 0 if (ip[0]) geoip_code2(ip, ccode) if (!ccode[0]) copy(ccode, charsmax(ccode), "--") new country[8] formatex(country, charsmax(country), "[%s]", ccode) replace_string(out, len, "{country}", country) /* VIP / admin tags */ new flags = get_user_flags(id) new vip[24] vip[0] = 0 if (flags & VIP_FLAG) { new tag[16] get_pcvar_string(g_pVipTag, tag, charsmax(tag)) formatex(vip, charsmax(vip), "^x04%s^x01 ", tag) } replace_string(out, len, "{vip}", vip) new admin[24] admin[0] = 0 if (flags & ADMIN_FLAG) { new tag[16] get_pcvar_string(g_pAdminTag, tag, charsmax(tag)) formatex(admin, charsmax(admin), "^x03%s^x01 ", tag) } replace_string(out, len, "{admin}", admin) /* team */ new tname[8] switch (get_user_team(id)) { case 1: copy(tname, charsmax(tname), "T") case 2: copy(tname, charsmax(tname), "CT") default: copy(tname, charsmax(tname), "SPEC") } replace_string(out, len, "{team}", tname) /* name and message */ new name[32] get_user_name(id, name, charsmax(name)) replace_string(out, len, "{name}", name) replace_string(out, len, "{msg}", msg) } censor(msg[]) { for (new w = 0; w < sizeof(g_szBadWords); w++) { new wlen = strlen(g_szBadWords[w]) new start = 0, pos while ((pos = containi(msg[start], g_szBadWords[w])) != -1) { new at = start + pos for (new k = 0; k < wlen; k++) msg[at + k] = '*' start = at + wlen if (msg[start] == 0) break } } } sendColored(id, const text[], bool:team) { new players[32], num get_players(players, num, "ch") for (new i = 0; i < num; i++) { new dst = players[i] if (team && get_user_team(dst) != get_user_team(id)) continue message_begin(MSG_ONE, g_iSayText, _, dst) write_byte(id) write_string(text) message_end() } }