/*
* CSB Rules Menu
* Copyright (C) 2026 counter-strike-boost.com
*
* "say /rules" opens a menu of the server rules, loaded from an ini split into
* [Section] blocks. Selecting a section shows its text in an MOTD window. Players
* who just connected get a short one-line reminder to read the rules, and admins
* can reload the ini without changing the map with amx_reloadrules.
*
* 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 Rules Menu"
new const VERSION[] = "1.0.0"
new const AUTHOR[] = "counter-strike-boost.com"
#define MAX_SECTIONS 16
new g_szTitle[MAX_SECTIONS][48]
new g_szBody[MAX_SECTIONS][512]
new g_iSections
public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)
register_clcmd("say /rules", "cmdRules")
register_clcmd("say_team /rules", "cmdRules")
register_concmd("amx_reloadrules", "cmdReload", ADMIN_CFG, "- reload the server rules ini")
}
public plugin_cfg()
{
loadRules()
}
loadRules()
{
g_iSections = 0
new cfgdir[128], path[160]
get_localinfo("amxx_configsdir", cfgdir, charsmax(cfgdir))
formatex(path, charsmax(path), "%s/csb_rules.ini", cfgdir)
if (!file_exists(path))
{
/* fall back to a built-in rule set so the menu is never empty */
copy(g_szTitle[0], charsmax(g_szTitle[]), "General")
copy(g_szBody[0], charsmax(g_szBody[]),
"1. Respect all players and admins.^n2. No cheating of any kind.^n3. No advertising or spam.^n4. Admin decisions are final.")
g_iSections = 1
return
}
new f = fopen(path, "rt")
if (!f)
return
new line[256], cur = -1
while (!feof(f) && g_iSections < MAX_SECTIONS)
{
fgets(f, line, charsmax(line))
trim(line)
if (!line[0] || line[0] == ';' || line[0] == '#')
continue
if (line[0] == '[')
{
new title[48]
copy(title, charsmax(title), line[1])
new close = contain(title, "]")
if (close != -1)
title[close] = 0
trim(title)
cur = g_iSections
copy(g_szTitle[cur], charsmax(g_szTitle[]), title)
g_szBody[cur][0] = 0
g_iSections++
}
else if (cur >= 0)
{
if (g_szBody[cur][0])
add(g_szBody[cur], charsmax(g_szBody[]), "^n")
add(g_szBody[cur], charsmax(g_szBody[]), line)
}
}
fclose(f)
if (!g_iSections)
{
copy(g_szTitle[0], charsmax(g_szTitle[]), "General")
copy(g_szBody[0], charsmax(g_szBody[]), "Be excellent to each other.")
g_iSections = 1
}
}
public client_putinserver(id)
{
set_task(12.0, "taskReminder", id)
}
public taskReminder(id)
{
if (is_user_connected(id))
client_print(id, print_chat, "[CSB] Type /rules to read the server rules.")
}
public cmdRules(id)
{
new menu = menu_create("\yServer Rules\w - pick a section:", "rulesHandler")
new info[8]
for (new i = 0; i < g_iSections; i++)
{
num_to_str(i, info, charsmax(info))
menu_additem(menu, g_szTitle[i], info, 0)
}
menu_setprop(menu, MPROP_EXIT, MEXIT_ALL)
menu_display(id, menu, 0)
return PLUGIN_HANDLED
}
public rulesHandler(id, menu, item)
{
if (item == MENU_EXIT)
{
menu_destroy(menu)
return PLUGIN_HANDLED
}
new info[8], name[48], access, callback
menu_item_getinfo(menu, item, access, info, charsmax(info), name, charsmax(name), callback)
menu_destroy(menu)
new sec = str_to_num(info)
if (sec < 0 || sec >= g_iSections)
return PLUGIN_HANDLED
new head[64]
formatex(head, charsmax(head), "Rules - %s", g_szTitle[sec])
show_motd(id, g_szBody[sec], head)
return PLUGIN_HANDLED
}
public cmdReload(id, level, cid)
{
if (!cmd_access(id, level, cid, 1))
return PLUGIN_HANDLED
loadRules()
console_print(id, "[CSB] Reloaded %d rule section(s).", g_iSections)
client_print(id, print_chat, "[CSB] Rules reloaded (%d sections).", g_iSections)
return PLUGIN_HANDLED
}