/*
* CSB Adverts
* Copyright (C) 2026 counter-strike-boost.com
*
* Rotating server messages read from a plain-text file and shown every N
* seconds as coloured chat, a HUD message or a DHUD message. Each line carries
* its own display type and may contain placeholders that are expanded at show
* time: {HOSTNAME} {TIMELEFT} {NEXTMAP} {PLAYERS} {MAXPLAYERS} {MAP} {NAME}.
* A per-map file is loaded automatically when it exists, and every player can
* mute the adverts for himself with "say /noads".
*
* Inspired by the classic "Advertisements" plugins for AMX Mod X. 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. 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
new const PLUGIN[] = "CSB Adverts"
new const VERSION[] = "1.0.0"
new const AUTHOR[] = "counter-strike-boost.com"
#define MAX_ADS 48
#define MAX_AD_LEN 192
new g_szAds[MAX_ADS][MAX_AD_LEN]
new g_iAdType[MAX_ADS] /* 0 = chat, 1 = hud, 2 = dhud */
new g_iNumAds
new g_iCurrent
new bool:g_bMuted[33]
new g_pEnabled, g_pInterval, g_pRandom, g_pPrefix
public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)
g_pEnabled = register_cvar("csb_adverts", "1")
g_pInterval = register_cvar("csb_adverts_interval", "45")
g_pRandom = register_cvar("csb_adverts_random", "0")
g_pPrefix = register_cvar("csb_adverts_prefix", "[Server]")
register_clcmd("say /noads", "cmdToggle")
register_clcmd("say_team /noads", "cmdToggle")
}
public plugin_cfg()
{
loadAds()
new Float:interval = get_pcvar_float(g_pInterval)
if (interval < 5.0)
interval = 5.0
set_task(interval, "showNextAd", 0, _, _, "b")
}
public client_putinserver(id)
{
g_bMuted[id] = false
}
public cmdToggle(id)
{
g_bMuted[id] = !g_bMuted[id]
client_print(id, print_chat, "[CSB] Adverts are now %s for you.", g_bMuted[id] ? "OFF" : "ON")
return PLUGIN_HANDLED
}
loadAds()
{
g_iNumAds = 0
g_iCurrent = 0
new dir[128]
get_localinfo("amxx_configsdir", dir, charsmax(dir))
new map[32]
get_mapname(map, charsmax(map))
/* prefer a per-map file, fall back to the global one */
new path[160]
formatex(path, charsmax(path), "%s/csb_adverts_%s.ini", dir, map)
if (!file_exists(path))
formatex(path, charsmax(path), "%s/csb_adverts.ini", dir)
if (!file_exists(path))
{
writeDefaults(path)
}
new fp = fopen(path, "rt")
if (!fp)
{
log_amx("[CSB Adverts] could not open %s", path)
return
}
new line[MAX_AD_LEN + 16]
while (!feof(fp) && g_iNumAds < MAX_ADS)
{
fgets(fp, line, charsmax(line))
trim(line)
if (!line[0] || line[0] == ';' || (line[0] == '/' && line[1] == '/'))
continue
new type = 0
new start = 0
if (equali(line, "hud:", 4)) { type = 1; start = 4; }
else if (equali(line, "dhud:", 5)) { type = 2; start = 5; }
else if (equali(line, "chat:", 5)) { type = 0; start = 5; }
new msg[MAX_AD_LEN]
copy(msg, charsmax(msg), line[start])
trim(msg)
if (!msg[0])
continue
copy(g_szAds[g_iNumAds], charsmax(g_szAds[]), msg)
g_iAdType[g_iNumAds] = type
g_iNumAds++
}
fclose(fp)
log_amx("[CSB Adverts] loaded %d advert(s) from %s", g_iNumAds, path)
}
writeDefaults(const path[])
{
new fp = fopen(path, "wt")
if (!fp)
return
fputs(fp, "; CSB Adverts - one message per line.^n")
fputs(fp, "; Prefix a line with chat:, hud: or dhud: to pick how it is shown.^n")
fputs(fp, "; Placeholders: {HOSTNAME} {TIMELEFT} {NEXTMAP} {PLAYERS} {MAXPLAYERS} {MAP} {NAME}^n")
fputs(fp, "chat: Welcome to {HOSTNAME}, {NAME}! There are {PLAYERS}/{MAXPLAYERS} players online.^n")
fputs(fp, "chat: Current map is {MAP} - {TIMELEFT} left. Next map: {NEXTMAP}.^n")
fputs(fp, "dhud: Type /noads to hide these messages.^n")
fclose(fp)
}
public showNextAd()
{
if (!get_pcvar_num(g_pEnabled) || g_iNumAds < 1)
return
if (!get_playersnum())
return
new idx
if (get_pcvar_num(g_pRandom))
idx = random_num(0, g_iNumAds - 1)
else
{
idx = g_iCurrent
g_iCurrent = (g_iCurrent + 1) % g_iNumAds
}
new players[32], num
get_players(players, num, "ch")
for (new i = 0; i < num; i++)
{
new id = players[i]
if (g_bMuted[id])
continue
new msg[MAX_AD_LEN]
buildMessage(id, g_szAds[idx], msg, charsmax(msg))
switch (g_iAdType[idx])
{
case 1:
{
set_hudmessage(0, 160, 255, -1.0, 0.18, 0, 0.0, 6.0, 0.2, 0.2, -1)
show_hudmessage(id, "%s", msg)
}
case 2:
{
show_dhudmessage(id, "%s", msg)
}
default:
{
new prefix[32], out[MAX_AD_LEN + 40]
get_pcvar_string(g_pPrefix, prefix, charsmax(prefix))
formatex(out, charsmax(out), "^x04%s^x01 %s", prefix, msg)
client_print_color(id, print_team_default, "%s", out)
}
}
}
}
buildMessage(id, const src[], out[], len)
{
copy(out, len, src)
new host[64], nextmap[32], map[32], name[32]
new tl[16], pc[8], mx[8]
get_cvar_string("hostname", host, charsmax(host))
get_cvar_string("amx_nextmap", nextmap, charsmax(nextmap))
if (!nextmap[0])
copy(nextmap, charsmax(nextmap), "unknown")
get_mapname(map, charsmax(map))
get_user_name(id, name, charsmax(name))
new left = get_timeleft()
if (left > 0)
formatex(tl, charsmax(tl), "%d:%02d", left / 60, left % 60)
else
copy(tl, charsmax(tl), "no limit")
num_to_str(get_playersnum(), pc, charsmax(pc))
num_to_str(get_maxplayers(), mx, charsmax(mx))
replace_string(out, len, "{HOSTNAME}", host)
replace_string(out, len, "{NEXTMAP}", nextmap)
replace_string(out, len, "{TIMELEFT}", tl)
replace_string(out, len, "{PLAYERS}", pc)
replace_string(out, len, "{MAXPLAYERS}", mx)
replace_string(out, len, "{MAP}", map)
replace_string(out, len, "{NAME}", name)
}