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