diff --git a/mapvote/README.md b/mapvote/README.md new file mode 100644 index 0000000..a91b5e2 --- /dev/null +++ b/mapvote/README.md @@ -0,0 +1,128 @@ +# Mapvote + +A customizable mapvote script for the multiplayer mode of Black Ops. + +This works in custom games, combat training and dedicated servers. +You can still run the script in a private match to get a preview of the menu and configure it the way you like before using it on your server but it won't rotate to your chosen map/mode due to a technical limitation the game has. + +This is heavily inspired by [LastDemon99's IW5 mapvote](https://github.com/LastDemon99/IW5_VoteSystem). +Huge thanks to him. + +![mapvote1](images/mapvote1.png) +*Mouse and keyboard input. Changed settings: horizontal spacing: 100* + +![mapvote2](images/mapvote2.png) +*Controller input. Changed settings: red colors, accent mode: max* + +## mapvote.gsc + +This script has to be installed in `scripts\mp` otherwise it will throw errors when playing zombies. + +**[IMPORTANT]** Installing `mapvote_mp_extend.gsc` in `scripts\mp` is **mandatory** to make the mapvote work normally. + +### Main features + +- It allows up to 11 elements (maps + modes) to be displayed on screen +- You can configure how much maps and modes you want on screen +- It will automatically adapt the amount of elements to display if you don't choose a fixed amount of maps/modes to show +- It has separate map and mode choices for multiplayer +- It supports custom gamemode names +- It rotates a random map from the list when there are no votes for maps. Same applies for modes too +- Controllers are fully supported and work out of the box +- It has a good level of customization +- It has a debug mode to quickly preview the menu and print some values in the console + +### Getting started + +By default the script is disabled to avoid running on all your servers. +Simply set `mapvote_enable` to 1 and the script will be loaded, which as a result will display the voting menu when the game ends (after the killcam). + +To configure the menu before putting it on your server I recommend running it in a custom game with the `mapvote_debug` dvar set to `1`. +To do that use this command in the console `set mapvote_enable 1;set mapvote_debug 1` before running a custom game. +Start a custom game and you will see the menu. +You can then configure the dvars directly in your console and restart the map with `map_restart` in the console to edit the menu quickly and get your perfect setup. + +### Dvars + +Here are the dvars you can configure: + +| Name | Description | Default value | Accepted values | +|---|---|---|---| +| mapvote_enable | Toggle whether the mapvote is activated or not. 0 is off and 1 is on | 0 | 0 or 1 | +| mapvote_debug | Toggle whether the mapvote runs in debug mode or not. This will display the mapvote menu a few seconds after starting the game. 0 is off and 1 is on | 0 | 0 or 1 | +| mapvote_maps | A list of the maps that are available for rotation | Every maps including DLC maps | Any map name. Each map is separated with a colon (:) | +| mapvote_modes | A list of the modes that are available for rotation. The first parameter is how the mode will be displayed, it can be set to anything you like, the second parameter is the gametype | "Team Deathmatch,tdm:Domination,dom" | Any text followed by a comma (,) and then the gametype name. Each block is separated with a colon (:) | +| mapvote_limits_maps | The amount of maps to display. 0 will handle it automatically | 0 | Any plain number from 0 to `mapvote_limits_max` | +| mapvote_limits_modes | The amount of modes to display. 0 will handle it automatically | 0 | Any plain number from 0 to `mapvote_limits_max` | +| mapvote_limits_max | The maximum amount of elements to display (maps + modes) | 11 | Any plain number from 3 to 11 | +| mapvote_colors_selected | The color of the text when hovered or selected. This is also the color of the votes count | cyan | red, green, yellow, blue, cyan, purple, white, grey, gray, black | +| mapvote_colors_unselected | The color of the text when not hovered and not selected | white | red, green, yellow, blue, cyan, purple, white, grey, gray, black | +| mapvote_colors_timer | The color of the timer as long as it has more than 5 seconds remaining | cyan | red, green, yellow, blue, cyan, purple, white, grey, gray, black | +| mapvote_colors_timer_low | The color of the timer when it has 5 or less seconds remaining | red | red, green, yellow, blue, cyan, purple, white, grey, gray, black | +| mapvote_colors_help_text | The color of the help text at the bottom explaining how to use the menu | white | red, green, yellow, blue, cyan, purple, white, grey, gray, black | +| mapvote_colors_help_accent | The color of the accented text of the help text at the bottom | cyan | red, green, yellow, blue, cyan, purple, white, grey, gray, black | +| mapvote_colors_help_accent_mode | The accenting mode for the help text. `standard` only puts the accent color on the button to press and `max` puts it on both the buttons and the action it does | standard | standard or max | +| mapvote_sounds_menu_enabled | Toggle whether the mapvote menu sounds are enabled or not. 0 is off and 1 is on | 1 | 0 or 1 | +| mapvote_sounds_timer_enabled | Toggle whether the timer will start making a beeping sound every second when there's 5 or less seconds remaining to vote or not. 0 is off and 1 is on | 1 | 0 or 1 | +| mapvote_vote_time | The time the vote lasts (in seconds) | 30 | Any plain number above 5 | +| mapvote_blur_level | The amount of blur to put when the mapvote menu starts to show. The max recommended value is 5. 0 disables it | 2.5 | Any number | +| mapvote_horizontal_spacing | The horizontal spacing between the map/mode names on the left and the vote counts on the right. I recommend setting this value according to the longest map or mode name length so that it doesn't overlap with the vote counts | 75 | Any plain number | +| mapvote_display_wait_time | Once the killcam ends, the time to wait before displaying the vote menu (in seconds) | 1 | Any number superior or equal to 0.05 | + +### Configuration + +Below is an example CFG showing how each dvars can be configured. +The values you see are the default values that will be used if you don't set a dvar. + +```c +set mapvote_enable 1 +set mapvote_maps "Array:Cracked:Crisis:Firing Range:Grid:Hanoi:Havana:Jungle:Launch:Nuketown:Radiation:Summit:Villa:WMD:Berlin Wall:Discovery:Kowloon:Stadium:Convoy:Hotel:Stockpile:Zoo:Drive-in:Hangar 18:Hazard:Silo" +set mapvote_modes "Team Deathmatch,tdm:Domination,dom" +set mapvote_limits_maps 0 +set mapvote_limits_modes 0 +set mapvote_limits_max 11 +set mapvote_colors_selected "cyan" +set mapvote_colors_unselected "white" +set mapvote_colors_timer "cyan" +set mapvote_colors_timer_low "red" +set mapvote_colors_help_text "white" +set mapvote_colors_help_accent "cyan" +set mapvote_colors_help_accent_mode "standard" +set mapvote_sounds_menu_enabled 1 +set mapvote_sounds_timer_enabled 1 +set mapvote_vote_time 30 +set mapvote_blur_level 2.5 +set mapvote_horizontal_spacing 75 +set mapvote_display_wait_time 1 +``` + +Here are some pre-set values if you want to quickly copy/paste something + +| Description | Value | +|---|---| +| All base game maps | "Array:Cracked:Crisis:Firing Range:Grid:Hanoi:Havana:Jungle:Launch:Nuketown:Radiation:Summit:Villa:WMD" | +| All DLC maps | "Berlin Wall:Discovery:Kowloon:Stadium:Convoy:Hotel:Stockpile:Zoo:Drive-in:Hangar 18:Hazard:Silo" | +| Classic modes | "Team Deathmatch,tdm:Domination,dom" | +| Objective modes | "Demolition,dem:Headquaters,hq:Sabotage,sab:Capture the Flag,ctf" | +| Alternative modes | "Kill Confirmed,conf:One Flag CTF,oneflag" | +| Party modes | "Gun Game,gun:One in the Chamber,oic:Sharpshooter,shrp:Sticks & Stones,hlnd" | +| FFA 24/7 | "Free for All,dm" | +| SND 24/7 | "Search & Destroy,sd" | + +### Notes + +- If right click is set to toggle ads then pressing right click will make the player go up by one every 0.25s until he right clicks again. +If I didn't change it to be that way players with toggle ads would have to press right click twice to go up by one all the time. +Now instead they simply right click once to start going up and right click again to stop which is a better user experience. +- When there's only one map/mode, instead of showing a single vote possibility, your single map/mode will be hidden to make the user experience better but it will still always rotate to your one map/mode +- If some map/mode names or vote count don't display at all then you probably have other scripts that create HUD elements and there's too much elements to display so either remove your script or lower `mapvote_limits_max` so that the mapvote will have less elements to display +- When two maps/modes have the same votes, the lowest one in the list will win. In the future it would be nice to randomize between both +- Ending the game with ESC doesn't work when in debug mode. +Use `map_restart` in the console when your script is compiled. And if you want to leave use `disconnect` in the console until this is fixed +- For some reason having 12 voting elements like on the other games doesn't show one of the HUD so 11 is the max. + +## mapvote_mp_extend.gsc + +A small script that goes with `mapvote.gsc` to make it work in multiplayer. +It has to be installed in the `scripts\mp` directory since it contains multiplayer only code. +Putting it in `scripts` or `scripts\zm` will throw an error when booting up a zombies map. diff --git a/mapvote/images/mapvote1.png b/mapvote/images/mapvote1.png new file mode 100644 index 0000000..b344642 Binary files /dev/null and b/mapvote/images/mapvote1.png differ diff --git a/mapvote/images/mapvote2.png b/mapvote/images/mapvote2.png new file mode 100644 index 0000000..cc37f20 Binary files /dev/null and b/mapvote/images/mapvote2.png differ diff --git a/mapvote/mapvote.gsc b/mapvote/mapvote.gsc new file mode 100644 index 0000000..971fdfd --- /dev/null +++ b/mapvote/mapvote.gsc @@ -0,0 +1,973 @@ +/* +====================================================================== +| Game: Plutonium T5 | +| Description : Let players vote | +| for a map and mode at the end of each game | +| Author: Resxt | +====================================================================== +| https://github.com/Resxt/Plutonium-T5-Scripts/tree/main/mapvote | +====================================================================== +*/ + +#include maps\mp\gametypes\_hud_util; +#include maps\mp\_utility; +#include common_scripts\utility; + +/* Entry point */ + +Init() +{ + if (GetDvarInt("mapvote_enable")) + { + level.mapvote_start_function = ::StartVote; + level.mapvote_end_function = ::ListenForEndVote; + + InitMapvote(); + } +} + + + +/* Init section */ + +InitMapvote() +{ + InitDvars(); + InitVariables(); + + if (GetDvarInt("mapvote_debug")) + { + Print("[MAPVOTE] Debug mode is ON"); + wait 3; + level thread StartVote(); + level thread ListenForEndVote(); + } + else + { + // Starting the mapvote normally is handled in mp\mapvote_mp_extend.gsc and zm\mapvote_zm_extend.gsc + } +} + +InitDvars() +{ + SetDvarIfNotInitialized("mapvote_debug", false); + + SetDvarIfNotInitialized("mapvote_maps", "Array:Cracked:Crisis:Firing Range:Grid:Hanoi:Havana:Jungle:Launch:Nuketown:Radiation:Summit:Villa:WMD:Berlin Wall:Discovery:Kowloon:Stadium:Convoy:Hotel:Stockpile:Zoo:Drive-in:Hangar 18:Hazard:Silo"); + SetDvarIfNotInitialized("mapvote_modes", "Team Deathmatch,tdm:Domination,dom"); + SetDvarIfNotInitialized("mapvote_limits_maps", 0); + SetDvarIfNotInitialized("mapvote_limits_modes", 0); + SetDvarIfNotInitialized("mapvote_sounds_menu_enabled", 1); + SetDvarIfNotInitialized("mapvote_sounds_timer_enabled", 1); + SetDvarIfNotInitialized("mapvote_limits_max", 11); + SetDvarIfNotInitialized("mapvote_colors_selected", "cyan"); + SetDvarIfNotInitialized("mapvote_colors_unselected", "white"); + SetDvarIfNotInitialized("mapvote_colors_timer", "cyan"); + SetDvarIfNotInitialized("mapvote_colors_timer_low", "red"); + SetDvarIfNotInitialized("mapvote_colors_help_text", "white"); + SetDvarIfNotInitialized("mapvote_colors_help_accent", "cyan"); + SetDvarIfNotInitialized("mapvote_colors_help_accent_mode", "standard"); + SetDvarIfNotInitialized("mapvote_vote_time", 30); + SetDvarIfNotInitialized("mapvote_blur_level", 2.5); + SetDvarIfNotInitialized("mapvote_horizontal_spacing", 75); + SetDvarIfNotInitialized("mapvote_display_wait_time", 1); +} + +InitVariables() +{ + mapsArray = StrTok(GetDvar("mapvote_maps"), ":"); + voteLimits = []; + + modesArray = StrTok(GetDvar("mapvote_modes"), ":"); + + if (GetDvarInt("mapvote_limits_maps") == 0 && GetDvarInt("mapvote_limits_modes") == 0) + { + voteLimits = GetVoteLimits(mapsArray.size, modesArray.size); + } + else if (GetDvarInt("mapvote_limits_maps") > 0 && GetDvarInt("mapvote_limits_modes") == 0) + { + voteLimits = GetVoteLimits(GetDvarInt("mapvote_limits_maps"), modesArray.size); + } + else if (GetDvarInt("mapvote_limits_maps") == 0 && GetDvarInt("mapvote_limits_modes") > 0) + { + voteLimits = GetVoteLimits(mapsArray.size, GetDvarInt("mapvote_limits_modes")); + } + else + { + voteLimits = GetVoteLimits(GetDvarInt("mapvote_limits_maps"), GetDvarInt("mapvote_limits_modes")); + } + + level.mapvote["limit"]["maps"] = voteLimits["maps"]; + level.mapvote["limit"]["modes"] = voteLimits["modes"]; + + SetMapvoteData("map"); + SetMapvoteData("mode"); + + level.mapvote["vote"]["maps"] = []; + level.mapvote["vote"]["modes"] = []; + level.mapvote["hud"]["maps"] = []; + level.mapvote["hud"]["modes"] = []; +} + + + +/* Player section */ + +/* +This is used instead of notifyonplayercommand("mapvote_up", "speed_throw") +to fix an issue where players using toggle ads would have to press right click twice for it to register one right click. +With this instead it keeps scrolling every 0.25s until they right click again which is a better user experience +*/ +ListenForRightClick() +{ + self endon("disconnect"); + + while (true) + { + if (self AdsButtonPressed()) + { + self notify("mapvote_up"); + wait 0.25; + } + + wait 0.05; + } +} + +ListenForVoteInputs() +{ + self endon("disconnect"); + + self thread ListenForRightClick(); + + self notifyonplayercommand("mapvote_down", "+attack"); + self notifyonplayercommand("mapvote_select", "+usereload"); + self notifyonplayercommand("mapvote_select", "+activate"); + self notifyonplayercommand("mapvote_unselect", "+melee"); + + if (GetDvarInt("mapvote_debug")) + { + self notifyonplayercommand("mapvote_debug", "+reload"); + } + + while(true) + { + input = self waittill_any_return("mapvote_down", "mapvote_up", "mapvote_select", "mapvote_unselect", "mapvote_debug"); + + section = self.mapvote["vote_section"]; + + if (section == "end" && input != "mapvote_unselect" && input != "mapvote_debug") + { + continue; // stop/skip execution + } + else if (section == "mode" && level.mapvote["modes"]["by_index"].size <= 1 && input != "mapvote_unselect" && input != "mapvote_debug") + { + continue; // stop/skip execution + } + + if (input == "mapvote_down") + { + if (self.mapvote[section]["hovered_index"] < (level.mapvote[section + "s"]["by_index"].size - 1)) + { + if (GetDvarInt("mapvote_sounds_menu_enabled")) + { + self playlocalsound("uin_timer_wager_beep"); + } + + self UpdateSelection(section, (self.mapvote[section]["hovered_index"] + 1)); + } + } + else if (input == "mapvote_up") + { + if (self.mapvote[section]["hovered_index"] > 0) + { + if (GetDvarInt("mapvote_sounds_menu_enabled")) + { + self playlocalsound("uin_timer_wager_beep"); + } + + self UpdateSelection(section, (self.mapvote[section]["hovered_index"] - 1)); + } + } + else if (input == "mapvote_select") + { + if (GetDvarInt("mapvote_sounds_menu_enabled")) + { + self playlocalsound("fly_equipment_pickup_plr"); + } + + self ConfirmSelection(section); + } + else if (input == "mapvote_unselect") + { + if (section != "map") + { + if (GetDvarInt("mapvote_sounds_menu_enabled")) + { + self playlocalsound("mpl_flag_drop_plr"); + } + + self CancelSelection(section); + } + } + else if (input == "mapvote_debug" && GetDvarInt("mapvote_debug")) + { + Print("--------------------------------"); + + humanPlayers = GetHumanPlayers(); + + for (i = 0; i < humanPlayers.size; i++) + { + if (humanPlayers[i].mapvote["map"]["selected_index"] == -1) + { + Print(humanPlayers[i].name + " did not vote for any map"); + } + else + { + mapName = level.mapvote["maps"]["by_index"][humanPlayers[i].mapvote["map"]["selected_index"]]; + + Print(humanPlayers[i].name + " voted for map [" + humanPlayers[i].mapvote["map"]["selected_index"] +"] " + mapName); + } + + if (humanPlayers[i].mapvote["mode"]["selected_index"] == -1) + { + Print(humanPlayers[i].name + " did not vote for any mode"); + } + else + { + Print(humanPlayers[i].name + " voted for mode [" + humanPlayers[i].mapvote["mode"]["selected_index"] + "] " + level.mapvote["modes"]["by_index"][humanPlayers[i].mapvote["mode"]["selected_index"]]); + } + } + } + + wait 0.05; + } +} + +OnPlayerDisconnect() +{ + self waittill("disconnect"); + + if (self.mapvote["map"]["selected_index"] != -1) + { + level.mapvote["vote"]["maps"][self.mapvote["map"]["selected_index"]] = (level.mapvote["vote"]["maps"][self.mapvote["map"]["selected_index"]] - 1); + level.mapvote["hud"]["maps"][self.mapvote["map"]["selected_index"]] SetValue(level.mapvote["vote"]["maps"][self.mapvote["map"]["selected_index"]]); + } + + if (self.mapvote["mode"]["selected_index"] != -1) + { + level.mapvote["vote"]["modes"][self.mapvote["mode"]["selected_index"]] = (level.mapvote["vote"]["modes"][self.mapvote["mode"]["selected_index"]] - 1); + level.mapvote["hud"]["modes"][self.mapvote["mode"]["selected_index"]] SetValue(level.mapvote["vote"]["modes"][self.mapvote["mode"]["selected_index"]]); + } +} + + +/* Vote section */ + +CreateVoteMenu() +{ + spacing = 20; + hudLastPosY = 0; + + sectionsSeparation = 0; + + if (level.mapvote["modes"]["by_index"].size > 1) + { + sectionsSeparation = 1; + } + + hudLastPosY = 0 - ((((level.mapvote["maps"]["by_index"].size + level.mapvote["modes"]["by_index"].size + sectionsSeparation) * spacing) / 2) - (spacing / 2)); + + humanPlayers = GetHumanPlayers(); + + for (mapIndex = 0; mapIndex < level.mapvote["maps"]["by_index"].size; mapIndex++) + { + mapVotesHud = CreateHudText("", "objective", 1.5, "LEFT", "CENTER", GetDvarInt("mapvote_horizontal_spacing"), hudLastPosY, true, 0); + mapVotesHud.color = GetGscColor(GetDvar("mapvote_colors_selected")); + + level.mapvote["hud"]["maps"][mapIndex] = mapVotesHud; + + for (i = 0; i < humanPlayers.size; i++) + { + mapName = level.mapvote["maps"]["by_index"][mapIndex]; + + humanPlayers[i].mapvote["map"][mapIndex]["hud"] = humanPlayers[i] CreateHudText(mapName, "objective", 1.5, "LEFT", "CENTER", 0 - (GetDvarInt("mapvote_horizontal_spacing")), hudLastPosY); + + if (mapIndex == 0) + { + humanPlayers[i] UpdateSelection("map", 0); + } + else + { + SetElementUnselected(humanPlayers[i].mapvote["map"][mapIndex]["hud"]); + } + } + + hudLastPosY += spacing; + } + + hudLastPosY += spacing; // Space between maps and modes sections + + for (modeIndex = 0; modeIndex < level.mapvote["modes"]["by_index"].size; modeIndex++) + { + modeVotesHud = CreateHudText("", "objective", 1.5, "LEFT", "CENTER", GetDvarInt("mapvote_horizontal_spacing"), hudLastPosY, true, 0); + modeVotesHud.color = GetGscColor(GetDvar("mapvote_colors_selected")); + + level.mapvote["hud"]["modes"][modeIndex] = modeVotesHud; + + for (i = 0; i < humanPlayers.size; i++) + { + humanPlayers[i].mapvote["mode"][modeIndex]["hud"] = humanPlayers[i] CreateHudText(level.mapvote["modes"]["by_index"][modeIndex], "objective", 1.5, "LEFT", "CENTER", 0 - (GetDvarInt("mapvote_horizontal_spacing")), hudLastPosY); + + SetElementUnselected(humanPlayers[i].mapvote["mode"][modeIndex]["hud"]); + } + + hudLastPosY += spacing; + } + + for (i = 0; i < humanPlayers.size; i++) + { + humanPlayers[i].mapvote["map"]["selected_index"] = -1; + humanPlayers[i].mapvote["mode"]["selected_index"] = -1; + + buttonsHelpMessage = ""; + + // before: select gostand | unselect activate + // after: select activate | unselect melee + + if (GetDvar("mapvote_colors_help_accent_mode") == "standard") + { + buttonsHelpMessage = GetChatColor(GetDvar("mapvote_colors_help_text")) + "Press " + GetChatColor(GetDvar("mapvote_colors_help_accent")) + "[{+attack}] " + GetChatColor(GetDvar("mapvote_colors_help_text")) + "to go down - Press " + GetChatColor(GetDvar("mapvote_colors_help_accent")) + "[{+speed_throw}] " + GetChatColor(GetDvar("mapvote_colors_help_text")) + "to go up - Press " + GetChatColor(GetDvar("mapvote_colors_help_accent")) + "[{+activate}] " + GetChatColor(GetDvar("mapvote_colors_help_text")) + "to select - Press " + GetChatColor(GetDvar("mapvote_colors_help_accent")) + "[{+melee}] " + GetChatColor(GetDvar("mapvote_colors_help_text")) + "to undo"; + } + else if(GetDvar("mapvote_colors_help_accent_mode") == "max") + { + buttonsHelpMessage = GetChatColor(GetDvar("mapvote_colors_help_text")) + "Press " + GetChatColor(GetDvar("mapvote_colors_help_accent")) + "[{+attack}] " + GetChatColor(GetDvar("mapvote_colors_help_text")) + "to go " + GetChatColor(GetDvar("mapvote_colors_help_accent")) + "down " + GetChatColor(GetDvar("mapvote_colors_help_text")) + "- Press " + GetChatColor(GetDvar("mapvote_colors_help_accent")) + "[{+speed_throw}] " + GetChatColor(GetDvar("mapvote_colors_help_text")) + "to go " + GetChatColor(GetDvar("mapvote_colors_help_accent")) + "up " + GetChatColor(GetDvar("mapvote_colors_help_text")) + "- Press " + GetChatColor(GetDvar("mapvote_colors_help_accent")) + "[{+activate}] " + GetChatColor(GetDvar("mapvote_colors_help_text")) + "to " + GetChatColor(GetDvar("mapvote_colors_help_accent")) + "select " + GetChatColor(GetDvar("mapvote_colors_help_text")) + "- Press " + GetChatColor(GetDvar("mapvote_colors_help_accent")) + "[{+melee}] " + GetChatColor(GetDvar("mapvote_colors_help_text")) + "to " + GetChatColor(GetDvar("mapvote_colors_help_accent")) + "undo"; + } + + if (GetDvarInt("mapvote_debug")) + { + if (GetDvar("mapvote_colors_help_accent_mode") == "standard") + { + buttonsHelpMessage = buttonsHelpMessage + " - Press " + GetChatColor(GetDvar("mapvote_colors_help_accent")) + "[{+reload}] " + GetChatColor(GetDvar("mapvote_colors_help_text")) + "to debug"; + } + else if(GetDvar("mapvote_colors_help_accent_mode") == "max") + { + buttonsHelpMessage = buttonsHelpMessage + GetChatColor(GetDvar("mapvote_colors_help_text")) + " - Press " + GetChatColor(GetDvar("mapvote_colors_help_accent")) + "[{+reload}] " + GetChatColor(GetDvar("mapvote_colors_help_text")) + "to " + GetChatColor(GetDvar("mapvote_colors_help_accent")) + "debug"; + } + } + + humanPlayers[i] CreateHudText(buttonsHelpMessage, "objective", 1.5, "CENTER", "CENTER", 0, 210); + } +} + +CreateVoteTimer() +{ + soundFX = spawn("script_origin", (0,0,0)); + soundFX hide(); + + timerhud = CreateTimer(GetDvarInt("mapvote_vote_time"), &"Vote ends in: ", "objective", 1.5, "CENTER", "CENTER", 0, -210); + timerhud.color = GetGscColor(GetDvar("mapvote_colors_timer")); + for (i = GetDvarInt("mapvote_vote_time"); i > 0; i--) + { + if(i <= 5) + { + timerhud.color = GetGscColor(GetDvar("mapvote_colors_timer_low")); + + if (GetDvarInt("mapvote_sounds_timer_enabled")) + { + soundFX playSound( "mpl_ui_timer_countdown" ); + } + } + wait(1); + } + level notify("mapvote_vote_end"); +} + +StartVote() +{ + level endon("end_game"); + + for (i = 0; i < level.mapvote["maps"]["by_index"].size; i++) + { + level.mapvote["vote"]["maps"][i] = 0; + } + + for (i = 0; i < level.mapvote["modes"]["by_index"].size; i++) + { + level.mapvote["vote"]["modes"][i] = 0; + } + + level thread CreateVoteMenu(); + level thread CreateVoteTimer(); + + humanPlayers = GetHumanPlayers(); + + for (i = 0; i < humanPlayers.size; i++) + { + humanPlayers[i] FreezeControlsAllowLook(1); + //humanPlayers[i] SetBlur(GetDvarInt("mapvote_blur_level"), GetDvarInt("mapvote_blur_fade_in_time")); + humanPlayers[i] SetClientDvar("r_blur", GetDvarInt("mapvote_blur_level")); + + humanPlayers[i] thread ListenForVoteInputs(); + humanPlayers[i] thread OnPlayerDisconnect(); + } +} + +ListenForEndVote() +{ + level endon("end_game"); + level waittill("mapvote_vote_end"); + + mostVotedMapIndex = 0; + mostVotedMapVotes = 0; + mostVotedModeIndex = 0; + mostVotedModeVotes = 0; + + mapsArrayKeys = GetArrayKeys(level.mapvote["vote"]["maps"]); + modesArrayKeys = GetArrayKeys(level.mapvote["vote"]["modes"]); + + for (i = 0; i < mapsArrayKeys.size; i++) + { + if (level.mapvote["vote"]["maps"][mapsArrayKeys[i]] > mostVotedMapVotes) + { + mostVotedMapIndex = mapsArrayKeys[i]; + mostVotedMapVotes = level.mapvote["vote"]["maps"][mapsArrayKeys[i]]; + } + } + + for (i = 0; i < modesArrayKeys.size; i++) + { + if (level.mapvote["vote"]["modes"][modesArrayKeys[i]] > mostVotedModeVotes) + { + mostVotedModeIndex = modesArrayKeys[i]; + mostVotedModeVotes = level.mapvote["vote"]["modes"][modesArrayKeys[i]]; + } + } + + if (mostVotedMapVotes == 0) + { + mostVotedMapIndex = GetRandomElementInArray(GetArrayKeys(level.mapvote["vote"]["maps"])); + + if (GetDvarInt("mapvote_debug")) + { + Print("[MAPVOTE] No vote for map. Chosen random map index: " + mostVotedMapIndex); + } + } + else + { + if (GetDvarInt("mapvote_debug")) + { + Print("[MAPVOTE] Most voted map has " + mostVotedMapVotes + " votes. Most voted map index: " + mostVotedMapIndex); + } + } + + if (mostVotedModeVotes == 0) + { + mostVotedModeIndex = GetRandomElementInArray(GetArrayKeys(level.mapvote["vote"]["modes"])); + + if (GetDvarInt("mapvote_debug")) + { + Print("[MAPVOTE] No vote for mode. Chosen random mode index: " + mostVotedModeIndex); + } + } + else + { + if (GetDvarInt("mapvote_debug")) + { + Print("[MAPVOTE] Most voted mode has " + mostVotedModeVotes + " votes. Most voted mode index: " + mostVotedModeIndex); + } + } + + modeName = level.mapvote["modes"]["by_index"][mostVotedModeIndex]; + modeCfg = level.mapvote["modes"]["by_name"][level.mapvote["modes"]["by_index"][mostVotedModeIndex]]; + mapName = GetMapCodeName(level.mapvote["maps"]["by_index"][mostVotedMapIndex]); + + if (GetDvarInt("mapvote_debug")) + { + Print("[MAPVOTE] mapName: " + mapName); + Print("[MAPVOTE] modeName: " + modeName); + Print("[MAPVOTE] modeCfg: " + modeCfg); + Print("[MAPVOTE] Rotating to " + mapName + " | " + modeName + " (" + modeCfg + ".cfg)"); + } + + SetDvar("sv_maprotationcurrent", "gametype " + modeCfg + " map " + mapName); + SetDvar("sv_maprotation", "gametype " + modeCfg + " map " + mapName); +} + +SetMapvoteData(type) +{ + limit = level.mapvote["limit"][type + "s"]; + + availableElements = StrTok(GetDvar("mapvote_" + type + "s"), ":"); + + if (availableElements.size < limit) + { + limit = availableElements.size; + } + + if (type == "map") + { + level.mapvote["maps"]["by_index"] = GetRandomUniqueElementsInArray(availableElements, limit); + } + else if (type == "mode") + { + finalElements = []; + + modes = GetRandomUniqueElementsInArray(availableElements, limit); + + for (i = 0; i < modes.size; i++) + { + splittedMode = StrTok(modes[i], ","); + finalElements = AddElementToArray(finalElements, splittedMode[0]); + + level.mapvote["modes"]["by_name"][splittedMode[0]] = splittedMode[1]; + } + + level.mapvote["modes"]["by_index"] = finalElements; + } +} + +/* +Gets the amount of maps and modes to display on screen +This is used to get default values if the limits dvars are not set +It will dynamically adjust the amount of maps and modes to show +*/ +GetVoteLimits(mapsAmount, modesAmount) +{ + maxLimit = GetDvarInt("mapvote_limits_max"); + limits = []; + + if (!IsDefined(modesAmount)) + { + if (mapsAmount <= maxLimit) + { + return mapsAmount; + } + else + { + return maxLimit; + } + } + + if ((mapsAmount + modesAmount) <= maxLimit) + { + limits["maps"] = mapsAmount; + limits["modes"] = modesAmount; + } + else + { + if (mapsAmount >= (maxLimit / 2) && modesAmount >= (maxLimit)) + { + limits["maps"] = (maxLimit / 2); + limits["modes"] = (maxLimit / 2); + } + else + { + if (mapsAmount > (maxLimit / 2)) + { + finalMapsAmount = 0; + + if (modesAmount <= 1) + { + limits["maps"] = maxLimit; + } + else + { + limits["maps"] = (maxLimit - modesAmount); + } + + limits["modes"] = modesAmount; + } + else if (modesAmount > (maxLimit / 2)) + { + limits["maps"] = mapsAmount; + limits["modes"] = (maxLimit - mapsAmount); + } + } + } + + return limits; +} + + + +/* HUD section */ + +UpdateSelection(type, index) +{ + if (type == "map" || type == "mode") + { + if (!IsDefined(self.mapvote[type]["hovered_index"])) + { + self.mapvote[type]["hovered_index"] = 0; + } + + self.mapvote["vote_section"] = type; + + SetElementUnselected(self.mapvote[type][self.mapvote[type]["hovered_index"]]["hud"]); // Unselect previous element + SetElementSelected(self.mapvote[type][index]["hud"]); // Select new element + + self.mapvote[type]["hovered_index"] = index; // Update the index + } + else if (type == "end") + { + self.mapvote["vote_section"] = "end"; + } +} + +ConfirmSelection(type) +{ + self.mapvote[type]["selected_index"] = self.mapvote[type]["hovered_index"]; + level.mapvote["vote"][type + "s"][self.mapvote[type]["selected_index"]] = (level.mapvote["vote"][type + "s"][self.mapvote[type]["selected_index"]] + 1); + level.mapvote["hud"][type + "s"][self.mapvote[type]["selected_index"]] SetValue(level.mapvote["vote"][type + "s"][self.mapvote[type]["selected_index"]]); + + if (type == "map") + { + modeIndex = 0; + + if (IsDefined(self.mapvote["mode"]["hovered_index"])) + { + modeIndex = self.mapvote["mode"]["hovered_index"]; + } + + self UpdateSelection("mode", modeIndex); + } + else if (type == "mode") + { + self UpdateSelection("end"); + } +} + +CancelSelection(type) +{ + typeToCancel = ""; + + if (type == "mode") + { + typeToCancel = "map"; + } + else if (type == "end") + { + typeToCancel = "mode"; + } + + level.mapvote["vote"][typeToCancel + "s"][self.mapvote[typeToCancel]["selected_index"]] = (level.mapvote["vote"][typeToCancel + "s"][self.mapvote[typeToCancel]["selected_index"]] - 1); + level.mapvote["hud"][typeToCancel + "s"][self.mapvote[typeToCancel]["selected_index"]] SetValue(level.mapvote["vote"][typeToCancel + "s"][self.mapvote[typeToCancel]["selected_index"]]); + + self.mapvote[typeToCancel]["selected_index"] = -1; + + if (type == "mode") + { + SetElementUnselected(self.mapvote["mode"][self.mapvote["mode"]["hovered_index"]]["hud"]); + self.mapvote["vote_section"] = "map"; + } + else if (type == "end") + { + self.mapvote["vote_section"] = "mode"; + } +} + +SetElementSelected(element) +{ + element.color = GetGscColor(GetDvar("mapvote_colors_selected")); +} + +SetElementUnselected(element) +{ + element.color = GetGscColor(GetDvar("mapvote_colors_unselected")); +} + +CreateHudText(text, font, fontScale, relativeToX, relativeToY, relativeX, relativeY, isServer, value) +{ + hudText = ""; + + if (IsDefined(isServer) && isServer) + { + hudText = CreateServerFontString( font, fontScale ); + } + else + { + hudText = CreateFontString( font, fontScale ); + } + + if (IsDefined(value)) + { + hudText.label = text; + hudText SetValue(value); + } + else + { + hudText SetText(text); + } + + hudText SetPoint(relativeToX, relativeToY, relativeX, relativeY); + + hudText.hideWhenInMenu = 1; + hudText.glowAlpha = 0; + + return hudText; +} + +CreateTimer(time, label, font, fontScale, relativeToX, relativeToY, relativeX, relativeY) +{ + timer = createServerTimer(font, fontScale); + timer setpoint(relativeToX, relativeToY, relativeX, relativeY); + timer.label = label; + timer.hideWhenInMenu = 1; + timer.glowAlpha = 0; + timer setTimer(time); + + return timer; +} + + + +/* Utils section */ + +SetDvarIfNotInitialized(dvar, value) +{ + if (!IsInitialized(dvar)) + { + SetDvar(dvar, value); + } +} + +IsInitialized(dvar) +{ + result = GetDvar(dvar); + return result != ""; +} + +IsBot() +{ + return IsDefined(self.pers["isBot"]) && self.pers["isBot"]; +} + +GetHumanPlayers() +{ + humanPlayers = []; + + for (i = 0; i < level.players.size; i++) + { + if (!level.players[i] IsBot()) + { + humanPlayers = AddElementToArray(humanPlayers, level.players[i]); + } + } + + return humanPlayers; +} + +GetRandomElementInArray(array) +{ + return array[GetArrayKeys(array)[randomint(array.size)]]; +} + +GetRandomUniqueElementsInArray(array, limit) +{ + finalElements = []; + + for (i = 0; i < limit; i++) + { + findElement = true; + + while (findElement) + { + randomElement = GetRandomElementInArray(array); + + if (!ArrayContainsValue(finalElements, randomElement)) + { + finalElements = AddElementToArray(finalElements, randomElement); + + findElement = false; + } + } + } + + return finalElements; +} + +ArrayContainsValue(array, valueToFind) +{ + if (array.size == 0) + { + return false; + } + + for (i = 0; i < array.size; i++) + { + if (array[i] == valueToFind) + { + return true; + } + } + + return false; +} + +AddElementToArray(array, element) +{ + array[array.size] = element; + return array; +} + +GetMapCodeName(mapName) +{ + formattedMapName = ToLower(mapName); + + switch(formattedMapName) + { + case "array": + return "mp_array"; + + case "cracked": + return "mp_cracked"; + + case "crisis": + return "mp_crisis"; + + case "firing range": + case "firing-range": + case "firingrange": + return "mp_firingrange"; + + case "grid": + return "mp_duga"; + + case "hanoi": + return "mp_hanoi"; + + case "havana": + return "mp_cairo"; + + case "jungle": + return "mp_havoc"; + + case "launch": + return "mp_cosmodrome"; + + case "nuketown": + return "mp_nuked"; + + case "radiation": + return "mp_radiation"; + + case "summit": + return "mp_mountain"; + + case "villa": + return "mp_villa"; + + case "wmd": + case "w.m.d": + return "mp_russianbase"; + + case "berlin wall": + case "berlin-wall": + case "berlinwall": + return "mp_berlinwall2"; + + case "discovery": + return "mp_discovery"; + + case "kowloon": + return "mp_kowloon"; + + case "stadium": + return "mp_stadium"; + + case "convoy": + return "mp_gridlock"; + + case "hotel": + return "mp_hotel"; + + case "stockpile": + return "mp_outskirts"; + + case "zoo": + return "mp_zoo"; + + case "drive-in": + case "drive in": + case "drivein": + return "mp_drivein"; + + case "hangar 18": + case "hangar-18": + case "hangar18": + return "mp_area51"; + + case "hazard": + return "mp_golfcourse"; + + case "silo": + return "mp_silo"; + } +} + +GetGscColor(colorName) +{ + switch (colorName) + { + case "red": + return (1, 0, 0.059); + + case "green": + return (0.549, 0.882, 0.043); + + case "yellow": + return (1, 0.725, 0); + + case "blue": + return (0, 0.553, 0.973); + + case "cyan": + return (0, 0.847, 0.922); + + case "purple": + return (0.427, 0.263, 0.651); + + case "white": + return (1, 1, 1); + + case "grey": + case "gray": + return (0.137, 0.137, 0.137); + + case "black": + return (0, 0, 0); + } +} + +GetChatColor(colorName) +{ + switch(colorName) + { + case "red": + return "^1"; + + case "green": + return "^2"; + + case "yellow": + return "^3"; + + case "blue": + return "^4"; + + case "cyan": + return "^5"; + + case "purple": + return "^6"; + + case "white": + return "^7"; + + case "grey": + return "^0"; + + case "black": + return "^0"; + } +} \ No newline at end of file diff --git a/mapvote/mapvote_mp_extend.gsc b/mapvote/mapvote_mp_extend.gsc new file mode 100644 index 0000000..15e95e3 --- /dev/null +++ b/mapvote/mapvote_mp_extend.gsc @@ -0,0 +1,37 @@ +#include maps\mp\_utility; + +Init() +{ + if (GetDvarInt("mapvote_enable")) + { + replaceFunc(maps\mp\gametypes\_killcam::finalkillcamwaiter, ::OnKillcamEnd); + } +} + +OnKillcamEnd() +{ + while (level.inFinalKillcam) + { + wait(0.05); + } + + if (!IsDefined(level.finalkillcam_winner)) + { + if (isRoundBased() && !wasLastRound()) + return false; + wait GetDvarInt("mapvote_display_wait_time"); + + [[level.mapvote_start_function]](); + [[level.mapvote_end_function]](); + return false; + } + + level waittill("final_killcam_done"); + if (isRoundBased() && !wasLastRound()) + return true; + wait GetDvarInt("mapvote_display_wait_time"); + + [[level.mapvote_start_function]](); + [[level.mapvote_end_function]](); + return true; +} \ No newline at end of file