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