CheezPuff

[AMXX V1.10] Map Voting System

Jul 14th, 2024 (edited)
1,760
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Pawn 15.95 KB | None | 0 0
  1. /*
  2.     Release VoteMap v1.3
  3.     - Maps loaded from configs/maps.ini
  4.     - Auto vote every X minutes (cvar: vm_interval, default 30)
  5.     - RTV system with player threshold
  6.     - Vote menu with real-time vote counts + HUD timer
  7.     - Map changes cleanly after round end
  8.  
  9.     Fixes v1.1:
  10.     - RTV now correctly checks player ratio before starting
  11.     - Vote menu timer no longer freezes at 5 seconds
  12.     - HUD repositioned so it doesn't overlap the vote menu
  13.     - RTV cooldown set to 5 minutes (300s) after map change
  14.  
  15.     Fixes v1.2:
  16.     - RTV cooldown now survives map changes (saved to votemap_cooldown.ini)
  17.     - Minimum 5 players required on server before RTV is allowed
  18.     - HUD position adjusted slightly inward (less extreme corner)
  19.  
  20.     Fixes v1.3:
  21.     - SaveCooldownExpiry/LoadCooldownExpiry type mismatch fixed (was writing
  22.       float %f but reading back with str_to_num -- now consistent integers)
  23.     - g_lastMap now persisted to votemap_lastmap.ini so "Last Map was:" shows
  24.       the correct previous map after changelevel (was showing current map)
  25.     - Removed unused g_voteMenuId global variable
  26. */
  27.  
  28. #include <amxmodx>
  29. #include <amxmisc>
  30.  
  31. #define PLUGIN   "VoteMap"
  32. #define VERSION  "1.3"
  33. #define AUTHOR   "CheezPuff"
  34.  
  35. #define MAPS_TO_SHOW      4
  36. #define MAX_MAPS          128
  37. #define RTV_MIN_PLAYERS   5      // server must have at least this many players for RTV
  38.  
  39. #define TASK_VOTE_COUNTDOWN  10001
  40. #define TASK_AUTO_VOTE       10002
  41. #define TASK_CHANGEMAP       10003
  42. #define TASK_HUD             10004
  43.  
  44. new g_mapList[MAX_MAPS][32]
  45. new g_mapCount
  46.  
  47. new g_voteActive
  48. new g_voteTime
  49. new g_voteMaps[MAPS_TO_SHOW][32]
  50. new g_mapVotes[MAPS_TO_SHOW]
  51. new g_votedPlayers[33]
  52.  
  53. new g_rtvVotes[33]
  54. new g_rtvCount
  55.  
  56. new g_nextMap[32]
  57. new g_lastMap[32]
  58. new bool:g_nextMapChosen
  59. new bool:g_mapChangeScheduled
  60. new bool:g_rtvCooldown
  61.  
  62. new g_CvarInterval
  63. new g_CvarVoteTime
  64. new g_CvarRtvRatio
  65. new g_CvarRtvCooldown
  66.  
  67. new g_cooldownFile[128]
  68.  
  69. public plugin_init() {
  70.     register_plugin(PLUGIN, VERSION, AUTHOR)
  71.  
  72.     g_CvarInterval    = register_cvar("vm_interval",     "30")
  73.     g_CvarVoteTime    = register_cvar("vm_vote_time",    "30")
  74.     g_CvarRtvRatio    = register_cvar("vm_rtv_ratio",    "60")
  75.     g_CvarRtvCooldown = register_cvar("vm_rtv_cooldown", "300")
  76.  
  77.     register_clcmd("say",      "CmdSay")
  78.     register_clcmd("say_team", "CmdSay")
  79.  
  80.     register_concmd("amx_startvote",  "CmdAdminStartVote",  ADMIN_MAP, "- Force start a map vote")
  81.     register_concmd("amx_cancelvote", "CmdAdminCancelVote", ADMIN_MAP, "- Cancel current vote")
  82.     register_concmd("amx_setnextmap", "CmdAdminSetNextMap", ADMIN_MAP, "<map> - Set next map manually")
  83.     register_concmd("amx_reloadmaps", "CmdAdminReloadMaps", ADMIN_MAP, "- Reload maps.ini")
  84.  
  85.     register_menuid("VoteMap")
  86.     register_menucmd(register_menuid("VoteMap"), MENU_KEY_1|MENU_KEY_2|MENU_KEY_3|MENU_KEY_4, "HandleVote")
  87.  
  88.     register_logevent("EventRoundEnd", 2, "1=Round_End")
  89.  
  90.     LoadMapList()
  91.  
  92.     g_nextMapChosen      = false
  93.     g_mapChangeScheduled = false
  94.     g_rtvCooldown        = false
  95.     g_nextMap[0]         = EOS
  96.     g_lastMap[0]         = EOS
  97.  
  98.     // Build cooldown file path: <configs>/votemap_cooldown.ini
  99.     get_configsdir(g_cooldownFile, charsmax(g_cooldownFile))
  100.     add(g_cooldownFile, charsmax(g_cooldownFile), "/votemap_cooldown.ini")
  101.  
  102.     // Check if a cooldown was active before the map change
  103.     new sysNow    = get_systime()
  104.     new sysExpiry = LoadCooldownExpiry()
  105.     if(sysExpiry > sysNow) {
  106.         g_rtvCooldown = true
  107.         new Float:remaining = float(sysExpiry - sysNow)
  108.         set_task(remaining, "RtvCooldownEnd")
  109.         log_amx("[VoteMap] RTV cooldown restored: %d seconds remaining", sysExpiry - sysNow)
  110.     } else {
  111.         DeleteCooldownFile()
  112.     }
  113.  
  114.     // Save current map as lastMap — also write to file so next session can show it
  115.     get_mapname(g_lastMap, charsmax(g_lastMap))
  116.     new lastMapFile[128]
  117.     get_configsdir(lastMapFile, charsmax(lastMapFile))
  118.     add(lastMapFile, charsmax(lastMapFile), "/votemap_lastmap.ini")
  119.     // Try to read previously saved lastMap first (set by DoChangeMap before changelevel)
  120.     new lf = fopen(lastMapFile, "rt")
  121.     if(lf) {
  122.         new lline[32]
  123.         fgets(lf, lline, charsmax(lline))
  124.         fclose(lf)
  125.         trim(lline)
  126.         if(lline[0])
  127.             copy(g_lastMap, charsmax(g_lastMap), lline)
  128.     }
  129.  
  130.     new Float:interval = float(get_pcvar_num(g_CvarInterval)) * 60.0
  131.     if(interval < 60.0) interval = 60.0
  132.     set_task(interval, "AutoVoteTask", TASK_AUTO_VOTE)
  133. }
  134.  
  135. public client_putinserver(id) {
  136.     g_votedPlayers[id] = 0
  137.     g_rtvVotes[id]     = 0
  138. }
  139.  
  140. public client_disconnected(id) {
  141.     if(g_rtvVotes[id]) {
  142.         g_rtvVotes[id] = 0
  143.         g_rtvCount--
  144.         if(g_rtvCount < 0) g_rtvCount = 0
  145.     }
  146.     g_votedPlayers[id] = 0
  147. }
  148.  
  149. stock SaveCooldownExpiry(expiresAt) {
  150.     new file = fopen(g_cooldownFile, "wt")
  151.     if(!file) return
  152.     fprintf(file, "%d^n", expiresAt)
  153.     fclose(file)
  154. }
  155.  
  156. stock LoadCooldownExpiry() {
  157.     new file = fopen(g_cooldownFile, "rt")
  158.     if(!file) return 0
  159.     new line[32]
  160.     fgets(file, line, charsmax(line))
  161.     fclose(file)
  162.     trim(line)
  163.     if(!line[0]) return 0
  164.     return str_to_num(line)
  165. }
  166.  
  167. stock DeleteCooldownFile() {
  168.     if(file_exists(g_cooldownFile))
  169.         delete_file(g_cooldownFile)
  170. }
  171.  
  172. stock LoadMapList() {
  173.     new mapFile[128]
  174.     get_configsdir(mapFile, charsmax(mapFile))
  175.     add(mapFile, charsmax(mapFile), "/maps.ini")
  176.  
  177.     new file = fopen(mapFile, "rt")
  178.     if(!file) {
  179.         log_amx("[VoteMap] ERROR: Cannot open %s", mapFile)
  180.         return
  181.     }
  182.  
  183.     g_mapCount = 0
  184.     new line[32]
  185.     while(!feof(file) && g_mapCount < MAX_MAPS) {
  186.         fgets(file, line, charsmax(line))
  187.         trim(line)
  188.         if(line[0] && line[0] != ';') {
  189.             copy(g_mapList[g_mapCount], charsmax(g_mapList[]), line)
  190.             g_mapCount++
  191.         }
  192.     }
  193.     fclose(file)
  194.     log_amx("[VoteMap] Loaded %d maps from maps.ini", g_mapCount)
  195. }
  196.  
  197. stock SelectRandomMaps() {
  198.     new used[MAX_MAPS]
  199.     arrayset(used, 0, sizeof(used))
  200.     for(new i = 0; i < MAPS_TO_SHOW; i++) {
  201.         new idx, attempts = 0
  202.         do {
  203.             idx = random(g_mapCount)
  204.             attempts++
  205.             if(attempts > 1000) break
  206.         } while(used[idx])
  207.         used[idx] = 1
  208.         copy(g_voteMaps[i], charsmax(g_voteMaps[]), g_mapList[idx])
  209.     }
  210. }
  211.  
  212. stock ShowVoteMenu() {
  213.     new menu[512], len
  214.     len = formatex(menu, charsmax(menu), "\yVote for next map: \w(%d sec left)^n", g_voteTime)
  215.     for(new i = 0; i < MAPS_TO_SHOW; i++)
  216.         len += formatex(menu[len], charsmax(menu) - len, "^n\r%d. \w%s \d[%d votes]", i+1, g_voteMaps[i], g_mapVotes[i])
  217.     if(g_lastMap[0])
  218.         len += formatex(menu[len], charsmax(menu) - len, "^n^n\d-- Last Map was: \y%s\d...", g_lastMap)
  219.     new players[32], pNum
  220.     get_players(players, pNum, "c")
  221.     for(new i = 0; i < pNum; i++)
  222.         if(!g_votedPlayers[players[i]])
  223.             show_menu(players[i], MENU_KEY_1|MENU_KEY_2|MENU_KEY_3|MENU_KEY_4, menu, -1, "VoteMap")
  224. }
  225.  
  226. stock ShowLockedMenu(id) {
  227.     new menu[512], len
  228.     len = formatex(menu, charsmax(menu), "\yVote for next map: \w(%d sec left)^n", g_voteTime)
  229.     for(new i = 0; i < MAPS_TO_SHOW; i++)
  230.         len += formatex(menu[len], charsmax(menu) - len, "^n\d%d. %s [%d votes]", i+1, g_voteMaps[i], g_mapVotes[i])
  231.     len += formatex(menu[len], charsmax(menu) - len, "^n^n\dYou have already voted.")
  232.     if(g_lastMap[0])
  233.         len += formatex(menu[len], charsmax(menu) - len, "^n\d-- Last Map was: \y%s\d...", g_lastMap)
  234.     show_menu(id, 0, menu, -1, "VoteMap")
  235. }
  236.  
  237. stock ShowVoteHud() {
  238.     new hud[256], len
  239.     len = formatex(hud, charsmax(hud), "-- Vote for next map (%ds) --^n", g_voteTime)
  240.     for(new i = 0; i < MAPS_TO_SHOW; i++)
  241.         len += formatex(hud[len], charsmax(hud) - len, "%d. %s [%d]^n", i+1, g_voteMaps[i], g_mapVotes[i])
  242.     set_hudmessage(255, 200, 0, 0.55, 0.05, 0, 0.1, 1.1, 0.0, 0.0, -1)
  243.     show_hudmessage(0, "%s", hud)
  244. }
  245.  
  246. stock EndVote() {
  247.     g_voteActive = 0
  248.     remove_task(TASK_HUD)
  249.     remove_task(TASK_VOTE_COUNTDOWN)
  250.     show_menu(0, 0, "", 1)
  251.  
  252.     new winner = 0
  253.     for(new i = 1; i < MAPS_TO_SHOW; i++)
  254.         if(g_mapVotes[i] > g_mapVotes[winner]) winner = i
  255.  
  256.     copy(g_nextMap, charsmax(g_nextMap), g_voteMaps[winner])
  257.     g_nextMapChosen      = true
  258.     g_mapChangeScheduled = true
  259.  
  260.     new totalVotes = 0
  261.     for(new i = 0; i < MAPS_TO_SHOW; i++) totalVotes += g_mapVotes[i]
  262.  
  263.     set_hudmessage(100, 255, 100, 0.02, 0.25, 0, 0.5, 6.0, 0.1, 0.3, -1)
  264.     show_hudmessage(0, "Vote ended!^nNext map: %s^n(%d/%d votes)", g_nextMap, g_mapVotes[winner], totalVotes)
  265.  
  266.     client_print(0, print_chat, "[VoteMap] Next map: %s (%d/%d votes) -- changes after this round.", g_nextMap, g_mapVotes[winner], totalVotes)
  267. }
  268.  
  269. stock StartVote() {
  270.     if(g_mapCount < MAPS_TO_SHOW) {
  271.         log_amx("[VoteMap] Not enough maps in maps.ini (need at least %d)", MAPS_TO_SHOW)
  272.         return
  273.     }
  274.  
  275.     g_voteActive = 1
  276.     g_voteTime   = get_pcvar_num(g_CvarVoteTime)
  277.     if(g_voteTime < 10) g_voteTime = 10
  278.     if(g_voteTime > 60) g_voteTime = 60
  279.  
  280.     arrayset(g_mapVotes,     0, sizeof(g_mapVotes))
  281.     arrayset(g_votedPlayers, 0, sizeof(g_votedPlayers))
  282.     arrayset(g_rtvVotes,     0, sizeof(g_rtvVotes))
  283.     g_rtvCount = 0
  284.  
  285.     SelectRandomMaps()
  286.  
  287.     set_hudmessage(255, 150, 0, 0.02, 0.25, 0, 0.5, 3.0, 0.1, 0.3, -1)
  288.     show_hudmessage(0, "Map vote starting!^nGet ready to vote...")
  289.     client_print(0, print_chat, "[VoteMap] Map vote is starting! Use the menu to vote.")
  290.     set_task(3.0, "ShowVoteTask", TASK_VOTE_COUNTDOWN)
  291. }
  292.  
  293. public ShowVoteTask() {
  294.     remove_task(TASK_VOTE_COUNTDOWN)
  295.     ShowVoteMenu()
  296.     ShowVoteHud()
  297.     set_task(1.0, "VoteCountdown", TASK_VOTE_COUNTDOWN, _, _, "b")
  298.     set_task(1.0, "VoteHudUpdate", TASK_HUD, _, _, "b")
  299. }
  300.  
  301. public VoteCountdown() {
  302.     if(!g_voteActive) {
  303.         remove_task(TASK_VOTE_COUNTDOWN)
  304.         return
  305.     }
  306.  
  307.     g_voteTime--
  308.  
  309.     if(g_voteTime <= 0) {
  310.         remove_task(TASK_VOTE_COUNTDOWN)
  311.         EndVote()
  312.         return
  313.     }
  314.  
  315.     ShowVoteMenu()
  316.  
  317.     new players[32], pNum
  318.     get_players(players, pNum, "c")
  319.     for(new i = 0; i < pNum; i++)
  320.         if(g_votedPlayers[players[i]])
  321.             ShowLockedMenu(players[i])
  322. }
  323.  
  324. public VoteHudUpdate() {
  325.     if(!g_voteActive) {
  326.         remove_task(TASK_HUD)
  327.         return
  328.     }
  329.     ShowVoteHud()
  330. }
  331.  
  332. public HandleVote(id, key) {
  333.     if(!g_voteActive) return PLUGIN_HANDLED
  334.     if(g_votedPlayers[id]) return PLUGIN_HANDLED
  335.  
  336.     g_votedPlayers[id] = 1
  337.     g_mapVotes[key]++
  338.  
  339.     ShowLockedMenu(id)
  340.  
  341.     set_hudmessage(100, 255, 100, 0.02, 0.55, 0, 0.5, 3.0, 0.1, 0.3, -1)
  342.     show_hudmessage(id, "Voted for: %s", g_voteMaps[key])
  343.  
  344.     new name[32]
  345.     get_user_name(id, name, charsmax(name))
  346.     client_print(0, print_chat, "[VoteMap] %s voted for %s", name, g_voteMaps[key])
  347.  
  348.     return PLUGIN_HANDLED
  349. }
  350.  
  351. public AutoVoteTask() {
  352.     if(g_voteActive || g_nextMapChosen) return
  353.     client_print(0, print_chat, "[VoteMap] Auto map vote starting...")
  354.     StartVote()
  355. }
  356.  
  357. public RtvCooldownEnd() {
  358.     g_rtvCooldown = false
  359.     DeleteCooldownFile()
  360.     client_print(0, print_chat, "[VoteMap] RTV is now available again.")
  361. }
  362.  
  363. public EventRoundEnd() {
  364.     if(!g_mapChangeScheduled) return
  365.     g_mapChangeScheduled = false
  366.  
  367.     g_rtvCooldown = true
  368.     new Float:cooldown = float(get_pcvar_num(g_CvarRtvCooldown))
  369.     if(cooldown < 10.0) cooldown = 10.0
  370.     new expiryTime = get_systime() + floatround(cooldown, floatround_floor)
  371.     SaveCooldownExpiry(expiryTime)
  372.     set_task(cooldown, "RtvCooldownEnd")
  373.  
  374.     set_hudmessage(255, 100, 100, -1.0, 0.45, 1, 0.5, 4.0, 0.1, 0.3, -1)
  375.     show_hudmessage(0, "Changing map to %s!", g_nextMap)
  376.     client_print(0, print_chat, "[VoteMap] Round over -- changing map to %s!", g_nextMap)
  377.     set_task(4.0, "DoChangeMap", TASK_CHANGEMAP)
  378. }
  379.  
  380. public DoChangeMap() {
  381.     new currentMap[32]
  382.     get_mapname(currentMap, charsmax(currentMap))
  383.     new lastMapFile[128]
  384.     get_configsdir(lastMapFile, charsmax(lastMapFile))
  385.     add(lastMapFile, charsmax(lastMapFile), "/votemap_lastmap.ini")
  386.     new lf = fopen(lastMapFile, "wt")
  387.     if(lf) {
  388.         fprintf(lf, "%s^n", currentMap)
  389.         fclose(lf)
  390.     }
  391.     server_cmd("changelevel %s", g_nextMap)
  392. }
  393.  
  394. public CmdSay(id) {
  395.     new said[32]
  396.     read_args(said, charsmax(said))
  397.     remove_quotes(said)
  398.     trim(said)
  399.  
  400.     if(equali(said, "rtv") || equali(said, "rockthevote")) {
  401.         HandleRTV(id)
  402.         return PLUGIN_CONTINUE
  403.     }
  404.     if(equali(said, "nextmap") || equali(said, "/nextmap")) {
  405.         if(g_nextMapChosen)
  406.             client_print(id, print_chat, "[VoteMap] Next map: %s", g_nextMap)
  407.         else
  408.             client_print(id, print_chat, "[VoteMap] No nextmap yet.")
  409.         return PLUGIN_CONTINUE
  410.     }
  411.     return PLUGIN_CONTINUE
  412. }
  413.  
  414. HandleRTV(id) {
  415.     if(g_rtvCooldown) {
  416.         client_print(id, print_chat, "[VoteMap] RTV is on cooldown after map change. Please wait.")
  417.         return
  418.     }
  419.     if(g_voteActive) {
  420.         client_print(id, print_chat, "[VoteMap] Vote already in progress -- use the menu!")
  421.         return
  422.     }
  423.     if(g_nextMapChosen) {
  424.         client_print(id, print_chat, "[VoteMap] Next map already chosen: %s", g_nextMap)
  425.         return
  426.     }
  427.     if(g_rtvVotes[id]) {
  428.         client_print(id, print_chat, "[VoteMap] You already voted to rock the vote!")
  429.         return
  430.     }
  431.  
  432.     new players[32], pNum
  433.     get_players(players, pNum, "ch")
  434.     if(pNum < 1) pNum = 1
  435.  
  436.     if(pNum < RTV_MIN_PLAYERS) {
  437.         client_print(id, print_chat, "[VoteMap] RTV requires at least %d players on the server. (%d/%d)", RTV_MIN_PLAYERS, pNum, RTV_MIN_PLAYERS)
  438.         return
  439.     }
  440.  
  441.     g_rtvVotes[id] = 1
  442.     g_rtvCount++
  443.  
  444.     new needed = ( (get_pcvar_num(g_CvarRtvRatio) * pNum) + 99 ) / 100
  445.     if(needed < 1) needed = 1
  446.  
  447.     new name[32]
  448.     get_user_name(id, name, charsmax(name))
  449.     client_print(0, print_chat, "[VoteMap] %s wants to rock the vote! (%d/%d needed)", name, g_rtvCount, needed)
  450.  
  451.     if(g_rtvCount >= needed) {
  452.         client_print(0, print_chat, "[VoteMap] RTV passed! Starting vote...")
  453.         StartVote()
  454.     }
  455. }
  456.  
  457. public CmdAdminStartVote(id, level, cid) {
  458.     if(!cmd_access(id, level, cid, 1)) return PLUGIN_HANDLED
  459.     if(g_voteActive) {
  460.         console_print(id, "[VoteMap] Vote already in progress.")
  461.         return PLUGIN_HANDLED
  462.     }
  463.     StartVote()
  464.     console_print(id, "[VoteMap] Vote started.")
  465.     return PLUGIN_HANDLED
  466. }
  467.  
  468. public CmdAdminCancelVote(id, level, cid) {
  469.     if(!cmd_access(id, level, cid, 1)) return PLUGIN_HANDLED
  470.     if(!g_voteActive) {
  471.         console_print(id, "[VoteMap] No vote in progress.")
  472.         return PLUGIN_HANDLED
  473.     }
  474.     remove_task(TASK_VOTE_COUNTDOWN)
  475.     remove_task(TASK_HUD)
  476.     show_menu(0, 0, "", 1)
  477.     g_voteActive = 0
  478.     arrayset(g_mapVotes,     0, sizeof(g_mapVotes))
  479.     arrayset(g_votedPlayers, 0, sizeof(g_votedPlayers))
  480.     client_print(0, print_chat, "[VoteMap] Vote cancelled by admin.")
  481.     console_print(id, "[VoteMap] Vote cancelled.")
  482.     return PLUGIN_HANDLED
  483. }
  484.  
  485. public CmdAdminSetNextMap(id, level, cid) {
  486.     if(!cmd_access(id, level, cid, 2)) return PLUGIN_HANDLED
  487.     new map[32]
  488.     read_argv(1, map, charsmax(map))
  489.     if(!map[0]) {
  490.         console_print(id, "[VoteMap] Usage: amx_setnextmap <mapname>")
  491.         return PLUGIN_HANDLED
  492.     }
  493.     copy(g_nextMap, charsmax(g_nextMap), map)
  494.     g_nextMapChosen      = true
  495.     g_mapChangeScheduled = true
  496.     client_print(0, print_chat, "[VoteMap] Admin set next map: %s", g_nextMap)
  497.     console_print(id, "[VoteMap] Next map set to: %s", g_nextMap)
  498.     return PLUGIN_HANDLED
  499. }
  500.  
  501. public CmdAdminReloadMaps(id, level, cid) {
  502.     if(!cmd_access(id, level, cid, 1)) return PLUGIN_HANDLED
  503.     LoadMapList()
  504.     console_print(id, "[VoteMap] Reloaded maps.ini -- %d maps loaded.", g_mapCount)
  505.     return PLUGIN_HANDLED
  506. }
  507.  
Advertisement
Add Comment
Please, Sign In to add comment