One-command setup (SteamCMD + pinned, checksum-verified ReHLDS / ReGameDLL / Metamod-R / AMXX / ReAPI / ReUnion / CSDM-ReAPI / YaPB / GunGame), .env settings, ./cs menu (map, DM/GunGame mode, bots, kill counter, restart, update), own plugins (extras, rtv, mode switch), all-time kill counter, watchdog + nightly backup, docs for admins and players. Co-Authored-By: Claude Opus 5.5 <[email protected]>
198 lines
4.9 KiB
Plaintext
198 lines
4.9 KiB
Plaintext
// rtv.sma - minimal "rock the vote" for cs16-dm-server.
|
|
// Players type rtv (or /rtv, rockthevote). When amx_rtv_ratio of the players
|
|
// agree, a 15s vote between up to 5 random mapcycle maps starts, then the map changes.
|
|
// Blocked early in a map (amx_rtv_delay) and near map end, where mapchooser's own vote runs.
|
|
|
|
#include <amxmodx>
|
|
|
|
#define VOTE_MAPS 5
|
|
#define VOTE_TIME 15
|
|
#define MENU_KEYS (MENU_KEY_1|MENU_KEY_2|MENU_KEY_3|MENU_KEY_4|MENU_KEY_5)
|
|
|
|
new bool:g_rocked[MAX_PLAYERS + 1]
|
|
new g_rocks
|
|
new bool:g_voting, bool:g_finished
|
|
new g_choices[VOTE_MAPS][32], g_numChoices, g_count[VOTE_MAPS]
|
|
new Float:g_mapStart
|
|
new g_pRatio, g_pDelay
|
|
|
|
public plugin_init()
|
|
{
|
|
register_plugin("RTV", "1.0", "cs16-dm-server")
|
|
|
|
register_clcmd("say rtv", "CmdRtv")
|
|
register_clcmd("say /rtv", "CmdRtv")
|
|
register_clcmd("say rockthevote", "CmdRtv")
|
|
register_clcmd("say_team rtv", "CmdRtv")
|
|
register_clcmd("say_team /rtv", "CmdRtv")
|
|
|
|
g_pRatio = register_cvar("amx_rtv_ratio", "0.6")
|
|
g_pDelay = register_cvar("amx_rtv_delay", "120")
|
|
|
|
register_menucmd(register_menuid("RTV Vote"), MENU_KEYS, "VoteHandler")
|
|
|
|
g_mapStart = get_gametime()
|
|
}
|
|
|
|
CountPlayers()
|
|
{
|
|
new players[MAX_PLAYERS], num
|
|
get_players(players, num, "ch")
|
|
return num
|
|
}
|
|
|
|
RocksNeeded()
|
|
{
|
|
new need = floatround(float(CountPlayers()) * get_pcvar_float(g_pRatio), floatround_ceil)
|
|
return need < 1 ? 1 : need
|
|
}
|
|
|
|
public CmdRtv(id)
|
|
{
|
|
if (g_voting || g_finished)
|
|
{
|
|
client_print_color(id, print_team_default, "^4[RTV]^1 A map vote already ran on this map.")
|
|
return PLUGIN_CONTINUE
|
|
}
|
|
|
|
new wait = get_pcvar_num(g_pDelay) - floatround(get_gametime() - g_mapStart)
|
|
if (wait > 0)
|
|
{
|
|
client_print_color(id, print_team_default, "^4[RTV]^1 Too early, try again in ^3%d^1 seconds.", wait)
|
|
return PLUGIN_CONTINUE
|
|
}
|
|
|
|
if (get_cvar_float("mp_timelimit") > 0.0 && get_timeleft() < 180)
|
|
{
|
|
client_print_color(id, print_team_default, "^4[RTV]^1 The end-of-map vote is about to start.")
|
|
return PLUGIN_CONTINUE
|
|
}
|
|
|
|
if (g_rocked[id])
|
|
{
|
|
client_print_color(id, print_team_default, "^4[RTV]^1 You already voted (^3%d^1/^3%d^1).", g_rocks, RocksNeeded())
|
|
return PLUGIN_CONTINUE
|
|
}
|
|
|
|
g_rocked[id] = true
|
|
g_rocks++
|
|
|
|
new name[32]
|
|
get_user_name(id, name, charsmax(name))
|
|
client_print_color(0, id, "^4[RTV]^3 %s^1 wants to change the map (^3%d^1/^3%d^1). Type ^4rtv^1 to agree.", name, g_rocks, RocksNeeded())
|
|
|
|
CheckRocks()
|
|
return PLUGIN_CONTINUE
|
|
}
|
|
|
|
public client_disconnected(id)
|
|
{
|
|
if (g_rocked[id])
|
|
{
|
|
g_rocked[id] = false
|
|
g_rocks--
|
|
}
|
|
// fewer players can mean the remaining votes are now enough
|
|
if (g_rocks > 0)
|
|
set_task(0.1, "CheckRocks")
|
|
}
|
|
|
|
public CheckRocks()
|
|
{
|
|
if (!g_voting && !g_finished && g_rocks > 0 && g_rocks >= RocksNeeded())
|
|
StartVote()
|
|
}
|
|
|
|
StartVote()
|
|
{
|
|
g_voting = true
|
|
|
|
new current[32], cyclefile[64]
|
|
get_mapname(current, charsmax(current))
|
|
get_cvar_string("mapcyclefile", cyclefile, charsmax(cyclefile))
|
|
|
|
new Array:maps = ArrayCreate(32)
|
|
new fp = fopen(cyclefile, "rt")
|
|
if (fp)
|
|
{
|
|
new line[64]
|
|
while (!feof(fp))
|
|
{
|
|
fgets(fp, line, charsmax(line))
|
|
trim(line)
|
|
if (!line[0] || line[0] == ';' || (line[0] == '/' && line[1] == '/'))
|
|
continue
|
|
if (equali(line, current) || !is_map_valid(line))
|
|
continue
|
|
ArrayPushString(maps, line)
|
|
}
|
|
fclose(fp)
|
|
}
|
|
|
|
g_numChoices = 0
|
|
while (g_numChoices < VOTE_MAPS && ArraySize(maps) > 0)
|
|
{
|
|
new pick = random(ArraySize(maps))
|
|
ArrayGetString(maps, pick, g_choices[g_numChoices], charsmax(g_choices[]))
|
|
ArrayDeleteItem(maps, pick)
|
|
g_count[g_numChoices] = 0
|
|
g_numChoices++
|
|
}
|
|
ArrayDestroy(maps)
|
|
|
|
if (!g_numChoices)
|
|
{
|
|
g_voting = false
|
|
client_print_color(0, print_team_default, "^4[RTV]^1 No other maps in the mapcycle.")
|
|
return
|
|
}
|
|
|
|
new menu[512], len, keys
|
|
len = formatex(menu, charsmax(menu), "\yRock the vote - pick the next map:^n^n")
|
|
for (new i = 0; i < g_numChoices; i++)
|
|
{
|
|
len += formatex(menu[len], charsmax(menu) - len, "\r%d.\w %s^n", i + 1, g_choices[i])
|
|
keys |= (1 << i)
|
|
}
|
|
|
|
show_menu(0, keys, menu, VOTE_TIME, "RTV Vote")
|
|
client_print_color(0, print_team_default, "^4[RTV]^1 Vote started! You have ^3%d^1 seconds.", VOTE_TIME)
|
|
set_task(float(VOTE_TIME) + 1.0, "FinishVote")
|
|
}
|
|
|
|
public VoteHandler(id, key)
|
|
{
|
|
if (!g_voting || key < 0 || key >= g_numChoices)
|
|
return PLUGIN_HANDLED
|
|
|
|
g_count[key]++
|
|
new name[32]
|
|
get_user_name(id, name, charsmax(name))
|
|
client_print_color(0, id, "^4[RTV]^3 %s^1 chose ^4%s", name, g_choices[key])
|
|
return PLUGIN_HANDLED
|
|
}
|
|
|
|
public FinishVote()
|
|
{
|
|
g_voting = false
|
|
g_finished = true
|
|
|
|
new best = 0
|
|
for (new i = 1; i < g_numChoices; i++)
|
|
{
|
|
if (g_count[i] > g_count[best])
|
|
best = i
|
|
}
|
|
if (!g_count[best])
|
|
best = random(g_numChoices)
|
|
|
|
set_cvar_string("amx_nextmap", g_choices[best])
|
|
client_print_color(0, print_team_default, "^4[RTV]^1 Next map: ^4%s^1 (^3%d^1 votes). Changing in 5 seconds...", g_choices[best], g_count[best])
|
|
set_task(5.0, "ChangeMap", best)
|
|
}
|
|
|
|
public ChangeMap(best)
|
|
{
|
|
server_cmd("changelevel %s", g_choices[best])
|
|
}
|