diff --git a/.github/nv_compile.sh b/.github/nv_compile.sh index f370aac..01ae2cd 100755 --- a/.github/nv_compile.sh +++ b/.github/nv_compile.sh @@ -6,36 +6,27 @@ versvers=$(spcomp64 -v | grep "Compiler" --color=never | cut -d " " -f 3 | cut - nv_plugs=("nativevotes") -nv_plugs+=("nativevotes-basecommands") +nv_plugs+=("nativevotes_basecommands") +nv_plugs+=("nativevotes_basevotes") +nv_plugs+=("nativevotes_funvotes") nv_plugs+=("nativevotes_mapchooser") nv_plugs+=("nativevotes_nominations") nv_plugs+=("nativevotes_rockthevote") - -nv_plugins_disabled=("nativevotes_votetest") -nv_plugins_disabled+=("nativevotes_votemanager_test") -nv_plugins_disabled+=("csgo_votestart_test") -nv_plugins_disabled+=("votedelay_changelevel") -nv_plugins_disabled+=("votediagnostics") -nv_plugins_disabled+=("votefailed") +nv_plugs+=("nativevotes_voterp") +nv_plugs+=("nativevotes_votescramble") # we start in git root pushd ./addons/sourcemod/scripting -mkdir ../plugins/disabled -p || true - +mkdir ../plugins || true for target in "${nv_plugs[@]}"; do spcomp64 -i"./include/" "${target}".sp -o ../plugins/"${target}".smx || exit 1 done -for target in "${nv_plugins_disabled[@]}"; do - spcomp64 -i"./include/" "${target}".sp -o ../plugins/disabled/"${target}".smx || exit 1 -done - popd mkdir build || true 7za a -r build/nativevotes_sm_"${versvers}".zip scripting/ translations/ plugins/ rm plugins/ -rfv ls -la - diff --git a/.github/workflows/nativevotes.yml b/.github/workflows/nativevotes.yml index b803e90..612d6a9 100644 --- a/.github/workflows/nativevotes.yml +++ b/.github/workflows/nativevotes.yml @@ -4,11 +4,22 @@ name: Nativevotes Autobuild on: push: branches: [ master ] + paths-ignore: + - '.gitignore' + - 'LICENSE' + - 'README.md' + pull_request: + branches: [ master ] + paths-ignore: + - '.gitignore' + - 'LICENSE' + - 'README.md' + workflow_dispatch: jobs: run: name: Run action - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest # skip build on '[ci skip]' if: "!contains(github.event.head_commit.message, '[ci skip]')" @@ -18,24 +29,28 @@ jobs: with: submodules: true - - name: Setup SourcePawn 1.11 Compiler - uses: rumblefrog/setup-sp@master - with: - version: "1.11.x" - - - name: Compile plugins 1.11 + - name: Download dependencies shell: bash run: | - bash .github/nv_compile.sh - bash + mkdir -p addons/sourcemod/scripting/include/multicolors + mkdir -p addons/sourcemod/scripting/include/ripext + curl -o addons/sourcemod/scripting/include/multicolors.inc https://raw.githubusercontent.com/Bara/Multi-Colors/master/addons/sourcemod/scripting/include/multicolors.inc + curl -o addons/sourcemod/scripting/include/multicolors/colors.inc https://raw.githubusercontent.com/Bara/Multi-Colors/master/addons/sourcemod/scripting/include/multicolors/colors.inc + curl -o addons/sourcemod/scripting/include/multicolors/morecolors.inc https://raw.githubusercontent.com/Bara/Multi-Colors/master/addons/sourcemod/scripting/include/multicolors/morecolors.inc + curl -o addons/sourcemod/scripting/include/ripext.inc https://raw.githubusercontent.com/ErikMinekus/sm-ripext/main/pawn/scripting/include/ripext.inc + curl -o addons/sourcemod/scripting/include/ripext/http.inc https://raw.githubusercontent.com/ErikMinekus/sm-ripext/main/pawn/scripting/include/ripext/http.inc + curl -o addons/sourcemod/scripting/include/ripext/json.inc https://raw.githubusercontent.com/ErikMinekus/sm-ripext/main/pawn/scripting/include/ripext/json.inc + curl -o addons/sourcemod/scripting/include/sbpp_version.inc https://raw.githubusercontent.com/sbpp/sourcebans-pp/refs/heads/main/game/addons/sourcemod/scripting/include/sbpp_version.inc + curl -o addons/sourcemod/scripting/include/sourcebanschecker.inc https://raw.githubusercontent.com/sbpp/sourcebans-pp/refs/heads/main/game/addons/sourcemod/scripting/include/sourcebanschecker.inc + curl -o addons/sourcemod/scripting/include/sourcebanspp.inc https://raw.githubusercontent.com/sbpp/sourcebans-pp/refs/heads/main/game/addons/sourcemod/scripting/include/sourcebanspp.inc + curl -o addons/sourcemod/scripting/include/sourcecomms.inc https://raw.githubusercontent.com/sbpp/sourcebans-pp/refs/heads/main/game/addons/sourcemod/scripting/include/sourcecomms.inc - - - name: Setup SourcePawn 1.12 Compiler + - name: Setup SourcePawn 1.13 Compiler uses: rumblefrog/setup-sp@master with: - version: "1.12.x" + version: "1.13.7301" - - name: Compile plugins 1.12 + - name: Compile plugins 1.13 shell: bash run: | bash .github/nv_compile.sh @@ -61,3 +76,4 @@ jobs: with: tag_name: workflow-build${{ github.run_number }} files: build/nativevotes* + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..13f81bb --- /dev/null +++ b/.gitignore @@ -0,0 +1,84 @@ +*.bat +*.sh +*.exe +*.log +*.smx + +.ref + +admin.inc +adminmenu.inc +adt_array.inc +adt_stack.inc +adt_trie.inc +adt.inc +banning.inc +basecomm.inc +bitbuffer.inc +clientprefs.inc +clients.inc +commandfilters.inc +commandline.inc +console.inc +convars.inc +core.inc +cstrike.inc +datapack.inc +dbi.inc +dhooks.inc +entity_prop_stocks.inc +entity.inc +entitylump.inc +events.inc +files.inc +float.inc +functions.inc +geoip.inc +halflife.inc +handles.inc +helpers.inc +keyvalues.inc +lang.inc +logging.inc +mapchooser.inc +menus.inc +nextmap.inc +profiler.inc +protobuf.inc +regex.inc +sdkhooks.inc +sdktools_client.inc +sdktools_engine.inc +sdktools_entinput.inc +sdktools_entoutput.inc +sdktools_functions.inc +sdktools_gamerules.inc +sdktools_hooks.inc +sdktools_sound.inc +sdktools_stocks.inc +sdktools_stringtables.inc +sdktools_tempents_stocks.inc +sdktools_tempents.inc +sdktools_trace.inc +sdktools_variant_t.inc +sdktools_voice.inc +sdktools.inc +sorting.inc +sourcemod.inc +string.inc +testing.inc +textparse.inc +tf2_stocks.inc +tf2.inc +timers.inc +topmenus.inc +usermessages.inc +vector.inc +version_auto.inc +version.inc +virtual_address.inc + +steam* +ripext* +multicolors* +source*.inc \ No newline at end of file diff --git a/README.md b/README.md index 60b6b59..a993071 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,114 @@ -sourcemod-nativevotes-updated -===================== - -### NativeVotes SourceMod plugin, with the following fixes: - -@nosoop -- [Prevent NativeVote menu callback from freeing in-use forward handle](https://github.com/powerlord/sourcemod-nativevotes/pull/3) -- [Hotfix for 2022-06-22 Update](https://github.com/sapphonie/sourcemod-nativevotes-updated/pull/11) - -@rowedahelicon -- [Fixed MvM mission votes from causing an Invalid forward handle](https://github.com/powerlord/sourcemod-nativevotes/pull/8) - -@Blueberryy -- [Added Russian translation](https://github.com/powerlord/sourcemod-nativevotes/pull/7) - -@arthurdead -- [Sourcemod 1.11 syntax update](https://github.com/arthurdead/sourcemod-nativevotes/tree/sm111-fix) - -@sapphonie (me) -- [Stop memleak with FireToClient](https://github.com/powerlord/sourcemod-nativevotes/pull/9) - -@justkamiii -- [Fix caps](https://github.com/sapphonie/sourcemod-nativevotes-updated/pull/7) - -@HotoCocoaco -- [Add chi translation](https://github.com/sapphonie/sourcemod-nativevotes-updated/pull/8) - -@fdxx -- [Fixed L4D2_VotePass](https://github.com/sapphonie/sourcemod-nativevotes-updated/pull/11) - -@satanskitty -- [danish and turkish translations](https://github.com/sapphonie/sourcemod-nativevotes-updated/pull/17) - -@Spaenny -- [added german translation](https://github.com/sapphonie/sourcemod-nativevotes-updated/pull/18) - -@iBoonie -- [fixed a bug where you can spam invalid votes and forcibly prematurely change the level](https://github.com/sapphonie/sourcemod-nativevotes-updated/pull/20) -- [removed an unnecessary ThrowNativeError](https://github.com/sapphonie/sourcemod-nativevotes-updated/pull/21) - -This repo also autoreleases all changes to master, compiled on SM 1.11 and SM 1.12. Check out the latest release [here](https://github.com/sapphonie/sourcemod-nativevotes-updated/releases/latest). - -NativeVotes is a SourceMod Plugin that lets plugins use the L4D/L4D2/TF2/CSGO built-in vote screens. -It supplies an API that plugins can use to create and display these votes. - -It replaces the BuiltinVotes SourceMod extension and has a few new features as well as bugfixes. - +![](https://wiki.teamfortress.com/w/images/8/88/Voting_YYN.png) +# / NativeVotes — Continued! +This fork aims to expand upon [ sapphonie](https://github.com/sapphonie)['s work](https://github.com/sapphonie/sourcemod-nativevotes-updated). + +> [!NOTE] +> If you want to benefit from this repository's features without NativeVotes, you may safely unload `nativevotes.smx`. Modules will simply fall back to Radio Menus. + +> [!WARNING] +> This plugin has only been tested in [ **Team Fortress 2**](https://store.steampowered.com/app/440)‼ If it doesn't work in any other game, open an [issue](https://github.com/Heapons/sourcemod-nativevotes-updated/issues/new). + +# [Differences](https://github.com/sapphonie/sourcemod-nativevotes-updated/compare/master...Heapons:sourcemod-nativevotes-updated:master) +## General +- Include [NativeVotes BaseVotes and FunVotes](https://github.com/powerlord/sourcemod-nativevotes-basevotes) in this repository. + - Show player avatar on vote panel (if the game supports it). + - Applies to: [voteban](https://github.com/Heapons/sourcemod-nativevotes-updated/blob/master/addons/sourcemod/scripting/nativevotes_basevotes/voteban.sp), [votekick](https://github.com/Heapons/sourcemod-nativevotes-updated/blob/master/addons/sourcemod/scripting/nativevotes_basevotes/votekick.sp), [voteburn](https://github.com/Heapons/sourcemod-nativevotes-updated/blob/master/addons/sourcemod/scripting/nativevotes_funvotes/voteburn.sp), [voteslay](https://github.com/Heapons/sourcemod-nativevotes-updated/blob/master/addons/sourcemod/scripting/nativevotes_funvotes/voteslay.sp). +- Chat tweaks. + - Team-colored player names. + - Highlight map names. +- Update [Nominations](https://github.com/Heapons/sourcemod-nativevotes-updated/blob/master/addons/sourcemod/scripting/nativevotes_nominations.sp) and [Rock The Vote](https://github.com/Heapons/sourcemod-nativevotes-updated/blob/master/addons/sourcemod/scripting/nativevotes_rockthevote.sp) to be on par with the latest [Sourcemod](https://github.com/alliedmodders/sourcemod/tree/master/plugins) version. +- Vote Progress HUD: + - Added `nativevotes_progress_hintcaption` convar to use the style of [game instructor hints](https://developer.valvesoftware.com/wiki/Env_instructor_hint) for supported games. + +## Team Fortress 2 +- Fixes: + - ✔️/❌ vote counts. + - Proper sounds get played upon selecting an item in a **multi-choice** (≤5 options) vote. + - Temporarily reverted. See: https://github.com/Heapons/sourcemod-nativevotes-updated/issues/2#issuecomment-3825973400. + - **Change VIP** vote issue now shows up again. +- Add [`sm_voterp`](https://github.com/Heapons/sourcemod-nativevotes-updated/blob/master/addons/sourcemod/scripting/nativevotes_voterp.sp). + - Controls `tf_medieval_autorp` cvar. +- Maplists in **Vote Setup** now work in **Mann Vs. Machine** as well. +- Added `sm_callvote` command and `callvote` chat trigger to open **Vote Setup**. + +## [Rock The Vote](https://github.com/Heapons/sourcemod-nativevotes-updated/blob/master/addons/sourcemod/scripting/nativevotes_rockthevote.sp) +- Admin commands: + - `sm_forcertv`. + - Force a RTV. + - `sm_resetrtv`. + - Reset RTV counts. +- Allow players to retract their rock-the-vote. + - Execute `sm_rtv` again to undo. +--- +|Name|Default Value|Description| +|-|-|-| +|`sm_rtv_needed`|`0.60`|Percentage of players needed to rockthevote| +|`sm_rtv_minplayers`|`0`|Number of players required before RTV will be enabled| +|`sm_rtv_initialdelay`|`30.0`|Time (in seconds) before first RTV can be held| +|`sm_rtv_interval`|`240.0`|Time (in seconds) after a failed RTV before another can be held| +|`sm_rtv_changetime`|`0`|When to change the map after a successful RTV: 0 - Instant, 1 - RoundEnd, 2 - MapEnd| +|`sm_rtv_postvoteaction`|`0`|What to do with RTV's after a mapvote has completed.
0 - Allow (success = instant change), 1 - Deny| + +## [Nominations](https://github.com/Heapons/sourcemod-nativevotes-updated/blob/master/addons/sourcemod/scripting/nativevotes_nominations.sp) +- Support partial map name matches +--- +|Name|Default Value|Description| +|-|-|-| +|`sm_nominate_excludeold`|`1`|Specifies if the current map should be excluded from the Nominations list| +|`sm_nominate_excludecurrent`|`1`|Specifies if the MapChooser excluded maps should also be excluded from Nominations| +|`sm_nominate_maxfound`|`0`|Maximum number of nomination matches to add to the menu.
0 = infinite| + +## [MapChooser](https://github.com/Heapons/sourcemod-nativevotes-updated/blob/master/addons/sourcemod/scripting/nativevotes_mapchooser.sp) +- Clean up workshop maps to reduce disk size. +- Automatically generate the mapcycle file. + - Import Workshop map collections (requires [Rest In Pawn](https://github.com/srcdslab/sm-ext-ripext) in order to use that feature). + +--- +|Name|Default Value|Description| +|-|-|-| +|`sm_mapvote_endvote`|`1`|Specifies if MapChooser should run an end of map vote| +|`sm_mapvote_start`|`3.0`|Specifies when to start the vote based on time remaining (in minutes)| +|`sm_mapvote_startround`|`2.0`|Specifies when to start the vote based on rounds remaining. Use '0' on TF2 to start vote during bonus round time| +|`sm_mapvote_startfrags`|`5.0`|Specifies when to start the vote based on frags remaining| +|`sm_extendmap_timestep`|`15`|Specifies how many more minutes each extension makes| +|`sm_extendmap_roundstep`|`5`|Specifies how many more rounds each extension makes| +|`sm_extendmap_fragstep`|`10`|Specifies how many more frags are allowed when map is extended| +|`sm_mapvote_exclude`|`5`|Specifies how many past maps to exclude from the vote| +|`sm_mapvote_include`|`5`|Specifies how many maps to include in the vote| +|`sm_mapvote_novote`|`1`|Specifies whether MapChooser should pick a map if no votes are received| +|`sm_mapvote_extend`|`0`|Number of extensions allowed each map| +|`sm_mapvote_dontchange`|`1`|Specifies if a 'Don't Change' option should be added to early votes| +|`sm_mapvote_voteduration`|`20`|Specifies how long the mapvote should be available for (in seconds)| +|`sm_mapvote_runoff`|`0`|Hold runoff votes if winning choice is less than a certain margin| +|`sm_mapvote_runoffpercent`|`50`|If winning choice has less than this percent of votes, hold a runoff| +|`sm_mapvote_shuffle_nominations`|`0`|If set, allows infinite nominations and picks a random subset to appear in the vote.| +|`sm_mapcycle_auto`|`0`|Specifies whether to automatically populate the maps list.| +|`sm_mapcycle_exclude`|`.*test.*\|background01\|^tr.*$`|Specifies which maps shouldn't be automatically added (regex pattern).| +| `sm_workshop_map_collection`|` `|Specifies the workshop collection to fetch the maps from.| +| `sm_workshop_map_cleanup`|`0`|Specifies whether to automatically cleanup workshop maps on map change| + +## [Scramble Teams](https://github.com/Heapons/sourcemod-nativevotes-updated/blob/master/addons/sourcemod/scripting/nativevotes_votescramble.sp) +> This is a module that makes Scramble Team votes behave like Rock The Vote. + +- Player commands: + - `sm_votescramble` (alias: `sm_scramble`). + - Attempt and request a Scramble Teams vote. +- Admin commands: + - `sm_forcescramble`. + - Force a scramble teams vote. + - `sm_resetscramble`. + - Reset scramble teams counts. +- Allow players to retract their scramble teams vote. + - Execute `sm_votescramble` or `sm_scramble` again to undo. + +> [!NOTE] +> It overrides built-in **Scramble Teams** vote issue. + +--- +|Name|Default Value|Description| +|-|-|-| +|`sm_scrambleteams_needed`|`0.60`|Percentage of players needed to scramble teams| +|`sm_scrambleteams_minplayers`|`0`|Number of players required before scramble will be enabled| +|`sm_scrambleteams_initialdelay`|`30.0`|Time (in seconds) before first scramble can be held| +|`sm_scrambleteams_interval`|`240.0`|Time (in seconds) after a failed scramble before another can be held| +|`sm_scrambleteams_full_reset`|`1`|Whether time/rounds played should reset after a scramble is triggered| \ No newline at end of file diff --git a/addons/sourcemod/scripting/csgo_votestart_test.sp b/addons/sourcemod/scripting/csgo_votestart_test.sp deleted file mode 100644 index be36804..0000000 --- a/addons/sourcemod/scripting/csgo_votestart_test.sp +++ /dev/null @@ -1,240 +0,0 @@ -/** - * vim: set ts=4 : - * ============================================================================= - * CSGO VoteStart Multiple Test - * Test CS:GO Start Vote Panel - * - * CSGO VoteStart Multiple Test(C) 2014 Ross Bemrose (Powerlord). All rights reserved. - * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. - * ============================================================================= - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License, version 3.0, as published by the - * Free Software Foundation. - * - * This program 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 the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - * - * As a special exception, AlliedModders LLC gives you permission to link the - * code of this program (as well as its derivative works) to "Half-Life 2," the - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software - * by the Valve Corporation. You must obey the GNU General Public License in - * all respects for all other code used. Additionally, AlliedModders LLC grants - * this exception to all derivative works. AlliedModders LLC defines further - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), - * or . - * - * Version: $Id$ - */ - -#include -#include - -#pragma semicolon 1 - -#define VERSION "1.1" - -new bool:g_bVoteActive = false; - -public Plugin:myinfo = -{ - name = "CSGO VoteStart Multiple Test", - author = "Powerlord", - description = "Test CS:GO VoteStart Panels", - version = VERSION, - url = "https://forums.alliedmods.net/showthread.php?t=208008" -} - -public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max) -{ - if (GetEngineVersion() != Engine_CSGO) - { - strcopy(error, err_max, "Only runs on CS:GO"); - return APLRes_Failure; - } - return APLRes_Success; -} - -public OnPluginStart() -{ - LoadTranslations("core.phrases"); - - RegAdminCmd("votemulti", Cmd_VoteMulti, ADMFLAG_VOTE, "Multiple Choice Vote"); - RegAdminCmd("voteyesno", Cmd_VoteYesNo, ADMFLAG_VOTE, "YesNo Choice Vote"); - RegAdminCmd("voteclear", Cmd_VoteFail, ADMFLAG_VOTE, "Multiple Choice Vote"); - - AddCommandListener(Listener_Vote, "vote"); -} - -public Action:Cmd_VoteMulti(client, args) -{ - new entity = FindEntityByClassname(-1, "vote_controller"); - if (entity > -1) - { - SetEntProp(entity, Prop_Send, "m_bIsYesNoVote", false); - //SetEntProp(entity, Prop_Send, "m_iActiveIssueIndex", 3); - SetEntProp(entity, Prop_Send, "m_iOnlyTeamToVote", -1); - for (new i = 0; i < 5; i++) - { - SetEntProp(entity, Prop_Send, "m_nVoteOptionCount", 0, _, i); - } - } - - new Handle:optionsEvent = CreateEvent("vote_options"); - - SetEventString(optionsEvent, "option1", "de_dust"); - SetEventString(optionsEvent, "option2", "de_dust2"); - SetEventString(optionsEvent, "option3", "cs_italy"); - SetEventString(optionsEvent, "option4", "de_sugarcane"); - SetEventString(optionsEvent, "option5", "ar_shoots"); - SetEventInt(optionsEvent, "count", 5); - FireEvent(optionsEvent); - - if (entity > -1) - { - SetEntProp(entity, Prop_Send, "m_nPotentialVotes", GetClientCount(true)); - } - - LogMessage("Finished setting vote_controller properties."); - - g_bVoteActive = true; - - CreateTimer(0.2, Timer_StartMultiVote, GetClientUserId(client)); - return Plugin_Handled; -} - -public Action:Timer_StartMultiVote(Handle:timer, any:userid) -{ - new client = GetClientOfUserId(userid); - if (client <= 0 && client > MaxClients) - { - return Plugin_Continue; - } - - new Handle:voteStart = StartMessageAll("VoteStart", USERMSG_RELIABLE); - PbSetInt(voteStart, "team", -1); - PbSetInt(voteStart, "ent_idx", 99); - PbSetString(voteStart, "disp_str", "#SFUI_vote_nextlevel_choices"); - PbSetString(voteStart, "details_str", ""); - PbSetBool(voteStart, "is_yes_no_vote", false); - PbSetString(voteStart, "other_team_str", "#SFUI_otherteam_vote_unimplemented"); - PbSetInt(voteStart, "vote_type", 1); - EndMessage(); - - return Plugin_Stop; -} - - -public Action:Cmd_VoteYesNo(client, args) -{ - new entity = FindEntityByClassname(-1, "vote_controller"); - if (entity > -1) - { - SetEntProp(entity, Prop_Send, "m_bIsYesNoVote", true); - //SetEntProp(entity, Prop_Send, "m_iActiveIssueIndex", 2); - SetEntProp(entity, Prop_Send, "m_iOnlyTeamToVote", -1); - for (new i = 0; i < 5; i++) - { - SetEntProp(entity, Prop_Send, "m_nVoteOptionCount", 0, _, i); - } - } - - new Handle:optionsEvent = CreateEvent("vote_options"); - SetEventString(optionsEvent, "option1", "Yes"); - SetEventString(optionsEvent, "option2", "No"); - SetEventString(optionsEvent, "option3", ""); - SetEventString(optionsEvent, "option4", ""); - SetEventString(optionsEvent, "option5", ""); - SetEventInt(optionsEvent, "count", 2); - FireEvent(optionsEvent); - - if (entity > -1) - { - SetEntProp(entity, Prop_Send, "m_nPotentialVotes", GetClientCount(true)); - } - - g_bVoteActive = true; - - LogMessage("Finished setting vote_controller properties."); - - CreateTimer(0.2, Timer_StartYesNoVote, GetClientUserId(client)); - return Plugin_Handled; -} - -public Action:Timer_StartYesNoVote(Handle:timer, any:userid) -{ - new client = GetClientOfUserId(userid); - if (client <= 0 && client > MaxClients) - { - return Plugin_Continue; - } - - new Handle:voteStart = StartMessageAll("VoteStart", USERMSG_RELIABLE); - PbSetInt(voteStart, "team", -1); - PbSetInt(voteStart, "ent_idx", client); - PbSetString(voteStart, "disp_str", "#SFUI_vote_changelevel"); - PbSetString(voteStart, "details_str", "de_dust2"); - PbSetBool(voteStart, "is_yes_no_vote", true); - PbSetString(voteStart, "other_team_str", "#SFUI_otherteam_vote_unimplemented"); - PbSetInt(voteStart, "vote_type", 1); - EndMessage(); - - return Plugin_Stop; -} - -public Action:Cmd_VoteFail(client, args) -{ - if (g_bVoteActive) - VoteFail(); - - return Plugin_Handled; -} - -public Action:Listener_Vote(client, const String:command[], argc) -{ - if (!g_bVoteActive) - { - return Plugin_Continue; - } - - new ReplySource:oldSource = SetCmdReplySource(SM_REPLY_TO_CHAT); - ReplyToCommand(client, "Caught vote."); - - VoteFail(); - - SetCmdReplySource(oldSource); - return Plugin_Handled; -} - -VoteFail() -{ - g_bVoteActive = false; - new Handle:voteFailed = StartMessageAll("VoteFailed", USERMSG_RELIABLE); - - PbSetInt(voteFailed, "team", -1); - PbSetInt(voteFailed, "reason", 0); - EndMessage(); - - CreateTimer(5.0, Timer_ResetData); -} - -public Action:Timer_ResetData(Handle:timer) -{ - new entity = FindEntityByClassname(-1, "vote_controller"); - if (entity > -1) - { - //SetEntProp(entity, Prop_Send, "m_iActiveIssueIndex", -1); - for (new i = 0; i < 5; i++) - { - SetEntProp(entity, Prop_Send, "m_nVoteOptionCount", 0, _, i); - } - SetEntProp(entity, Prop_Send, "m_nPotentialVotes", 0); - SetEntProp(entity, Prop_Send, "m_iOnlyTeamToVote", -1); - SetEntProp(entity, Prop_Send, "m_bIsYesNoVote", true); - } -} \ No newline at end of file diff --git a/addons/sourcemod/scripting/include/nativevotes.inc b/addons/sourcemod/scripting/include/nativevotes.inc index 426e6d1..65be07b 100644 --- a/addons/sourcemod/scripting/include/nativevotes.inc +++ b/addons/sourcemod/scripting/include/nativevotes.inc @@ -31,8 +31,7 @@ #include #include - -// NativeVotes 1.1 series +#include #if defined _nativevotes_included #endinput @@ -84,65 +83,69 @@ MenuAction_DisplayItem Item text is being drawn to the display (param1=client, p */ enum NativeVotesType { - NativeVotesType_None = 0, /**< Special placeholder for callvote with no arguments for NativeVotes_OnCallVote */ - NativeVotesType_Custom_YesNo, /**< Yes/No, details are vote text. */ - NativeVotesType_Custom_Mult, /**< TF2/CS:GO: Multiple-choice, details are vote text. */ - NativeVotesType_ChgCampaign, /**< L4D/L4D2: Yes/No, details are campaign name */ - NativeVotesType_ChgDifficulty, /**< L4D/L4D2: Yes/No, details are difficulty number in L4D/L4D2 */ - NativeVotesType_ReturnToLobby, /**< L4D/L4D2: Yes/No, details are ignored */ - NativeVotesType_AlltalkOn, /**< L4D2: Yes/No, details are ignored (handled internally by extension) */ - NativeVotesType_AlltalkOff, /**< L4D2: Yes/No, details are ignored (handled internally by extension) */ - NativeVotesType_Restart, /**< Yes/No, details are ignored */ - NativeVotesType_Kick, /**< Yes/No, target is player userid, details are auto-set by target */ - NativeVotesType_KickIdle, /**< TF2/CS:GO: Yes/No, target is player userid, details are auto-set by target */ - NativeVotesType_KickScamming, /**< TF2/CS:GO: Yes/No, target is player userid, details are auto-set by target */ - NativeVotesType_KickCheating, /**< TF2/CS:GO: Yes/No, target is player userid, details are auto-set by target */ - NativeVotesType_ChgLevel, /**< Yes/No, details are level number in L4D/L4D2 or map name in TF2 */ - NativeVotesType_NextLevel, /**< TF2/CS:GO: Yes/No, details are map name */ - NativeVotesType_NextLevelMult, /**< TF2/CS:GO: Multiple-choice, details are ignored */ - NativeVotesType_ScrambleNow, /**< TF2/CS:GO: Yes/No, details are ignored */ - NativeVotesType_ScrambleEnd, /**< TF2: Yes/No, details are ignored */ - NativeVotesType_ChgMission, /**< TF2: Yes/No, details are popfile */ - NativeVotesType_SwapTeams, /**< CS:GO: Yes/No, details are ignored */ - NativeVotesType_Surrender, /**< CS:GO: Yes/No, details are ignored */ - NativeVotesType_Rematch, /**< CS:GO: Yes/No, details are ignored */ - NativeVotesType_Continue, /**< CS:GO: Yes/No, details are ignored */ - NativeVotesType_StartRound, /**< TF2: Yes/No, details are ignored */ - NativeVotesType_Eternaween, /**< TF2: Yes/No, details are ignored */ - NativeVotesType_AutoBalanceOn, /**< TF2: Yes/No, details are ignored */ - NativeVotesType_AutoBalanceOff, /**< TF2: Yes/No, details are ignored */ - NativeVotesType_ClassLimitsOn, /**< TF2: Yes/No, details are ignored */ - NativeVotesType_ClassLimitsOff, /**< TF2: Yes/No, details are ignored */ - NativeVotesType_Extend, /**< TF2: Yes/No, details are ignored */ + NativeVotesType_None = 0, /**< Special placeholder for callvote with no arguments for NativeVotes_OnCallVote */ + NativeVotesType_Custom_YesNo, /**< Yes/No, details are vote text. */ + NativeVotesType_Custom_Mult, /**< TF2/CS:GO: Multiple-choice, details are vote text. */ + NativeVotesType_ChgCampaign, /**< L4D/L4D2: Yes/No, details are campaign name */ + NativeVotesType_ChgDifficulty, /**< L4D/L4D2/Fortress Connected: Yes/No, details are difficulty number */ + NativeVotesType_ReturnToLobby, /**< L4D/L4D2: Yes/No, details are ignored */ + NativeVotesType_AlltalkOn, /**< L4D2: Yes/No, details are ignored (handled internally by extension) */ + NativeVotesType_AlltalkOff, /**< L4D2: Yes/No, details are ignored (handled internally by extension) */ + NativeVotesType_Restart, /**< Yes/No, details are ignored */ + NativeVotesType_Kick, /**< Yes/No, target is player userid, details are auto-set by target */ + NativeVotesType_KickIdle, /**< TF2/CS:GO: Yes/No, target is player userid, details are auto-set by target */ + NativeVotesType_KickScamming, /**< TF2/CS:GO: Yes/No, target is player userid, details are auto-set by target */ + NativeVotesType_KickCheating, /**< TF2/CS:GO: Yes/No, target is player userid, details are auto-set by target */ + NativeVotesType_ChgLevel, /**< Yes/No, details are level number in L4D/L4D2 or map name in TF2 */ + NativeVotesType_NextLevel, /**< TF2/CS:GO: Yes/No, details are map name */ + NativeVotesType_NextLevelMult, /**< TF2/CS:GO: Multiple-choice, details are ignored */ + NativeVotesType_ScrambleNow, /**< TF2/CS:GO: Yes/No, details are ignored */ + NativeVotesType_ScrambleEnd, /**< TF2: Yes/No, details are ignored */ + NativeVotesType_ChgMission, /**< TF2: Yes/No, details are popfile */ + NativeVotesType_SwapTeams, /**< CS:GO: Yes/No, details are ignored */ + NativeVotesType_Surrender, /**< CS:GO: Yes/No, details are ignored */ + NativeVotesType_Rematch, /**< CS:GO: Yes/No, details are ignored */ + NativeVotesType_Continue, /**< CS:GO: Yes/No, details are ignored */ + NativeVotesType_StartRound, /**< TF2: Yes/No, details are ignored */ + NativeVotesType_Eternaween, /**< TF2: Yes/No, details are ignored */ + NativeVotesType_AutoBalanceOn, /**< TF2: Yes/No, details are ignored */ + NativeVotesType_AutoBalanceOff, /**< TF2: Yes/No, details are ignored */ + NativeVotesType_ClassLimitsOn, /**< TF2: Yes/No, details are ignored */ + NativeVotesType_ClassLimitsOff, /**< TF2: Yes/No, details are ignored */ + NativeVotesType_Extend, /**< TF2: Yes/No, details are ignored */ + NativeVotesType_ChgCivilian, /**< TF2 Classified: Yes/No, target is player userid, details are auto-set by target */ + NativeVotesType_ChgGamemode /**< Fortress Connected: Yes/No, details are gamemode number */ }; enum NativeVotesPassType { - NativeVotesPass_None = 0, /**< Special placeholder for error value */ - NativeVotesPass_Custom, /**< Details are custom pass message */ - NativeVotesPass_ChgCampaign, /**< L4D/L4D2: Details are campaign name */ - NativeVotesPass_ChgDifficulty, /**< L4D/L4D2/TF2: Details are difficulty number in L4D/L4D2 and mission name in TF2 */ - NativeVotesPass_ReturnToLobby, /**< L4D/L4D2: Details are ignored */ - NativeVotesPass_AlltalkOn, /**< L4D2: Details are ignored */ - NativeVotesPass_AlltalkOff, /**< L4D2: Details are ignored */ - NativeVotesPass_Restart, /**< Details are ignored */ - NativeVotesPass_Kick, /**< Details are player name */ - NativeVotesPass_ChgLevel, /**< Details are level number in L4D/L4D2 or map name in TF2/CS:GO */ - NativeVotesPass_NextLevel, /**< TF2/CS:GO: Details are map name */ - NativeVotesPass_Extend, /**< TF2/CS:GO: Details are ignored */ - NativeVotesPass_Scramble, /**< TF2/CS:GO: Details are ignored */ - NativeVotesPass_ChgMission, /**< TF2: Details are popfile */ - NativeVotesPass_SwapTeams, /**< CS:GO: Details are ignored */ - NativeVotesPass_Surrender, /**< CS:GO: Details are ignored */ - NativeVotesPass_Rematch, /**< CS:GO: Details are ignored */ - NativeVotesPass_Continue, /**< CS:GO: Details are ignored */ - NativeVotesPass_StartRound, /**< TF2: Details are ignored */ - NativeVotesPass_Eternaween, /**< TF2: Details are ignored */ - NativeVotesPass_AutoBalanceOn, /**< TF2: Yes/No, details are ignored */ - NativeVotesPass_AutoBalanceOff, /**< TF2: Yes/No, details are ignored */ - NativeVotesPass_ClassLimitsOn, /**< TF2: Yes/No, details are ignored */ - NativeVotesPass_ClassLimitsOff, /**< TF2: Yes/No, details are ignored */ -}; + NativeVotesPass_None = 0, /**< Special placeholder for error value */ + NativeVotesPass_Custom, /**< Details are custom pass message */ + NativeVotesPass_ChgCampaign, /**< L4D/L4D2: Details are campaign name */ + NativeVotesPass_ChgDifficulty, /**< L4D/L4D2/TF2/Fortress Connected: Details are difficulty number in L4D/L4D2/FC and mission name in TF2 */ + NativeVotesPass_ReturnToLobby, /**< L4D/L4D2: Details are ignored */ + NativeVotesPass_AlltalkOn, /**< L4D2: Details are ignored */ + NativeVotesPass_AlltalkOff, /**< L4D2: Details are ignored */ + NativeVotesPass_Restart, /**< Details are ignored */ + NativeVotesPass_Kick, /**< Details are player name */ + NativeVotesPass_ChgLevel, /**< Details are level number in L4D/L4D2 or map name in TF2/CS:GO */ + NativeVotesPass_NextLevel, /**< TF2/CS:GO: Details are map name */ + NativeVotesPass_Extend, /**< TF2/CS:GO: Details are ignored */ + NativeVotesPass_Scramble, /**< TF2/CS:GO: Details are ignored */ + NativeVotesPass_ChgMission, /**< TF2: Details are popfile */ + NativeVotesPass_SwapTeams, /**< CS:GO: Details are ignored */ + NativeVotesPass_Surrender, /**< CS:GO: Details are ignored */ + NativeVotesPass_Rematch, /**< CS:GO: Details are ignored */ + NativeVotesPass_Continue, /**< CS:GO: Details are ignored */ + NativeVotesPass_StartRound, /**< TF2: Details are ignored */ + NativeVotesPass_Eternaween, /**< TF2: Details are ignored */ + NativeVotesPass_AutoBalanceOn, /**< TF2: Yes/No, details are ignored */ + NativeVotesPass_AutoBalanceOff, /**< TF2: Yes/No, details are ignored */ + NativeVotesPass_ClassLimitsOn, /**< TF2: Yes/No, details are ignored */ + NativeVotesPass_ClassLimitsOff, /**< TF2: Yes/No, details are ignored */ + NativeVotesPass_ChgCivilian, /**< TF2 Classified: Yes/No, target is player userid, details are auto-set by target */ + NativeVotesPass_ChgGamemode /**< Fortress Connected: Yes/No, details are gamemode number */ +}; /** * Reasons a vote was canceled. Not used for L4D/L4D2, as they don't care @@ -238,7 +241,10 @@ enum NativeVotesOverride NativeVotesOverride_AutoBalance, NativeVotesOverride_ClassLimits, NativeVotesOverride_Extend, - NativeVotesOverride_Count, + NativeVotesOverride_ChgCivilian, + NativeVotesOverride_ChgGamemode, + NativeVotesOverride_ChgDifficulty, + NativeVotesOverride_Count } methodmap NativeVote < KeyValues @@ -521,11 +527,11 @@ native bool NativeVotes_IsVoteTypeSupported(NativeVotesType voteType); /** * Creates a new, empty vote. * - * @param handler Function which will receive vote actions. + * @param handler Function which will receive vote actions. * @param voteType Vote type, cannot be changed after set - * @param actions Optionally set which actions to receive. VoteStart, + * @param actions Optionally set which actions to receive. VoteStart, * VoteCancel, VoteEnd, and End will always be received - * regardless of whether they are set or not. They are + * regardless of whether they are set or not. They are * also the only default actions. * @return A new vote Handle on INVALID_HANDLE if a vote type is unsupported by this game. */ @@ -1395,7 +1401,7 @@ typedef NativeVotes_CallVoteVisCheck = function Action (int client, NativeVotesO * Are vote commands supported by this game? * * @return true if they are supported, false if not. - * As of this writing, only TF2 will return true + * As of this writing, only TF2 will return true */ native bool NativeVotes_AreVoteCommandsSupported(); @@ -1502,6 +1508,37 @@ public SharedPlugin __pl_nativevotes = #if !defined REQUIRE_PLUGIN public void __pl_nativevotes_SetNTVOptional() { + MarkNativeAsOptional("NativeVote.NativeVote"); + MarkNativeAsOptional("NativeVote.Close"); + MarkNativeAsOptional("NativeVote.AddItem"); + MarkNativeAsOptional("NativeVote.InsertItem"); + MarkNativeAsOptional("NativeVote.RemoveItem"); + MarkNativeAsOptional("NativeVote.RemoveAllItems"); + MarkNativeAsOptional("NativeVote.GetItem"); + MarkNativeAsOptional("NativeVote.SetDetails"); + MarkNativeAsOptional("NativeVote.GetDetails"); + MarkNativeAsOptional("NativeVote.SetTitle"); + MarkNativeAsOptional("NativeVote.GetTitle"); + MarkNativeAsOptional("NativeVote.SetTarget"); + MarkNativeAsOptional("NativeVote.GetTarget"); + MarkNativeAsOptional("NativeVote.GetTargetSteam"); + MarkNativeAsOptional("NativeVote.DisplayVote"); + MarkNativeAsOptional("NativeVote.DisplayVoteToAll"); + MarkNativeAsOptional("NativeVote.DisplayPass"); + MarkNativeAsOptional("NativeVote.DisplayPassCustomToOne"); + MarkNativeAsOptional("NativeVote.DisplayPassCustom"); + MarkNativeAsOptional("NativeVote.DisplayPassEx"); + MarkNativeAsOptional("NativeVote.DisplayFail"); + MarkNativeAsOptional("NativeVote.OptionFlags.get"); + MarkNativeAsOptional("NativeVote.OptionFlags.set"); + MarkNativeAsOptional("NativeVote.NoVoteButton.set"); + MarkNativeAsOptional("NativeVote.VoteResultCallback.set"); + MarkNativeAsOptional("NativeVote.ItemCount.get"); + MarkNativeAsOptional("NativeVote.VoteType.get"); + MarkNativeAsOptional("NativeVote.Team.get"); + MarkNativeAsOptional("NativeVote.Team.set"); + MarkNativeAsOptional("NativeVote.Initiator.get"); + MarkNativeAsOptional("NativeVote.Initiator.set"); MarkNativeAsOptional("NativeVotes_IsVoteTypeSupported"); MarkNativeAsOptional("NativeVotes_Create"); MarkNativeAsOptional("NativeVotes_Close"); @@ -1550,4 +1587,4 @@ public void __pl_nativevotes_SetNTVOptional() MarkNativeAsOptional("NativeVotes_RedrawVoteTitle"); MarkNativeAsOptional("NativeVotes_RedrawVoteItem"); } -#endif +#endif \ No newline at end of file diff --git a/addons/sourcemod/scripting/nativevotes.sp b/addons/sourcemod/scripting/nativevotes.sp index 4de0473..e1e2081 100644 --- a/addons/sourcemod/scripting/nativevotes.sp +++ b/addons/sourcemod/scripting/nativevotes.sp @@ -1,2529 +1,2693 @@ -/** - * vim: set ts=4 : - * ============================================================================= - * NativeVotes - * NativeVotes is a voting API plugin for L4D, L4D2, TF2, and CS:GO. - * Based on the SourceMod voting API - * - * NativeVotes (C) 2011-2015 Ross Bemrose (Powerlord). All rights reserved. - * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. - * ============================================================================= - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License, version 3.0, as published by the - * Free Software Foundation. - * - * This program 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 the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - * - * As a special exception, AlliedModders LLC gives you permission to link the - * code of this program (as well as its derivative works) to "Half-Life 2," the - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software - * by the Valve Corporation. You must obey the GNU General Public License in - * all respects for all other code used. Additionally, AlliedModders LLC grants - * this exception to all derivative works. AlliedModders LLC defines further - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), - * or . - * - * Version: $Id$ - */ - -#include -#include - -#pragma semicolon 1 -#pragma newdecls required - -#include "include/nativevotes.inc" -#include "include/implodeexplode.inc" - -EngineVersion g_EngineVersion = Engine_Unknown; - -#include "nativevotes/data-keyvalues.sp" - -#define VERSION "1.1.1fix" - -#define LOGTAG "NV" - -#define MAX_VOTE_DETAILS_LENGTH 64 // From SourceSDK2013's shareddefs.h -#define TRANSLATION_LENGTH 192 - -#define VOTE_DELAY_TIME 3.0 - -// SourceMod uses these internally, so... we do too. -#define VOTE_NOT_VOTING -2 -#define VOTE_PENDING -1 - -#define MAX_VOTE_ISSUES 20 -#define VOTE_STRING_SIZE 32 - -//---------------------------------------------------------------------------- -// These values are swapped from their NativeVotes equivalent -#define L4D2_VOTE_YES_INDEX 1 -#define L4D2_VOTE_NO_INDEX 0 - -#define L4DL4D2_COUNT 2 -#define TF2CSGO_COUNT 5 - -#define MAX_CALLVOTE_SIZE 128 - -//#define LOG - -//---------------------------------------------------------------------------- -// Global Variables -int g_NextVote = 0; - -//---------------------------------------------------------------------------- -// CVars -ConVar g_Cvar_VoteHintbox; -ConVar g_Cvar_VoteChat; -ConVar g_Cvar_VoteConsole; -ConVar g_Cvar_VoteClientConsole; -ConVar g_Cvar_VoteDelay; - -//---------------------------------------------------------------------------- -// Used to track current vote data -//new Handle:g_hVoteTimer; -Handle g_hDisplayTimer; - -int g_Clients; -int g_TotalClients; -int g_Items; -ArrayList g_hVotes; -NativeVote g_hCurVote; -int g_curDisplayClient = 0; -char g_newMenuTitle[TRANSLATION_LENGTH]; -int g_curItemClient = 0; -char g_newMenuItem[TRANSLATION_LENGTH]; - -bool g_bStarted; -bool g_bCancelled; -int g_NumVotes; -int g_VoteTime; -int g_VoteFlags; -float g_fStartTime; -int g_TimeLeft; -int g_ClientVotes[MAXPLAYERS+1]; -bool g_bRevoting[MAXPLAYERS+1]; -char g_LeaderList[1024]; - -ConVar sv_vote_holder_may_vote_no; - -// Map list stuffs - -#define STRINGTABLE_NAME "ServerMapCycle" -#define STRINGTABLE_ITEM "ServerMapCycle" -//#define MAP_STRING_CACHE_SIZE PLATFORM_MAX_PATH * 256 - -// Forward -Handle g_OverrideMaps; -StringMap g_MapOverrides; -bool g_OverridesSet; -bool g_OverrideNextCallVote[MAXPLAYERS + 1]; - -enum struct CallVoteForwards -{ - Handle CallVote_Forward; - Handle CallVote_Vis; -} - -enum struct CallVoteListData -{ - NativeVotesOverride CallVoteList_VoteType; - bool CallVoteList_VoteEnabled; -} - -CallVoteForwards g_CallVotes[NativeVotesOverride_Count]; - -#include "nativevotes/game.sp" - -public Plugin myinfo = -{ - name = "NativeVotes", - author = "Powerlord", - description = "Voting API to use the game's native vote panels. Compatible with L4D, L4D2, TF2, and CS:GO.", - version = VERSION, - url = "https://forums.alliedmods.net/showthread.php?t=208008" -} - -public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) -{ - char engineName[64]; - if (!Game_IsGameSupported(engineName, sizeof(engineName))) - { - Format(error, err_max, "Unsupported game: %s", engineName); - //strcopy(error, err_max, "Unsupported game"); - return APLRes_Failure; - } - - CreateNative("NativeVotes_IsVoteTypeSupported", Native_IsVoteTypeSupported); - CreateNative("NativeVotes_Create", Native_Create); - CreateNative("NativeVotes_Close", Native_Close); - CreateNative("NativeVotes_Display", Native_Display); - CreateNative("NativeVotes_AddItem", Native_AddItem); - CreateNative("NativeVotes_InsertItem", Native_InsertItem); - CreateNative("NativeVotes_RemoveItem", Native_RemoveItem); - CreateNative("NativeVotes_RemoveAllItems", Native_RemoveAllItems); - CreateNative("NativeVotes_GetItem", Native_GetItem); - CreateNative("NativeVotes_GetItemCount", Native_GetItemCount); - CreateNative("NativeVotes_SetDetails", Native_SetDetails); - CreateNative("NativeVotes_GetDetails", Native_GetDetails); - CreateNative("NativeVotes_SetTitle", Native_SetTitle); - CreateNative("NativeVotes_GetTitle", Native_GetTitle); - CreateNative("NativeVotes_SetTarget", Native_SetTarget); - CreateNative("NativeVotes_GetTarget", Native_GetTarget); - CreateNative("NativeVotes_GetTargetSteam", Native_GetTargetSteam); - CreateNative("NativeVotes_IsVoteInProgress", Native_IsVoteInProgress); - CreateNative("NativeVotes_GetMaxItems", Native_GetMaxItems); - CreateNative("NativeVotes_SetOptionFlags", Native_SetOptionFlags); - CreateNative("NativeVotes_GetOptionFlags", Native_GetOptionFlags); - CreateNative("NativeVotes_SetNoVoteButton", Native_SetNoVoteButton); - CreateNative("NativeVotes_Cancel", Native_Cancel); - CreateNative("NativeVotes_SetResultCallback", Native_SetResultCallback); - CreateNative("NativeVotes_CheckVoteDelay", Native_CheckVoteDelay); - CreateNative("NativeVotes_IsClientInVotePool", Native_IsClientInVotePool); - CreateNative("NativeVotes_RedrawClientVote", Native_RedrawClientVote); - CreateNative("NativeVotes_GetType", Native_GetType); - CreateNative("NativeVotes_SetTeam", Native_SetTeam); - CreateNative("NativeVotes_GetTeam", Native_GetTeam); - CreateNative("NativeVotes_SetInitiator", Native_SetInitiator); - CreateNative("NativeVotes_GetInitiator", Native_GetInitiator); - CreateNative("NativeVotes_DisplayPass", Native_DisplayPass); - CreateNative("NativeVotes_DisplayPassCustomToOne", Native_DisplayPassCustomToOne); - CreateNative("NativeVotes_DisplayPassEx", Native_DisplayPassEx); - //CreateNative("NativeVotes_DisplayRawPass", Native_DisplayRawPass); - CreateNative("NativeVotes_DisplayRawPassToOne", Native_DisplayRawPassToOne); - CreateNative("NativeVotes_DisplayRawPassCustomToOne", Native_DisplayRawPassCustomToOne); - CreateNative("NativeVotes_DisplayFail", Native_DisplayFail); - CreateNative("NativeVotes_DisplayRawFail", Native_DisplayRawFail); - //CreateNative("NativeVotes_DisplayRawFailToOne", Native_DisplayRawFailToOne); - CreateNative("NativeVotes_AreVoteCommandsSupported", Native_AreVoteCommandsSupported); - CreateNative("NativeVotes_RegisterVoteCommand", Native_RegisterVoteCommand); - CreateNative("NativeVotes_UnregisterVoteCommand", Native_UnregisterVoteCommand); - CreateNative("NativeVotes_DisplayCallVoteFail", Native_DisplayCallVoteFail); - CreateNative("NativeVotes_RedrawVoteTitle", Native_RedrawVoteTitle); - CreateNative("NativeVotes_RedrawVoteItem", Native_RedrawVoteItem); - - // Transitional syntax support - CreateNative("NativeVote.NativeVote", Native_Create); - CreateNative("NativeVote.Close", Native_Close); - CreateNative("NativeVote.AddItem", Native_AddItem); - CreateNative("NativeVote.InsertItem", Native_InsertItem); - CreateNative("NativeVote.RemoveItem", Native_RemoveItem); - CreateNative("NativeVote.RemoveAllItems", Native_RemoveAllItems); - CreateNative("NativeVote.GetItem", Native_GetItem); - CreateNative("NativeVote.SetDetails", Native_SetDetails); - CreateNative("NativeVote.GetDetails", Native_GetDetails); - CreateNative("NativeVote.SetTitle", Native_SetTitle); - CreateNative("NativeVote.GetTitle", Native_GetTitle); - CreateNative("NativeVote.SetTarget", Native_SetTarget); - CreateNative("NativeVote.GetTarget", Native_GetTarget); - CreateNative("NativeVote.GetTargetSteam", Native_GetTargetSteam); - CreateNative("NativeVote.DisplayVote", Native_Display); - CreateNative("NativeVote.DisplayPass", Native_DisplayPass); - CreateNative("NativeVote.DisplayPassCustomToOne", Native_DisplayPassCustomToOne); - CreateNative("NativeVote.DisplayPassEx", Native_DisplayPassEx); - CreateNative("NativeVote.DisplayFail", Native_DisplayFail); - CreateNative("NativeVote.OptionFlags.set", Native_SetOptionFlags); - CreateNative("NativeVote.OptionFlags.get", Native_GetOptionFlags); - CreateNative("NativeVote.NoVoteButton.set", Native_SetNoVoteButton); - CreateNative("NativeVote.VoteResultCallback.set", Native_SetResultCallback); - CreateNative("NativeVote.ItemCount.get", Native_GetItemCount); - CreateNative("NativeVote.VoteType.get", Native_GetType); - CreateNative("NativeVote.Team.set", Native_SetTeam); - CreateNative("NativeVote.Team.get", Native_GetTeam); - CreateNative("NativeVote.Initiator.set", Native_SetInitiator); - CreateNative("NativeVote.Initiator.get", Native_GetInitiator); - - RegPluginLibrary("nativevotes"); - - return APLRes_Success; -} - -public void OnPluginStart() -{ - LoadTranslations("core.phrases"); - LoadTranslations("nativevotes.phrases.txt"); - - CreateConVar("nativevotes_version", VERSION, "NativeVotes API version", FCVAR_DONTRECORD | FCVAR_NOTIFY); - - g_Cvar_VoteHintbox = CreateConVar("nativevotes_progress_hintbox", "0", "Show current vote progress in a hint box", FCVAR_NONE, true, 0.0, true, 1.0); - g_Cvar_VoteChat = CreateConVar("nativevotes_progress_chat", "0", "Show current vote progress as chat messages", FCVAR_NONE, true, 0.0, true, 1.0); - g_Cvar_VoteConsole = CreateConVar("nativevotes_progress_console", "0", "Show current vote progress as console messages", FCVAR_NONE, true, 0.0, true, 1.0); - g_Cvar_VoteClientConsole = CreateConVar("nativevotes_progress_client_console", "0", "Show current vote progress as console messages to clients", FCVAR_NONE, true, 0.0, true, 1.0); - g_Cvar_VoteDelay = CreateConVar("nativevotes_vote_delay", "30", "Sets the recommended time in between public votes", FCVAR_NONE, true, 0.0); - - Game_InitializeCvars(); - - HookConVarChange(g_Cvar_VoteDelay, OnVoteDelayChange); - - AddCommandListener(Command_Vote, "vote"); // All games, command listeners aren't case sensitive - - sv_vote_holder_may_vote_no = FindConVar("sv_vote_holder_may_vote_no"); - - // The new version of the CallVote system is TF2 only - if (Game_AreVoteCommandsSupported()) - { - AddCommandListener(Command_CallVote, "callvote"); - - // None is type 0, which has no overrides - // As of 2015-09-28, there are 10 votes for a total of 20 private forwards created here. - for (int i = 1; i < sizeof(g_CallVotes); i++) - { - g_CallVotes[i].CallVote_Forward = CreateForward(ET_Hook, Param_Cell, Param_Cell, Param_String, Param_Cell, Param_Cell); - g_CallVotes[i].CallVote_Vis = CreateForward(ET_Hook, Param_Cell, Param_Cell); - } - - g_OverrideMaps = CreateGlobalForward("NativeVotes_OverrideMaps", ET_Hook, Param_Cell); - } - - g_hVotes = new ArrayList(1, Game_GetMaxItems()); - - AutoExecConfig(true, "nativevotes"); -} - -public void OnMapStart() -{ - // Map list stuffs - if (g_MapOverrides != null) - delete g_MapOverrides; - - g_OverridesSet = false; -} - -public Action Timer_RetryCallVote(Handle timer, any data) -{ - int client = GetClientOfUserId(data); - - if (client == 0) - { - return Plugin_Stop; - } - - FakeClientCommand(client, "callvote"); - return Plugin_Stop; -} - -public void ProcessMapList() -{ - int stringTableIndex = FindStringTable(STRINGTABLE_NAME); - int stringIndex = FindStringIndex(stringTableIndex, STRINGTABLE_ITEM); - - StringMap overrideList = new StringMap(); - - // Maplist resets every map - int length = GetStringTableDataLength(stringTableIndex, stringIndex); - char[] mapData = new char[length]; - GetStringTableData(stringTableIndex, stringIndex, mapData, length); - - // We'll get an extra blank entry if we don't do this - TrimString(mapData); - - ExplodeStringToStringMap(mapData, "\n", overrideList, PLATFORM_MAX_PATH, ImplodePart_Key); - - Action mapResult = Plugin_Continue; - Call_StartForward(g_OverrideMaps); - Call_PushCell(overrideList); - Call_Finish(mapResult); - - if (mapResult == Plugin_Changed && overrideList.Size > 0) - { -#if defined LOG - LogMessage("Overriding map list with %d maps", overrideList.Size); -#endif - - g_MapOverrides = overrideList; - - int maxLength = GetStringMapImplodeSize(overrideList, 1, ImplodePart_Key); - - char[] newMapData = new char[maxLength]; - int newLength = ImplodeStringMapToString(overrideList, "\n", newMapData, maxLength, ImplodePart_Key); - if (newLength < maxLength && newMapData[newLength] != '\n') - { - // do this to avoid a StrCat - newLength += strcopy(newMapData[newLength], maxLength, "\n") + 1; - } - - SetStringTableData(stringTableIndex, stringIndex, newMapData, newLength); - } - else - { - delete overrideList; - } -} - -public void OnClientDisconnect_Post(int client) -{ - if (!Internal_IsVoteInProgress() || !Internal_IsClientInVotePool(client)) - { - return; - } - - /* Wipe out their vote if they had one. We have to make sure the - * newly connected client is not allowed to vote. - */ - int item = g_ClientVotes[client]; - if (item >= VOTE_PENDING) - { - if (item > VOTE_PENDING) - { - g_hVotes.Set(item, g_hVotes.Get(item) - 1); - } - - g_ClientVotes[client] = VOTE_NOT_VOTING; - } - - CancelClientVote(g_hCurVote, client, MenuCancel_Disconnected); -} - -void CancelClientVote(NativeVote vote, int client, int reason) -{ - OnCancel(vote, client, reason); - OnClientEnd(); -} - -public Action Command_CallVote(int client, const char[] command, int argc) -{ - if (g_OverrideNextCallVote[client]) - { - g_OverrideNextCallVote[client] = false; - return Plugin_Continue; - } - - if (Internal_IsVoteInProgress() || Game_IsVoteInProgress() || !IsClientInGame(client)) - { - return Plugin_Handled; - } - - Action result = Plugin_Continue; - - switch (argc) - { - // No args means that we need to return a CallVoteSetup usermessage - case 0: - { - if (!g_OverridesSet && Game_AreVoteCommandsSupported()) - { - g_OverridesSet = true; - ProcessMapList(); - -#if defined LOG - LogMessage("Delaying to allow stringtable time to network"); -#endif - // Force it to reissue the callvote command so that the map list gets networked - CreateTimer(0.1, Timer_RetryCallVote, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); - return Plugin_Handled; - } - - ArrayList hVoteTypes = new ArrayList(sizeof(CallVoteListData)); // Stores arrays of CallVoteListData - - Game_AddDefaultVotes(hVoteTypes); - - // Add our overridden votes to the system - bool overridesPresent = false; - for (int i = 1; i < sizeof(g_CallVotes); i++) - { - if (GetForwardFunctionCount(g_CallVotes[i].CallVote_Forward) > 0) - { -#if defined LOG - LogMessage("Found overrides for vote type: %d", i); -#endif - overridesPresent = true; - CallVoteListData voteType; - - int pos = FindVoteInArray(hVoteTypes, view_as(i)); - if (pos > -1) - { - hVoteTypes.GetArray(pos, voteType, sizeof(CallVoteListData)); - voteType.CallVoteList_VoteEnabled = true; -#if defined LOG - LogMessage("Forcing vote type to visible: %d", i); -#endif - hVoteTypes.SetArray(pos, voteType); - } - else - { -#if defined LOG - LogMessage("Creating override for vote type: %d", i); -#endif - voteType.CallVoteList_VoteType = view_as(i); - voteType.CallVoteList_VoteEnabled = true; - hVoteTypes.PushArray(voteType); - } - } - } - - if (overridesPresent) - { - PerformVisChecks(client, hVoteTypes); -#if defined LOG - LogMessage("Overriding VoteSetup message"); -#endif - Game_DisplayVoteSetup(client, hVoteTypes); - delete hVoteTypes; - return Plugin_Handled; - } - else - { - delete hVoteTypes; - return Plugin_Continue; - } - } - - default: - { - char voteCommand[VOTE_STRING_SIZE]; - GetCmdArg(1, voteCommand, VOTE_STRING_SIZE); - -#if defined LOG - LogMessage("User is attempting to call %s", voteCommand); -#endif - - NativeVotesOverride overrideType = Game_VoteStringToVoteOverride(voteCommand); - - char argument[PLATFORM_MAX_PATH]; - - if (GetForwardFunctionCount(g_CallVotes[overrideType].CallVote_Forward) == 0) - { - - if (g_MapOverrides != null && - (overrideType == NativeVotesOverride_ChgLevel || - overrideType == NativeVotesOverride_NextLevel)) - { - char map[PLATFORM_MAX_PATH]; - - GetCmdArg(2, map, sizeof(map)); - g_MapOverrides.GetString(map, argument, sizeof(argument)); - - g_OverrideNextCallVote[client] = true; - FakeClientCommandEx(client, "callvote %s %s", voteCommand, argument); - return Plugin_Handled; - } - -#if defined LOG - LogMessage("We don't have a handler for %s, passing back to server", voteCommand); -#endif - return Plugin_Continue; - } - - // Vis checks are done here just in case something went wrong and the vote option was shown to a person it shouldn't be. -#if defined LOG - LogMessage("Calling visForward for %s", voteCommand); -#endif - Call_StartForward(g_CallVotes[overrideType].CallVote_Vis); - Call_PushCell(client); - Call_PushCell(overrideType); - Call_Finish(result); - if (result >= Plugin_Handled) - { - return result; - } - - NativeVotesType voteType = Game_VoteStringToVoteType(voteCommand); - - int target = 0; - - NativeVotesKickType kickType = NativeVotesKickType_None; - - switch (voteType) - { - case NativeVotesType_Kick: - { - char param1[20]; - GetCmdArg(2, param1, sizeof(param1)); - - kickType = Game_GetKickType(param1, target); - - int targetClient = GetClientOfUserId(target); - - if (targetClient < 1 || targetClient > MaxClients || !IsClientInGame(targetClient)) - { - return Plugin_Continue; - } - - GetClientName(targetClient, argument, sizeof(argument)); - } - - case NativeVotesType_ChgLevel, NativeVotesType_NextLevel: - { - if (g_MapOverrides == null) - { - GetCmdArg(2, argument, sizeof(argument)); - } - else - { - char map[PLATFORM_MAX_PATH]; - - GetCmdArg(2, map, sizeof(map)); - g_MapOverrides.GetString(map, argument, sizeof(argument)); - } - } - - default: - { - GetCmdArg(2, argument, sizeof(argument)); - } - } - -#if defined LOG - LogMessage("Calling callVoteForward for %s", voteCommand); -#endif - - Call_StartForward(g_CallVotes[overrideType].CallVote_Forward); - Call_PushCell(client); - Call_PushCell(overrideType); - Call_PushString(argument); - Call_PushCell(kickType); - Call_PushCell(target); - Call_Finish(result); - } - } - - // Default to continue if we're not processing things - return result; - -} - -stock int FindVoteInArray(ArrayList myArray, NativeVotesOverride value) -{ - int size = myArray.Length; - for (int i = 0; i < size; i++) - { - CallVoteListData voteData; - myArray.GetArray(i, voteData, sizeof(CallVoteListData)); - - if (voteData.CallVoteList_VoteType == value) - { - return i; - } - } - - return -1; -} - -stock bool IsVoteEnabled(ArrayList myArray, NativeVotesOverride value) -{ - int pos = FindVoteInArray(myArray, value); - if (pos > -1) - { - CallVoteListData voteType; - myArray.GetArray(pos, voteType, sizeof(CallVoteListData)); - if (voteType.CallVoteList_VoteEnabled) - return true; - } - return false; -} - -public void OnVoteDelayChange(ConVar convar, const char[] oldValue, const char[] newValue) -{ - /* See if the new vote delay isn't something we need to account for */ - if (convar.IntValue < 1) - { - g_NextVote = 0; - return; - } - - /* If there was never a last vote, ignore this change */ - if (g_NextVote <= 0) - { - return; - } - - /* Subtract the original value, then add the new one. */ - g_NextVote -= StringToInt(oldValue); - g_NextVote += StringToInt(newValue); -} - -public void OnMapEnd() -{ - if (g_hCurVote != null) - { - // Cancel the ongoing vote, but don't close the handle, as the other plugins may still re-use it - CancelVoting(); - //OnVoteCancel(g_hCurVote, VoteCancel_Generic); - g_hCurVote = null; - } - - if (g_hDisplayTimer != null) - { - delete g_hDisplayTimer; - } - -// g_hVoteTimer = INVALID_HANDLE; -} - -public Action Command_Vote(int client, const char[] command, int argc) -{ -#if defined LOG - char voteString[128]; - GetCmdArgString(voteString, sizeof(voteString)); - LogMessage("Client %N ran a vote command: %s", client, voteString); -#endif - - // If we're not running a vote, return the vote control back to the server - if (!Internal_IsVoteInProgress() || g_ClientVotes[client] != VOTE_PENDING) - { - return Plugin_Continue; - } - - char option[64]; - GetCmdArgString(option, sizeof(option)); - - int item = Game_ParseVote(option); - - // Make sure we don't go out of bounds on the vote - if (item == NATIVEVOTES_VOTE_INVALID || item > g_Items) - { - return Plugin_Handled; - } - - bool cancel; - - if (Data_GetFlags(g_hCurVote) & MENUFLAG_BUTTON_NOVOTE && item == 0) - { - cancel = true; - } - - if (cancel) - { - OnCancel(g_hCurVote, client, MenuCancel_Exit); - } - else - { - OnVoteSelect(g_hCurVote, client, item); - } - - OnClientEnd(); - - return Plugin_Handled; -} - -void OnVoteSelect(NativeVote vote, int client, int item) -{ - if (Internal_IsVoteInProgress() && g_ClientVotes[client] == VOTE_PENDING) - { - /* Check by our item count, NOT the vote array size */ - if (item < g_Items) - { - Game_ClientSelectedItem(vote, client, item); - - g_ClientVotes[client] = item; - g_hVotes.Set(item, g_hVotes.Get(item) + 1); - g_NumVotes++; - - Game_UpdateVoteCounts(g_hVotes, g_TotalClients); - - if (g_Cvar_VoteChat.BoolValue || g_Cvar_VoteConsole.BoolValue || g_Cvar_VoteClientConsole.BoolValue) - { - char choice[128]; - char name[MAX_NAME_LENGTH+1]; - Data_GetItemDisplay(vote, item, choice, sizeof(choice)); - - GetClientName(client, name, MAX_NAME_LENGTH); - - if (g_Cvar_VoteConsole.BoolValue) - { - PrintToServer("[%s] %T", LOGTAG, "Voted For", LANG_SERVER, name, choice); - } - - if (g_Cvar_VoteChat.BoolValue || g_Cvar_VoteClientConsole.BoolValue) - { - char phrase[30]; - - if (g_bRevoting[client]) - { - strcopy(phrase, sizeof(phrase), "Changed Vote"); - } - else - { - strcopy(phrase, sizeof(phrase), "Voted For"); - } - - if (g_Cvar_VoteChat.BoolValue) - { - PrintToChatAll("[%s] %t", LOGTAG, phrase, name, choice); - } - - if (g_Cvar_VoteClientConsole.BoolValue) - { - for (int i = 1; i <= MaxClients; i++) - { - if (IsClientInGame(i) && !IsFakeClient(i)) - { - PrintToConsole(i, "[%s] %t", LOGTAG, phrase, name, choice); - } - } - } - } - } - - BuildVoteLeaders(); - DrawHintProgress(); - - OnSelect(g_hCurVote, client, item); - } - } -} - -//MenuAction_Select -void OnSelect(NativeVote vote, int client, int item) -{ - MenuAction actions = Data_GetActions(vote); - if (actions & MenuAction_Select) - { - DoAction(vote, MenuAction_Select, client, item); - } -} - -//MenuAction_End -void OnEnd(NativeVote vote, int item) -{ - // Always called - DoAction(vote, MenuAction_End, item, 0); -} - - -stock void OnVoteEnd(NativeVote vote, int item) -{ - // Always called - DoAction(vote, MenuAction_VoteEnd, item, 0); -} - -void OnVoteStart(NativeVote vote) -{ - // Fire both Start and VoteStart in the other plugin. - - MenuAction actions = Data_GetActions(vote); - if (actions & MenuAction_Start) - { - DoAction(vote, MenuAction_Start, 0, 0); - } - - // Always called - DoAction(vote, MenuAction_VoteStart, 0, 0); -} - -void OnVoteCancel(NativeVote vote, int reason) -{ - // Always called - DoAction(vote, MenuAction_VoteCancel, reason, 0); -} - -void OnCancel(NativeVote vote, int client, int reason) -{ - DoAction(vote, MenuAction_Cancel, client, reason); -} - -void OnClientEnd() -{ - DecrementPlayerCount(); -} - -Action DoAction(NativeVote vote, MenuAction action, int param1, int param2, Action def_res = Plugin_Continue) -{ - Action res = def_res; - - Handle handler = CloneHandle(Data_GetHandler(vote)); -#if defined LOG - LogMessage("Calling Menu forward for vote: %d, handler: %d, action: %d, param1: %d, param2: %d", vote, handler, action, param1, param2); -#endif - Call_StartForward(handler); - Call_PushCell(vote); - Call_PushCell(action); - Call_PushCell(param1); - Call_PushCell(param2); - Call_Finish(res); - delete handler; - return res; -} - -void OnVoteResults(NativeVote vote, const int[][] votes, int num_votes, int item_count, const int[][] client_list, int num_clients) -{ - Handle resultsHandler = Data_GetResultCallback(vote); - - if (resultsHandler == null || !GetForwardFunctionCount(resultsHandler)) - { - /* Call MenuAction_VoteEnd instead. See if there are any extra winners. */ - int num_items = 1; - for (int i = 1; i < num_votes; i++) - { - if (votes[i][VOTEINFO_ITEM_VOTES] != votes[0][VOTEINFO_ITEM_VOTES]) - { - break; - } - num_items++; - } - - /* See if we need to pick a random winner. */ - int winning_item; - if (num_items > 1) - { - /* Yes, we do */ - winning_item = GetRandomInt(0, num_items - 1); - winning_item = votes[winning_item][VOTEINFO_ITEM_INDEX]; - } - else - { - /* No, take the first */ - winning_item = votes[0][VOTEINFO_ITEM_INDEX]; - } - - int winning_votes = votes[0][VOTEINFO_ITEM_VOTES]; - - DoAction(vote, MenuAction_VoteEnd, winning_item, (num_votes << 16) | (winning_votes & 0xFFFF)); - } - else - { - // This code is quite different than its C++ version, as we're reversing the logic previously done - - int[] client_indexes = new int[num_clients]; - int[] client_items = new int[num_clients]; - int[] vote_items = new int[item_count]; - int[] vote_votes = new int[item_count]; - - /* First array */ - for (int i = 0; i < item_count; i++) - { - vote_items[i] = votes[i][VOTEINFO_ITEM_INDEX]; - vote_votes[i] = votes[i][VOTEINFO_ITEM_VOTES]; - } - - /* Second array */ - for (int i = 0; i < num_clients; i++) - { - client_indexes[i] = client_list[i][VOTEINFO_CLIENT_INDEX]; - client_items[i] = client_list[i][VOTEINFO_CLIENT_ITEM]; - } - -#if defined LOG - LogMessage("Calling results forward for vote: %d, num_votes: %d, num_clients: %d, item_count: %d", vote, num_votes, num_clients, item_count); -#endif - - Call_StartForward(resultsHandler); - Call_PushCell(vote); - Call_PushCell(num_votes); - Call_PushCell(num_clients); - Call_PushArray(client_indexes, num_clients); - Call_PushArray(client_items, num_clients); - Call_PushCell(item_count); - Call_PushArray(vote_items, item_count); - Call_PushArray(vote_votes, item_count); - Call_Finish(); - } -} - -/* -VoteEnd(Handle:vote) -{ - if (g_NumVotes == 0) - { - // Fire VoteCancel in the other plugin - OnVoteCancel(vote, VoteCancel_NoVotes); - } - else - { - new num_items; - new num_votes; - - new slots = Game_GetMaxItems(); - new votes[slots][2]; - - Internal_GetResults(votes, slots); - - if (!SendResultCallback(vote, num_votes, num_items, votes)) - { - new Handle:handler = Data_GetHandler(g_hCurVote); - - Call_StartForward(handler); - Call_PushCell(g_CurVote); - Call_PushCell(MenuAction_VoteEnd); - Call_PushCell(votes[0][VOTEINFO_ITEM_INDEX]); - Call_PushCell(0); - Call_Finish(); - } - } - -} - -bool:SendResultCallback(Handle:vote, num_votes, num_items, const votes[][]) -{ - new Handle:voteResults = Data_GetResultCallback(g_CurVote); - if (GetForwardFunctionCount(voteResults) == 0) - { - return false; - } - - // This block is present because we can't pass 2D arrays to other plugins' functions - new item_indexes[]; - new item_votes[]; - - for (int i = 0, i < num_items; i++) - { - item_indexes[i] = votes[i][VOTEINFO_ITEM_INDEX]; - item_votes[i] = votes[i][VOTEINFO_ITEM_VOTES]; - } - - // Client block - new client_indexes[MaxClients]; - new client_votes[MaxClients]; - - new num_clients; - for (new i = 1; i <= MaxClients; i++) - { - if (g_ClientVotes[i] > VOTE_PENDING) - { - client_indexes[num_clients] = i; - client_votes[num_clients] = g_ClientVotes[i]; - num_clients++; - } - } - - Call_StartForward(voteResults); - Call_PushCell(_:vote); - Call_PushCell(num_votes); - Call_PushCell(num_clients); - Call_PushArray(client_indexes, num_clients); - Call_PushArray(client_votes, num_clients); - Call_PushCell(num_items); - Call_PushArray(item_indexes, num_items); - Call_PushArray(item_votes, num_items); - Call_Finish(); - - return true; -} -*/ - -void DrawHintProgress() -{ - if (!g_Cvar_VoteHintbox.BoolValue) - { - return; - } - - float timeRemaining = (g_fStartTime + g_VoteTime) - GetGameTime(); - - if (timeRemaining < 0.0) - { - timeRemaining = 0.0; - } - - int iTimeRemaining = RoundFloat(timeRemaining); - - PrintHintTextToAll("%t%s", "Vote Count", g_NumVotes, g_TotalClients, iTimeRemaining, g_LeaderList); -} - -void BuildVoteLeaders() -{ - if (g_NumVotes == 0 || !g_Cvar_VoteHintbox.BoolValue) - { - return; - } - - // Since we can't have structs, we get "struct" with this instead - - int slots = Game_GetMaxItems(); - int[][] votes = new int[slots][2]; - - int num_items = Internal_GetResults(votes); - - /* Take the top 3 (if applicable) and draw them */ - g_LeaderList[0] = '\0'; - - for (int i = 0; i < num_items && i < 3; i++) - { - int cur_item = votes[i][VOTEINFO_ITEM_INDEX]; - char choice[256]; - Data_GetItemDisplay(g_hCurVote, cur_item, choice, sizeof(choice)); - Format(g_LeaderList, sizeof(g_LeaderList), "%s\n%i. %s: (%i)", g_LeaderList, i+1, choice, votes[i][VOTEINFO_ITEM_VOTES]); - } - -} - -public int SortVoteItems(int[] a, int[] b, const int[][] array, Handle hndl) -{ - if (b[VOTEINFO_ITEM_VOTES] == a[VOTEINFO_ITEM_VOTES]) - { - return 0; - } - else if (b[VOTEINFO_ITEM_VOTES] > a[VOTEINFO_ITEM_VOTES]) - { - return 1; - } - else - { - return -1; - } -} - -void DecrementPlayerCount() -{ - g_Clients--; - - // The vote is running and we have no clients left, so end the vote. - if (g_bStarted && g_Clients == 0) - { - EndVoting(); - } - -} - - -void EndVoting() -{ - int voteDelay = g_Cvar_VoteDelay.IntValue; - if (voteDelay < 1) - { - g_NextVote = 0; - } - else - { - g_NextVote = GetTime() + voteDelay; - } - - if (g_hDisplayTimer != null) - { - delete g_hDisplayTimer; - } - - if (g_bCancelled) - { - /* If we were cancelled, don't bother tabulating anything. - * Reset just in case someone tries to redraw, which means - * we need to save our states. - */ - NativeVote vote = g_hCurVote; - Internal_Reset(true); - OnVoteCancel(vote, VoteCancel_Generic); - OnEnd(vote, MenuEnd_VotingCancelled); - return; - } - - int slots = Game_GetMaxItems(); - int[][] votes = new int[slots][2]; - int num_votes; - int num_items = Internal_GetResults(votes, num_votes); - - if (!num_votes) - { - NativeVote vote = g_hCurVote; - Internal_Reset(); - OnVoteCancel(vote, VoteCancel_NoVotes); - OnEnd(vote, MenuEnd_VotingCancelled); - return; - } - - int[][] client_list = new int[MaxClients][2]; - int num_clients = Internal_GetClients(client_list); - - /* Save states, then clear what we've saved. - * This makes us re-entrant, which is always the safe way to go. - */ - NativeVote vote = g_hCurVote; - Internal_Reset(); - -#if defined LOG - LogMessage("Voting done"); -#endif - - /* Send vote info */ - OnVoteResults(vote, votes, num_votes, num_items, client_list, num_clients); - OnEnd(vote, MenuEnd_VotingDone); -} - -bool StartVote(NativeVote vote, int num_clients, int[] clients, int max_time, int flags) -{ - if (!InitializeVoting(vote, max_time, flags)) - { - return false; - } - - /* Due to hibernating servers, we no longer use GameTime, but instead standard timestamps. - */ - - int voteDelay = g_Cvar_VoteDelay.IntValue; - if (voteDelay < 1) - { - g_NextVote = 0; - } - else - { - /* This little trick break for infinite votes! - * However, we just ignore that since those 1) shouldn't exist and - * 2) people must be checking IsVoteInProgress() beforehand anyway. - */ - g_NextVote = GetTime() + voteDelay + max_time; - } - - g_fStartTime = GetGameTime(); - g_VoteTime = max_time; - g_TimeLeft = max_time; - - int clientCount = 0; - - for (int i = 0; i < num_clients; ++i) - { - if (clients[i] < 1 || clients[i] > MaxClients) - { - continue; - } - - g_ClientVotes[clients[i]] = VOTE_PENDING; - clientCount++; - } - - g_Clients = clientCount; - - Game_UpdateVoteCounts(g_hVotes, clientCount); - - DoClientVote(vote, clients, num_clients); - - StartVoting(); - - DrawHintProgress(); - - return true; -} - -bool DoClientVote(NativeVote vote, int[] clients, int num_clients) -{ - int totalPlayers = 0; - int[] realClients = new int[MaxClients+1]; - - for (int i = 0; i < num_clients; ++i) - { - if (clients[i] < 1 || clients[i] > MaxClients || !IsClientInGame(clients[i]) || IsFakeClient(clients[i])) - { - continue; - } - - realClients[totalPlayers++] = clients[i]; - } - - if (totalPlayers > 0) - { - Game_DisplayVote(vote, realClients, totalPlayers); - return true; - } - else - { - return false; - } -} - -bool InitializeVoting(NativeVote vote, int time, int flags) -{ - if (Internal_IsVoteInProgress()) - { - return false; - } - - Internal_Reset(); - - /* Mark all clients as not voting */ - for (int i = 1; i <= MaxClients; ++i) - { - g_ClientVotes[i] = VOTE_NOT_VOTING; - g_bRevoting[i] = false; - } - - g_Items = Data_GetItemCount(vote); - - // Clear all items - for (int i = 0; i < g_hVotes.Length; ++i) - { - g_hVotes.Set(i, 0); - } - - g_hCurVote = vote; - g_VoteTime = time; - g_VoteFlags = flags; - - return true; -} - -void StartVoting() -{ - if (g_hCurVote == null) - { - return; - } - - g_bStarted = true; - - OnVoteStart(g_hCurVote); - - g_hDisplayTimer = CreateTimer(1.0, DisplayTimer, _, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); - - g_TotalClients = g_Clients; - - /* By now we know how many clients were set. - * If there are none, we should end IMMEDIATELY. - */ - if (g_Clients == 0) - { - EndVoting(); - return; - } - - // Kick targets automatically vote no if they're in the pool - NativeVotesType voteType = Data_GetType(g_hCurVote); - - switch (voteType) - { - case NativeVotesType_Kick, NativeVotesType_KickCheating, NativeVotesType_KickIdle, NativeVotesType_KickScamming: - { - int target = Data_GetTarget(g_hCurVote); - - if (target > 0 && target <= MaxClients && IsClientConnected(target) && Internal_IsClientInVotePool(target)) - { - Game_VoteNo(target); - } - } - } - - // Initiators always vote yes when they're in the pool. - if (voteType != NativeVotesType_Custom_Mult && voteType != NativeVotesType_NextLevelMult) - { - int initiator = Data_GetInitiator(g_hCurVote); - - if (initiator > 0 && initiator <= MaxClients && IsClientConnected(initiator) && Internal_IsClientInVotePool(initiator)) - { - Game_VoteYes(initiator); - } - } -} - -public Action DisplayTimer(Handle timer) -{ - DrawHintProgress(); - if (--g_TimeLeft == 0) - { - if (g_hDisplayTimer != null) - { - g_hDisplayTimer = null; - EndVoting(); - } - return Plugin_Stop; - } - - return Plugin_Continue; -} - -int Internal_GetResults(int[][] votes, int &num_votes=0) -{ - if (!Internal_IsVoteInProgress()) - { - return 0; - } - - // Since we can't have structs, we get "struct" with this instead - int num_items; - - num_votes = 0; - - for (int i = 0; i < g_Items; i++) - { - int voteCount = g_hVotes.Get(i); - if (voteCount > 0) - { - votes[num_items][VOTEINFO_ITEM_INDEX] = i; - votes[num_items][VOTEINFO_ITEM_VOTES] = voteCount; - num_votes += voteCount; - num_items++; - } - } - - /* Sort the item list descending like we promised */ - SortCustom2D(votes, num_items, SortVoteItems); - - return num_items; -} - -int Internal_GetClients(int[][] client_vote) -{ - if (!Internal_IsVoteInProgress()) - { - return 0; - } - - /* Build the client list */ - int num_clients; - - for (int i = 1; i <= MaxClients; i++) - { - if (g_ClientVotes[i] >= VOTE_PENDING) - { - client_vote[num_clients][VOTEINFO_CLIENT_INDEX] = i; - client_vote[num_clients][VOTEINFO_CLIENT_ITEM] = g_ClientVotes[i]; - num_clients++; - } - } - - return num_clients; -} - -bool Internal_IsCancelling() -{ - return g_bCancelled; -} - -stock NativeVote Internal_GetCurrentVote() -{ - return g_hCurVote; -} - -void Internal_Reset(bool cancel=false) -{ - g_Clients = 0; - g_Items = 0; - g_bStarted = false; - g_hCurVote = null; - g_NumVotes = 0; - g_bCancelled = false; - g_LeaderList[0] = '\0'; - g_TotalClients = 0; - - if (g_hDisplayTimer != null) - { - delete g_hDisplayTimer; - } - - if (!cancel) - { - CreateTimer(5.0, Game_ResetVote, TIMER_FLAG_NO_MAPCHANGE); - } -} - -bool Internal_IsVoteInProgress() -{ - return (g_hCurVote != INVALID_HANDLE); -} - -bool Internal_IsClientInVotePool(int client) -{ - if (client < 1 - || client > MaxClients - || g_hCurVote == null) - { - return false; - } - - return (g_ClientVotes[client] > VOTE_NOT_VOTING); -} - -bool Internal_RedrawToClient(int client, bool revotes) -{ - if (!Internal_IsVoteInProgress() || !Internal_IsClientInVotePool(client)) - { - return false; - } - - if (g_ClientVotes[client] >= 0) - { - if ((g_VoteFlags & VOTEFLAG_NO_REVOTES) || !revotes || g_VoteTime <= VOTE_DELAY_TIME) - { - return false; - } - - g_Clients++; - SetArrayCell(g_hVotes, g_ClientVotes[client], GetArrayCell(g_hVotes, g_ClientVotes[client]) - 1); - g_ClientVotes[client] = VOTE_PENDING; - g_bRevoting[client] = true; - g_NumVotes--; - Game_UpdateVoteCounts(g_hVotes, g_TotalClients); - } - - // Display the vote fail screen for a few seconds - //Game_DisplayVoteFail(g_hCurVote, NativeVotesFail_Generic, client); - - // No, display a vote pass screen because that's nicer and we can customize it. - // Note: This isn't inside the earlier if because some players have had issues where the display - // doesn't always appear the first time. - char revotePhrase[128]; - Format(revotePhrase, sizeof(revotePhrase), "%T", "NativeVotes Revote", client); - Game_DisplayVotePassCustom(g_hCurVote, revotePhrase, client); - - DataPack data; - - CreateDataTimer(VOTE_DELAY_TIME, RedrawTimer, data, TIMER_FLAG_NO_MAPCHANGE); - data.WriteCell(GetClientUserId(client)); - data.WriteCell(view_as(g_hCurVote)); - - return true; -} - -public Action RedrawTimer(Handle timer, DataPack data) -{ - if (g_hCurVote == null) - { - return Plugin_Stop; - } - - ResetPack(data); - int client = GetClientOfUserId(data.ReadCell()); - if (client == 0) - { - return Plugin_Stop; - } - - NativeVote vote = view_as(data.ReadCell()); - - if (Internal_IsVoteInProgress() && !Internal_IsCancelling() && vote == g_hCurVote) - { - Game_DisplayVoteToOne(vote, client); - } - - return Plugin_Stop; -} - -void CancelVoting() -{ - if (g_bCancelled || g_hCurVote == INVALID_HANDLE) - { - return; - } - - g_bCancelled = true; - - EndVoting(); -} - -void PerformVisChecks(int client, ArrayList hVoteTypes) -{ - // Iterate backwards so we can safely remove items - for (int i = hVoteTypes.Length - 1; i >= 0; i--) - { - CallVoteListData voteData; - hVoteTypes.GetArray(i, voteData, sizeof(CallVoteListData)); - - Action hide = Plugin_Continue; - -#if defined LOG - LogMessage("Checking visibility forward for %d: %d", voteData.CallVoteList_VoteType, g_CallVotes[voteData.CallVoteList_VoteType].CallVote_Vis); -#endif - Call_StartForward(g_CallVotes[voteData.CallVoteList_VoteType].CallVote_Vis); - Call_PushCell(client); - Call_PushCell(voteData.CallVoteList_VoteType); - Call_Finish(hide); - if (hide >= Plugin_Handled) - { -#if defined LOG - LogMessage("Hiding vote type %d", voteData.CallVoteList_VoteType); -#endif - if (Game_AreDisabledIssuesHidden()) - { - // Since we hide disabled issues, remove it from the arraylist - RemoveFromArray(hVoteTypes, i); - } - else - { - // Arrays are pass by ref, so this should update the one inside the ArrayList - voteData.CallVoteList_VoteEnabled = false; - } - } - } -} - -//---------------------------------------------------------------------------- -// Natives - -// native bool:NativeVotes_IsVoteTypeSupported(NativeVotesType:voteType); -public int Native_IsVoteTypeSupported(Handle plugin, int numParams) -{ - NativeVotesType type = GetNativeCell(1); - - return Game_CheckVoteType(type); -} - -// native Handle:NativeVotes_Create(MenuHandler:handler, NativeVotesType:voteType, -// MenuAction:actions=NATIVEVOTES_ACTIONS_DEFAULT); -public int Native_Create(Handle plugin, int numParams) -{ - Function handler = GetNativeFunction(1); - NativeVotesType voteType = GetNativeCell(2); - MenuAction actions = GetNativeCell(3); - - if (handler == INVALID_FUNCTION) - { - ThrowNativeError(SP_ERROR_NATIVE, "Menuhandler is invalid"); - } - - NativeVote vote; - if (Game_CheckVoteType(voteType)) - { - vote = Data_CreateVote(voteType, actions); - } - else - { - return view_as(INVALID_HANDLE); - } - - if (voteType != NativeVotesType_NextLevelMult && voteType != NativeVotesType_Custom_Mult) - { - Data_AddItem(vote, "yes", "Yes"); - Data_AddItem(vote, "no", "No"); - } - - Handle menuForward = Data_GetHandler(vote); - - AddToForward(menuForward, plugin, handler); - - return view_as(vote); -} - -// native Handle:NativeVotes_Close(Handle:vote); -public int Native_Close(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - - if (vote == null) - { - return; - } - - if (g_hCurVote == vote) - { - CancelVoting(); - g_hCurVote = null; - -/* - if (g_hVoteTimer != INVALID_HANDLE) - { - KillTimer(g_hVoteTimer); - g_hVoteTimer = INVALID_HANDLE; - } -*/ - } - - // This bit is necessary because the Forward system appears to not remove these when the forward Handle is closed - // This was necessary in SM 1.5.x, but has a REALLY high probability of crashing in SM 1.6, plus is no longer needed - //new Handle:menuForward = Data_GetHandler(vote); - //RemoveAllFromForward(menuForward, plugin); - - //new Handle:voteResults = Data_GetResultCallback(vote); - //RemoveAllFromForward(voteResults, plugin); - - // Do the datatype-specific close operations - Data_CloseVote(vote); -} - -// native bool:NativeVotes_Display(Handle:vote, clients[], numClients, time, flags=0); -public int Native_Display(Handle plugin, int numParams) -{ - if (Internal_IsVoteInProgress()) - { - ThrowNativeError(SP_ERROR_NATIVE, "A vote is already in progress"); - } - - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return false; - } - - int count = GetNativeCell(3); - int[] clients = new int[count]; - GetNativeArray(2, clients, count); - - // Note: Only one flag exists: VOTEFLAG_NO_REVOTES - int flags = 0; - - if (numParams >= 5) - { - flags = GetNativeCell(5); - } - - if (!StartVote(vote, count, clients, GetNativeCell(4), flags)) - { - return 0; - } - - return 1; - -} - -// native bool:NativeVotes_AddItem(Handle:vote, const String:info[], const String:display[]); -public int Native_AddItem(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return false; - } - - NativeVotesType voteType = Data_GetType(vote); - - if (voteType != NativeVotesType_NextLevelMult && voteType != NativeVotesType_Custom_Mult) - { - return false; - } - - char info[256]; - char display[256]; - GetNativeString(2, info, sizeof(info)); - GetNativeString(3, display, sizeof(display)); - - return Data_AddItem(vote, info, display); -} - -// native bool:NativeVotes_InsertItem(Handle:vote, position, const String:info[], const String:display[]); -public int Native_InsertItem(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return false; - } - - NativeVotesType voteType = Data_GetType(vote); - - if (voteType != NativeVotesType_NextLevelMult && voteType != NativeVotesType_Custom_Mult) - { - return false; - } - - int position = GetNativeCell(2); - - if (position < 0) - { - ThrowNativeError(SP_ERROR_NATIVE, "Vote index can't be negative: %d", position); - return false; - } - - char info[256]; - char display[256]; - GetNativeString(3, info, sizeof(info)); - GetNativeString(4, display, sizeof(display)); - - return Data_InsertItem(vote, position, info, display); - -} - -// native bool:NativeVotes_RemoveItem(Handle:vote, position); -public int Native_RemoveItem(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return false; - } - - NativeVotesType voteType = Data_GetType(vote); - - if (voteType != NativeVotesType_NextLevelMult && voteType != NativeVotesType_Custom_Mult) - { - return false; - } - - int position = GetNativeCell(2); - - return Data_RemoveItem(vote, position); -} - -// native NativeVotes_RemoveAllItems(Handle:vote); -public int Native_RemoveAllItems(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return; - } - - Data_RemoveAllItems(vote); -} - -// native bool:NativeVotes_GetItem(Handle:vote, -// position, -// String:infoBuf[], -// infoBufLen, -// String:dispBuf[]="", -// dispBufLen=0); -public int Native_GetItem(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return; - } - - int position = GetNativeCell(2); - - int infoLength = GetNativeCell(4); - char[] info = new char[infoLength]; - Data_GetItemInfo(vote, position, info, infoLength); - SetNativeString(3, info, infoLength); - - if (numParams >= 6) - { - int displayLength = GetNativeCell(6); - if (displayLength > 0) - { - char[] display = new char[displayLength]; - Data_GetItemDisplay(vote, position, display, displayLength); - SetNativeString(5, display, displayLength); - } - } -} - -// native NativeVotes_GetItemCount(Handle:vote); -public int Native_GetItemCount(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return 0; - } - - return Data_GetItemCount(vote); -} - -// native NativeVotes_GetDetails(Handle:vote, String:buffer[], maxlength); -public int Native_GetDetails(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return; - } - - int len = GetNativeCell(3); - - char[] details = new char[len]; - - Data_GetDetails(vote, details, len); - - SetNativeString(2, details, len); -} - -// native NativeVotes_SetDetails(Handle:vote, String:fmt[], any:...); -public int Native_SetDetails(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return; - } - - char details[MAX_VOTE_DETAILS_LENGTH]; - - //SetGlobalTransTarget(LANG_SERVER); - FormatNativeString(0, 2, 3, sizeof(details), _, details); - - Data_SetDetails(vote, details); -} - -// native NativeVotes_GetDetails(Handle:vote, String:buffer[], maxlength); -public int Native_GetTitle(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return; - } - - int len = GetNativeCell(3); - - char[] title = new char[len]; - - Data_GetTitle(vote, title, len); - - SetNativeString(2, title, len); -} - -// native NativeVotes_SetTitle(Handle:vote, String:fmt[], any:...); -public int Native_SetTitle(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return; - } - - char details[MAX_VOTE_DETAILS_LENGTH]; - - //SetGlobalTransTarget(LANG_SERVER); - FormatNativeString(0, 2, 3, sizeof(details), _, details); - - Data_SetTitle(vote, details); -} - -// native bool:NativeVotes_IsVoteInProgress(); -public int Native_IsVoteInProgress(Handle plugin, int numParams) -{ - return Internal_IsVoteInProgress() || Game_IsVoteInProgress(); -} - -// native NativeVotes_GetMaxItems(); -public int Native_GetMaxItems(Handle plugin, int numParams) -{ - return Game_GetMaxItems(); -} - -// native NativeVotes_GetOptionFlags(Handle:vote); -public int Native_GetOptionFlags(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return 0; - } - - return Data_GetFlags(vote); -} - -// native NativeVotes_SetOptionFlags(Handle:vote, flags); -public int Native_SetOptionFlags(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return; - } - - int flags = GetNativeCell(2); - - // This is an ORed group of flags to strip the ones we don't support - flags &= (MENUFLAG_BUTTON_NOVOTE); - - Data_SetFlags(vote, flags); -} - -// native bool NativeVotes_SetNoVoteButton(Handle vote, bool button); -public int Native_SetNoVoteButton(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - return ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - } - - int flags = Data_GetFlags(vote); - - if (GetNativeCell(2)) - { - flags |= MENUFLAG_BUTTON_NOVOTE; - } - else - { - flags &= ~MENUFLAG_BUTTON_NOVOTE; - } - - Data_SetFlags(vote, flags); - - int newflags = Data_GetFlags(vote); - - return (flags == newflags); -} - -// native NativeVotes_Cancel(); -public int Native_Cancel(Handle plugin, int numParams) -{ - if (!Internal_IsVoteInProgress()) - { - ThrowNativeError(SP_ERROR_NATIVE, "No vote is in progress"); - return; - } - - CancelVoting(); -} - -// native NativeVotes_SetResultCallback(Handle:vote, NativeVotes_VoteHandler:callback); -public int Native_SetResultCallback(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return; - } - - Function handler = GetNativeFunction(2); - - if (handler == INVALID_FUNCTION) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes_VoteHandler is invalid"); - return; - } - - Handle voteResults = Data_GetResultCallback(vote); - - RemoveAllFromForward(voteResults, plugin); - if (!AddToForward(voteResults, plugin, handler)) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes_VoteHandler cannot be added to forward"); - } -} - -// native NativeVotes_CheckVoteDelay(); -public int Native_CheckVoteDelay(Handle plugin, int numParams) -{ - int curTime = GetTime(); - if (g_NextVote <= curTime) - { - return 0; - } - - return (g_NextVote - curTime); -} - -// native bool:NativeVotes_IsClientInVotePool(client); -public int Native_IsClientInVotePool(Handle plugin, int numParams) -{ - int client = GetNativeCell(1); - - if (client <= 0 || client > MaxClients) - { - ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); - return false; - } - - if (!Internal_IsVoteInProgress()) - { - ThrowNativeError(SP_ERROR_NATIVE, "No vote is in progress"); - return false; - } - - return Internal_IsClientInVotePool(client); -} - -// native bool:NativeVotes_RedrawClientVote(client, bool:revotes=true); -public int Native_RedrawClientVote(Handle plugin, int numParams) -{ - int client = GetNativeCell(1); - - if (client < 1 || client > MaxClients || !IsClientConnected(client)) - { - ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); - return false; - } - - if (!Internal_IsVoteInProgress()) - { - // When revoting in TF2, NativeVotes_IsVoteInProgress always gets skipped because of Game_IsVoteInProgress() - // TF2s vote controller will stay alive a few seconds after the vote is complete - // If one tries to revote right as a vote completes, it will throw an error - LogError("No vote is in progress"); - return false; - } - - if (!Internal_IsClientInVotePool(client)) - { - ThrowNativeError(SP_ERROR_NATIVE, "Client is not in the voting pool"); - return false; - } - - bool revote = true; - if (numParams >= 2 && !GetNativeCell(2)) - { - revote = false; - } - - return Internal_RedrawToClient(client, revote); -} - -// native NativeVotesType:NativeVotes_GetType(Handle:vote); -public int Native_GetType(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return 0; - } - - return view_as(Data_GetType(vote)); -} - -// native NativeVotes_GetTeam(Handle:vote); -public int Native_GetTeam(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return NATIVEVOTES_ALL_TEAMS; - } - - return Data_GetTeam(vote); - -} - -// native NativeVotes_SetTeam(Handle:vote, team); -public int Native_SetTeam(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return; - } - - int team = GetNativeCell(2); - - // Teams are numbered starting with 0 - // Currently 4 is the maximum (Unassigned, Spectator, Team 1, Team 2) - if (team >= GetTeamCount()) - { - ThrowNativeError(SP_ERROR_NATIVE, "Team %d is invalid", team); - return; - } - - if (g_EngineVersion == Engine_TF2 && team == NATIVEVOTES_ALL_TEAMS) - { - team = NATIVEVOTES_TF2_ALL_TEAMS; - } - - Data_SetTeam(vote, team); -} - -// native NativeVotes_GetInitiator(Handle:vote); -public int Native_GetInitiator(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return NATIVEVOTES_SERVER_INDEX; - } - - return Data_GetInitiator(vote); -} - -// native NativeVotes_SetInitiator(Handle:vote, client); -public int Native_SetInitiator(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return; - } - - int initiator = GetNativeCell(2); - Data_SetInitiator(vote, initiator); -} - -// native NativeVotes_DisplayPass(Handle:vote, const String:details[]=""); -public int Native_DisplayPass(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return; - } - - if (numParams >= 2) - { - char winner[TRANSLATION_LENGTH]; - FormatNativeString(0, 2, 3, sizeof(winner), _, winner); - - Game_DisplayVotePass(vote, winner); - } - else - { - Game_DisplayVotePass(vote); - } - -} - -// native NativeVotes_DisplayPassCustomToOne(Handle:vote, client, const String:format[], any:...); -public int Native_DisplayPassCustomToOne(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return; - } - - int client = GetNativeCell(2); - - char translation[TRANSLATION_LENGTH]; - - SetGlobalTransTarget(client); - FormatNativeString(0, 3, 4, TRANSLATION_LENGTH, _, translation); - - Game_DisplayVotePassCustom(vote, translation, client); -} - -// native NativeVotes_DisplayPassEx(Handle:vote, NativeVotesPassType:passType, const String:details[]=""); -public int Native_DisplayPassEx(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return; - } - - NativeVotesPassType passType = view_as(GetNativeCell(2)); - - if (!Game_CheckVotePassType(passType)) - { - ThrowNativeError(SP_ERROR_NATIVE, "Invalid vote pass type: %d", passType); - } - - if (numParams >= 3) - { - char winner[TRANSLATION_LENGTH]; - //SetGlobalTransTarget(LANG_SERVER); - FormatNativeString(0, 3, 4, sizeof(winner), _, winner); - - Game_DisplayVotePassEx(vote, passType, winner); - } - else - { - Game_DisplayVotePassEx(vote, passType); - } -} - -// native NativeVotes_DisplayRawPass(NativeVotesPassType:passType, const String:details[]="", team=NATIVEVOTES_ALL_TEAMS); -/* -public Native_DisplayRawPass(Handle:plugin, numParams) -{ - new NativeVotesPassType:passType = NativeVotesPassType:GetNativeCell(1); - - if (!Game_CheckVotePassType(passType)) - { - ThrowNativeError(SP_ERROR_NATIVE, "Invalid vote pass type: %d", passType); - } - - new len; - GetNativeStringLength(2, len); - new String:winner[len+1]; - GetNativeString(2, winner, len+1); - new team = GetNativeCell(3); - - if (g_EngineVersion == Engine_TF2 && team == NATIVEVOTES_ALL_TEAMS) - { - team = NATIVEVOTES_TF2_ALL_TEAMS; - } - - Game_DisplayRawVotePass(passType, winner, team); -} -*/ - -// native NativeVotes_DisplayRawPassToOne(client, NativeVotesPassType:passType, const String:details[]="", team=NATIVEVOTES_ALL_TEAMS); -public int Native_DisplayRawPassToOne(Handle plugin, int numParams) -{ - int client = GetNativeCell(1); - NativeVotesPassType passType = view_as(GetNativeCell(2)); - - if (!Game_CheckVotePassType(passType)) - { - ThrowNativeError(SP_ERROR_NATIVE, "Invalid vote pass type: %d", passType); - } - - int team = GetNativeCell(3); - - if (g_EngineVersion == Engine_TF2 && team == NATIVEVOTES_ALL_TEAMS) - { - team = NATIVEVOTES_TF2_ALL_TEAMS; - } - - if (numParams >= 4) - { - char winner[TRANSLATION_LENGTH]; - SetGlobalTransTarget(client); - FormatNativeString(0, 4, 5, sizeof(winner), _, winner); - - Game_DisplayRawVotePass(passType, team, client, winner); - } - else - { - Game_DisplayRawVotePass(passType, team, client); - } -} - -// native NativeVotes_DisplayRawPassCustomToOne(client, team, const String:format[], any:...); -public int Native_DisplayRawPassCustomToOne(Handle plugin, int numParams) -{ - int client = GetNativeCell(1); - int team = GetNativeCell(2); - - char translation[TRANSLATION_LENGTH]; - - SetGlobalTransTarget(client); - FormatNativeString(0, 3, 4, TRANSLATION_LENGTH, _, translation); - - Game_DisplayRawVotePassCustom(translation, team, client); -} - -// native NativeVotes_DisplayFail(Handle:vote, NativeVotesFailType:reason=NativeVotesFail_Generic); -public int Native_DisplayFail(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return; - } - - NativeVotesFailType reason = view_as(GetNativeCell(2)); - - Game_DisplayVoteFail(vote, reason); -} - -// native NativeVotes_DisplayRawFail(NativeVotesFailType:reason=NativeVotesFail_Generic, team=NATIVEVOTES_ALL_TEAMS); -public int Native_DisplayRawFail(Handle plugin, int numParams) -{ - int size = GetNativeCell(2); - int[] clients = new int[size]; - GetNativeArray(1, clients, size); - - NativeVotesFailType reason = view_as(GetNativeCell(3)); - - int team = GetNativeCell(4); - - if (g_EngineVersion == Engine_TF2 && team == NATIVEVOTES_ALL_TEAMS) - { - team = NATIVEVOTES_TF2_ALL_TEAMS; - } - - Game_DisplayRawVoteFail(clients, size, reason, team); -} - -// native NativeVotes_DisplayRawFailToOne(client, NativeVotesFailType:reason=NativeVotesFail_Generic, team=NATIVEVOTES_ALL_TEAMS); -/* -public Native_DisplayRawFailToOne(Handle:plugin, numParams) -{ - new client = GetNativeCell(1); - - new NativeVotesFailType:reason = NativeVotesFailType:GetNativeCell(2); - - new team = GetNativeCell(3); - - if (g_EngineVersion == Engine_TF2 && team == NATIVEVOTES_ALL_TEAMS) - { - team = NATIVEVOTES_TF2_ALL_TEAMS; - } - - Game_DisplayRawVoteFail(reason, team, client); -} -*/ -// native NativeVotes_GetTarget(Handle:vote); -public int Native_GetTarget(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return 0; - } - - int target = Data_GetTarget(vote); - - if (target == 0) - { - // No target was set, return -1 - return -1; - } - - return GetClientOfUserId(target); -} - -// native NativeVotes_GetTargetSteam(Handle:vote, String:buffer[], maxlength); -public int Native_GetTargetSteam(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return; - } - - int size = GetNativeCell(3); - char[] steamId = new char[size]; - GetNativeString(2, steamId, size); - - Data_GetTargetSteam(vote, steamId, size); -} - -// native NativeVotes_SetTarget(Handle:vote, client, bool:setDetails=true); -public int Native_SetTarget(Handle plugin, int numParams) -{ - NativeVote vote = GetNativeCell(1); - if (vote == null) - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); - return; - } - - int client = GetNativeCell(2); - - if (client < 1 || client > MaxClients || !IsClientConnected(client)) - { - ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); - return; - } - - int userid; - char steamId[20]; - - if (client <= 0) - { - userid = 0; - steamId = ""; - } - else - { - userid = GetClientUserId(client); - if (!GetClientAuthId(client, AuthId_Steam2, steamId, sizeof(steamId))) - { - steamId = ""; - } - } - - Data_SetTarget(vote, userid); - Data_SetTargetSteam(vote, steamId); - - bool changeDetails = GetNativeCell(3); - if (changeDetails) - { - char name[MAX_NAME_LENGTH+1]; - if (client > 0) - { - GetClientName(client, name, MAX_NAME_LENGTH); - Data_SetDetails(vote, name); - } - else - { - Data_SetDetails(vote, ""); - } - } -} - -// native bool:NativeVotes_AreVoteCommandsSupported(); -public int Native_AreVoteCommandsSupported(Handle plugin, int numParams) -{ - return Game_AreVoteCommandsSupported(); -} - -// native NativeVotes_RegisterVoteCommand(NativeVotesOverride:overrideType, NativeVotes_CallVoteHandler:callHandler, NativeVotes_CallVoteVisCheck:visHandler=INVALID_FUNCTION); -public int Native_RegisterVoteCommand(Handle plugin, int numParams) -{ - NativeVotesOverride overrideType = GetNativeCell(1); - Function callVoteHandler = GetNativeFunction(2); - Function visHandler = GetNativeFunction(3); - - // This tosses an error so use the simplified version - // if (view_as(overrideType) > sizeof(g_CallVotes)) - if (overrideType > NativeVotesOverride_Count) - { - ThrowNativeError(SP_ERROR_NATIVE, "Override Type %d is not supported by this version of NativeVotes", overrideType); - return; - } - - if (callVoteHandler == INVALID_FUNCTION) - { - ThrowNativeError(SP_ERROR_NATIVE, "CallVoteHandler function was invalid"); - return; - } - - AddToForward(g_CallVotes[overrideType].CallVote_Forward, plugin, callVoteHandler); - - if (visHandler != INVALID_FUNCTION) - { - AddToForward(g_CallVotes[overrideType].CallVote_Vis, plugin, visHandler); - } -} - -// native NativeVotes_UnregisterVoteCommand(NativeVotesOverride:overrideType, NativeVotes_CallVoteHandler:callHandler, NativeVotes_CallVoteVisCheck:visHandler=INVALID_FUNCTION); -public int Native_UnregisterVoteCommand(Handle plugin, int numParams) -{ - NativeVotesOverride overrideType = GetNativeCell(1); - Function callVoteHandler = GetNativeFunction(2); - Function visHandler = GetNativeFunction(3); - - if (overrideType > NativeVotesOverride_Count) - { - ThrowNativeError(SP_ERROR_NATIVE, "Override Type %d is not supported by this version of NativeVotes", overrideType); - return; - } - - if (callVoteHandler == INVALID_FUNCTION) - { - ThrowNativeError(SP_ERROR_NATIVE, "CallVoteHandler function was invalid"); - return; - } - - RemoveFromForward(g_CallVotes[overrideType].CallVote_Forward, plugin, callVoteHandler); - - if (visHandler != INVALID_FUNCTION) - { - RemoveFromForward(g_CallVotes[overrideType].CallVote_Vis, plugin, visHandler); - } -} - -// native NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFailType:reason, time=0); -public int Native_DisplayCallVoteFail(Handle plugin, int numParams) -{ - int client = GetNativeCell(1); - - if (client < 1 || client > MaxClients || !IsClientConnected(client)) - { - ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); - return; - } - - NativeVotesCallFailType reason = GetNativeCell(2); - - int time = GetNativeCell(3); - - Game_DisplayCallVoteFail(client, reason, time); -} - -// native Action:NativeVotes_RedrawVoteTitle(const String:text[]); -public int Native_RedrawVoteTitle(Handle plugin, int numParams) -{ - if (!g_curDisplayClient) - { - ThrowNativeError(SP_ERROR_NATIVE, "You can only call this once from a MenuAction_Display callback"); - } - - NativeVotesType voteType = Data_GetType(g_hCurVote); - - if (voteType != NativeVotesType_Custom_Mult && voteType != NativeVotesType_Custom_YesNo) - { - return view_as(Plugin_Continue); - } - - GetNativeString(1, g_newMenuTitle, TRANSLATION_LENGTH); - return view_as(Plugin_Changed); -} - -// native Action:NativeVotes_RedrawVoteItem(const String:text[]); -public int Native_RedrawVoteItem(Handle plugin, int numParams) -{ - if (!g_curItemClient) - { - ThrowNativeError(SP_ERROR_NATIVE, "You can only call this once from a MenuAction_DisplayItem callback"); - } - - if (Game_GetMaxItems() == L4DL4D2_COUNT) - { - return view_as(Plugin_Continue); - } - - GetNativeString(1, g_newMenuItem, TRANSLATION_LENGTH); - return view_as(Plugin_Changed); -} +/** + * vim: set ts=4 : + * ============================================================================= + * NativeVotes + * NativeVotes is a voting API plugin for L4D, L4D2, TF2, and CS:GO. + * Based on the SourceMod voting API + * + * NativeVotes (C) 2011-2015 Ross Bemrose (Powerlord). All rights reserved. + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program 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 the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#include +#include + +#include +#include + +EngineVersion g_EngineVersion = Engine_Unknown; +int g_AppID = 0; + +#include "nativevotes/data-keyvalues.sp" + +#define LOGTAG "NativeVotes" + +#define MAX_VOTE_DETAILS_LENGTH 64 // From SourceSDK2013's shareddefs.h +#define TRANSLATION_LENGTH 192 + +#define VOTE_DELAY_TIME 3.0 + +// SourceMod uses these internally, so... we do too. +#define VOTE_NOT_VOTING -2 +#define VOTE_PENDING -1 + +#define MAX_VOTE_ISSUES 20 +#define VOTE_STRING_SIZE 32 + +//---------------------------------------------------------------------------- +// These values are swapped from their NativeVotes equivalent +#define L4D2_VOTE_YES_INDEX 1 +#define L4D2_VOTE_NO_INDEX 0 + +#define L4DL4D2_COUNT 2 +#define TF2CSGO_COUNT 5 + +#define MAX_CALLVOTE_SIZE 128 + +//#define LOG + +//---------------------------------------------------------------------------- +// Global Variables +int g_NextVote = 0; + +//---------------------------------------------------------------------------- +// CVars +enum +{ + progress_hintbox, + progress_chat, + progress_console, + progress_client_console, + progress_hintcaption, + progress_hintcaption_icon, + vote_delay, + + MAX_CONVARS +}; + +ConVar g_ConVars[MAX_CONVARS]; + +//---------------------------------------------------------------------------- +// Used to track current vote data +Handle g_hVoteTimer; +Handle g_hDisplayTimer; + +int g_Clients; +int g_TotalClients; +int g_Items; +ArrayList g_hVotes; +NativeVote g_hCurVote; +int g_curDisplayClient = 0; +char g_newMenuTitle[TRANSLATION_LENGTH]; +int g_curItemClient = 0; +char g_newMenuItem[TRANSLATION_LENGTH]; + +bool g_bStarted; +bool g_bCancelled; +int g_NumVotes; +int g_VoteTime; +int g_VoteFlags; +float g_fStartTime; +int g_TimeLeft; +int g_ClientVotes[MAXPLAYERS+1]; +bool g_bRevoting[MAXPLAYERS+1]; + +ConVar sv_vote_holder_may_vote_no; + +// Map list stuffs + +//#define MAP_STRING_CACHE_SIZE PLATFORM_MAX_PATH * 256 + +// Forward +Handle g_OverrideMaps; +StringMap g_MapOverrides; +bool g_OverridesSet; +bool g_OverrideNextCallVote[MAXPLAYERS + 1]; + +enum struct CallVoteForwards +{ + Handle CallVote_Forward; + Handle CallVote_Vis; +} + +enum struct CallVoteListData +{ + NativeVotesOverride CallVoteList_VoteType; + bool CallVoteList_VoteEnabled; +} + +CallVoteForwards g_CallVotes[NativeVotesOverride_Count]; + +#include "nativevotes/game.sp" + +public Plugin myinfo = +{ + name = "NativeVotes", + author = "Powerlord", + description = "Voting API to use the game's native vote panels. Compatible with L4D, L4D2, TF2, and CS:GO.", + version = "26w15a", + url = "https://github.com/Heapons/sourcemod-nativevotes-updated/" +} + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) +{ + char engineName[64]; + if (!Game_IsGameSupported(engineName, sizeof(engineName))) + { + Format(error, err_max, "Unsupported game: %s", engineName); + return APLRes_Failure; + } + + KeyValues kv = new KeyValues("GameInfo"); + if (kv.ImportFromFile("gameinfo.txt") && kv.JumpToKey("FileSystem")) + { + g_AppID = kv.GetNum("SteamAppId"); + } + delete kv; + + CreateNative("NativeVotes_IsVoteTypeSupported", Native_IsVoteTypeSupported); + CreateNative("NativeVotes_Create", Native_Create); + CreateNative("NativeVotes_Close", Native_Close); + CreateNative("NativeVotes_Display", Native_Display); + CreateNative("NativeVotes_AddItem", Native_AddItem); + CreateNative("NativeVotes_InsertItem", Native_InsertItem); + CreateNative("NativeVotes_RemoveItem", Native_RemoveItem); + CreateNative("NativeVotes_RemoveAllItems", Native_RemoveAllItems); + CreateNative("NativeVotes_GetItem", Native_GetItem); + CreateNative("NativeVotes_GetItemCount", Native_GetItemCount); + CreateNative("NativeVotes_SetDetails", Native_SetDetails); + CreateNative("NativeVotes_GetDetails", Native_GetDetails); + CreateNative("NativeVotes_SetTitle", Native_SetTitle); + CreateNative("NativeVotes_GetTitle", Native_GetTitle); + CreateNative("NativeVotes_SetTarget", Native_SetTarget); + CreateNative("NativeVotes_GetTarget", Native_GetTarget); + CreateNative("NativeVotes_GetTargetSteam", Native_GetTargetSteam); + CreateNative("NativeVotes_IsVoteInProgress", Native_IsVoteInProgress); + CreateNative("NativeVotes_GetMaxItems", Native_GetMaxItems); + CreateNative("NativeVotes_SetOptionFlags", Native_SetOptionFlags); + CreateNative("NativeVotes_GetOptionFlags", Native_GetOptionFlags); + CreateNative("NativeVotes_SetNoVoteButton", Native_SetNoVoteButton); + CreateNative("NativeVotes_Cancel", Native_Cancel); + CreateNative("NativeVotes_SetResultCallback", Native_SetResultCallback); + CreateNative("NativeVotes_CheckVoteDelay", Native_CheckVoteDelay); + CreateNative("NativeVotes_IsClientInVotePool", Native_IsClientInVotePool); + CreateNative("NativeVotes_RedrawClientVote", Native_RedrawClientVote); + CreateNative("NativeVotes_GetType", Native_GetType); + CreateNative("NativeVotes_SetTeam", Native_SetTeam); + CreateNative("NativeVotes_GetTeam", Native_GetTeam); + CreateNative("NativeVotes_SetInitiator", Native_SetInitiator); + CreateNative("NativeVotes_GetInitiator", Native_GetInitiator); + CreateNative("NativeVotes_DisplayPass", Native_DisplayPass); + CreateNative("NativeVotes_DisplayPassCustomToOne", Native_DisplayPassCustomToOne); + CreateNative("NativeVotes_DisplayPassEx", Native_DisplayPassEx); + //CreateNative("NativeVotes_DisplayRawPass", Native_DisplayRawPass); + CreateNative("NativeVotes_DisplayRawPassToOne", Native_DisplayRawPassToOne); + CreateNative("NativeVotes_DisplayRawPassCustomToOne", Native_DisplayRawPassCustomToOne); + CreateNative("NativeVotes_DisplayFail", Native_DisplayFail); + CreateNative("NativeVotes_DisplayRawFail", Native_DisplayRawFail); + //CreateNative("NativeVotes_DisplayRawFailToOne", Native_DisplayRawFailToOne); + CreateNative("NativeVotes_AreVoteCommandsSupported", Native_AreVoteCommandsSupported); + CreateNative("NativeVotes_RegisterVoteCommand", Native_RegisterVoteCommand); + CreateNative("NativeVotes_UnregisterVoteCommand", Native_UnregisterVoteCommand); + CreateNative("NativeVotes_DisplayCallVoteFail", Native_DisplayCallVoteFail); + CreateNative("NativeVotes_RedrawVoteTitle", Native_RedrawVoteTitle); + CreateNative("NativeVotes_RedrawVoteItem", Native_RedrawVoteItem); + + // Transitional syntax support + CreateNative("NativeVote.NativeVote", Native_Create); + CreateNative("NativeVote.Close", Native_Close); + CreateNative("NativeVote.AddItem", Native_AddItem); + CreateNative("NativeVote.InsertItem", Native_InsertItem); + CreateNative("NativeVote.RemoveItem", Native_RemoveItem); + CreateNative("NativeVote.RemoveAllItems", Native_RemoveAllItems); + CreateNative("NativeVote.GetItem", Native_GetItem); + CreateNative("NativeVote.SetDetails", Native_SetDetails); + CreateNative("NativeVote.GetDetails", Native_GetDetails); + CreateNative("NativeVote.SetTitle", Native_SetTitle); + CreateNative("NativeVote.GetTitle", Native_GetTitle); + CreateNative("NativeVote.SetTarget", Native_SetTarget); + CreateNative("NativeVote.GetTarget", Native_GetTarget); + CreateNative("NativeVote.GetTargetSteam", Native_GetTargetSteam); + CreateNative("NativeVote.DisplayVote", Native_Display); + CreateNative("NativeVote.DisplayPass", Native_DisplayPass); + CreateNative("NativeVote.DisplayPassCustomToOne", Native_DisplayPassCustomToOne); + CreateNative("NativeVote.DisplayPassEx", Native_DisplayPassEx); + CreateNative("NativeVote.DisplayFail", Native_DisplayFail); + CreateNative("NativeVote.OptionFlags.set", Native_SetOptionFlags); + CreateNative("NativeVote.OptionFlags.get", Native_GetOptionFlags); + CreateNative("NativeVote.NoVoteButton.set", Native_SetNoVoteButton); + CreateNative("NativeVote.VoteResultCallback.set", Native_SetResultCallback); + CreateNative("NativeVote.ItemCount.get", Native_GetItemCount); + CreateNative("NativeVote.VoteType.get", Native_GetType); + CreateNative("NativeVote.Team.set", Native_SetTeam); + CreateNative("NativeVote.Team.get", Native_GetTeam); + CreateNative("NativeVote.Initiator.set", Native_SetInitiator); + CreateNative("NativeVote.Initiator.get", Native_GetInitiator); + + RegPluginLibrary("nativevotes"); + + return APLRes_Success; +} + +public void OnPluginStart() +{ + LoadTranslations("core.phrases"); + LoadTranslations("nativevotes.phrases.txt"); + + g_ConVars[progress_hintbox] = CreateConVar("nativevotes_progress_hintbox", "0", "Show current vote progress in a hint box", _, true, 0.0, true, 1.0); + g_ConVars[progress_chat] = CreateConVar("nativevotes_progress_chat", "0", "Show current vote progress as chat messages", _, true, 0.0, true, 1.0); + g_ConVars[progress_console] = CreateConVar("nativevotes_progress_console", "0", "Show current vote progress as console messages", _, true, 0.0, true, 1.0); + g_ConVars[progress_client_console] = CreateConVar("nativevotes_progress_client_console", "0", "Show current vote progress as console messages to clients", _, true, 0.0, true, 1.0); + g_ConVars[vote_delay] = CreateConVar("nativevotes_vote_delay", "0", "Sets the recommended time in between public votes", _, true, 0.0); + g_ConVars[vote_delay].AddChangeHook(OnVoteDelayChange); + + Event instructor = CreateEvent("instructor_server_hint_create", true); + if (instructor != null) + { + g_ConVars[progress_hintcaption] = CreateConVar("nativevotes_progress_hintcaption", "0", "Show current vote progress in a instructor hint caption", _, true, 0.0, true, 1.0); + g_ConVars[progress_hintcaption_icon] = CreateConVar("nativevotes_progress_hintcaption_icon", "icon_tip", "Set the icon to use in instructor hint captions during votes"); + instructor.Cancel(); + } + + Game_InitializeCvars(); + + AddCommandListener(Command_Vote, "vote"); // All games, command listeners aren't case sensitive + + sv_vote_holder_may_vote_no = FindConVar("sv_vote_holder_may_vote_no"); + // The new version of the CallVote system is TF2 only + if (Game_AreVoteCommandsSupported()) + { + AddCommandListener(Command_CallVote, "callvote"); + + // None is type 0, which has no overrides + // As of 2015-09-28, there are 10 votes for a total of 20 private forwards created here. + for (int i = 1; i < sizeof(g_CallVotes); i++) + { + g_CallVotes[i].CallVote_Forward = CreateForward(ET_Hook, Param_Cell, Param_Cell, Param_String, Param_Cell, Param_Cell); + g_CallVotes[i].CallVote_Vis = CreateForward(ET_Hook, Param_Cell, Param_Cell); + } + + g_OverrideMaps = CreateGlobalForward("NativeVotes_OverrideMaps", ET_Hook, Param_Cell); + } + + g_hVotes = new ArrayList(1, Game_GetMaxItems()); + + AutoExecConfig(true, "nativevotes"); +} + +public void OnMapStart() +{ + // Map list stuffs + if (g_MapOverrides != null) + delete g_MapOverrides; + + g_OverridesSet = false; + g_NextVote = 0; +} + +public Action Timer_RetryCallVote(Handle timer, any data) +{ + int client = GetClientOfUserId(data); + + if (client == 0) + { + return Plugin_Stop; + } + + FakeClientCommand(client, "callvote"); + return Plugin_Stop; +} + +public void ProcessMapList() +{ + char stringTable[64] = "ServerMapCycle"; + if (FindSendPropInfo("CTFGameRulesProxy", "m_bPlayingMannVsMachine") > 0 && GameRules_GetProp("m_bPlayingMannVsMachine")) + { + Format(stringTable, sizeof(stringTable), "%sMvM", stringTable); + } + + int stringTableIndex = FindStringTable(stringTable); + int stringIndex = FindStringIndex(stringTableIndex, stringTable); + + // Maplist resets every map + int length = GetStringTableDataLength(stringTableIndex, stringIndex); + char[] mapData = new char[length]; + GetStringTableData(stringTableIndex, stringIndex, mapData, length); + + // We'll get an extra blank entry if we don't do this + TrimString(mapData); + + StringMap overrideList = new StringMap(); + ExplodeStringToStringMap(mapData, "\n", overrideList, PLATFORM_MAX_PATH, ImplodePart_Key); + + Action mapResult = Plugin_Continue; + Call_StartForward(g_OverrideMaps); + Call_PushCell(overrideList); + Call_Finish(mapResult); + + if (mapResult != Plugin_Changed || overrideList.Size <= 0) + { + delete overrideList; + return; + } + +#if defined LOG + LogMessage("Overriding map list with %d maps", overrideList.Size); +#endif + + g_MapOverrides = overrideList; + + int maxLength = GetStringMapImplodeSize(overrideList, 1, ImplodePart_Key); + + char[] newMapData = new char[maxLength]; + int newLength = ImplodeStringMapToString(overrideList, "\n", newMapData, maxLength, ImplodePart_Key); + if (newLength < maxLength && newMapData[newLength] != '\n') + { + // do this to avoid a StrCat + newLength += strcopy(newMapData[newLength], maxLength, "\n") + 1; + } + + SetStringTableData(stringTableIndex, stringIndex, newMapData, newLength); +} + +public void OnClientDisconnect_Post(int client) +{ + if (!Internal_IsVoteInProgress() || !Internal_IsClientInVotePool(client)) + { + return; + } + + /* Wipe out their vote if they had one. We have to make sure the + * newly connected client is not allowed to vote. + */ + int item = g_ClientVotes[client]; + if (item >= VOTE_PENDING) + { + if (item > VOTE_PENDING) + { + g_hVotes.Set(item, g_hVotes.Get(item) - 1); + } + + g_ClientVotes[client] = VOTE_NOT_VOTING; + } + + CancelClientVote(g_hCurVote, client, MenuCancel_Disconnected); +} + +void CancelClientVote(NativeVote vote, int client, int reason) +{ + OnCancel(vote, client, reason); + OnClientEnd(); +} + +public Action Command_CallVote(int client, const char[] command, int argc) +{ + if (g_OverrideNextCallVote[client]) + { + g_OverrideNextCallVote[client] = false; + return Plugin_Continue; + } + + if (Internal_IsVoteInProgress() || Game_IsVoteInProgress() || !IsClientInGame(client)) + { + return Plugin_Handled; + } + + Action result = Plugin_Continue; + + switch (argc) + { + // No args means that we need to return a CallVoteSetup usermessage + case 0: + { + if (!g_OverridesSet && Game_AreVoteCommandsSupported()) + { + g_OverridesSet = true; + ProcessMapList(); + +#if defined LOG + LogMessage("Delaying to allow stringtable time to network"); +#endif + // Force it to reissue the callvote command so that the map list gets networked + CreateTimer(0.1, Timer_RetryCallVote, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + return Plugin_Handled; + } + + ArrayList hVoteTypes = new ArrayList(sizeof(CallVoteListData)); // Stores arrays of CallVoteListData + + Game_AddDefaultVotes(hVoteTypes); + + // Add our overridden votes to the system + bool overridesPresent = false; + for (int i = 1; i < sizeof(g_CallVotes); i++) + { + if (GetForwardFunctionCount(g_CallVotes[i].CallVote_Forward) > 0) + { +#if defined LOG + LogMessage("Found overrides for vote type: %d", i); +#endif + overridesPresent = true; + CallVoteListData voteType; + + int pos = FindVoteInArray(hVoteTypes, view_as(i)); + if (pos > -1) + { + hVoteTypes.GetArray(pos, voteType, sizeof(CallVoteListData)); + voteType.CallVoteList_VoteEnabled = true; +#if defined LOG + LogMessage("Forcing vote type to visible: %d", i); +#endif + hVoteTypes.SetArray(pos, voteType); + } + else + { +#if defined LOG + LogMessage("Creating override for vote type: %d", i); +#endif + voteType.CallVoteList_VoteType = view_as(i); + voteType.CallVoteList_VoteEnabled = true; + hVoteTypes.PushArray(voteType); + } + } + } + + if (overridesPresent) + { + PerformVisChecks(client, hVoteTypes); +#if defined LOG + LogMessage("Overriding VoteSetup message"); +#endif + Game_DisplayVoteSetup(client, hVoteTypes); + delete hVoteTypes; + return Plugin_Handled; + } + else + { + delete hVoteTypes; + return Plugin_Continue; + } + } + + default: + { + char voteCommand[VOTE_STRING_SIZE]; + GetCmdArg(1, voteCommand, VOTE_STRING_SIZE); + +#if defined LOG + LogMessage("User is attempting to call %s", voteCommand); +#endif + + NativeVotesOverride overrideType = Game_VoteStringToVoteOverride(voteCommand); + + char argument[PLATFORM_MAX_PATH]; + + if (GetForwardFunctionCount(g_CallVotes[overrideType].CallVote_Forward) == 0) + { + + if (g_MapOverrides != null && + (overrideType == NativeVotesOverride_ChgLevel || + overrideType == NativeVotesOverride_NextLevel)) + { + char map[PLATFORM_MAX_PATH]; + + GetCmdArg(2, map, sizeof(map)); + g_MapOverrides.GetString(map, argument, sizeof(argument)); + + g_OverrideNextCallVote[client] = true; + FakeClientCommandEx(client, "callvote %s %s", voteCommand, argument); + return Plugin_Handled; + } + +#if defined LOG + LogMessage("We don't have a handler for %s, passing back to server", voteCommand); +#endif + return Plugin_Continue; + } + + // Vis checks are done here just in case something went wrong and the vote option was shown to a person it shouldn't be. +#if defined LOG + LogMessage("Calling visForward for %s", voteCommand); +#endif + Call_StartForward(g_CallVotes[overrideType].CallVote_Vis); + Call_PushCell(client); + Call_PushCell(overrideType); + Call_Finish(result); + if (result >= Plugin_Handled) + { + return result; + } + + NativeVotesType voteType = Game_VoteStringToVoteType(voteCommand); + + int target = 0; + + NativeVotesKickType kickType = NativeVotesKickType_None; + + switch (voteType) + { + case NativeVotesType_Kick: + { + char param1[20]; + GetCmdArg(2, param1, sizeof(param1)); + + kickType = Game_GetKickType(param1, target); + + int targetClient = GetClientOfUserId(target); + + if (targetClient < 1 || targetClient > MaxClients || !IsClientInGame(targetClient)) + { + return Plugin_Continue; + } + + GetClientName(targetClient, argument, sizeof(argument)); + } + case NativeVotesType_ChgLevel, NativeVotesType_NextLevel: + { + if (g_MapOverrides == null) + { + GetCmdArg(2, argument, sizeof(argument)); + } + else + { + char map[PLATFORM_MAX_PATH]; + + GetCmdArg(2, map, sizeof(map)); + g_MapOverrides.GetString(map, argument, sizeof(argument)); + } + } + default: + { + GetCmdArg(2, argument, sizeof(argument)); + } + } + +#if defined LOG + LogMessage("Calling callVoteForward for %s", voteCommand); +#endif + + Call_StartForward(g_CallVotes[overrideType].CallVote_Forward); + Call_PushCell(client); + Call_PushCell(overrideType); + Call_PushString(argument); + Call_PushCell(kickType); + Call_PushCell(target); + Call_Finish(result); + if (result >= Plugin_Handled) + { + return result; + } + } + } + + // Default to continue if we're not processing things + return result; + +} + +stock int FindVoteInArray(ArrayList myArray, NativeVotesOverride value) +{ + int size = myArray.Length; + for (int i = 0; i < size; i++) + { + CallVoteListData voteData; + myArray.GetArray(i, voteData, sizeof(CallVoteListData)); + + if (voteData.CallVoteList_VoteType == value) + { + return i; + } + } + + return -1; +} + +stock bool IsVoteEnabled(ArrayList myArray, NativeVotesOverride value) +{ + int pos = FindVoteInArray(myArray, value); + if (pos > -1) + { + CallVoteListData voteType; + myArray.GetArray(pos, voteType, sizeof(CallVoteListData)); + if (voteType.CallVoteList_VoteEnabled) + return true; + } + return false; +} + +public void OnVoteDelayChange(ConVar convar, const char[] oldValue, const char[] newValue) +{ + /* See if the new vote delay isn't something we need to account for */ + if (convar.IntValue < 1) + { + g_NextVote = 0; + return; + } + + /* If there was never a last vote, ignore this change */ + if (g_NextVote <= 0) + { + return; + } + + /* Subtract the original value, then add the new one. */ + g_NextVote -= StringToInt(oldValue); + g_NextVote += StringToInt(newValue); +} + +public void OnMapEnd() +{ + if (g_hCurVote != null) + { + // Cancel the ongoing vote, but don't close the handle, as the other plugins may still re-use it + CancelVoting(); + //OnVoteCancel(g_hCurVote, VoteCancel_Generic); + g_hCurVote = null; + } + + if (g_hDisplayTimer != null) + { + delete g_hDisplayTimer; + } + +// g_hVoteTimer = INVALID_HANDLE; +} + +public Action Command_Vote(int client, const char[] command, int argc) +{ +#if defined LOG + char voteString[128]; + GetCmdArgString(voteString, sizeof(voteString)); + LogMessage("Client %N ran a vote command: %s", client, voteString); +#endif + + // If we're not running a vote, return the vote control back to the server + if (!Internal_IsVoteInProgress() || g_ClientVotes[client] != VOTE_PENDING) + { + return Plugin_Continue; + } + + char option[64]; + GetCmdArgString(option, sizeof(option)); + + int item = Game_ParseVote(option); + + // Make sure we don't go out of bounds on the vote + if (item == NATIVEVOTES_VOTE_INVALID || item > g_Items) + { + return Plugin_Handled; + } + + bool cancel; + + if (Data_GetFlags(g_hCurVote) & MENUFLAG_BUTTON_NOVOTE && item == 0) + { + cancel = true; + } + + if (cancel) + { + OnCancel(g_hCurVote, client, MenuCancel_Exit); + } + else + { + OnVoteSelect(g_hCurVote, client, item); + } + + OnClientEnd(); + + return Plugin_Handled; +} + +void OnVoteSelect(NativeVote vote, int client, int item) +{ + if (Internal_IsVoteInProgress() && g_ClientVotes[client] == VOTE_PENDING) + { + /* Check by our item count, NOT the vote array size */ + if (item < g_Items) + { + Game_ClientSelectedItem(vote, client, item); + + g_ClientVotes[client] = item; + g_hVotes.Set(item, g_hVotes.Get(item) + 1); + g_NumVotes++; + + Game_UpdateVoteCounts(g_hVotes, g_TotalClients); + + if (g_ConVars[progress_chat].BoolValue || g_ConVars[progress_console].BoolValue || g_ConVars[progress_client_console].BoolValue) + { + char choice[128]; + char name[MAX_NAME_LENGTH+1]; + Data_GetItemDisplay(vote, item, choice, sizeof(choice)); + + GetClientName(client, name, MAX_NAME_LENGTH); + + if (g_ConVars[progress_console].BoolValue) + { + PrintToServer("[%s] %T", LOGTAG, "Voted For", LANG_SERVER, name, choice); + } + + if (g_ConVars[progress_chat].BoolValue || g_ConVars[progress_client_console].BoolValue) + { + char phrase[30]; + + if (g_bRevoting[client]) + { + strcopy(phrase, sizeof(phrase), "Changed Vote"); + } + else + { + strcopy(phrase, sizeof(phrase), "Voted For"); + } + + if (g_ConVars[progress_chat].BoolValue) + { + int r, g, b, a, color; + GetEntityRenderColor(client, r, g, b, a); + color = (r << 16) | (g << 8) | b; + if (color != 0xFFFFFF) + { + Format(name, sizeof(name), "{#%06X}%N\x01", color, client); + } + else + { + Format(name, sizeof(name), "{teamcolor}%N\x01", client); + } + CPrintToChatAllEx(client, "[%s] %t", LOGTAG, phrase, name, choice); + } + + if (g_ConVars[progress_client_console].BoolValue) + { + for (int i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i) && !IsFakeClient(i)) + { + PrintToConsole(i, "[%s] %t", LOGTAG, phrase, name, choice); + } + } + } + } + } + + DrawHintProgress(); + + OnSelect(g_hCurVote, client, item); + } + } +} + +//MenuAction_Select +void OnSelect(NativeVote vote, int client, int item) +{ + MenuAction actions = Data_GetActions(vote); + if (actions & MenuAction_Select) + { + DoAction(vote, MenuAction_Select, client, item); + } +} + +//MenuAction_End +void OnEnd(NativeVote vote, int item) +{ + // Always called + DoAction(vote, MenuAction_End, item, 0); +} + + +stock void OnVoteEnd(NativeVote vote, int item) +{ + // Always called + DoAction(vote, MenuAction_VoteEnd, item, 0); +} + +void OnVoteStart(NativeVote vote) +{ + // Fire both Start and VoteStart in the other plugin. + + MenuAction actions = Data_GetActions(vote); + if (actions & MenuAction_Start) + { + DoAction(vote, MenuAction_Start, 0, 0); + } + + // Always called + DoAction(vote, MenuAction_VoteStart, 0, 0); +} + +void OnVoteCancel(NativeVote vote, int reason) +{ + // Always called + DoAction(vote, MenuAction_VoteCancel, reason, 0); +} + +void OnCancel(NativeVote vote, int client, int reason) +{ + DoAction(vote, MenuAction_Cancel, client, reason); +} + +void OnClientEnd() +{ + DecrementPlayerCount(); +} + +Action DoAction(NativeVote vote, MenuAction action, int param1, int param2, Action def_res = Plugin_Continue) +{ + Action res = def_res; + + Handle handler = CloneHandle(Data_GetHandler(vote)); +#if defined LOG + LogMessage("Calling Menu forward for vote: %d, handler: %d, action: %d, param1: %d, param2: %d", vote, handler, action, param1, param2); +#endif + Call_StartForward(handler); + Call_PushCell(vote); + Call_PushCell(action); + Call_PushCell(param1); + Call_PushCell(param2); + Call_Finish(res); + delete handler; + return res; +} + +void OnVoteResults(NativeVote vote, const int[][] votes, int num_votes, int item_count, const int[][] client_list, int num_clients) +{ + Handle resultsHandler = Data_GetResultCallback(vote); + + if (resultsHandler == null || !GetForwardFunctionCount(resultsHandler)) + { + /* Call MenuAction_VoteEnd instead. See if there are any extra winners. */ + int num_items = 1; + for (int i = 1; i < num_votes; i++) + { + if (votes[i][VOTEINFO_ITEM_VOTES] != votes[0][VOTEINFO_ITEM_VOTES]) + { + break; + } + num_items++; + } + + /* See if we need to pick a random winner. */ + int winning_item; + if (num_items > 1) + { + /* Yes, we do */ + winning_item = GetRandomInt(0, num_items - 1); + winning_item = votes[winning_item][VOTEINFO_ITEM_INDEX]; + } + else + { + /* No, take the first */ + winning_item = votes[0][VOTEINFO_ITEM_INDEX]; + } + + int winning_votes = votes[0][VOTEINFO_ITEM_VOTES]; + + DoAction(vote, MenuAction_VoteEnd, winning_item, (num_votes << 16) | (winning_votes & 0xFFFF)); + } + else + { + // This code is quite different than its C++ version, as we're reversing the logic previously done + + int[] client_indexes = new int[num_clients]; + int[] client_items = new int[num_clients]; + int[] vote_items = new int[item_count]; + int[] vote_votes = new int[item_count]; + + /* First array */ + for (int i = 0; i < item_count; i++) + { + vote_items[i] = votes[i][VOTEINFO_ITEM_INDEX]; + vote_votes[i] = votes[i][VOTEINFO_ITEM_VOTES]; + } + + /* Second array */ + for (int i = 0; i < num_clients; i++) + { + client_indexes[i] = client_list[i][VOTEINFO_CLIENT_INDEX]; + client_items[i] = client_list[i][VOTEINFO_CLIENT_ITEM]; + } + +#if defined LOG + LogMessage("Calling results forward for vote: %d, num_votes: %d, num_clients: %d, item_count: %d", vote, num_votes, num_clients, item_count); +#endif + + Call_StartForward(resultsHandler); + Call_PushCell(vote); + Call_PushCell(num_votes); + Call_PushCell(num_clients); + Call_PushArray(client_indexes, num_clients); + Call_PushArray(client_items, num_clients); + Call_PushCell(item_count); + Call_PushArray(vote_items, item_count); + Call_PushArray(vote_votes, item_count); + Call_Finish(); + } +} + +/* +VoteEnd(Handle:vote) +{ + if (g_NumVotes == 0) + { + // Fire VoteCancel in the other plugin + OnVoteCancel(vote, VoteCancel_NoVotes); + } + else + { + new num_items; + new num_votes; + + new slots = Game_GetMaxItems(); + new votes[slots][2]; + + Internal_GetResults(votes, slots); + + if (!SendResultCallback(vote, num_votes, num_items, votes)) + { + Handle handler = Data_GetHandler(g_hCurVote); + + Call_StartForward(handler); + Call_PushCell(g_CurVote); + Call_PushCell(MenuAction_VoteEnd); + Call_PushCell(votes[0][VOTEINFO_ITEM_INDEX]); + Call_PushCell(0); + Call_Finish(); + } + } + +} + +bool:SendResultCallback(Handle:vote, num_votes, num_items, const votes[][]) +{ + Handle voteResults = Data_GetResultCallback(g_CurVote); + if (GetForwardFunctionCount(voteResults) == 0) + { + return false; + } + + // This block is present because we can't pass 2D arrays to other plugins' functions + new item_indexes[]; + new item_votes[]; + + for (int i = 0, i < num_items; i++) + { + item_indexes[i] = votes[i][VOTEINFO_ITEM_INDEX]; + item_votes[i] = votes[i][VOTEINFO_ITEM_VOTES]; + } + + // Client block + new client_indexes[MaxClients]; + new client_votes[MaxClients]; + + new num_clients; + for (int i = 1; i <= MaxClients; i++) + { + if (g_ClientVotes[i] > VOTE_PENDING) + { + client_indexes[num_clients] = i; + client_votes[num_clients] = g_ClientVotes[i]; + num_clients++; + } + } + + Call_StartForward(voteResults); + Call_PushCell(_:vote); + Call_PushCell(num_votes); + Call_PushCell(num_clients); + Call_PushArray(client_indexes, num_clients); + Call_PushArray(client_votes, num_clients); + Call_PushCell(num_items); + Call_PushArray(item_indexes, num_items); + Call_PushArray(item_votes, num_items); + Call_Finish(); + + return true; +} +*/ + +void DrawHintProgress() +{ + bool showHintbox = g_ConVars[progress_hintbox].BoolValue; + bool showHintCaption = g_ConVars[progress_hintcaption] != null && g_ConVars[progress_hintcaption].BoolValue; + if (!showHintbox && !showHintCaption) + { + return; + } + + float timeRemaining = (g_fStartTime + g_VoteTime) - GetGameTime(); + + if (timeRemaining < 0.0) + { + timeRemaining = 0.0; + } + + int iTimeRemaining = RoundFloat(timeRemaining); + + NativeVotesType voteType = Data_GetType(g_hCurVote); + bool isMultVote = (voteType == NativeVotesType_Custom_Mult || voteType == NativeVotesType_NextLevelMult); + char leaderList[1024]; + if (isMultVote) + { + BuildVoteLeaders(leaderList, sizeof(leaderList)); + } + else + { + leaderList[0] = '\0'; + } + + for (int client = 1; client <= MaxClients; client++) + { + if (!IsClientInGame(client) || IsFakeClient(client)) + continue; + + SetGlobalTransTarget(client); + if (showHintbox) + { + if (isMultVote) + PrintHintText(client, "%t\n%s", "Vote Count", g_NumVotes, g_TotalClients, iTimeRemaining, leaderList); + else + PrintHintText(client, "%t", "Vote Count", g_NumVotes, g_TotalClients, iTimeRemaining); + } + if (showHintCaption) + { + char baseText[TRANSLATION_LENGTH]; + Format(baseText, sizeof(baseText), "%t", "Vote Count", g_NumVotes, g_TotalClients, iTimeRemaining); + if (isMultVote && leaderList[0] != '\0') + { + int baseLen = strlen(baseText); + int maxLeaderLen = 256 - baseLen - 1; + if (maxLeaderLen < 1) + { + char caption[1024]; + Format(caption, sizeof(caption), "%t", "Vote Count", g_NumVotes, g_TotalClients, iTimeRemaining); + SendInstructorHint(client, caption); + } + else + { + char[] cappedLeaders = new char[maxLeaderLen + 1]; + BuildVoteLeaders(cappedLeaders, maxLeaderLen + 1); + char caption[1024]; + Format(caption, sizeof(caption), "%s\n%s", baseText, cappedLeaders); + SendInstructorHint(client, caption); + } + } + else + { + char caption[1024]; + Format(caption, sizeof(caption), "%t", "Vote Count", g_NumVotes, g_TotalClients, iTimeRemaining); + SendInstructorHint(client, caption); + } + } + } +} + +void BuildVoteLeaders(char[] leaderList, int leaderListSize) +{ + if (leaderListSize <= 1) + { + return; + } + + leaderList[0] = '\0'; + + if (g_NumVotes == 0) + { + return; + } + + // Since we can't have structs, we get "struct" with this instead + + int slots = Game_GetMaxItems(); + int[][] votes = new int[slots][2]; + + int num_items = Internal_GetResults(votes); + + /* Build the full leaderboard */ + for (int i = 0; i < num_items; i++) + { + int cur_item = votes[i][VOTEINFO_ITEM_INDEX]; + char choice[1024]; + Data_GetItemDisplay(g_hCurVote, cur_item, choice, sizeof(choice)); + char line[1024]; + Format(line, sizeof(line), "%i. %s: (%i)", i + 1, choice, votes[i][VOTEINFO_ITEM_VOTES]); + if (i == 0) + { + Format(leaderList, leaderListSize, "%s", line); + } + else + { + Format(leaderList, leaderListSize, "%s\n%s", leaderList, line); + } + } +} + +public int SortVoteItems(int[] a, int[] b, const int[][] array, Handle hndl) +{ + if (b[VOTEINFO_ITEM_VOTES] == a[VOTEINFO_ITEM_VOTES]) + { + return 0; + } + else if (b[VOTEINFO_ITEM_VOTES] > a[VOTEINFO_ITEM_VOTES]) + { + return 1; + } + else + { + return -1; + } +} + +void DecrementPlayerCount() +{ + g_Clients--; + + // The vote is running and we have no clients left, so end the vote. + if (g_bStarted && g_Clients == 0) + { + EndVoting(); + } + +} + + +void EndVoting() +{ + for (int client = 1; client <= MaxClients; client++) + { + if (!IsClientInGame(client) || !IsClientConnected(client) || IsFakeClient(client)) + continue; + EndInstructorHint(client); + } + + int voteDelay = g_ConVars[vote_delay].IntValue; + if (voteDelay < 1) + { + g_NextVote = 0; + } + else + { + g_NextVote = GetTime() + voteDelay; + } + + if (g_hDisplayTimer != null) + { + delete g_hDisplayTimer; + } + + if (g_bCancelled) + { + /* If we were cancelled, don't bother tabulating anything. + * Reset just in case someone tries to redraw, which means + * we need to save our states. + */ + NativeVote vote = g_hCurVote; + Internal_Reset(true); + OnVoteCancel(vote, VoteCancel_Generic); + OnEnd(vote, MenuEnd_VotingCancelled); + return; + } + + int slots = Game_GetMaxItems(); + int[][] votes = new int[slots][2]; + int num_votes; + int num_items = Internal_GetResults(votes, num_votes); + + if (!num_votes) + { + NativeVote vote = g_hCurVote; + Internal_Reset(); + OnVoteCancel(vote, VoteCancel_NoVotes); + OnEnd(vote, MenuEnd_VotingCancelled); + return; + } + + int[][] client_list = new int[MaxClients][2]; + int num_clients = Internal_GetClients(client_list); + + /* Save states, then clear what we've saved. + * This makes us re-entrant, which is always the safe way to go. + */ + NativeVote vote = g_hCurVote; + Internal_Reset(); + +#if defined LOG + LogMessage("Voting done"); +#endif + + /* Send vote info */ + OnVoteResults(vote, votes, num_votes, num_items, client_list, num_clients); + OnEnd(vote, MenuEnd_VotingDone); +} + +bool StartVote(NativeVote vote, int num_clients, int[] clients, int max_time, int flags) +{ + if (!InitializeVoting(vote, max_time, flags)) + { + return false; + } + + /* Due to hibernating servers, we no longer use GameTime, but instead standard timestamps. + */ + + int voteDelay = g_ConVars[vote_delay].IntValue; + if (voteDelay < 1) + { + g_NextVote = 0; + } + else + { + /* This little trick break for infinite votes! + * However, we just ignore that since those 1) shouldn't exist and + * 2) people must be checking IsVoteInProgress() beforehand anyway. + */ + g_NextVote = GetTime() + voteDelay + max_time; + } + + g_fStartTime = GetGameTime(); + g_VoteTime = max_time; + g_TimeLeft = max_time; + + int clientCount = 0; + + for (int i = 0; i < num_clients; ++i) + { + if (clients[i] < 1 || clients[i] > MaxClients) + { + continue; + } + + g_ClientVotes[clients[i]] = VOTE_PENDING; + clientCount++; + } + + g_Clients = clientCount; + + Game_UpdateVoteCounts(g_hVotes, clientCount); + + DoClientVote(vote, clients, num_clients); + + StartVoting(); + + DrawHintProgress(); + + return true; +} + +bool DoClientVote(NativeVote vote, int[] clients, int num_clients) +{ + int totalPlayers = 0; + int[] realClients = new int[MaxClients+1]; + + for (int i = 0; i < num_clients; ++i) + { + if (clients[i] < 1 || clients[i] > MaxClients || !IsClientInGame(clients[i]) || IsFakeClient(clients[i])) + { + continue; + } + + realClients[totalPlayers++] = clients[i]; + } + + if (totalPlayers > 0) + { + Game_DisplayVote(vote, realClients, totalPlayers, (g_fStartTime + g_VoteTime) - GetGameTime()); + return true; + } + else + { + return false; + } +} + +bool InitializeVoting(NativeVote vote, int time, int flags) +{ + if (Internal_IsVoteInProgress()) + { + return false; + } + + if (g_hVoteTimer != null) + { + delete g_hVoteTimer; + } + if (g_hDisplayTimer != null) + { + delete g_hDisplayTimer; + } + + Internal_Reset(); + + /* Mark all clients as not voting */ + for (int i = 1; i <= MaxClients; ++i) + { + g_ClientVotes[i] = VOTE_NOT_VOTING; + g_bRevoting[i] = false; + } + + g_Items = Data_GetItemCount(vote); + + // Clear all items + for (int i = 0; i < g_hVotes.Length; ++i) + { + g_hVotes.Set(i, 0); + } + + g_hCurVote = vote; + g_VoteTime = time; + g_VoteFlags = flags; + + return true; +} + +void StartVoting() +{ + if (g_hCurVote == null) + { + return; + } + + g_bStarted = true; + + OnVoteStart(g_hCurVote); + + g_hDisplayTimer = CreateTimer(1.0, DisplayTimer, _, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + + g_TotalClients = g_Clients; + + /* + * By now we know how many clients were set. + * If there are none, we should end IMMEDIATELY. + */ + if (g_Clients == 0) + { + EndVoting(); + return; + } + + // Kick targets automatically vote no if they're in the pool + NativeVotesType voteType = Data_GetType(g_hCurVote); + + switch (voteType) + { + case NativeVotesType_Kick, NativeVotesType_KickCheating, NativeVotesType_KickIdle, NativeVotesType_KickScamming: + { + int target = Data_GetTarget(g_hCurVote); + + if (target > 0 && target <= MaxClients && IsClientConnected(target) && Internal_IsClientInVotePool(target)) + { + Game_VoteNo(target); + } + } + } + + // Initiators always vote yes when they're in the pool. + if (voteType != NativeVotesType_Custom_Mult && voteType != NativeVotesType_NextLevelMult) + { + int initiator = Data_GetInitiator(g_hCurVote); + + if (initiator > 0 && initiator <= MaxClients && IsClientConnected(initiator) && Internal_IsClientInVotePool(initiator)) + { + Game_VoteYes(initiator); + } + } +} + +public Action DisplayTimer(Handle timer) +{ + if (!Internal_IsVoteInProgress() || --g_TimeLeft <= 0) + { + if (g_hDisplayTimer != null) + { + g_hDisplayTimer = null; + EndVoting(); + } + return Plugin_Stop; + } + DrawHintProgress(); + return Plugin_Continue; +} + +int Internal_GetResults(int[][] votes, int &num_votes=0) +{ + if (!Internal_IsVoteInProgress()) + { + return 0; + } + + // Since we can't have structs, we get "struct" with this instead + int num_items; + + num_votes = 0; + + for (int i = 0; i < g_Items; i++) + { + int voteCount = g_hVotes.Get(i); + if (voteCount > 0) + { + votes[num_items][VOTEINFO_ITEM_INDEX] = i; + votes[num_items][VOTEINFO_ITEM_VOTES] = voteCount; + num_votes += voteCount; + num_items++; + } + } + + /* Sort the item list descending like we promised */ + SortCustom2D(votes, num_items, SortVoteItems); + + return num_items; +} + +int Internal_GetClients(int[][] client_vote) +{ + if (!Internal_IsVoteInProgress()) + { + return 0; + } + + /* Build the client list */ + int num_clients; + + for (int i = 1; i <= MaxClients; i++) + { + if (g_ClientVotes[i] >= VOTE_PENDING) + { + client_vote[num_clients][VOTEINFO_CLIENT_INDEX] = i; + client_vote[num_clients][VOTEINFO_CLIENT_ITEM] = g_ClientVotes[i]; + num_clients++; + } + } + + return num_clients; +} + +bool Internal_IsCancelling() +{ + return g_bCancelled; +} + +stock NativeVote Internal_GetCurrentVote() +{ + return g_hCurVote; +} + +void Internal_Reset(bool cancel=false) +{ + g_Clients = 0; + g_Items = 0; + g_bStarted = false; + g_hCurVote = null; + g_NumVotes = 0; + g_bCancelled = false; + g_TotalClients = 0; + + if (g_hDisplayTimer != null) + { + delete g_hDisplayTimer; + } + + Game_ResetVote(null); + + if (!cancel) + { + if (g_hVoteTimer != null) + { + delete g_hVoteTimer; + } + g_hVoteTimer = CreateTimer(5.0, Game_ResetVote, TIMER_FLAG_NO_MAPCHANGE); + } +} + +bool Internal_IsVoteInProgress() +{ + return (g_hCurVote != INVALID_HANDLE); +} + +bool Internal_IsClientInVotePool(int client) +{ + if (client < 1 || client > MaxClients || g_hCurVote == null) + { + return false; + } + + return (g_ClientVotes[client] > VOTE_NOT_VOTING); +} + +bool Internal_RedrawToClient(int client, bool revotes) +{ + if (!Internal_IsVoteInProgress() || !Internal_IsClientInVotePool(client)) + { + return false; + } + + if (g_ClientVotes[client] >= 0) + { + if ((g_VoteFlags & VOTEFLAG_NO_REVOTES) || !revotes || g_VoteTime <= VOTE_DELAY_TIME) + { + return false; + } + + g_Clients++; + SetArrayCell(g_hVotes, g_ClientVotes[client], GetArrayCell(g_hVotes, g_ClientVotes[client]) - 1); + g_ClientVotes[client] = VOTE_PENDING; + g_bRevoting[client] = true; + g_NumVotes--; + Game_UpdateVoteCounts(g_hVotes, g_TotalClients); + } + + // Display the vote fail screen for a few seconds + //Game_DisplayVoteFail(g_hCurVote, NativeVotesFail_Generic, client); + + // No, display a vote pass screen because that's nicer and we can customize it. + // @note This isn't inside the earlier if because some players have had issues where the display doesn't always appear the first time. + char revotePhrase[128]; + Format(revotePhrase, sizeof(revotePhrase), "%T", "NativeVotes Revote", client); + Game_DisplayVotePassCustom(g_hCurVote, revotePhrase, client); + + DataPack data; + + CreateDataTimer(VOTE_DELAY_TIME, RedrawTimer, data, TIMER_FLAG_NO_MAPCHANGE); + data.WriteCell(GetClientUserId(client)); + data.WriteCell(view_as(g_hCurVote)); + + return true; +} + +public Action RedrawTimer(Handle timer, DataPack data) +{ + if (g_hCurVote == null) + { + return Plugin_Stop; + } + + ResetPack(data); + int client = GetClientOfUserId(data.ReadCell()); + if (client == 0) + { + return Plugin_Stop; + } + + NativeVote vote = view_as(data.ReadCell()); + + if (Internal_IsVoteInProgress() && !Internal_IsCancelling() && vote == g_hCurVote) + { + Game_DisplayVoteToOne(vote, client, (g_fStartTime + g_VoteTime) - GetGameTime()); + } + + return Plugin_Stop; +} + +void CancelVoting() +{ + if (g_bCancelled || g_hCurVote == INVALID_HANDLE) + { + return; + } + + g_bCancelled = true; + + EndVoting(); +} + +void PerformVisChecks(int client, ArrayList hVoteTypes) +{ + // Iterate backwards so we can safely remove items + for (int i = hVoteTypes.Length - 1; i >= 0; i--) + { + CallVoteListData voteData; + hVoteTypes.GetArray(i, voteData, sizeof(CallVoteListData)); + + Action hide = Plugin_Continue; + +#if defined LOG + LogMessage("Checking visibility forward for %d: %d", voteData.CallVoteList_VoteType, g_CallVotes[voteData.CallVoteList_VoteType].CallVote_Vis); +#endif + Call_StartForward(g_CallVotes[voteData.CallVoteList_VoteType].CallVote_Vis); + Call_PushCell(client); + Call_PushCell(voteData.CallVoteList_VoteType); + Call_Finish(hide); + if (hide >= Plugin_Handled) + { +#if defined LOG + LogMessage("Hiding vote type %d", voteData.CallVoteList_VoteType); +#endif + if (Game_AreDisabledIssuesHidden()) + { + // Since we hide disabled issues, remove it from the arraylist + RemoveFromArray(hVoteTypes, i); + } + else + { + // Arrays are pass by ref, so this should update the one inside the ArrayList + voteData.CallVoteList_VoteEnabled = false; + } + } + } +} + +//---------------------------------------------------------------------------- +// Natives + +// native bool NativeVotes_IsVoteTypeSupported(NativeVotesType voteType); +public int Native_IsVoteTypeSupported(Handle plugin, int numParams) +{ + NativeVotesType type = GetNativeCell(1); + + return Game_CheckVoteType(type); +} + +// native Handle NativeVotes_Create(MenuHandler handler, NativeVotesType voteType, MenuAction actions = NATIVEVOTES_ACTIONS_DEFAULT); +public int Native_Create(Handle plugin, int numParams) +{ + Function handler = GetNativeFunction(1); + NativeVotesType voteType = GetNativeCell(2); + MenuAction actions = GetNativeCell(3); + + if (handler == INVALID_FUNCTION) + { + ThrowNativeError(SP_ERROR_NATIVE, "Menuhandler is invalid"); + } + + NativeVote vote; + if (Game_CheckVoteType(voteType)) + { + vote = Data_CreateVote(voteType, actions); + } + else + { + return view_as(INVALID_HANDLE); + } + + if (voteType != NativeVotesType_NextLevelMult && voteType != NativeVotesType_Custom_Mult) + { + SetGlobalTransTarget(LANG_SERVER); + Data_AddItem(vote, "yes", "Yes"); + Data_AddItem(vote, "no", "No"); + } + + Handle menuForward = Data_GetHandler(vote); + + AddToForward(menuForward, plugin, handler); + + return view_as(vote); +} + +// native Handle NativeVotes_Close(Handle vote); +public int Native_Close(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + + if (vote == null) + { + return 0; + } + + if (g_hCurVote == vote) + { + CancelVoting(); + g_hCurVote = null; + + /* + if (g_hVoteTimer != INVALID_HANDLE) + { + KillTimer(g_hVoteTimer); + g_hVoteTimer = INVALID_HANDLE; + } + */ + } + + // This bit is necessary because the Forward system appears to not remove these when the forward Handle is closed + // This was necessary in SM 1.5.x, but has a REALLY high probability of crashing in SM 1.6, plus is no longer needed + //Handle menuForward = Data_GetHandler(vote); + //RemoveAllFromForward(menuForward, plugin); + + //Handle voteResults = Data_GetResultCallback(vote); + //RemoveAllFromForward(voteResults, plugin); + + // Do the datatype-specific close operations + Data_CloseVote(vote); +} + +// native bool NativeVotes_Display(Handle vote, int clients[], int numClients, int time, int flags=0); +public int Native_Display(Handle plugin, int numParams) +{ + if (Internal_IsVoteInProgress()) + { + ThrowNativeError(SP_ERROR_NATIVE, "A vote is already in progress"); + } + + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + int count = GetNativeCell(3); + if (count <= 0) + { + return 0; + } + + int[] clients = new int[count]; + GetNativeArray(2, clients, count); + + // Note: Only one flag exists: VOTEFLAG_NO_REVOTES + int flags = 0; + + if (numParams >= 5) + { + flags = GetNativeCell(5); + } + + if (!StartVote(vote, count, clients, GetNativeCell(4), flags)) + { + return 0; + } + + return 1; + +} + +// native bool NativeVotes_AddItem(Handle vote, const char[] info, const char[] display); +public int Native_AddItem(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + NativeVotesType voteType = Data_GetType(vote); + + if (voteType != NativeVotesType_NextLevelMult && voteType != NativeVotesType_Custom_Mult) + { + return 0; + } + + char info[256]; + char display[256]; + GetNativeString(2, info, sizeof(info)); + GetNativeString(3, display, sizeof(display)); + + return Data_AddItem(vote, info, display); +} + +// native bool NativeVotes_InsertItem(Handle vote, int position, const char[] info, const char[] display); +public int Native_InsertItem(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + NativeVotesType voteType = Data_GetType(vote); + + if (voteType != NativeVotesType_NextLevelMult && voteType != NativeVotesType_Custom_Mult) + { + return 0; + } + + int position = GetNativeCell(2); + + if (position < 0) + { + ThrowNativeError(SP_ERROR_NATIVE, "Vote index can't be negative: %d", position); + return 0; + } + + char info[256]; + char display[256]; + GetNativeString(3, info, sizeof(info)); + GetNativeString(4, display, sizeof(display)); + + return Data_InsertItem(vote, position, info, display); + +} + +// native bool NativeVotes_RemoveItem(Handle vote, int position); +public int Native_RemoveItem(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + NativeVotesType voteType = Data_GetType(vote); + + if (voteType != NativeVotesType_NextLevelMult && voteType != NativeVotesType_Custom_Mult) + { + return 0; + } + + int position = GetNativeCell(2); + + return Data_RemoveItem(vote, position); +} + +// native NativeVotes_RemoveAllItems(Handle vote); +public int Native_RemoveAllItems(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + Data_RemoveAllItems(vote); +} + +// native bool NativeVotes_GetItem(Handle vote, int position, const char[] infoBuf, int infoBufLen, const char[] dispBuf = "", int dispBufLen = 0); +public int Native_GetItem(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + int position = GetNativeCell(2); + + int infoLength = GetNativeCell(4); + char[] info = new char[infoLength]; + Data_GetItemInfo(vote, position, info, infoLength); + SetNativeString(3, info, infoLength); + + if (numParams >= 6) + { + int displayLength = GetNativeCell(6); + if (displayLength > 0) + { + char[] display = new char[displayLength]; + Data_GetItemDisplay(vote, position, display, displayLength); + SetNativeString(5, display, displayLength); + } + } +} + +// native NativeVotes_GetItemCount(Handle vote); +public int Native_GetItemCount(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + return Data_GetItemCount(vote); +} + +// native NativeVotes_GetDetails(Handle vote, const char[] buffer, int maxlength); +public int Native_GetDetails(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + int len = GetNativeCell(3); + + char[] details = new char[len]; + + Data_GetDetails(vote, details, len); + + SetNativeString(2, details, len); +} + +// native NativeVotes_SetDetails(Handle vote, const char[] fmt, any:...); +public int Native_SetDetails(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + char details[MAX_VOTE_DETAILS_LENGTH]; + + FormatNativeString(0, 2, 3, sizeof(details), _, details); + + Data_SetDetails(vote, details); +} + +// native NativeVotes_GetDetails(Handle vote, const char[] buffer, int maxlength); +public int Native_GetTitle(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + int len = GetNativeCell(3); + + char[] title = new char[len]; + + Data_GetTitle(vote, title, len); + + SetNativeString(2, title, len); +} + +// native NativeVotes_SetTitle(Handle vote, const char[] fmt, any:...); +public int Native_SetTitle(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + char details[MAX_VOTE_DETAILS_LENGTH]; + + FormatNativeString(0, 2, 3, sizeof(details), _, details); + + Data_SetTitle(vote, details); +} + +// native bool NativeVotes_IsVoteInProgress(); +public int Native_IsVoteInProgress(Handle plugin, int numParams) +{ + return Internal_IsVoteInProgress() || Game_IsVoteInProgress(); +} + +// native NativeVotes_GetMaxItems(); +public int Native_GetMaxItems(Handle plugin, int numParams) +{ + return Game_GetMaxItems(); +} + +// native NativeVotes_GetOptionFlags(Handle vote); +public int Native_GetOptionFlags(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + return Data_GetFlags(vote); +} + +// native NativeVotes_SetOptionFlags(Handle vote, flags); +public int Native_SetOptionFlags(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + int flags = GetNativeCell(2); + + // This is an ORed group of flags to strip the ones we don't support + flags &= (MENUFLAG_BUTTON_NOVOTE); + + Data_SetFlags(vote, flags); +} + +// native bool NativeVotes_SetNoVoteButton(Handle vote, bool button); +public int Native_SetNoVoteButton(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + return ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + } + + int flags = Data_GetFlags(vote); + + if (GetNativeCell(2)) + { + flags |= MENUFLAG_BUTTON_NOVOTE; + } + else + { + flags &= ~MENUFLAG_BUTTON_NOVOTE; + } + + Data_SetFlags(vote, flags); + + int newflags = Data_GetFlags(vote); + + return (flags == newflags); +} + +// native NativeVotes_Cancel(); +public int Native_Cancel(Handle plugin, int numParams) +{ + if (!Internal_IsVoteInProgress()) + { + ThrowNativeError(SP_ERROR_NATIVE, "No vote is in progress"); + return 0; + } + + CancelVoting(); +} + +// native NativeVotes_SetResultCallback(Handle vote, NativeVotes_VoteHandler callback); +public int Native_SetResultCallback(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + Function handler = GetNativeFunction(2); + + if (handler == INVALID_FUNCTION) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes_VoteHandler is invalid"); + return; + } + + Handle voteResults = Data_GetResultCallback(vote); + + RemoveAllFromForward(voteResults, plugin); + if (!AddToForward(voteResults, plugin, handler)) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes_VoteHandler cannot be added to forward"); + } +} + +// native NativeVotes_CheckVoteDelay(); +public int Native_CheckVoteDelay(Handle plugin, int numParams) +{ + int curTime = GetTime(); + if (g_NextVote <= curTime) + { + return 0; + } + + return (g_NextVote - curTime); +} + +// native bool NativeVotes_IsClientInVotePool(int client); +public int Native_IsClientInVotePool(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + + if (!client || client > MaxClients) + { + ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + return false; + } + + if (!Internal_IsVoteInProgress()) + { + ThrowNativeError(SP_ERROR_NATIVE, "No vote is in progress"); + return false; + } + + return Internal_IsClientInVotePool(client); +} + +// native bool NativeVotes_RedrawClientVote(int client, bool revotes = true); +public int Native_RedrawClientVote(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + + if (client < 1 || client > MaxClients || !IsClientConnected(client)) + { + ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + return false; + } + + if (!Internal_IsVoteInProgress()) + { + // When revoting in TF2, NativeVotes_IsVoteInProgress always gets skipped because of Game_IsVoteInProgress() + // TF2's vote controller will stay alive a few seconds after the vote is complete + // If one tries to revote right as a vote completes, it will throw an error + LogError("No vote is in progress"); + return false; + } + + if (!Internal_IsClientInVotePool(client)) + { + ThrowNativeError(SP_ERROR_NATIVE, "Client is not in the voting pool"); + return false; + } + + bool revote = true; + if (numParams >= 2 && !GetNativeCell(2)) + { + revote = false; + } + + return Internal_RedrawToClient(client, revote); +} + +// native NativeVotesType NativeVotes_GetType(Handle vote); +public int Native_GetType(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + return view_as(Data_GetType(vote)); +} + +// native NativeVotes_GetTeam(Handle vote); +public int Native_GetTeam(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return NATIVEVOTES_ALL_TEAMS; + } + + return Data_GetTeam(vote); + +} + +// native NativeVotes_SetTeam(Handle vote, int team); +public int Native_SetTeam(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + int team = GetNativeCell(2); + + // Teams are numbered starting with 0 + // Currently 4 is the maximum (Unassigned, Spectator, Team 1, Team 2) + if (team >= GetTeamCount()) + { + ThrowNativeError(SP_ERROR_NATIVE, "Team %d is invalid", team); + return; + } + + if (g_EngineVersion == Engine_TF2 && team == NATIVEVOTES_ALL_TEAMS) + { + team = NATIVEVOTES_TF2_ALL_TEAMS; + } + + Data_SetTeam(vote, team); +} + +// native NativeVotes_GetInitiator(Handle vote); +public int Native_GetInitiator(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return NATIVEVOTES_SERVER_INDEX; + } + + return Data_GetInitiator(vote); +} + +// native int NativeVotes_SetInitiator(Handle vote, int client); +public int Native_SetInitiator(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + int initiator = GetNativeCell(2); + Data_SetInitiator(vote, initiator); +} + +// native NativeVotes_DisplayPass(Handle vote, const char[] details = ""); +public int Native_DisplayPass(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + if (numParams >= 2) + { + char winner[TRANSLATION_LENGTH]; + FormatNativeString(0, 2, 3, sizeof(winner), _, winner); + + Game_DisplayVotePass(vote, winner); + } + else + { + Game_DisplayVotePass(vote); + } +} + +// native NativeVotes_DisplayPassCustomToOne(Handle vote, client, const char[] format, any:...); +public int Native_DisplayPassCustomToOne(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + int client = GetNativeCell(2); + + char translation[TRANSLATION_LENGTH]; + + SetGlobalTransTarget(client); + FormatNativeString(0, 3, 4, TRANSLATION_LENGTH, _, translation); + + Game_DisplayVotePassCustom(vote, translation, client); +} + +// native NativeVotes_DisplayPassEx(Handle vote, NativeVotesPassType passType, const char[] details = ""); +public int Native_DisplayPassEx(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + NativeVotesPassType passType = view_as(GetNativeCell(2)); + + if (!Game_CheckVotePassType(passType)) + { + ThrowNativeError(SP_ERROR_NATIVE, "Invalid vote pass type: %d", passType); + } + + if (numParams >= 3) + { + char winner[TRANSLATION_LENGTH]; + FormatNativeString(0, 3, 4, sizeof(winner), _, winner); + + Game_DisplayVotePassEx(vote, passType, winner); + } + else + { + Game_DisplayVotePassEx(vote, passType); + } +} + +// native NativeVotes_DisplayRawPass(NativeVotesPassType:passType, const String:details[]="", team=NATIVEVOTES_ALL_TEAMS); +/* +public Native_DisplayRawPass(Handle:plugin, numParams) +{ + new NativeVotesPassType:passType = NativeVotesPassType:GetNativeCell(1); + + if (!Game_CheckVotePassType(passType)) + { + ThrowNativeError(SP_ERROR_NATIVE, "Invalid vote pass type: %d", passType); + } + + new len; + GetNativeStringLength(2, len); + new String:winner[len+1]; + GetNativeString(2, winner, len+1); + new team = GetNativeCell(3); + + if (g_EngineVersion == Engine_TF2 && team == NATIVEVOTES_ALL_TEAMS) + { + team = NATIVEVOTES_TF2_ALL_TEAMS; + } + + Game_DisplayRawVotePass(passType, winner, team); +} +*/ + +// native NativeVotes_DisplayRawPassToOne(int client, NativeVotesPassType passType, const char[] details = "", int team = NATIVEVOTES_ALL_TEAMS); +public int Native_DisplayRawPassToOne(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + NativeVotesPassType passType = view_as(GetNativeCell(2)); + + if (!Game_CheckVotePassType(passType)) + { + ThrowNativeError(SP_ERROR_NATIVE, "Invalid vote pass type: %d", passType); + } + + int team = GetNativeCell(3); + + if (g_EngineVersion == Engine_TF2 && team == NATIVEVOTES_ALL_TEAMS) + { + team = NATIVEVOTES_TF2_ALL_TEAMS; + } + + if (numParams >= 4) + { + char winner[TRANSLATION_LENGTH]; + SetGlobalTransTarget(client); + FormatNativeString(0, 4, 5, sizeof(winner), _, winner); + + Game_DisplayRawVotePass(passType, team, client, winner); + } + else + { + Game_DisplayRawVotePass(passType, team, client); + } +} +// native NativeVotes_DisplayRawPassCustomToOne(int client, int team, const char[] format, any:...); +public int Native_DisplayRawPassCustomToOne(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + int team = GetNativeCell(2); + + char translation[TRANSLATION_LENGTH]; + + SetGlobalTransTarget(client); + FormatNativeString(0, 3, 4, TRANSLATION_LENGTH, _, translation); + + Game_DisplayRawVotePassCustom(translation, team, client); +} + +// native NativeVotes_DisplayFail(Handle vote, NativeVotesFailType reason = NativeVotesFail_Generic); +public int Native_DisplayFail(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + NativeVotesFailType reason = view_as(GetNativeCell(2)); + + Game_DisplayVoteFail(vote, reason); +} + +// native NativeVotes_DisplayRawFail(NativeVotesFailType reason = NativeVotesFail_Generic, int team = NATIVEVOTES_ALL_TEAMS); +public int Native_DisplayRawFail(Handle plugin, int numParams) +{ + int size = GetNativeCell(2); + int[] clients = new int[size]; + GetNativeArray(1, clients, size); + + NativeVotesFailType reason = view_as(GetNativeCell(3)); + + int team = GetNativeCell(4); + + if (g_EngineVersion == Engine_TF2 && team == NATIVEVOTES_ALL_TEAMS) + { + team = NATIVEVOTES_TF2_ALL_TEAMS; + } + + Game_DisplayRawVoteFail(clients, size, reason, team); +} + +// native NativeVotes_DisplayRawFailToOne(int client, NativeVotesFailType reason = NativeVotesFail_Generic, int team = NATIVEVOTES_ALL_TEAMS); +/* +public Native_DisplayRawFailToOne(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + + NativeVotesFailType reason = view_as(GetNativeCell(2)); + + int team = GetNativeCell(3); + + if (g_EngineVersion == Engine_TF2 && team == NATIVEVOTES_ALL_TEAMS) + { + team = NATIVEVOTES_TF2_ALL_TEAMS; + } + + Game_DisplayRawVoteFail(reason, team, client); +} +*/ +// native NativeVotes_GetTarget(Handle vote); +public int Native_GetTarget(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + int target = Data_GetTarget(vote); + + if (target == 0) + { + // No target was set, return -1 + return -1; + } + + return GetClientOfUserId(target); +} + +// native NativeVotes_GetTargetSteam(Handle vote, const char[] buffer, maxlength); +public int Native_GetTargetSteam(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + int size = GetNativeCell(3); + char[] steamId = new char[size]; + GetNativeString(2, steamId, size); + + Data_GetTargetSteam(vote, steamId, size); +} + +// native NativeVotes_SetTarget(Handle vote, int client, bool setDetails = true); +public int Native_SetTarget(Handle plugin, int numParams) +{ + NativeVote vote = GetNativeCell(1); + if (vote == null) + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes handle %x is invalid", vote); + return 0; + } + + int client = GetNativeCell(2); + + if (client < 1 || client > MaxClients || !IsClientConnected(client)) + { + ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + return; + } + + int userid; + char steamId[MAX_AUTHID_LENGTH]; + + if (!client) + { + userid = 0; + steamId = ""; + } + else + { + userid = GetClientUserId(client); + if (!GetClientAuthId(client, AuthId_Steam2, steamId, sizeof(steamId))) + { + steamId = ""; + } + } + + Data_SetTarget(vote, userid); + Data_SetTargetSteam(vote, steamId); + + bool changeDetails = GetNativeCell(3); + if (changeDetails) + { + char name[MAX_NAME_LENGTH+1]; + if (client > 0) + { + GetClientName(client, name, MAX_NAME_LENGTH); + Data_SetDetails(vote, name); + } + else + { + Data_SetDetails(vote, ""); + } + } +} + +// native bool NativeVotes_AreVoteCommandsSupported(); +public int Native_AreVoteCommandsSupported(Handle plugin, int numParams) +{ + return Game_AreVoteCommandsSupported(); +} + +// native NativeVotes_RegisterVoteCommand(NativeVotesOverride overrideType, NativeVotes_CallVoteHandler callHandler, NativeVotes_CallVoteVisCheck visHandler = INVALID_FUNCTION); +public int Native_RegisterVoteCommand(Handle plugin, int numParams) +{ + NativeVotesOverride overrideType = GetNativeCell(1); + Function callVoteHandler = GetNativeFunction(2); + Function visHandler = GetNativeFunction(3); + + // This tosses an error so use the simplified version + // if (view_as(overrideType) > sizeof(g_CallVotes)) + if (overrideType > NativeVotesOverride_Count) + { + ThrowNativeError(SP_ERROR_NATIVE, "Override Type %d is not supported by this version of NativeVotes", overrideType); + return 0; + } + + if (callVoteHandler == INVALID_FUNCTION) + { + ThrowNativeError(SP_ERROR_NATIVE, "CallVoteHandler function was invalid"); + return 0; + } + + AddToForward(g_CallVotes[overrideType].CallVote_Forward, plugin, callVoteHandler); + + if (visHandler != INVALID_FUNCTION) + { + AddToForward(g_CallVotes[overrideType].CallVote_Vis, plugin, visHandler); + } +} + +// native NativeVotes_UnregisterVoteCommand(NativeVotesOverride overrideType, NativeVotes_CallVoteHandler callHandler, NativeVotes_CallVoteVisCheck visHandler=INVALID_FUNCTION); +public int Native_UnregisterVoteCommand(Handle plugin, int numParams) +{ + NativeVotesOverride overrideType = GetNativeCell(1); + Function callVoteHandler = GetNativeFunction(2); + Function visHandler = GetNativeFunction(3); + + if (overrideType > NativeVotesOverride_Count) + { + ThrowNativeError(SP_ERROR_NATIVE, "Override Type %d is not supported by this version of NativeVotes", overrideType); + return 0; + } + + if (callVoteHandler == INVALID_FUNCTION) + { + ThrowNativeError(SP_ERROR_NATIVE, "CallVoteHandler function was invalid"); + return 0; + } + + RemoveFromForward(g_CallVotes[overrideType].CallVote_Forward, plugin, callVoteHandler); + + if (visHandler != INVALID_FUNCTION) + { + RemoveFromForward(g_CallVotes[overrideType].CallVote_Vis, plugin, visHandler); + } +} + +// native NativeVotes_DisplayCallVoteFail(int client, NativeVotesCallFailType reason, int time=0); +public int Native_DisplayCallVoteFail(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + + if (client < 1 || client > MaxClients || !IsClientConnected(client)) + { + ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index %d", client); + return 0; + } + + NativeVotesCallFailType reason = GetNativeCell(2); + + int time = GetNativeCell(3); + + Game_DisplayCallVoteFail(client, reason, time); +} + +// native Action NativeVotes_RedrawVoteTitle(const char[] text); +public int Native_RedrawVoteTitle(Handle plugin, int numParams) +{ + if (!g_curDisplayClient) + { + ThrowNativeError(SP_ERROR_NATIVE, "You can only call this once from a MenuAction_Display callback"); + } + + NativeVotesType voteType = Data_GetType(g_hCurVote); + + if (voteType != NativeVotesType_Custom_Mult && voteType != NativeVotesType_Custom_YesNo) + { + return view_as(Plugin_Continue); + } + + GetNativeString(1, g_newMenuTitle, TRANSLATION_LENGTH); + return view_as(Plugin_Changed); +} + +// native Action NativeVotes_RedrawVoteItem(const char[] text); +public int Native_RedrawVoteItem(Handle plugin, int numParams) +{ + if (!g_curItemClient) + { + ThrowNativeError(SP_ERROR_NATIVE, "You can only call this once from a MenuAction_DisplayItem callback"); + } + + if (Game_GetMaxItems() == L4DL4D2_COUNT) + { + return view_as(Plugin_Continue); + } + + GetNativeString(1, g_newMenuItem, TRANSLATION_LENGTH); + return view_as(Plugin_Changed); +} + +// Sends a vote progress hint caption to a specific client using game events +void SendInstructorHint(int client, const char[] caption) +{ + if (g_ConVars[progress_hintcaption] == null || !g_ConVars[progress_hintcaption].BoolValue) + { + return; + } + + Event event = CreateEvent("instructor_server_hint_create", true); + if (event == null) + { + return; + } + + event.SetString("hint_name", "nativevotes_progress"); + event.SetString("hint_replace_key", "nativevotes_progress"); + //event.SetInt("hint_target", client); + event.SetInt("hint_activator_userid", GetClientUserId(client)); + event.SetInt("hint_timeout", 0); + char icon[64]; + g_ConVars[progress_hintcaption_icon].GetString(icon, sizeof(icon)); + if (icon[0] != '\0') + { + event.SetString("hint_icon_onscreen", icon); + event.SetString("hint_icon_offscreen", icon); + } + event.SetString("hint_activator_caption", caption); + event.SetInt("hint_allow_nodraw_target", 1); + event.SetInt("hint_nooffscreen", 1); + event.SetInt("hint_forcecaption", 1); + event.SetBool("hint_local_player_only", true); + + event.FireToClient(client); +} + +// Ends the vote progress hint for a specific client +void EndInstructorHint(int client) +{ + if (g_ConVars[progress_hintcaption] == null || !g_ConVars[progress_hintcaption].BoolValue) + { + return; + } + + Event event = CreateEvent("instructor_server_hint_stop", true); + if (event == null) + { + return; + } + event.SetString("hint_name", "nativevotes_progress"); + event.FireToClient(client); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/nativevotes/data-keyvalues.sp b/addons/sourcemod/scripting/nativevotes/data-keyvalues.sp index 6b66a51..4dc92a0 100644 --- a/addons/sourcemod/scripting/nativevotes/data-keyvalues.sp +++ b/addons/sourcemod/scripting/nativevotes/data-keyvalues.sp @@ -216,14 +216,7 @@ NativeVote Data_CreateVote(NativeVotesType voteType, MenuAction actions) vote.SetNum("actions", view_as(actions)); vote.SetNum("result_callback", view_as(voteResults)); vote.SetNum("initiator", NATIVEVOTES_SERVER_INDEX); - if (g_EngineVersion == Engine_TF2) - { - vote.SetNum("team", NATIVEVOTES_TF2_ALL_TEAMS); - } - else - { - vote.SetNum("team", NATIVEVOTES_ALL_TEAMS); - } + vote.SetNum("team", g_EngineVersion == Engine_TF2 ? NATIVEVOTES_TF2_ALL_TEAMS : NATIVEVOTES_ALL_TEAMS); vote.SetNum("flags", 0); vote.SetString("custom_title", ""); @@ -310,30 +303,30 @@ void Data_CloseVote(KeyValues vote) { return; } - + Handle handler = Data_GetHandler(vote); if (handler != null) { delete handler; } - + Handle voteResults = Data_GetResultCallback(vote); if (voteResults != null) { delete voteResults; } - + ArrayList infoArray = view_as(vote.GetNum(INFO, view_as(INVALID_HANDLE))); if (infoArray != null) { delete infoArray; } - + ArrayList displayArray = view_as(vote.GetNum(DISPLAY, view_as(INVALID_HANDLE))); if (displayArray != null) { delete displayArray; } - + delete vote; } diff --git a/addons/sourcemod/scripting/nativevotes/game.sp b/addons/sourcemod/scripting/nativevotes/game.sp index 46f8286..b8265b8 100644 --- a/addons/sourcemod/scripting/nativevotes/game.sp +++ b/addons/sourcemod/scripting/nativevotes/game.sp @@ -1,3723 +1,3669 @@ -/** - * vim: set ts=4 : - * ============================================================================= - * NativeVotes - * NativeVotes is a voting API plugin for L4D, L4D2, TF2, and CS:GO. - * Based on the SourceMod voting API - * - * NativeVotes (C) 2011-2014 Ross Bemrose (Powerlord). All rights reserved. - * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. - * ============================================================================= - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License, version 3.0, as published by the - * Free Software Foundation. - * - * This program 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 the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - * - * As a special exception, AlliedModders LLC gives you permission to link the - * code of this program (as well as its derivative works) to "Half-Life 2," the - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software - * by the Valve Corporation. You must obey the GNU General Public License in - * all respects for all other code used. Additionally, AlliedModders LLC grants - * this exception to all derivative works. AlliedModders LLC defines further - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), - * or . - * - * Version: $Id$ - */ -#if defined _nativevotes_game_included - #endinput -#endif - -#define _nativevotes_game_included - -#include - -#define L4DL4D2_COUNT 2 -#define TF2CSGO_COUNT 5 - -#define INVALID_ISSUE -1 - -//---------------------------------------------------------------------------- -// Translation strings - -//---------------------------------------------------------------------------- -// L4D/L4D2 - -#define L4DL4D2_VOTE_YES_STRING "Yes" -#define L4DL4D2_VOTE_NO_STRING "No" - -#define L4D_VOTE_KICK_START "#L4D_vote_kick_player" -#define L4D_VOTE_KICK_PASSED "#L4D_vote_passed_kick_player" - -// User vote to restart map. -#define L4D_VOTE_RESTART_START "#L4D_vote_restart_game" -#define L4D_VOTE_RESTART_PASSED "#L4D_vote_passed_restart_game" - -// User vote to change maps. -#define L4D_VOTE_CHANGECAMPAIGN_START "#L4D_vote_mission_change" -#define L4D_VOTE_CHANGECAMPAIGN_PASSED "#L4D_vote_passed_mission_change" -#define L4D_VOTE_CHANGELEVEL_START "#L4D_vote_chapter_change" -#define L4D_VOTE_CHANGELEVEL_PASSED "#L4D_vote_passed_chapter_change" - -// User vote to return to lobby. -#define L4D_VOTE_RETURNTOLOBBY_START "#L4D_vote_return_to_lobby" -#define L4D_VOTE_RETURNTOLOBBY_PASSED "#L4D_vote_passed_return_to_lobby" - -// User vote to change difficulty. -#define L4D_VOTE_CHANGEDIFFICULTY_START "#L4D_vote_change_difficulty" -#define L4D_VOTE_CHANGEDIFFICULTY_PASSED "#L4D_vote_passed_change_difficulty" - -// While not a vote string, it works just as well. -#define L4D_VOTE_CUSTOM "#L4D_TargetID_Player" - -//---------------------------------------------------------------------------- -// L4D2 - -// User vote to change alltalk. -#define L4D2_VOTE_ALLTALK_START "#L4D_vote_alltalk_change" -#define L4D2_VOTE_ALLTALK_PASSED "#L4D_vote_passed_alltalk_change" -#define L4D2_VOTE_ALLTALK_ENABLE "#L4D_vote_alltalk_enable" -#define L4D2_VOTE_ALLTALK_DISABLE "#L4D_vote_alltalk_disable" - -//---------------------------------------------------------------------------- -// TF2/CSGO -#define TF2CSGO_VOTE_PREFIX "option" - -#define TF2CSGO_VOTE_STRING_KICK "Kick" -#define TF2CSGO_VOTE_STRING_RESTART "RestartGame" -#define TF2CSGO_VOTE_STRING_CHANGELEVEL "ChangeLevel" -#define TF2CSGO_VOTE_STRING_NEXTLEVEL "NextLevel" -#define TF2CSGO_VOTE_STRING_SCRAMBLE "ScrambleTeams" - -//---------------------------------------------------------------------------- -// TF2 - -#define TF2_VOTE_STRING_CHANGEMISSION "ChangeMission" - -// This one doesn't actually exist in the normal vote menu, but it has a translation string for it. -// Thus, this is the name we will use for it internally -#define TF2_VOTE_STRING_ETERNAWEEN "Eternaween" - -// These are toggles, but the on and off versions are identical -#define TF2_VOTE_STRING_AUTOBALANCE "TeamAutoBalance" -#define TF2_VOTE_STRING_CLASSLIMIT "ClassLimits" - -// New as of 2015-09-24 -#define TF2_VOTE_STRING_EXTEND "ExtendLevel" - -// Menu items for votes -#define TF2_VOTE_MENU_RESTART "#Vote_RestartGame" -#define TF2_VOTE_MENU_KICK "#Vote_Kick" -#define TF2_VOTE_MENU_CHANGELEVEL "#Vote_ChangeLevel" -#define TF2_VOTE_MENU_NEXTLEVEL "#Vote_NextLevel" -#define TF2_VOTE_MENU_SCRAMBLE "#Vote_ScrambleTeams" -#define TF2_VOTE_MENU_CHANGEMISSION "#Vote_ChangeMission" -#define TF2_VOTE_MENU_ETERNAWEEN "#Vote_Eternaween" -#define TF2_VOTE_MENU_AUTOBALANCE_ON "#Vote_TeamAutoBalance_Enable" -#define TF2_VOTE_MENU_AUTOBALANCE_OFF "#Vote_TeamAutoBalance_Disable" -#define TF2_VOTE_MENU_CLASSLIMIT_ON "#Vote_ClassLimit_Enable" -#define TF2_VOTE_MENU_CLASSLIMIT_OFF "#Vote_ClassLimit_Disable" -#define TF2_VOTE_MENU_EXTEND "#Vote_ExtendLevel" - -// User vote to kick user. -#define TF2_VOTE_KICK_IDLE_START "#TF_vote_kick_player_idle" -#define TF2_VOTE_KICK_SCAMMING_START "#TF_vote_kick_player_scamming" -#define TF2_VOTE_KICK_CHEATING_START "#TF_vote_kick_player_cheating" -#define TF2_VOTE_KICK_START "#TF_vote_kick_player_other" -#define TF2_VOTE_KICK_PASSED "#TF_vote_passed_kick_player" - -// User vote to restart map. -#define TF2_VOTE_RESTART_START "#TF_vote_restart_game" -#define TF2_VOTE_RESTART_PASSED "#TF_vote_passed_restart_game" - -// User vote to change maps. -#define TF2_VOTE_CHANGELEVEL_START "#TF_vote_changelevel" -#define TF2_VOTE_CHANGELEVEL_PASSED "#TF_vote_passed_changelevel" - -// User vote to change next level. -#define TF2_VOTE_NEXTLEVEL_SINGLE_START "#TF_vote_nextlevel" -#define TF2_VOTE_NEXTLEVEL_MULTIPLE_START "#TF_vote_nextlevel_choices" // Started by server -#define TF2_VOTE_NEXTLEVEL_EXTEND_PASSED "#TF_vote_passed_nextlevel_extend" // Also used for extend vote -#define TF2_VOTE_NEXTLEVEL_PASSED "#TF_vote_passed_nextlevel" - -// User vote to scramble teams. Can be immediate or end of round. -#define TF2_VOTE_SCRAMBLE_IMMEDIATE_START "#TF_vote_scramble_teams" -#define TF2_VOTE_SCRAMBLE_ROUNDEND_START "#TF_vote_should_scramble_round" -#define TF2_VOTE_SCRAMBLE_PASSED "#TF_vote_passed_scramble_teams" - -// User vote to change MvM mission -#define TF2_VOTE_CHANGEMISSION_START "#TF_vote_changechallenge" -#define TF2_VOTE_CHANGEMISSION_PASSED "#TF_vote_passed_changechallenge" - -// User vote for eternaween -#define TF2_VOTE_ETERNAWEEN_START "#TF_vote_eternaween" -#define TF2_VOTE_ETERNAWEEN_PASSED "#TF_vote_passed_eternaween" - -// User vote to start round -#define TF2_VOTE_ROUND_START "#TF_vote_td_start_round" -#define TF2_VOTE_ROUND_PASSED "#TF_vote_passed_td_start_round" - -// User vote to enable autobalance -#define TF2_VOTE_AUTOBALANCE_ENABLE_START "#TF_vote_autobalance_enable" -#define TF2_VOTE_AUTOBALANCE_ENABLE_PASSED "#TF_vote_passed_autobalance_enable" - -// User vote to disable autobalance -#define TF2_VOTE_AUTOBALANCE_DISABLE_START "#TF_vote_autobalance_disable" -#define TF2_VOTE_AUTOBALANCE_DISABLE_PASSED "#TF_vote_passed_autobalance_disable" - -// User vote to enable classlimits -#define TF2_VOTE_CLASSLIMITS_ENABLE_START "#TF_vote_classlimits_enable" -#define TF2_VOTE_CLASSLIMITS_ENABLE_PASSED "#TF_vote_passed_classlimits_enable" - -// User vote to disable classlimits -#define TF2_VOTE_CLASSLIMITS_DISABLE_START "#TF_vote_classlimits_disable" -#define TF2_VOTE_CLASSLIMITS_DISABLE_PASSED "#TF_vote_passed_classlimits_disable" - -// Use vote to extend map. -#define TF2_VOTE_EXTEND_START "#TF_vote_extendlevel" - -// While not a vote string, it works just as well. -#define TF2_VOTE_CUSTOM "#TF_playerid_noteam" - -// TF2 (and SDK2013?) VoteFail / CallVoteFail reasons -enum -{ - VOTE_FAILED_GENERIC, - VOTE_FAILED_TRANSITIONING_PLAYERS, - VOTE_FAILED_RATE_EXCEEDED, - VOTE_FAILED_YES_MUST_EXCEED_NO, - VOTE_FAILED_QUORUM_FAILURE, - VOTE_FAILED_ISSUE_DISABLED, - VOTE_FAILED_MAP_NOT_FOUND, - VOTE_FAILED_MAP_NAME_REQUIRED, - VOTE_FAILED_FAILED_RECENTLY, - VOTE_FAILED_TEAM_CANT_CALL, - VOTE_FAILED_WAITINGFORPLAYERS, - VOTE_FAILED_PLAYERNOTFOUND, - VOTE_FAILED_CANNOT_KICK_ADMIN, - VOTE_FAILED_SCRAMBLE_IN_PROGRESS, - VOTE_FAILED_SPECTATOR, - VOTE_FAILED_NEXTLEVEL_SET, - VOTE_FAILED_MAP_NOT_VALID, - VOTE_FAILED_CANNOT_KICK_FOR_TIME, - VOTE_FAILED_CANNOT_KICK_DURING_ROUND, - VOTE_FAILED_MODIFICATION_ALREADY_ACTIVE, -} - -//---------------------------------------------------------------------------- -// CSGO -// User vote to kick user. -#define CSGO_VOTE_KICK_IDLE_START "#SFUI_vote_kick_player_idle" -#define CSGO_VOTE_KICK_SCAMMING_START "#SFUI_vote_kick_player_scamming" -#define CSGO_VOTE_KICK_CHEATING_START "#SFUI_vote_kick_player_cheating" -#define CSGO_VOTE_KICK_START "#SFUI_vote_kick_player_other" -#define CSGO_VOTE_KICK_OTHERTEAM "#SFUI_otherteam_vote_kick_player" -#define CSGO_VOTE_KICK_PASSED "#SFUI_vote_passed_kick_player" - -// User vote to restart map. -#define CSGO_VOTE_RESTART_START "#SFUI_vote_restart_game" -#define CSGO_VOTE_RESTART_PASSED "#SFUI_vote_passed_restart_game" - -// User vote to change maps. -#define CSGO_VOTE_CHANGELEVEL_START "#SFUI_vote_changelevel" -#define CSGO_VOTE_CHANGELEVEL_PASSED "#SFUI_vote_passed_changelevel" - -// User vote to change next level. -#define CSGO_VOTE_NEXTLEVEL_SINGLE_START "#SFUI_vote_nextlevel" -#define CSGO_VOTE_NEXTLEVEL_MULTIPLE_START "#SFUI_vote_nextlevel_choices" // Started by server -#define CSGO_VOTE_NEXTLEVEL_EXTEND_PASSED "#SFUI_vote_passed_nextlevel_extend" -#define CSGO_VOTE_NEXTLEVEL_PASSED "#SFUI_vote_passed_nextlevel" - -// User vote to scramble teams. -#define CSGO_VOTE_SCRAMBLE_START "#SFUI_vote_scramble_teams" -#define CSGO_VOTE_SCRAMBLE_PASSED "#SFUI_vote_passed_scramble_teams" - -// User vote to swap teams. -#define CSGO_VOTE_SWAPTEAMS_START "#SFUI_vote_swap_teams" -#define CSGO_VOTE_SWAPTEAMS_PASSED "#SFUI_vote_passed_swap_teams" - -// User vote to surrender -#define CSGO_VOTE_SURRENDER_START "#SFUI_vote_surrender" -#define CSGO_VOTE_SURRENDER_OTHERTEAM "#SFUI_otherteam_vote_continue_or_surrender" -#define CSGO_VOTE_SURRENDER_PASSED "#SFUI_vote_passed_surrender" - -// User vote to rematch -#define CSGO_VOTE_REMATCH_START "#SFUI_vote_rematch" -#define CSGO_VOTE_REMATCH_PASSED "#SFUI_vote_passed_rematch" - -// User vote to continue match with bots -#define CSGO_VOTE_CONTINUE_START "#SFUI_vote_continue" -#define CSGO_VOTE_CONTINUE_OTHERTEAM "#SFUI_otherteam_vote_continue_or_surrender" -#define CSGO_VOTE_CONTINUE_PASSED "#SFUI_vote_passed_continue" - -// User vote to pause game -#define CSGO_VOTE_PAUSE_START "#SFUI_Vote_pause_match" -#define CSGO_VOTE_PAUSE_PASSED "#SFUI_vote_passed_pause_match" - -// User vote to unpause game -#define CSGO_VOTE_UNPAUSE_START "#SFUI_Vote_unpause_match" -#define CSGO_VOTE_UNPAUSE_PASSED "#SFUI_vote_passed_unpause_match" - -// User vote to load backups -#define CSGO_VOTE_LOADBACKUP_START "#SFUI_Vote_loadbackup" -#define CSGO_VOTE_LOADBACKUP_PASSED "#SFUI_vote_passed_loadbackup" - -// User vote to start match -#define CSGO_VOTE_READY_START "#SFUI_Vote_ready_for_match" -#define CSGO_VOTE_READY_PASSED "#SFUI_vote_passed_ready_for_match" - -// User vote to delay match start -#define CSGO_VOTE_NOTREADY_START "#SFUI_Vote_not_ready_for_match" -#define CSGO_VOTE_NOTREADY_PASSED "#SFUI_vote_passed_not_ready_for_match" - -// User vote to start round -#define CSGO_VOTE_ROUND_START "#SFUI_vote_td_start_round" -#define CSGO_VOTE_ROUND_PASSED "#SFUI_vote_passed_td_start_round" - -#define CSGO_VOTE_UNIMPLEMENTED_OTHERTEAM "#SFUI_otherteam_vote_unimplemented" - -// While not a vote string, it works just as well. -#define CSGO_VOTE_CUSTOM "#SFUI_Scoreboard_NormalPlayer" - -// CSGO VoteFail / CallVoteFail reasons -enum -{ - CSGO_VOTE_FAILED_GENERIC, - CSGO_VOTE_FAILED_TRANSITIONING_PLAYERS, - CSGO_VOTE_FAILED_RATE_EXCEEDED, - CSGO_VOTE_FAILED_YES_MUST_EXCEED_NO, - CSGO_VOTE_FAILED_QUORUM_FAILURE, - CSGO_VOTE_FAILED_ISSUE_DISABLED, - CSGO_VOTE_FAILED_MAP_NOT_FOUND, - CSGO_VOTE_FAILED_MAP_NAME_REQUIRED, - CSGO_VOTE_FAILED_FAILED_RECENTLY, - CSGO_VOTE_FAILED_FAILED_RECENTLY_KICK, - CSGO_VOTE_FAILED_FAILED_RECENTLY_MAP, - CSGO_VOTE_FAILED_FAILED_RECENTLY_SWAP, - CSGO_VOTE_FAILED_FAILED_RECENTLY_SCRAMBLE, - CSGO_VOTE_FAILED_FAILED_RECENTLY_RESTART, - CSGO_VOTE_FAILED_TEAM_CANT_CALL, - CSGO_VOTE_FAILED_WARMUP, - CSGO_VOTE_FAILED_PLAYERNOTFOUND, - CSGO_VOTE_FAILED_CANNOT_KICK_ADMIN, - CSGO_VOTE_FAILED_SCRAMBLE_IN_PROGRESS, - CSGO_VOTE_FAILED_SWAP_IN_PROGRESS, - CSGO_VOTE_FAILED_SPECTATOR, - CSGO_VOTE_FAILED_NEXTLEVEL_SET, - CSGO_VOTE_FAILED_UNKNOWN1, - CSGO_VOTE_FAILED_SURRENDER_ABANDON, - CSGO_VOTE_FAILED_UNKNOWN2, - CSGO_VOTE_FAILED_PAUSED, - CSGO_VOTE_FAILED_NOT_PAUSED, - CSGO_VOTE_FAILED_NOT_WARMUP, - CSGO_VOTE_FAILED_MIN_PLAYERS, - CSGO_VOTE_FAILED_ROUND_ENDED, -} - - -//---------------------------------------------------------------------------- -// Generic functions -// - -// This is deprecated in NativeVotes 1.1 -enum -{ - ValveVote_Kick = 0, - ValveVote_Restart = 1, - ValveVote_ChangeLevel = 2, - ValveVote_NextLevel = 3, - ValveVote_Scramble = 4, - ValveVote_SwapTeams = 5, -} - -static int g_VoteController = -1; -static bool g_bUserBuf = false; - -static ConVar g_Cvar_Votes_Enabled; -static ConVar g_Cvar_VoteKick_Enabled; -static ConVar g_Cvar_MvM_VoteKick_Enabled; -static ConVar g_Cvar_VoteNextLevel_Enabled; -static ConVar g_Cvar_VoteChangeLevel_Enabled; -static ConVar g_Cvar_MvM_VoteChangeLevel_Enabled; -static ConVar g_Cvar_VoteRestart_Enabled; -static ConVar g_Cvar_MvM_VoteRestart_Enabled; -static ConVar g_Cvar_VoteScramble_Enabled; -static ConVar g_Cvar_MvM_VoteChallenge_Enabled; -static ConVar g_Cvar_VoteAutoBalance_Enabled; -static ConVar g_Cvar_VoteClassLimits_Enabled; -static ConVar g_Cvar_MvM_VoteClassLimits_Enabled; -static ConVar g_Cvar_VoteExtend_Enabled; - -static ConVar g_Cvar_ClassLimit; -static ConVar g_Cvar_AutoBalance; - -static ConVar g_Cvar_HideDisabledIssues; - -/** - * TODO(UPDATE): For now we only support one vote from NativeVotes at a time. - * - * Ideally we peek and poke at the game's `s_nVoteIdx` and increment it accordingly, but we also - * have to store an internal list of active votes. - */ -static int s_nNativeVoteIdx = 0; - -bool isTF2SDKModHack = false; - -bool Game_IsGameSupported(char[] engineName="", int maxlength=0) -{ - g_EngineVersion = GetEngineVersion(); - - g_bUserBuf = GetFeatureStatus(FeatureType_Native, "GetUserMessageType") == FeatureStatus_Available && GetUserMessageType() == UM_Protobuf; - - //LogMessage("Detected Engine version: %d", g_EngineVersion); - if (maxlength > 0) - { - GetEngineVersionName(g_EngineVersion, engineName, maxlength); - } - - char gameDir[128]; - GetGameFolderName(gameDir, sizeof(gameDir)); - if ( !StrEqual(gameDir, "tf") ) - { - isTF2SDKModHack = true; - } - - //Fix for Open Fortress - if ( g_EngineVersion == Engine_SDK2013 && ( StrEqual(gameDir, "open_fortress") || StrEqual(gameDir, "tf2classic") || StrEqual(gameDir, "pf2") ) ) - { - g_EngineVersion = Engine_TF2; - } - - switch (g_EngineVersion) - { - case Engine_Left4Dead, Engine_Left4Dead2, Engine_CSGO, Engine_TF2: - { - return true; - } - } - - return false; -} - -void Game_InitializeCvars() -{ - switch (g_EngineVersion) - { - case Engine_Left4Dead, Engine_Left4Dead2, Engine_CSGO: - { - g_Cvar_Votes_Enabled = FindConVar("sv_allow_votes"); - } - - case Engine_TF2: - { - g_Cvar_Votes_Enabled = FindConVar("sv_allow_votes"); - g_Cvar_VoteKick_Enabled = FindConVar("sv_vote_issue_kick_allowed"); - g_Cvar_MvM_VoteKick_Enabled = FindConVar("sv_vote_issue_kick_allowed_mvm"); - g_Cvar_VoteNextLevel_Enabled = FindConVar("sv_vote_issue_nextlevel_allowed"); - g_Cvar_VoteChangeLevel_Enabled = FindConVar("sv_vote_issue_changelevel_allowed"); - g_Cvar_MvM_VoteChangeLevel_Enabled = FindConVar("sv_vote_issue_changelevel_allowed_mvm"); - g_Cvar_VoteRestart_Enabled = FindConVar("sv_vote_issue_restart_game_allowed"); - g_Cvar_MvM_VoteRestart_Enabled = FindConVar("sv_vote_issue_restart_game_allowed_mvm"); - g_Cvar_VoteScramble_Enabled = FindConVar("sv_vote_issue_scramble_teams_allowed"); - g_Cvar_MvM_VoteChallenge_Enabled = FindConVar("sv_vote_issue_mvm_challenge_allowed"); - g_Cvar_VoteAutoBalance_Enabled = FindConVar("sv_vote_issue_autobalance_allowed"); - g_Cvar_VoteClassLimits_Enabled = FindConVar("sv_vote_issue_classlimits_allowed"); - g_Cvar_MvM_VoteClassLimits_Enabled = FindConVar("sv_vote_issue_classlimits_allowed_mvm"); - g_Cvar_VoteExtend_Enabled = FindConVar("sv_vote_issue_extendlevel_allowed"); - - g_Cvar_ClassLimit = FindConVar("tf_classlimit"); - g_Cvar_AutoBalance = FindConVar("mp_autoteambalance"); - - g_Cvar_HideDisabledIssues = FindConVar("sv_vote_ui_hide_disabled_issues"); - } - } -} - -NativeVotesKickType Game_GetKickType(const char[] param1, int &target) -{ - NativeVotesKickType kickType; - - switch(g_EngineVersion) - { - case Engine_Left4Dead, Engine_Left4Dead2, Engine_CSGO: - { - target = StringToInt(param1); - kickType = NativeVotesKickType_Generic; - } - - case Engine_TF2: - { - char params[2][20]; - ExplodeString(param1, " ", params, sizeof(params), sizeof(params[])); - - target = StringToInt(params[0]); - - if (StrEqual(params[1], "cheating", false)) - { - kickType = NativeVotesKickType_Cheating; - } - else if (StrEqual(params[1], "idle", false)) - { - kickType = NativeVotesKickType_Idle; - } - else if (StrEqual(params[1], "scamming", false)) - { - kickType = NativeVotesKickType_Scamming; - } - else - { - kickType = NativeVotesKickType_Generic; - } - } - } - return kickType; -} - -bool CheckVoteController() -{ - int entity = INVALID_ENT_REFERENCE; - if (g_VoteController != -1) - { - entity = EntRefToEntIndex(g_VoteController); - } - - if (entity == INVALID_ENT_REFERENCE) - { - entity = FindEntityByClassname(-1, "vote_controller"); - if (entity == -1) - { - //LogError("Could not find Vote Controller."); - return false; - } - - g_VoteController = EntIndexToEntRef(entity); - } - return true; -} - -// All logic for choosing a game-specific function should happen here. -// There should be one per function in the game shared and specific sections -int Game_ParseVote(const char[] option) -{ - int item = NATIVEVOTES_VOTE_INVALID; - -#if defined LOG - LogMessage("Parsing vote option %s", option); -#endif - - switch(g_EngineVersion) - { - case Engine_Left4Dead, Engine_Left4Dead2: - { - item = L4DL4D2_ParseVote(option); - } - - case Engine_CSGO: - { - item = CSGO_ParseVote(option); - } - case Engine_TF2: - { - item = TF2_ParseVote(option); - } - } - - return item; - -} - -int Game_GetMaxItems() -{ - switch (g_EngineVersion) - { - case Engine_Left4Dead, Engine_Left4Dead2: - { - return L4DL4D2_COUNT; - } - - case Engine_CSGO, Engine_TF2: - { - return TF2CSGO_COUNT; - } - } - - return 0; // Here to prevent warnings -} - -bool Game_CheckVoteType(NativeVotesType type) -{ - bool returnVal = false; - - switch(g_EngineVersion) - { - case Engine_Left4Dead: - { - returnVal = L4D_CheckVoteType(type); - } - - case Engine_Left4Dead2: - { - returnVal = L4D2_CheckVoteType(type); - } - - case Engine_CSGO: - { - returnVal = CSGO_CheckVoteType(type); - } - - case Engine_TF2: - { - returnVal = TF2_CheckVoteType(type); - } - } - - return returnVal; -} - -bool Game_CheckVotePassType(NativeVotesPassType type) -{ - bool returnVal = false; - - switch(g_EngineVersion) - { - case Engine_Left4Dead: - { - returnVal = L4D_CheckVotePassType(type); - } - - case Engine_Left4Dead2: - { - returnVal = L4D2_CheckVotePassType(type); - } - - case Engine_CSGO: - { - returnVal = CSGO_CheckVotePassType(type); - } - - case Engine_TF2: - { - returnVal = TF2_CheckVotePassType(type); - } - } - - return returnVal; -} - -bool Game_DisplayVoteToOne(NativeVote vote, int client) -{ - if (g_bCancelled) - { - return false; - } - - int clients[1]; - clients[0] = client; - - return Game_DisplayVote(vote, clients, 1); -} - -bool Game_DisplayVote(NativeVote vote, int[] clients, int num_clients) -{ - if (g_bCancelled) - { - return false; - } - - switch(g_EngineVersion) - { - case Engine_Left4Dead: - { - L4D_DisplayVote(vote, num_clients); - } - - case Engine_Left4Dead2: - { - L4D2_DisplayVote(vote, clients, num_clients); - } - - case Engine_CSGO, Engine_TF2: - { - TF2CSGO_DisplayVote(vote, clients, num_clients); - } - } - - -#if defined LOG - char details[MAX_VOTE_DETAILS_LENGTH]; - char translation[TRANSLATION_LENGTH]; - - NativeVotesType voteType = Data_GetType(vote); - Data_GetDetails(vote, details, sizeof(details)); - Game_VoteTypeToTranslation(voteType, translation, sizeof(translation)); - - LogMessage("Displaying vote: type: %d, translation: \"%s\", details: \"%s\"", voteType, translation, details); -#endif - - return true; -} - -void Game_DisplayVoteFail(NativeVote vote, NativeVotesFailType reason) -{ - int team = Data_GetTeam(vote); - - int total = 0; - int[] players = new int[MaxClients]; - - for (int i = 1; i <= MaxClients; i++) - { - if (!IsClientInGame(i) || IsFakeClient(i)) - continue; - players[total++] = i; - } - - Game_DisplayRawVoteFail(players, total, reason, team); -} - -void Game_DisplayRawVoteFail(int[] clients, int numClients, NativeVotesFailType reason, int team) -{ - switch(g_EngineVersion) - { - case Engine_Left4Dead: - { - L4D_VoteFail(team); - } - - case Engine_Left4Dead2: - { - L4D2_VoteFail(clients, numClients, team); - } - - case Engine_CSGO: - { - int reasonType = VoteFailTypeToInt(reason); - CSGO_VoteFail(clients, numClients, reasonType, team); - } - - case Engine_TF2: - { - int reasonType = VoteFailTypeToInt(reason); - TF2_VoteFail(clients, numClients, reasonType, team); - } - } - -#if defined LOG - LogMessage("Vote Failed to %d client(s): \"%d\"", numClients, reason); -#endif -} - -void Game_DisplayVotePass(NativeVote vote, const char[] details="", int client=0) -{ - NativeVotesPassType passType = VoteTypeToVotePass(Data_GetType(vote)); - - Game_DisplayVotePassEx(vote, passType, details, client); -} - -void Game_DisplayVotePassEx(NativeVote vote, NativeVotesPassType passType, const char[] details="", int client=0) -{ - int team = Data_GetTeam(vote); - - Game_DisplayRawVotePass(passType, team, client, details); -} - -void Game_DisplayRawVotePass(NativeVotesPassType passType, int team, int client=0, const char[] details="") -{ - char translation[TRANSLATION_LENGTH]; - - switch (g_EngineVersion) - { - case Engine_Left4Dead: - { - if (!client) - { - L4DL4D2_VotePassToTranslation(passType, translation, sizeof(translation)); - - L4D_VotePass(translation, details, team); - } - } - - case Engine_Left4Dead2: - { - L4DL4D2_VotePassToTranslation(passType, translation, sizeof(translation)); - - switch (passType) - { - case NativeVotesPass_AlltalkOn: - { - L4D2_VotePass(translation, L4D2_VOTE_ALLTALK_ENABLE, team, client); - } - - case NativeVotesPass_AlltalkOff: - { - L4D2_VotePass(translation, L4D2_VOTE_ALLTALK_DISABLE, team, client); - } - - default: - { - L4D2_VotePass(translation, details, team, client); - } - } - } - - case Engine_CSGO: - { - CSGO_VotePassToTranslation(passType, translation, sizeof(translation)); - CSGO_VotePass(translation, details, team, client); - } - - case Engine_TF2: - { - TF2_VotePassToTranslation(passType, translation, sizeof(translation)); - TF2_VotePass(translation, details, team, client); - } - } - -#if defined LOG - if (client != 0) - LogMessage("Vote Passed: \"%s\", \"%s\"", translation, details); -#endif -} - -void Game_DisplayVotePassCustom(NativeVote vote, const char[] translation, int client) -{ - int team = Data_GetTeam(vote); - Game_DisplayRawVotePassCustom(translation, team, client); -} - -void Game_DisplayRawVotePassCustom(const char[] translation, int team, int client) -{ - switch (g_EngineVersion) - { - case Engine_Left4Dead: - { - ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes_DisplayPassCustom is not supported on L4D"); - } - - case Engine_Left4Dead2: - { - L4D2_VotePass(L4D_VOTE_CUSTOM, translation, team, client); - } - - case Engine_CSGO: - { - CSGO_VotePass(CSGO_VOTE_CUSTOM, translation, team, client); - } - - case Engine_TF2: - { - TF2_VotePass(TF2_VOTE_CUSTOM, translation, team, client); - } - } - -#if defined LOG - if (client != 0) - LogMessage("Vote Passed Custom: \"%s\"", translation); -#endif -} - -void Game_DisplayCallVoteFail(int client, NativeVotesCallFailType reason, int time) -{ - - int reasonType = VoteCallFailTypeToInt(reason); - - switch (g_EngineVersion) - { - case Engine_Left4Dead, Engine_Left4Dead2: - { - L4DL4D2_CallVoteFail(client, reasonType); - } - - case Engine_CSGO: - { - CSGO_CallVoteFail(client, reasonType, time); - } - - case Engine_TF2: - { - TF2_CallVoteFail(client, reasonType, time); - } - } - -#if defined LOG - LogMessage("Call vote failed: client: %N, reason: %d, time: %d", client, reason, time); -#endif -} - -void Game_ClientSelectedItem(NativeVote vote, int client, int item) -{ -#if defined LOG - LogMessage("Client %N selected item %d", client, item); -#endif - switch(g_EngineVersion) - { - case Engine_Left4Dead, Engine_Left4Dead2: - { - L4DL4D2_ClientSelectedItem(client, item); - } - - case Engine_CSGO: - { - CSGO_ClientSelectedItem(vote, client, item); - } - case Engine_TF2: - { - TF2_ClientSelectedItem(vote, client, item); - } -/* - case Engine_Left4Dead: - { - L4D_ClientSelectedItem(vote, client, item); - } - - case Engine_Left4Dead2: - { - L4D2_ClientSelectedItem(client, item); - } -*/ - } -} - -void Game_UpdateVoteCounts(ArrayList hVoteCounts, int totalClients) -{ - switch(g_EngineVersion) - { - case Engine_Left4Dead, Engine_Left4Dead2: - { - L4DL4D2_UpdateVoteCounts(hVoteCounts, totalClients); - } - - case Engine_CSGO, Engine_TF2: - { - TF2CSGO_UpdateVoteCounts(hVoteCounts); - } - } -} - -void Game_DisplayVoteSetup(int client, ArrayList hVoteTypes) -{ - switch (g_EngineVersion) - { - case Engine_Left4Dead: - { - //L4D_DisplayVoteSetup(client, voteTypes); - } - - case Engine_Left4Dead2: - { - //L4D2_DisplayVoteSetup(client, voteTypes); - } - - case Engine_TF2: - { - TF2_DisplayVoteSetup(client, hVoteTypes); - } - - case Engine_CSGO: - { - //CSGO_DisplayVoteSetup(client, hVoteTypes); - } - - } -} - -// stock because at the moment it's only used in logging code which isn't always compiled. -stock void Game_VoteTypeToTranslation(NativeVotesType voteType, char[] translation, int maxlength) -{ - switch(g_EngineVersion) - { - case Engine_Left4Dead, Engine_Left4Dead2: - { - L4DL4D2_VoteTypeToTranslation(voteType, translation, maxlength); - } - - case Engine_CSGO: - { - CSGO_VoteTypeToTranslation(voteType, translation, maxlength); - } - - case Engine_TF2: - { - TF2_VoteTypeToTranslation(voteType, translation, maxlength); - } - } -} - -stock void Game_UpdateClientCount(int num_clients) -{ - switch(g_EngineVersion) - { - case Engine_Left4Dead, Engine_Left4Dead2: - { - L4DL4D2_UpdateClientCount(num_clients); - } - - case Engine_CSGO, Engine_TF2: - { - TF2CSGO_UpdateClientCount(num_clients); - } - } -} - -public Action Game_ResetVote(Handle timer) -{ - switch(g_EngineVersion) - { - case Engine_Left4Dead, Engine_Left4Dead2: - { - L4DL4D2_ResetVote(); - } - - case Engine_CSGO, Engine_TF2: - { - TF2CSGO_ResetVote(); - } - } - return Plugin_Continue; -} - -void Game_VoteYes(int client) -{ - switch (g_EngineVersion) - { - case Engine_Left4Dead, Engine_Left4Dead2: - { - FakeClientCommand(client, "Vote Yes"); - } - - case Engine_CSGO, Engine_TF2: - { - FakeClientCommand(client, "vote option1"); - } - } -} - -void Game_VoteNo(int client) -{ - switch (g_EngineVersion) - { - case Engine_Left4Dead, Engine_Left4Dead2: - { - FakeClientCommand(client, "Vote No"); - } - - case Engine_CSGO, Engine_TF2: - { - FakeClientCommand(client, "vote option2"); - } - } -} - -bool Game_IsVoteInProgress() -{ - switch (g_EngineVersion) - { - case Engine_Left4Dead, Engine_Left4Dead2: - { - return L4DL4D2_IsVoteInProgress(); - } - - case Engine_CSGO, Engine_TF2: - { - return TF2CSGO_IsVoteInProgress(); - } - } - - return false; -} - -bool Game_AreVoteCommandsSupported() -{ - switch (g_EngineVersion) - { - case Engine_TF2: - { - return true; - } - } - - return false; -} - -stock bool Game_VoteTypeToVoteString(NativeVotesType voteType, char[] voteString, int maxlength) -{ - switch (g_EngineVersion) - { - case Engine_TF2: - { - return TF2_VoteTypeToVoteString(voteType, voteString, maxlength); - } - } - - return false; -} - -stock NativeVotesType Game_VoteStringToVoteType(char[] voteString) -{ - switch (g_EngineVersion) - { - case Engine_TF2: - { - return TF2_VoteStringToVoteType(voteString); - } - } - - return NativeVotesType_None; -} - -stock NativeVotesOverride Game_VoteTypeToVoteOverride(NativeVotesType voteType) -{ - switch (g_EngineVersion) - { - case Engine_TF2: - { - return TF2_VoteTypeToVoteOverride(voteType); - } - } - - return NativeVotesOverride_None; -} - -stock NativeVotesType Game_VoteOverrideToVoteType(NativeVotesOverride overrideType) -{ - switch (g_EngineVersion) - { - case Engine_TF2: - { - return TF2_VoteOverrideToVoteType(overrideType); - } - } - - return NativeVotesType_None; -} - -stock NativeVotesOverride Game_VoteStringToVoteOverride(const char[] voteString) -{ - switch (g_EngineVersion) - { - case Engine_TF2: - { - return TF2_VoteStringToVoteOverride(voteString); - } - } - - return NativeVotesOverride_None; -} - -stock bool Game_OverrideTypeToVoteString(NativeVotesOverride overrideType, char[] voteString, int maxlength) -{ - switch (g_EngineVersion) - { - case Engine_TF2: - { - return TF2_OverrideTypeToVoteString(overrideType, voteString, maxlength); - } - } - - return false; -} - -stock bool Game_OverrideTypeToTranslationString(NativeVotesOverride overrideType, char[] translationString, int maxlength) -{ - switch (g_EngineVersion) - { - case Engine_TF2: - { - return TF2_OverrideTypeToTranslationString(overrideType, translationString, maxlength); - } - } - - return false; -} - -void Game_AddDefaultVotes(ArrayList hVoteTypes) -{ - switch (g_EngineVersion) - { - case Engine_TF2: - { - TF2_AddDefaultVotes(hVoteTypes, Game_AreDisabledIssuesHidden()); - } - } -} - -bool Game_AreDisabledIssuesHidden() -{ - switch (g_EngineVersion) - { - case Engine_TF2: - { - return g_Cvar_HideDisabledIssues.BoolValue; - } - } - - return true; -} - -// All games shared functions - -//---------------------------------------------------------------------------- -// Data functions - -static NativeVotesPassType VoteTypeToVotePass(NativeVotesType voteType) -{ - NativeVotesPassType passType = NativeVotesPass_None; - - switch(voteType) - { - case NativeVotesType_Custom_YesNo, NativeVotesType_Custom_Mult: - { - passType = NativeVotesPass_Custom; - } - - case NativeVotesType_ChgCampaign: - { - passType = NativeVotesPass_ChgCampaign; - } - - case NativeVotesType_ChgDifficulty: - { - passType = NativeVotesPass_ChgDifficulty; - } - - case NativeVotesType_ReturnToLobby: - { - passType = NativeVotesPass_ReturnToLobby; - } - - case NativeVotesType_AlltalkOn: - { - passType = NativeVotesPass_AlltalkOn; - } - - case NativeVotesType_AlltalkOff: - { - passType = NativeVotesPass_AlltalkOff; - } - - case NativeVotesType_Restart: - { - passType = NativeVotesPass_Restart; - } - - case NativeVotesType_Kick, NativeVotesType_KickIdle, NativeVotesType_KickScamming, NativeVotesType_KickCheating: - { - passType = NativeVotesPass_Kick; - } - - case NativeVotesType_ChgLevel: - { - passType = NativeVotesPass_ChgLevel; - } - - case NativeVotesType_NextLevel, NativeVotesType_NextLevelMult: - { - passType = NativeVotesPass_NextLevel; - } - - case NativeVotesType_ScrambleNow, NativeVotesType_ScrambleEnd: - { - passType = NativeVotesPass_Scramble; - } - - case NativeVotesType_ChgMission: - { - passType = NativeVotesPass_ChgMission; - } - - case NativeVotesType_SwapTeams: - { - passType = NativeVotesPass_SwapTeams; - } - - case NativeVotesType_Surrender: - { - passType = NativeVotesPass_Surrender; - } - - case NativeVotesType_Rematch: - { - passType = NativeVotesPass_Rematch; - } - - case NativeVotesType_Continue: - { - passType = NativeVotesPass_Continue; - } - - case NativeVotesType_StartRound: - { - passType = NativeVotesPass_StartRound; - } - - case NativeVotesType_Eternaween: - { - passType = NativeVotesPass_Eternaween; - } - - case NativeVotesType_AutoBalanceOn: - { - passType = NativeVotesPass_AutoBalanceOn; - } - - case NativeVotesType_AutoBalanceOff: - { - passType = NativeVotesPass_AutoBalanceOff; - } - - case NativeVotesType_ClassLimitsOn: - { - passType = NativeVotesPass_ClassLimitsOn; - } - - case NativeVotesType_ClassLimitsOff: - { - passType = NativeVotesPass_ClassLimitsOff; - } - - case NativeVotesType_Extend: - { - passType = NativeVotesPass_Extend; - } - - default: - { - passType = NativeVotesPass_Custom; - } - } - - return passType; -} - -// In case we find more types later -static int VoteFailTypeToInt(NativeVotesFailType failType) -{ - return view_as(failType); -} - -static int VoteCallFailTypeToInt(NativeVotesCallFailType failType) -{ - switch (g_EngineVersion) - { - case Engine_Left4Dead, Engine_Left4Dead2: - { - switch (failType) - { - case NativeVotesCallFail_Generic: - { - return VOTE_FAILED_GENERIC; - } - - case NativeVotesCallFail_Loading: - { - return VOTE_FAILED_TRANSITIONING_PLAYERS; - } - - case NativeVotesCallFail_Recent: - { - return VOTE_FAILED_FAILED_RECENTLY; - } - } - } - - case Engine_TF2: - { - switch (failType) - { - case NativeVotesCallFail_Generic: - { - return VOTE_FAILED_GENERIC; - } - - case NativeVotesCallFail_Loading: - { - return VOTE_FAILED_TRANSITIONING_PLAYERS; - } - - case NativeVotesCallFail_Recent: - { - return VOTE_FAILED_FAILED_RECENTLY; - } - - case NativeVotesCallFail_Disabled: - { - return VOTE_FAILED_ISSUE_DISABLED; - } - - case NativeVotesCallFail_MapNotFound: - { - return VOTE_FAILED_MAP_NOT_FOUND; - } - - case NativeVotesCallFail_SpecifyMap: - { - return VOTE_FAILED_MAP_NAME_REQUIRED; - } - - case NativeVotesCallFail_Failed: - { - return VOTE_FAILED_FAILED_RECENTLY; - } - - case NativeVotesCallFail_WrongTeam: - { - return VOTE_FAILED_TEAM_CANT_CALL; - } - - case NativeVotesCallFail_Waiting: - { - return VOTE_FAILED_WAITINGFORPLAYERS; - } - - case NativeVotesCallFail_PlayerNotFound: - { - return VOTE_FAILED_PLAYERNOTFOUND; - } - - case NativeVotesCallFail_CantKickAdmin: - { - return VOTE_FAILED_CANNOT_KICK_ADMIN; - } - - case NativeVotesCallFail_ScramblePending: - { - return VOTE_FAILED_SCRAMBLE_IN_PROGRESS; - } - - case NativeVotesCallFail_Spectators: - { - return VOTE_FAILED_SPECTATOR; - } - - case NativeVotesCallFail_LevelSet: - { - return VOTE_FAILED_NEXTLEVEL_SET; - } - - case NativeVotesCallFail_MapNotValid: - { - return VOTE_FAILED_MAP_NOT_VALID; - } - - case NativeVotesCallFail_KickTime: - { - return VOTE_FAILED_CANNOT_KICK_FOR_TIME; - } - - case NativeVotesCallFail_KickDuringRound: - { - return VOTE_FAILED_CANNOT_KICK_DURING_ROUND; - } - - case NativeVotesCallFail_AlreadyActive: - { - return VOTE_FAILED_MODIFICATION_ALREADY_ACTIVE; - } - } - } - - case Engine_CSGO: - { - switch (failType) - { - case NativeVotesCallFail_Generic: - { - return CSGO_VOTE_FAILED_GENERIC; - } - - case NativeVotesCallFail_Loading: - { - return CSGO_VOTE_FAILED_TRANSITIONING_PLAYERS; - } - - case NativeVotesCallFail_Recent: - { - return CSGO_VOTE_FAILED_RATE_EXCEEDED; - } - - case NativeVotesCallFail_Disabled: - { - return CSGO_VOTE_FAILED_ISSUE_DISABLED; - } - - case NativeVotesCallFail_MapNotFound: - { - return CSGO_VOTE_FAILED_MAP_NOT_FOUND; - } - - case NativeVotesCallFail_SpecifyMap: - { - return CSGO_VOTE_FAILED_MAP_NAME_REQUIRED; - } - - case NativeVotesCallFail_Failed: - { - return CSGO_VOTE_FAILED_FAILED_RECENTLY; - } - - case NativeVotesCallFail_WrongTeam: - { - return CSGO_VOTE_FAILED_TEAM_CANT_CALL; - } - - case NativeVotesCallFail_Warmup: - { - return CSGO_VOTE_FAILED_WARMUP; - } - - case NativeVotesCallFail_PlayerNotFound: - { - return CSGO_VOTE_FAILED_PLAYERNOTFOUND; - } - - case NativeVotesCallFail_CantKickAdmin: - { - return CSGO_VOTE_FAILED_CANNOT_KICK_ADMIN; - } - - case NativeVotesCallFail_ScramblePending: - { - return CSGO_VOTE_FAILED_SCRAMBLE_IN_PROGRESS; - } - - case NativeVotesCallFail_Spectators: - { - return CSGO_VOTE_FAILED_SPECTATOR; - } - - case NativeVotesCallFail_LevelSet: - { - return CSGO_VOTE_FAILED_NEXTLEVEL_SET; - } - - case NativeVotesCallFail_KickFailed: - { - return CSGO_VOTE_FAILED_FAILED_RECENTLY_KICK; - } - - case NativeVotesCallFail_MapFailed: - { - return CSGO_VOTE_FAILED_FAILED_RECENTLY_MAP; - } - - case NativeVotesCallFail_SwapFailed: - { - return CSGO_VOTE_FAILED_FAILED_RECENTLY_SWAP; - } - - case NativeVotesCallFail_ScrambleFailed: - { - return CSGO_VOTE_FAILED_FAILED_RECENTLY_SCRAMBLE; - } - - case NativeVotesCallFail_RestartFailed: - { - return CSGO_VOTE_FAILED_FAILED_RECENTLY_RESTART; - } - - case NativeVotesCallFail_SwapPending: - { - return CSGO_VOTE_FAILED_SWAP_IN_PROGRESS; - } - - case NativeVotesCallFail_Unknown2: - { - return CSGO_VOTE_FAILED_UNKNOWN1; - } - - case NativeVotesCallFail_CantSurrender: - { - return CSGO_VOTE_FAILED_SURRENDER_ABANDON; - } - - case NativeVotesCallFail_Unknown3: - { - return CSGO_VOTE_FAILED_UNKNOWN2; - } - - case NativeVotesCallFail_MatchPaused: - { - return CSGO_VOTE_FAILED_PAUSED; - } - - case NativeVotesCallFail_NotPaused: - { - return CSGO_VOTE_FAILED_NOT_PAUSED; - } - - case NativeVotesCallFail_NotWarmup: - { - return CSGO_VOTE_FAILED_NOT_WARMUP; - } - - case NativeVotesCallFail_MinPlayers: - { - return CSGO_VOTE_FAILED_MIN_PLAYERS; - } - - case NativeVotesCallFail_RoundEnded: - { - return CSGO_VOTE_FAILED_ROUND_ENDED; - } - } - } - } - - return VOTE_FAILED_GENERIC; -} - -static void GetEngineVersionName(EngineVersion version, char[] printName, int maxlength) -{ - switch (version) - { - case Engine_Unknown: - { - strcopy(printName, maxlength, "Unknown"); - } - - case Engine_Original: - { - strcopy(printName, maxlength, "Original"); - } - - case Engine_SourceSDK2006: - { - strcopy(printName, maxlength, "Source SDK 2006"); - } - - case Engine_SourceSDK2007: - { - strcopy(printName, maxlength, "Source SDK 2007"); - } - - case Engine_Left4Dead: - { - strcopy(printName, maxlength, "Left 4 Dead "); - } - - case Engine_DarkMessiah: - { - strcopy(printName, maxlength, "Dark Messiah"); - } - - case Engine_Left4Dead2: - { - strcopy(printName, maxlength, "Left 4 Dead 2"); - } - - case Engine_AlienSwarm: - { - strcopy(printName, maxlength, "Alien Swarm"); - } - - case Engine_BloodyGoodTime: - { - strcopy(printName, maxlength, "Bloody Good Time"); - } - - case Engine_EYE: - { - strcopy(printName, maxlength, "E.Y.E. Divine Cybermancy"); - } - - case Engine_Portal2: - { - strcopy(printName, maxlength, "Portal 2"); - } - - case Engine_CSGO: - { - strcopy(printName, maxlength, "Counter-Strike: Global Offensive"); - } - - case Engine_CSS: - { - strcopy(printName, maxlength, "Counter-Strike: Source"); - } - - case Engine_DOTA: - { - strcopy(printName, maxlength, "DOTA 2"); - } - - case Engine_HL2DM: - { - strcopy(printName, maxlength, "Half-Life 2: Deathmatch"); - } - - case Engine_DODS: - { - strcopy(printName, maxlength, "Day of Defeat: Source"); - } - - case Engine_TF2: - { - strcopy(printName, maxlength, "Team Fortress 2"); - } - - case Engine_NuclearDawn: - { - strcopy(printName, maxlength, "Nuclear Dawn"); - } - - default: - { - strcopy(printName, maxlength, "Not listed"); - } - } -} - - - -//---------------------------------------------------------------------------- -// L4D/L4D2 shared functions - -// NATIVEVOTES_VOTE_INVALID means parse failed -static int L4DL4D2_ParseVote(const char[] option) -{ - if (StrEqual(option, "Yes", false)) - { - return NATIVEVOTES_VOTE_YES; - } - else if (StrEqual(option, "No", false)) - { - return NATIVEVOTES_VOTE_NO; - } - - return NATIVEVOTES_VOTE_INVALID; -} - -static void L4DL4D2_ClientSelectedItem(int client, int item) -{ - int choice; - - if (item == NATIVEVOTES_VOTE_NO) - { - choice = 0; - } - else if (item == NATIVEVOTES_VOTE_YES) - { - choice = 1; - } - - BfWrite voteCast = UserMessageToBfWrite(StartMessageOne("VoteRegistered", client, USERMSG_RELIABLE)); - voteCast.WriteByte(choice); - EndMessage(); -} - -static void L4DL4D2_UpdateVoteCounts(ArrayList votes, int totalClients) -{ - int yesVotes = votes.Get(NATIVEVOTES_VOTE_YES); - int noVotes = votes.Get(NATIVEVOTES_VOTE_NO); - Event changeEvent = CreateEvent("vote_changed"); - changeEvent.SetInt("yesVotes", yesVotes); - changeEvent.SetInt("noVotes", noVotes); - changeEvent.SetInt("potentialVotes", totalClients); - changeEvent.Fire(); - - if (CheckVoteController()) - { - SetEntProp(g_VoteController, Prop_Send, "m_votesYes", yesVotes); - SetEntProp(g_VoteController, Prop_Send, "m_votesNo", noVotes); - } -} - -static stock void L4DL4D2_UpdateClientCount(int num_clients) -{ - if (CheckVoteController()) - { - SetEntProp(g_VoteController, Prop_Send, "m_potentialVotes", num_clients); - } -} - -static void L4DL4D2_CallVoteFail(int client, int reason) -{ - BfWrite callVoteFail = UserMessageToBfWrite(StartMessageOne("CallVoteFailed", client, USERMSG_RELIABLE)); - - callVoteFail.WriteByte(reason); - - EndMessage(); -} - -static void L4DL4D2_VoteTypeToTranslation(NativeVotesType voteType, char[] translation, int maxlength) -{ - switch(voteType) - { - case NativeVotesType_ChgCampaign: - { - strcopy(translation, maxlength, L4D_VOTE_CHANGECAMPAIGN_START); - } - - case NativeVotesType_ChgDifficulty: - { - strcopy(translation, maxlength, L4D_VOTE_CHANGEDIFFICULTY_START); - } - - case NativeVotesType_ReturnToLobby: - { - strcopy(translation, maxlength, L4D_VOTE_RETURNTOLOBBY_START); - } - - case NativeVotesType_AlltalkOn, NativeVotesType_AlltalkOff: - { - strcopy(translation, maxlength, L4D2_VOTE_ALLTALK_START); - } - - case NativeVotesType_Restart: - { - strcopy(translation, maxlength, L4D_VOTE_RESTART_START); - } - - case NativeVotesType_Kick: - { - strcopy(translation, maxlength, L4D_VOTE_KICK_START); - } - - case NativeVotesType_ChgLevel: - { - strcopy(translation, maxlength, L4D_VOTE_CHANGELEVEL_START); - } - - default: - { - strcopy(translation, maxlength, L4D_VOTE_CUSTOM); - } - } -} - -static void L4DL4D2_VotePassToTranslation(NativeVotesPassType passType, char[] translation, int maxlength) -{ - switch(passType) - { - case NativeVotesPass_Custom: - { - strcopy(translation, maxlength, L4D_VOTE_CUSTOM); - } - - case NativeVotesPass_ChgCampaign: - { - strcopy(translation, maxlength, L4D_VOTE_CHANGECAMPAIGN_PASSED); - } - - case NativeVotesPass_ChgDifficulty: - { - strcopy(translation, maxlength, L4D_VOTE_CHANGEDIFFICULTY_PASSED); - } - - case NativeVotesPass_ReturnToLobby: - { - strcopy(translation, maxlength, L4D_VOTE_RETURNTOLOBBY_PASSED); - } - - case NativeVotesPass_AlltalkOn, NativeVotesPass_AlltalkOff: - { - strcopy(translation, maxlength, L4D2_VOTE_ALLTALK_PASSED); - } - - case NativeVotesPass_Restart: - { - strcopy(translation, maxlength, L4D_VOTE_RESTART_PASSED); - } - - case NativeVotesPass_Kick: - { - strcopy(translation, maxlength, L4D_VOTE_KICK_PASSED); - } - - case NativeVotesPass_ChgLevel: - { - strcopy(translation, maxlength, L4D_VOTE_CHANGELEVEL_PASSED); - } - - default: - { - strcopy(translation, maxlength, L4D_VOTE_CUSTOM); - } - } -} - -static void L4DL4D2_ResetVote() -{ - if (CheckVoteController()) - { - SetEntProp(g_VoteController, Prop_Send, "m_activeIssueIndex", INVALID_ISSUE); - SetEntProp(g_VoteController, Prop_Send, "m_votesYes", 0); - SetEntProp(g_VoteController, Prop_Send, "m_votesNo", 0); - SetEntProp(g_VoteController, Prop_Send, "m_potentialVotes", 0); - SetEntProp(g_VoteController, Prop_Send, "m_onlyTeamToVote", NATIVEVOTES_ALL_TEAMS); - } -} - -static bool L4DL4D2_IsVoteInProgress() -{ - if (CheckVoteController()) - { - return (GetEntProp(g_VoteController, Prop_Send, "m_activeIssueIndex") > INVALID_ISSUE); - } - return false; -} - -//---------------------------------------------------------------------------- -// L4D functions - -/* -static L4D_ClientSelectedItem(Handle:vote, client, item) -{ - if (item > NATIVEVOTES_VOTE_INVALID && item <= Game_GetMaxItems()) - { - new Handle:castEvent; - - switch (item) - { - case NATIVEVOTES_VOTE_YES: - { - castEvent = CreateEvent("vote_cast_no"); - } - - case NATIVEVOTES_VOTE_NO: - { - castEvent = CreateEvent("vote_cast_yes"); - } - - default: - { - return; - } - } - - if (castEvent != INVALID_HANDLE) - { - SetEventInt(castEvent, "team", Data_GetTeam(vote)); - SetEventInt(castEvent, "entityid", client); - FireEvent(castEvent); - } - - } -} -*/ - -static void L4D_DisplayVote(NativeVote vote, int num_clients) -{ - char translation[TRANSLATION_LENGTH]; - - NativeVotesType voteType = Data_GetType(vote); - - L4DL4D2_VoteTypeToTranslation(voteType, translation, sizeof(translation)); - - char details[MAX_VOTE_DETAILS_LENGTH]; - Data_GetDetails(vote, details, MAX_VOTE_DETAILS_LENGTH); - - int team = Data_GetTeam(vote); - - if (CheckVoteController()) - { - // TODO: Need to look these values up - SetEntProp(g_VoteController, Prop_Send, "m_activeIssueIndex", 0); // For now, set to 0 to block in-game votes - SetEntProp(g_VoteController, Prop_Send, "m_onlyTeamToVote", team); - SetEntProp(g_VoteController, Prop_Send, "m_votesYes", 0); - SetEntProp(g_VoteController, Prop_Send, "m_votesNo", 0); - SetEntProp(g_VoteController, Prop_Send, "m_potentialVotes", num_clients); - } - - Event voteStart = CreateEvent("vote_started"); - voteStart.SetInt("team", team); - voteStart.SetInt("initiator", Data_GetInitiator(vote)); - voteStart.SetString("issue", translation); - voteStart.SetString("param1", details); - voteStart.Fire(); - -} - -static void L4D_VoteEnded() -{ - Event endEvent = CreateEvent("vote_ended"); - endEvent.Fire(); -} - -static void L4D_VotePass(const char[] translation, const char[] details, int team) -{ - L4D_VoteEnded(); - - Event passEvent = CreateEvent("vote_passed"); - passEvent.SetString("details", translation); - passEvent.SetString("param1", details); - passEvent.SetInt("team", team); - passEvent.Fire(); -} - -static void L4D_VoteFail(int team) -{ - L4D_VoteEnded(); - - Event failEvent = CreateEvent("vote_failed"); - failEvent.SetInt("team", team); - failEvent.Fire(); -} - -static bool L4D_CheckVoteType(NativeVotesType voteType) -{ - switch(voteType) - { - case NativeVotesType_Custom_YesNo, NativeVotesType_ChgCampaign, NativeVotesType_ChgDifficulty, - NativeVotesType_ReturnToLobby, NativeVotesType_Restart, NativeVotesType_Kick, - NativeVotesType_ChgLevel: - { - return true; - } - } - - return false; -} - -static bool L4D_CheckVotePassType(NativeVotesPassType passType) -{ - switch(passType) - { - case NativeVotesPass_Custom, NativeVotesPass_ChgCampaign, NativeVotesPass_ChgDifficulty, - NativeVotesPass_ReturnToLobby, NativeVotesPass_Restart, NativeVotesPass_Kick, - NativeVotesPass_ChgLevel: - { - return true; - } - } - - return false; -} - -//---------------------------------------------------------------------------- -// L4D2 functions - -static void L4D2_DisplayVote(NativeVote vote, int[] clients, int num_clients) -{ - char translation[TRANSLATION_LENGTH]; - - NativeVotesType voteType = Data_GetType(vote); - - L4DL4D2_VoteTypeToTranslation(voteType, translation, sizeof(translation)); - - char details[MAX_VOTE_DETAILS_LENGTH]; - - int team = Data_GetTeam(vote); - bool bCustom = false; - - switch (voteType) - { - case NativeVotesType_AlltalkOn: - { - strcopy(details, MAX_VOTE_DETAILS_LENGTH, L4D2_VOTE_ALLTALK_ENABLE); - } - - case NativeVotesType_AlltalkOff: - { - strcopy(details, MAX_VOTE_DETAILS_LENGTH, L4D2_VOTE_ALLTALK_DISABLE); - } - - case NativeVotesType_Custom_YesNo, NativeVotesType_Custom_Mult: - { - Data_GetTitle(vote, details, MAX_VOTE_DETAILS_LENGTH); - bCustom = true; - } - - default: - { - Data_GetDetails(vote, details, MAX_VOTE_DETAILS_LENGTH); - } - } - - int initiator = Data_GetInitiator(vote); - char initiatorName[MAX_NAME_LENGTH]; - - if (initiator != NATIVEVOTES_SERVER_INDEX && initiator > 0 && initiator <= MaxClients && IsClientInGame(initiator)) - { - GetClientName(initiator, initiatorName, MAX_NAME_LENGTH); - } - - for (int i = 0; i < num_clients; ++i) - { - g_newMenuTitle[0] = '\0'; - - MenuAction actions = Data_GetActions(vote); - - Action changeTitle = Plugin_Continue; - if (bCustom && actions & MenuAction_Display) - { - g_curDisplayClient = clients[i]; - changeTitle = view_as(DoAction(vote, MenuAction_Display, clients[i], 0)); - } - - g_curDisplayClient = 0; - - BfWrite voteStart = UserMessageToBfWrite(StartMessageOne("VoteStart", clients[i], USERMSG_RELIABLE)); - voteStart.WriteByte(team); - voteStart.WriteByte(initiator); - voteStart.WriteString(translation); - if (changeTitle == Plugin_Changed) - { - voteStart.WriteString(g_newMenuTitle); - } - else - { - voteStart.WriteString(details); - } - voteStart.WriteString(initiatorName); - EndMessage(); - } - - if (CheckVoteController()) - { - SetEntProp(g_VoteController, Prop_Send, "m_onlyTeamToVote", team); - SetEntProp(g_VoteController, Prop_Send, "m_votesYes", 0); - SetEntProp(g_VoteController, Prop_Send, "m_votesNo", 0); - SetEntProp(g_VoteController, Prop_Send, "m_potentialVotes", num_clients); - SetEntProp(g_VoteController, Prop_Send, "m_activeIssueIndex", 0); // Set to 0 to block ingame votes - } -} - -static void L4D2_VotePass(const char[] translation, const char[] details, int team, int client=0) -{ - BfWrite votePass; - if (!client) - { - votePass = UserMessageToBfWrite(StartMessageAll("VotePass", USERMSG_RELIABLE)); - } - else - { - votePass = UserMessageToBfWrite(StartMessageOne("VotePass", client, USERMSG_RELIABLE)); - } - - votePass.WriteByte(team); - votePass.WriteString(translation); - votePass.WriteString(details); - EndMessage(); -} - -static void L4D2_VoteFail(int[] clients, int numClients, int team) -{ - BfWrite voteFailed = UserMessageToBfWrite(StartMessage("VoteFail", clients, numClients, USERMSG_RELIABLE)); - - voteFailed.WriteByte(team); - EndMessage(); -} - -static bool L4D2_CheckVoteType(NativeVotesType voteType) -{ - switch(voteType) - { - case NativeVotesType_Custom_YesNo, NativeVotesType_ChgCampaign, NativeVotesType_ChgDifficulty, - NativeVotesType_ReturnToLobby, NativeVotesType_AlltalkOn, NativeVotesType_AlltalkOff, - NativeVotesType_Restart, NativeVotesType_Kick, NativeVotesType_ChgLevel: - { - return true; - } - } - - return false; -} - -static bool L4D2_CheckVotePassType(NativeVotesPassType passType) -{ - switch(passType) - { - case NativeVotesPass_Custom, NativeVotesPass_ChgCampaign, NativeVotesPass_ChgDifficulty, - NativeVotesPass_ReturnToLobby, NativeVotesPass_AlltalkOn, NativeVotesPass_AlltalkOff, - NativeVotesPass_Restart, NativeVotesPass_Kick, NativeVotesPass_ChgLevel: - { - return true; - } - } - - return false; -} - -//---------------------------------------------------------------------------- -// TF2/CSGO shared functions - -// TF2 and CSGO functions are still together in case Valve moves TF2 to protobufs. - -// NATIVEVOTES_VOTE_INVALID means parse failed -static int CSGO_ParseVote(const char[] option) -{ - // option1 <-- 7 characters exactly - if (strlen(option) != 7) - { - return NATIVEVOTES_VOTE_INVALID; - } - - return StringToInt(option[6]) - 1; -} - -// NATIVEVOTES_VOTE_INVALID means parse failed -static int TF2_SDKHACK_ParseVote(const char[] option) -{ - // currently sdk2013 tf mods match csgo voting but if that ever changes - // this func will have to be properly written - return CSGO_ParseVote(option); -} - -// NATIVEVOTES_VOTE_INVALID means parse failed -static int TF2_ParseVote(const char[] option) -{ - if (isTF2SDKModHack) - { - return TF2_SDKHACK_ParseVote(option); - } - - // the update on 2022-06-22 changed the params passed to the `vote` command - // previously it was a single string "optionN", where N was the option to be selected - // now it's two arguments "X optionN", where X is the vote index being acted on - - if (strlen(option) == 0 || GetCmdArgs() != 2) - { - return NATIVEVOTES_VOTE_INVALID; - } - - // int voteidx = GetCmdArgInt(1); - - char voteOption[16]; - GetCmdArg(2, voteOption, sizeof(voteOption)); - - // option1 <-- 7 characters exactly - // voteOption's last character should be numeric - if (strlen(voteOption) != 7 || !IsCharNumeric(voteOption[6])) - { - return NATIVEVOTES_VOTE_INVALID; - } - - return StringToInt(voteOption[6]) - 1; -} - -static void CSGO_ClientSelectedItem(NativeVote vote, int client, int item) -{ - Event castEvent = CreateEvent("vote_cast"); - - castEvent.SetInt("team", Data_GetTeam(vote)); - castEvent.SetInt("entityid", client); - castEvent.SetInt("vote_option", item); - castEvent.Fire(); -} - -static void TF2_ClientSelectedItem(NativeVote vote, int client, int item) -{ - Event castEvent = CreateEvent("vote_cast"); - - castEvent.SetInt("team", Data_GetTeam(vote)); - if (!isTF2SDKModHack) - { - castEvent.SetInt("voteidx", s_nNativeVoteIdx); // TODO(UPDATE): this was added in 2022-06-22 - figure out what the client voted for - } - castEvent.SetInt("entityid", client); - castEvent.SetInt("vote_option", item); - castEvent.Fire(); -} - -static void TF2CSGO_UpdateVoteCounts(ArrayList votes) -{ - if (CheckVoteController()) - { - int size = votes.Length; - for (int i = 0; i < size; i++) - { - SetEntProp(g_VoteController, Prop_Send, "m_nVoteOptionCount", votes.Get(i), 4, i); - } - } -} - -static stock void TF2CSGO_UpdateClientCount(int num_clients) -{ - if (CheckVoteController()) - { - SetEntProp(g_VoteController, Prop_Send, "m_nPotentialVotes", num_clients); - } -} - -static void TF2CSGO_DisplayVote(NativeVote vote, int[] clients, int num_clients) -{ - NativeVotesType voteType = Data_GetType(vote); - - // Added for novote support - bool bNoVoteButton = (Data_GetFlags(vote) & MENUFLAG_BUTTON_NOVOTE) == MENUFLAG_BUTTON_NOVOTE; - - if (bNoVoteButton) - { - int max = Game_GetMaxItems(); - if (Data_GetItemCount(vote) == max) - { - // item must be removed before No Vote is added to prevent it from being blocked - Data_RemoveItem(vote, max-1); - } - - char display[TRANSLATION_LENGTH]; - Format(display, sizeof(display), "%T", "No Vote", LANG_SERVER); - - Data_InsertItem(vote, 0, "No Vote", display); - } - - char translation[TRANSLATION_LENGTH]; - char otherTeamString[TRANSLATION_LENGTH]; - bool bYesNo = true; - bool bCustom = false; - - char details[MAX_VOTE_DETAILS_LENGTH]; - - // voteIndex is used by the CVoteController, which we're not using. - // -1 means no vote in progress, so any other value should work. - //int voteIndex = TF2CSGO_GetVoteType(voteType); - int voteIndex = 0; - - switch (voteType) - { - case NativeVotesType_Custom_YesNo, NativeVotesType_Custom_Mult: - { - Data_GetTitle(vote, details, MAX_VOTE_DETAILS_LENGTH); - bCustom = true; - } - - default: - { - Data_GetDetails(vote, details, MAX_VOTE_DETAILS_LENGTH); - } - } - - switch(g_EngineVersion) - { - case Engine_CSGO: - { - bYesNo = CSGO_VoteTypeToTranslation(voteType, translation, sizeof(translation)); - CSGO_VoteTypeToVoteOtherTeamString(voteType, otherTeamString, sizeof(otherTeamString)); - } - - case Engine_TF2: - { - bYesNo = TF2_VoteTypeToTranslation(voteType, translation, sizeof(translation)); - } - } - - int team = Data_GetTeam(vote); - - // Moved to mimic SourceSDK2013's server/vote_controller.cpp - if (CheckVoteController()) - { - SetEntProp(g_VoteController, Prop_Send, "m_bIsYesNoVote", bYesNo); - - // CSGO gets very cranky if you try setting this - if (g_EngineVersion == Engine_TF2) - SetEntProp(g_VoteController, Prop_Send, "m_iActiveIssueIndex", voteIndex); - - SetEntProp(g_VoteController, Prop_Send, "m_iOnlyTeamToVote", team); - for (int i = 0; i < 5; i++) - { - SetEntProp(g_VoteController, Prop_Send, "m_nVoteOptionCount", 0, _, i); - } - - if (!isTF2SDKModHack) - { - // TODO(UPDATE): M-M-M-MULTIVOTE - // we need unique vote indices; HUD elements for previous votes aren't cleaned up (?) - // the game implements this as `this->m_nVoteIdx = s_nVoteIdx++` - s_nNativeVoteIdx = GetEntProp(g_VoteController, Prop_Send, "m_nVoteIdx"); - -#if defined LOG - PrintToServer("Starting vote index: %d (controller: %d)", s_nNativeVoteIdx, GetEntProp(g_VoteController, Prop_Send, "m_nVoteIdx")); -#endif - SetEntProp(g_VoteController, Prop_Send, "m_nVoteIdx", s_nNativeVoteIdx + 1); // TODO(UPDATE) - } - } - - // According to Source SDK 2013, vote_options is only sent for a multiple choice vote. - // As of 2015-09-28, vote_options is sent for all votes in TF2 despite Yes/No being - // translated in the UI itself. - // As of 2016-07-10, we send this only when we're doing Yes/No votes as we handle - // multiple choice votes separately later. - if (bYesNo) - { - int itemCount = Data_GetItemCount(vote); - - Event optionsEvent = CreateEvent("vote_options"); - - for (int i = 0; i < itemCount; i++) - { - char option[8]; - Format(option, sizeof(option), "%s%d", TF2CSGO_VOTE_PREFIX, i+1); - - char display[TRANSLATION_LENGTH]; - Data_GetItemDisplay(vote, i, display, sizeof(display)); - optionsEvent.SetString(option, display); - } - optionsEvent.SetInt("count", itemCount); - if (!isTF2SDKModHack) - { - optionsEvent.SetInt("voteidx", s_nNativeVoteIdx); // TODO(UPDATE) - } - optionsEvent.Fire(); - } - - // Moved to mimic SourceSDK2013's server/vote_controller.cpp - // For whatever reason, while the other props are set first, this one's set after the vote_options event - if (CheckVoteController()) - { - SetEntProp(g_VoteController, Prop_Send, "m_nPotentialVotes", num_clients); - } - - if (!isTF2SDKModHack) - { - int holder = vote.Initiator; - // required to allow the initiator to vote on their own issue - // ValveSoftware/Source-1-Games#3934 - if (sv_vote_holder_may_vote_no && holder <= MaxClients && IsClientConnected(holder)) - { - sv_vote_holder_may_vote_no.ReplicateToClient(holder, "1"); - } - } - - - MenuAction actions = Data_GetActions(vote); - - for (int i = 0; i < num_clients; ++i) - { - g_newMenuTitle[0] = '\0'; - - Action changeTitle = Plugin_Continue; - if (bCustom && actions & MenuAction_Display) - { - g_curDisplayClient = clients[i]; - changeTitle = DoAction(vote, MenuAction_Display, clients[i], 0); - } - - g_curDisplayClient = 0; - - if (!bYesNo) - { - TF2CSGO_SendOptionsToClient(vote, clients[i]); - } - - Handle voteStart = StartMessageOne("VoteStart", clients[i], USERMSG_RELIABLE); - - if(g_bUserBuf) - { - Protobuf protoStart = UserMessageToProtobuf(voteStart); - protoStart.SetInt("team", team); - protoStart.SetInt("ent_idx", Data_GetInitiator(vote)); - protoStart.SetString("disp_str", translation); - if (bCustom && changeTitle == Plugin_Changed) - { - protoStart.SetString("details_str", g_newMenuTitle); - } - else - { - protoStart.SetString("details_str", details); - } - protoStart.SetBool("is_yes_no_vote", bYesNo); - protoStart.SetString("other_team_str", otherTeamString); - protoStart.SetInt("vote_type", voteIndex); - } - else - { - BfWrite bfStart = UserMessageToBfWrite(voteStart); - bfStart.WriteByte(team); - if (!isTF2SDKModHack) - { - bfStart.WriteNum(s_nNativeVoteIdx); - } - - bfStart.WriteByte(Data_GetInitiator(vote)); - bfStart.WriteString(translation); - if (bCustom && changeTitle == Plugin_Changed) - { - bfStart.WriteString(g_newMenuTitle); - } - else - { - bfStart.WriteString(details); - } - bfStart.WriteBool(bYesNo); - } - - EndMessage(); - } - - g_curDisplayClient = 0; - -} - -static void TF2CSGO_SendOptionsToClient(NativeVote vote, int client) -{ - Event optionsEvent = CreateEvent("vote_options"); - - MenuAction actions = Data_GetActions(vote); - bool bNoVoteButton = (Data_GetFlags(vote) & MENUFLAG_BUTTON_NOVOTE) == MENUFLAG_BUTTON_NOVOTE; - - int start = 0; - - if (bNoVoteButton) - { - start = 1; - char option[8]; - Format(option, sizeof(option), "%s1", TF2CSGO_VOTE_PREFIX); - - char display[TRANSLATION_LENGTH]; - Format(display, sizeof(display), "%T", "No Vote", client); - optionsEvent.SetString(option, display); - } - - int itemCount = Data_GetItemCount(vote); - - for (int i = start; i < itemCount; i++) - { - Action changeItem = Plugin_Continue; - - if (actions & MenuAction_DisplayItem) - { - g_curItemClient = client; - g_newMenuItem[0] = '\0'; - - changeItem = DoAction(vote, MenuAction_DisplayItem, client, i); - g_curItemClient = 0; - } - - char option[8]; - Format(option, sizeof(option), "%s%d", TF2CSGO_VOTE_PREFIX, i+1); - char display[TRANSLATION_LENGTH]; - - if (changeItem == Plugin_Changed) - { - strcopy(display, TRANSLATION_LENGTH, g_newMenuItem); - } - else - { - Data_GetItemDisplay(vote, i, display, sizeof(display)); - } - optionsEvent.SetString(option, display); - } - optionsEvent.SetInt("count", itemCount); - if (!isTF2SDKModHack) - { - optionsEvent.SetInt("voteidx", s_nNativeVoteIdx); // TODO(UPDATE) - } - optionsEvent.FireToClient(client); - // FireToClient does not close the handle, so we call Cancel() to do that for us. - optionsEvent.Cancel(); -} - -static void CSGO_VotePass(const char[] translation, const char[] details, int team, int client=0) -{ - Protobuf votePass = null; - - if (!client) - { - votePass = UserMessageToProtobuf(StartMessageAll("VotePass", USERMSG_RELIABLE)); - } - else - { - votePass = UserMessageToProtobuf(StartMessageOne("VotePass", client, USERMSG_RELIABLE)); - } - - votePass.SetInt("team", team); - votePass.SetString("disp_str", translation); - votePass.SetString("details_str", details); - votePass.SetInt("vote_type", 0); // Unknown, need to check values - - EndMessage(); -} - -static void TF2_VotePass(const char[] translation, const char[] details, int team, int client=0) -{ - BfWrite votePass = null; - - if (!client) - { - votePass = UserMessageToBfWrite(StartMessageAll("VotePass", USERMSG_RELIABLE)); - } - else - { - votePass = UserMessageToBfWrite(StartMessageOne("VotePass", client, USERMSG_RELIABLE)); - } - - votePass.WriteByte(team); - if (!isTF2SDKModHack) - { - votePass.WriteNum(s_nNativeVoteIdx); - } - votePass.WriteString(translation); - votePass.WriteString(details); - - EndMessage(); -} - -static void CSGO_VoteFail(int[] clients, int numClients, int reason, int team) -{ - Protobuf voteFailed = UserMessageToProtobuf(StartMessage("VoteFailed", clients, numClients, USERMSG_RELIABLE)); - - voteFailed.SetInt("team", team); - voteFailed.SetInt("reason", reason); - - EndMessage(); -} - -static void TF2_VoteFail(int[] clients, int numClients, int reason, int team) -{ - BfWrite voteFailed = UserMessageToBfWrite(StartMessage("VoteFailed", clients, numClients, USERMSG_RELIABLE)); - - voteFailed.WriteByte(team); - if (!isTF2SDKModHack) - { - voteFailed.WriteNum(s_nNativeVoteIdx); // TODO(UPDATE) - } - voteFailed.WriteByte(reason); - - EndMessage(); -} - -static void CSGO_CallVoteFail(int client, int reason, int time) -{ - Protobuf callVoteFail = UserMessageToProtobuf(StartMessageOne("CallVoteFailed", client, USERMSG_RELIABLE)); - - callVoteFail.SetInt("reason", reason); - callVoteFail.SetInt("time", time); - - EndMessage(); -} - -static void TF2_CallVoteFail(int client, int reason, int time) -{ - BfWrite callVoteFail = UserMessageToBfWrite(StartMessageOne("CallVoteFailed", client, USERMSG_RELIABLE)); - - callVoteFail.WriteByte(reason); - callVoteFail.WriteShort(time); - - EndMessage(); -} - -stock static void CSGO_DisplayVoteSetup(int client, ArrayList hVoteTypes) -{ - int count = hVoteTypes.Length; - - Protobuf voteSetup = UserMessageToProtobuf(StartMessageOne("VoteSetup", client, USERMSG_RELIABLE)); - - for (int i = 0; i < count; ++i) - { - char voteIssue[128]; - - CallVoteListData voteData; - hVoteTypes.GetArray(i, voteData.CallVoteList_VoteType); - - Game_OverrideTypeToVoteString(voteData.CallVoteList_VoteType, voteIssue, sizeof(voteIssue)); - - - voteSetup.AddString("potential_issues", voteIssue); - } - - EndMessage(); -} - -static void TF2_DisplayVoteSetup(int client, ArrayList hVoteTypes) -{ - int count = hVoteTypes.Length; - - BfWrite voteSetup = UserMessageToBfWrite(StartMessageOne("VoteSetup", client, USERMSG_RELIABLE)); - - voteSetup.WriteByte(count); - - for (int i = 0; i < count; ++i) - { - char voteIssue[128]; - - CallVoteListData voteData; - hVoteTypes.GetArray(i, voteData, sizeof(CallVoteListData)); - - Game_OverrideTypeToVoteString(voteData.CallVoteList_VoteType, voteIssue, sizeof(voteIssue)); - - char translation[128]; - Game_OverrideTypeToTranslationString(voteData.CallVoteList_VoteType, translation, sizeof(translation)); - - voteSetup.WriteString(voteIssue); - voteSetup.WriteString(translation); - voteSetup.WriteByte(voteData.CallVoteList_VoteEnabled); - } - - EndMessage(); -} - -static void TF2CSGO_ResetVote() -{ - if (CheckVoteController()) - { - if (g_EngineVersion == Engine_TF2) - { - SetEntProp(g_VoteController, Prop_Send, "m_iActiveIssueIndex", INVALID_ISSUE); - if (!isTF2SDKModHack) - { - SetEntProp(g_VoteController, Prop_Send, "m_nVoteIdx", -1); // TODO(UPDATE) - } - } - - for (int i = 0; i < 5; i++) - { - SetEntProp(g_VoteController, Prop_Send, "m_nVoteOptionCount", 0, _, i); - } - SetEntProp(g_VoteController, Prop_Send, "m_nPotentialVotes", 0); - if (g_EngineVersion == Engine_TF2) - { - SetEntProp(g_VoteController, Prop_Send, "m_iOnlyTeamToVote", NATIVEVOTES_TF2_ALL_TEAMS); - } - else - { - SetEntProp(g_VoteController, Prop_Send, "m_iOnlyTeamToVote", NATIVEVOTES_ALL_TEAMS); - } - SetEntProp(g_VoteController, Prop_Send, "m_bIsYesNoVote", true); - } -} - -static bool TF2CSGO_IsVoteInProgress() -{ - if (CheckVoteController()) - { - return (GetEntProp(g_VoteController, Prop_Send, "m_iActiveIssueIndex") != INVALID_ISSUE); - } - return false; -} - - -//---------------------------------------------------------------------------- -// TF2 functions - -static bool TF2_CheckVoteType(NativeVotesType voteType) -{ - switch(voteType) - { - case NativeVotesType_Custom_YesNo, NativeVotesType_Restart, - NativeVotesType_Kick, NativeVotesType_KickIdle, NativeVotesType_KickScamming, NativeVotesType_KickCheating, - NativeVotesType_ChgLevel, NativeVotesType_NextLevel, NativeVotesType_ScrambleNow, NativeVotesType_ScrambleEnd, - NativeVotesType_ChgMission, NativeVotesType_StartRound, NativeVotesType_Eternaween, - NativeVotesType_AutoBalanceOn, NativeVotesType_AutoBalanceOff, - NativeVotesType_ClassLimitsOn, NativeVotesType_ClassLimitsOff, NativeVotesType_Extend: - { - return true; - } - - case NativeVotesType_Custom_Mult, NativeVotesType_NextLevelMult: - { - return true; - } - } - - return false; -} - -static bool TF2_CheckVotePassType(NativeVotesPassType passType) -{ - switch(passType) - { - case NativeVotesPass_Custom, NativeVotesPass_Restart, NativeVotesPass_ChgLevel, - NativeVotesPass_Kick, NativeVotesPass_NextLevel, NativeVotesPass_Extend, - NativeVotesPass_Scramble, NativeVotesPass_ChgMission, NativeVotesPass_StartRound, NativeVotesPass_Eternaween, - NativeVotesPass_AutoBalanceOn, NativeVotesPass_AutoBalanceOff, - NativeVotesPass_ClassLimitsOn, NativeVotesPass_ClassLimitsOff: - { - return true; - } - } - - return false; -} - -static bool TF2_VoteTypeToTranslation(NativeVotesType voteType, char[] translation, int maxlength) -{ - bool bYesNo = true; - switch(voteType) - { - case NativeVotesType_Custom_Mult: - { - strcopy(translation, maxlength, TF2_VOTE_CUSTOM); - bYesNo = false; - } - - case NativeVotesType_Restart: - { - strcopy(translation, maxlength, TF2_VOTE_RESTART_START); - } - - case NativeVotesType_Kick: - { - strcopy(translation, maxlength, TF2_VOTE_KICK_START); - } - - case NativeVotesType_KickIdle: - { - strcopy(translation, maxlength, TF2_VOTE_KICK_IDLE_START); - } - - case NativeVotesType_KickScamming: - { - strcopy(translation, maxlength, TF2_VOTE_KICK_SCAMMING_START); - } - - case NativeVotesType_KickCheating: - { - strcopy(translation, maxlength, TF2_VOTE_KICK_CHEATING_START); - } - - case NativeVotesType_ChgLevel: - { - strcopy(translation, maxlength, TF2_VOTE_CHANGELEVEL_START); - } - - case NativeVotesType_NextLevel: - { - strcopy(translation, maxlength, TF2_VOTE_NEXTLEVEL_SINGLE_START); - } - - case NativeVotesType_NextLevelMult: - { - - strcopy(translation, maxlength, TF2_VOTE_NEXTLEVEL_MULTIPLE_START); - bYesNo = false; - } - - case NativeVotesType_ScrambleNow: - { - strcopy(translation, maxlength, TF2_VOTE_SCRAMBLE_IMMEDIATE_START); - } - - case NativeVotesType_ScrambleEnd: - { - strcopy(translation, maxlength, TF2_VOTE_SCRAMBLE_ROUNDEND_START); - } - - case NativeVotesType_ChgMission: - { - strcopy(translation, maxlength, TF2_VOTE_CHANGEMISSION_START); - } - - case NativeVotesType_StartRound: - { - strcopy(translation, maxlength, TF2_VOTE_ROUND_START); - } - - case NativeVotesType_Eternaween: - { - strcopy(translation, maxlength, TF2_VOTE_ETERNAWEEN_START); - } - - case NativeVotesType_AutoBalanceOn: - { - strcopy(translation, maxlength, TF2_VOTE_AUTOBALANCE_ENABLE_START); - } - - case NativeVotesType_AutoBalanceOff: - { - strcopy(translation, maxlength, TF2_VOTE_AUTOBALANCE_DISABLE_START); - } - - case NativeVotesType_ClassLimitsOn: - { - strcopy(translation, maxlength, TF2_VOTE_CLASSLIMITS_ENABLE_START); - } - - case NativeVotesType_ClassLimitsOff: - { - strcopy(translation, maxlength, TF2_VOTE_CLASSLIMITS_DISABLE_START); - } - - case NativeVotesType_Extend: - { - strcopy(translation, maxlength, TF2_VOTE_EXTEND_START); - } - - default: - { - strcopy(translation, maxlength, TF2_VOTE_CUSTOM); - } - } - - return bYesNo; -} - -static void TF2_VotePassToTranslation(NativeVotesPassType passType, char[] translation, int maxlength) -{ - switch(passType) - { - case NativeVotesPass_Restart: - { - strcopy(translation, maxlength, TF2_VOTE_RESTART_PASSED); - } - - case NativeVotesPass_Kick: - { - strcopy(translation, maxlength, TF2_VOTE_KICK_PASSED); - } - - case NativeVotesPass_ChgLevel: - { - strcopy(translation, maxlength, TF2_VOTE_CHANGELEVEL_PASSED); - } - - case NativeVotesPass_NextLevel: - { - strcopy(translation, maxlength, TF2_VOTE_NEXTLEVEL_PASSED); - } - - case NativeVotesPass_Extend: - { - strcopy(translation, maxlength, TF2_VOTE_NEXTLEVEL_EXTEND_PASSED); - } - - case NativeVotesPass_Scramble: - { - strcopy(translation, maxlength, TF2_VOTE_SCRAMBLE_PASSED); - } - - case NativeVotesPass_ChgMission: - { - strcopy(translation, maxlength, TF2_VOTE_CHANGEMISSION_PASSED); - } - - case NativeVotesPass_StartRound: - { - strcopy(translation, maxlength, TF2_VOTE_ROUND_PASSED); - } - - case NativeVotesPass_Eternaween: - { - strcopy(translation, maxlength, TF2_VOTE_ETERNAWEEN_PASSED); - } - - case NativeVotesPass_AutoBalanceOn: - { - strcopy(translation, maxlength, TF2_VOTE_AUTOBALANCE_ENABLE_PASSED); - } - - case NativeVotesPass_AutoBalanceOff: - { - strcopy(translation, maxlength, TF2_VOTE_AUTOBALANCE_DISABLE_PASSED); - } - - case NativeVotesPass_ClassLimitsOn: - { - strcopy(translation, maxlength, TF2_VOTE_CLASSLIMITS_ENABLE_PASSED); - } - - case NativeVotesPass_ClassLimitsOff: - { - strcopy(translation, maxlength, TF2_VOTE_CLASSLIMITS_DISABLE_PASSED); - } - - default: - { - strcopy(translation, maxlength, TF2_VOTE_CUSTOM); - } - } -} - -static void TF2_AddDefaultVotes(ArrayList hVoteTypes, bool bHideDisabledVotes) -{ - int globalEnable = g_Cvar_Votes_Enabled.BoolValue; - if (!globalEnable && bHideDisabledVotes) - { - return; - } - - if (GameRules_GetProp("m_bPlayingMannVsMachine")) - { - // Default MvM vote types - - // Kick - VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_Kick, globalEnable && g_Cvar_MvM_VoteKick_Enabled.BoolValue); - - // Restart - VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_Restart, globalEnable && g_Cvar_MvM_VoteRestart_Enabled.BoolValue); - - // ChgLevel - VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_ChgLevel, globalEnable && g_Cvar_MvM_VoteChangeLevel_Enabled.BoolValue); - - // ChgMission - VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_ChgMission, globalEnable && g_Cvar_MvM_VoteChallenge_Enabled.BoolValue); - - // ClassLimits - VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_ClassLimits, globalEnable && g_Cvar_MvM_VoteClassLimits_Enabled.BoolValue); - } - else - { - // Default vote types - - // Kick - VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_Kick, globalEnable && g_Cvar_VoteKick_Enabled.BoolValue); - - // Restart - VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_Restart, globalEnable && g_Cvar_VoteRestart_Enabled.BoolValue); - - // ChgLevel - VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_ChgLevel, globalEnable && g_Cvar_VoteChangeLevel_Enabled.BoolValue); - - // NextLevel - VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_NextLevel, globalEnable && g_Cvar_VoteNextLevel_Enabled.BoolValue); - - // Scramble - VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_Scramble, globalEnable && g_Cvar_VoteScramble_Enabled.BoolValue); - - // ClassLimits - VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_ClassLimits, globalEnable && g_Cvar_VoteClassLimits_Enabled.BoolValue); - - // AutoBalance - VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_AutoBalance, globalEnable && g_Cvar_VoteAutoBalance_Enabled.BoolValue); - - // Extend - VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_Extend, globalEnable && g_Cvar_VoteExtend_Enabled.BoolValue); - } - -} - -static void VoteTypeSet(ArrayList hVoteTypes, bool bHideDisabledVotes, NativeVotesOverride voteType, bool bEnabled) -{ - CallVoteListData voteList; - - if (bEnabled || !bHideDisabledVotes) - { - voteList.CallVoteList_VoteType = voteType; - voteList.CallVoteList_VoteEnabled = bEnabled; - - hVoteTypes.PushArray(voteList); - } -} - -//---------------------------------------------------------------------------- -// CSGO functions - -static bool CSGO_CheckVoteType(NativeVotesType voteType) -{ - switch(voteType) - { - case NativeVotesType_Custom_YesNo, NativeVotesType_Restart, - NativeVotesType_Kick, NativeVotesType_KickIdle, NativeVotesType_KickScamming, NativeVotesType_KickCheating, - NativeVotesType_ChgLevel, NativeVotesType_NextLevel, NativeVotesType_ScrambleNow, NativeVotesType_SwapTeams, - NativeVotesType_Surrender, NativeVotesType_Rematch, NativeVotesType_Continue: - { - return true; - } - - case NativeVotesType_Custom_Mult, NativeVotesType_NextLevelMult: - { - // Until/unless Valve fixes their menu code, this is false. - return false; - } - - } - - return false; -} - -static bool CSGO_CheckVotePassType(NativeVotesPassType passType) -{ - switch(passType) - { - case NativeVotesPass_Custom, NativeVotesPass_Restart, NativeVotesPass_Kick, - NativeVotesPass_ChgLevel, NativeVotesPass_NextLevel, NativeVotesPass_Extend, - NativeVotesPass_Scramble, NativeVotesPass_SwapTeams, NativeVotesPass_Surrender, - NativeVotesPass_Rematch, NativeVotesPass_Continue: - { - return true; - } - } - - return false; -} - -static bool CSGO_VoteTypeToTranslation(NativeVotesType voteType, char[] translation, int maxlength) -{ - bool bYesNo = true; - switch(voteType) - { - case NativeVotesType_Custom_Mult: - { - strcopy(translation, maxlength, CSGO_VOTE_CUSTOM); - bYesNo = false; - } - - case NativeVotesType_Restart: - { - strcopy(translation, maxlength, CSGO_VOTE_RESTART_START); - } - - case NativeVotesType_Kick: - { - strcopy(translation, maxlength, CSGO_VOTE_KICK_START); - } - - case NativeVotesType_KickIdle: - { - strcopy(translation, maxlength, CSGO_VOTE_KICK_IDLE_START); - } - - case NativeVotesType_KickScamming: - { - strcopy(translation, maxlength, CSGO_VOTE_KICK_SCAMMING_START); - } - - case NativeVotesType_KickCheating: - { - strcopy(translation, maxlength, CSGO_VOTE_KICK_CHEATING_START); - } - - case NativeVotesType_ChgLevel: - { - strcopy(translation, maxlength, CSGO_VOTE_CHANGELEVEL_START); - } - - case NativeVotesType_NextLevel: - { - strcopy(translation, maxlength, CSGO_VOTE_NEXTLEVEL_SINGLE_START); - } - - case NativeVotesType_NextLevelMult: - { - - strcopy(translation, maxlength, CSGO_VOTE_NEXTLEVEL_MULTIPLE_START); - bYesNo = false; - } - - case NativeVotesType_ScrambleNow: - { - strcopy(translation, maxlength, CSGO_VOTE_SCRAMBLE_START); - } - - case NativeVotesType_SwapTeams: - { - strcopy(translation, maxlength, CSGO_VOTE_SWAPTEAMS_START); - } - - case NativeVotesType_Surrender: - { - strcopy(translation, maxlength, CSGO_VOTE_SURRENDER_START); - } - - case NativeVotesType_Rematch: - { - strcopy(translation, maxlength, CSGO_VOTE_REMATCH_START); - } - - case NativeVotesType_Continue: - { - strcopy(translation, maxlength, CSGO_VOTE_CONTINUE_START); - } - - default: - { - strcopy(translation, maxlength, CSGO_VOTE_CUSTOM); - } - } - - return bYesNo; -} - -static void CSGO_VotePassToTranslation(NativeVotesPassType passType, char[] translation, int maxlength) -{ - switch(passType) - { - case NativeVotesPass_Restart: - { - strcopy(translation, maxlength, CSGO_VOTE_RESTART_PASSED); - } - - case NativeVotesPass_Kick: - { - strcopy(translation, maxlength, CSGO_VOTE_KICK_PASSED); - } - - case NativeVotesPass_ChgLevel: - { - strcopy(translation, maxlength, CSGO_VOTE_CHANGELEVEL_PASSED); - } - - case NativeVotesPass_NextLevel: - { - strcopy(translation, maxlength, CSGO_VOTE_NEXTLEVEL_PASSED); - } - - case NativeVotesPass_Extend: - { - strcopy(translation, maxlength, CSGO_VOTE_NEXTLEVEL_EXTEND_PASSED); - } - - case NativeVotesPass_Scramble: - { - strcopy(translation, maxlength, CSGO_VOTE_SCRAMBLE_PASSED); - } - - case NativeVotesPass_SwapTeams: - { - strcopy(translation, maxlength, CSGO_VOTE_SWAPTEAMS_PASSED); - } - - case NativeVotesPass_Surrender: - { - strcopy(translation, maxlength, CSGO_VOTE_SURRENDER_PASSED); - } - - case NativeVotesPass_Rematch: - { - strcopy(translation, maxlength, CSGO_VOTE_REMATCH_PASSED); - } - - case NativeVotesPass_Continue: - { - strcopy(translation, maxlength, CSGO_VOTE_CONTINUE_PASSED); - } - - default: - { - strcopy(translation, maxlength, CSGO_VOTE_CUSTOM); - } - } -} - -static void CSGO_VoteTypeToVoteOtherTeamString(NativeVotesType voteType, char[] otherTeamString, int maxlength) -{ - switch(voteType) - { - case NativeVotesType_Kick: - { - strcopy(otherTeamString, maxlength, CSGO_VOTE_KICK_OTHERTEAM); - } - - case NativeVotesType_Surrender: - { - strcopy(otherTeamString, maxlength, CSGO_VOTE_SURRENDER_OTHERTEAM); - } - - case NativeVotesType_Continue: - { - strcopy(otherTeamString, maxlength, CSGO_VOTE_CONTINUE_OTHERTEAM); - } - - default: - { - strcopy(otherTeamString, maxlength, CSGO_VOTE_UNIMPLEMENTED_OTHERTEAM); - } - } - -} - -static stock int TF2CSGO_GetVoteType(NativeVotesType voteType) -{ - int valveVoteType = ValveVote_Restart; - - switch (voteType) - { - case NativeVotesType_Custom_YesNo, NativeVotesType_Restart: - { - valveVoteType = ValveVote_Restart; - } - - case NativeVotesType_Custom_Mult, NativeVotesType_NextLevel, NativeVotesType_NextLevelMult: - { - valveVoteType = ValveVote_NextLevel; - } - - case NativeVotesType_Kick, NativeVotesType_KickIdle, NativeVotesType_KickScamming, NativeVotesType_KickCheating: - { - valveVoteType = ValveVote_Kick; - } - - case NativeVotesType_ChgLevel: - { - valveVoteType = ValveVote_ChangeLevel; - } - - case NativeVotesType_ScrambleNow, NativeVotesType_ScrambleEnd: - { - valveVoteType = ValveVote_Scramble; - } - - case NativeVotesType_SwapTeams: - { - valveVoteType = ValveVote_SwapTeams; - } - } - - return valveVoteType; -} - -// The stocks below are used by the vote override system -// Not all are used by the plugin -static stock bool TF2_VoteTypeToVoteString(NativeVotesType voteType, char[] voteString, int maxlength) -{ - bool valid = false; - switch(voteType) - { - case NativeVotesType_Kick, NativeVotesType_KickCheating, NativeVotesType_KickIdle, NativeVotesType_KickScamming: - { - strcopy(voteString, maxlength, TF2CSGO_VOTE_STRING_KICK); - valid = true; - } - - case NativeVotesType_ChgLevel: - { - strcopy(voteString, maxlength, TF2CSGO_VOTE_STRING_CHANGELEVEL); - valid = true; - } - - case NativeVotesType_NextLevel: - { - strcopy(voteString, maxlength, TF2CSGO_VOTE_STRING_NEXTLEVEL); - valid = true; - } - - case NativeVotesType_Restart: - { - strcopy(voteString, maxlength, TF2CSGO_VOTE_STRING_RESTART); - valid = true; - } - - case NativeVotesType_ScrambleEnd, NativeVotesType_ScrambleNow: - { - strcopy(voteString, maxlength, TF2CSGO_VOTE_STRING_SCRAMBLE); - valid = true; - } - - case NativeVotesType_Eternaween: - { - strcopy(voteString, maxlength, TF2_VOTE_STRING_ETERNAWEEN); - valid = true; - } - - case NativeVotesType_AutoBalanceOn: - { - strcopy(voteString, maxlength, TF2_VOTE_STRING_AUTOBALANCE); - valid = true; - } - - case NativeVotesType_AutoBalanceOff: - { - strcopy(voteString, maxlength, TF2_VOTE_STRING_AUTOBALANCE); - valid = true; - } - - case NativeVotesType_ClassLimitsOn: - { - strcopy(voteString, maxlength, TF2_VOTE_STRING_CLASSLIMIT); - valid = true; - } - - case NativeVotesType_ClassLimitsOff: - { - strcopy(voteString, maxlength, TF2_VOTE_STRING_CLASSLIMIT); - valid = true; - } - - case NativeVotesType_Extend: - { - strcopy(voteString, maxlength, TF2_VOTE_STRING_EXTEND); - } - } - - return valid; -} - -static stock NativeVotesType TF2_VoteStringToVoteType(const char[] voteString) -{ - NativeVotesType voteType = NativeVotesType_None; - - if (StrEqual(voteString, TF2CSGO_VOTE_STRING_KICK, false)) - { - voteType = NativeVotesType_Kick; - } - else if (StrEqual(voteString, TF2CSGO_VOTE_STRING_CHANGELEVEL, false)) - { - voteType = NativeVotesType_ChgLevel; - } - else if (StrEqual(voteString, TF2CSGO_VOTE_STRING_NEXTLEVEL, false)) - { - voteType = NativeVotesType_NextLevel; - } - else if (StrEqual(voteString, TF2CSGO_VOTE_STRING_RESTART, false)) - { - voteType = NativeVotesType_Restart; - } - else if (StrEqual(voteString, TF2CSGO_VOTE_STRING_SCRAMBLE, false)) - { - voteType = NativeVotesType_ScrambleNow; - } - else if (StrEqual(voteString, TF2_VOTE_STRING_ETERNAWEEN, false)) - { - voteType = NativeVotesType_Eternaween; - } - else if (StrEqual(voteString, TF2_VOTE_STRING_AUTOBALANCE, false)) - { - if (g_Cvar_AutoBalance.BoolValue) - { - voteType = NativeVotesType_AutoBalanceOff; - } - else - { - voteType = NativeVotesType_AutoBalanceOn; - } - } - else if (StrEqual(voteString, TF2_VOTE_STRING_CLASSLIMIT, false)) - { - if (g_Cvar_ClassLimit.IntValue) - { - voteType = NativeVotesType_ClassLimitsOff; - } - else - { - voteType = NativeVotesType_ClassLimitsOn; - } - } - else if (StrEqual(voteString, TF2_VOTE_STRING_EXTEND, false)) - { - voteType = NativeVotesType_Extend; - } - else if (StrEqual(voteString, TF2_VOTE_STRING_CHANGEMISSION, false)) - { - voteType = NativeVotesType_ChgMission; - } - else if (StrEqual(voteString, TF2_VOTE_STRING_CHANGEMISSION, false)) - { - voteType = NativeVotesType_ChgMission; - } - - return voteType; -} - -static stock NativeVotesOverride TF2_VoteTypeToVoteOverride(NativeVotesType voteType) -{ - NativeVotesOverride overrideType = NativeVotesOverride_None; - - switch (voteType) - { - case NativeVotesType_Kick, NativeVotesType_KickCheating, NativeVotesType_KickIdle, NativeVotesType_KickScamming: - { - overrideType = NativeVotesOverride_Kick; - } - - case NativeVotesType_ChgLevel: - { - overrideType = NativeVotesOverride_ChgLevel; - } - - case NativeVotesType_NextLevel: - { - overrideType = NativeVotesOverride_NextLevel; - } - - case NativeVotesType_Restart: - { - overrideType = NativeVotesOverride_Restart; - } - - case NativeVotesType_ScrambleEnd, NativeVotesType_ScrambleNow: - { - overrideType = NativeVotesOverride_Scramble; - } - - case NativeVotesType_Eternaween: - { - overrideType = NativeVotesOverride_Eternaween; - } - - case NativeVotesType_AutoBalanceOn, NativeVotesType_AutoBalanceOff: - { - overrideType = NativeVotesOverride_AutoBalance; - } - - case NativeVotesType_ClassLimitsOn, NativeVotesType_ClassLimitsOff: - { - overrideType = NativeVotesOverride_ClassLimits; - } - - case NativeVotesType_Extend: - { - overrideType = NativeVotesOverride_Extend; - } - } - - return overrideType; -} - -static stock NativeVotesType TF2_VoteOverrideToVoteType(NativeVotesOverride overrideType) -{ - NativeVotesType voteType = NativeVotesType_None; - - switch (overrideType) - { - case NativeVotesOverride_Restart: - { - voteType = NativeVotesType_Restart; - } - - case NativeVotesOverride_Kick: - { - voteType = NativeVotesType_Kick; - } - - case NativeVotesOverride_ChgLevel: - { - voteType = NativeVotesType_ChgLevel; - } - - case NativeVotesOverride_NextLevel: - { - voteType = NativeVotesType_NextLevel; - } - - case NativeVotesOverride_Scramble: - { - voteType = NativeVotesType_ScrambleNow; - } - - case NativeVotesOverride_ChgMission: - { - voteType = NativeVotesType_ChgMission; - } - - case NativeVotesOverride_Eternaween: - { - voteType = NativeVotesType_Eternaween; - } - - case NativeVotesOverride_AutoBalance: - { - voteType = NativeVotesType_AutoBalanceOn; - } - - case NativeVotesOverride_ClassLimits: - { - voteType = NativeVotesType_ClassLimitsOn; - } - - case NativeVotesOverride_Extend: - { - voteType = NativeVotesType_Extend; - } - } - - return voteType; -} - -static stock NativeVotesOverride TF2_VoteStringToVoteOverride(const char[] voteString) -{ - NativeVotesOverride overrideType = NativeVotesOverride_None; - - if (StrEqual(voteString, TF2CSGO_VOTE_STRING_KICK, false)) - { - overrideType = NativeVotesOverride_Kick; - } - else if (StrEqual(voteString, TF2CSGO_VOTE_STRING_CHANGELEVEL, false)) - { - overrideType = NativeVotesOverride_ChgLevel; - } - else if (StrEqual(voteString, TF2CSGO_VOTE_STRING_NEXTLEVEL, false)) - { - overrideType = NativeVotesOverride_NextLevel; - } - else if (StrEqual(voteString, TF2CSGO_VOTE_STRING_RESTART, false)) - { - overrideType = NativeVotesOverride_Restart; - } - else if (StrEqual(voteString, TF2CSGO_VOTE_STRING_SCRAMBLE, false)) - { - overrideType = NativeVotesOverride_Scramble; - } - else if (StrEqual(voteString, TF2_VOTE_STRING_ETERNAWEEN, false)) - { - overrideType = NativeVotesOverride_Eternaween; - } - else if (StrEqual(voteString, TF2_VOTE_STRING_AUTOBALANCE, false)) - { - overrideType = NativeVotesOverride_AutoBalance; - } - else if (StrEqual(voteString, TF2_VOTE_STRING_CLASSLIMIT, false)) - { - overrideType = NativeVotesOverride_ClassLimits; - } - else if (StrEqual(voteString, TF2_VOTE_STRING_EXTEND, false)) - { - overrideType = NativeVotesOverride_Extend; - } - else if (StrEqual(voteString, TF2_VOTE_STRING_CHANGEMISSION, false)) - { - overrideType = NativeVotesOverride_ChgMission; - } - -#if defined LOG - LogMessage("Resolved \"%s\" to %d", voteString, overrideType); -#endif - - return overrideType; -} - -static stock bool TF2_OverrideTypeToVoteString(NativeVotesOverride overrideType, char[] voteString, int maxlength) -{ - bool valid = false; - switch(overrideType) - { - case NativeVotesOverride_Kick: - { - strcopy(voteString, maxlength, TF2CSGO_VOTE_STRING_KICK); - valid = true; - } - - case NativeVotesOverride_ChgLevel: - { - strcopy(voteString, maxlength, TF2CSGO_VOTE_STRING_CHANGELEVEL); - valid = true; - } - - case NativeVotesOverride_NextLevel: - { - strcopy(voteString, maxlength, TF2CSGO_VOTE_STRING_NEXTLEVEL); - valid = true; - } - - case NativeVotesOverride_Restart: - { - strcopy(voteString, maxlength, TF2CSGO_VOTE_STRING_RESTART); - valid = true; - } - - case NativeVotesOverride_Scramble: - { - strcopy(voteString, maxlength, TF2CSGO_VOTE_STRING_SCRAMBLE); - valid = true; - } - - case NativeVotesOverride_Eternaween: - { - strcopy(voteString, maxlength, TF2_VOTE_STRING_ETERNAWEEN); - valid = true; - } - - case NativeVotesOverride_AutoBalance: - { - strcopy(voteString, maxlength, TF2_VOTE_STRING_AUTOBALANCE); - valid = true; - } - - case NativeVotesOverride_ClassLimits: - { - strcopy(voteString, maxlength, TF2_VOTE_STRING_CLASSLIMIT); - valid = true; - } - - case NativeVotesOverride_Extend: - { - strcopy(voteString, maxlength, TF2_VOTE_STRING_EXTEND); - } - - case NativeVotesOverride_ChgMission: - { - strcopy(voteString, maxlength, TF2_VOTE_STRING_CHANGEMISSION); - } - } - - return valid; -} - -static stock bool TF2_OverrideTypeToTranslationString(NativeVotesOverride overrideType, char[] translationString, int maxlength) -{ - bool valid = false; - switch(overrideType) - { - case NativeVotesOverride_Kick: - { - strcopy(translationString, maxlength, TF2_VOTE_MENU_KICK); - valid = true; - } - - case NativeVotesOverride_ChgLevel: - { - strcopy(translationString, maxlength, TF2_VOTE_MENU_CHANGELEVEL); - valid = true; - } - - case NativeVotesOverride_NextLevel: - { - strcopy(translationString, maxlength, TF2_VOTE_MENU_NEXTLEVEL); - valid = true; - } - - case NativeVotesOverride_Restart: - { - strcopy(translationString, maxlength, TF2_VOTE_MENU_RESTART); - valid = true; - } - - case NativeVotesOverride_Scramble: - { - strcopy(translationString, maxlength, TF2_VOTE_MENU_SCRAMBLE); - valid = true; - } - - case NativeVotesOverride_Eternaween: - { - strcopy(translationString, maxlength, TF2_VOTE_MENU_ETERNAWEEN); - valid = true; - } - - case NativeVotesOverride_AutoBalance: - { - if (GetConVarBool(g_Cvar_AutoBalance)) - { - strcopy(translationString, maxlength, TF2_VOTE_MENU_AUTOBALANCE_OFF); - } - else - { - strcopy(translationString, maxlength, TF2_VOTE_MENU_AUTOBALANCE_ON); - } - valid = true; - } - - case NativeVotesOverride_ClassLimits: - { - if (GetConVarInt(g_Cvar_ClassLimit)) - { - strcopy(translationString, maxlength, TF2_VOTE_MENU_CLASSLIMIT_OFF); - } - else - { - strcopy(translationString, maxlength, TF2_VOTE_MENU_CLASSLIMIT_ON); - } - valid = true; - } - - case NativeVotesOverride_Extend: - { - strcopy(translationString, maxlength, TF2_VOTE_MENU_EXTEND); - } - } - - return valid; -} - -stock bool Game_IsVoteTypeCustom(NativeVotesType voteType) -{ - switch(voteType) - { - case NativeVotesType_Custom_YesNo, NativeVotesType_Custom_Mult: - { - return true; - } - } - - return false; -} - -stock bool Game_IsVoteTypeYesNo(NativeVotesType voteType) -{ - switch(voteType) - { - case NativeVotesType_Custom_Mult, NativeVotesType_NextLevelMult: - { - return false; - } - } - - return true; -} - +/** + * vim: set ts=4 : + * ============================================================================= + * NativeVotes + * NativeVotes is a voting API plugin for L4D, L4D2, TF2, and CS:GO. + * Based on the SourceMod voting API + * + * NativeVotes (C) 2011-2014 Ross Bemrose (Powerlord). All rights reserved. + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program 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 the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ +#if defined _nativevotes_game_included + #endinput +#endif + +#define _nativevotes_game_included + +#include + +#define L4DL4D2_COUNT 2 +#define TF2CSGO_COUNT 5 + +#define INVALID_ISSUE -1 + +//---------------------------------------------------------------------------- +// Translation strings + +//---------------------------------------------------------------------------- +// L4D/L4D2 + +#define L4DL4D2_VOTE_YES_STRING "Yes" +#define L4DL4D2_VOTE_NO_STRING "No" + +#define L4D_VOTE_KICK_START "#L4D_vote_kick_player" +#define L4D_VOTE_KICK_PASSED "#L4D_vote_passed_kick_player" + +// User vote to restart map. +#define L4D_VOTE_RESTART_START "#L4D_vote_restart_game" +#define L4D_VOTE_RESTART_PASSED "#L4D_vote_passed_restart_game" + +// User vote to change maps. +#define L4D_VOTE_CHANGECAMPAIGN_START "#L4D_vote_mission_change" +#define L4D_VOTE_CHANGECAMPAIGN_PASSED "#L4D_vote_passed_mission_change" +#define L4D_VOTE_CHANGELEVEL_START "#L4D_vote_chapter_change" +#define L4D_VOTE_CHANGELEVEL_PASSED "#L4D_vote_passed_chapter_change" + +// User vote to return to lobby. +#define L4D_VOTE_RETURNTOLOBBY_START "#L4D_vote_return_to_lobby" +#define L4D_VOTE_RETURNTOLOBBY_PASSED "#L4D_vote_passed_return_to_lobby" + +// User vote to change difficulty. +#define L4D_VOTE_CHANGEDIFFICULTY_START "#L4D_vote_change_difficulty" +#define L4D_VOTE_CHANGEDIFFICULTY_PASSED "#L4D_vote_passed_change_difficulty" + +// While not a vote string, it works just as well. +#define L4D_VOTE_CUSTOM "#L4D_TargetID_Player" + +//---------------------------------------------------------------------------- +// L4D2 + +// User vote to change alltalk. +#define L4D2_VOTE_ALLTALK_START "#L4D_vote_alltalk_change" +#define L4D2_VOTE_ALLTALK_PASSED "#L4D_vote_passed_alltalk_change" +#define L4D2_VOTE_ALLTALK_ENABLE "#L4D_vote_alltalk_enable" +#define L4D2_VOTE_ALLTALK_DISABLE "#L4D_vote_alltalk_disable" + +//---------------------------------------------------------------------------- +// TF2/CSGO +#define TF2CSGO_VOTE_PREFIX "option" + +#define TF2CSGO_VOTE_STRING_KICK "Kick" +#define TF2CSGO_VOTE_STRING_RESTART "RestartGame" +#define TF2CSGO_VOTE_STRING_CHANGELEVEL "ChangeLevel" +#define TF2CSGO_VOTE_STRING_NEXTLEVEL "NextLevel" +#define TF2CSGO_VOTE_STRING_SCRAMBLE "ScrambleTeams" + +//---------------------------------------------------------------------------- +// TF2 + +#define TF2_VOTE_STRING_CHANGEMISSION "ChangeMission" + +// This one doesn't actually exist in the normal vote menu, but it has a translation string for it. +// Thus, this is the name we will use for it internally +#define TF2_VOTE_STRING_ETERNAWEEN "Eternaween" + +// These are toggles, but the on and off versions are identical +#define TF2_VOTE_STRING_AUTOBALANCE "TeamAutoBalance" +#define TF2_VOTE_STRING_CLASSLIMIT "ClassLimits" + +// New as of 2015-09-24 +#define TF2_VOTE_STRING_EXTEND "ExtendLevel" + +// Menu items for votes +#define TF2_VOTE_MENU_RESTART "#Vote_RestartGame" +#define TF2_VOTE_MENU_KICK "#Vote_Kick" +#define TF2_VOTE_MENU_CHANGELEVEL "#Vote_ChangeLevel" +#define TF2_VOTE_MENU_NEXTLEVEL "#Vote_NextLevel" +#define TF2_VOTE_MENU_SCRAMBLE "#Vote_ScrambleTeams" +#define TF2_VOTE_MENU_CHANGEMISSION "#Vote_ChangeMission" +#define TF2_VOTE_MENU_ETERNAWEEN "#Vote_Eternaween" +#define TF2_VOTE_MENU_AUTOBALANCE_ON "#Vote_TeamAutoBalance_Enable" +#define TF2_VOTE_MENU_AUTOBALANCE_OFF "#Vote_TeamAutoBalance_Disable" +#define TF2_VOTE_MENU_CLASSLIMIT_ON "#Vote_ClassLimit_Enable" +#define TF2_VOTE_MENU_CLASSLIMIT_OFF "#Vote_ClassLimit_Disable" +#define TF2_VOTE_MENU_EXTEND "#Vote_ExtendLevel" + +// User vote to kick user. +#define TF2_VOTE_KICK_IDLE_START "#TF_vote_kick_player_idle" +#define TF2_VOTE_KICK_SCAMMING_START "#TF_vote_kick_player_scamming" +#define TF2_VOTE_KICK_CHEATING_START "#TF_vote_kick_player_cheating" +#define TF2_VOTE_KICK_START "#TF_vote_kick_player_other" +#define TF2_VOTE_KICK_PASSED "#TF_vote_passed_kick_player" + +// User vote to restart map. +#define TF2_VOTE_RESTART_START "#TF_vote_restart_game" +#define TF2_VOTE_RESTART_PASSED "#TF_vote_passed_restart_game" + +// User vote to change maps. +#define TF2_VOTE_CHANGELEVEL_START "#TF_vote_changelevel" +#define TF2_VOTE_CHANGELEVEL_PASSED "#TF_vote_passed_changelevel" + +// User vote to change next level. +#define TF2_VOTE_NEXTLEVEL_SINGLE_START "#TF_vote_nextlevel" +#define TF2_VOTE_NEXTLEVEL_MULTIPLE_START "#TF_vote_nextlevel_choices" // Started by server +#define TF2_VOTE_NEXTLEVEL_EXTEND_PASSED "#TF_vote_passed_nextlevel_extend" // Also used for extend vote +#define TF2_VOTE_NEXTLEVEL_PASSED "#TF_vote_passed_nextlevel" + +// User vote to scramble teams. +// Can be immediate or end of round. +#define TF2_VOTE_SCRAMBLE_IMMEDIATE_START "#TF_vote_scramble_teams" +#define TF2_VOTE_SCRAMBLE_ROUNDEND_START "#TF_vote_should_scramble_round" +#define TF2_VOTE_SCRAMBLE_PASSED "#TF_vote_passed_scramble_teams" + +// User vote to change MvM mission +#define TF2_VOTE_CHANGEMISSION_START "#TF_vote_changechallenge" +#define TF2_VOTE_CHANGEMISSION_PASSED "#TF_vote_passed_changechallenge" + +// User vote for eternaween +#define TF2_VOTE_ETERNAWEEN_START "#TF_vote_eternaween" +#define TF2_VOTE_ETERNAWEEN_PASSED "#TF_vote_passed_eternaween" + +// User vote to start round +#define TF2_VOTE_ROUND_START "#TF_vote_td_start_round" +#define TF2_VOTE_ROUND_PASSED "#TF_vote_passed_td_start_round" + +// User vote to enable autobalance +#define TF2_VOTE_AUTOBALANCE_ENABLE_START "#TF_vote_autobalance_enable" +#define TF2_VOTE_AUTOBALANCE_ENABLE_PASSED "#TF_vote_passed_autobalance_enable" + +// User vote to disable autobalance +#define TF2_VOTE_AUTOBALANCE_DISABLE_START "#TF_vote_autobalance_disable" +#define TF2_VOTE_AUTOBALANCE_DISABLE_PASSED "#TF_vote_passed_autobalance_disable" + +// User vote to enable classlimits +#define TF2_VOTE_CLASSLIMITS_ENABLE_START "#TF_vote_classlimits_enable" +#define TF2_VOTE_CLASSLIMITS_ENABLE_PASSED "#TF_vote_passed_classlimits_enable" + +// User vote to disable classlimits +#define TF2_VOTE_CLASSLIMITS_DISABLE_START "#TF_vote_classlimits_disable" +#define TF2_VOTE_CLASSLIMITS_DISABLE_PASSED "#TF_vote_passed_classlimits_disable" + +// Use vote to extend map. +#define TF2_VOTE_EXTEND_START "#TF_vote_extendlevel" + +// While not a vote string, it works just as well. +#define TF2_VOTE_CUSTOM "#TF_playerid_noteam" + +// TF2 (and SDK2013?) VoteFail / CallVoteFail reasons +enum +{ + VOTE_FAILED_GENERIC, + VOTE_FAILED_TRANSITIONING_PLAYERS, + VOTE_FAILED_RATE_EXCEEDED, + VOTE_FAILED_YES_MUST_EXCEED_NO, + VOTE_FAILED_QUORUM_FAILURE, + VOTE_FAILED_ISSUE_DISABLED, + VOTE_FAILED_MAP_NOT_FOUND, + VOTE_FAILED_MAP_NAME_REQUIRED, + VOTE_FAILED_FAILED_RECENTLY, + VOTE_FAILED_TEAM_CANT_CALL, + VOTE_FAILED_WAITINGFORPLAYERS, + VOTE_FAILED_PLAYERNOTFOUND, + VOTE_FAILED_CANNOT_KICK_ADMIN, + VOTE_FAILED_SCRAMBLE_IN_PROGRESS, + VOTE_FAILED_SPECTATOR, + VOTE_FAILED_NEXTLEVEL_SET, + VOTE_FAILED_MAP_NOT_VALID, + VOTE_FAILED_CANNOT_KICK_FOR_TIME, + VOTE_FAILED_CANNOT_KICK_DURING_ROUND, + VOTE_FAILED_MODIFICATION_ALREADY_ACTIVE, +} + +//---------------------------------------------------------------------------- +// TF2 Classified + +#define TF2CLASSIFIED_VOTE_STRING_CHANGE_CIVILIAN "ChangeCivilian" + +#define TF2CLASSIFIED_VOTE_MENU_CHANGE_CIVILIAN "#Vote_ChangeCivilian" + +#define TF2CLASSIFIED_VOTE_CHANGE_CIVILIAN_START "#TF_vote_change_civilian" +#define TF2CLASSIFIED_VOTE_CHANGE_CIVILIAN_CALLER "#TF_vote_change_civilian_caller" +#define TF2CLASSIFIED_VOTE_CHANGE_CIVILIAN_PASSED "#TF_vote_passed_change_civilian" + +//---------------------------------------------------------------------------- +// Fortress Connected + +#define FC_VOTE_STRING_CHANGE_DIFFICULTY "ChangeDifficulty" + +#define FC_VOTE_MENU_CHANGE_DIFFICULTY "#Vote_changedifficulty" +#define FC_VOTE_PASSED_CHANGE_DIFFICULTY "#Vote_passed_changedifficulty" + +#define FC_VOTE_STRING_CHANGE_GAMEMODE "ChangeGamemode" + +#define FC_VOTE_MENU_CHANGE_GAMEMODE "#Vote_changegamemode" +#define FC_VOTE_PASSED_CHANGE_GAMEMODE "#Vote_passed_changegamemode" + +//---------------------------------------------------------------------------- +// CSGO +// User vote to kick user. +#define CSGO_VOTE_KICK_IDLE_START "#SFUI_vote_kick_player_idle" +#define CSGO_VOTE_KICK_SCAMMING_START "#SFUI_vote_kick_player_scamming" +#define CSGO_VOTE_KICK_CHEATING_START "#SFUI_vote_kick_player_cheating" +#define CSGO_VOTE_KICK_START "#SFUI_vote_kick_player_other" +#define CSGO_VOTE_KICK_OTHERTEAM "#SFUI_otherteam_vote_kick_player" +#define CSGO_VOTE_KICK_PASSED "#SFUI_vote_passed_kick_player" + +// User vote to restart map. +#define CSGO_VOTE_RESTART_START "#SFUI_vote_restart_game" +#define CSGO_VOTE_RESTART_PASSED "#SFUI_vote_passed_restart_game" + +// User vote to change maps. +#define CSGO_VOTE_CHANGELEVEL_START "#SFUI_vote_changelevel" +#define CSGO_VOTE_CHANGELEVEL_PASSED "#SFUI_vote_passed_changelevel" + +// User vote to change next level. +#define CSGO_VOTE_NEXTLEVEL_SINGLE_START "#SFUI_vote_nextlevel" +#define CSGO_VOTE_NEXTLEVEL_MULTIPLE_START "#SFUI_vote_nextlevel_choices" // Started by server +#define CSGO_VOTE_NEXTLEVEL_EXTEND_PASSED "#SFUI_vote_passed_nextlevel_extend" +#define CSGO_VOTE_NEXTLEVEL_PASSED "#SFUI_vote_passed_nextlevel" + +// User vote to scramble teams. +#define CSGO_VOTE_SCRAMBLE_START "#SFUI_vote_scramble_teams" +#define CSGO_VOTE_SCRAMBLE_PASSED "#SFUI_vote_passed_scramble_teams" + +// User vote to swap teams. +#define CSGO_VOTE_SWAPTEAMS_START "#SFUI_vote_swap_teams" +#define CSGO_VOTE_SWAPTEAMS_PASSED "#SFUI_vote_passed_swap_teams" + +// User vote to surrender +#define CSGO_VOTE_SURRENDER_START "#SFUI_vote_surrender" +#define CSGO_VOTE_SURRENDER_OTHERTEAM "#SFUI_otherteam_vote_continue_or_surrender" +#define CSGO_VOTE_SURRENDER_PASSED "#SFUI_vote_passed_surrender" + +// User vote to rematch +#define CSGO_VOTE_REMATCH_START "#SFUI_vote_rematch" +#define CSGO_VOTE_REMATCH_PASSED "#SFUI_vote_passed_rematch" + +// User vote to continue match with bots +#define CSGO_VOTE_CONTINUE_START "#SFUI_vote_continue" +#define CSGO_VOTE_CONTINUE_OTHERTEAM "#SFUI_otherteam_vote_continue_or_surrender" +#define CSGO_VOTE_CONTINUE_PASSED "#SFUI_vote_passed_continue" + +// User vote to pause game +#define CSGO_VOTE_PAUSE_START "#SFUI_Vote_pause_match" +#define CSGO_VOTE_PAUSE_PASSED "#SFUI_vote_passed_pause_match" + +// User vote to unpause game +#define CSGO_VOTE_UNPAUSE_START "#SFUI_Vote_unpause_match" +#define CSGO_VOTE_UNPAUSE_PASSED "#SFUI_vote_passed_unpause_match" + +// User vote to load backups +#define CSGO_VOTE_LOADBACKUP_START "#SFUI_Vote_loadbackup" +#define CSGO_VOTE_LOADBACKUP_PASSED "#SFUI_vote_passed_loadbackup" + +// User vote to start match +#define CSGO_VOTE_READY_START "#SFUI_Vote_ready_for_match" +#define CSGO_VOTE_READY_PASSED "#SFUI_vote_passed_ready_for_match" + +// User vote to delay match start +#define CSGO_VOTE_NOTREADY_START "#SFUI_Vote_not_ready_for_match" +#define CSGO_VOTE_NOTREADY_PASSED "#SFUI_vote_passed_not_ready_for_match" + +// User vote to start round +#define CSGO_VOTE_ROUND_START "#SFUI_vote_td_start_round" +#define CSGO_VOTE_ROUND_PASSED "#SFUI_vote_passed_td_start_round" + +#define CSGO_VOTE_UNIMPLEMENTED_OTHERTEAM "#SFUI_otherteam_vote_unimplemented" + +// While not a vote string, it works just as well. +#define CSGO_VOTE_CUSTOM "#SFUI_Scoreboard_NormalPlayer" + +// CSGO VoteFail / CallVoteFail reasons +enum +{ + CSGO_VOTE_FAILED_GENERIC, + CSGO_VOTE_FAILED_TRANSITIONING_PLAYERS, + CSGO_VOTE_FAILED_RATE_EXCEEDED, + CSGO_VOTE_FAILED_YES_MUST_EXCEED_NO, + CSGO_VOTE_FAILED_QUORUM_FAILURE, + CSGO_VOTE_FAILED_ISSUE_DISABLED, + CSGO_VOTE_FAILED_MAP_NOT_FOUND, + CSGO_VOTE_FAILED_MAP_NAME_REQUIRED, + CSGO_VOTE_FAILED_FAILED_RECENTLY, + CSGO_VOTE_FAILED_FAILED_RECENTLY_KICK, + CSGO_VOTE_FAILED_FAILED_RECENTLY_MAP, + CSGO_VOTE_FAILED_FAILED_RECENTLY_SWAP, + CSGO_VOTE_FAILED_FAILED_RECENTLY_SCRAMBLE, + CSGO_VOTE_FAILED_FAILED_RECENTLY_RESTART, + CSGO_VOTE_FAILED_TEAM_CANT_CALL, + CSGO_VOTE_FAILED_WARMUP, + CSGO_VOTE_FAILED_PLAYERNOTFOUND, + CSGO_VOTE_FAILED_CANNOT_KICK_ADMIN, + CSGO_VOTE_FAILED_SCRAMBLE_IN_PROGRESS, + CSGO_VOTE_FAILED_SWAP_IN_PROGRESS, + CSGO_VOTE_FAILED_SPECTATOR, + CSGO_VOTE_FAILED_NEXTLEVEL_SET, + CSGO_VOTE_FAILED_UNKNOWN1, + CSGO_VOTE_FAILED_SURRENDER_ABANDON, + CSGO_VOTE_FAILED_UNKNOWN2, + CSGO_VOTE_FAILED_PAUSED, + CSGO_VOTE_FAILED_NOT_PAUSED, + CSGO_VOTE_FAILED_NOT_WARMUP, + CSGO_VOTE_FAILED_MIN_PLAYERS, + CSGO_VOTE_FAILED_ROUND_ENDED, +} + + +//---------------------------------------------------------------------------- +// Generic functions +// + +// This is deprecated in NativeVotes 1.1 +enum +{ + ValveVote_Kick = 0, + ValveVote_Restart = 1, + ValveVote_ChangeLevel = 2, + ValveVote_NextLevel = 3, + ValveVote_Scramble = 4, + ValveVote_SwapTeams = 5, +} + +static int g_VoteController = -1; +static bool g_bUserBuf = false; + +static ConVar g_Cvar_Votes_Enabled; +static ConVar g_Cvar_VoteKick_Enabled; +static ConVar g_Cvar_MvM_VoteKick_Enabled; +static ConVar g_Cvar_VoteNextLevel_Enabled; +static ConVar g_Cvar_VoteChangeLevel_Enabled; +static ConVar g_Cvar_MvM_VoteChangeLevel_Enabled; +static ConVar g_Cvar_VoteRestart_Enabled; +static ConVar g_Cvar_MvM_VoteRestart_Enabled; +static ConVar g_Cvar_VoteScramble_Enabled; +static ConVar g_Cvar_MvM_VoteChallenge_Enabled; +static ConVar g_Cvar_VoteAutoBalance_Enabled; +static ConVar g_Cvar_VoteClassLimits_Enabled; +static ConVar g_Cvar_MvM_VoteClassLimits_Enabled; +static ConVar g_Cvar_VoteExtend_Enabled; + +static ConVar g_Cvar_ClassLimit; +static ConVar g_Cvar_AutoBalance; + +static ConVar g_Cvar_HideDisabledIssues; + +// TF2 Classified +static ConVar g_Cvar_ChangeCivilian_Enabled; + +// Fortress Connected +static ConVar g_Cvar_ChangeDifficulty_Enabled; +static ConVar g_Cvar_ChangeGamemode_Enabled; + +/** + * TODO(UPDATE): For now we only support one vote from NativeVotes at a time. + * + * Ideally we peek and poke at the game's `s_nVoteIdx` and increment it accordingly, but we also + * have to store an internal list of active votes. + */ +static int s_nNativeVoteIdx = 0; + +bool Game_IsGameSupported(char[] engineName="", int maxlength=0) +{ + g_EngineVersion = GetEngineVersion(); + + g_bUserBuf = GetFeatureStatus(FeatureType_Native, "GetUserMessageType") == FeatureStatus_Available && GetUserMessageType() == UM_Protobuf; + + //LogMessage("Detected Engine version: %d", g_EngineVersion); + if (maxlength > 0) + { + GetEngineVersionName(g_EngineVersion, engineName, maxlength); + } + + char gameDir[128]; + GetGameFolderName(gameDir, sizeof(gameDir)); + if (!StrEqual(gameDir, "tf")) + { + if (g_EngineVersion == Engine_SDK2013 && FileExists("resource/tf.ttf")) + { + g_EngineVersion = Engine_TF2; + } + else + { + KeyValues kv = new KeyValues("GameInfo"); + if (kv.ImportFromFile("gameinfo.txt") && kv.GetNum("DependsOnAppID") == 440) + { + g_EngineVersion = Engine_TF2; + } + delete kv; + } + } + + switch (g_EngineVersion) + { + case Engine_Left4Dead, Engine_Left4Dead2, Engine_CSGO, Engine_TF2: + { + return true; + } + } + + return false; +} + +void Game_InitializeCvars() +{ + switch (g_EngineVersion) + { + case Engine_Left4Dead, Engine_Left4Dead2, Engine_CSGO: + { + g_Cvar_Votes_Enabled = FindConVar("sv_allow_votes"); + } + case Engine_TF2: + { + g_Cvar_Votes_Enabled = FindConVar("sv_allow_votes"); + g_Cvar_VoteKick_Enabled = FindConVar("sv_vote_issue_kick_allowed"); + g_Cvar_MvM_VoteKick_Enabled = FindConVar("sv_vote_issue_kick_allowed_mvm"); + g_Cvar_VoteNextLevel_Enabled = FindConVar("sv_vote_issue_nextlevel_allowed"); + g_Cvar_VoteChangeLevel_Enabled = FindConVar("sv_vote_issue_changelevel_allowed"); + g_Cvar_MvM_VoteChangeLevel_Enabled = FindConVar("sv_vote_issue_changelevel_allowed_mvm"); + g_Cvar_VoteRestart_Enabled = FindConVar("sv_vote_issue_restart_game_allowed"); + g_Cvar_MvM_VoteRestart_Enabled = FindConVar("sv_vote_issue_restart_game_allowed_mvm"); + g_Cvar_VoteScramble_Enabled = FindConVar("sv_vote_issue_scramble_teams_allowed"); + g_Cvar_MvM_VoteChallenge_Enabled = FindConVar("sv_vote_issue_mvm_challenge_allowed"); + g_Cvar_VoteAutoBalance_Enabled = FindConVar("sv_vote_issue_autobalance_allowed"); + g_Cvar_VoteClassLimits_Enabled = FindConVar("sv_vote_issue_classlimits_allowed"); + g_Cvar_MvM_VoteClassLimits_Enabled = FindConVar("sv_vote_issue_classlimits_allowed_mvm"); + g_Cvar_VoteExtend_Enabled = FindConVar("sv_vote_issue_extendlevel_allowed"); + + g_Cvar_ClassLimit = FindConVar("tf_classlimit"); + g_Cvar_AutoBalance = FindConVar("mp_autoteambalance"); + + g_Cvar_HideDisabledIssues = FindConVar("sv_vote_ui_hide_disabled_issues"); + + // Team Fortress 2 Classified + g_Cvar_ChangeCivilian_Enabled = FindConVar("tf2c_vote_issue_change_civilian_allowed"); + + // Fortress Connected + g_Cvar_ChangeDifficulty_Enabled = FindConVar("sv_vote_issue_changedifficulty_allowed"); + g_Cvar_ChangeGamemode_Enabled = FindConVar("sv_vote_issue_changegamemode_allowed"); + } + } +} + +NativeVotesKickType Game_GetKickType(const char[] param1, int &target) +{ + NativeVotesKickType kickType; + + switch(g_EngineVersion) + { + case Engine_Left4Dead, Engine_Left4Dead2, Engine_CSGO: + { + target = StringToInt(param1); + kickType = NativeVotesKickType_Generic; + } + case Engine_TF2: + { + char params[2][20]; + ExplodeString(param1, " ", params, sizeof(params), sizeof(params[])); + + target = StringToInt(params[0]); + + if (StrEqual(params[1], "cheating", false)) + { + kickType = NativeVotesKickType_Cheating; + } + else if (StrEqual(params[1], "idle", false)) + { + kickType = NativeVotesKickType_Idle; + } + else if (StrEqual(params[1], "scamming", false)) + { + kickType = NativeVotesKickType_Scamming; + } + else + { + kickType = NativeVotesKickType_Generic; + } + } + } + return kickType; +} + +bool CheckVoteController() +{ + int entity = INVALID_ENT_REFERENCE; + if (g_VoteController != -1) + { + entity = EntRefToEntIndex(g_VoteController); + } + + if (entity == INVALID_ENT_REFERENCE) + { + entity = FindEntityByClassname(-1, "vote_controller"); + if (entity == -1) + { + //LogError("Could not find Vote Controller."); + return false; + } + + g_VoteController = EntIndexToEntRef(entity); + } + return true; +} + +// All logic for choosing a game-specific function should happen here. +// There should be one per function in the game shared and specific sections +int Game_ParseVote(const char[] option) +{ + int item = NATIVEVOTES_VOTE_INVALID; + +#if defined LOG + LogMessage("Parsing vote option %s", option); +#endif + + switch(g_EngineVersion) + { + case Engine_Left4Dead, Engine_Left4Dead2: + { + item = L4DL4D2_ParseVote(option); + } + case Engine_CSGO: + { + item = CSGO_ParseVote(option); + } + case Engine_TF2: + { + item = TF2_ParseVote(option); + } + } + + return item; + +} + +int Game_GetMaxItems() +{ + switch (g_EngineVersion) + { + case Engine_Left4Dead, Engine_Left4Dead2: + { + return L4DL4D2_COUNT; + } + case Engine_CSGO, Engine_TF2: + { + return TF2CSGO_COUNT; + } + } + + return 0; // Here to prevent warnings +} + +bool Game_CheckVoteType(NativeVotesType type) +{ + bool returnVal = false; + + switch(g_EngineVersion) + { + case Engine_Left4Dead: + { + returnVal = L4D_CheckVoteType(type); + } + case Engine_Left4Dead2: + { + returnVal = L4D2_CheckVoteType(type); + } + case Engine_CSGO: + { + returnVal = CSGO_CheckVoteType(type); + } + case Engine_TF2: + { + returnVal = TF2_CheckVoteType(type); + } + } + + return returnVal; +} + +bool Game_CheckVotePassType(NativeVotesPassType type) +{ + bool returnVal = false; + + switch(g_EngineVersion) + { + case Engine_Left4Dead: + { + returnVal = L4D_CheckVotePassType(type); + } + case Engine_Left4Dead2: + { + returnVal = L4D2_CheckVotePassType(type); + } + case Engine_CSGO: + { + returnVal = CSGO_CheckVotePassType(type); + } + case Engine_TF2: + { + returnVal = TF2_CheckVotePassType(type); + } + } + + return returnVal; +} + +bool Game_DisplayVoteToOne(NativeVote vote, int client, float end_time = 0.0) +{ + if (g_bCancelled) + { + return false; + } + + int clients[1]; + clients[0] = client; + + return Game_DisplayVote(vote, clients, 1, end_time); +} + +bool Game_DisplayVote(NativeVote vote, int[] clients, int num_clients, float end_time = 0.0) +{ + if (g_bCancelled) + { + return false; + } + + switch(g_EngineVersion) + { + case Engine_Left4Dead: + { + L4D_DisplayVote(vote, num_clients); + } + case Engine_Left4Dead2: + { + L4D2_DisplayVote(vote, clients, num_clients); + } + case Engine_CSGO, Engine_TF2: + { + TF2CSGO_DisplayVote(vote, clients, num_clients, end_time); + } + } + + +#if defined LOG + char details[MAX_VOTE_DETAILS_LENGTH]; + char translation[TRANSLATION_LENGTH]; + + NativeVotesType voteType = Data_GetType(vote); + Data_GetDetails(vote, details, sizeof(details)); + Game_VoteTypeToTranslation(voteType, translation, sizeof(translation)); + + LogMessage("Displaying vote: type: %d, translation: \"%s\", details: \"%s\"", voteType, translation, details); +#endif + + return true; +} + +void Game_DisplayVoteFail(NativeVote vote, NativeVotesFailType reason) +{ + int team = Data_GetTeam(vote); + + int total = 0; + int[] players = new int[MaxClients]; + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsClientInGame(i) || IsFakeClient(i)) + continue; + players[total++] = i; + } + + Game_DisplayRawVoteFail(players, total, reason, team); +} + +void Game_DisplayRawVoteFail(int[] clients, int numClients, NativeVotesFailType reason, int team) +{ + switch(g_EngineVersion) + { + case Engine_Left4Dead: + { + L4D_VoteFail(team); + } + case Engine_Left4Dead2: + { + L4D2_VoteFail(clients, numClients, team); + } + case Engine_CSGO: + { + int reasonType = VoteFailTypeToInt(reason); + CSGO_VoteFail(clients, numClients, reasonType, team); + } + case Engine_TF2: + { + int reasonType = VoteFailTypeToInt(reason); + TF2_VoteFail(clients, numClients, reasonType, team); + } + } + +#if defined LOG + LogMessage("Vote Failed to %d client(s): \"%d\"", numClients, reason); +#endif +} + +void Game_DisplayVotePass(NativeVote vote, const char[] details="", int client=0) +{ + NativeVotesPassType passType = VoteTypeToVotePass(Data_GetType(vote)); + + Game_DisplayVotePassEx(vote, passType, details, client); +} + +void Game_DisplayVotePassEx(NativeVote vote, NativeVotesPassType passType, const char[] details="", int client=0) +{ + int team = Data_GetTeam(vote); + + Game_DisplayRawVotePass(passType, team, client, details); +} + +void Game_DisplayRawVotePass(NativeVotesPassType passType, int team, int client=0, const char[] details="") +{ + char translation[TRANSLATION_LENGTH]; + + switch (g_EngineVersion) + { + case Engine_Left4Dead: + { + if (client <= 0) + { + L4DL4D2_VotePassToTranslation(passType, translation, sizeof(translation)); + L4D_VotePass(translation, details, team); + } + } + case Engine_Left4Dead2: + { + L4DL4D2_VotePassToTranslation(passType, translation, sizeof(translation)); + + switch (passType) + { + case NativeVotesPass_AlltalkOn: + { + L4D2_VotePass(translation, L4D2_VOTE_ALLTALK_ENABLE, team, client); + } + case NativeVotesPass_AlltalkOff: + { + L4D2_VotePass(translation, L4D2_VOTE_ALLTALK_DISABLE, team, client); + } + default: + { + L4D2_VotePass(translation, details, team, client); + } + } + } + case Engine_CSGO: + { + CSGO_VotePassToTranslation(passType, translation, sizeof(translation)); + CSGO_VotePass(translation, details, team, client); + } + case Engine_TF2: + { + TF2_VotePassToTranslation(passType, translation, sizeof(translation)); + TF2_VotePass(translation, details, team, client); + } + } + +#if defined LOG + if (client != 0) + LogMessage("Vote Passed: \"%s\", \"%s\"", translation, details); +#endif +} + +void Game_DisplayVotePassCustom(NativeVote vote, const char[] translation, int client) +{ + int team = Data_GetTeam(vote); + Game_DisplayRawVotePassCustom(translation, team, client); +} + +void Game_DisplayRawVotePassCustom(const char[] translation, int team, int client) +{ + switch (g_EngineVersion) + { + case Engine_Left4Dead: + { + ThrowNativeError(SP_ERROR_NATIVE, "NativeVotes_DisplayPassCustom is not supported on L4D"); + } + case Engine_Left4Dead2: + { + L4D2_VotePass(L4D_VOTE_CUSTOM, translation, team, client); + } + case Engine_CSGO: + { + CSGO_VotePass(CSGO_VOTE_CUSTOM, translation, team, client); + } + case Engine_TF2: + { + TF2_VotePass(TF2_VOTE_CUSTOM, translation, team, client); + } + } + +#if defined LOG + if (client != 0) + LogMessage("Vote Passed Custom: \"%s\"", translation); +#endif +} + +void Game_DisplayCallVoteFail(int client, NativeVotesCallFailType reason, int time) +{ + + int reasonType = VoteCallFailTypeToInt(reason); + + switch (g_EngineVersion) + { + case Engine_Left4Dead, Engine_Left4Dead2: + { + L4DL4D2_CallVoteFail(client, reasonType); + } + case Engine_CSGO: + { + CSGO_CallVoteFail(client, reasonType, time); + } + case Engine_TF2: + { + TF2_CallVoteFail(client, reasonType, time); + } + } + +#if defined LOG + LogMessage("Call vote failed: client: %N, reason: %d, time: %d", client, reason, time); +#endif +} + +void Game_ClientSelectedItem(NativeVote vote, int client, int item) +{ +#if defined LOG + LogMessage("Client %N selected item %d", client, item); +#endif + switch(g_EngineVersion) + { + case Engine_Left4Dead, Engine_Left4Dead2: + { + L4DL4D2_ClientSelectedItem(client, item); + } + case Engine_CSGO: + { + CSGO_ClientSelectedItem(vote, client, item); + } + case Engine_TF2: + { + TF2_ClientSelectedItem(vote, client, item); + } + } +} + +void Game_UpdateVoteCounts(ArrayList hVoteCounts, int totalClients) +{ + switch(g_EngineVersion) + { + case Engine_Left4Dead, Engine_Left4Dead2: + { + L4DL4D2_UpdateVoteCounts(hVoteCounts, totalClients); + } + case Engine_CSGO, Engine_TF2: + { + TF2CSGO_UpdateVoteCounts(hVoteCounts); + } + } +} + +void Game_DisplayVoteSetup(int client, ArrayList hVoteTypes) +{ + switch (g_EngineVersion) + { + case Engine_TF2: + { + TF2_DisplayVoteSetup(client, hVoteTypes); + } + } +} + +// stock because at the moment it's only used in logging code which isn't always compiled. +stock void Game_VoteTypeToTranslation(NativeVotesType voteType, char[] translation, int maxlength) +{ + switch(g_EngineVersion) + { + case Engine_Left4Dead, Engine_Left4Dead2: + { + L4DL4D2_VoteTypeToTranslation(voteType, translation, maxlength); + } + case Engine_CSGO: + { + CSGO_VoteTypeToTranslation(voteType, translation, maxlength); + } + case Engine_TF2: + { + TF2_VoteTypeToTranslation(voteType, translation, maxlength); + } + } +} + +stock void Game_UpdateClientCount(int num_clients) +{ + switch(g_EngineVersion) + { + case Engine_Left4Dead, Engine_Left4Dead2: + { + L4DL4D2_UpdateClientCount(num_clients); + } + case Engine_CSGO, Engine_TF2: + { + TF2CSGO_UpdateClientCount(num_clients); + } + } +} + +public Action Game_ResetVote(Handle timer) +{ + g_hVoteTimer = null; + + switch(g_EngineVersion) + { + case Engine_Left4Dead, Engine_Left4Dead2: + { + L4DL4D2_ResetVote(); + } + case Engine_CSGO, Engine_TF2: + { + TF2CSGO_ResetVote(); + } + } + return Plugin_Continue; +} + +void Game_VoteYes(int client) +{ + switch (g_EngineVersion) + { + case Engine_Left4Dead, Engine_Left4Dead2: + { + FakeClientCommand(client, "Vote Yes"); + } + case Engine_CSGO, Engine_TF2: + { + FakeClientCommand(client, "vote option1"); + } + } +} + +void Game_VoteNo(int client) +{ + switch (g_EngineVersion) + { + case Engine_Left4Dead, Engine_Left4Dead2: + { + FakeClientCommand(client, "Vote No"); + } + case Engine_CSGO, Engine_TF2: + { + FakeClientCommand(client, "vote option2"); + } + } +} + +bool Game_IsVoteInProgress() +{ + switch (g_EngineVersion) + { + case Engine_Left4Dead, Engine_Left4Dead2: + { + return L4DL4D2_IsVoteInProgress(); + } + case Engine_CSGO, Engine_TF2: + { + return TF2CSGO_IsVoteInProgress(); + } + } + + return false; +} + +bool Game_AreVoteCommandsSupported() +{ + switch (g_EngineVersion) + { + case Engine_TF2: + { + return true; + } + } + + return false; +} + +stock bool Game_VoteTypeToVoteString(NativeVotesType voteType, char[] voteString, int maxlength) +{ + switch (g_EngineVersion) + { + case Engine_TF2: + { + return TF2_VoteTypeToVoteString(voteType, voteString, maxlength); + } + } + + return false; +} + +stock NativeVotesType Game_VoteStringToVoteType(char[] voteString) +{ + switch (g_EngineVersion) + { + case Engine_TF2: + { + return TF2_VoteStringToVoteType(voteString); + } + } + + return NativeVotesType_None; +} + +stock NativeVotesOverride Game_VoteTypeToVoteOverride(NativeVotesType voteType) +{ + switch (g_EngineVersion) + { + case Engine_TF2: + { + return TF2_VoteTypeToVoteOverride(voteType); + } + } + + return NativeVotesOverride_None; +} + +stock NativeVotesType Game_VoteOverrideToVoteType(NativeVotesOverride overrideType) +{ + switch (g_EngineVersion) + { + case Engine_TF2: + { + return TF2_VoteOverrideToVoteType(overrideType); + } + } + + return NativeVotesType_None; +} + +stock NativeVotesOverride Game_VoteStringToVoteOverride(const char[] voteString) +{ + switch (g_EngineVersion) + { + case Engine_TF2: + { + return TF2_VoteStringToVoteOverride(voteString); + } + } + + return NativeVotesOverride_None; +} + +stock bool Game_OverrideTypeToVoteString(NativeVotesOverride overrideType, char[] voteString, int maxlength) +{ + switch (g_EngineVersion) + { + case Engine_TF2: + { + return TF2_OverrideTypeToVoteString(overrideType, voteString, maxlength); + } + } + + return false; +} + +stock bool Game_OverrideTypeToTranslationString(NativeVotesOverride overrideType, char[] translationString, int maxlength) +{ + switch (g_EngineVersion) + { + case Engine_TF2: + { + return TF2_OverrideTypeToTranslationString(overrideType, translationString, maxlength); + } + } + + return false; +} + +void Game_AddDefaultVotes(ArrayList hVoteTypes) +{ + switch (g_EngineVersion) + { + case Engine_TF2: + { + TF2_AddDefaultVotes(hVoteTypes, Game_AreDisabledIssuesHidden()); + } + } +} + +bool Game_AreDisabledIssuesHidden() +{ + switch (g_EngineVersion) + { + case Engine_TF2: + { + return g_Cvar_HideDisabledIssues.BoolValue; + } + } + + return true; +} + +// All games shared functions + +//---------------------------------------------------------------------------- +// Data functions + +static NativeVotesPassType VoteTypeToVotePass(NativeVotesType voteType) +{ + NativeVotesPassType passType = NativeVotesPass_None; + + switch(voteType) + { + case NativeVotesType_Custom_YesNo, NativeVotesType_Custom_Mult: + { + passType = NativeVotesPass_Custom; + } + case NativeVotesType_ChgCampaign: + { + passType = NativeVotesPass_ChgCampaign; + } + case NativeVotesType_ChgDifficulty: + { + passType = NativeVotesPass_ChgDifficulty; + } + case NativeVotesType_ReturnToLobby: + { + passType = NativeVotesPass_ReturnToLobby; + } + case NativeVotesType_AlltalkOn: + { + passType = NativeVotesPass_AlltalkOn; + } + case NativeVotesType_AlltalkOff: + { + passType = NativeVotesPass_AlltalkOff; + } + case NativeVotesType_Restart: + { + passType = NativeVotesPass_Restart; + } + case NativeVotesType_Kick, NativeVotesType_KickIdle, NativeVotesType_KickScamming, NativeVotesType_KickCheating: + { + passType = NativeVotesPass_Kick; + } + case NativeVotesType_ChgLevel: + { + passType = NativeVotesPass_ChgLevel; + } + case NativeVotesType_NextLevel, NativeVotesType_NextLevelMult: + { + passType = NativeVotesPass_NextLevel; + } + case NativeVotesType_ScrambleNow, NativeVotesType_ScrambleEnd: + { + passType = NativeVotesPass_Scramble; + } + case NativeVotesType_ChgMission: + { + passType = NativeVotesPass_ChgMission; + } + case NativeVotesType_SwapTeams: + { + passType = NativeVotesPass_SwapTeams; + } + case NativeVotesType_Surrender: + { + passType = NativeVotesPass_Surrender; + } + case NativeVotesType_Rematch: + { + passType = NativeVotesPass_Rematch; + } + case NativeVotesType_Continue: + { + passType = NativeVotesPass_Continue; + } + case NativeVotesType_StartRound: + { + passType = NativeVotesPass_StartRound; + } + case NativeVotesType_Eternaween: + { + passType = NativeVotesPass_Eternaween; + } + case NativeVotesType_AutoBalanceOn: + { + passType = NativeVotesPass_AutoBalanceOn; + } + case NativeVotesType_AutoBalanceOff: + { + passType = NativeVotesPass_AutoBalanceOff; + } + case NativeVotesType_ClassLimitsOn: + { + passType = NativeVotesPass_ClassLimitsOn; + } + case NativeVotesType_ClassLimitsOff: + { + passType = NativeVotesPass_ClassLimitsOff; + } + case NativeVotesType_Extend: + { + passType = NativeVotesPass_Extend; + } + case NativeVotesType_ChgCivilian: + { + passType = NativeVotesPass_ChgCivilian; + } + case NativeVotesType_ChgGamemode: + { + passType = NativeVotesPass_ChgGamemode; + } + default: + { + passType = NativeVotesPass_Custom; + } + } + + return passType; +} + +// In case we find more types later +static int VoteFailTypeToInt(NativeVotesFailType failType) +{ + return view_as(failType); +} + +static int VoteCallFailTypeToInt(NativeVotesCallFailType failType) +{ + switch (g_EngineVersion) + { + case Engine_Left4Dead, Engine_Left4Dead2: + { + switch (failType) + { + case NativeVotesCallFail_Generic: + { + return VOTE_FAILED_GENERIC; + } + case NativeVotesCallFail_Loading: + { + return VOTE_FAILED_TRANSITIONING_PLAYERS; + } + case NativeVotesCallFail_Recent: + { + return VOTE_FAILED_FAILED_RECENTLY; + } + } + } + case Engine_TF2: + { + switch (failType) + { + case NativeVotesCallFail_Generic: + { + return VOTE_FAILED_GENERIC; + } + case NativeVotesCallFail_Loading: + { + return VOTE_FAILED_TRANSITIONING_PLAYERS; + } + case NativeVotesCallFail_Recent: + { + return VOTE_FAILED_FAILED_RECENTLY; + } + case NativeVotesCallFail_Disabled: + { + return VOTE_FAILED_ISSUE_DISABLED; + } + case NativeVotesCallFail_MapNotFound: + { + return VOTE_FAILED_MAP_NOT_FOUND; + } + case NativeVotesCallFail_SpecifyMap: + { + return VOTE_FAILED_MAP_NAME_REQUIRED; + } + case NativeVotesCallFail_Failed: + { + return VOTE_FAILED_FAILED_RECENTLY; + } + case NativeVotesCallFail_WrongTeam: + { + return VOTE_FAILED_TEAM_CANT_CALL; + } + case NativeVotesCallFail_Waiting: + { + return VOTE_FAILED_WAITINGFORPLAYERS; + } + case NativeVotesCallFail_PlayerNotFound: + { + return VOTE_FAILED_PLAYERNOTFOUND; + } + case NativeVotesCallFail_CantKickAdmin: + { + return VOTE_FAILED_CANNOT_KICK_ADMIN; + } + case NativeVotesCallFail_ScramblePending: + { + return VOTE_FAILED_SCRAMBLE_IN_PROGRESS; + } + case NativeVotesCallFail_Spectators: + { + return VOTE_FAILED_SPECTATOR; + } + case NativeVotesCallFail_LevelSet: + { + return VOTE_FAILED_NEXTLEVEL_SET; + } + case NativeVotesCallFail_MapNotValid: + { + return VOTE_FAILED_MAP_NOT_VALID; + } + case NativeVotesCallFail_KickTime: + { + return VOTE_FAILED_CANNOT_KICK_FOR_TIME; + } + case NativeVotesCallFail_KickDuringRound: + { + return VOTE_FAILED_CANNOT_KICK_DURING_ROUND; + } + case NativeVotesCallFail_AlreadyActive: + { + return VOTE_FAILED_MODIFICATION_ALREADY_ACTIVE; + } + } + } + case Engine_CSGO: + { + switch (failType) + { + case NativeVotesCallFail_Generic: + { + return CSGO_VOTE_FAILED_GENERIC; + } + case NativeVotesCallFail_Loading: + { + return CSGO_VOTE_FAILED_TRANSITIONING_PLAYERS; + } + case NativeVotesCallFail_Recent: + { + return CSGO_VOTE_FAILED_RATE_EXCEEDED; + } + case NativeVotesCallFail_Disabled: + { + return CSGO_VOTE_FAILED_ISSUE_DISABLED; + } + case NativeVotesCallFail_MapNotFound: + { + return CSGO_VOTE_FAILED_MAP_NOT_FOUND; + } + case NativeVotesCallFail_SpecifyMap: + { + return CSGO_VOTE_FAILED_MAP_NAME_REQUIRED; + } + case NativeVotesCallFail_Failed: + { + return CSGO_VOTE_FAILED_FAILED_RECENTLY; + } + case NativeVotesCallFail_WrongTeam: + { + return CSGO_VOTE_FAILED_TEAM_CANT_CALL; + } + case NativeVotesCallFail_Warmup: + { + return CSGO_VOTE_FAILED_WARMUP; + } + case NativeVotesCallFail_PlayerNotFound: + { + return CSGO_VOTE_FAILED_PLAYERNOTFOUND; + } + case NativeVotesCallFail_CantKickAdmin: + { + return CSGO_VOTE_FAILED_CANNOT_KICK_ADMIN; + } + case NativeVotesCallFail_ScramblePending: + { + return CSGO_VOTE_FAILED_SCRAMBLE_IN_PROGRESS; + } + case NativeVotesCallFail_Spectators: + { + return CSGO_VOTE_FAILED_SPECTATOR; + } + case NativeVotesCallFail_LevelSet: + { + return CSGO_VOTE_FAILED_NEXTLEVEL_SET; + } + case NativeVotesCallFail_KickFailed: + { + return CSGO_VOTE_FAILED_FAILED_RECENTLY_KICK; + } + case NativeVotesCallFail_MapFailed: + { + return CSGO_VOTE_FAILED_FAILED_RECENTLY_MAP; + } + case NativeVotesCallFail_SwapFailed: + { + return CSGO_VOTE_FAILED_FAILED_RECENTLY_SWAP; + } + case NativeVotesCallFail_ScrambleFailed: + { + return CSGO_VOTE_FAILED_FAILED_RECENTLY_SCRAMBLE; + } + case NativeVotesCallFail_RestartFailed: + { + return CSGO_VOTE_FAILED_FAILED_RECENTLY_RESTART; + } + case NativeVotesCallFail_SwapPending: + { + return CSGO_VOTE_FAILED_SWAP_IN_PROGRESS; + } + case NativeVotesCallFail_Unknown2: + { + return CSGO_VOTE_FAILED_UNKNOWN1; + } + case NativeVotesCallFail_CantSurrender: + { + return CSGO_VOTE_FAILED_SURRENDER_ABANDON; + } + case NativeVotesCallFail_Unknown3: + { + return CSGO_VOTE_FAILED_UNKNOWN2; + } + case NativeVotesCallFail_MatchPaused: + { + return CSGO_VOTE_FAILED_PAUSED; + } + case NativeVotesCallFail_NotPaused: + { + return CSGO_VOTE_FAILED_NOT_PAUSED; + } + case NativeVotesCallFail_NotWarmup: + { + return CSGO_VOTE_FAILED_NOT_WARMUP; + } + case NativeVotesCallFail_MinPlayers: + { + return CSGO_VOTE_FAILED_MIN_PLAYERS; + } + case NativeVotesCallFail_RoundEnded: + { + return CSGO_VOTE_FAILED_ROUND_ENDED; + } + } + } + } + return VOTE_FAILED_GENERIC; +} + +static void GetEngineVersionName(EngineVersion version, char[] printName, int maxlength) +{ + switch (version) + { + case Engine_Unknown: + { + strcopy(printName, maxlength, "Unknown"); + } + case Engine_Original: + { + strcopy(printName, maxlength, "Original"); + } + case Engine_SourceSDK2006: + { + strcopy(printName, maxlength, "Source SDK 2006"); + } + case Engine_SourceSDK2007: + { + strcopy(printName, maxlength, "Source SDK 2007"); + } + case Engine_Left4Dead: + { + strcopy(printName, maxlength, "Left 4 Dead "); + } + case Engine_DarkMessiah: + { + strcopy(printName, maxlength, "Dark Messiah"); + } + case Engine_Left4Dead2: + { + strcopy(printName, maxlength, "Left 4 Dead 2"); + } + case Engine_AlienSwarm: + { + strcopy(printName, maxlength, "Alien Swarm"); + } + case Engine_BloodyGoodTime: + { + strcopy(printName, maxlength, "Bloody Good Time"); + } + case Engine_EYE: + { + strcopy(printName, maxlength, "E.Y.E. Divine Cybermancy"); + } + case Engine_Portal2: + { + strcopy(printName, maxlength, "Portal 2"); + } + case Engine_CSGO: + { + strcopy(printName, maxlength, "Counter-Strike: Global Offensive"); + } + case Engine_CSS: + { + strcopy(printName, maxlength, "Counter-Strike: Source"); + } + case Engine_DOTA: + { + strcopy(printName, maxlength, "DOTA 2"); + } + case Engine_HL2DM: + { + char gameDir[32]; + GetGameFolderName(gameDir, sizeof(gameDir)); + if (StrEqual(gameDir, "hl2mp")) + { + strcopy(printName, maxlength, "Half-Life 2: Deathmatch"); + } + else + { + // https://github.com/alliedmodders/metamod-source/pull/210 + strcopy(printName, maxlength, "Source SDK 2025"); + } + } + case Engine_DODS: + { + strcopy(printName, maxlength, "Day of Defeat: Source"); + } + case Engine_TF2: + { + strcopy(printName, maxlength, "Team Fortress 2"); + } + case Engine_NuclearDawn: + { + strcopy(printName, maxlength, "Nuclear Dawn"); + } + case Engine_SDK2013: + { + strcopy(printName, maxlength , "Source SDK 2013"); + } + case Engine_Blade: + { + strcopy(printName, maxlength , "Blade Symphony"); + } + case Engine_Insurgency: + { + strcopy(printName, maxlength , "Insurgency (2013 Retail version)"); + } + case Engine_Contagion: + { + strcopy(printName, maxlength , "Contagion"); + } + case Engine_BlackMesa: + { + strcopy(printName, maxlength , "Black Mesa Multiplayer"); + } + case Engine_DOI: + { + strcopy(printName, maxlength , "Day of Infamy"); + } + case Engine_PVKII: + { + strcopy(printName, maxlength , "Pirates, Vikings, and Knights II"); + } + case Engine_MCV: + { + strcopy(printName, maxlength , "Military Conflict: Vietnam"); + } + default: + { + strcopy(printName, maxlength, "Not listed"); + } + } +} + + + +//---------------------------------------------------------------------------- +// L4D/L4D2 shared functions + +// NATIVEVOTES_VOTE_INVALID means parse failed +static int L4DL4D2_ParseVote(const char[] option) +{ + if (StrEqual(option, "Yes", false)) + { + return NATIVEVOTES_VOTE_YES; + } + else if (StrEqual(option, "No", false)) + { + return NATIVEVOTES_VOTE_NO; + } + + return NATIVEVOTES_VOTE_INVALID; +} + +static void L4DL4D2_ClientSelectedItem(int client, int item) +{ + int choice; + + if (item == NATIVEVOTES_VOTE_NO) + { + choice = 0; + } + else if (item == NATIVEVOTES_VOTE_YES) + { + choice = 1; + } + + BfWrite voteCast = UserMessageToBfWrite(StartMessageOne("VoteRegistered", client, USERMSG_RELIABLE)); + voteCast.WriteByte(choice); + EndMessage(); +} + +static void L4DL4D2_UpdateVoteCounts(ArrayList votes, int totalClients) +{ + int yesVotes = votes.Get(NATIVEVOTES_VOTE_YES); + int noVotes = votes.Get(NATIVEVOTES_VOTE_NO); + Event changeEvent = CreateEvent("vote_changed"); + changeEvent.SetInt("yesVotes", yesVotes); + changeEvent.SetInt("noVotes", noVotes); + changeEvent.SetInt("potentialVotes", totalClients); + changeEvent.Fire(); + + if (CheckVoteController()) + { + SetEntProp(g_VoteController, Prop_Send, "m_votesYes", yesVotes); + SetEntProp(g_VoteController, Prop_Send, "m_votesNo", noVotes); + } +} + +static stock void L4DL4D2_UpdateClientCount(int num_clients) +{ + if (CheckVoteController()) + { + SetEntProp(g_VoteController, Prop_Send, "m_potentialVotes", num_clients); + } +} + +static void L4DL4D2_CallVoteFail(int client, int reason) +{ + BfWrite callVoteFail = UserMessageToBfWrite(StartMessageOne("CallVoteFailed", client, USERMSG_RELIABLE)); + + callVoteFail.WriteByte(reason); + + EndMessage(); +} + +static void L4DL4D2_VoteTypeToTranslation(NativeVotesType voteType, char[] translation, int maxlength) +{ + switch(voteType) + { + case NativeVotesType_ChgCampaign: + { + strcopy(translation, maxlength, L4D_VOTE_CHANGECAMPAIGN_START); + } + case NativeVotesType_ChgDifficulty: + { + strcopy(translation, maxlength, L4D_VOTE_CHANGEDIFFICULTY_START); + } + case NativeVotesType_ReturnToLobby: + { + strcopy(translation, maxlength, L4D_VOTE_RETURNTOLOBBY_START); + } + case NativeVotesType_AlltalkOn, NativeVotesType_AlltalkOff: + { + strcopy(translation, maxlength, L4D2_VOTE_ALLTALK_START); + } + case NativeVotesType_Restart: + { + strcopy(translation, maxlength, L4D_VOTE_RESTART_START); + } + case NativeVotesType_Kick: + { + strcopy(translation, maxlength, L4D_VOTE_KICK_START); + } + case NativeVotesType_ChgLevel: + { + strcopy(translation, maxlength, L4D_VOTE_CHANGELEVEL_START); + } + default: + { + strcopy(translation, maxlength, L4D_VOTE_CUSTOM); + } + } +} + +static void L4DL4D2_VotePassToTranslation(NativeVotesPassType passType, char[] translation, int maxlength) +{ + switch(passType) + { + case NativeVotesPass_Custom: + { + strcopy(translation, maxlength, L4D_VOTE_CUSTOM); + } + case NativeVotesPass_ChgCampaign: + { + strcopy(translation, maxlength, L4D_VOTE_CHANGECAMPAIGN_PASSED); + } + case NativeVotesPass_ChgDifficulty: + { + strcopy(translation, maxlength, L4D_VOTE_CHANGEDIFFICULTY_PASSED); + } + case NativeVotesPass_ReturnToLobby: + { + strcopy(translation, maxlength, L4D_VOTE_RETURNTOLOBBY_PASSED); + } + case NativeVotesPass_AlltalkOn, NativeVotesPass_AlltalkOff: + { + strcopy(translation, maxlength, L4D2_VOTE_ALLTALK_PASSED); + } + case NativeVotesPass_Restart: + { + strcopy(translation, maxlength, L4D_VOTE_RESTART_PASSED); + } + case NativeVotesPass_Kick: + { + strcopy(translation, maxlength, L4D_VOTE_KICK_PASSED); + } + case NativeVotesPass_ChgLevel: + { + strcopy(translation, maxlength, L4D_VOTE_CHANGELEVEL_PASSED); + } + default: + { + strcopy(translation, maxlength, L4D_VOTE_CUSTOM); + } + } +} + +static void L4DL4D2_ResetVote() +{ + if (CheckVoteController()) + { + SetEntProp(g_VoteController, Prop_Send, "m_activeIssueIndex", INVALID_ISSUE); + SetEntProp(g_VoteController, Prop_Send, "m_votesYes", 0); + SetEntProp(g_VoteController, Prop_Send, "m_votesNo", 0); + SetEntProp(g_VoteController, Prop_Send, "m_potentialVotes", 0); + SetEntProp(g_VoteController, Prop_Send, "m_onlyTeamToVote", NATIVEVOTES_ALL_TEAMS); + } +} + +static bool L4DL4D2_IsVoteInProgress() +{ + if (CheckVoteController()) + { + return (GetEntProp(g_VoteController, Prop_Send, "m_activeIssueIndex") > INVALID_ISSUE); + } + return false; +} + +//---------------------------------------------------------------------------- +// L4D functions + +/* +static L4D_ClientSelectedItem(Handle:vote, client, item) +{ + if (item > NATIVEVOTES_VOTE_INVALID && item <= Game_GetMaxItems()) + { + Handle castEvent; + + switch (item) + { + case NATIVEVOTES_VOTE_YES: + { + castEvent = CreateEvent("vote_cast_no"); + } + + case NATIVEVOTES_VOTE_NO: + { + castEvent = CreateEvent("vote_cast_yes"); + } + + default: + { + return; + } + } + + if (castEvent != INVALID_HANDLE) + { + SetEventInt(castEvent, "team", Data_GetTeam(vote)); + SetEventInt(castEvent, "entityid", client); + FireEvent(castEvent); + } + + } +} +*/ + +static void L4D_DisplayVote(NativeVote vote, int num_clients) +{ + char translation[TRANSLATION_LENGTH]; + + NativeVotesType voteType = Data_GetType(vote); + + L4DL4D2_VoteTypeToTranslation(voteType, translation, sizeof(translation)); + + char details[MAX_VOTE_DETAILS_LENGTH]; + Data_GetDetails(vote, details, MAX_VOTE_DETAILS_LENGTH); + + int team = Data_GetTeam(vote); + + if (CheckVoteController()) + { + // TODO: Need to look these values up + SetEntProp(g_VoteController, Prop_Send, "m_activeIssueIndex", 0); // For now, set to 0 to block in-game votes + SetEntProp(g_VoteController, Prop_Send, "m_onlyTeamToVote", team); + SetEntProp(g_VoteController, Prop_Send, "m_votesYes", 0); + SetEntProp(g_VoteController, Prop_Send, "m_votesNo", 0); + SetEntProp(g_VoteController, Prop_Send, "m_potentialVotes", num_clients); + } + + Event voteStart = CreateEvent("vote_started"); + voteStart.SetInt("team", team); + voteStart.SetInt("initiator", Data_GetInitiator(vote)); + voteStart.SetString("issue", translation); + voteStart.SetString("param1", details); + voteStart.Fire(); + +} + +static void L4D_VoteEnded() +{ + Event endEvent = CreateEvent("vote_ended"); + endEvent.Fire(); +} + +static void L4D_VotePass(const char[] translation, const char[] details, int team) +{ + L4D_VoteEnded(); + + Event passEvent = CreateEvent("vote_passed"); + passEvent.SetString("details", translation); + passEvent.SetString("param1", details); + passEvent.SetInt("team", team); + passEvent.Fire(); +} + +static void L4D_VoteFail(int team) +{ + L4D_VoteEnded(); + + Event failEvent = CreateEvent("vote_failed"); + failEvent.SetInt("team", team); + failEvent.Fire(); +} + +static bool L4D_CheckVoteType(NativeVotesType voteType) +{ + switch(voteType) + { + case NativeVotesType_Custom_YesNo, + NativeVotesType_ChgCampaign, + NativeVotesType_ChgDifficulty, + NativeVotesType_ReturnToLobby, + NativeVotesType_Restart, + NativeVotesType_Kick, + NativeVotesType_ChgLevel: + { + return true; + } + } + + return false; +} + +static bool L4D_CheckVotePassType(NativeVotesPassType passType) +{ + switch(passType) + { + case NativeVotesPass_Custom, + NativeVotesPass_ChgCampaign, + NativeVotesPass_ChgDifficulty, + NativeVotesPass_ReturnToLobby, + NativeVotesPass_Restart, + NativeVotesPass_Kick, + NativeVotesPass_ChgLevel: + { + return true; + } + } + + return false; +} + +//---------------------------------------------------------------------------- +// L4D2 functions + +static void L4D2_DisplayVote(NativeVote vote, int[] clients, int num_clients) +{ + char translation[TRANSLATION_LENGTH]; + + NativeVotesType voteType = Data_GetType(vote); + + L4DL4D2_VoteTypeToTranslation(voteType, translation, sizeof(translation)); + + char details[MAX_VOTE_DETAILS_LENGTH]; + + int team = Data_GetTeam(vote); + bool bCustom = false; + + switch (voteType) + { + case NativeVotesType_AlltalkOn: + { + strcopy(details, MAX_VOTE_DETAILS_LENGTH, L4D2_VOTE_ALLTALK_ENABLE); + } + case NativeVotesType_AlltalkOff: + { + strcopy(details, MAX_VOTE_DETAILS_LENGTH, L4D2_VOTE_ALLTALK_DISABLE); + } + case NativeVotesType_Custom_YesNo, NativeVotesType_Custom_Mult: + { + Data_GetTitle(vote, details, MAX_VOTE_DETAILS_LENGTH); + bCustom = true; + } + default: + { + Data_GetDetails(vote, details, MAX_VOTE_DETAILS_LENGTH); + } + } + + int initiator = Data_GetInitiator(vote); + char initiatorName[MAX_NAME_LENGTH]; + + if (initiator != NATIVEVOTES_SERVER_INDEX && initiator > 0 && initiator <= MaxClients && IsClientInGame(initiator)) + { + GetClientName(initiator, initiatorName, MAX_NAME_LENGTH); + } + + for (int i = 0; i < num_clients; ++i) + { + g_newMenuTitle[0] = '\0'; + + MenuAction actions = Data_GetActions(vote); + + Action changeTitle = Plugin_Continue; + if (bCustom && actions & MenuAction_Display) + { + g_curDisplayClient = clients[i]; + changeTitle = DoAction(vote, MenuAction_Display, clients[i], 0); + } + + g_curDisplayClient = 0; + + BfWrite voteStart = UserMessageToBfWrite(StartMessageOne("VoteStart", clients[i], USERMSG_RELIABLE)); + voteStart.WriteByte(team); + voteStart.WriteByte(initiator); + voteStart.WriteString(translation); + if (changeTitle == Plugin_Changed) + { + voteStart.WriteString(g_newMenuTitle); + } + else + { + voteStart.WriteString(details); + } + voteStart.WriteString(initiatorName); + EndMessage(); + } + + if (CheckVoteController()) + { + SetEntProp(g_VoteController, Prop_Send, "m_onlyTeamToVote", team); + SetEntProp(g_VoteController, Prop_Send, "m_votesYes", 0); + SetEntProp(g_VoteController, Prop_Send, "m_votesNo", 0); + SetEntProp(g_VoteController, Prop_Send, "m_potentialVotes", num_clients); + SetEntProp(g_VoteController, Prop_Send, "m_activeIssueIndex", 0); // Set to 0 to block ingame votes + } +} + +static void L4D2_VotePass(const char[] translation, const char[] details, int team, int client=0) +{ + BfWrite votePass; + if (!client) + { + votePass = UserMessageToBfWrite(StartMessageAll("VotePass", USERMSG_RELIABLE)); + } + else + { + votePass = UserMessageToBfWrite(StartMessageOne("VotePass", client, USERMSG_RELIABLE)); + } + + votePass.WriteByte(team); + votePass.WriteString(translation); + votePass.WriteString(details); + EndMessage(); +} + +static void L4D2_VoteFail(int[] clients, int numClients, int team) +{ + BfWrite voteFailed = UserMessageToBfWrite(StartMessage("VoteFail", clients, numClients, USERMSG_RELIABLE)); + + voteFailed.WriteByte(team); + EndMessage(); +} + +static bool L4D2_CheckVoteType(NativeVotesType voteType) +{ + switch(voteType) + { + case NativeVotesType_Custom_YesNo, + NativeVotesType_ChgCampaign, + NativeVotesType_ChgDifficulty, + NativeVotesType_ReturnToLobby, + NativeVotesType_AlltalkOn, + NativeVotesType_AlltalkOff, + NativeVotesType_Restart, + NativeVotesType_Kick, + NativeVotesType_ChgLevel: + { + return true; + } + } + + return false; +} + +static bool L4D2_CheckVotePassType(NativeVotesPassType passType) +{ + switch(passType) + { + case NativeVotesPass_Custom, + NativeVotesPass_ChgCampaign, + NativeVotesPass_ChgDifficulty, + NativeVotesPass_ReturnToLobby, + NativeVotesPass_Restart, + NativeVotesPass_Kick, + NativeVotesPass_ChgLevel: + { + return true; + } + } + + return false; +} + +//---------------------------------------------------------------------------- +// TF2/CSGO shared functions + +// TF2 and CSGO functions are still together in case Valve moves TF2 to protobufs. + +// NATIVEVOTES_VOTE_INVALID means parse failed +static int CSGO_ParseVote(const char[] option) +{ + // option1 <-- 7 characters exactly + if (strlen(option) != 7) + { + return NATIVEVOTES_VOTE_INVALID; + } + + return StringToInt(option[6]) - 1; +} + +// NATIVEVOTES_VOTE_INVALID means parse failed +static int TF2_ParseVote(const char[] option) +{ + // the update on 2022-06-22 changed the params passed to the `vote` command + // previously it was a single string "optionN", where N was the option to be selected + // now it's two arguments "X optionN", where X is the vote index being acted on + + if (strlen(option) == 0 || GetCmdArgs() != 2) + { + return NATIVEVOTES_VOTE_INVALID; + } + + // int voteidx = GetCmdArgInt(1); + + char voteOption[16]; + GetCmdArg(2, voteOption, sizeof(voteOption)); + + // option1 <-- 7 characters exactly + // voteOption's last character should be numeric + if (strlen(voteOption) != 7 || !IsCharNumeric(voteOption[6])) + { + return NATIVEVOTES_VOTE_INVALID; + } + + return StringToInt(voteOption[6]) - 1; +} + +static void CSGO_ClientSelectedItem(NativeVote vote, int client, int item) +{ + Event castEvent = CreateEvent("vote_cast"); + + castEvent.SetInt("team", Data_GetTeam(vote)); + castEvent.SetInt("entityid", client); + castEvent.SetInt("vote_option", item); + castEvent.Fire(); +} + +static void TF2_ClientSelectedItem(NativeVote vote, int client, int item) +{ + Event castEvent = CreateEvent("vote_cast"); + + castEvent.SetInt("team", Data_GetTeam(vote)); + castEvent.SetInt("voteidx", s_nNativeVoteIdx); + castEvent.SetInt("entityid", client); + castEvent.SetInt("vote_option", item); + castEvent.Fire(); +} + +static void TF2CSGO_UpdateVoteCounts(ArrayList votes) +{ + if (CheckVoteController()) + { + int size = votes.Length; + Event changeEvent = CreateEvent("vote_changed"); + for (int i = 0; i < size; i++) + { + SetEntProp(g_VoteController, Prop_Send, "m_nVoteOptionCount", votes.Get(i), 4, i); + + char szVoteOption[13]; + Format(szVoteOption, sizeof(szVoteOption), "vote_option%d", i+1); + changeEvent.SetInt(szVoteOption, votes.Get(i)); + } + changeEvent.SetInt("voteidx", s_nNativeVoteIdx); + changeEvent.SetInt("potentialVotes", GetEntProp(g_VoteController, Prop_Send, "m_nPotentialVotes")); + changeEvent.Fire(); + } +} + +static stock void TF2CSGO_UpdateClientCount(int num_clients) +{ + if (CheckVoteController()) + { + SetEntProp(g_VoteController, Prop_Send, "m_nPotentialVotes", num_clients); + } +} + +static void TF2CSGO_DisplayVote(NativeVote vote, int[] clients, int num_clients, float end_time) +{ + NativeVotesType voteType = Data_GetType(vote); + + // Added for novote support + bool bNoVoteButton = (Data_GetFlags(vote) & MENUFLAG_BUTTON_NOVOTE) == MENUFLAG_BUTTON_NOVOTE; + + if (bNoVoteButton) + { + int max = Game_GetMaxItems(); + if (Data_GetItemCount(vote) == max) + { + // item must be removed before No Vote is added to prevent it from being blocked + Data_RemoveItem(vote, max-1); + } + + char display[TRANSLATION_LENGTH]; + Format(display, sizeof(display), "%T", "No Vote", LANG_SERVER); + + Data_InsertItem(vote, 0, "No Vote", display); + } + + char translation[TRANSLATION_LENGTH]; + char otherTeamString[TRANSLATION_LENGTH]; + bool bYesNo = true; + bool bCustom = false; + + char details[MAX_VOTE_DETAILS_LENGTH]; + + // voteIndex is used by the CVoteController, which we're not using. + // -1 means no vote in progress, so any other value should work. + //int voteIndex = TF2CSGO_GetVoteType(voteType); + int voteIndex = 0; + + switch (voteType) + { + case NativeVotesType_Custom_YesNo, NativeVotesType_Custom_Mult: + { + Data_GetTitle(vote, details, MAX_VOTE_DETAILS_LENGTH); + bCustom = true; + } + default: + { + Data_GetDetails(vote, details, MAX_VOTE_DETAILS_LENGTH); + } + } + + switch (g_EngineVersion) + { + case Engine_CSGO: + { + bYesNo = CSGO_VoteTypeToTranslation(voteType, translation, sizeof(translation)); + CSGO_VoteTypeToVoteOtherTeamString(voteType, otherTeamString, sizeof(otherTeamString)); + } + case Engine_TF2: + { + bYesNo = TF2_VoteTypeToTranslation(voteType, translation, sizeof(translation)); + } + } + + int team = Data_GetTeam(vote); + + // Moved to mimic SourceSDK2013's server/vote_controller.cpp + if (CheckVoteController()) + { + SetEntProp(g_VoteController, Prop_Send, "m_bIsYesNoVote", bYesNo); + + // CSGO gets very cranky if you try setting this + if (g_EngineVersion == Engine_TF2) + { + SetEntProp(g_VoteController, Prop_Send, "m_iActiveIssueIndex", voteIndex); + } + + SetEntProp(g_VoteController, Prop_Send, "m_iOnlyTeamToVote", team); + + for (int i = 0; i < TF2CSGO_COUNT; i++) + { + SetEntProp(g_VoteController, Prop_Send, "m_nVoteOptionCount", 0, _, i); + } + + // TODO(UPDATE): M-M-M-MULTIVOTE + // we need unique vote indices; HUD elements for previous votes aren't cleaned up (?) + // the game implements this as `this->m_nVoteIdx = s_nVoteIdx++` + s_nNativeVoteIdx = GetEntProp(g_VoteController, Prop_Send, "m_nVoteIdx"); + + #if defined LOG + PrintToServer("Starting vote index: %d (controller: %d)", s_nNativeVoteIdx, GetEntProp(g_VoteController, Prop_Send, "m_nVoteIdx")); + #endif + SetEntProp(g_VoteController, Prop_Send, "m_nVoteIdx", s_nNativeVoteIdx + 1); // TODO(UPDATE) + } + + // According to Source SDK 2013, vote_options is only sent for a multiple choice vote. + // As of 2015-09-28, vote_options is sent for all votes in TF2 despite Yes/No being + // translated in the UI itself. + // As of 2016-07-10, we send this only when we're doing Yes/No votes as we handle + // multiple choice votes separately later. + if (bYesNo) + { + int itemCount = Data_GetItemCount(vote); + + Event optionsEvent = CreateEvent("vote_options"); + + for (int i = 0; i < itemCount; i++) + { + char option[8]; + Format(option, sizeof(option), "%s%d", TF2CSGO_VOTE_PREFIX, i+1); + char display[TRANSLATION_LENGTH]; + Data_GetItemDisplay(vote, i, display, sizeof(display)); + + optionsEvent.SetString(option, display); + } + optionsEvent.SetInt("count", itemCount); + optionsEvent.SetInt("voteidx", s_nNativeVoteIdx); + optionsEvent.Fire(); + } + + // Moved to mimic SourceSDK2013's server/vote_controller.cpp + // For whatever reason, while the other props are set first, this one's set after the vote_options event + if (CheckVoteController()) + { + SetEntProp(g_VoteController, Prop_Send, "m_nPotentialVotes", num_clients); + } + + int holder = vote.Initiator; + // required to allow the initiator to vote on their own issue + // https://github.com/ValveSoftware/Source-1-Games/issues/3934 + if (sv_vote_holder_may_vote_no && holder <= MaxClients && IsClientConnected(holder) && !IsFakeClient(holder)) + { + sv_vote_holder_may_vote_no.ReplicateToClient(holder, "1"); + } + + MenuAction actions = Data_GetActions(vote); + + for (int i = 0; i < num_clients; ++i) + { + g_newMenuTitle[0] = '\0'; + + Action changeTitle = Plugin_Continue; + if (bCustom && actions & MenuAction_Display) + { + g_curDisplayClient = clients[i]; + changeTitle = DoAction(vote, MenuAction_Display, clients[i], 0); + } + + g_curDisplayClient = 0; + + if (!bYesNo) + { + TF2CSGO_SendOptionsToClient(vote, clients[i]); + } + + Handle voteStart = StartMessageOne("VoteStart", clients[i], USERMSG_RELIABLE); + + if (g_bUserBuf) + { + Protobuf protoStart = UserMessageToProtobuf(voteStart); + protoStart.SetInt("team", team); + protoStart.SetInt("ent_idx", Data_GetInitiator(vote)); + protoStart.SetString("disp_str", translation); + if (bCustom && changeTitle == Plugin_Changed) + { + protoStart.SetString("details_str", g_newMenuTitle); + } + else + { + protoStart.SetString("details_str", details); + } + protoStart.SetBool("is_yes_no_vote", bYesNo); + protoStart.SetString("other_team_str", otherTeamString); + protoStart.SetInt("vote_type", voteIndex); + } + else + { + BfWrite bfStart = UserMessageToBfWrite(voteStart); + bfStart.WriteByte(team); + bfStart.WriteNum(s_nNativeVoteIdx); + bfStart.WriteByte(Data_GetInitiator(vote)); + bfStart.WriteString(translation); + if (bCustom && changeTitle == Plugin_Changed) + { + bfStart.WriteString(g_newMenuTitle); + } + else + { + bfStart.WriteString(details); + } + bfStart.WriteBool(bYesNo); + bfStart.WriteByte(Data_GetTarget(vote) - 1); + if (g_AppID == 3545060) // Team Fortress 2 Classified + { + bfStart.WriteFloat(GetGameTime() + end_time); + } + } + EndMessage(); + } + g_curDisplayClient = 0; +} + +static void TF2CSGO_SendOptionsToClient(NativeVote vote, int client) +{ + Event optionsEvent = CreateEvent("vote_options"); + + MenuAction actions = Data_GetActions(vote); + bool bNoVoteButton = (Data_GetFlags(vote) & MENUFLAG_BUTTON_NOVOTE) == MENUFLAG_BUTTON_NOVOTE; + + int start = 0; + + if (bNoVoteButton) + { + start = 1; + char option[8]; + Format(option, sizeof(option), "%s1", TF2CSGO_VOTE_PREFIX); + + char display[TRANSLATION_LENGTH]; + Format(display, sizeof(display), "%T", "No Vote", client); + optionsEvent.SetString(option, display); + } + + int itemCount = Data_GetItemCount(vote); + + for (int i = start; i < itemCount; i++) + { + Action changeItem = Plugin_Continue; + + if (actions & MenuAction_DisplayItem) + { + g_curItemClient = client; + g_newMenuItem[0] = '\0'; + + changeItem = DoAction(vote, MenuAction_DisplayItem, client, i); + g_curItemClient = 0; + } + + char option[8]; + Format(option, sizeof(option), "%s%d", TF2CSGO_VOTE_PREFIX, i+1); + char display[TRANSLATION_LENGTH]; + + if (changeItem == Plugin_Changed) + { + strcopy(display, TRANSLATION_LENGTH, g_newMenuItem); + } + else + { + Data_GetItemDisplay(vote, i, display, sizeof(display)); + } + optionsEvent.SetString(option, display); + } + optionsEvent.SetInt("count", itemCount); + optionsEvent.SetInt("voteidx", s_nNativeVoteIdx); + optionsEvent.FireToClient(client); + // FireToClient does not close the handle, so we call Cancel() to do that for us. + optionsEvent.Cancel(); +} + +static void CSGO_VotePass(const char[] translation, const char[] details, int team, int client=0) +{ + Protobuf votePass = null; + + if (client <= 0) + { + votePass = UserMessageToProtobuf(StartMessageAll("VotePass", USERMSG_RELIABLE)); + } + else + { + votePass = UserMessageToProtobuf(StartMessageOne("VotePass", client, USERMSG_RELIABLE)); + } + + votePass.SetInt("team", team); + votePass.SetString("disp_str", translation); + votePass.SetString("details_str", details); + votePass.SetInt("vote_type", 0); // Unknown, need to check values + + EndMessage(); +} + +static void TF2_VotePass(const char[] translation, const char[] details, int team, int client=0) +{ + BfWrite votePass = null; + + if (client <= 0) + { + votePass = UserMessageToBfWrite(StartMessageAll("VotePass", USERMSG_RELIABLE)); + } + else + { + votePass = UserMessageToBfWrite(StartMessageOne("VotePass", client, USERMSG_RELIABLE)); + } + + votePass.WriteByte(team); + votePass.WriteNum(s_nNativeVoteIdx); + votePass.WriteString(translation); + votePass.WriteString(details); + + EndMessage(); +} + +static void CSGO_VoteFail(int[] clients, int numClients, int reason, int team) +{ + Protobuf voteFailed = UserMessageToProtobuf(StartMessage("VoteFailed", clients, numClients, USERMSG_RELIABLE)); + + voteFailed.SetInt("team", team); + voteFailed.SetInt("reason", reason); + + EndMessage(); +} + +static void TF2_VoteFail(int[] clients, int numClients, int reason, int team) +{ + BfWrite voteFailed = UserMessageToBfWrite(StartMessage("VoteFailed", clients, numClients, USERMSG_RELIABLE)); + + voteFailed.WriteByte(team); + voteFailed.WriteNum(s_nNativeVoteIdx); + voteFailed.WriteByte(reason); + + EndMessage(); +} + +static void CSGO_CallVoteFail(int client, int reason, int time) +{ + Protobuf callVoteFail = UserMessageToProtobuf(StartMessageOne("CallVoteFailed", client, USERMSG_RELIABLE)); + + callVoteFail.SetInt("reason", reason); + callVoteFail.SetInt("time", time); + + EndMessage(); +} + +static void TF2_CallVoteFail(int client, int reason, int time) +{ + BfWrite callVoteFail = UserMessageToBfWrite(StartMessageOne("CallVoteFailed", client, USERMSG_RELIABLE)); + + callVoteFail.WriteByte(reason); + callVoteFail.WriteShort(time); + + EndMessage(); +} + +stock static void CSGO_DisplayVoteSetup(int client, ArrayList hVoteTypes) +{ + int count = hVoteTypes.Length; + + Protobuf voteSetup = UserMessageToProtobuf(StartMessageOne("VoteSetup", client, USERMSG_RELIABLE)); + + for (int i = 0; i < count; ++i) + { + char voteIssue[128]; + + CallVoteListData voteData; + hVoteTypes.GetArray(i, voteData.CallVoteList_VoteType); + + Game_OverrideTypeToVoteString(voteData.CallVoteList_VoteType, voteIssue, sizeof(voteIssue)); + + voteSetup.AddString("potential_issues", voteIssue); + } + + EndMessage(); +} + +static void TF2_DisplayVoteSetup(int client, ArrayList hVoteTypes) +{ + int count = hVoteTypes.Length; + + BfWrite voteSetup = UserMessageToBfWrite(StartMessageOne("VoteSetup", client, USERMSG_RELIABLE)); + + voteSetup.WriteByte(count); + + for (int i = 0; i < count; ++i) + { + char voteIssue[128]; + + CallVoteListData voteData; + hVoteTypes.GetArray(i, voteData, sizeof(CallVoteListData)); + + Game_OverrideTypeToVoteString(voteData.CallVoteList_VoteType, voteIssue, sizeof(voteIssue)); + + char translation[128]; + Game_OverrideTypeToTranslationString(voteData.CallVoteList_VoteType, translation, sizeof(translation)); + + voteSetup.WriteString(voteIssue); + voteSetup.WriteString(translation); + voteSetup.WriteByte(voteData.CallVoteList_VoteEnabled); + } + + EndMessage(); +} + +static void TF2CSGO_ResetVote() +{ + if (CheckVoteController()) + { + if (g_EngineVersion == Engine_TF2) + { + SetEntProp(g_VoteController, Prop_Send, "m_nVoteIdx", -1); + SetEntProp(g_VoteController, Prop_Send, "m_iActiveIssueIndex", INVALID_ISSUE); + + ChangeEdictState(g_VoteController, GetEntSendPropOffs(g_VoteController, "m_nVoteIdx")); + } + SetEntProp(g_VoteController, Prop_Send, "m_iOnlyTeamToVote", g_EngineVersion == Engine_TF2 ? NATIVEVOTES_TF2_ALL_TEAMS : NATIVEVOTES_ALL_TEAMS); + for (int i = 0; i < TF2CSGO_COUNT; i++) + { + SetEntProp(g_VoteController, Prop_Send, "m_nVoteOptionCount", 0, _, i); + } + SetEntProp(g_VoteController, Prop_Send, "m_nPotentialVotes", 0); + SetEntProp(g_VoteController, Prop_Send, "m_bIsYesNoVote", true); + } +} + +static bool TF2CSGO_IsVoteInProgress() +{ + if (CheckVoteController()) + { + return (GetEntProp(g_VoteController, Prop_Send, "m_iActiveIssueIndex") != INVALID_ISSUE); + } + return false; +} + + +//---------------------------------------------------------------------------- +// TF2 functions + +static bool TF2_CheckVoteType(NativeVotesType voteType) +{ + switch(voteType) + { + case NativeVotesType_Custom_YesNo, + NativeVotesType_Restart, + NativeVotesType_Kick, + NativeVotesType_KickIdle, + NativeVotesType_KickScamming, + NativeVotesType_KickCheating, + NativeVotesType_ChgLevel, + NativeVotesType_NextLevel, + NativeVotesType_ScrambleNow, + NativeVotesType_ScrambleEnd, + NativeVotesType_ChgMission, + NativeVotesType_StartRound, + NativeVotesType_Eternaween, + NativeVotesType_AutoBalanceOn, + NativeVotesType_AutoBalanceOff, + NativeVotesType_ClassLimitsOn, + NativeVotesType_ClassLimitsOff, + NativeVotesType_Extend, + NativeVotesType_ChgCivilian, + NativeVotesType_ChgDifficulty, + NativeVotesType_ChgGamemode: + { + return true; + } + case NativeVotesType_Custom_Mult, NativeVotesType_NextLevelMult: + { + return true; + } + } + + return false; +} + +static bool TF2_CheckVotePassType(NativeVotesPassType passType) +{ + switch(passType) + { + case NativeVotesPass_Custom, + NativeVotesPass_Restart, + NativeVotesPass_ChgLevel, + NativeVotesPass_Kick, + NativeVotesPass_NextLevel, + NativeVotesPass_Extend, + NativeVotesPass_Scramble, + NativeVotesPass_ChgMission, + NativeVotesPass_StartRound, + NativeVotesPass_Eternaween, + NativeVotesPass_AutoBalanceOn, + NativeVotesPass_AutoBalanceOff, + NativeVotesPass_ClassLimitsOn, + NativeVotesPass_ClassLimitsOff, + NativeVotesPass_ChgCivilian, + NativeVotesPass_ChgDifficulty, + NativeVotesPass_ChgGamemode: + { + return true; + } + } + + return false; +} + +static bool TF2_VoteTypeToTranslation(NativeVotesType voteType, char[] translation, int maxlength) +{ + bool bYesNo = true; + switch(voteType) + { + case NativeVotesType_Custom_Mult: + { + strcopy(translation, maxlength, TF2_VOTE_CUSTOM); + bYesNo = false; + } + case NativeVotesType_Restart: + { + strcopy(translation, maxlength, TF2_VOTE_RESTART_START); + } + case NativeVotesType_Kick: + { + strcopy(translation, maxlength, TF2_VOTE_KICK_START); + } + case NativeVotesType_KickIdle: + { + strcopy(translation, maxlength, TF2_VOTE_KICK_IDLE_START); + } + case NativeVotesType_KickScamming: + { + strcopy(translation, maxlength, TF2_VOTE_KICK_SCAMMING_START); + } + case NativeVotesType_KickCheating: + { + strcopy(translation, maxlength, TF2_VOTE_KICK_CHEATING_START); + } + case NativeVotesType_ChgLevel: + { + strcopy(translation, maxlength, TF2_VOTE_CHANGELEVEL_START); + } + case NativeVotesType_NextLevel: + { + strcopy(translation, maxlength, TF2_VOTE_NEXTLEVEL_SINGLE_START); + } + case NativeVotesType_NextLevelMult: + { + strcopy(translation, maxlength, TF2_VOTE_NEXTLEVEL_MULTIPLE_START); + bYesNo = false; + } + case NativeVotesType_ScrambleNow: + { + strcopy(translation, maxlength, TF2_VOTE_SCRAMBLE_IMMEDIATE_START); + } + case NativeVotesType_ScrambleEnd: + { + strcopy(translation, maxlength, TF2_VOTE_SCRAMBLE_ROUNDEND_START); + } + case NativeVotesType_ChgMission: + { + strcopy(translation, maxlength, TF2_VOTE_CHANGEMISSION_START); + } + case NativeVotesType_StartRound: + { + strcopy(translation, maxlength, TF2_VOTE_ROUND_START); + } + case NativeVotesType_Eternaween: + { + strcopy(translation, maxlength, TF2_VOTE_ETERNAWEEN_START); + } + case NativeVotesType_AutoBalanceOn: + { + strcopy(translation, maxlength, TF2_VOTE_AUTOBALANCE_ENABLE_START); + } + case NativeVotesType_AutoBalanceOff: + { + strcopy(translation, maxlength, TF2_VOTE_AUTOBALANCE_DISABLE_START); + } + case NativeVotesType_ClassLimitsOn: + { + strcopy(translation, maxlength, TF2_VOTE_CLASSLIMITS_ENABLE_START); + } + case NativeVotesType_ClassLimitsOff: + { + strcopy(translation, maxlength, TF2_VOTE_CLASSLIMITS_DISABLE_START); + } + case NativeVotesType_Extend: + { + strcopy(translation, maxlength, TF2_VOTE_EXTEND_START); + } + case NativeVotesType_ChgCivilian: + { + strcopy(translation, maxlength, TF2CLASSIFIED_VOTE_CHANGE_CIVILIAN_START); + } + case NativeVotesType_ChgDifficulty: + { + strcopy(translation, maxlength, FC_VOTE_MENU_CHANGE_DIFFICULTY); + } + case NativeVotesType_ChgGamemode: + { + strcopy(translation, maxlength, FC_VOTE_MENU_CHANGE_GAMEMODE); + } + default: + { + strcopy(translation, maxlength, TF2_VOTE_CUSTOM); + } + } + + return bYesNo; +} + +static void TF2_VotePassToTranslation(NativeVotesPassType passType, char[] translation, int maxlength) +{ + switch(passType) + { + case NativeVotesPass_Restart: + { + strcopy(translation, maxlength, TF2_VOTE_RESTART_PASSED); + } + case NativeVotesPass_Kick: + { + strcopy(translation, maxlength, TF2_VOTE_KICK_PASSED); + } + case NativeVotesPass_ChgLevel: + { + strcopy(translation, maxlength, TF2_VOTE_CHANGELEVEL_PASSED); + } + case NativeVotesPass_NextLevel: + { + strcopy(translation, maxlength, TF2_VOTE_NEXTLEVEL_PASSED); + } + case NativeVotesPass_Extend: + { + strcopy(translation, maxlength, TF2_VOTE_NEXTLEVEL_EXTEND_PASSED); + } + case NativeVotesPass_Scramble: + { + strcopy(translation, maxlength, TF2_VOTE_SCRAMBLE_PASSED); + } + case NativeVotesPass_ChgMission: + { + strcopy(translation, maxlength, TF2_VOTE_CHANGEMISSION_PASSED); + } + case NativeVotesPass_StartRound: + { + strcopy(translation, maxlength, TF2_VOTE_ROUND_PASSED); + } + case NativeVotesPass_Eternaween: + { + strcopy(translation, maxlength, TF2_VOTE_ETERNAWEEN_PASSED); + } + case NativeVotesPass_AutoBalanceOn: + { + strcopy(translation, maxlength, TF2_VOTE_AUTOBALANCE_ENABLE_PASSED); + } + case NativeVotesPass_AutoBalanceOff: + { + strcopy(translation, maxlength, TF2_VOTE_AUTOBALANCE_DISABLE_PASSED); + } + case NativeVotesPass_ClassLimitsOn: + { + strcopy(translation, maxlength, TF2_VOTE_CLASSLIMITS_ENABLE_PASSED); + } + case NativeVotesPass_ClassLimitsOff: + { + strcopy(translation, maxlength, TF2_VOTE_CLASSLIMITS_DISABLE_PASSED); + } + case NativeVotesPass_ChgCivilian: + { + strcopy(translation, maxlength, TF2CLASSIFIED_VOTE_CHANGE_CIVILIAN_PASSED); + } + case NativeVotesPass_ChgDifficulty: + { + strcopy(translation, maxlength, FC_VOTE_PASSED_CHANGE_DIFFICULTY); + } + case NativeVotesPass_ChgGamemode: + { + strcopy(translation, maxlength, FC_VOTE_PASSED_CHANGE_GAMEMODE); + } + default: + { + strcopy(translation, maxlength, TF2_VOTE_CUSTOM); + } + } +} + +static void TF2_AddDefaultVotes(ArrayList hVoteTypes, bool bHideDisabledVotes) +{ + bool globalEnable = g_Cvar_Votes_Enabled.BoolValue; + if (!globalEnable && bHideDisabledVotes) + { + return; + } + + if (FindSendPropInfo("CTFGameRulesProxy", "m_bPlayingMannVsMachine") > 0 && GameRules_GetProp("m_bPlayingMannVsMachine")) + { + // Default MvM vote types + + // Kick + VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_Kick, globalEnable && g_Cvar_MvM_VoteKick_Enabled.BoolValue); + + // Restart + VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_Restart, globalEnable && g_Cvar_MvM_VoteRestart_Enabled.BoolValue); + + // ChgLevel + VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_ChgLevel, globalEnable && g_Cvar_MvM_VoteChangeLevel_Enabled.BoolValue); + + // ChgMission + VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_ChgMission, globalEnable && g_Cvar_MvM_VoteChallenge_Enabled.BoolValue); + + // ClassLimits + VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_ClassLimits, globalEnable && g_Cvar_MvM_VoteClassLimits_Enabled.BoolValue); + } + else + { + // Default vote types + + // Kick + VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_Kick, globalEnable && g_Cvar_VoteKick_Enabled.BoolValue); + + // Restart + VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_Restart, globalEnable && g_Cvar_VoteRestart_Enabled.BoolValue); + + // ChgLevel + VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_ChgLevel, globalEnable && g_Cvar_VoteChangeLevel_Enabled.BoolValue); + + // NextLevel + VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_NextLevel, globalEnable && g_Cvar_VoteNextLevel_Enabled.BoolValue); + + // Scramble + VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_Scramble, globalEnable && g_Cvar_VoteScramble_Enabled.BoolValue); + + // ClassLimits + VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_ClassLimits, globalEnable && g_Cvar_VoteClassLimits_Enabled.BoolValue); + + // AutoBalance + VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_AutoBalance, globalEnable && g_Cvar_VoteAutoBalance_Enabled.BoolValue); + + // Extend + VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_Extend, globalEnable && g_Cvar_VoteExtend_Enabled.BoolValue); + } + + switch (g_AppID) + { + case 3545060: // Team Fortress 2 Classified + { + /* ChangeCivilian */ + int entity = FindEntityByClassname(INVALID_ENT_REFERENCE, "tf2c_logic_vip"); + VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_ChgCivilian, globalEnable && g_Cvar_ChangeCivilian_Enabled.BoolValue && entity != INVALID_ENT_REFERENCE); + } + case 3561600: // Fortress Connected + { + /* ChangeDifficulty */ + VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_ChgDifficulty, globalEnable && g_Cvar_ChangeDifficulty_Enabled.BoolValue); + + /* ChangeGamemode */ + VoteTypeSet(hVoteTypes, bHideDisabledVotes, NativeVotesOverride_ChgGamemode, globalEnable && g_Cvar_ChangeGamemode_Enabled.BoolValue); + } + } +} + +static void VoteTypeSet(ArrayList hVoteTypes, bool bHideDisabledVotes, NativeVotesOverride voteType, bool bEnabled) +{ + CallVoteListData voteList; + + if (bEnabled || !bHideDisabledVotes) + { + voteList.CallVoteList_VoteType = voteType; + voteList.CallVoteList_VoteEnabled = bEnabled; + + hVoteTypes.PushArray(voteList); + } +} + +//---------------------------------------------------------------------------- +// CSGO functions + +static bool CSGO_CheckVoteType(NativeVotesType voteType) +{ + switch(voteType) + { + case NativeVotesType_Custom_YesNo, + NativeVotesType_Restart, + NativeVotesType_Kick, + NativeVotesType_KickIdle, + NativeVotesType_KickScamming, + NativeVotesType_KickCheating, + NativeVotesType_ChgLevel, + NativeVotesType_NextLevel, + NativeVotesType_ScrambleNow, + NativeVotesType_SwapTeams, + NativeVotesType_Surrender, + NativeVotesType_Rematch, + NativeVotesType_Continue: + { + return true; + } + case NativeVotesType_Custom_Mult, NativeVotesType_NextLevelMult: + { + // Until/unless Valve fixes their menu code, this is false. + return false; + } + + } + + return false; +} + +static bool CSGO_CheckVotePassType(NativeVotesPassType passType) +{ + switch(passType) + { + case NativeVotesPass_Custom, NativeVotesPass_Restart, NativeVotesPass_Kick, + NativeVotesPass_ChgLevel, NativeVotesPass_NextLevel, NativeVotesPass_Extend, + NativeVotesPass_Scramble, NativeVotesPass_SwapTeams, NativeVotesPass_Surrender, + NativeVotesPass_Rematch, NativeVotesPass_Continue: + { + return true; + } + } + + return false; +} + +static bool CSGO_VoteTypeToTranslation(NativeVotesType voteType, char[] translation, int maxlength) +{ + bool bYesNo = true; + switch(voteType) + { + case NativeVotesType_Custom_Mult: + { + strcopy(translation, maxlength, CSGO_VOTE_CUSTOM); + bYesNo = false; + } + case NativeVotesType_Restart: + { + strcopy(translation, maxlength, CSGO_VOTE_RESTART_START); + } + case NativeVotesType_Kick: + { + strcopy(translation, maxlength, CSGO_VOTE_KICK_START); + } + case NativeVotesType_KickIdle: + { + strcopy(translation, maxlength, CSGO_VOTE_KICK_IDLE_START); + } + case NativeVotesType_KickScamming: + { + strcopy(translation, maxlength, CSGO_VOTE_KICK_SCAMMING_START); + } + case NativeVotesType_KickCheating: + { + strcopy(translation, maxlength, CSGO_VOTE_KICK_CHEATING_START); + } + case NativeVotesType_ChgLevel: + { + strcopy(translation, maxlength, CSGO_VOTE_CHANGELEVEL_START); + } + case NativeVotesType_NextLevel: + { + strcopy(translation, maxlength, CSGO_VOTE_NEXTLEVEL_SINGLE_START); + } + case NativeVotesType_NextLevelMult: + { + strcopy(translation, maxlength, CSGO_VOTE_NEXTLEVEL_MULTIPLE_START); + bYesNo = false; + } + case NativeVotesType_ScrambleNow: + { + strcopy(translation, maxlength, CSGO_VOTE_SCRAMBLE_START); + } + case NativeVotesType_SwapTeams: + { + strcopy(translation, maxlength, CSGO_VOTE_SWAPTEAMS_START); + } + case NativeVotesType_Surrender: + { + strcopy(translation, maxlength, CSGO_VOTE_SURRENDER_START); + } + case NativeVotesType_Rematch: + { + strcopy(translation, maxlength, CSGO_VOTE_REMATCH_START); + } + case NativeVotesType_Continue: + { + strcopy(translation, maxlength, CSGO_VOTE_CONTINUE_START); + } + default: + { + strcopy(translation, maxlength, CSGO_VOTE_CUSTOM); + } + } + + return bYesNo; +} + +static void CSGO_VotePassToTranslation(NativeVotesPassType passType, char[] translation, int maxlength) +{ + switch(passType) + { + case NativeVotesPass_Restart: + { + strcopy(translation, maxlength, CSGO_VOTE_RESTART_PASSED); + } + case NativeVotesPass_Kick: + { + strcopy(translation, maxlength, CSGO_VOTE_KICK_PASSED); + } + case NativeVotesPass_ChgLevel: + { + strcopy(translation, maxlength, CSGO_VOTE_CHANGELEVEL_PASSED); + } + case NativeVotesPass_NextLevel: + { + strcopy(translation, maxlength, CSGO_VOTE_NEXTLEVEL_PASSED); + } + case NativeVotesPass_Extend: + { + strcopy(translation, maxlength, CSGO_VOTE_NEXTLEVEL_EXTEND_PASSED); + } + case NativeVotesPass_Scramble: + { + strcopy(translation, maxlength, CSGO_VOTE_SCRAMBLE_PASSED); + } + case NativeVotesPass_SwapTeams: + { + strcopy(translation, maxlength, CSGO_VOTE_SWAPTEAMS_PASSED); + } + case NativeVotesPass_Surrender: + { + strcopy(translation, maxlength, CSGO_VOTE_SURRENDER_PASSED); + } + case NativeVotesPass_Rematch: + { + strcopy(translation, maxlength, CSGO_VOTE_REMATCH_PASSED); + } + case NativeVotesPass_Continue: + { + strcopy(translation, maxlength, CSGO_VOTE_CONTINUE_PASSED); + } + default: + { + strcopy(translation, maxlength, CSGO_VOTE_CUSTOM); + } + } +} + +static void CSGO_VoteTypeToVoteOtherTeamString(NativeVotesType voteType, char[] otherTeamString, int maxlength) +{ + switch(voteType) + { + case NativeVotesType_Kick: + { + strcopy(otherTeamString, maxlength, CSGO_VOTE_KICK_OTHERTEAM); + } + case NativeVotesType_Surrender: + { + strcopy(otherTeamString, maxlength, CSGO_VOTE_SURRENDER_OTHERTEAM); + } + case NativeVotesType_Continue: + { + strcopy(otherTeamString, maxlength, CSGO_VOTE_CONTINUE_OTHERTEAM); + } + default: + { + strcopy(otherTeamString, maxlength, CSGO_VOTE_UNIMPLEMENTED_OTHERTEAM); + } + } + +} + +static stock int TF2CSGO_GetVoteType(NativeVotesType voteType) +{ + int valveVoteType = ValveVote_Restart; + + switch (voteType) + { + case NativeVotesType_Custom_YesNo, NativeVotesType_Restart: + { + valveVoteType = ValveVote_Restart; + } + case NativeVotesType_Custom_Mult, NativeVotesType_NextLevel, NativeVotesType_NextLevelMult: + { + valveVoteType = ValveVote_NextLevel; + } + case NativeVotesType_Kick, NativeVotesType_KickIdle, NativeVotesType_KickScamming, NativeVotesType_KickCheating: + { + valveVoteType = ValveVote_Kick; + } + case NativeVotesType_ChgLevel: + { + valveVoteType = ValveVote_ChangeLevel; + } + case NativeVotesType_ScrambleNow, NativeVotesType_ScrambleEnd: + { + valveVoteType = ValveVote_Scramble; + } + case NativeVotesType_SwapTeams: + { + valveVoteType = ValveVote_SwapTeams; + } + } + + return valveVoteType; +} + +// The stocks below are used by the vote override system +// Not all are used by the plugin +static stock bool TF2_VoteTypeToVoteString(NativeVotesType voteType, char[] voteString, int maxlength) +{ + bool valid = false; + switch(voteType) + { + case NativeVotesType_Kick, NativeVotesType_KickCheating, NativeVotesType_KickIdle, NativeVotesType_KickScamming: + { + strcopy(voteString, maxlength, TF2CSGO_VOTE_STRING_KICK); + valid = true; + } + case NativeVotesType_ChgLevel: + { + strcopy(voteString, maxlength, TF2CSGO_VOTE_STRING_CHANGELEVEL); + valid = true; + } + case NativeVotesType_NextLevel: + { + strcopy(voteString, maxlength, TF2CSGO_VOTE_STRING_NEXTLEVEL); + valid = true; + } + case NativeVotesType_Restart: + { + strcopy(voteString, maxlength, TF2CSGO_VOTE_STRING_RESTART); + valid = true; + } + case NativeVotesType_ScrambleEnd, NativeVotesType_ScrambleNow: + { + strcopy(voteString, maxlength, TF2CSGO_VOTE_STRING_SCRAMBLE); + valid = true; + } + case NativeVotesType_Eternaween: + { + strcopy(voteString, maxlength, TF2_VOTE_STRING_ETERNAWEEN); + valid = true; + } + case NativeVotesType_AutoBalanceOn: + { + strcopy(voteString, maxlength, TF2_VOTE_STRING_AUTOBALANCE); + valid = true; + } + case NativeVotesType_AutoBalanceOff: + { + strcopy(voteString, maxlength, TF2_VOTE_STRING_AUTOBALANCE); + valid = true; + } + case NativeVotesType_ClassLimitsOn: + { + strcopy(voteString, maxlength, TF2_VOTE_STRING_CLASSLIMIT); + valid = true; + } + case NativeVotesType_ClassLimitsOff: + { + strcopy(voteString, maxlength, TF2_VOTE_STRING_CLASSLIMIT); + valid = true; + } + case NativeVotesType_Extend: + { + strcopy(voteString, maxlength, TF2_VOTE_STRING_EXTEND); + } + case NativeVotesType_ChgCivilian: + { + strcopy(voteString, maxlength, TF2CLASSIFIED_VOTE_STRING_CHANGE_CIVILIAN); + valid = true; + } + case NativeVotesType_ChgDifficulty: + { + strcopy(voteString, maxlength, FC_VOTE_STRING_CHANGE_DIFFICULTY); + valid = true; + } + case NativeVotesType_ChgGamemode: + { + strcopy(voteString, maxlength, FC_VOTE_STRING_CHANGE_GAMEMODE); + valid = true; + } + } + + return valid; +} + +static stock NativeVotesType TF2_VoteStringToVoteType(const char[] voteString) +{ + NativeVotesType voteType; + + if (StrEqual(voteString, TF2CSGO_VOTE_STRING_KICK, false)) + { + voteType = NativeVotesType_Kick; + } + else if (StrEqual(voteString, TF2CSGO_VOTE_STRING_CHANGELEVEL, false)) + { + voteType = NativeVotesType_ChgLevel; + } + else if (StrEqual(voteString, TF2CSGO_VOTE_STRING_NEXTLEVEL, false)) + { + voteType = NativeVotesType_NextLevel; + } + else if (StrEqual(voteString, TF2CSGO_VOTE_STRING_RESTART, false)) + { + voteType = NativeVotesType_Restart; + } + else if (StrEqual(voteString, TF2CSGO_VOTE_STRING_SCRAMBLE, false)) + { + voteType = NativeVotesType_ScrambleNow; + } + else if (StrEqual(voteString, TF2_VOTE_STRING_ETERNAWEEN, false)) + { + voteType = NativeVotesType_Eternaween; + } + else if (StrEqual(voteString, TF2_VOTE_STRING_AUTOBALANCE, false)) + { + voteType = g_Cvar_AutoBalance.BoolValue ? NativeVotesType_AutoBalanceOff : NativeVotesType_AutoBalanceOn; + } + else if (StrEqual(voteString, TF2_VOTE_STRING_CLASSLIMIT, false)) + { + voteType = g_Cvar_ClassLimit.IntValue ? NativeVotesType_ClassLimitsOff : NativeVotesType_ClassLimitsOn; + } + else if (StrEqual(voteString, TF2_VOTE_STRING_EXTEND, false)) + { + voteType = NativeVotesType_Extend; + } + else if (StrEqual(voteString, TF2_VOTE_STRING_CHANGEMISSION, false)) + { + voteType = NativeVotesType_ChgMission; + } + else if (StrEqual(voteString, TF2CLASSIFIED_VOTE_STRING_CHANGE_CIVILIAN, false)) + { + voteType = NativeVotesType_ChgCivilian; + } + else if (StrEqual(voteString, FC_VOTE_STRING_CHANGE_DIFFICULTY, false)) + { + voteType = NativeVotesType_ChgDifficulty; + } + else if (StrEqual(voteString, FC_VOTE_STRING_CHANGE_GAMEMODE, false)) + { + voteType = NativeVotesType_ChgGamemode; + } + else + { + voteType = NativeVotesType_None; + } + + return voteType; +} + +static stock NativeVotesOverride TF2_VoteTypeToVoteOverride(NativeVotesType voteType) +{ + NativeVotesOverride overrideType; + + switch (voteType) + { + case NativeVotesType_Kick, NativeVotesType_KickCheating, NativeVotesType_KickIdle, NativeVotesType_KickScamming: + { + overrideType = NativeVotesOverride_Kick; + } + case NativeVotesType_ChgLevel: + { + overrideType = NativeVotesOverride_ChgLevel; + } + case NativeVotesType_NextLevel: + { + overrideType = NativeVotesOverride_NextLevel; + } + case NativeVotesType_Restart: + { + overrideType = NativeVotesOverride_Restart; + } + case NativeVotesType_ScrambleEnd, NativeVotesType_ScrambleNow: + { + overrideType = NativeVotesOverride_Scramble; + } + case NativeVotesType_Eternaween: + { + overrideType = NativeVotesOverride_Eternaween; + } + case NativeVotesType_AutoBalanceOn, NativeVotesType_AutoBalanceOff: + { + overrideType = NativeVotesOverride_AutoBalance; + } + case NativeVotesType_ClassLimitsOn, NativeVotesType_ClassLimitsOff: + { + overrideType = NativeVotesOverride_ClassLimits; + } + case NativeVotesType_Extend: + { + overrideType = NativeVotesOverride_Extend; + } + case NativeVotesType_ChgCivilian: + { + overrideType = NativeVotesOverride_ChgCivilian; + } + case NativeVotesType_ChgDifficulty: + { + overrideType = NativeVotesOverride_ChgDifficulty; + } + case NativeVotesType_ChgGamemode: + { + overrideType = NativeVotesOverride_ChgGamemode; + } + default: + { + overrideType = NativeVotesOverride_None; + } + } + + return overrideType; +} + +static stock NativeVotesType TF2_VoteOverrideToVoteType(NativeVotesOverride overrideType) +{ + NativeVotesType voteType; + + switch (overrideType) + { + case NativeVotesOverride_Restart: + { + voteType = NativeVotesType_Restart; + } + case NativeVotesOverride_Kick: + { + voteType = NativeVotesType_Kick; + } + case NativeVotesOverride_ChgLevel: + { + voteType = NativeVotesType_ChgLevel; + } + case NativeVotesOverride_NextLevel: + { + voteType = NativeVotesType_NextLevel; + } + case NativeVotesOverride_Scramble: + { + voteType = NativeVotesType_ScrambleNow; + } + case NativeVotesOverride_ChgMission: + { + voteType = NativeVotesType_ChgMission; + } + case NativeVotesOverride_Eternaween: + { + voteType = NativeVotesType_Eternaween; + } + case NativeVotesOverride_AutoBalance: + { + voteType = NativeVotesType_AutoBalanceOn; + } + case NativeVotesOverride_ClassLimits: + { + voteType = NativeVotesType_ClassLimitsOn; + } + case NativeVotesOverride_Extend: + { + voteType = NativeVotesType_Extend; + } + case NativeVotesOverride_ChgCivilian: + { + voteType = NativeVotesType_ChgCivilian; + } + case NativeVotesOverride_ChgDifficulty: + { + voteType = NativeVotesType_ChgDifficulty; + } + case NativeVotesOverride_ChgGamemode: + { + voteType = NativeVotesType_ChgGamemode; + } + default: + { + voteType = NativeVotesType_None; + } + } + + return voteType; +} + +static stock NativeVotesOverride TF2_VoteStringToVoteOverride(const char[] voteString) +{ + NativeVotesOverride overrideType; + + if (StrEqual(voteString, TF2CSGO_VOTE_STRING_KICK, false)) + { + overrideType = NativeVotesOverride_Kick; + } + else if (StrEqual(voteString, TF2CSGO_VOTE_STRING_CHANGELEVEL, false)) + { + overrideType = NativeVotesOverride_ChgLevel; + } + else if (StrEqual(voteString, TF2CSGO_VOTE_STRING_NEXTLEVEL, false)) + { + overrideType = NativeVotesOverride_NextLevel; + } + else if (StrEqual(voteString, TF2CSGO_VOTE_STRING_RESTART, false)) + { + overrideType = NativeVotesOverride_Restart; + } + else if (StrEqual(voteString, TF2CSGO_VOTE_STRING_SCRAMBLE, false)) + { + overrideType = NativeVotesOverride_Scramble; + } + else if (StrEqual(voteString, TF2_VOTE_STRING_ETERNAWEEN, false)) + { + overrideType = NativeVotesOverride_Eternaween; + } + else if (StrEqual(voteString, TF2_VOTE_STRING_AUTOBALANCE, false)) + { + overrideType = NativeVotesOverride_AutoBalance; + } + else if (StrEqual(voteString, TF2_VOTE_STRING_CLASSLIMIT, false)) + { + overrideType = NativeVotesOverride_ClassLimits; + } + else if (StrEqual(voteString, TF2_VOTE_STRING_EXTEND, false)) + { + overrideType = NativeVotesOverride_Extend; + } + else if (StrEqual(voteString, TF2_VOTE_STRING_CHANGEMISSION, false)) + { + overrideType = NativeVotesOverride_ChgMission; + } + else if (StrEqual(voteString, TF2CLASSIFIED_VOTE_STRING_CHANGE_CIVILIAN, false)) + { + overrideType = NativeVotesOverride_ChgCivilian; + } + else if (StrEqual(voteString, FC_VOTE_STRING_CHANGE_DIFFICULTY, false)) + { + overrideType = NativeVotesOverride_ChgDifficulty; + } + else if (StrEqual(voteString, FC_VOTE_STRING_CHANGE_GAMEMODE, false)) + { + overrideType = NativeVotesOverride_ChgGamemode; + } + else + { + overrideType = NativeVotesOverride_None; + } + +#if defined LOG + LogMessage("Resolved \"%s\" to %d", voteString, overrideType); +#endif + + return overrideType; +} + +static stock bool TF2_OverrideTypeToVoteString(NativeVotesOverride overrideType, char[] voteString, int maxlength) +{ + bool valid = false; + switch(overrideType) + { + case NativeVotesOverride_Kick: + { + strcopy(voteString, maxlength, TF2CSGO_VOTE_STRING_KICK); + valid = true; + } + case NativeVotesOverride_ChgLevel: + { + strcopy(voteString, maxlength, TF2CSGO_VOTE_STRING_CHANGELEVEL); + valid = true; + } + case NativeVotesOverride_NextLevel: + { + strcopy(voteString, maxlength, TF2CSGO_VOTE_STRING_NEXTLEVEL); + valid = true; + } + case NativeVotesOverride_Restart: + { + strcopy(voteString, maxlength, TF2CSGO_VOTE_STRING_RESTART); + valid = true; + } + case NativeVotesOverride_Scramble: + { + strcopy(voteString, maxlength, TF2CSGO_VOTE_STRING_SCRAMBLE); + valid = true; + } + case NativeVotesOverride_Eternaween: + { + strcopy(voteString, maxlength, TF2_VOTE_STRING_ETERNAWEEN); + valid = true; + } + case NativeVotesOverride_AutoBalance: + { + strcopy(voteString, maxlength, TF2_VOTE_STRING_AUTOBALANCE); + valid = true; + } + case NativeVotesOverride_ClassLimits: + { + strcopy(voteString, maxlength, TF2_VOTE_STRING_CLASSLIMIT); + valid = true; + } + case NativeVotesOverride_Extend: + { + strcopy(voteString, maxlength, TF2_VOTE_STRING_EXTEND); + } + case NativeVotesOverride_ChgMission: + { + strcopy(voteString, maxlength, TF2_VOTE_STRING_CHANGEMISSION); + } + case NativeVotesOverride_ChgCivilian: + { + strcopy(voteString, maxlength, TF2CLASSIFIED_VOTE_STRING_CHANGE_CIVILIAN); + valid = true; + } + case NativeVotesOverride_ChgDifficulty: + { + strcopy(voteString, maxlength, FC_VOTE_STRING_CHANGE_DIFFICULTY); + valid = true; + } + case NativeVotesOverride_ChgGamemode: + { + strcopy(voteString, maxlength, FC_VOTE_STRING_CHANGE_GAMEMODE); + valid = true; + } + } + + return valid; +} + +static stock bool TF2_OverrideTypeToTranslationString(NativeVotesOverride overrideType, char[] translationString, int maxlength) +{ + bool valid = false; + switch(overrideType) + { + case NativeVotesOverride_Kick: + { + strcopy(translationString, maxlength, TF2_VOTE_MENU_KICK); + valid = true; + } + case NativeVotesOverride_ChgLevel: + { + strcopy(translationString, maxlength, TF2_VOTE_MENU_CHANGELEVEL); + valid = true; + } + case NativeVotesOverride_NextLevel: + { + strcopy(translationString, maxlength, TF2_VOTE_MENU_NEXTLEVEL); + valid = true; + } + case NativeVotesOverride_Restart: + { + strcopy(translationString, maxlength, TF2_VOTE_MENU_RESTART); + valid = true; + } + case NativeVotesOverride_Scramble: + { + strcopy(translationString, maxlength, TF2_VOTE_MENU_SCRAMBLE); + valid = true; + } + case NativeVotesOverride_Eternaween: + { + strcopy(translationString, maxlength, TF2_VOTE_MENU_ETERNAWEEN); + valid = true; + } + case NativeVotesOverride_AutoBalance: + { + strcopy(translationString, maxlength, GetConVarBool(g_Cvar_AutoBalance) ? TF2_VOTE_MENU_AUTOBALANCE_OFF : TF2_VOTE_MENU_AUTOBALANCE_ON); + valid = true; + } + case NativeVotesOverride_ClassLimits: + { + strcopy(translationString, maxlength, GetConVarInt(g_Cvar_ClassLimit) ? TF2_VOTE_MENU_CLASSLIMIT_OFF : TF2_VOTE_MENU_CLASSLIMIT_ON); + valid = true; + } + case NativeVotesOverride_Extend: + { + strcopy(translationString, maxlength, TF2_VOTE_MENU_EXTEND); + } + case NativeVotesOverride_ChgCivilian: + { + strcopy(translationString, maxlength, TF2CLASSIFIED_VOTE_MENU_CHANGE_CIVILIAN); + valid = true; + } + case NativeVotesOverride_ChgDifficulty: + { + strcopy(translationString, maxlength, FC_VOTE_MENU_CHANGE_DIFFICULTY); + valid = true; + } + case NativeVotesOverride_ChgGamemode: + { + strcopy(translationString, maxlength, FC_VOTE_MENU_CHANGE_GAMEMODE); + valid = true; + } + } + + return valid; +} + +stock bool Game_IsVoteTypeCustom(NativeVotesType voteType) +{ + switch(voteType) + { + case NativeVotesType_Custom_YesNo, NativeVotesType_Custom_Mult: + { + return true; + } + } + + return false; +} + +stock bool Game_IsVoteTypeYesNo(NativeVotesType voteType) +{ + switch(voteType) + { + case NativeVotesType_Custom_Mult, NativeVotesType_NextLevelMult: + { + return false; + } + } + + return true; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/nativevotes-basecommands.sp b/addons/sourcemod/scripting/nativevotes_basecommands.sp similarity index 63% rename from addons/sourcemod/scripting/nativevotes-basecommands.sp rename to addons/sourcemod/scripting/nativevotes_basecommands.sp index 8696a54..aff94d0 100644 --- a/addons/sourcemod/scripting/nativevotes-basecommands.sp +++ b/addons/sourcemod/scripting/nativevotes_basecommands.sp @@ -1,188 +1,221 @@ -/** - * vim: set ts=4 : - * ============================================================================= - * NativeVotes Basecommands Plugin - * Provides cancelvote and revote functionality for NativeVotes - * - * NativeVotes (C) 2011-2014 Ross Bemrose (Powerlord). All rights reserved. - * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. - * ============================================================================= - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License, version 3.0, as published by the - * Free Software Foundation. - * - * This program 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 the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - * - * As a special exception, AlliedModders LLC gives you permission to link the - * code of this program (as well as its derivative works) to "Half-Life 2," the - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software - * by the Valve Corporation. You must obey the GNU General Public License in - * all respects for all other code used. Additionally, AlliedModders LLC grants - * this exception to all derivative works. AlliedModders LLC defines further - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), - * or . - * - * Version: $Id$ - */ - -#include -#include - -#undef REQUIRE_PLUGIN -#include - -#define VERSION "1.1" -#pragma newdecls required -#pragma semicolon 1 - -TopMenu hTopMenu; - -public Plugin myinfo = -{ - name = "NativeVotes Basic Commands", - author = "Powerlord and AlliedModders LLC", - description = "Revote and Cancel support for NativeVotes", - version = VERSION, - url = "https://forums.alliedmods.net/showthread.php?t=208008" -} - -public void OnPluginStart() -{ - LoadTranslations("core.phrases"); - LoadTranslations("common.phrases"); - - AddCommandListener(Command_CancelVote, "sm_cancelvote"); - AddCommandListener(Command_ReVote, "sm_revote"); -} - -bool PerformCancelVote(int client) -{ - if (!NativeVotes_IsVoteInProgress()) - { - return false; - } - - ShowActivity2(client, "[NV] ", "%t", "Cancelled Vote"); - - NativeVotes_Cancel(); - return true; -} - -public Action Command_CancelVote(int client, const char[] command, int argc) -{ - if (!CheckCommandAccess(client, "sm_cancelvote", ADMFLAG_VOTE)) - { - if (IsVoteInProgress()) - { - // Let basecommands handle it - return Plugin_Continue; - } - - ReplyToCommand(client, "%t", "No Access"); - return Plugin_Stop; - } - - if (PerformCancelVote(client)) - { - return Plugin_Stop; - } - else - { - return Plugin_Continue; - } -} - -public void AdminMenu_CancelVote(Handle topmenu, - TopMenuAction action, - TopMenuObject object_id, - int param, - char[] buffer, - int maxlength) -{ - if (action == TopMenuAction_DisplayOption) - { - Format(buffer, maxlength, "%T", "Cancel vote", param); - } - else if (action == TopMenuAction_SelectOption) - { - PerformCancelVote(param); - RedisplayAdminMenu(topmenu, param); - } - else if (action == TopMenuAction_DrawOption) - { - buffer[0] = NativeVotes_IsVoteInProgress() ? ITEMDRAW_DEFAULT : ITEMDRAW_IGNORE; - } -} - -public Action Command_ReVote(int client, const char[] command, int argc) -{ - if (client == 0) - { - return Plugin_Continue; - } - - if (!NativeVotes_IsVoteInProgress()) - { - return Plugin_Continue; - } - - if (!NativeVotes_IsClientInVotePool(client)) - { - if (IsVoteInProgress()) - { - // Let basecommands handle it - return Plugin_Continue; - } - - ReplyToCommand(client, "[NV] %t", "Cannot participate in vote"); - return Plugin_Stop; - } - - if (NativeVotes_RedrawClientVote(client)) - { - return Plugin_Stop; - } - else if (!IsVoteInProgress()) - { - ReplyToCommand(client, "[NV] %t", "Cannot change vote"); - return Plugin_Stop; - } - - return Plugin_Continue; -} - -public void OnAdminMenuReady(Handle aTopMenu) -{ - TopMenu topmenu = TopMenu.FromHandle(aTopMenu); - - /* Block us from being called twice */ - if (topmenu == hTopMenu) - { - return; - } - - /* Save the Handle */ - hTopMenu = topmenu; - - TopMenuObject voting_commands = hTopMenu.FindCategory(ADMINMENU_VOTINGCOMMANDS); - - if (voting_commands != INVALID_TOPMENUOBJECT) - { - hTopMenu.AddItem("sm_cancelvote", AdminMenu_CancelVote, voting_commands, "sm_cancelvote", ADMFLAG_VOTE); - } -} - -public void OnLibraryRemoved(const char[] name) -{ - if (strcmp(name, "adminmenu") == 0) - { - hTopMenu = null; - } -} +/** + * vim: set ts=4 : + * ============================================================================= + * NativeVotes Basecommands Plugin + * Provides cancelvote and revote functionality for NativeVotes + * + * NativeVotes (C) 2011-2014 Ross Bemrose (Powerlord). All rights reserved. + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program 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 the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#include +#include + +#undef REQUIRE_PLUGIN +#include + +#define PLUGIN_PREFIX "[\x04NativeVotes\x01]" + +TopMenu g_TopMenu; + +public Plugin myinfo = +{ + name = "NativeVotes | Basic Commands", + author = "Powerlord and AlliedModders LLC", + description = "Revote and Cancel support for NativeVotes", + version = "26w15a", + url = "https://github.com/Heapons/sourcemod-nativevotes-updated/" +} + +public void OnPluginStart() +{ + LoadTranslations("core.phrases"); + LoadTranslations("common.phrases"); + + AddCommandListener(Command_CancelVote, "sm_cancelvote"); + AddCommandListener(Command_ReVote, "sm_revote"); + RegConsoleCmd("sm_callvote", Command_CallVote, "Start a vote on an issue."); +} + +bool PerformCancelVote(int client) +{ + if (!NativeVotes_IsVoteInProgress()) + { + return false; + } + + CShowActivity2(client, PLUGIN_PREFIX ... " ", "%t", "Cancelled Vote"); + + NativeVotes_Cancel(); + return true; +} + +public void OnClientSayCommand_Post(int client, const char[] command, const char[] sArgs) +{ + if (!client || IsChatTrigger()) + { + return; + } + + if (strcmp(sArgs, "callvote", false) == 0) + { + ReplySource old = SetCmdReplySource(SM_REPLY_TO_CHAT); + + Command_CallVote(client, 0); + + SetCmdReplySource(old); + } +} + +public Action Command_CancelVote(int client, const char[] command, int argc) +{ + if (!CheckCommandAccess(client, "sm_cancelvote", ADMFLAG_VOTE)) + { + if (IsVoteInProgress()) + { + // Let basecommands handle it + return Plugin_Continue; + } + + CReplyToCommand(client, "%t", "No Access"); + return Plugin_Stop; + } + + if (PerformCancelVote(client)) + { + return Plugin_Stop; + } + else + { + return Plugin_Continue; + } +} + +public void AdminMenu_CancelVote(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) +{ + switch (action) + { + case TopMenuAction_DisplayOption: + { + Format(buffer, maxlength, "%T", "Cancel vote", param); + } + case TopMenuAction_SelectOption: + { + PerformCancelVote(param); + RedisplayAdminMenu(topmenu, param); + } + case TopMenuAction_DrawOption: + { + buffer[0] = NativeVotes_IsVoteInProgress() ? ITEMDRAW_DEFAULT : ITEMDRAW_IGNORE; + } + } +} + +public Action Command_ReVote(int client, const char[] command, int argc) +{ +/* + if (FindSendPropInfo("CVoteController", "m_nVoteIdx") > 0) + { + LogError("[NativeVotes] Re-voting is buggy in Team Fortress 2 and SDK 2025 mods."); + return Plugin_Continue; + } +*/ + if (client <= 0) + { + return Plugin_Continue; + } + + if (!NativeVotes_IsVoteInProgress()) + { + return Plugin_Continue; + } + + if (!NativeVotes_IsClientInVotePool(client)) + { + if (IsVoteInProgress()) + { + // Let basecommands handle it + return Plugin_Continue; + } + + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Cannot participate in vote"); + return Plugin_Stop; + } + + if (NativeVotes_RedrawClientVote(client)) + { + return Plugin_Stop; + } + else if (!IsVoteInProgress()) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Cannot change vote"); + return Plugin_Stop; + } + + return Plugin_Continue; +} + +public Action Command_CallVote(int client, int args) +{ + if (client <= 0) + { + return Plugin_Continue; + } + + FakeClientCommand(client, "callvote"); + + return Plugin_Continue; +} + +public void OnAdminMenuReady(Handle aTopMenu) +{ + TopMenu topmenu = TopMenu.FromHandle(aTopMenu); + + /* Block us from being called twice */ + if (topmenu == g_TopMenu) + { + return; + } + + /* Save the Handle */ + g_TopMenu = topmenu; + + TopMenuObject voting_commands = g_TopMenu.FindCategory(ADMINMENU_VOTINGCOMMANDS); + + if (voting_commands != INVALID_TOPMENUOBJECT) + { + g_TopMenu.AddItem("sm_cancelvote", AdminMenu_CancelVote, voting_commands, "sm_cancelvote", ADMFLAG_VOTE); + } +} + +public void OnLibraryRemoved(const char[] name) +{ + if (strcmp(name, "adminmenu") == 0) + { + g_TopMenu = null; + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/nativevotes_basevotes.sp b/addons/sourcemod/scripting/nativevotes_basevotes.sp new file mode 100644 index 0000000..32ce422 --- /dev/null +++ b/addons/sourcemod/scripting/nativevotes_basevotes.sp @@ -0,0 +1,868 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * NativeVotes Basic Votes Plugin + * Implements basic vote commands using NativeVotes. + * Based on the SourceMod version. + * + * NativeVotes (C)2011-2014 Ross Bemrose (Powerlord). All rights reserved. + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program 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 the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#include +#include + +#undef REQUIRE_PLUGIN +#include +#include +#include + +#define PLUGIN_PREFIX "[\x04NativeVotes\x01]" + +public Plugin myinfo = +{ + name = "NativeVotes | Basic Votes", + author = "Powerlord and AlliedModders LLC", + description = "NativeVotes Basic Vote Commands", + version = "26w15a", + url = "https://github.com/Heapons/sourcemod-nativevotes-updated/" +}; + +#define VOTE_NO "###no###" +#define VOTE_YES "###yes###" + +// Handle g_hVoteMenu; + +enum +{ + vote_map, + vote_kick, + vote_ban, + vote_question, + + sv_vote_issue_kick_allowed, + sv_vote_issue_kick_allowed_mvm, + sv_vote_kick_ban_duration, + sv_vote_timer_duration, + + MAX_CONVARS +} + +ConVar g_ConVars[MAX_CONVARS]; +// ConVar g_Cvar_VoteSay; + +enum VoteType +{ + map, + kick, + ban, + question +} + +VoteType g_VoteType = question; + +// Menu API does not provide us with a way to pass multiple peices of data with a single +// choice, so some globals are used to hold stuff. +// +#define VOTE_CLIENTID 0 +#define VOTE_USERID 1 + +int g_VoteClient[2]; // Holds the target's client id and user id + +#define VOTE_NAME 0 +#define VOTE_AUTHID 1 +#define VOTE_IP 2 + +char g_VoteInfo[3][65]; // Holds the target's name, authid, and IP +char g_VoteArg[256]; // Used to hold ban/kick reasons or vote questions + +Handle g_TopMenu = INVALID_HANDLE; + +bool g_KickVoteRegistered = false; +bool g_isMvM = false; + +// Libraries +bool g_NativeVotes; +bool g_SourceBans; + +//Handle g_Cvar_NativeVotesMenu = INVALID_HANDLE; + +#include "nativevotes_basevotes/votekick.sp" +#include "nativevotes_basevotes/voteban.sp" +#include "nativevotes_basevotes/votemap.sp" + +public void OnPluginStart() +{ + LoadTranslations("common.phrases"); + LoadTranslations("basevotes.phrases"); + LoadTranslations("plugin.basecommands"); + LoadTranslations("basebans.phrases"); + + RegAdminCmd("sm_votemap", Command_Votemap, ADMFLAG_VOTE|ADMFLAG_CHANGEMAP, "sm_votemap [mapname2] ... [mapname5] "); + RegAdminCmd("sm_votekick", Command_Votekick, ADMFLAG_VOTE|ADMFLAG_KICK, "sm_votekick [reason]"); + RegAdminCmd("sm_voteban", Command_Voteban, ADMFLAG_VOTE|ADMFLAG_BAN, "sm_voteban [reason]"); + RegAdminCmd("sm_vote", Command_Vote, ADMFLAG_VOTE, "sm_vote [option1] [option2] ... [option5]"); + + g_ConVars[vote_map] = CreateConVar("sm_vote_map", "0.60", "percent required for successful map vote.", 0, true, 0.05, true, 1.0); + g_ConVars[vote_kick] = CreateConVar("sm_vote_kick", "0.60", "percent required for successful kick vote.", 0, true, 0.05, true, 1.0); + g_ConVars[vote_ban] = CreateConVar("sm_vote_ban", "0.60", "percent required for successful ban vote.", 0, true, 0.05, true, 1.0); + + g_ConVars[sv_vote_issue_kick_allowed] = FindConVar("sv_vote_issue_kick_allowed"); + g_ConVars[sv_vote_issue_kick_allowed].AddChangeHook(ConVar_VoteKickAllowed); + g_ConVars[sv_vote_issue_kick_allowed_mvm] = FindConVar("sv_vote_issue_kick_allowed_mvm"); + g_ConVars[sv_vote_issue_kick_allowed_mvm].AddChangeHook(ConVar_VoteKickAllowed); + g_ConVars[sv_vote_kick_ban_duration] = FindConVar("sv_vote_kick_ban_duration"); + g_ConVars[sv_vote_timer_duration] = FindConVar("sv_vote_timer_duration"); + + g_SelectedMaps = CreateArray(PLATFORM_MAX_PATH); + + g_MapList = CreateMenu(MenuHandler_Map, MenuAction_DrawItem|MenuAction_Display); + SetMenuTitle(g_MapList, "%T", "Please select a map", LANG_SERVER); + SetMenuExitBackButton(g_MapList, true); + + char mapListPath[PLATFORM_MAX_PATH]; + BuildPath(Path_SM, mapListPath, sizeof(mapListPath), "configs/adminmenu_maplist.ini"); + SetMapListCompatBind("sm_votemap menu", mapListPath); +} + +public void OnMapStart() +{ + g_isMvM = FindSendPropInfo("CTFGameRulesProxy", "m_bPlayingMannVsMachine") > 0 && GameRules_GetProp("m_bPlayingMannVsMachine"); +} + +void ConVar_VoteKickAllowed(ConVar convar, const char[] oldValue, const char[] newValue) +{ + if (!g_NativeVotes) + { + return; + } + + if (g_ConVars[sv_vote_issue_kick_allowed].BoolValue || (g_isMvM && g_ConVars[sv_vote_issue_kick_allowed_mvm].BoolValue)) + { + RegisterKickVoteCommand(); + } + else + { + UnregisterKickVoteCommand(); + } +} + +void RegisterKickVoteCommand() +{ + if (!g_KickVoteRegistered && g_NativeVotes) + { + bool isAllowed = false; + if (g_ConVars[sv_vote_issue_kick_allowed] != null && g_ConVars[sv_vote_issue_kick_allowed_mvm] != null) + { + isAllowed = g_isMvM ? g_ConVars[sv_vote_issue_kick_allowed_mvm].BoolValue : + g_ConVars[sv_vote_issue_kick_allowed].BoolValue; + } + if (isAllowed) + { + NativeVotes_RegisterVoteCommand(NativeVotesOverride_Kick, Menu_Kick); + g_KickVoteRegistered = true; + } + } +} + +void UnregisterKickVoteCommand() +{ + if (g_KickVoteRegistered && g_NativeVotes) + { + NativeVotes_UnregisterVoteCommand(NativeVotesOverride_Kick, Menu_Kick); + g_KickVoteRegistered = false; + } +} + +public void OnPluginEnd() +{ + UnregisterKickVoteCommand(); +} + +public void OnAllPluginsLoaded() +{ + if (FindPluginByFile("basevotes.smx") != INVALID_HANDLE) + { + LogMessage("Unloading basevotes to prevent conflicts..."); + ServerCommand("sm plugins unload basevotes"); + + char oldPath[PLATFORM_MAX_PATH], newPath[PLATFORM_MAX_PATH]; + + BuildPath(Path_SM, oldPath, sizeof(oldPath), "plugins/basevotes.smx"); + BuildPath(Path_SM, newPath, sizeof(newPath), "plugins/disabled/basevotes.smx"); + if (RenameFile(newPath, oldPath)) + { + LogMessage("Moving basevotes to disabled."); + } + } + + /* Account for late loading */ + Handle topmenu; + if (LibraryExists("adminmenu") && ((topmenu = GetAdminTopMenu()) != INVALID_HANDLE)) + { + OnAdminMenuReady(topmenu); + } + + g_NativeVotes = LibraryExists("nativevotes") && NativeVotes_IsVoteTypeSupported(NativeVotesType_Custom_YesNo); + g_SourceBans = LibraryExists("sourcebans++"); +} + +public void OnLibraryAdded(const char[] name) +{ + Handle topmenu; + if (StrEqual(name, "adminmenu") && ((topmenu = GetAdminTopMenu()) != INVALID_HANDLE)) + { + OnAdminMenuReady(topmenu); + } + else if (StrEqual(name, "nativevotes") && NativeVotes_IsVoteTypeSupported(NativeVotesType_Custom_YesNo)) + { + g_NativeVotes = true; + RegisterKickVoteCommand(); + } + else if (StrEqual(name, "sourcebans++")) + { + g_SourceBans = true; + } +} + +public void OnLibraryRemoved(const char[] name) +{ + if (StrEqual(name, "adminmenu")) + { + g_TopMenu = INVALID_HANDLE; + } + else if (StrEqual(name, "nativevotes")) + { + g_NativeVotes = false; + UnregisterKickVoteCommand(); + } + else if (StrEqual(name, "sourcebans++")) + { + g_SourceBans = false; + } +} + +public void OnConfigsExecuted() +{ + g_mapCount = LoadMapList(g_MapList); + if (g_KickVoteRegistered) + { + UnregisterKickVoteCommand(); + RegisterKickVoteCommand(); + } +} + +public Action Menu_Kick(int client, NativeVotesOverride overrideType, const char[] voteArgument) +{ + int target = 0; + if (strlen(voteArgument) > 0) + { + target = FindTarget(client, voteArgument); + } + + if (!CanUserTarget(client, target)) + { + NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_CantKickAdmin); + CPrintToChat(client, "%t", "Unable to target"); + return Plugin_Stop; + } + + ReplySource old = SetCmdReplySource(SM_REPLY_TO_CHAT); + + if (target > 0) + { + DisplayVoteKickMenu(client, target); + } + + SetCmdReplySource(old); + + return Plugin_Stop; +} + +public void OnAdminMenuReady(Handle topmenu) +{ + /* Block us from being called twice */ + if (topmenu == g_TopMenu) + { + return; + } + + /* Save the Handle */ + g_TopMenu = topmenu; + + /* Build the "Voting Commands" category */ + TopMenuObject voting_commands = FindTopMenuCategory(g_TopMenu, ADMINMENU_VOTINGCOMMANDS); + + if (voting_commands != INVALID_TOPMENUOBJECT) + { + AddToTopMenu(g_TopMenu, + "sm_votekick", + TopMenuObject_Item, + AdminMenu_VoteKick, + voting_commands, + "sm_votekick", + ADMFLAG_VOTE|ADMFLAG_KICK); + + AddToTopMenu(g_TopMenu, + "sm_voteban", + TopMenuObject_Item, + AdminMenu_VoteBan, + voting_commands, + "sm_voteban", + ADMFLAG_VOTE|ADMFLAG_BAN); + + AddToTopMenu(g_TopMenu, + "sm_votemap", + TopMenuObject_Item, + AdminMenu_VoteMap, + voting_commands, + "sm_votemap", + ADMFLAG_VOTE|ADMFLAG_CHANGEMAP); + } +} + +public Action Command_Vote(int client, int args) +{ + if (args < 1) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " Usage: sm_vote <title> [option1] [option2] ... [option5]"); + return Plugin_Handled; + } + + if (Internal_IsVoteInProgress()) + { + CReplyToCommand(client, "%t", "Vote in Progress"); + return Plugin_Handled; + } + + if (!TestVoteDelay(client)) + { + return Plugin_Handled; + } + + char text[256]; + GetCmdArgString(text, sizeof(text)); + + char answers[5][64]; + int answerCount = 0; + int len = BreakString(text, g_VoteArg, sizeof(g_VoteArg)); + int pos = len; + + while (args > 1 && pos != -1 && answerCount < 5) + { + pos = BreakString(text[len], answers[answerCount], sizeof(answers[])); + answerCount++; + + if (pos != -1) + { + len += pos; + } + } + LogAction(client, -1, "\"%L\" initiated a generic vote.", client); + CShowActivity2(client, PLUGIN_PREFIX ... " ", "%t", "Initiate Vote", g_VoteArg); + g_VoteType = question; + Handle voteMenu; + + if (g_NativeVotes) + { + NativeVotesType nVoteType = answerCount == 0 ? NativeVotesType_Custom_YesNo : NativeVotesType_Custom_Mult; + voteMenu = NativeVotes_Create(Handler_NativeVoteCallback, nVoteType, view_as<MenuAction>(MENU_ACTIONS_ALL)); + if (nVoteType == NativeVotesType_Custom_Mult) + { + for (int i = 0; i < answerCount; i++) + { + NativeVotes_AddItem(voteMenu, answers[i], answers[i]); + } + } + NativeVotes_SetTitle(voteMenu, g_VoteArg); + NativeVotes_DisplayToAll(voteMenu, g_ConVars[sv_vote_timer_duration].IntValue); + } + else + { + voteMenu = CreateMenu(Handler_VoteCallback, view_as<MenuAction>(MENU_ACTIONS_ALL)); + SetMenuTitle(voteMenu, g_VoteArg); + + if (answerCount == 0) + { + AddMenuItem(voteMenu, VOTE_YES, "Yes"); + AddMenuItem(voteMenu, VOTE_NO, "No"); + } + else + { + for (int i = 0; i < answerCount; i++) + { + AddMenuItem(voteMenu, answers[i], answers[i]); + } + } + + SetMenuExitButton(voteMenu, false); + VoteMenuToAll(voteMenu, g_ConVars[sv_vote_timer_duration].IntValue); + } + return Plugin_Handled; +} + +public Handler_VoteCallback(Handle:menu, MenuAction:action, param1, param2) +{ + switch (action) + { + case MenuAction_End: + { + CloseHandle(menu); + } + case MenuAction_Display: + { + if (g_VoteType != question) + { + char title[64]; + GetMenuTitle(menu, title, sizeof(title)); + + char buffer[255]; + Format(buffer, sizeof(buffer), "%T", title, param1, g_VoteInfo[VOTE_NAME]); + + Handle panel = Handle:param2; + SetPanelTitle(panel, buffer); + } + } + case MenuAction_DisplayItem: + { + char display[64]; + GetMenuItem(menu, param2, "", 0, _, display, sizeof(display)); + + if (strcmp(display, "No") == 0 || strcmp(display, "Yes") == 0) + { + char buffer[255]; + Format(buffer, sizeof(buffer), "%T", display, param1); + + return RedrawMenuItem(buffer); + } + } + case MenuAction_VoteCancel: + { + if (param1 == VoteCancel_NoVotes) + { + CPrintToChatAll("%t", "No Votes Cast"); + } + } + case MenuAction_VoteEnd: + { + char item[64], display[64]; + float percent, limit; + int votes, totalVotes; + + GetMenuVoteInfo(param2, votes, totalVotes); + GetMenuItem(menu, param1, item, sizeof(item), _, display, sizeof(display)); + + if (strcmp(item, VOTE_NO) == 0 && param1 == 1) + { + votes = totalVotes - votes; // Reverse the votes to be in relation to the Yes option. + } + + percent = GetVotePercent(votes, totalVotes); + + if (g_VoteType != question) + { + limit = GetConVarFloat(g_ConVars[g_VoteType]); + } + + /* :TODO: g_VoteClient[userid] needs to be checked */ + + // A multi-argument vote is "always successful", but have to check if its a Yes/No vote. + if ((strcmp(item, VOTE_YES) == 0 && FloatCompare(percent,limit) < 0 && param1 == 0) || (strcmp(item, VOTE_NO) == 0 && param1 == 1)) + { + /* :TODO: g_VoteClient[userid] should be used here and set to -1 if not applicable. + */ + LogAction(-1, -1, "Vote failed."); + CPrintToChatAll("%t", "Vote Failed", RoundToNearest(100.0*limit), RoundToNearest(100.0*percent), totalVotes); + } + else + { + CPrintToChatAll("%t", "Vote Successful", RoundToNearest(100.0*percent), totalVotes); + + int client = GetClientOfUserId(g_VoteClient[VOTE_USERID]); + int r, g, b, a, color; + GetEntityRenderColor(client, r, g, b, a); + color = (r << 16) | (g << 8) | b; + if (color != 0xFFFFFF) + { + Format(g_VoteInfo[VOTE_NAME], sizeof(g_VoteInfo[VOTE_NAME]), "{#%06X}%N\x01", color, client); + } + else + { + Format(g_VoteInfo[VOTE_NAME], sizeof(g_VoteInfo[VOTE_NAME]), "{teamcolor}%N\x01", client); + } + + switch (g_VoteType) + { + case question: + { + if (strcmp(item, VOTE_NO) == 0 || strcmp(item, VOTE_YES) == 0) + { + for (int i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i) && !IsFakeClient(i)) + { + Format(item, sizeof(item), "%T", display, i); + CPrintToChat(i, "%t", "Vote End", g_VoteArg, item); + } + } + } + else + { + CPrintToChatAll("%t", "Vote End", g_VoteArg, item); + } + } + case map: + { + LogAction(-1, -1, "Changing map to %s due to vote.", item); + CPrintToChatAll("%t", "Changing map", item); + DataPack dp; + CreateDataTimer(5.0, Timer_ChangeMap, dp); + WritePackString(dp, item); + } + case kick: + { + if (g_VoteArg[0] == '\0') + { + strcopy(g_VoteArg, sizeof(g_VoteArg), "Votekicked"); + } + + if (GetClientOfUserId(g_VoteClient[VOTE_USERID]) > 0 && !IsClientInKickQueue(g_VoteClient[VOTE_CLIENTID])) + { + CPrintToChatAllEx(client, "%t", "Kicked target", "_s", g_VoteInfo[VOTE_NAME]); + LogAction(-1, g_VoteClient[VOTE_CLIENTID], "Vote kick successful, kicked \"%L\" (reason \"%s\")", g_VoteClient[VOTE_CLIENTID], g_VoteArg); + + if (g_SourceBans) + { + SBPP_BanPlayer(0, g_VoteClient[VOTE_CLIENTID], g_ConVars[sv_vote_kick_ban_duration].IntValue, g_VoteArg); + } + else if (g_ConVars[sv_vote_kick_ban_duration].IntValue < 0) + { + KickClient(g_VoteClient[VOTE_CLIENTID], g_VoteArg); + } + else + { + BanClient(g_VoteClient[VOTE_CLIENTID], g_ConVars[sv_vote_kick_ban_duration].IntValue, BANFLAG_AUTO, g_VoteArg, "Removed from match by system", "sm_votekick"); + } + } + + } + case ban: + { + if (g_VoteArg[0] == '\0') + { + strcopy(g_VoteArg, sizeof(g_VoteArg), "Votebanned"); + } + + if (GetClientOfUserId(g_VoteClient[VOTE_USERID]) > 0) + { + CPrintToChatAllEx(client, "%t", "Banned player", g_VoteInfo[VOTE_NAME], 30); + LogAction(-1, g_VoteClient[VOTE_CLIENTID], "Vote ban successful, banned \"%L\" (minutes \"30\") (reason \"%s\")", g_VoteClient[VOTE_CLIENTID], g_VoteArg); + + BanClient(g_VoteClient[VOTE_CLIENTID], + 30, + BANFLAG_AUTO, + g_VoteArg, + "Banned by vote", + "sm_voteban"); + } + } + } + } + } + } + + return Plugin_Continue; +} + +public Handler_NativeVoteCallback(Handle:menu, MenuAction:action, param1, param2) +{ + switch (action) + { + + case MenuAction_End: + { + NativeVotes_Close(menu); + } + + case MenuAction_Display: + { + NativeVotesType nVoteType = NativeVotes_GetType(menu); + if (g_VoteType != question && (nVoteType == NativeVotesType_Custom_YesNo || nVoteType == NativeVotesType_Custom_Mult)) + { + char title[64]; + NativeVotes_GetTitle(menu, title, sizeof(title)); + + char buffer[255]; + Format(buffer, sizeof(buffer), "%T", title, param1, g_VoteInfo[VOTE_NAME]); + + return NativeVotes_RedrawVoteTitle(buffer); + } + } + + case MenuAction_VoteCancel: + { + if (param1 == VoteCancel_NoVotes) + { + NativeVotes_DisplayFail(menu, NativeVotesFail_NotEnoughVotes); + CPrintToChatAll("%t", "No Votes Cast"); + } + else + { + NativeVotes_DisplayFail(menu, NativeVotesFail_Generic); + } + } + + case MenuAction_VoteEnd: + { + char item[64], display[64]; + float percent, limit; + int votes, totalVotes; + + NativeVotesType nVoteType = NativeVotes_GetType(menu); + + NativeVotes_GetInfo(param2, votes, totalVotes); + NativeVotes_GetItem(menu, param1, item, sizeof(item), display, sizeof(display)); + + if (nVoteType == NativeVotesType_Custom_YesNo && param1 == NATIVEVOTES_VOTE_NO) + { + votes = totalVotes - votes; // Reverse the votes to be in relation to the Yes option. + } + + percent = GetVotePercent(votes, totalVotes); + + switch (nVoteType) + { + case NativeVotesType_Kick, NativeVotesType_KickIdle, NativeVotesType_KickScamming, NativeVotesType_KickCheating: + { + g_VoteType = kick; + } + case NativeVotesType_ChgLevel: + { + g_VoteType = map; + } + case NativeVotesType_Custom_YesNo, NativeVotesType_Custom_Mult: + { + g_VoteType = question; + } + } + + if (g_VoteType != question) + { + limit = GetConVarFloat(g_ConVars[g_VoteType]); + } + + /* :TODO: g_VoteClient[userid] needs to be checked */ + + // A multi-argument vote is "always successful", but have to check if its a Yes/No vote. + if ((nVoteType != NativeVotesType_NextLevelMult && nVoteType != NativeVotesType_Custom_Mult) && ((param1 == NATIVEVOTES_VOTE_YES && FloatCompare(percent,limit) < 0) || (param1 == NATIVEVOTES_VOTE_NO))) + { + /* :TODO: g_VoteClient[userid] should be used here and set to -1 if not applicable. + */ + NativeVotes_DisplayFail(menu, NativeVotesFail_Loses); + LogAction(-1, -1, "Vote failed."); + CPrintToChatAll("%t", "Vote Failed", RoundToNearest(100.0*limit), RoundToNearest(100.0*percent), totalVotes); + } + else + { + CPrintToChatAll("%t", "Vote Successful", RoundToNearest(100.0*percent), totalVotes); + switch (g_VoteType) + { + case question: + { + if (nVoteType == NativeVotesType_Custom_YesNo) + { + for (int i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i) && !IsFakeClient(i)) + { + Format(item, sizeof(item), "%T", display, i); + CPrintToChat(i, "%t", "Vote End", g_VoteArg, item); + NativeVotes_DisplayPassCustomToOne(menu, i, "%t", "Vote End", g_VoteArg, item); + } + } + } + else + { + CPrintToChatAll("%t", "Vote End", g_VoteArg, item); + NativeVotes_DisplayPassCustom(menu, "%t", "Vote End", g_VoteArg, item); + } + } + case map: + { + if (nVoteType == NativeVotesType_ChgLevel) + { + NativeVotes_GetDetails(menu, item, sizeof(item)); + } + + //NativeVotes_DisplayPass(menu, item); + NativeVotes_DisplayPassEx(menu, NativeVotesPass_ChgLevel, item); + LogAction(-1, -1, "Changing map to %s due to vote.", item); + CPrintToChatAll("%t", "Changing map", item); + DataPack dp; + CreateDataTimer(5.0, Timer_ChangeMap, dp); + WritePackString(dp, item); + } + case kick: + { + if (g_VoteArg[0] == '\0') + { + strcopy(g_VoteArg, sizeof(g_VoteArg), "Votekicked"); + } + if (GetClientOfUserId(g_VoteClient[VOTE_USERID]) > 0 && !IsClientInKickQueue(g_VoteClient[VOTE_CLIENTID])) + { + CPrintToChatAll("%t", "Kicked target", "_s", g_VoteInfo[VOTE_NAME]); + LogAction(-1, g_VoteClient[VOTE_CLIENTID], "Vote kick successful, kicked \"%L\" (reason \"%s\")", g_VoteClient[VOTE_CLIENTID], g_VoteArg); + if (g_SourceBans) + { + SBPP_BanPlayer(0, g_VoteClient[VOTE_CLIENTID], g_ConVars[sv_vote_kick_ban_duration].IntValue, g_VoteArg); + } + else if (g_ConVars[sv_vote_kick_ban_duration].IntValue < 0) + { + KickClient(g_VoteClient[VOTE_CLIENTID], "%s", g_VoteArg); + } + else + { + BanClient(g_VoteClient[VOTE_CLIENTID], g_ConVars[sv_vote_kick_ban_duration].IntValue, BANFLAG_AUTO, g_VoteArg, "Kicked by vote", "sm_votekick"); + } + NativeVotes_DisplayPass(menu, g_VoteInfo[VOTE_NAME]); + } + } + case ban: + { + if (g_VoteArg[0] == '\0') + { + strcopy(g_VoteArg, sizeof(g_VoteArg), "Votebanned"); + } + if (GetClientOfUserId(g_VoteClient[VOTE_USERID]) > 0) + { + CPrintToChatAll("%t", "Banned player", g_VoteInfo[VOTE_NAME], 30); + LogAction(-1, g_VoteClient[VOTE_CLIENTID], "Vote ban successful, banned \"%L\" (minutes \"30\") (reason \"%s\")", g_VoteClient[VOTE_CLIENTID], g_VoteArg); + BanClient(g_VoteClient[VOTE_CLIENTID], + 30, + BANFLAG_AUTO, + g_VoteArg, + "Banned by vote", + "sm_voteban"); + NativeVotes_DisplayPassCustom(menu, "%t", "Banned player", g_VoteInfo[VOTE_NAME], 30); + } + } + } + } + } + } + + return Plugin_Continue; +} + +float GetVotePercent(int votes, int totalVotes) +{ + return float(votes) / float(totalVotes); +} + +bool TestVoteDelay(int client) +{ + int delay = Internal_CheckVoteDelay(); + if (client <= 0 || GetUserFlagBits(client) & ADMFLAG_GENERIC) + { + delay = 0; + } + + if (delay > 0) + { + if (delay > 60) + { + CReplyToCommand(client, "%t", "Vote Delay Minutes", delay % 60); + } + else + { + CReplyToCommand(client, "%t", "Vote Delay Seconds", delay); + } + + if (g_NativeVotes) + { + NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_Recent, delay); + } + + return false; + } + + return true; +} + +public Action Timer_ChangeMap(Handle timer, DataPack dp) +{ + char mapname[PLATFORM_MAX_PATH]; + + ResetPack(dp); + ReadPackString(dp, mapname, sizeof(mapname)); + + ForceChangeLevel(mapname, "sm_votemap Result"); + + return Plugin_Stop; +} + +bool Internal_IsVoteInProgress() +{ + if (g_NativeVotes) + { + return NativeVotes_IsVoteInProgress(); + } + return IsVoteInProgress(); +} + +int Internal_CheckVoteDelay() +{ + if (g_NativeVotes) + { + return NativeVotes_CheckVoteDelay(); + } + return CheckVoteDelay(); +} + +bool Internal_IsNewVoteAllowed() +{ + if (g_NativeVotes) + { + return NativeVotes_IsNewVoteAllowed(); + } + return IsNewVoteAllowed(); +} + +void GetPlayerName(int client, char[] name, int maxlen) +{ + int r, g, b, a, color; + GetEntityRenderColor(client, r, g, b, a); + color = (r << 16) | (g << 8) | b; + if (color != 0xFFFFFF) + { + Format(name, maxlen, "{#%06X}%N\x01", color, client); + } + else + { + Format(name, maxlen, "{teamcolor}%N\x01", client); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/nativevotes_basevotes/voteban.sp b/addons/sourcemod/scripting/nativevotes_basevotes/voteban.sp new file mode 100644 index 0000000..9f40954 --- /dev/null +++ b/addons/sourcemod/scripting/nativevotes_basevotes/voteban.sp @@ -0,0 +1,186 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * NativeVotes Basic Votes Plugin + * Provides ban functionality + * + * NativeVotes (C)2011-2014 Ross Bemrose (Powerlord). All rights reserved. + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program 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 the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or <http://www.sourcemod.net/license.php>. + * + * Version: $Id$ + */ + +void DisplayVoteBanMenu(int client, int target) +{ + g_VoteClient[VOTE_CLIENTID] = target; + g_VoteClient[VOTE_USERID] = GetClientUserId(target); + + char playerName[MAX_NAME_LENGTH]; + GetPlayerName(target, playerName, sizeof(playerName)); + + GetClientName(target, g_VoteInfo[VOTE_NAME], sizeof(g_VoteInfo[])); + GetClientIP(target, g_VoteInfo[VOTE_IP], sizeof(g_VoteInfo[])); + + LogAction(client, target, "\"%L\" initiated a ban vote against %s", client, playerName); + CShowActivity2(client, PLUGIN_PREFIX ... " ", "%t", "Initiated Vote Ban", playerName); + + g_VoteType = ban; + + if (g_NativeVotes) + { + Handle voteMenu = NativeVotes_Create(Handler_NativeVoteCallback, NativeVotesType_Custom_YesNo, view_as<MenuAction>(MENU_ACTIONS_ALL)); + NativeVotes_SetTitle(voteMenu, "Voteban Player"); + NativeVotes_SetTarget(voteMenu, target); + NativeVotes_DisplayToAll(voteMenu, g_ConVars[sv_vote_timer_duration].IntValue); + } + else + { + Handle voteMenu = CreateMenu(Handler_VoteCallback, view_as<MenuAction>(MENU_ACTIONS_ALL)); + SetMenuTitle(voteMenu, "Voteban Player"); + AddMenuItem(voteMenu, VOTE_YES, "Yes"); + AddMenuItem(voteMenu, VOTE_NO, "No"); + SetMenuExitButton(voteMenu, false); + VoteMenuToAll(voteMenu, g_ConVars[sv_vote_timer_duration].IntValue); + } +} + +void DisplayBanTargetMenu(int client) +{ + Handle menu = CreateMenu(MenuHandler_Ban); + char title[100]; + Format(title, sizeof(title), "%T:", "Ban vote", client); + SetMenuTitle(menu, title); + SetMenuExitBackButton(menu, true); + AddTargetsToMenu(menu, client, false, false); + DisplayMenu(menu, client, MENU_TIME_FOREVER); +} + +public void AdminMenu_VoteBan(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) +{ + if (action == TopMenuAction_DisplayOption) + { + Format(buffer, maxlength, "%T", "Ban vote", param); + } + else if (action == TopMenuAction_SelectOption) + { + DisplayBanTargetMenu(param); + } + else if (action == TopMenuAction_DrawOption) + { + /* disable this option if a vote is already running */ + buffer[0] = Internal_IsNewVoteAllowed() ? ITEMDRAW_IGNORE : ITEMDRAW_DEFAULT; + } +} + +public int MenuHandler_Ban(Handle menu, MenuAction action, int param1, int param2) +{ + switch (action) + { + case MenuAction_End: + { + CloseHandle(menu); + } + case MenuAction_Cancel: + { + if (param2 == MenuCancel_ExitBack && g_TopMenu != INVALID_HANDLE) + { + DisplayTopMenu(g_TopMenu, param1, TopMenuPosition_LastCategory); + } + } + case MenuAction_Select: + { + char info[32], name[32]; + int userid, target; + GetMenuItem(menu, param2, info, sizeof(info), _, name, sizeof(name)); + userid = StringToInt(info); + if ((target = GetClientOfUserId(userid)) == 0) + { + CPrintToChat(param1, PLUGIN_PREFIX ... " %t", "Player no longer available"); + } + else if (!CanUserTarget(param1, target)) + { + CPrintToChat(param1, PLUGIN_PREFIX ... " %t", "Unable to target"); + } + else + { + g_VoteArg[0] = '\0'; + DisplayVoteBanMenu(param1, target); + } + } + } + + return Plugin_Continue; +} + +public Action Command_Voteban(int client, int args) +{ + if (args < 1) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " Usage: sm_voteban <player> [reason]"); + return Plugin_Handled; + } + + if (Internal_IsVoteInProgress()) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Vote in Progress"); + return Plugin_Handled; + } + + if (!TestVoteDelay(client)) + { + return Plugin_Handled; + } + + char text[256], arg[64]; + GetCmdArgString(text, sizeof(text)); + int len = BreakString(text, arg, sizeof(arg)); + if (len != -1) + { + strcopy(g_VoteArg, sizeof(g_VoteArg), text[len]); + } + else + { + g_VoteArg[0] = '\0'; + } + + char target_name[MAX_TARGET_LENGTH]; + int target_list[MAXPLAYERS], target_count; + bool tn_is_ml; + if ((target_count = ProcessTargetString( + arg, + client, + target_list, + MAXPLAYERS, + COMMAND_FILTER_NO_MULTI|COMMAND_FILTER_NO_BOTS, + target_name, + sizeof(target_name), + tn_is_ml)) <= 0) + { + ReplyToTargetError(client, target_count); + return Plugin_Handled; + } + DisplayVoteBanMenu(client, target_list[0]); + return Plugin_Handled; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/nativevotes_basevotes/votekick.sp b/addons/sourcemod/scripting/nativevotes_basevotes/votekick.sp new file mode 100644 index 0000000..e14488e --- /dev/null +++ b/addons/sourcemod/scripting/nativevotes_basevotes/votekick.sp @@ -0,0 +1,170 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * NativeVotes Basic Votes Plugin + * Provides kick functionality + * + * NativeVotes (C)2011-2014 Ross Bemrose (Powerlord). All rights reserved. + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program 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 the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or <http://www.sourcemod.net/license.php>. + * + * Version: $Id$ + */ + +void DisplayVoteKickMenu(int client, int target) +{ + g_VoteClient[VOTE_CLIENTID] = target; + g_VoteClient[VOTE_USERID] = GetClientUserId(target); + + char playerName[MAX_NAME_LENGTH]; + GetPlayerName(target, playerName, sizeof(playerName)); + + GetClientName(target, g_VoteInfo[VOTE_NAME], sizeof(g_VoteInfo[])); + + LogAction(client, target, "\"%L\" initiated a kick vote against %N", client, target); + CShowActivity(client, "%t", "Initiated Vote Kick", playerName); + + g_VoteType = kick; + + if (g_NativeVotes) + { + Handle voteMenu = NativeVotes_Create(Handler_NativeVoteCallback, NativeVotesType_Kick, view_as<MenuAction>(MENU_ACTIONS_ALL)); + // No title, builtin type + NativeVotes_SetTarget(voteMenu, target); + NativeVotes_DisplayToAll(voteMenu, g_ConVars[sv_vote_timer_duration].IntValue); + } + else + { + Handle voteMenu = CreateMenu(Handler_VoteCallback, MENU_ACTIONS_ALL); + SetMenuTitle(voteMenu, "Votekick Player"); + AddMenuItem(voteMenu, VOTE_YES, "Yes"); + AddMenuItem(voteMenu, VOTE_NO, "No"); + SetMenuExitButton(voteMenu, false); + VoteMenuToAll(voteMenu, g_ConVars[sv_vote_timer_duration].IntValue); + } +} + +void DisplayKickTargetMenu(int client) +{ + Handle menu = CreateMenu(MenuHandler_Kick); + char title[100]; + Format(title, sizeof(title), "%T:", "Kick vote", client); + SetMenuTitle(menu, title); + SetMenuExitBackButton(menu, true); + AddTargetsToMenu(menu, client, false, false); + DisplayMenu(menu, client, MENU_TIME_FOREVER); +} + +public void AdminMenu_VoteKick(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) +{ + if (action == TopMenuAction_DisplayOption) + { + Format(buffer, maxlength, "%T", "Kick vote", param); + } + else if (action == TopMenuAction_SelectOption) + { + DisplayKickTargetMenu(param); + } + else if (action == TopMenuAction_DrawOption) + { + /* disable this option if a vote is already running */ + buffer[0] = !IsNewVoteAllowed() ? ITEMDRAW_IGNORE : ITEMDRAW_DEFAULT; + } +} + +public int MenuHandler_Kick(Handle menu, MenuAction action, int param1, int param2) +{ + switch (action) + { + case MenuAction_End: + { + CloseHandle(menu); + } + case MenuAction_Cancel: + { + if (param2 == MenuCancel_ExitBack && g_TopMenu != INVALID_HANDLE) + { + DisplayTopMenu(g_TopMenu, param1, TopMenuPosition_LastCategory); + } + } + case MenuAction_Select: + { + char info[32], name[32]; + int userid, target; + GetMenuItem(menu, param2, info, sizeof(info), _, name, sizeof(name)); + userid = StringToInt(info); + if ((target = GetClientOfUserId(userid)) == 0) + { + CPrintToChat(param1, PLUGIN_PREFIX ... " %t", "Player no longer available"); + } + else if (!CanUserTarget(param1, target)) + { + CPrintToChat(param1, PLUGIN_PREFIX ... " %t", "Unable to target"); + } + else + { + g_VoteArg[0] = '\0'; + DisplayVoteKickMenu(param1, target); + } + } + } + + return Plugin_Continue; +} + +public Action Command_Votekick(int client, int args) +{ + if (args < 1) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " Usage: sm_votekick <player> [reason]"); + return Plugin_Handled; + } + if (Internal_IsVoteInProgress()) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Vote in Progress"); + return Plugin_Handled; + } + if (!TestVoteDelay(client)) + { + return Plugin_Handled; + } + char text[256], arg[64]; + GetCmdArgString(text, sizeof(text)); + int len = BreakString(text, arg, sizeof(arg)); + int target = FindTarget(client, arg); + if (target == -1) + { + return Plugin_Handled; + } + if (len != -1) + { + strcopy(g_VoteArg, sizeof(g_VoteArg), text[len]); + } + else + { + g_VoteArg[0] = '\0'; + } + DisplayVoteKickMenu(client, target); + return Plugin_Handled; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/nativevotes_basevotes/votemap.sp b/addons/sourcemod/scripting/nativevotes_basevotes/votemap.sp new file mode 100644 index 0000000..e147ed3 --- /dev/null +++ b/addons/sourcemod/scripting/nativevotes_basevotes/votemap.sp @@ -0,0 +1,329 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * NativeVotes Basic Votes Plugin + * Provides map functionality + * + * NativeVotes (C)2011-2014 Ross Bemrose (Powerlord). All rights reserved. + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program 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 the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or <http://www.sourcemod.net/license.php>. + * + * Version: $Id$ + */ + +Handle g_MapList; +int g_mapCount; + +Handle g_SelectedMaps; +bool g_VoteMapInUse; + +void DisplayVoteMapMenu(int client, int mapCount, char maps[5][PLATFORM_MAX_PATH]) +{ + LogAction(client, -1, "\"%L\" initiated a map vote.", client); + CShowActivity2(client, PLUGIN_PREFIX ... " ", "%t", "Initiated Vote Map"); + + g_VoteType = map; + + char resolvedMaps[5][PLATFORM_MAX_PATH]; + char displayNames[5][PLATFORM_MAX_PATH]; + for (int i = 0; i < mapCount; i++) + { + FindMap(maps[i], resolvedMaps[i], sizeof(resolvedMaps[])); + GetMapDisplayName(resolvedMaps[i], displayNames[i], sizeof(displayNames[])); + } + + if (g_NativeVotes && (mapCount == 1 || NativeVotes_IsVoteTypeSupported(NativeVotesType_NextLevelMult)) ) + { + Handle voteMenu; + if (mapCount == 1) + { + strcopy(g_VoteInfo[VOTE_NAME], sizeof(g_VoteInfo[]), displayNames[0]); + + voteMenu = NativeVotes_Create(Handler_NativeVoteCallback, NativeVotesType_ChgLevel, view_as<MenuAction>(MENU_ACTIONS_ALL)); + + // No title, builtin type + NativeVotes_SetDetails(voteMenu, displayNames[0]); + } + else + { + voteMenu = NativeVotes_Create(Handler_NativeVoteCallback, NativeVotesType_NextLevelMult, view_as<MenuAction>(MENU_ACTIONS_ALL)); + + g_VoteInfo[VOTE_NAME][0] = '\0'; + + // No title, builtin type + for (int i = 0; i < mapCount; i++) + { + NativeVotes_AddItem(voteMenu, resolvedMaps[i], displayNames[i]); + } + } + NativeVotes_DisplayToAll(voteMenu, g_ConVars[sv_vote_timer_duration].IntValue); + } + else + { + Handle voteMenu = CreateMenu(Handler_VoteCallback, MENU_ACTIONS_ALL); + + if (mapCount == 1) + { + strcopy(g_VoteInfo[VOTE_NAME], sizeof(g_VoteInfo[]), displayNames[0]); + + SetMenuTitle(voteMenu, "Change Map To"); + AddMenuItem(voteMenu, resolvedMaps[0], "Yes"); + AddMenuItem(voteMenu, VOTE_NO, "No"); + } + else + { + g_VoteInfo[VOTE_NAME][0] = '\0'; + + SetMenuTitle(voteMenu, "Map Vote"); + for (int i = 0; i < mapCount; i++) + { + AddMenuItem(voteMenu, resolvedMaps[i], displayNames[i]); + } + } + SetMenuExitButton(voteMenu, false); + VoteMenuToAll(voteMenu, g_ConVars[sv_vote_timer_duration].IntValue); + } +} + +void ResetMenu() +{ + g_VoteMapInUse = false; + ClearArray(g_SelectedMaps); +} + +void ConfirmVote(int client) +{ + Handle menu = CreateMenu(MenuHandler_Confirm); + char title[100]; + + Format(title, sizeof(title), "%T:", "Confirm Vote", client); + SetMenuTitle(menu, title); + SetMenuExitBackButton(menu, true); + + char itemtext[256]; + Format(itemtext, sizeof(itemtext), "%T", "Start the Vote", client); + AddMenuItem(menu, "Confirm", itemtext); + + DisplayMenu(menu, client, MENU_TIME_FOREVER); +} + +public int MenuHandler_Confirm(Handle menu, MenuAction action, int param1, int param2) +{ + switch (action) + { + case MenuAction_End: + { + CloseHandle(menu); + g_VoteMapInUse = false; + } + case MenuAction_Cancel: + { + ResetMenu(); + if (param2 == MenuCancel_ExitBack && g_TopMenu != INVALID_HANDLE) + { + DisplayTopMenu(g_TopMenu, param1, TopMenuPosition_LastCategory); + } + } + case MenuAction_Select: + { + char maps[5][64]; + int selectedmaps = GetArraySize(g_SelectedMaps); + for (int i = 0; i < selectedmaps; i++) + { + GetArrayString(g_SelectedMaps, i, maps[i], sizeof(maps[])); + } + DisplayVoteMapMenu(param1, selectedmaps, maps); + ResetMenu(); + } + } + + return Plugin_Continue; +} + +public int MenuHandler_Map(Handle menu, MenuAction action, int param1, int param2) +{ + switch (action) + { + case MenuAction_Cancel: + { + if (param2 == MenuCancel_ExitBack && g_TopMenu != INVALID_HANDLE) + { + ConfirmVote(param1); + } + else // no action was selected. + { + /* Re-enable the menu option */ + ResetMenu(); + } + } + case MenuAction_DrawItem: + { + char info[32], name[32]; + GetMenuItem(menu, param2, info, sizeof(info), _, name, sizeof(name)); + if (FindStringInArray(g_SelectedMaps, info) != -1) + { + return ITEMDRAW_IGNORE; + } + else + { + return ITEMDRAW_DEFAULT; + } + } + case MenuAction_Select: + { + char info[32], name[32]; + GetMenuItem(menu, param2, info, sizeof(info), _, name, sizeof(name)); + PushArrayString(g_SelectedMaps, info); + /* Redisplay the list */ + if (GetArraySize(g_SelectedMaps) < 5) + { + DisplayMenu(g_MapList, param1, MENU_TIME_FOREVER); + } + else + { + ConfirmVote(param1); + } + } + case MenuAction_Display: + { + char title[128]; + Format(title, sizeof(title), "%T", "Please select a map", param1); + SetPanelTitle(view_as<Handle>(param2), title); + } + } + + return Plugin_Continue; +} + +public void AdminMenu_VoteMap(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) +{ + switch (action) + { + case TopMenuAction_DisplayOption: + { + Format(buffer, maxlength, "%T", "Map vote", param); + } + case TopMenuAction_SelectOption: + { + if (!g_VoteMapInUse) + { + ResetMenu(); + g_VoteMapInUse = true; + DisplayMenu(g_MapList, param, MENU_TIME_FOREVER); + } + else + { + CPrintToChat(param, PLUGIN_PREFIX ... " %t", "Map Vote In Use", param); + } + } + case TopMenuAction_DrawOption: + { + /* disable this option if a vote is already running, theres no maps listed or someone else has already acessed this menu */ + buffer[0] = (!IsNewVoteAllowed() || g_mapCount < 1 || g_VoteMapInUse) ? ITEMDRAW_IGNORE : ITEMDRAW_DEFAULT; + } + } +} + +public Action Command_Votemap(int client, int args) +{ + if (args < 1) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " Usage: sm_votemap <mapname> [mapname2] ... [mapname5]"); + return Plugin_Handled; + } + + if (Internal_IsVoteInProgress()) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Vote in Progress"); + return Plugin_Handled; + } + + if (!TestVoteDelay(client) && !CheckCommandAccess(client, "sm_votemap", ADMFLAG_CHANGEMAP)) + { + return Plugin_Handled; + } + + char text[256]; + GetCmdArgString(text, sizeof(text)); + + char maps[5][PLATFORM_MAX_PATH]; + int mapCount; + int len, pos; + + while (pos != -1 && mapCount < 5) + { + pos = BreakString(text[len], maps[mapCount], sizeof(maps[])); + + if (!IsMapValid(maps[mapCount])) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Map was not found", maps[mapCount]); + return Plugin_Handled; + } + + mapCount++; + + if (pos != -1) + { + len += pos; + } + } + + DisplayVoteMapMenu(client, mapCount, maps); + + return Plugin_Handled; +} + +Handle g_map_array = INVALID_HANDLE; +int g_map_serial = -1; + +int LoadMapList(Handle menu) +{ + Handle map_array; + if ((map_array = ReadMapList(g_map_array, + g_map_serial, + "sm_votemap menu", + MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_NO_DEFAULT|MAPLIST_FLAG_MAPSFOLDER)) + != INVALID_HANDLE) + { + g_map_array = map_array; + } + + if (g_map_array == INVALID_HANDLE) + { + return 0; + } + + RemoveAllMenuItems(menu); + + char map_name[PLATFORM_MAX_PATH]; + int map_count = GetArraySize(g_map_array); + + for (int i = 0; i < map_count; i++) + { + GetArrayString(g_map_array, i, map_name, sizeof(map_name)); + AddMenuItem(menu, map_name, map_name); + } + + return map_count; +} diff --git a/addons/sourcemod/scripting/nativevotes_funvotes.sp b/addons/sourcemod/scripting/nativevotes_funvotes.sp new file mode 100644 index 0000000..f7dde42 --- /dev/null +++ b/addons/sourcemod/scripting/nativevotes_funvotes.sp @@ -0,0 +1,571 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * NativeVotes Fun Votes Plugin + * Implements extra fun vote commands using NativeVotes. + * Based on the SourceMod version. + * + * NativeVotes (C)2011-2014 Ross Bemrose (Powerlord). All rights reserved. + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program 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 the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or <http://www.sourcemod.net/license.php>. + * + * Version: $Id$ + */ + +#include <sourcemod> +#include <sdktools> + +#undef REQUIRE_PLUGIN +#include <adminmenu> +#include <nativevotes> + +#define PLUGIN_PREFIX "[\x04NativeVotes\x01]" + +public Plugin myinfo = +{ + name = "NativeVotes | Fun Votes", + author = "Powerlord and AlliedModders LLC", + description = "NativeVotes Fun Vote Commands", + version = "26w15a", + url = "https://github.com/Heapons/sourcemod-nativevotes-updated/" +}; + +#define VOTE_NO "###no###" +#define VOTE_YES "###yes###" + +// Handle g_hVoteMenu; + +enum +{ + sv_gravity, + sv_alltalk, + mp_friendlyfire, + sv_vote_timer_duration, + + vote_gravity, + vote_burn, + vote_slay, + vote_alltalk, + vote_ff, + + MAX_CONVARS +} + +ConVar g_ConVars[MAX_CONVARS]; + +enum VoteType +{ + gravity, + burn, + slay, + alltalk, + ff +}; + +VoteType g_VoteType = gravity; + +// Menu API does not provide us with a way to pass multiple peices of data with a single +// choice, so some globals are used to hold stuff. +// +#define VOTE_CLIENTID 0 +#define VOTE_USERID 1 +int g_VoteClient[2]; // Holds the target's client id and user id + +#define VOTE_NAME 0 +#define VOTE_AUTHID 1 +#define VOTE_IP 2 +char g_VoteInfo[3][65]; // Holds the target's name, authid, and IP + +Handle g_TopMenu; + +// NativeVotes +bool g_NativeVotes; + +#include "nativevotes_funvotes/votegravity.sp" +#include "nativevotes_funvotes/voteburn.sp" +#include "nativevotes_funvotes/voteslay.sp" +#include "nativevotes_funvotes/votealltalk.sp" +#include "nativevotes_funvotes/voteff.sp" + +public void OnPluginStart() +{ + LoadTranslations("common.phrases"); + LoadTranslations("basevotes.phrases"); + LoadTranslations("funvotes.phrases"); + LoadTranslations("funcommands.phrases"); + + RegAdminCmd("sm_votegravity", Command_VoteGravity, ADMFLAG_VOTE, "sm_votegravity <amount> [amount2] ... [amount5]"); + RegAdminCmd("sm_voteburn", Command_VoteBurn, ADMFLAG_VOTE|ADMFLAG_SLAY, "sm_voteburn <player>"); + RegAdminCmd("sm_voteslay", Command_VoteSlay, ADMFLAG_VOTE|ADMFLAG_SLAY, "sm_voteslay <player>"); + RegAdminCmd("sm_votealltalk", Command_VoteAlltalk, ADMFLAG_VOTE, "sm_votealltalk"); + RegAdminCmd("sm_voteff", Command_VoteFF, ADMFLAG_VOTE, "sm_voteff"); + + g_ConVars[sv_gravity] = FindConVar("sv_gravity"); + g_ConVars[sv_alltalk] = FindConVar("sv_alltalk"); + g_ConVars[mp_friendlyfire] = FindConVar("mp_friendlyfire"); + g_ConVars[sv_vote_timer_duration] = FindConVar("sv_vote_timer_duration"); + g_ConVars[vote_gravity] = CreateConVar("sm_vote_gravity", "0.60", "Percent required for successful gravity vote.", 0, true, 0.05, true, 1.0); + g_ConVars[vote_burn] = CreateConVar("sm_vote_burn", "0.60", "Percent required for successful burn vote.", 0, true, 0.05, true, 1.0); + g_ConVars[vote_slay] = CreateConVar("sm_vote_slay", "0.60", "Percent required for successful slay vote.", 0, true, 0.05, true, 1.0); + g_ConVars[vote_alltalk] = CreateConVar("sm_vote_alltalk", "0.60", "Percent required for successful alltalk vote.", 0, true, 0.05, true, 1.0); + g_ConVars[vote_ff] = CreateConVar("sm_vote_ff", "0.60", "Percent required for successful friendly fire vote.", 0, true, 0.05, true, 1.0); +} + +public void OnAllPluginsLoaded() +{ + if (FindPluginByFile("basefunvotes.smx") != null) + { + SetFailState("This plugin replaces basefuncommands. You cannot run both at once."); + } + + if (FindPluginByFile("funvotes.smx") != null) + { + LogMessage("Unloading funvotes to prevent conflicts..."); + ServerCommand("sm plugins unload funvotes"); + + char oldPath[PLATFORM_MAX_PATH], newPath[PLATFORM_MAX_PATH]; + + BuildPath(Path_SM, oldPath, sizeof(oldPath), "plugins/funvotes.smx"); + BuildPath(Path_SM, newPath, sizeof(newPath), "plugins/disabled/funvotes.smx"); + if (RenameFile(newPath, oldPath)) + { + LogMessage("Moving funvotes to disabled."); + } + //SetFailState("This plugin replaces funvotes. You cannot run both at once."); + } + /* Account for late loading */ + Handle topmenu; + if (LibraryExists("adminmenu") && ((topmenu = GetAdminTopMenu()) != null)) + { + OnAdminMenuReady(topmenu); + } + + g_NativeVotes = LibraryExists("nativevotes") && NativeVotes_IsVoteTypeSupported(NativeVotesType_Custom_YesNo); +} + +public void OnLibraryAdded(const char[] name) +{ + Handle topmenu; + if (StrEqual(name, "adminmenu") && ((topmenu = GetAdminTopMenu()) != null)) + { + OnAdminMenuReady(topmenu); + } + else if (StrEqual(name, "nativevotes") && NativeVotes_IsVoteTypeSupported(NativeVotesType_Custom_YesNo)) + { + g_NativeVotes = true; + } +} + +public void OnLibraryRemoved(const char[] name) +{ + if (StrEqual(name, "adminmenu")) + { + g_TopMenu = null; + } + else if (StrEqual(name, "nativevotes")) + { + g_NativeVotes = false; + } +} + +public void OnAdminMenuReady(Handle topmenu) +{ + /* Block us from being called twice */ + if (topmenu == g_TopMenu) + { + return; + } + + /* Save the Handle */ + g_TopMenu = topmenu; + + /* Build the "Voting Commands" category */ + TopMenuObject voting_commands = FindTopMenuCategory(g_TopMenu, ADMINMENU_VOTINGCOMMANDS); + + if (voting_commands != INVALID_TOPMENUOBJECT) + { + AddToTopMenu(g_TopMenu, + "sm_votegravity", + TopMenuObject_Item, + AdminMenu_VoteGravity, + voting_commands, + "sm_votegravity", + ADMFLAG_VOTE); + + AddToTopMenu(g_TopMenu, + "sm_voteburn", + TopMenuObject_Item, + AdminMenu_VoteBurn, + voting_commands, + "sm_voteburn", + ADMFLAG_VOTE|ADMFLAG_SLAY); + + AddToTopMenu(g_TopMenu, + "sm_voteslay", + TopMenuObject_Item, + AdminMenu_VoteSlay, + voting_commands, + "sm_voteslay", + ADMFLAG_VOTE|ADMFLAG_SLAY); + + AddToTopMenu(g_TopMenu, + "sm_votealltalk", + TopMenuObject_Item, + AdminMenu_VoteAllTalk, + voting_commands, + "sm_votealltalk", + ADMFLAG_VOTE); + + AddToTopMenu(g_TopMenu, + "sm_voteff", + TopMenuObject_Item, + AdminMenu_VoteFF, + voting_commands, + "sm_voteff", + ADMFLAG_VOTE); + } +} + +public int Handler_VoteCallback(Handle menu, MenuAction action, int param1, int param2) +{ + switch (action) + { + case MenuAction_End: + { + //VoteMenuClose(); + CloseHandle(menu); + } + + case MenuAction_Display: + { + char title[64]; + GetMenuTitle(menu, title, sizeof(title)); + + char buffer[255]; + Format(buffer, sizeof(buffer), "%T", title, param1, g_VoteInfo[VOTE_NAME]); + + Handle panel = view_as<Handle>(param2); + SetPanelTitle(panel, buffer); + } + + case MenuAction_DisplayItem: + { + char display[64]; + GetMenuItem(menu, param2, "", 0, _, display, sizeof(display)); + + if (strcmp(display, VOTE_NO) == 0 || strcmp(display, VOTE_YES) == 0) + { + char buffer[255]; + Format(buffer, sizeof(buffer), "%T", display, param1); + + return RedrawMenuItem(buffer); + } + } + + case MenuAction_VoteCancel: + { + if (param1 == VoteCancel_NoVotes) + { + CPrintToChatAll("%t", "No Votes Cast"); + } + } + + case MenuAction_VoteEnd: + { + char item[64]; + float percent, limit; + int votes, totalVotes; + + GetMenuVoteInfo(param2, votes, totalVotes); + GetMenuItem(menu, param1, item, sizeof(item)); + + if (strcmp(item, VOTE_NO) == 0 && param1 == 1) + { + votes = totalVotes - votes; // Reverse the votes to be in relation to the Yes option. + } + + percent = GetVotePercent(votes, totalVotes); + + limit = GetConVarFloat(g_ConVars[vote_gravity + view_as<int>(g_VoteType)]); + + // :TODO: g_VoteClient[userid] needs to be checked + + // A multi-argument vote is "always successful", but have to check if its a Yes/No vote. + if ((strcmp(item, VOTE_YES) == 0 && FloatCompare(percent,limit) < 0 && param1 == 0) || (strcmp(item, VOTE_NO) == 0 && param1 == 1)) + { + /* + * :TODO: g_VoteClient[userid] should be used here and set to -1 if not applicable. + */ + LogAction(-1, -1, "Vote failed."); + CPrintToChatAll("%t", "Vote Failed", RoundToNearest(100.0*limit), RoundToNearest(100.0*percent), totalVotes); + } + else + { + CPrintToChatAll("%t", "Vote Successful", RoundToNearest(100.0*percent), totalVotes); + + char name[MAX_NAME_LENGTH]; + GetPlayerName(g_VoteClient[VOTE_CLIENTID], name, sizeof(name)); + + switch (g_VoteType) + { + case gravity: + { + CPrintToChatAll("%t", "Cvar changed", "sv_gravity", item); + LogAction(-1, -1, "Changing gravity to %s due to vote.", item); + SetConVarInt(g_ConVars[sv_gravity], StringToInt(item)); + } + case burn: + { + CPrintToChatAllEx(g_VoteClient[VOTE_CLIENTID], "%t", "Set target on fire", "_s", name); + LogAction(-1, g_VoteClient[VOTE_CLIENTID], "Vote burn successful, igniting \"%L\"", g_VoteClient[VOTE_CLIENTID]); + + IgniteEntity(g_VoteClient[VOTE_CLIENTID], 19.8); + } + case slay: + { + CPrintToChatAllEx(g_VoteClient[VOTE_CLIENTID], "%t", "Slayed player", "_s", name); + LogAction(-1, g_VoteClient[VOTE_CLIENTID], "Vote slay successful, slaying \"%L\"", g_VoteClient[VOTE_CLIENTID]); + + ExtinguishEntity(g_VoteClient[VOTE_CLIENTID]); + ForcePlayerSuicide(g_VoteClient[VOTE_CLIENTID]); + } + case alltalk: + { + CPrintToChatAll("%t", "Cvar changed", "sv_alltalk", (GetConVarBool(g_ConVars[sv_alltalk]) ? "0" : "1")); + LogAction(-1, -1, "Changing alltalk to %s due to vote.", (GetConVarBool(g_ConVars[sv_alltalk]) ? "0" : "1")); + SetConVarBool(g_ConVars[sv_alltalk], !GetConVarBool(g_ConVars[sv_alltalk])); + } + case ff: + { + CPrintToChatAll("%t", "Cvar changed", "mp_friendlyfire", (GetConVarBool(g_ConVars[mp_friendlyfire]) ? "0" : "1")); + LogAction(-1, -1, "Changing friendly fire to %s due to vote.", (GetConVarBool(g_ConVars[mp_friendlyfire]) ? "0" : "1")); + SetConVarBool(g_ConVars[mp_friendlyfire], !GetConVarBool(g_ConVars[mp_friendlyfire])); + } + } + } + } + } + + return Plugin_Continue; +} + +public int Handler_NativeVoteCallback(Handle menu, MenuAction action, int param1, int param2) +{ + switch (action) + { + case MenuAction_End: + { + NativeVotes_Close(menu); + } + + case MenuAction_Display: + { + NativeVotesType nVoteType = NativeVotes_GetType(menu); + if (nVoteType == NativeVotesType_Custom_YesNo || nVoteType == NativeVotesType_Custom_Mult) + { + char title[64]; + NativeVotes_GetTitle(menu, title, sizeof(title)); + + char buffer[255]; + Format(buffer, sizeof(buffer), "%T", title, param1, g_VoteInfo[VOTE_NAME]); + + return NativeVotes_RedrawVoteTitle(buffer); + } + } + + case MenuAction_VoteCancel: + { + if (param1 == VoteCancel_NoVotes) + { + NativeVotes_DisplayFail(menu, NativeVotesFail_NotEnoughVotes); + CPrintToChatAll("%t", "No Votes Cast"); + } + else + { + NativeVotes_DisplayFail(menu, NativeVotesFail_Generic); + } + } + + case MenuAction_VoteEnd: + { + char item[64]; + float percent, limit; + int votes, totalVotes; + + NativeVotesType nVoteType = NativeVotes_GetType(menu); + + NativeVotes_GetInfo(param2, votes, totalVotes); + NativeVotes_GetItem(menu, param1, item, sizeof(item)); + + if (nVoteType == NativeVotesType_Custom_YesNo && param1 == NATIVEVOTES_VOTE_NO) + { + votes = totalVotes - votes; // Reverse the votes to be in relation to the Yes option. + } + + percent = GetVotePercent(votes, totalVotes); + + limit = GetConVarFloat(g_ConVars[vote_gravity + view_as<int>(g_VoteType)]); + + // :TODO: g_VoteClient[userid] needs to be checked + + // A multi-argument vote is "always successful", but have to check if its a Yes/No vote. + if ((nVoteType != NativeVotesType_NextLevelMult && nVoteType != NativeVotesType_Custom_Mult) && ((param1 == NATIVEVOTES_VOTE_YES && FloatCompare(percent,limit) < 0) || (param1 == NATIVEVOTES_VOTE_NO))) + { + /* :TODO: g_VoteClient[userid] should be used here and set to -1 if not applicable. + */ + NativeVotes_DisplayFail(menu, NativeVotesFail_Loses); + LogAction(-1, -1, "Vote failed."); + CPrintToChatAll("%t", "Vote Failed", RoundToNearest(100.0*limit), RoundToNearest(100.0*percent), totalVotes); + } + else + { + CPrintToChatAll("%t", "Vote Successful", RoundToNearest(100.0*percent), totalVotes); + + switch (g_VoteType) + { + case gravity: + { + CPrintToChatAll("%t", "Cvar changed", "sv_gravity", item); + NativeVotes_DisplayPassCustom(menu, "%t", "Cvar changed", "sv_gravity", item); + LogAction(-1, -1, "Changing gravity to %s due to vote.", item); + SetConVarInt(g_ConVars[sv_gravity], StringToInt(item)); + } + case burn: + { + CPrintToChatAll("%t", "Set target on fire", "_s", g_VoteInfo[VOTE_NAME]); + NativeVotes_DisplayPassCustom(menu, "%t", "Set target on fire", "_s", g_VoteInfo[VOTE_NAME]); + LogAction(-1, g_VoteClient[VOTE_CLIENTID], "Vote burn successful, igniting \"%L\"", g_VoteClient[VOTE_CLIENTID]); + + IgniteEntity(g_VoteClient[VOTE_CLIENTID], 19.8); + } + case slay: + { + CPrintToChatAll("%t", "Slayed player", g_VoteInfo[VOTE_NAME]); + NativeVotes_DisplayPassCustom(menu, "%t", "Slayed player", g_VoteInfo[VOTE_NAME]); + LogAction(-1, g_VoteClient[VOTE_CLIENTID], "Vote slay successful, slaying \"%L\"", g_VoteClient[VOTE_CLIENTID]); + + ExtinguishEntity(g_VoteClient[VOTE_CLIENTID]); + ForcePlayerSuicide(g_VoteClient[VOTE_CLIENTID]); + } + case alltalk: + { + CPrintToChatAll("%t", "Cvar changed", "sv_alltalk", (GetConVarBool(g_ConVars[sv_alltalk]) ? "0" : "1")); + NativeVotes_DisplayPassCustom(menu, "%t", "Cvar changed", "sv_alltalk", (GetConVarBool(g_ConVars[sv_alltalk]) ? "0" : "1")); + LogAction(-1, -1, "Changing alltalk to %s due to vote.", (GetConVarBool(g_ConVars[sv_alltalk]) ? "0" : "1")); + SetConVarBool(g_ConVars[sv_alltalk], !GetConVarBool(g_ConVars[sv_alltalk])); + } + case ff: + { + CPrintToChatAll("%t", "Cvar changed", "mp_friendlyfire", (GetConVarBool(g_ConVars[mp_friendlyfire]) ? "0" : "1")); + NativeVotes_DisplayPassCustom(menu, "%t", "Cvar changed", "mp_friendlyfire", (GetConVarBool(g_ConVars[mp_friendlyfire]) ? "0" : "1")); + LogAction(-1, -1, "Changing friendly fire to %s due to vote.", (GetConVarBool(g_ConVars[mp_friendlyfire]) ? "0" : "1")); + SetConVarBool(g_ConVars[mp_friendlyfire], !GetConVarBool(g_ConVars[mp_friendlyfire])); + } + } + } + } + } + + return Plugin_Continue; +} + +float GetVotePercent(int votes, int totalVotes) +{ + return float(votes) / float(totalVotes); +} + +bool TestVoteDelay(int client) +{ + int delay = Internal_CheckVoteDelay(); + if (client <= 0 || GetUserFlagBits(client) & ADMFLAG_GENERIC) + { + delay = 0; + } + + if (delay > 0) + { + if (delay > 60) + { + CReplyToCommand(client, "%t", "Vote Delay Minutes", delay % 60); + } + else + { + CReplyToCommand(client, "%t", "Vote Delay Seconds", delay); + } + + if (g_NativeVotes) + { + NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_Recent, delay); + } + + return false; + } + + return true; +} + +bool Internal_IsVoteInProgress() +{ + if (g_NativeVotes) + { + return NativeVotes_IsVoteInProgress(); + } + + return IsVoteInProgress(); +} + +int Internal_CheckVoteDelay() +{ + if (g_NativeVotes) + { + return NativeVotes_CheckVoteDelay(); + } + + return CheckVoteDelay(); +} + +bool Internal_IsNewVoteAllowed() +{ + if (g_NativeVotes) + { + return NativeVotes_IsNewVoteAllowed(); + } + + return IsNewVoteAllowed(); +} + +void GetPlayerName(int client, char[] name, int maxlen) +{ + int r, g, b, a, color; + GetEntityRenderColor(client, r, g, b, a); + color = (r << 16) | (g << 8) | b; + if (color != 0xFFFFFF) + { + Format(name, maxlen, "{#%06X}%N\x01", color, client); + } + else + { + Format(name, maxlen, "{teamcolor}%N\x01", client); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/nativevotes_funvotes/votealltalk.sp b/addons/sourcemod/scripting/nativevotes_funvotes/votealltalk.sp new file mode 100644 index 0000000..054c174 --- /dev/null +++ b/addons/sourcemod/scripting/nativevotes_funvotes/votealltalk.sp @@ -0,0 +1,138 @@ + /** + * vim: set ts=4 : + * ============================================================================= + * NativeVotes Fun Votes Plugin + * Provides votealltalk functionality + * + * NativeVotes (C)2011-2014 Ross Bemrose (Powerlord). All rights reserved. + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program 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 the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or <http://www.sourcemod.net/license.php>. + * + * Version: $Id$ + */ + +void DisplayVoteAllTalkMenu(int client) +{ + if (Internal_IsVoteInProgress()) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Vote in Progress"); + return; + } + + if (!TestVoteDelay(client)) + { + return; + } + + LogAction(client, -1, "\"%L\" initiated an alltalk vote.", client); + CShowActivity2(client, PLUGIN_PREFIX ... " ", "%t", "Initiated Vote Alltalk"); + + g_VoteType = alltalk; + g_VoteInfo[VOTE_NAME][0] = '\0'; + + if (g_NativeVotes) + { + Handle hVoteMenu; + if (NativeVotes_IsVoteTypeSupported(NativeVotesType_AlltalkOn)) + { + NativeVotesType nVoteType; + if (GetConVarBool(g_ConVars[sv_alltalk])) + { + nVoteType = NativeVotesType_AlltalkOff; + } + else + { + nVoteType = NativeVotesType_AlltalkOn; + } + hVoteMenu = NativeVotes_Create(Handler_NativeVoteCallback, nVoteType, view_as<MenuAction>(MENU_ACTIONS_ALL)); + } + else + { + hVoteMenu = NativeVotes_Create(Handler_NativeVoteCallback, NativeVotesType_Custom_YesNo, view_as<MenuAction>(MENU_ACTIONS_ALL)); + + if (GetConVarBool(g_ConVars[sv_alltalk])) + { + NativeVotes_SetTitle(hVoteMenu, "Votealltalk Off"); + } + else + { + NativeVotes_SetTitle(hVoteMenu, "Votealltalk On"); + } + } + + NativeVotes_DisplayToAll(hVoteMenu, g_ConVars[sv_vote_timer_duration].IntValue); + } + else + { + Handle hVoteMenu = CreateMenu(Handler_VoteCallback, MENU_ACTIONS_ALL); + + if (GetConVarBool(g_ConVars[sv_alltalk])) + { + SetMenuTitle(hVoteMenu, "Votealltalk Off"); + } + else + { + SetMenuTitle(hVoteMenu, "Votealltalk On"); + } + + AddMenuItem(hVoteMenu, VOTE_YES, "Yes"); + AddMenuItem(hVoteMenu, VOTE_NO, "No"); + SetMenuExitButton(hVoteMenu, false); + VoteMenuToAll(hVoteMenu, g_ConVars[sv_vote_timer_duration].IntValue); + } +} + + +public void AdminMenu_VoteAllTalk(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) +{ + switch (action) + { + case TopMenuAction_DisplayOption: + { + Format(buffer, maxlength, "%T", "Alltalk vote", param); + } + case TopMenuAction_SelectOption: + { + DisplayVoteAllTalkMenu(param); + } + case TopMenuAction_DrawOption: + { + /* disable this option if a vote is already running */ + buffer[0] = !Internal_IsNewVoteAllowed() ? ITEMDRAW_IGNORE : ITEMDRAW_DEFAULT; + } + } +} + +public Action Command_VoteAlltalk(int client, int args) +{ + if (args > 0) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " Usage: sm_votealltalk"); + return Plugin_Handled; + } + + DisplayVoteAllTalkMenu(client); + + return Plugin_Handled; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/nativevotes_funvotes/voteburn.sp b/addons/sourcemod/scripting/nativevotes_funvotes/voteburn.sp new file mode 100644 index 0000000..ef0a4e1 --- /dev/null +++ b/addons/sourcemod/scripting/nativevotes_funvotes/voteburn.sp @@ -0,0 +1,192 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * NativeVotes Fun Votes Plugin + * Provides voteburn functionality + * + * NativeVotes (C)2011-2014 Ross Bemrose (Powerlord). All rights reserved. + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program 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 the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or <http://www.sourcemod.net/license.php>. + * + * Version: $Id$ + */ + +void DisplayVoteBurnMenu(int client, int target, char name[MAX_NAME_LENGTH]) +{ + if (!IsPlayerAlive(target)) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Cannot be performed on dead", name); + return; + } + + g_VoteClient[VOTE_CLIENTID] = target; + + char playerName[MAX_NAME_LENGTH]; + GetPlayerName(target, playerName, sizeof(playerName)); + + GetClientName(target, g_VoteInfo[VOTE_NAME], sizeof(g_VoteInfo[])); + + LogAction(client, target, "\"%L\" initiated a burn vote against %N", client, target); + CShowActivity2(client, PLUGIN_PREFIX ... " ", "%t", "Initiated Vote Burn", playerName); + + g_VoteType = burn; + + if (g_NativeVotes) + { + Handle hVoteMenu = NativeVotes_Create(Handler_NativeVoteCallback, NativeVotesType_Custom_YesNo, view_as<MenuAction>(MENU_ACTIONS_ALL)); + NativeVotes_SetTitle(hVoteMenu, "Voteburn player"); + NativeVotes_SetTarget(hVoteMenu, target); + NativeVotes_DisplayToAll(hVoteMenu, g_ConVars[sv_vote_timer_duration].IntValue); + } + else + { + Handle hVoteMenu = CreateMenu(Handler_VoteCallback, view_as<MenuAction>(MENU_ACTIONS_ALL)); + SetMenuTitle(hVoteMenu, "Voteburn player"); + AddMenuItem(hVoteMenu, VOTE_YES, "Yes"); + AddMenuItem(hVoteMenu, VOTE_NO, "No"); + SetMenuExitButton(hVoteMenu, false); + VoteMenuToAll(hVoteMenu, g_ConVars[sv_vote_timer_duration].IntValue); + } +} + +void DisplayBurnTargetMenu(int client) +{ + Handle menu = CreateMenu(MenuHandler_Burn); + + char title[100]; + Format(title, sizeof(title), "%T:", "Burn vote", client); + SetMenuTitle(menu, title); + SetMenuExitBackButton(menu, true); + AddTargetsToMenu(menu, client, true, true); + DisplayMenu(menu, client, MENU_TIME_FOREVER); +} + +public void AdminMenu_VoteBurn(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) +{ + switch (action) + { + case TopMenuAction_DisplayOption: + { + Format(buffer, maxlength, "%T", "Burn vote", param); + } + case TopMenuAction_SelectOption: + { + DisplayBurnTargetMenu(param); + } + case TopMenuAction_DrawOption: + { + /* disable this option if a vote is already running */ + buffer[0] = !Internal_IsNewVoteAllowed() ? ITEMDRAW_IGNORE : ITEMDRAW_DEFAULT; + } + } +} + +public int MenuHandler_Burn(Handle menu, MenuAction action, int param1, int param2) +{ + switch (action) + { + case MenuAction_End: + { + CloseHandle(menu); + } + case MenuAction_Cancel: + { + if (param2 == MenuCancel_ExitBack && g_TopMenu != INVALID_HANDLE) + { + DisplayTopMenu(g_TopMenu, param1, TopMenuPosition_LastCategory); + } + } + case MenuAction_Select: + { + char info[32], name[32]; + int userid, target; + GetMenuItem(menu, param2, info, sizeof(info), _, name, sizeof(name)); + userid = StringToInt(info); + if ((target = GetClientOfUserId(userid)) == 0) + { + CPrintToChat(param1, PLUGIN_PREFIX ... " %t", "Player no longer available"); + } + else if (!CanUserTarget(param1, target)) + { + CPrintToChat(param1, PLUGIN_PREFIX ... " %t", "Unable to target"); + } + else if (!IsPlayerAlive(target)) + { + CPrintToChat(param1, PLUGIN_PREFIX ... " %t", "Player has since died"); + } + else + { + DisplayVoteBurnMenu(param1, target, name); + } + } + } + + return Plugin_Continue; +} + +public Action Command_VoteBurn(int client, int args) +{ + if (args < 1) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " Usage: sm_voteburn <player>"); + return Plugin_Handled; + } + + if (Internal_IsVoteInProgress()) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Vote in Progress"); + return Plugin_Handled; + } + + if (!TestVoteDelay(client)) + { + return Plugin_Handled; + } + + char text[256], arg[64]; + GetCmdArgString(text, sizeof(text)); + + BreakString(text, arg, sizeof(arg)); + + char target_name[MAX_TARGET_LENGTH]; + int target_list[MAXPLAYERS], target_count; + bool tn_is_ml; + if ((target_count = ProcessTargetString( + arg, + client, + target_list, + MAXPLAYERS, + COMMAND_FILTER_NO_MULTI, + target_name, + sizeof(target_name), + tn_is_ml)) <= 0) + { + ReplyToTargetError(client, target_count); + return Plugin_Handled; + } + + DisplayVoteBurnMenu(client, target_list[0], arg); + + return Plugin_Handled; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/nativevotes_funvotes/voteff.sp b/addons/sourcemod/scripting/nativevotes_funvotes/voteff.sp new file mode 100644 index 0000000..72527c2 --- /dev/null +++ b/addons/sourcemod/scripting/nativevotes_funvotes/voteff.sp @@ -0,0 +1,119 @@ + /** + * vim: set ts=4 : + * ============================================================================= + * NativeVotes Fun Votes Plugin + * Provides vote ff functionality + * + * NativeVotes (C)2011-2014 Ross Bemrose (Powerlord). All rights reserved. + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program 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 the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or <http://www.sourcemod.net/license.php>. + * + * Version: $Id$ + */ + +void DisplayVoteFFMenu(int client) +{ + if (Internal_IsVoteInProgress()) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Vote in Progress"); + return; + } + + if (!TestVoteDelay(client)) + { + return; + } + + LogAction(client, -1, "\"%L\" initiated a friendly fire vote.", client); + CShowActivity2(client, PLUGIN_PREFIX ... " ", "%t", "Initiated Vote FF"); + + g_VoteType = ff; + g_VoteInfo[VOTE_NAME][0] = '\0'; + + if (g_NativeVotes) + { + Handle hVoteMenu = NativeVotes_Create(Handler_NativeVoteCallback, NativeVotesType_Custom_YesNo, view_as<MenuAction>(MENU_ACTIONS_ALL)); + + if (GetConVarBool(g_ConVars[mp_friendlyfire])) + { + NativeVotes_SetTitle(hVoteMenu, "Voteff Off"); + } + else + { + NativeVotes_SetTitle(hVoteMenu, "Voteff On"); + } + NativeVotes_DisplayToAll(hVoteMenu, g_ConVars[sv_vote_timer_duration].IntValue); + } + else + { + Handle hVoteMenu = CreateMenu(Handler_VoteCallback, view_as<MenuAction>(MENU_ACTIONS_ALL)); + + if (GetConVarBool(g_ConVars[mp_friendlyfire])) + { + SetMenuTitle(hVoteMenu, "Voteff Off"); + } + else + { + SetMenuTitle(hVoteMenu, "Voteff On"); + } + + AddMenuItem(hVoteMenu, VOTE_YES, "Yes"); + AddMenuItem(hVoteMenu, VOTE_NO, "No"); + SetMenuExitButton(hVoteMenu, false); + VoteMenuToAll(hVoteMenu, g_ConVars[sv_vote_timer_duration].IntValue); + } +} + +public void AdminMenu_VoteFF(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) +{ + switch (action) + { + case TopMenuAction_DisplayOption: + { + Format(buffer, maxlength, "%T", "Vote FF", param); + } + case TopMenuAction_SelectOption: + { + DisplayVoteFFMenu(param); + } + case TopMenuAction_DrawOption: + { + /* disable this option if a vote is already running */ + buffer[0] = !Internal_IsNewVoteAllowed() ? ITEMDRAW_IGNORE : ITEMDRAW_DEFAULT; + } + } +} + +public Action Command_VoteFF(int client, int args) +{ + if (args > 0) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " Usage: sm_voteff"); + return Plugin_Handled; + } + + DisplayVoteFFMenu(client); + + return Plugin_Handled; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/nativevotes_funvotes/votegravity.sp b/addons/sourcemod/scripting/nativevotes_funvotes/votegravity.sp new file mode 100644 index 0000000..3948a58 --- /dev/null +++ b/addons/sourcemod/scripting/nativevotes_funvotes/votegravity.sp @@ -0,0 +1,159 @@ + /** + * vim: set ts=4 : + * ============================================================================= + * NativeVotes Fun Votes Plugin + * Provides votegravity functionality + * + * NativeVotes (C)2011-2014 Ross Bemrose (Powerlord). All rights reserved. + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program 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 the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or <http://www.sourcemod.net/license.php>. + * + * Version: $Id$ + */ + +void DisplayVoteGravityMenu(int client, int count, char items[5][64]) +{ + LogAction(client, -1, "\"%L\" initiated a gravity vote.", client); + CShowActivity2(client, PLUGIN_PREFIX ... " ", "%t", "Initiated Vote Gravity"); + + g_VoteType = gravity; + + if (g_NativeVotes && (count == 1 || NativeVotes_IsVoteTypeSupported(NativeVotesType_Custom_Mult)) ) + { + Handle hVoteMenu; + if (count == 1) + { + strcopy(g_VoteInfo[VOTE_NAME], sizeof(g_VoteInfo[]), items[0]); + + hVoteMenu = NativeVotes_Create(Handler_NativeVoteCallback, NativeVotesType_Custom_YesNo, view_as<MenuAction>(MENU_ACTIONS_ALL)); + NativeVotes_SetTitle(hVoteMenu, "Change Gravity To"); + // No details for custom votes + } + else + { + hVoteMenu = NativeVotes_Create(Handler_NativeVoteCallback, NativeVotesType_Custom_Mult, view_as<MenuAction>(MENU_ACTIONS_ALL)); + NativeVotes_SetTitle(hVoteMenu, "Gravity Vote"); + for (int i = 0; i < count; i++) + { + NativeVotes_AddItem(hVoteMenu, items[i], items[i]); + } + } + NativeVotes_DisplayToAll(hVoteMenu, g_ConVars[sv_vote_timer_duration].IntValue); + } + else + { + Handle hVoteMenu = CreateMenu(Handler_VoteCallback, view_as<MenuAction>(MENU_ACTIONS_ALL)); + + if (count == 1) + { + strcopy(g_VoteInfo[VOTE_NAME], sizeof(g_VoteInfo[]), items[0]); + + SetMenuTitle(hVoteMenu, "Change Gravity To"); + AddMenuItem(hVoteMenu, items[0], "Yes"); + AddMenuItem(hVoteMenu, VOTE_NO, "No"); + } + else + { + g_VoteInfo[VOTE_NAME][0] = '\0'; + + SetMenuTitle(hVoteMenu, "Gravity Vote"); + for (int i = 0; i < count; i++) + { + AddMenuItem(hVoteMenu, items[i], items[i]); + } + } + + SetMenuExitButton(hVoteMenu, false); + VoteMenuToAll(hVoteMenu, g_ConVars[sv_vote_timer_duration].IntValue); + } +} + +public void AdminMenu_VoteGravity(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) +{ + if (action == TopMenuAction_DisplayOption) + { + Format(buffer, maxlength, "%T", "Gravity vote", param); + } + else if (action == TopMenuAction_SelectOption) + { + /* Might need a better way of selecting the list of pre-defined gravity choices */ + char items[5][5] ={"200","400","800","1600","3200"}; + DisplayVoteGravityMenu(param,5, items); + } + else if (action == TopMenuAction_DrawOption) + { + /* disable this option if a vote is already running */ + buffer[0] = !Internal_IsNewVoteAllowed() ? ITEMDRAW_IGNORE : ITEMDRAW_DEFAULT; + } +} + +public Action Command_VoteGravity(int client, int args) +{ + if (args < 1) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " Usage: sm_votegravity <amount> [amount2] ... [amount5]"); + return Plugin_Handled; + } + + if (Internal_IsVoteInProgress()) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Vote in Progress"); + return Plugin_Handled; + } + + if (!TestVoteDelay(client)) + { + return Plugin_Handled; + } + + char text[256]; + GetCmdArgString(text, sizeof(text)); + + char items[5][64]; + int count; + int len, pos; + + while (pos != -1 && count < 5) + { + pos = BreakString(text[len], items[count], sizeof(items[])); + + float temp; + if (StringToFloatEx(items[count], temp) == 0) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Invalid Amount"); + return Plugin_Handled; + } + + count++; + + if (pos != -1) + { + len += pos; + } + } + + DisplayVoteGravityMenu(client, count, items); + + return Plugin_Handled; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/nativevotes_funvotes/voteslay.sp b/addons/sourcemod/scripting/nativevotes_funvotes/voteslay.sp new file mode 100644 index 0000000..1376a3d --- /dev/null +++ b/addons/sourcemod/scripting/nativevotes_funvotes/voteslay.sp @@ -0,0 +1,194 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * NativeVotes Fun Votes Plugin + * Provides voteslay functionality + * + * NativeVotes (C)2011-2014 Ross Bemrose (Powerlord). All rights reserved. + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program 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 the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or <http://www.sourcemod.net/license.php>. + * + * Version: $Id$ + */ + +void DisplayVoteSlayMenu(int client, int target, char name[MAX_NAME_LENGTH]) +{ + if (!IsPlayerAlive(target)) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Cannot be performed on dead", name); + return; + } + + g_VoteClient[VOTE_CLIENTID] = target; + + char playerName[MAX_NAME_LENGTH]; + GetPlayerName(target, playerName, sizeof(playerName)); + + GetClientName(target, g_VoteInfo[VOTE_NAME], sizeof(g_VoteInfo[])); + + LogAction(client, target, "\"%L\" initiated a slay vote against %N", client, target); + CShowActivity2(client, PLUGIN_PREFIX ... " ", "%t", "Initiated Vote Slay", playerName); + + g_VoteType = slay; + + if (g_NativeVotes) + { + Handle hVoteMenu = NativeVotes_Create(Handler_NativeVoteCallback, NativeVotesType_Custom_YesNo, view_as<MenuAction>(MENU_ACTIONS_ALL)); + NativeVotes_SetTitle(hVoteMenu, "Voteslay Player"); + NativeVotes_SetTarget(hVoteMenu, target); + NativeVotes_DisplayToAll(hVoteMenu, g_ConVars[sv_vote_timer_duration].IntValue); + } + else + { + Handle hVoteMenu = CreateMenu(Handler_VoteCallback, view_as<MenuAction>(MENU_ACTIONS_ALL)); + SetMenuTitle(hVoteMenu, "Voteslay Player"); + AddMenuItem(hVoteMenu, VOTE_YES, "Yes"); + AddMenuItem(hVoteMenu, VOTE_NO, "No"); + SetMenuExitButton(hVoteMenu, false); + VoteMenuToAll(hVoteMenu, g_ConVars[sv_vote_timer_duration].IntValue); + } +} + +void DisplaySlayTargetMenu(int client) +{ + Handle menu = CreateMenu(MenuHandler_Slay); + char title[100]; + Format(title, sizeof(title), "%T:", "Slay vote", client); + SetMenuTitle(menu, title); + SetMenuExitBackButton(menu, true); + + AddTargetsToMenu(menu, client, true, true); + + DisplayMenu(menu, client, MENU_TIME_FOREVER); +} + +public void AdminMenu_VoteSlay(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) +{ + switch (action) + { + case TopMenuAction_DisplayOption: + { + Format(buffer, maxlength, "%T", "Slay vote", param); + } + case TopMenuAction_SelectOption: + { + DisplaySlayTargetMenu(param); + } + case TopMenuAction_DrawOption: + { + /* disable this option if a vote is already running */ + buffer[0] = !Internal_IsNewVoteAllowed() ? ITEMDRAW_IGNORE : ITEMDRAW_DEFAULT; + } + } +} + +public int MenuHandler_Slay(Handle menu, MenuAction action, int param1, int param2) +{ + switch (action) + { + case MenuAction_End: + { + CloseHandle(menu); + } + case MenuAction_Cancel: + { + if (param2 == MenuCancel_ExitBack && g_TopMenu != INVALID_HANDLE) + { + DisplayTopMenu(g_TopMenu, param1, TopMenuPosition_LastCategory); + } + } + case MenuAction_Select: + { + char info[32], name[32]; + int userid, target; + GetMenuItem(menu, param2, info, sizeof(info), _, name, sizeof(name)); + userid = StringToInt(info); + + if ((target = GetClientOfUserId(userid)) == 0) + { + CPrintToChat(param1, PLUGIN_PREFIX ... " %t", "Player no longer available"); + } + else if (!CanUserTarget(param1, target)) + { + CPrintToChat(param1, PLUGIN_PREFIX ... " %t", "Unable to target"); + } + else if (!IsPlayerAlive(target)) + { + CPrintToChat(param1, PLUGIN_PREFIX ... " %t", "Player has since died"); + } + else + { + DisplayVoteSlayMenu(param1, target, name); + } + } + } + + return Plugin_Continue; +} + +public Action Command_VoteSlay(int client, int args) +{ + if (args < 1) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " Usage: sm_voteslay <player>"); + return Plugin_Handled; + } + + if (Internal_IsVoteInProgress()) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Vote in Progress"); + return Plugin_Handled; + } + + if (!TestVoteDelay(client)) + { + return Plugin_Handled; + } + + char text[256], arg[64]; + GetCmdArgString(text, sizeof(text)); + + BreakString(text, arg, sizeof(arg)); + + char target_name[MAX_TARGET_LENGTH]; + int target_list[MAXPLAYERS], target_count; + bool tn_is_ml; + if ((target_count = ProcessTargetString( + arg, + client, + target_list, + MAXPLAYERS, + COMMAND_FILTER_NO_MULTI, + target_name, + sizeof(target_name), + tn_is_ml)) <= 0) + { + ReplyToTargetError(client, target_count); + return Plugin_Handled; + } + + DisplayVoteSlayMenu(client, target_list[0], arg); + + return Plugin_Handled; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/nativevotes_kickvote_immunity.sp b/addons/sourcemod/scripting/nativevotes_kickvote_immunity.sp deleted file mode 100644 index 8bc7f44..0000000 --- a/addons/sourcemod/scripting/nativevotes_kickvote_immunity.sp +++ /dev/null @@ -1,169 +0,0 @@ -/** - * vim: set ts=4 : - * ============================================================================= - * NativeVotes Kick Vote Immunity - * Causes TF2 kick votes to fail against people who the current user can't target. - * - * Inspired by psychonic's [TF2] Basic Votekick Immunity - * - * NativeVotes Kick Vote Immunity (C)2014 Powerlord (Ross Bemrose). - * All rights reserved. - * ============================================================================= - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License, version 3.0, as published by the - * Free Software Foundation. - * - * This program 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 the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see <http://www.gnu.org/licenses/>. - * - * As a special exception, AlliedModders LLC gives you permission to link the - * code of this program (as well as its derivative works) to "Half-Life 2," the - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software - * by the Valve Corporation. You must obey the GNU General Public License in - * all respects for all other code used. Additionally, AlliedModders LLC grants - * this exception to all derivative works. AlliedModders LLC defines further - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), - * or <http://www.sourcemod.net/license.php>. - * - * Version: $Id$ - */ -#include <sourcemod> -#include <sdktools> - -#include <nativevotes> - -#pragma semicolon 1 -#pragma newdecls required - -#define VERSION "3.0.0" - -ConVar g_Cvar_Votes; -ConVar g_Cvar_KickVote; -ConVar g_Cvar_KickVoteMvM; - -bool g_bRegistered = false; - -bool g_bMapActive = false; - -public Plugin myinfo = { - name = "NativeVotes Kick Vote Immunity", - author = "Powerlord", - description = "Causes TF2 kick votes to fail against people who the current user can't target.", - version = VERSION, - url = "https://forums.alliedmods.net/showthread.php?t=208008" -}; - -public void OnPluginStart() -{ - g_Cvar_Votes = FindConVar("sv_allow_votes"); - g_Cvar_KickVote = FindConVar("sv_vote_issue_kick_allowed"); - g_Cvar_KickVoteMvM = FindConVar("sv_vote_issue_kick_allowed_mvm"); - - HookConVarChange(g_Cvar_Votes, Cvar_CheckEnable); - HookConVarChange(g_Cvar_KickVote, Cvar_CheckEnable); - HookConVarChange(g_Cvar_KickVoteMvM, Cvar_CheckEnable); - - LoadTranslations("common.phrases"); - CreateConVar("nativevotes_kickvote_immunity_version", VERSION, "NativeVotes Kickvote Immunity version", FCVAR_NOTIFY|FCVAR_DONTRECORD|FCVAR_SPONLY); -} - -public void OnAllPluginsLoaded() -{ - if (GetFeatureStatus(FeatureType_Native, "NativeVotes_AreVoteCommandsSupported") != FeatureStatus_Available) - { - SetFailState("Requires NativeVotes 1.1 or newer"); - } -} - -public void OnConfigsExecuted() -{ - g_bMapActive = true; - CheckStatus(); -} - -public void OnMapEnd() -{ - g_bMapActive = false; -} - -public void OnPluginEnd() -{ - UnregisterVoteCommand(); -} - -void RegisterVoteCommand() -{ - if (!g_bRegistered && - NativeVotes_AreVoteCommandsSupported()) - { - NativeVotes_RegisterVoteCommand(NativeVotesOverride_Kick, KickVoteHandler); - g_bRegistered = true; - } -} - -void UnregisterVoteCommand() -{ - if (g_bRegistered) - { - NativeVotes_UnregisterVoteCommand(NativeVotesOverride_Kick, KickVoteHandler); - g_bRegistered = false; - } -} - -public void Cvar_CheckEnable(ConVar convar, const char[] oldValue, const char[] newValue) -{ - if (!g_bMapActive) - return; - - CheckStatus(); -} - -void CheckStatus() -{ - bool bIsMvM = IsMvM(); - if (g_bRegistered) - { - if (!g_Cvar_Votes.BoolValue || - (bIsMvM && !g_Cvar_KickVoteMvM.BoolValue) || - (!bIsMvM && !g_Cvar_KickVote.BoolValue) - ) - { - LogMessage("Disabling."); - UnregisterVoteCommand(); - } - } - else - { - if (g_Cvar_Votes.BoolValue && - ((bIsMvM && g_Cvar_KickVoteMvM.BoolValue) || - (!bIsMvM && g_Cvar_KickVote.BoolValue)) - ) - { - LogMessage("Enabling."); - RegisterVoteCommand(); - } - } -} - -stock bool IsMvM() -{ - return view_as<bool>(GameRules_GetProp("m_bPlayingMannVsMachine")); -} - -public Action KickVoteHandler(int client, NativeVotesOverride overrideType, const char[] voteArgument, NativeVotesKickType kickType, int target) -{ - if (!CanUserTarget(client, target)) - { - NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_CantKickAdmin); - PrintToChat(client, "%t", "Unable to target"); - return Plugin_Stop; - } - - return Plugin_Continue; -} \ No newline at end of file diff --git a/addons/sourcemod/scripting/nativevotes_mapchooser.sp b/addons/sourcemod/scripting/nativevotes_mapchooser.sp index ffaccc0..49b3ef8 100644 --- a/addons/sourcemod/scripting/nativevotes_mapchooser.sp +++ b/addons/sourcemod/scripting/nativevotes_mapchooser.sp @@ -1,1569 +1,1922 @@ -/** - * vim: set ts=4 : - * ============================================================================= - * SourceMod NativeVotes Mapchooser Plugin - * Creates a map vote at appropriate times, setting sm_nextmap to the winning - * vote - * Updated with NativeVotes support - * - * NativeVotes (C)2011-2016 Ross Bemrose (Powerlord). All rights reserved. - * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. - * ============================================================================= - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License, version 3.0, as published by the - * Free Software Foundation. - * - * This program 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 the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see <http://www.gnu.org/licenses/>. - * - * As a special exception, AlliedModders LLC gives you permission to link the - * code of this program (as well as its derivative works) to "Half-Life 2," the - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software - * by the Valve Corporation. You must obey the GNU General Public License in - * all respects for all other code used. Additionally, AlliedModders LLC grants - * this exception to all derivative works. AlliedModders LLC defines further - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), - * or <http://www.sourcemod.net/license.php>. - * - * Version: $Id$ - */ - -#include <sourcemod> -#include <mapchooser> -#include <nextmap> - -#undef REQUIRE_PLUGIN -#include <nativevotes> -#define REQUIRE_PLUGIN - -#pragma semicolon 1 -#pragma newdecls required - -#define VERSION "1.8.0 beta 1" - -public Plugin myinfo = -{ - name = "NativeVotes MapChooser", - author = "AlliedModders LLC and Powerlord", - description = "Automated Map Voting", - version = VERSION, - url = "https://forums.alliedmods.net/showthread.php?t=208010" -}; - -/* Valve ConVars */ -ConVar g_Cvar_Winlimit; -ConVar g_Cvar_Maxrounds; -ConVar g_Cvar_Fraglimit; -ConVar g_Cvar_Bonusroundtime; - -/* Plugin ConVars */ -ConVar g_Cvar_StartTime; -ConVar g_Cvar_StartRounds; -ConVar g_Cvar_StartFrags; -ConVar g_Cvar_ExtendTimeStep; -ConVar g_Cvar_ExtendRoundStep; -ConVar g_Cvar_ExtendFragStep; -ConVar g_Cvar_ExcludeMaps; -ConVar g_Cvar_IncludeMaps; -ConVar g_Cvar_NoVoteMode; -ConVar g_Cvar_Extend; -ConVar g_Cvar_DontChange; -ConVar g_Cvar_EndOfMapVote; -ConVar g_Cvar_VoteDuration; -ConVar g_Cvar_RunOff; -ConVar g_Cvar_RunOffPercent; - -Handle g_VoteTimer = null; -Handle g_RetryTimer = null; - -// g_MapList stores unresolved names so we can resolve them after every map change in the workshop updates. -// g_OldMapList and g_NextMapList are resolved. g_NominateList depends on the nominations implementation. -/* Data Handles */ -ArrayList g_MapList; -ArrayList g_NominateList; -ArrayList g_NominateOwners; -ArrayList g_OldMapList; -ArrayList g_NextMapList; -Menu g_VoteMenu; -NativeVote g_VoteNative; - -int g_Extends; -int g_TotalRounds; -bool g_HasVoteStarted; -bool g_WaitingForVote; -bool g_MapVoteCompleted; -bool g_ChangeMapAtRoundEnd; -bool g_ChangeMapInProgress; -int g_mapFileSerial = -1; - -MapChange g_ChangeTime; - -Handle g_NominationsResetForward = null; -Handle g_MapVoteStartedForward = null; - -/* Upper bound of how many team there could be */ -#define MAXTEAMS 10 -int g_winCount[MAXTEAMS]; - -#define VOTE_EXTEND "##extend##" -#define VOTE_DONTCHANGE "##dontchange##" - -// NativeVotes -bool g_NativeVotes; -#define LIBRARY "nativevotes" - -public void OnPluginStart() -{ - LoadTranslations("mapchooser.phrases"); - LoadTranslations("common.phrases"); - - int arraySize = ByteCountToCells(PLATFORM_MAX_PATH); - g_MapList = new ArrayList(arraySize); - g_NominateList = new ArrayList(arraySize); - g_NominateOwners = new ArrayList(); - g_OldMapList = new ArrayList(arraySize); - g_NextMapList = new ArrayList(arraySize); - CreateConVar("nativevotes_mapchooser_version", VERSION, "NativeVotes MapChooser version", FCVAR_DONTRECORD|FCVAR_NOTIFY|FCVAR_SPONLY); - - g_Cvar_EndOfMapVote = CreateConVar("sm_mapvote_endvote", "1", "Specifies if MapChooser should run an end of map vote", _, true, 0.0, true, 1.0); - - g_Cvar_StartTime = CreateConVar("sm_mapvote_start", "3.0", "Specifies when to start the vote based on time remaining.", _, true, 1.0); - g_Cvar_StartRounds = CreateConVar("sm_mapvote_startround", "2.0", "Specifies when to start the vote based on rounds remaining. Use 0 on TF2 to start vote during bonus round time", _, true, 0.0); - g_Cvar_StartFrags = CreateConVar("sm_mapvote_startfrags", "5.0", "Specifies when to start the vote base on frags remaining.", _, true, 1.0); - g_Cvar_ExtendTimeStep = CreateConVar("sm_extendmap_timestep", "15", "Specifies how much many more minutes each extension makes", _, true, 5.0); - g_Cvar_ExtendRoundStep = CreateConVar("sm_extendmap_roundstep", "5", "Specifies how many more rounds each extension makes", _, true, 1.0); - g_Cvar_ExtendFragStep = CreateConVar("sm_extendmap_fragstep", "10", "Specifies how many more frags are allowed when map is extended.", _, true, 5.0); - g_Cvar_ExcludeMaps = CreateConVar("sm_mapvote_exclude", "5", "Specifies how many past maps to exclude from the vote.", _, true, 0.0); - g_Cvar_IncludeMaps = CreateConVar("sm_mapvote_include", "5", "Specifies how many maps to include in the vote.", _, true, 2.0, true, 6.0); - g_Cvar_NoVoteMode = CreateConVar("sm_mapvote_novote", "1", "Specifies whether or not MapChooser should pick a map if no votes are received.", _, true, 0.0, true, 1.0); - g_Cvar_Extend = CreateConVar("sm_mapvote_extend", "0", "Number of extensions allowed each map.", _, true, 0.0); - g_Cvar_DontChange = CreateConVar("sm_mapvote_dontchange", "1", "Specifies if a 'Don't Change' option should be added to early votes", _, true, 0.0); - g_Cvar_VoteDuration = CreateConVar("sm_mapvote_voteduration", "20", "Specifies how long the mapvote should be available for.", _, true, 5.0); - g_Cvar_RunOff = CreateConVar("sm_mapvote_runoff", "0", "Hold run of votes if winning choice is less than a certain margin", _, true, 0.0, true, 1.0); - g_Cvar_RunOffPercent = CreateConVar("sm_mapvote_runoffpercent", "50", "If winning choice has less than this percent of votes, hold a runoff", _, true, 0.0, true, 100.0); - - RegAdminCmd("sm_mapvote", Command_Mapvote, ADMFLAG_CHANGEMAP, "sm_mapvote - Forces MapChooser to attempt to run a map vote now."); - RegAdminCmd("sm_setnextmap", Command_SetNextmap, ADMFLAG_CHANGEMAP, "sm_setnextmap <map>"); - - g_Cvar_Winlimit = FindConVar("mp_winlimit"); - g_Cvar_Maxrounds = FindConVar("mp_maxrounds"); - g_Cvar_Fraglimit = FindConVar("mp_fraglimit"); - g_Cvar_Bonusroundtime = FindConVar("mp_bonusroundtime"); - - if (g_Cvar_Winlimit || g_Cvar_Maxrounds) - { - char folder[64]; - GetGameFolderName(folder, sizeof(folder)); - - if (strcmp(folder, "tf") == 0) - { - HookEvent("teamplay_win_panel", Event_TeamPlayWinPanel); - HookEvent("teamplay_restart_round", Event_TFRestartRound); - HookEvent("arena_win_panel", Event_TeamPlayWinPanel); - } - else if (strcmp(folder, "nucleardawn") == 0) - { - HookEvent("round_win", Event_RoundEnd); - } - else - { - HookEvent("round_end", Event_RoundEnd); - } - } - - if (g_Cvar_Fraglimit) - { - HookEvent("player_death", Event_PlayerDeath); - } - - AutoExecConfig(true, "mapchooser"); - - //Change the mp_bonusroundtime max so that we have time to display the vote - //If you display a vote during bonus time good defaults are 17 vote duration and 19 mp_bonustime - if (g_Cvar_Bonusroundtime) - { - g_Cvar_Bonusroundtime.SetBounds(ConVarBound_Upper, true, 30.0); - } - - g_NominationsResetForward = CreateGlobalForward("OnNominationRemoved", ET_Ignore, Param_String, Param_Cell); - g_MapVoteStartedForward = CreateGlobalForward("OnMapVoteStarted", ET_Ignore); -} - -public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) -{ - RegPluginLibrary("mapchooser"); - - CreateNative("NominateMap", Native_NominateMap); - CreateNative("RemoveNominationByMap", Native_RemoveNominationByMap); - CreateNative("RemoveNominationByOwner", Native_RemoveNominationByOwner); - CreateNative("InitiateMapChooserVote", Native_InitiateVote); - CreateNative("CanMapChooserStartVote", Native_CanVoteStart); - CreateNative("HasEndOfMapVoteFinished", Native_CheckVoteDone); - CreateNative("GetExcludeMapList", Native_GetExcludeMapList); - CreateNative("GetNominatedMapList", Native_GetNominatedMapList); - CreateNative("EndOfMapVoteEnabled", Native_EndOfMapVoteEnabled); - - return APLRes_Success; -} - -public void OnAllPluginsLoaded() -{ - if (FindPluginByFile("mapchooser.smx") != null) - { - LogMessage("Unloading mapchooser to prevent conflicts..."); - ServerCommand("sm plugins unload mapchooser"); - - char oldPath[PLATFORM_MAX_PATH]; - char newPath[PLATFORM_MAX_PATH]; - - BuildPath(Path_SM, oldPath, sizeof(oldPath), "plugins/mapchooser.smx"); - BuildPath(Path_SM, newPath, sizeof(newPath), "plugins/disabled/mapchooser.smx"); - if (RenameFile(newPath, oldPath)) - { - LogMessage("Moving mapchooser to disabled."); - } - } - - g_NativeVotes = LibraryExists(LIBRARY) && NativeVotes_IsVoteTypeSupported(NativeVotesType_NextLevelMult); -} - -public void OnLibraryAdded(const char[] name) -{ - if (StrEqual(name, LIBRARY, false) && NativeVotes_IsVoteTypeSupported(NativeVotesType_NextLevelMult)) - { - g_NativeVotes = true; - } -} - -public void OnLibraryRemoved(const char[] name) -{ - if (StrEqual(name, LIBRARY, false)) - { - g_NativeVotes = false; - } -} - -public void OnConfigsExecuted() -{ - if (ReadMapList(g_MapList, - g_mapFileSerial, - "mapchooser", - MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER) - != null) - - { - if (g_mapFileSerial == -1) - { - LogError("Unable to create a valid map list."); - } - } - - CreateNextVote(); - SetupTimeleftTimer(); - - g_TotalRounds = 0; - - g_Extends = 0; - - g_MapVoteCompleted = false; - - g_NominateList.Clear(); - g_NominateOwners.Clear(); - - for (int i=0; i<MAXTEAMS; i++) - { - g_winCount[i] = 0; - } - - - /* Check if mapchooser will attempt to start mapvote during bonus round time - TF2 Only */ - if (g_Cvar_Bonusroundtime && !g_Cvar_StartRounds.IntValue) - { - if (g_Cvar_Bonusroundtime.FloatValue <= g_Cvar_VoteDuration.FloatValue) - { - LogMessage("Warning - Bonus Round Time shorter than Vote Time. Votes during bonus round may not have time to complete"); - } - } -} - -public void OnMapEnd() -{ - g_HasVoteStarted = false; - g_WaitingForVote = false; - g_ChangeMapAtRoundEnd = false; - g_ChangeMapInProgress = false; - - g_VoteTimer = null; - g_RetryTimer = null; - - char map[PLATFORM_MAX_PATH]; - GetCurrentMap(map, sizeof(map)); - g_OldMapList.PushString(map); - - if (g_OldMapList.Length > g_Cvar_ExcludeMaps.IntValue) - { - g_OldMapList.Erase(0); - } -} - -public void OnClientDisconnect(int client) -{ - int index = g_NominateOwners.FindValue(client); - - if (index == -1) - { - return; - } - - char oldmap[PLATFORM_MAX_PATH]; - g_NominateList.GetString(index, oldmap, sizeof(oldmap)); - Call_StartForward(g_NominationsResetForward); - Call_PushString(oldmap); - Call_PushCell(g_NominateOwners.Get(index)); - Call_Finish(); - - g_NominateOwners.Erase(index); - g_NominateList.Erase(index); -} - -public Action Command_SetNextmap(int client, int args) -{ - if (args < 1) - { - ReplyToCommand(client, "[SM] Usage: sm_setnextmap <map>"); - return Plugin_Handled; - } - - char map[PLATFORM_MAX_PATH]; - char displayName[PLATFORM_MAX_PATH]; - GetCmdArg(1, map, sizeof(map)); - - if (FindMap(map, displayName, sizeof(displayName)) == FindMap_NotFound) - { - ReplyToCommand(client, "[SM] %t", "Map was not found", map); - return Plugin_Handled; - } - - GetMapDisplayName(displayName, displayName, sizeof(displayName)); - - ShowActivity(client, "%t", "Changed Next Map", displayName); - LogAction(client, -1, "\"%L\" changed nextmap to \"%s\"", client, map); - - SetNextMap(map); - g_MapVoteCompleted = true; - - return Plugin_Handled; -} - -public void OnMapTimeLeftChanged() -{ - if (g_MapList.Length) - { - SetupTimeleftTimer(); - } -} - -void SetupTimeleftTimer() -{ - int time; - if (GetMapTimeLeft(time) && time > 0) - { - int startTime = g_Cvar_StartTime.IntValue * 60; - if (time - startTime < 0 && g_Cvar_EndOfMapVote.BoolValue && !g_MapVoteCompleted && !g_HasVoteStarted) - { - InitiateVote(MapChange_MapEnd, null); - } - else - { - if (g_VoteTimer != null) - { - KillTimer(g_VoteTimer); - g_VoteTimer = null; - } - - //g_VoteTimer = CreateTimer(float(time - startTime), Timer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE); - DataPack data; - g_VoteTimer = CreateDataTimer(float(time - startTime), Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE); - data.WriteCell(MapChange_MapEnd); - data.WriteCell(INVALID_HANDLE); - data.Reset(); - } - } -} - -public Action Timer_StartMapVote(Handle timer, DataPack data) -{ - if (timer == g_RetryTimer) - { - g_WaitingForVote = false; - g_RetryTimer = null; - } - else - { - g_VoteTimer = null; - } - - if (!g_MapList.Length || !g_Cvar_EndOfMapVote.BoolValue || g_MapVoteCompleted || g_HasVoteStarted) - { - return Plugin_Stop; - } - - MapChange mapChange = view_as<MapChange>(data.ReadCell()); - ArrayList hndl = view_as<ArrayList>(data.ReadCell()); - - InitiateVote(mapChange, hndl); - - return Plugin_Stop; -} - -public void Event_TFRestartRound(Event event, const char[] name, bool dontBroadcast) -{ - /* Game got restarted - reset our round count tracking */ - g_TotalRounds = 0; -} - -public void Event_TeamPlayWinPanel(Event event, const char[] name, bool dontBroadcast) -{ - if (g_ChangeMapAtRoundEnd) - { - g_ChangeMapAtRoundEnd = false; - CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE); - g_ChangeMapInProgress = true; - } - - int bluescore = event.GetInt("blue_score"); - int redscore = event.GetInt("red_score"); - - if (event.GetInt("round_complete") == 1 || StrEqual(name, "arena_win_panel")) - { - g_TotalRounds++; - - if (!g_MapList.Length || g_HasVoteStarted || g_MapVoteCompleted || !g_Cvar_EndOfMapVote.BoolValue) - { - return; - } - - CheckMaxRounds(g_TotalRounds); - - switch(event.GetInt("winning_team")) - { - case 3: - { - CheckWinLimit(bluescore); - } - case 2: - { - CheckWinLimit(redscore); - } - //We need to do nothing on winning_team == 0 this indicates stalemate. - default: - { - return; - } - } - } -} -/* You ask, why don't you just use team_score event? And I answer... Because CSS doesn't. */ -public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) -{ - if (g_ChangeMapAtRoundEnd) - { - g_ChangeMapAtRoundEnd = false; - CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE); - g_ChangeMapInProgress = true; - } - - int winner; - if (strcmp(name, "round_win") == 0) - { - // Nuclear Dawn - winner = event.GetInt("team"); - } - else - { - winner = event.GetInt("winner"); - } - - if (winner == 0 || winner == 1 || !g_Cvar_EndOfMapVote.BoolValue) - { - return; - } - - if (winner >= MAXTEAMS) - { - SetFailState("Mod exceed maximum team count - Please file a bug report."); - } - - g_TotalRounds++; - - g_winCount[winner]++; - - if (!g_MapList.Length || g_HasVoteStarted || g_MapVoteCompleted) - { - return; - } - - CheckWinLimit(g_winCount[winner]); - CheckMaxRounds(g_TotalRounds); -} - -public void CheckWinLimit(int winner_score) -{ - if (g_Cvar_Winlimit) - { - int winlimit = g_Cvar_Winlimit.IntValue; - if (winlimit) - { - if (winner_score >= (winlimit - g_Cvar_StartRounds.IntValue)) - { - InitiateVote(MapChange_MapEnd, null); - } - } - } -} - -public void CheckMaxRounds(int roundcount) -{ - if (g_Cvar_Maxrounds) - { - int maxrounds = g_Cvar_Maxrounds.IntValue; - if (maxrounds) - { - if (roundcount >= (maxrounds - g_Cvar_StartRounds.IntValue)) - { - InitiateVote(MapChange_MapEnd, null); - } - } - } -} - -public void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) -{ - if (!g_MapList.Length || !g_Cvar_Fraglimit || g_HasVoteStarted) - { - return; - } - - if (!g_Cvar_Fraglimit.IntValue || !g_Cvar_EndOfMapVote.BoolValue) - { - return; - } - - if (g_MapVoteCompleted) - { - return; - } - - int fragger = GetClientOfUserId(event.GetInt("attacker")); - - if (!fragger) - { - return; - } - - if (GetClientFrags(fragger) >= (g_Cvar_Fraglimit.IntValue - g_Cvar_StartFrags.IntValue)) - { - InitiateVote(MapChange_MapEnd, null); - } -} - -public Action Command_Mapvote(int client, int args) -{ - InitiateVote(MapChange_MapEnd, null); - - return Plugin_Handled; -} - -/** - * Starts a new map vote - * - * @param when When the resulting map change should occur. - * @param inputlist Optional list of maps to use for the vote, otherwise an internal list of nominations + random maps will be used. - * @param noSpecials Block special vote options like extend/nochange (upgrade this to bitflags instead?) - */ -void InitiateVote(MapChange when, ArrayList inputlist=null) -{ - g_WaitingForVote = true; - - if ((g_NativeVotes && NativeVotes_IsVoteInProgress()) || (!g_NativeVotes && IsVoteInProgress())) - //if (IsVoteInProgress()) - { - // Can't start a vote, try again in 5 seconds. - //g_RetryTimer = CreateTimer(5.0, Timer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE); - - DataPack data; - g_RetryTimer = CreateDataTimer(5.0, Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE); - data.WriteCell(when); - data.WriteCell(inputlist); - data.Reset(); - return; - } - - /* If the main map vote has completed (and chosen result) and its currently changing (not a delayed change) we block further attempts */ - if (g_MapVoteCompleted && g_ChangeMapInProgress) - { - return; - } - - g_ChangeTime = when; - - g_WaitingForVote = false; - - g_HasVoteStarted = true; - if (g_NativeVotes) - { - g_VoteNative = new NativeVote(Handler_NV_MapVoteMenu, NativeVotesType_NextLevelMult, NATIVEVOTES_ACTIONS_DEFAULT | MenuAction_DisplayItem); - g_VoteNative.VoteResultCallback = Handler_NV_MapVoteFinished; - } - else - { - g_VoteMenu = new Menu(Handler_MapVoteMenu, MENU_ACTIONS_ALL); - g_VoteMenu.SetTitle("Vote Nextmap"); - g_VoteMenu.VoteResultCallback = Handler_MapVoteFinished; - } - - /* Call OnMapVoteStarted() Forward */ - Call_StartForward(g_MapVoteStartedForward); - Call_Finish(); - - /** - * TODO: Make a proper decision on when to clear the nominations list. - * Currently it clears when used, and stays if an external list is provided. - * Is this the right thing to do? External lists will probably come from places - * like sm_mapvote from the adminmenu in the future. - */ - - char map[PLATFORM_MAX_PATH]; - - /* No input given - User our internal nominations and maplist */ - if (inputlist == null) - { - int nominateCount = g_NominateList.Length; - int voteSize = g_Cvar_IncludeMaps.IntValue; - - // New in 1.5.1 to fix missing extend vote - if (g_NativeVotes) - { - if (voteSize > NativeVotes_GetMaxItems()) - { - voteSize = NativeVotes_GetMaxItems(); - } - - if (g_Cvar_Extend.IntValue && g_Extends < g_Cvar_Extend.IntValue) - { - voteSize--; - } - } - - /* Smaller of the two - It should be impossible for nominations to exceed the size though (cvar changed mid-map?) */ - int nominationsToAdd = nominateCount >= voteSize ? voteSize : nominateCount; - - for (int i=0; i<nominationsToAdd; i++) - { - char displayName[PLATFORM_MAX_PATH]; - g_NominateList.GetString(i, map, sizeof(map)); - GetMapDisplayName(map, displayName, sizeof(displayName)); - - if (g_NativeVotes) - { - g_VoteNative.AddItem(map, displayName); - } - else - { - g_VoteMenu.AddItem(map, displayName); - } - - RemoveStringFromArray(g_NextMapList, map); - - /* Notify Nominations that this map is now free */ - Call_StartForward(g_NominationsResetForward); - Call_PushString(map); - Call_PushCell(g_NominateOwners.Get(i)); - Call_Finish(); - } - - /* Clear out the rest of the nominations array */ - for (int i=nominationsToAdd; i<nominateCount; i++) - { - g_NominateList.GetString(i, map, sizeof(map)); - /* These maps shouldn't be excluded from the vote as they weren't really nominated at all */ - - /* Notify Nominations that this map is now free */ - Call_StartForward(g_NominationsResetForward); - Call_PushString(map); - Call_PushCell(g_NominateOwners.Get(i)); - Call_Finish(); - } - - /* There should currently be 'nominationsToAdd' unique maps in the vote */ - - int i = nominationsToAdd; - int count = 0; - int availableMaps = g_NextMapList.Length; - - while (i < voteSize) - { - if (count >= availableMaps) - { - //Run out of maps, this will have to do. - break; - } - - g_NextMapList.GetString(count, map, sizeof(map)); - count++; - - /* Insert the map and increment our count */ - char displayName[PLATFORM_MAX_PATH]; - GetMapDisplayName(map, displayName, sizeof(displayName)); - if (g_NativeVotes) - { - g_VoteNative.AddItem(map, displayName); - } - else - { - g_VoteMenu.AddItem(map, displayName); - } - i++; - } - - /* Wipe out our nominations list - Nominations have already been informed of this */ - g_NominateOwners.Clear(); - g_NominateList.Clear(); - } - else //We were given a list of maps to start the vote with - { - int size = inputlist.Length; - - for (int i=0; i<size; i++) - { - inputlist.GetString(i, map, sizeof(map)); - - if (IsMapValid(map)) - { - char displayName[PLATFORM_MAX_PATH]; - GetMapDisplayName(map, displayName, sizeof(displayName)); - if (g_NativeVotes) - { - g_VoteNative.AddItem(map, displayName); - } - else - { - g_VoteMenu.AddItem(map, displayName); - } - } - } - } - - /* Do we add any special items? */ - if ((when == MapChange_Instant || when == MapChange_RoundEnd) && g_Cvar_DontChange.BoolValue) - { - if (g_NativeVotes) - { - g_VoteNative.AddItem(VOTE_DONTCHANGE, "Don't Change"); - } - else - { - g_VoteMenu.AddItem(VOTE_DONTCHANGE, "Don't Change"); - } - } - else if (g_Cvar_Extend.BoolValue && g_Extends < g_Cvar_Extend.IntValue) - { - if (g_NativeVotes) - { - g_VoteNative.AddItem(VOTE_EXTEND, "Extend Map"); - } - else - { - g_VoteMenu.AddItem(VOTE_EXTEND, "Extend Map"); - } - } - - /* There are no maps we could vote for. Don't show anything. */ - if (g_NativeVotes && g_VoteNative.ItemCount == 0) - { - g_HasVoteStarted = false; - g_VoteNative.Close(); - g_VoteNative = null; - return; - } - else if (!g_NativeVotes && g_VoteMenu.ItemCount == 0) - { - g_HasVoteStarted = false; - delete g_VoteMenu; - g_VoteMenu = null; - return; - } - - int voteDuration = g_Cvar_VoteDuration.IntValue; - - if (g_NativeVotes) - { - g_VoteNative.DisplayVoteToAll(voteDuration); - } - else - { - g_VoteMenu.ExitButton = false; - g_VoteMenu.DisplayVoteToAll(voteDuration); - } - - LogAction(-1, -1, "Voting for next map has started."); - PrintToChatAll("[SM] %t", "Nextmap Voting Started"); -} - -public void Handler_NV_VoteFinishedGeneric(NativeVote menu, - int num_votes, - int num_clients, - const int[] client_indexes, - const int[] client_votes, - int num_items, - const int[] item_indexes, - const int[] item_votes) -{ - int[][] client_info = new int[num_clients][2]; - int[][] item_info = new int[num_items][2]; - - NativeVotes_FixResults(num_clients, client_indexes, client_votes, num_items, item_indexes, item_votes, client_info, item_info); - - char map[PLATFORM_MAX_PATH]; - char displayName[PLATFORM_MAX_PATH]; - menu.GetItem(item_indexes[0], map, sizeof(map), displayName, sizeof(displayName)); - - Handler_VoteFinishedGenericShared(map, displayName, num_votes, num_clients, client_info, num_items, item_info, true); -} - -public void Handler_VoteFinishedGeneric(Menu menu, - int num_votes, - int num_clients, - const int[][] client_info, - int num_items, - const int[][] item_info) -{ - char map[PLATFORM_MAX_PATH]; - char displayName[PLATFORM_MAX_PATH]; - menu.GetItem(item_info[0][VOTEINFO_ITEM_INDEX], map, sizeof(map), _, displayName, sizeof(displayName)); - - Handler_VoteFinishedGenericShared(map, displayName, num_votes, num_clients, client_info, num_items, item_info, false); -} - -public void Handler_VoteFinishedGenericShared(const char[] map, - const char[] displayName, - int num_votes, - int num_clients, - const int[][] client_info, - int num_items, - const int[][] item_info, - bool isNativeVotes) -{ - if (strcmp(map, VOTE_EXTEND, false) == 0) - { - g_Extends++; - - int time; - if (GetMapTimeLimit(time)) - { - if (time > 0) - { - ExtendMapTimeLimit(g_Cvar_ExtendTimeStep.IntValue * 60); - } - } - - if (g_Cvar_Winlimit) - { - int winlimit = g_Cvar_Winlimit.IntValue; - if (winlimit) - { - g_Cvar_Winlimit.IntValue = winlimit + g_Cvar_ExtendRoundStep.IntValue; - } - } - - if (g_Cvar_Maxrounds) - { - int maxrounds = g_Cvar_Maxrounds.IntValue; - if (maxrounds) - { - g_Cvar_Maxrounds.IntValue = maxrounds + g_Cvar_ExtendRoundStep.IntValue; - } - } - - if (g_Cvar_Fraglimit) - { - int fraglimit = g_Cvar_Fraglimit.IntValue; - if (fraglimit) - { - g_Cvar_Fraglimit.IntValue = fraglimit + g_Cvar_ExtendFragStep.IntValue; - } - } - - PrintToChatAll("[SM] %t", "Current Map Extended", RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes); - LogAction(-1, -1, "Voting for next map has finished. The current map has been extended."); - - if (isNativeVotes) - { - g_VoteNative.DisplayPassEx(NativeVotesPass_Extend); - } - - // We extended, so we'll have to vote again. - g_HasVoteStarted = false; - CreateNextVote(); - SetupTimeleftTimer(); - - } - else if (strcmp(map, VOTE_DONTCHANGE, false) == 0) - { - PrintToChatAll("[SM] %t", "Current Map Stays", RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes); - LogAction(-1, -1, "Voting for next map has finished. 'No Change' was the winner"); - - if (isNativeVotes) - { - g_VoteNative.DisplayPassEx(NativeVotesPass_Extend); - } - - g_HasVoteStarted = false; - CreateNextVote(); - SetupTimeleftTimer(); - } - else - { - if (g_ChangeTime == MapChange_MapEnd) - { - SetNextMap(map); - } - else if (g_ChangeTime == MapChange_Instant) - { - DataPack data; - CreateDataTimer(2.0, Timer_ChangeMap, data); - data.WriteString(map); - g_ChangeMapInProgress = false; - } - else // MapChange_RoundEnd - { - SetNextMap(map); - g_ChangeMapAtRoundEnd = true; - } - - g_HasVoteStarted = false; - g_MapVoteCompleted = true; - - if (isNativeVotes) - { - g_VoteNative.DisplayPass(displayName); - } - - PrintToChatAll("[SM] %t", "Nextmap Voting Finished", displayName, RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes); - LogAction(-1, -1, "Voting for next map has finished. Nextmap: %s.", map); - } -} - -public void Handler_NV_MapVoteFinished(NativeVote menu, - int num_votes, - int num_clients, - const int[] client_indexes, - const int[] client_votes, - int num_items, - const int[] item_indexes, - const int[] item_votes) -{ - if (g_Cvar_RunOff.BoolValue && num_items > 1) - { - float winningvotes = float(item_votes[0]); - float required = num_votes * (g_Cvar_RunOffPercent.FloatValue / 100.0); - - if (winningvotes < required) - { - //Added in 1.5.1 - menu.DisplayFail(NativeVotesFail_NotEnoughVotes); - - /* Insufficient Winning margin - Lets do a runoff */ - - char map1[PLATFORM_MAX_PATH]; - char map2[PLATFORM_MAX_PATH]; - char info1[PLATFORM_MAX_PATH]; - char info2[PLATFORM_MAX_PATH]; - - DataPack data; - - menu.GetItem(item_indexes[0], map1, sizeof(map1), info1, sizeof(info1)); - menu.GetItem(item_indexes[1], map2, sizeof(map2), info2, sizeof(info2)); - - CreateDataTimer(3.0, Timer_NV_Runoff, data, TIMER_FLAG_NO_MAPCHANGE); - - data.WriteString(map1); - data.WriteString(info1); - data.WriteString(map2); - data.WriteString(info2); - - data.Reset(); - - /* Notify */ - float map1percent = float(item_votes[0])/ float(num_votes) * 100; - float map2percent = float(item_votes[1])/ float(num_votes) * 100; - - - PrintToChatAll("[SM] %t", "Starting Runoff", g_Cvar_RunOffPercent.FloatValue, info1, map1percent, info2, map2percent); - LogMessage("Voting for next map was indecisive, beginning runoff vote"); - - return; - } - } - - Handler_NV_VoteFinishedGeneric(menu, num_votes, num_clients, client_indexes, client_votes, num_items, item_indexes, item_votes); -} - -// New in 1.5.1, used to fix runoff not working properly -public Action Timer_NV_Runoff(Handle timer, DataPack data) -{ - char map[PLATFORM_MAX_PATH]; - char info[PLATFORM_MAX_PATH]; - - g_VoteNative = new NativeVote(Handler_NV_MapVoteMenu, NativeVotesType_NextLevelMult, NATIVEVOTES_ACTIONS_DEFAULT | MenuAction_DisplayItem); - g_VoteNative.VoteResultCallback = Handler_NV_VoteFinishedGeneric; - - data.ReadString(map, sizeof(map)); - data.ReadString(info, sizeof(info)); - g_VoteNative.AddItem(map, info); - - data.ReadString(map, sizeof(map)); - data.ReadString(info, sizeof(info)); - g_VoteNative.AddItem(map, info); - - int voteDuration = g_Cvar_VoteDuration.IntValue; - g_VoteNative.DisplayVoteToAll(voteDuration); - - return Plugin_Continue; -} - -public void Handler_MapVoteFinished(Menu menu, - int num_votes, - int num_clients, - const int[][] client_info, - int num_items, - const int[][] item_info) -{ - if (g_Cvar_RunOff.BoolValue && num_items > 1) - { - float winningvotes = float(item_info[0][VOTEINFO_ITEM_VOTES]); - float required = num_votes * (g_Cvar_RunOffPercent.FloatValue / 100.0); - - if (winningvotes < required) - { - /* Insufficient Winning margin - Lets do a runoff */ - g_VoteMenu = new Menu(Handler_MapVoteMenu, MENU_ACTIONS_ALL); - g_VoteMenu.SetTitle("Runoff Vote Nextmap"); - g_VoteMenu.VoteResultCallback = Handler_VoteFinishedGeneric; - - char map[PLATFORM_MAX_PATH]; - char info1[PLATFORM_MAX_PATH]; - char info2[PLATFORM_MAX_PATH]; - - menu.GetItem(item_info[0][VOTEINFO_ITEM_INDEX], map, sizeof(map), _, info1, sizeof(info1)); - g_VoteMenu.AddItem(map, info1); - menu.GetItem(item_info[1][VOTEINFO_ITEM_INDEX], map, sizeof(map), _, info2, sizeof(info2)); - g_VoteMenu.AddItem(map, info2); - - int voteDuration = g_Cvar_VoteDuration.IntValue; - g_VoteMenu.ExitButton = false; - g_VoteMenu.DisplayVoteToAll(voteDuration); - - /* Notify */ - float map1percent = float(item_info[0][VOTEINFO_ITEM_VOTES])/ float(num_votes) * 100; - float map2percent = float(item_info[1][VOTEINFO_ITEM_VOTES])/ float(num_votes) * 100; - - - PrintToChatAll("[SM] %t", "Starting Runoff", g_Cvar_RunOffPercent.FloatValue, info1, map1percent, info2, map2percent); - LogMessage("Voting for next map was indecisive, beginning runoff vote"); - - return; - } - } - - Handler_VoteFinishedGeneric(menu, num_votes, num_clients, client_info, num_items, item_info); -} - -public int Handler_MapVoteMenu(Menu menu, MenuAction action, int param1, int param2) -{ - switch (action) - { - case MenuAction_End: - { - g_VoteMenu = null; - delete menu; - } - - case MenuAction_Display: - { - char buffer[255]; - Format(buffer, sizeof(buffer), "%T", "Vote Nextmap", param1); - - Panel panel = view_as<Panel>(param2); - panel.SetTitle(buffer); - } - - case MenuAction_DisplayItem: - { - if (menu.ItemCount - 1 == param2) - { - char map[PLATFORM_MAX_PATH], buffer[255]; - menu.GetItem(param2, map, sizeof(map)); - if (strcmp(map, VOTE_EXTEND, false) == 0) - { - Format(buffer, sizeof(buffer), "%T", "Extend Map", param1); - return RedrawMenuItem(buffer); - } - else if (strcmp(map, VOTE_DONTCHANGE, false) == 0) - { - Format(buffer, sizeof(buffer), "%T", "Dont Change", param1); - return RedrawMenuItem(buffer); - } - } - } - - case MenuAction_VoteCancel: - { - // If we receive 0 votes, pick at random. - if (param1 == VoteCancel_NoVotes && g_Cvar_NoVoteMode.BoolValue) - { - int count = menu.ItemCount; - char map[PLATFORM_MAX_PATH]; - menu.GetItem(0, map, sizeof(map)); - - // Make sure the first map in the menu isn't one of the special items. - // This would mean there are no real maps in the menu, because the special items are added after all maps. Don't do anything if that's the case. - if (strcmp(map, VOTE_EXTEND, false) != 0 && strcmp(map, VOTE_DONTCHANGE, false) != 0) - { - // Get a random map from the list. - int item = GetRandomInt(0, count - 1); - menu.GetItem(item, map, sizeof(map)); - - // Make sure it's not one of the special items. - while (strcmp(map, VOTE_EXTEND, false) == 0 || strcmp(map, VOTE_DONTCHANGE, false) == 0) - { - item = GetRandomInt(0, count - 1); - menu.GetItem(item, map, sizeof(map)); - } - - SetNextMap(map); - g_MapVoteCompleted = true; - } - } - else - { - // We were actually cancelled. I guess we do nothing. - } - - g_HasVoteStarted = false; - } - } - - return 0; -} - -public int Handler_NV_MapVoteMenu(NativeVote menu, MenuAction action, int param1, int param2) -{ - switch (action) - { - case MenuAction_End: - { - g_VoteMenu = null; - menu.Close(); - } - - case MenuAction_DisplayItem: - { - if (menu.ItemCount - 1 == param2) - { - char map[PLATFORM_MAX_PATH], buffer[255]; - menu.GetItem(param2, map, sizeof(map)); - if (strcmp(map, VOTE_EXTEND, false) == 0) - { - Format(buffer, sizeof(buffer), "%T", "Extend Map", param1); - return view_as<int>(NativeVotes_RedrawVoteItem(buffer)); - } - else if (strcmp(map, VOTE_DONTCHANGE, false) == 0) - { - Format(buffer, sizeof(buffer), "%T", "Dont Change", param1); - return view_as<int>(NativeVotes_RedrawVoteItem(buffer)); - } - } - } - - case MenuAction_VoteCancel: - { - // If we receive 0 votes, pick at random. - if (param1 == VoteCancel_NoVotes && g_Cvar_NoVoteMode.BoolValue) - { - int count = menu.ItemCount; - char map[PLATFORM_MAX_PATH]; - char displayName[PLATFORM_MAX_PATH]; - menu.GetItem(0, map, sizeof(map)); - - // Make sure the first map in the menu isn't one of the special items. - // This would mean there are no real maps in the menu, because the special items are added after all maps. Don't do anything if that's the case. - if (strcmp(map, VOTE_EXTEND, false) != 0 && strcmp(map, VOTE_DONTCHANGE, false) != 0) - { - // Get a random map from the list. - int item = GetRandomInt(0, count - 1); - menu.GetItem(item, map, sizeof(map), displayName, sizeof(displayName)); - - // Make sure it's not one of the special items. - while (strcmp(map, VOTE_EXTEND, false) == 0 || strcmp(map, VOTE_DONTCHANGE, false) == 0) - { - item = GetRandomInt(0, count - 1); - menu.GetItem(item, map, sizeof(map), displayName, sizeof(displayName)); - } - - SetNextMap(map); - g_MapVoteCompleted = true; - menu.DisplayPass(displayName); - } - } - else if (param1 == VoteCancel_NoVotes) - { - // We didn't have enough votes. Display the note enough votes fail message. - menu.DisplayFail(NativeVotesFail_NotEnoughVotes); - } - else - { - // We were actually cancelled. Display the generic fail message - menu.DisplayFail(NativeVotesFail_Generic); - } - - g_HasVoteStarted = false; - } - } - - return 0; -} - -public Action Timer_ChangeMap(Handle hTimer, DataPack dp) -{ - g_ChangeMapInProgress = false; - - char map[PLATFORM_MAX_PATH]; - - if (dp == null) - { - if (!GetNextMap(map, sizeof(map))) - { - //No passed map and no set nextmap. fail! - return Plugin_Stop; - } - } - else - { - dp.Reset(); - dp.ReadString(map, sizeof(map)); - } - - ForceChangeLevel(map, "Map Vote"); - - return Plugin_Stop; -} - -bool RemoveStringFromArray(ArrayList array, char[] str) -{ - int index = array.FindString(str); - if (index != -1) - { - array.Erase(index); - return true; - } - - return false; -} - -void CreateNextVote() -{ - g_NextMapList.Clear(); - - char map[PLATFORM_MAX_PATH]; - // tempMaps is a resolved map list - ArrayList tempMaps = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); - - for (int i = 0; i < g_MapList.Length; i++) - { - g_MapList.GetString(i, map, sizeof(map)); - if (FindMap(map, map, sizeof(map)) != FindMap_NotFound) - { - tempMaps.PushString(map); - } - } - - //GetCurrentMap always returns a resolved map - GetCurrentMap(map, sizeof(map)); - RemoveStringFromArray(tempMaps, map); - - if (g_Cvar_ExcludeMaps.IntValue && tempMaps.Length > g_Cvar_ExcludeMaps.IntValue) - { - for (int i = 0; i < g_OldMapList.Length; i++) - { - g_OldMapList.GetString(i, map, sizeof(map)); - RemoveStringFromArray(tempMaps, map); - } - } - - int limit = (g_Cvar_IncludeMaps.IntValue < tempMaps.Length ? g_Cvar_IncludeMaps.IntValue : tempMaps.Length); - for (int i = 0; i < limit; i++) - { - int b = GetRandomInt(0, tempMaps.Length - 1); - tempMaps.GetString(b, map, sizeof(map)); - g_NextMapList.PushString(map); - tempMaps.Erase(b); - } - - delete tempMaps; -} - -bool CanVoteStart() -{ - if (g_WaitingForVote || g_HasVoteStarted) - { - return false; - } - - return true; -} - -NominateResult InternalNominateMap(char[] map, bool force, int owner) -{ - if (!IsMapValid(map)) - { - return Nominate_InvalidMap; - } - - /* Map already in the vote */ - if (g_NominateList.FindString(map) != -1) - { - return Nominate_AlreadyInVote; - } - - int index; - - /* Look to replace an existing nomination by this client - Nominations made with owner = 0 aren't replaced */ - if (owner && ((index = g_NominateOwners.FindValue(owner)) != -1)) - { - char oldmap[PLATFORM_MAX_PATH]; - g_NominateList.GetString(index, oldmap, sizeof(oldmap)); - Call_StartForward(g_NominationsResetForward); - Call_PushString(oldmap); - Call_PushCell(owner); - Call_Finish(); - - g_NominateList.SetString(index, map); - return Nominate_Replaced; - } - - /* Too many nominated maps. */ - int maxIncludes = 0; - if (g_NativeVotes) - { - maxIncludes = NativeVotes_GetMaxItems(); - - if (g_Cvar_IncludeMaps.IntValue < maxIncludes) - { - maxIncludes = g_Cvar_IncludeMaps.IntValue; - } - - if (g_Cvar_Extend.BoolValue && g_Extends < g_Cvar_Extend.IntValue) - { - maxIncludes--; - } - } - else - { - maxIncludes = g_Cvar_IncludeMaps.IntValue; - } - - if (g_NominateList.Length >= maxIncludes && !force) - { - return Nominate_VoteFull; - } - - g_NominateList.PushString(map); - g_NominateOwners.Push(owner); - - while (g_NominateList.Length > g_Cvar_IncludeMaps.IntValue) - { - char oldmap[PLATFORM_MAX_PATH]; - g_NominateList.GetString(0, oldmap, sizeof(oldmap)); - Call_StartForward(g_NominationsResetForward); - Call_PushString(oldmap); - Call_PushCell(g_NominateOwners.Get(0)); - Call_Finish(); - - g_NominateList.Erase(0); - g_NominateOwners.Erase(0); - } - - return Nominate_Added; -} - -/* Add natives to allow nominate and initiate vote to be call */ - -/* native NominateResult NominateMap(const char[] map, bool force, int owner); */ -public int Native_NominateMap(Handle plugin, int numParams) -{ - int len; - GetNativeStringLength(1, len); - - if (len <= 0) - { - return false; - } - - char[] map = new char[len+1]; - GetNativeString(1, map, len+1); - - return view_as<int>(InternalNominateMap(map, GetNativeCell(2), GetNativeCell(3))); -} - -bool InternalRemoveNominationByMap(char[] map) -{ - for (int i = 0; i < g_NominateList.Length; i++) - { - char oldmap[PLATFORM_MAX_PATH]; - g_NominateList.GetString(i, oldmap, sizeof(oldmap)); - - if(strcmp(map, oldmap, false) == 0) - { - Call_StartForward(g_NominationsResetForward); - Call_PushString(oldmap); - Call_PushCell(g_NominateOwners.Get(i)); - Call_Finish(); - - g_NominateList.Erase(i); - g_NominateOwners.Erase(i); - - return true; - } - } - - return false; -} - -/* native bool RemoveNominationByMap(const char[] map); */ -public int Native_RemoveNominationByMap(Handle plugin, int numParams) -{ - int len; - GetNativeStringLength(1, len); - - if (len <= 0) - { - return false; - } - - char[] map = new char[len+1]; - GetNativeString(1, map, len+1); - - return InternalRemoveNominationByMap(map); -} - -bool InternalRemoveNominationByOwner(int owner) -{ - int index; - - if (owner && ((index = g_NominateOwners.FindValue(owner)) != -1)) - { - char oldmap[PLATFORM_MAX_PATH]; - g_NominateList.GetString(index, oldmap, sizeof(oldmap)); - - Call_StartForward(g_NominationsResetForward); - Call_PushString(oldmap); - Call_PushCell(owner); - Call_Finish(); - - g_NominateList.Erase(index); - g_NominateOwners.Erase(index); - - return true; - } - - return false; -} - -/* native bool RemoveNominationByOwner(int owner); */ -public int Native_RemoveNominationByOwner(Handle plugin, int numParams) -{ - return InternalRemoveNominationByOwner(GetNativeCell(1)); -} - -/* native void InitiateMapChooserVote(MapChange when, ArrayList inputarray=null); */ -public int Native_InitiateVote(Handle plugin, int numParams) -{ - MapChange when = view_as<MapChange>(GetNativeCell(1)); - ArrayList inputarray = view_as<ArrayList>(GetNativeCell(2)); - - LogAction(-1, -1, "Starting map vote because outside request"); - InitiateVote(when, inputarray); -} - -/* native bool CanMapChooserStartVote(); */ -public int Native_CanVoteStart(Handle plugin, int numParams) -{ - return CanVoteStart(); -} - -/* native bool HasEndOfMapVoteFinished(); */ -public int Native_CheckVoteDone(Handle plugin, int numParams) -{ - return g_MapVoteCompleted; -} - -/* native bool EndOfMapVoteEnabled(); */ -public int Native_EndOfMapVoteEnabled(Handle plugin, int numParams) -{ - return g_Cvar_EndOfMapVote.BoolValue; -} - -/* native void GetExcludeMapList(ArrayList array); */ -public int Native_GetExcludeMapList(Handle plugin, int numParams) -{ - ArrayList array = view_as<ArrayList>(GetNativeCell(1)); - - if (array == null) - { - return; - } - int size = g_OldMapList.Length; - char map[PLATFORM_MAX_PATH]; - - for (int i=0; i<size; i++) - { - g_OldMapList.GetString(i, map, sizeof(map)); - array.PushString(map); - } - - return; -} - -/* native void GetNominatedMapList(ArrayList maparray, ArrayList ownerarray = null); */ -public int Native_GetNominatedMapList(Handle plugin, int numParams) -{ - ArrayList maparray = view_as<ArrayList>(GetNativeCell(1)); - ArrayList ownerarray = view_as<ArrayList>(GetNativeCell(2)); - - if (maparray == null) - return; - - char map[PLATFORM_MAX_PATH]; - - for (int i = 0; i < g_NominateList.Length; i++) - { - g_NominateList.GetString(i, map, sizeof(map)); - maparray.PushString(map); - - // If the optional parameter for an owner list was passed, then we need to fill that out as well - if(ownerarray != null) - { - int index = g_NominateOwners.Get(i); - ownerarray.Push(index); - } - } - - return; -} +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod NativeVotes Mapchooser Plugin + * Creates a map vote at appropriate times, setting sm_nextmap to the winning + * vote + * Updated with NativeVotes support + * + * NativeVotes (C)2011-2016 Ross Bemrose (Powerlord). All rights reserved. + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program 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 the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or <http://www.sourcemod.net/license.php>. + * + * Version: $Id$ + */ + +#include <sourcemod> +#include <mapchooser> +#include <sdktools> +#include <nextmap> +#include <regex> + +#undef REQUIRE_PLUGIN +#include <nativevotes> +#define REQUIRE_PLUGIN + +#undef REQUIRE_EXTENSIONS +#include <ripext> +#define REQUIRE_EXTENSIONS + +#define PLUGIN_PREFIX "[\x04MapChooser\x01]" + +public Plugin myinfo = +{ + name = "NativeVotes | MapChooser", + author = "AlliedModders LLC and Powerlord", + description = "Automated Map Voting", + version = "26w31a", + url = "https://github.com/Heapons/sourcemod-nativevotes-updated/" +}; + +/* ConVars */ +enum +{ + /* Valve */ + mp_winlimit, + mp_maxrounds, + mp_fraglimit, + mp_bonusroundtime, + mapcyclefile, + + /* Plugin */ + mapvote_endvote, + mapvote_start, + mapvote_startround, + mapvote_startfrags, + extendmap_timestep, + extendmap_roundstep, + extendmap_fragstep, + mapvote_exclude, + mapvote_include, + mapvote_persistentmaps, + mapvote_novote, + mapvote_extend, + mapvote_dontchange, + mapvote_voteduration, + mapvote_runoff, + mapvote_runoffpercent, + mapvote_shuffle_nominations, + mapcycle_auto, + mapcycle_exclude, + workshop_map_collection, + workshop_cleanup, + + /* Rafmod */ + sig_etc_workshop_map_fix, + + MAX_CONVARS +} + +ConVar g_ConVars[MAX_CONVARS]; + +Handle g_VoteTimer = null; +Handle g_RetryTimer = null; + +// g_MapList stores unresolved names so we can resolve them after every map change in the workshop updates. +// g_OldMapList and g_NextMapList are resolved. g_NominateList depends on the nominations implementation. +/* Data Handles */ +ArrayList g_MapList; +ArrayList g_NominateList; +ArrayList g_NominateOwners; +ArrayList g_OldMapList; +ArrayList g_NextMapList; +Menu g_VoteMenu; +NativeVote g_VoteNative; + +int g_Extends; +int g_TotalRounds; +bool g_HasVoteStarted; +bool g_WaitingForVote; +bool g_MapVoteCompleted; +bool g_ChangeMapAtRoundEnd; +bool g_ChangeMapInProgress; +int g_mapFileSerial = -1; +int g_AppID; + +MapChange g_ChangeTime; + +GlobalForward g_NominationsResetForward; +GlobalForward g_MapVoteStartedForward; + +/** + * Upper bound of how many team there could be. + * https://github.com/ValveSoftware/source-sdk-2013/blob/master/src/game/shared/shareddefs.h#L288 + */ +#define MAX_TEAMS 32 +int g_winCount[MAX_TEAMS]; + +#define VOTE_EXTEND "##extend##" +#define VOTE_DONTCHANGE "##dontchange##" + +// Libraries +bool g_NativeVotes; +bool g_RestInPawn; + +public void OnPluginStart() +{ + LoadTranslations("mapchooser.phrases"); + LoadTranslations("common.phrases"); + + KeyValues kv = new KeyValues("GameInfo"); + if (kv.ImportFromFile("gameinfo.txt") && kv.JumpToKey("FileSystem")) + { + g_AppID = kv.GetNum("SteamAppId"); + } + + delete kv; + + int arraySize = ByteCountToCells(PLATFORM_MAX_PATH); + g_MapList = new ArrayList(arraySize); + g_NominateList = new ArrayList(arraySize); + g_NominateOwners = new ArrayList(); + g_OldMapList = new ArrayList(arraySize); + g_NextMapList = new ArrayList(arraySize); + + g_ConVars[mapvote_endvote] = CreateConVar("sm_mapvote_endvote", "1", "Specifies if MapChooser should run an end of map vote.", _, true, 0.0, true, 1.0); + g_ConVars[mapvote_start] = CreateConVar("sm_mapvote_start", "3.0", "Specifies when to start the vote based on time remaining.", _, true, 1.0); + g_ConVars[mapvote_startround] = CreateConVar("sm_mapvote_startround", "2.0", "Specifies when to start the vote based on rounds remaining. Use '0' on TF2 to start vote during bonus round time", _, true, 0.0); + g_ConVars[mapvote_startfrags] = CreateConVar("sm_mapvote_startfrags", "5.0", "Specifies when to start the vote base on frags remaining.", _, true, 1.0); + g_ConVars[extendmap_timestep] = CreateConVar("sm_extendmap_timestep", "15", "Specifies how much many more minutes each extension makes.", _, true, 5.0); + g_ConVars[extendmap_roundstep] = CreateConVar("sm_extendmap_roundstep", "5", "Specifies how many more rounds each extension makes.", _, true, 1.0); + g_ConVars[extendmap_fragstep] = CreateConVar("sm_extendmap_fragstep", "10", "Specifies how many more frags are allowed when map is extended.", _, true, 5.0); + g_ConVars[mapvote_exclude] = CreateConVar("sm_mapvote_exclude", "5", "Specifies how many past maps to exclude from the vote.", _, true, 0.0); + g_ConVars[mapvote_include] = CreateConVar("sm_mapvote_include", "5", "Specifies how many maps to include in the vote.", _, true, 2.0, true, 6.0); + g_ConVars[mapvote_persistentmaps] = CreateConVar("sm_mapvote_persistentmaps", "0", "Specifies if previous maps should be stored persistently.", _, true, 0.0, true, 1.0); + g_ConVars[mapvote_novote] = CreateConVar("sm_mapvote_novote", "1", "Specifies whether or not MapChooser should pick a map if no votes are received.", _, true, 0.0, true, 1.0); + g_ConVars[mapvote_extend] = CreateConVar("sm_mapvote_extend", "0", "Number of extensions allowed each map.", _, true, 0.0); + g_ConVars[mapvote_dontchange] = CreateConVar("sm_mapvote_dontchange", "1", "Specifies if a 'Don't Change' option should be added to early votes.", _, true, 0.0, true, 1.0); + g_ConVars[mapvote_voteduration] = CreateConVar("sm_mapvote_voteduration", "20", "Specifies how long the mapvote should be available for.", _, true, 5.0); + g_ConVars[mapvote_runoff] = CreateConVar("sm_mapvote_runoff", "0", "Hold run of votes if winning choice is less than a certain margin.", _, true, 0.0, true, 1.0); + g_ConVars[mapvote_runoffpercent] = CreateConVar("sm_mapvote_runoffpercent", "50", "If winning choice has less than this percent of votes, hold a runoff.", _, true, 0.0, true, 100.0); + g_ConVars[mapvote_shuffle_nominations] = CreateConVar("sm_mapvote_shuffle_nominations", "0", "If set, allows infinite nominations and picks a random subset to appear in the vote.", _, true, 0.0, true, 1.0); + g_ConVars[mapcycle_auto] = CreateConVar("sm_mapcycle_auto", "0", "Specifies whether or not to automatically populate the maps list.", _, true, 0.0, true, 1.0); + g_ConVars[mapcycle_exclude] = CreateConVar("sm_mapcycle_exclude", ".*test.*|background01|^tr.*$", "Specifies which maps shouldn't be automatically added with a regex pattern."); + + EngineVersion engine = GetEngineVersion(); + if (engine != Engine_SDK2013 && engine == Engine_TF2) + { + g_ConVars[workshop_map_collection] = CreateConVar("sm_workshop_map_collection", "", "Specifies the workshop collection to fetch the maps from."); + g_ConVars[workshop_cleanup] = CreateConVar("sm_workshop_map_cleanup", "0", "Specifies whether or not to automatically workshop maps on map change.", _, true, 0.0, true, 1.0); + } + + RegAdminCmd("sm_mapvote", Command_MapVote, ADMFLAG_CHANGEMAP, "Forces MapChooser to attempt to run a map vote now."); + RegAdminCmd("sm_setnextmap", Command_SetNextMap, ADMFLAG_CHANGEMAP, "sm_setnextmap <map>"); + + g_ConVars[mp_winlimit] = FindConVar("mp_winlimit"); + g_ConVars[mp_maxrounds] = FindConVar("mp_maxrounds"); + g_ConVars[mp_fraglimit] = FindConVar("mp_fraglimit"); + g_ConVars[mp_bonusroundtime] = FindConVar("mp_bonusroundtime"); + g_ConVars[mapcyclefile] = FindConVar("mapcyclefile"); + g_ConVars[sig_etc_workshop_map_fix] = FindConVar("sig_etc_workshop_map_fix"); + + if (g_ConVars[mp_winlimit] || g_ConVars[mp_maxrounds]) + { + char folder[64]; + GetGameFolderName(folder, sizeof(folder)); + + if (HookEventEx("teamplay_win_panel", Event_TeamplayWinPanel)) + { + HookEvent("teamplay_restart_round", Event_TeamplayRestartRound); + HookEvent("arena_win_panel", Event_TeamplayWinPanel); + } + else if (strcmp(folder, "nucleardawn") == 0) + { + HookEvent("round_win", Event_RoundEnd); + } + else if (strcmp(folder, "empires") == 0) + { + HookEvent("game_end", Event_RoundEnd); + } + else + { + HookEvent("round_end", Event_RoundEnd); + } + } + + if (g_ConVars[mp_fraglimit]) + { + HookEvent("player_death", Event_PlayerDeath); + } + + AutoExecConfig(true, "mapchooser"); + + // Change the mp_bonusroundtime max so that we have time to display the vote + // If you display a vote during bonus time good defaults are 17 vote duration and 19 mp_bonustime + if (g_ConVars[mp_bonusroundtime]) + { + g_ConVars[mp_bonusroundtime].SetBounds(ConVarBound_Upper, true, 30.0); + } + + g_NominationsResetForward = CreateGlobalForward("OnNominationRemoved", ET_Ignore, Param_String, Param_Cell); + g_MapVoteStartedForward = CreateGlobalForward("OnMapVoteStarted", ET_Ignore); +} + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) +{ + RegPluginLibrary("mapchooser"); + + CreateNative("NominateMap", Native_NominateMap); + CreateNative("RemoveNominationByMap", Native_RemoveNominationByMap); + CreateNative("RemoveNominationByOwner", Native_RemoveNominationByOwner); + CreateNative("InitiateMapChooserVote", Native_InitiateVote); + CreateNative("CanMapChooserStartVote", Native_CanVoteStart); + CreateNative("HasEndOfMapVoteFinished", Native_CheckVoteDone); + CreateNative("GetExcludeMapList", Native_GetExcludeMapList); + CreateNative("GetNominatedMapList", Native_GetNominatedMapList); + CreateNative("EndOfMapVoteEnabled", Native_EndOfMapVoteEnabled); + + MarkNativeAsOptional("HTTPRequest.HTTPRequest"); + MarkNativeAsOptional("HTTPRequest.AppendFormParam"); + MarkNativeAsOptional("HTTPRequest.PostForm"); + MarkNativeAsOptional("HTTPResponse.Status.get"); + MarkNativeAsOptional("HTTPResponse.Data.get"); + MarkNativeAsOptional("JSONObject.Get"); + MarkNativeAsOptional("JSONObject.GetInt"); + MarkNativeAsOptional("JSONObject.GetString"); + MarkNativeAsOptional("JSONArray.Get"); + MarkNativeAsOptional("JSONArray.Length.get"); + + return APLRes_Success; +} + +public void OnAllPluginsLoaded() +{ + if (FindPluginByFile("mapchooser.smx") != null) + { + LogMessage("Unloading mapchooser to prevent conflicts..."); + ServerCommand("sm plugins unload mapchooser"); + + char oldPath[PLATFORM_MAX_PATH]; + char newPath[PLATFORM_MAX_PATH]; + + BuildPath(Path_SM, oldPath, sizeof(oldPath), "plugins/mapchooser.smx"); + BuildPath(Path_SM, newPath, sizeof(newPath), "plugins/disabled/mapchooser.smx"); + if (RenameFile(newPath, oldPath)) + { + LogMessage("Moving mapchooser to disabled."); + } + } + + g_NativeVotes = LibraryExists("nativevotes") && NativeVotes_IsVoteTypeSupported(NativeVotesType_NextLevelMult); + g_RestInPawn = GetExtensionFileStatus("rip.ext") == 1; +} + +public void OnLibraryAdded(const char[] name) +{ + if (StrEqual(name, "nativevotes", false) && NativeVotes_IsVoteTypeSupported(NativeVotesType_NextLevelMult)) + { + g_NativeVotes = true; + } +} + +public void OnLibraryRemoved(const char[] name) +{ + if (StrEqual(name, "nativevotes", false)) + { + g_NativeVotes = false; + } + else if (StrEqual(name, "rip.ext", false)) + { + g_RestInPawn = false; + } +} + +public void OnConfigsExecuted() +{ + if (g_ConVars[workshop_cleanup] != null && g_ConVars[workshop_cleanup].BoolValue) + { + CleanupWorkshopMaps(); + } + + if (g_ConVars[mapcycle_auto].BoolValue) + { + PopulateMapList(); + } + + if (ReadMapList(g_MapList, g_mapFileSerial, "mapchooser", MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER) == null) + { + if (g_mapFileSerial == -1) + { + LogError("Unable to create a valid map list."); + } + } + + /* First-load previous maps from a text file when persistency is enabled. */ + static bool g_FirstConfigExec = true; + if (g_FirstConfigExec) + { + if (g_ConVars[mapvote_persistentmaps].BoolValue) + { + ReadPreviousMapsFromText(); + } + + g_FirstConfigExec = false; + } + + CreateNextVote(); + SetupTimeleftTimer(); + + g_TotalRounds = 0; + + g_Extends = 0; + + g_MapVoteCompleted = false; + + g_NominateList.Clear(); + g_NominateOwners.Clear(); + + for (int i = 0; i < MAX_TEAMS; i++) + { + g_winCount[i] = 0; + } + + /* Check if mapchooser will attempt to start mapvote during bonus round time - TF2 Only */ + if (g_ConVars[mp_bonusroundtime] && !g_ConVars[mapvote_startround].IntValue) + { + if (g_ConVars[mp_bonusroundtime].FloatValue <= g_ConVars[mapvote_voteduration].FloatValue) + { + LogError("Warning - Bonus Round Time shorter than Vote Time. Votes during bonus round may not have time to complete"); + } + } +} + +public void OnMapEnd() +{ + g_HasVoteStarted = false; + g_WaitingForVote = false; + g_ChangeMapAtRoundEnd = false; + g_ChangeMapInProgress = false; + + g_VoteTimer = null; + g_RetryTimer = null; + + char map[PLATFORM_MAX_PATH]; + GetCurrentMap(map, sizeof(map)); + RemoveStringFromArray(g_OldMapList, map); + g_OldMapList.PushString(map); + + while (g_OldMapList.Length > g_ConVars[mapvote_exclude].IntValue) + { + g_OldMapList.Erase(0); + } + + if (g_ConVars[mapvote_persistentmaps].BoolValue) + { + WritePreviousMapsToText(); + } +} + +public void OnClientDisconnect(int client) +{ + int index = g_NominateOwners.FindValue(client); + + if (index == -1) + { + return; + } + + char oldmap[PLATFORM_MAX_PATH]; + g_NominateList.GetString(index, oldmap, sizeof(oldmap)); + Call_StartForward(g_NominationsResetForward); + Call_PushString(oldmap); + Call_PushCell(g_NominateOwners.Get(index)); + Call_Finish(); + + g_NominateOwners.Erase(index); + g_NominateList.Erase(index); +} + +public Action Command_SetNextMap(int client, int args) +{ + if (args < 1) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " Usage: sm_setnextmap <map>"); + return Plugin_Handled; + } + + char map[PLATFORM_MAX_PATH], displayName[PLATFORM_MAX_PATH]; + GetCmdArg(1, map, sizeof(map)); + + if (FindMap(map, displayName, sizeof(displayName)) == FindMap_NotFound) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Map was not found", map); + return Plugin_Handled; + } + + GetMapDisplayName(displayName, displayName, sizeof(displayName)); + Format(displayName, sizeof(displayName), "\x05%s\x01", displayName); + + CShowActivity2(client, PLUGIN_PREFIX ... " ", "%t", "Changed Next Map", displayName); + LogAction(client, -1, "\"%L\" changed nextmap to \"%s\"", client, map); + + SetNextMap(map); + g_MapVoteCompleted = true; + + return Plugin_Handled; +} + +public void OnMapTimeLeftChanged() +{ + if (g_MapList.Length) + { + SetupTimeleftTimer(); + } +} + +void SetupTimeleftTimer() +{ + int time; + if (GetMapTimeLeft(time) && time > 0) + { + int startTime = g_ConVars[mapvote_start].IntValue * 60; + if (time - startTime < 0 && g_ConVars[mapvote_endvote].BoolValue && !g_MapVoteCompleted && !g_HasVoteStarted) + { + InitiateVote(MapChange_MapEnd, null); + } + else + { + if (g_VoteTimer != null) + { + KillTimer(g_VoteTimer); + g_VoteTimer = null; + } + + //g_VoteTimer = CreateTimer(float(time - startTime), Timer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE); + DataPack data; + g_VoteTimer = CreateDataTimer(float(time - startTime), Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE); + data.WriteCell(MapChange_MapEnd); + data.WriteCell(INVALID_HANDLE); + data.Reset(); + } + } +} + +public Action Timer_StartMapVote(Handle timer, DataPack data) +{ + if (timer == g_RetryTimer) + { + g_WaitingForVote = false; + g_RetryTimer = null; + } + else + { + g_VoteTimer = null; + } + + // Prevent map vote if the server is empty + int playerCount = 0; + for (int i = 1; i <= MaxClients; i++) + { + if (!IsClientInGame(i)) + continue; + + if (IsFakeClient(i)) + continue; + + playerCount++; + } + if (playerCount == 0) + { + return Plugin_Stop; + } + + if (!g_MapList.Length || !g_ConVars[mapvote_endvote].BoolValue || g_MapVoteCompleted || g_HasVoteStarted) + { + return Plugin_Stop; + } + + MapChange mapChange = view_as<MapChange>(data.ReadCell()); + ArrayList hndl = view_as<ArrayList>(data.ReadCell()); + + InitiateVote(mapChange, hndl); + + return Plugin_Stop; +} + +public void Event_TeamplayRestartRound(Event event, const char[] name, bool dontBroadcast) +{ + /* Game got restarted - reset our round count tracking */ + g_TotalRounds = 0; +} + +public void Event_TeamplayWinPanel(Event event, const char[] name, bool dontBroadcast) +{ + if (g_ChangeMapAtRoundEnd) + { + g_ChangeMapAtRoundEnd = false; + CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE); + g_ChangeMapInProgress = true; + } + + if (event.GetInt("round_complete") == 1 || StrContains(name, "_win")) + { + if (!g_MapList.Length || g_HasVoteStarted || g_MapVoteCompleted || !g_ConVars[mapvote_endvote].BoolValue) + { + return; + } + + g_TotalRounds++; + + CheckMaxRounds(g_TotalRounds); + + int winning_team = event.GetInt("winning_team"); + if (winning_team > 1 && winning_team < MAX_TEAMS) + { + int score = GetTeamScore(winning_team); + CheckWinLimit(score); + } + } +} +/* You ask, why don't you just use team_score event? And I answer... Because CSS doesn't. */ +public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) +{ + if (g_ChangeMapAtRoundEnd) + { + g_ChangeMapAtRoundEnd = false; + CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE); + g_ChangeMapInProgress = true; + } + + int winner; + if (strcmp(name, "round_win") == 0) + { + // Nuclear Dawn + winner = event.GetInt("team"); + } + else + { + winner = event.GetInt("winner"); + } + + if (winner == 0 || winner == 1 || !g_ConVars[mapvote_endvote].BoolValue) + { + return; + } + + if (winner >= MAX_TEAMS) + { + SetFailState("Mod exceed maximum team count - Please file a bug report."); + } + + g_TotalRounds++; + + g_winCount[winner]++; + + if (!g_MapList.Length || g_HasVoteStarted || g_MapVoteCompleted) + { + return; + } + + CheckWinLimit(g_winCount[winner]); + CheckMaxRounds(g_TotalRounds); +} + +public void CheckWinLimit(int winner_score) +{ + if (g_ConVars[mp_winlimit]) + { + int winlimit = g_ConVars[mp_winlimit].IntValue; + if (winlimit) + { + if (winner_score >= (winlimit - g_ConVars[mapvote_startround].IntValue)) + { + InitiateVote(MapChange_MapEnd, null); + } + } + } +} + +public void CheckMaxRounds(int roundcount) +{ + if (g_ConVars[mp_maxrounds]) + { + int maxrounds = g_ConVars[mp_maxrounds].IntValue; + if (maxrounds) + { + if (roundcount >= (maxrounds - g_ConVars[mapvote_startround].IntValue)) + { + InitiateVote(MapChange_MapEnd, null); + } + } + } +} + +public void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) +{ + if (!g_MapList.Length || !g_ConVars[mp_fraglimit] || g_HasVoteStarted) + { + return; + } + + if (!g_ConVars[mp_fraglimit].IntValue || !g_ConVars[mapvote_endvote].BoolValue) + { + return; + } + + if (g_MapVoteCompleted) + { + return; + } + + int fragger = GetClientOfUserId(event.GetInt("attacker")); + + if (!fragger) + { + return; + } + + if (GetClientFrags(fragger) >= (g_ConVars[mp_fraglimit].IntValue - g_ConVars[mapvote_startfrags].IntValue)) + { + InitiateVote(MapChange_MapEnd, null); + } +} + +public Action Command_MapVote(int client, int args) +{ + InitiateVote(MapChange_MapEnd, null); + + return Plugin_Handled; +} + +void ShuffleNominations() +{ + int lengthList = g_NominateList.Length; + for (int i = lengthList - 1; i > 0; --i) + { + int j = GetRandomInt(0, i); + g_NominateList.SwapAt(i, j); + g_NominateOwners.SwapAt(i, j); + } +} + +/** + * Starts a new map vote + * + * @param when When the resulting map change should occur. + * @param inputlist Optional list of maps to use for the vote, otherwise an internal list of nominations + random maps will be used. + * @param noSpecials Block special vote options like extend/nochange (upgrade this to bitflags instead?) + */ +void InitiateVote(MapChange when, ArrayList inputlist=null) +{ + g_WaitingForVote = true; + + bool isVoteInProgress = g_NativeVotes ? NativeVotes_IsVoteInProgress() : IsVoteInProgress(); + if (isVoteInProgress) + { + // Can't start a vote, try again in 5 seconds. + // g_RetryTimer = CreateTimer(5.0, Timer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE); + + DataPack data; + g_RetryTimer = CreateDataTimer(5.0, Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE); + data.WriteCell(when); + data.WriteCell(inputlist); + data.Reset(); + return; + } + + /* If the main map vote has completed (and chosen result) and its currently changing (not a delayed change) we block further attempts */ + if (g_MapVoteCompleted && g_ChangeMapInProgress) + { + return; + } + + g_ChangeTime = when; + + g_WaitingForVote = false; + + g_HasVoteStarted = true; + if (g_NativeVotes) + { + g_VoteNative = new NativeVote(Handler_NV_MapVoteMenu, NativeVotesType_NextLevelMult, NATIVEVOTES_ACTIONS_DEFAULT | MenuAction_DisplayItem); + g_VoteNative.VoteResultCallback = Handler_NV_MapVoteFinished; + } + else + { + g_VoteMenu = new Menu(Handler_MapVoteMenu, MENU_ACTIONS_ALL); + g_VoteMenu.SetTitle("Vote Nextmap"); + g_VoteMenu.VoteResultCallback = Handler_MapVoteFinished; + } + + /* Call OnMapVoteStarted() Forward */ + Call_StartForward(g_MapVoteStartedForward); + Call_Finish(); + + /** + * TODO: Make a proper decision on when to clear the nominations list. + * Currently it clears when used, and stays if an external list is provided. + * Is this the right thing to do? External lists will probably come from places + * like sm_mapvote from the adminmenu in the future. + */ + + char map[PLATFORM_MAX_PATH]; + + /* No input given - Use our internal nominations and maplist */ + if (inputlist == null) + { + int nominateCount = g_NominateList.Length; + int voteSize = g_ConVars[mapvote_include].IntValue; + + // New in 1.5.1 to fix missing extend vote + if (g_NativeVotes) + { + int maxItems = NativeVotes_GetMaxItems(); + if (maxItems < 1) + { + voteSize = 1; + } + else if (voteSize > maxItems) + { + voteSize = maxItems; + } + + if (g_ConVars[mapvote_extend].IntValue && g_Extends < g_ConVars[mapvote_extend].IntValue) + { + voteSize--; + } + + if (voteSize < 1) + { + voteSize = 1; + } + } + /* Smaller of the two - It should be impossible for nominations to exceed the size though (cvar changed mid-map?) */ + int nominationsToAdd = nominateCount >= voteSize ? voteSize : nominateCount; + + if(g_ConVars[mapvote_shuffle_nominations].BoolValue && nominateCount > voteSize) + { + ShuffleNominations(); + } + + for (int i = 0; i < nominationsToAdd; i++) + { + char displayName[PLATFORM_MAX_PATH]; + g_NominateList.GetString(i, map, sizeof(map)); + GetMapDisplayName(map, displayName, sizeof(displayName)); + + if (g_NativeVotes) + { + g_VoteNative.AddItem(map, displayName); + } + else + { + g_VoteMenu.AddItem(map, displayName); + } + + RemoveStringFromArray(g_NextMapList, map); + + /* Notify Nominations that this map is now free */ + Call_StartForward(g_NominationsResetForward); + Call_PushString(map); + Call_PushCell(g_NominateOwners.Get(i)); + Call_Finish(); + } + + /* Clear out the rest of the nominations array */ + for (int i = nominationsToAdd; i < nominateCount; i++) + { + g_NominateList.GetString(i, map, sizeof(map)); + /* These maps shouldn't be excluded from the vote as they weren't really nominated at all */ + + /* Notify Nominations that this map is now free */ + Call_StartForward(g_NominationsResetForward); + Call_PushString(map); + Call_PushCell(g_NominateOwners.Get(i)); + Call_Finish(); + } + + /* There should currently be 'nominationsToAdd' unique maps in the vote */ + + int i = nominationsToAdd; + int count = 0; + int availableMaps = g_NextMapList.Length; + + while (i < voteSize) + { + if (count >= availableMaps) + { + //Run out of maps, this will have to do. + break; + } + + g_NextMapList.GetString(count, map, sizeof(map)); + count++; + + /* Insert the map and increment our count */ + char displayName[PLATFORM_MAX_PATH]; + GetMapDisplayName(map, displayName, sizeof(displayName)); + if (g_NativeVotes) + { + g_VoteNative.AddItem(map, displayName); + } + else + { + g_VoteMenu.AddItem(map, displayName); + } + i++; + } + + /* Wipe out our nominations list - Nominations have already been informed of this */ + g_NominateOwners.Clear(); + g_NominateList.Clear(); + } + else //We were given a list of maps to start the vote with + { + int size = inputlist.Length; + + for (int i = 0; i < size; i++) + { + inputlist.GetString(i, map, sizeof(map)); + + if (IsMapValid(map)) + { + char displayName[PLATFORM_MAX_PATH]; + GetMapDisplayName(map, displayName, sizeof(displayName)); + if (g_NativeVotes) + { + g_VoteNative.AddItem(map, displayName); + } + else + { + g_VoteMenu.AddItem(map, displayName); + } + } + } + } + + /* Do we add any special items? */ + if ((when == MapChange_Instant || when == MapChange_RoundEnd) && g_ConVars[mapvote_dontchange].BoolValue) + { + if (g_NativeVotes) + { + g_VoteNative.AddItem(VOTE_DONTCHANGE, "Don't Change"); + } + else + { + g_VoteMenu.AddItem(VOTE_DONTCHANGE, "Don't Change"); + } + } + else if (g_ConVars[mapvote_extend].BoolValue && g_Extends < g_ConVars[mapvote_extend].IntValue) + { + if (g_NativeVotes) + { + g_VoteNative.AddItem(VOTE_EXTEND, "Extend Map"); + } + else + { + g_VoteMenu.AddItem(VOTE_EXTEND, "Extend Map"); + } + } + + /* There are no maps we could vote for. Don't show anything. */ + if (g_NativeVotes && g_VoteNative.ItemCount == 0) + { + g_HasVoteStarted = false; + g_VoteNative.Close(); + return; + } + else if (!g_NativeVotes && g_VoteMenu.ItemCount == 0) + { + g_HasVoteStarted = false; + delete g_VoteMenu; + return; + } + + int voteDuration = g_ConVars[mapvote_voteduration].IntValue; + + if (g_NativeVotes) + { + g_VoteNative.DisplayVoteToAll(voteDuration); + } + else + { + g_VoteMenu.ExitButton = false; + g_VoteMenu.DisplayVoteToAll(voteDuration); + } + + LogAction(-1, -1, "Voting for next map has started."); + CPrintToChatAll(PLUGIN_PREFIX ... " %t", "Nextmap Voting Started"); +} + +public void Handler_NV_VoteFinishedGeneric(NativeVote menu, int num_votes, int num_clients, const int[] client_indexes, const int[] client_votes, int num_items, const int[] item_indexes, const int[] item_votes) +{ + int[][] client_info = new int[num_clients][2]; + int[][] item_info = new int[num_items][2]; + + NativeVotes_FixResults(num_clients, client_indexes, client_votes, num_items, item_indexes, item_votes, client_info, item_info); + + char map[PLATFORM_MAX_PATH]; + char displayName[PLATFORM_MAX_PATH]; + menu.GetItem(item_indexes[0], map, sizeof(map), displayName, sizeof(displayName)); + + Handler_VoteFinishedGenericShared(map, displayName, num_votes, num_clients, client_info, num_items, item_info, true); +} + +public void Handler_VoteFinishedGeneric(Menu menu, int num_votes, int num_clients, const int[][] client_info, int num_items, const int[][] item_info) +{ + char map[PLATFORM_MAX_PATH]; + char displayName[PLATFORM_MAX_PATH]; + menu.GetItem(item_info[0][VOTEINFO_ITEM_INDEX], map, sizeof(map), _, displayName, sizeof(displayName)); + + Handler_VoteFinishedGenericShared(map, displayName, num_votes, num_clients, client_info, num_items, item_info, false); +} + +public void Handler_VoteFinishedGenericShared(const char[] map, const char[] displayName, int num_votes, int num_clients, const int[][] client_info, int num_items, const int[][] item_info, bool isNativeVotes) +{ + if (strcmp(map, VOTE_EXTEND, false) == 0) + { + g_Extends++; + + int time; + if (GetMapTimeLimit(time)) + { + if (time > 0) + { + ExtendMapTimeLimit(g_ConVars[extendmap_timestep].IntValue * 60); + } + } + + if (g_ConVars[mp_winlimit]) + { + int winlimit = g_ConVars[mp_winlimit].IntValue; + if (winlimit) + { + g_ConVars[mp_winlimit].IntValue = winlimit + g_ConVars[extendmap_roundstep].IntValue; + } + } + + if (g_ConVars[mp_maxrounds]) + { + int maxrounds = g_ConVars[mp_maxrounds].IntValue; + if (maxrounds) + { + g_ConVars[mp_maxrounds].IntValue = maxrounds + g_ConVars[extendmap_roundstep].IntValue; + } + } + + if (g_ConVars[mp_fraglimit]) + { + int fraglimit = g_ConVars[mp_fraglimit].IntValue; + if (fraglimit) + { + g_ConVars[mp_fraglimit].IntValue = fraglimit + g_ConVars[extendmap_fragstep].IntValue; + } + } + + CPrintToChatAll(PLUGIN_PREFIX ... " %t", "Current Map Extended", RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes); + LogAction(-1, -1, "Voting for next map has finished. The current map has been extended."); + + if (isNativeVotes) + { + g_VoteNative.DisplayPassEx(NativeVotesPass_Extend); + } + + // We extended, so we'll have to vote again. + g_HasVoteStarted = false; + CreateNextVote(); + SetupTimeleftTimer(); + + } + else if (strcmp(map, VOTE_DONTCHANGE, false) == 0) + { + CPrintToChatAll(PLUGIN_PREFIX ... " %t", "Current Map Stays", RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes); + LogAction(-1, -1, "Voting for next map has finished. 'No Change' was the winner"); + + if (isNativeVotes) + { + g_VoteNative.DisplayPassEx(NativeVotesPass_Extend); + } + + g_HasVoteStarted = false; + CreateNextVote(); + SetupTimeleftTimer(); + } + else + { + if (g_ChangeTime == MapChange_MapEnd) + { + SetNextMap(map); + } + else if (g_ChangeTime == MapChange_Instant) + { + DataPack data; + CreateDataTimer(2.0, Timer_ChangeMap, data); + data.WriteString(map); + g_ChangeMapInProgress = false; + } + else // MapChange_RoundEnd + { + SetNextMap(map); + g_ChangeMapAtRoundEnd = true; + } + + g_HasVoteStarted = false; + g_MapVoteCompleted = true; + + if (isNativeVotes) + { + g_VoteNative.DisplayPass(displayName); + } + + char buffer[PLATFORM_MAX_PATH]; + Format(buffer, sizeof(buffer), "\x05%s\x01", displayName); + CPrintToChatAll(PLUGIN_PREFIX ... " %t", "Nextmap Voting Finished", buffer, RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes); + LogAction(-1, -1, "Voting for next map has finished. Nextmap: %s.", map); + } +} + +public void Handler_NV_MapVoteFinished(NativeVote menu, int num_votes, int num_clients, const int[] client_indexes, const int[] client_votes, int num_items, const int[] item_indexes, const int[] item_votes) +{ + if (g_ConVars[mapvote_runoff].BoolValue && num_items > 1) + { + float winningvotes = float(item_votes[0]); + float required = num_votes * (g_ConVars[mapvote_runoffpercent].FloatValue / 100.0); + + if (winningvotes < required) + { + //Added in 1.5.1 + menu.DisplayFail(NativeVotesFail_NotEnoughVotes); + + /* Insufficient Winning margin - Lets do a runoff */ + + char map1[PLATFORM_MAX_PATH]; + char map2[PLATFORM_MAX_PATH]; + char info1[PLATFORM_MAX_PATH]; + char info2[PLATFORM_MAX_PATH]; + + DataPack data; + + menu.GetItem(item_indexes[0], map1, sizeof(map1), info1, sizeof(info1)); + menu.GetItem(item_indexes[1], map2, sizeof(map2), info2, sizeof(info2)); + + CreateDataTimer(3.0, Timer_NV_Runoff, data, TIMER_FLAG_NO_MAPCHANGE); + + data.WriteString(map1); + data.WriteString(info1); + data.WriteString(map2); + data.WriteString(info2); + + data.Reset(); + + /* Notify */ + float map1percent = float(item_votes[0])/ float(num_votes) * 100; + float map2percent = float(item_votes[1])/ float(num_votes) * 100; + + CPrintToChatAll(PLUGIN_PREFIX ... " %t", "Starting Runoff", g_ConVars[mapvote_runoffpercent].FloatValue, info1, map1percent, info2, map2percent); + LogMessage("Voting for next map was indecisive, beginning runoff vote"); + + return; + } + } + + Handler_NV_VoteFinishedGeneric(menu, num_votes, num_clients, client_indexes, client_votes, num_items, item_indexes, item_votes); +} + +// New in 1.5.1, used to fix runoff not working properly +public Action Timer_NV_Runoff(Handle timer, DataPack data) +{ + char map[PLATFORM_MAX_PATH], info[PLATFORM_MAX_PATH]; + + g_VoteNative = new NativeVote(Handler_NV_MapVoteMenu, NativeVotesType_NextLevelMult, NATIVEVOTES_ACTIONS_DEFAULT | MenuAction_DisplayItem); + g_VoteNative.VoteResultCallback = Handler_NV_VoteFinishedGeneric; + + data.ReadString(map, sizeof(map)); + data.ReadString(info, sizeof(info)); + g_VoteNative.AddItem(map, info); + + data.ReadString(map, sizeof(map)); + data.ReadString(info, sizeof(info)); + g_VoteNative.AddItem(map, info); + + int voteDuration = g_ConVars[mapvote_voteduration].IntValue; + g_VoteNative.DisplayVoteToAll(voteDuration); + + return Plugin_Continue; +} + +public void Handler_MapVoteFinished(Menu menu, int num_votes, int num_clients, const int[][] client_info, int num_items, const int[][] item_info) +{ + if (g_ConVars[mapvote_runoff].BoolValue && num_items > 1) + { + float winningvotes = float(item_info[0][VOTEINFO_ITEM_VOTES]); + float required = num_votes * (g_ConVars[mapvote_runoffpercent].FloatValue / 100.0); + + if (winningvotes < required) + { + /* Insufficient Winning margin - Lets do a runoff */ + g_VoteMenu = new Menu(Handler_MapVoteMenu, MENU_ACTIONS_ALL); + g_VoteMenu.SetTitle("Runoff Vote Nextmap"); + g_VoteMenu.VoteResultCallback = Handler_VoteFinishedGeneric; + + char map[PLATFORM_MAX_PATH], info1[PLATFORM_MAX_PATH], info2[PLATFORM_MAX_PATH]; + + menu.GetItem(item_info[0][VOTEINFO_ITEM_INDEX], map, sizeof(map), _, info1, sizeof(info1)); + g_VoteMenu.AddItem(map, info1); + menu.GetItem(item_info[1][VOTEINFO_ITEM_INDEX], map, sizeof(map), _, info2, sizeof(info2)); + g_VoteMenu.AddItem(map, info2); + + int voteDuration = g_ConVars[mapvote_voteduration].IntValue; + g_VoteMenu.ExitButton = false; + g_VoteMenu.DisplayVoteToAll(voteDuration); + + /* Notify */ + float map1percent = float(item_info[0][VOTEINFO_ITEM_VOTES])/ float(num_votes) * 100; + float map2percent = float(item_info[1][VOTEINFO_ITEM_VOTES])/ float(num_votes) * 100; + + CPrintToChatAll(PLUGIN_PREFIX ... " %t", "Starting Runoff", g_ConVars[mapvote_runoffpercent].FloatValue, info1, map1percent, info2, map2percent); + LogMessage("Voting for next map was indecisive, beginning runoff vote"); + + return; + } + } + + Handler_VoteFinishedGeneric(menu, num_votes, num_clients, client_info, num_items, item_info); +} + +public int Handler_MapVoteMenu(Menu menu, MenuAction action, int param1, int param2) +{ + switch (action) + { + case MenuAction_End: + { + g_VoteMenu = null; + delete menu; + } + case MenuAction_Display: + { + char buffer[255]; + SetGlobalTransTarget(param1); + Format(buffer, sizeof(buffer), "%t", "Vote Nextmap"); + + Panel panel = view_as<Panel>(param2); + panel.SetTitle(buffer); + } + case MenuAction_DisplayItem: + { + if (menu.ItemCount - 1 == param2) + { + char map[PLATFORM_MAX_PATH], buffer[255]; + menu.GetItem(param2, map, sizeof(map)); + SetGlobalTransTarget(param1); + if (strcmp(map, VOTE_EXTEND, false) == 0) + { + Format(buffer, sizeof(buffer), "%t", "Extend Map"); + return RedrawMenuItem(buffer); + } + else if (strcmp(map, VOTE_DONTCHANGE, false) == 0) + { + Format(buffer, sizeof(buffer), "%t", "Dont Change"); + return RedrawMenuItem(buffer); + } + } + } + case MenuAction_VoteCancel: + { + // If we receive 0 votes, pick at random. + if (param1 == VoteCancel_NoVotes && g_ConVars[mapvote_novote].BoolValue) + { + int count = menu.ItemCount; + char map[PLATFORM_MAX_PATH]; + menu.GetItem(0, map, sizeof(map)); + + // Make sure the first map in the menu isn't one of the special items. + // This would mean there are no real maps in the menu, because the special items are added after all maps. Don't do anything if that's the case. + if (strcmp(map, VOTE_EXTEND, false) != 0 && strcmp(map, VOTE_DONTCHANGE, false) != 0) + { + // Get a random map from the list. + int item = GetRandomInt(0, count - 1); + menu.GetItem(item, map, sizeof(map)); + + // Make sure it's not one of the special items. + while (strcmp(map, VOTE_EXTEND, false) == 0 || strcmp(map, VOTE_DONTCHANGE, false) == 0) + { + item = GetRandomInt(0, count - 1); + menu.GetItem(item, map, sizeof(map)); + } + + SetNextMap(map); + g_MapVoteCompleted = true; + } + } + g_HasVoteStarted = false; + } + } + return 0; +} + +public int Handler_NV_MapVoteMenu(NativeVote menu, MenuAction action, int param1, int param2) +{ + switch (action) + { + case MenuAction_End: + { + g_VoteMenu = null; + menu.Close(); + g_HasVoteStarted = false; + } + case MenuAction_DisplayItem: + { + if (menu.ItemCount - 1 == param2) + { + char map[PLATFORM_MAX_PATH], buffer[255]; + menu.GetItem(param2, map, sizeof(map)); + SetGlobalTransTarget(param1); + if (strcmp(map, VOTE_EXTEND, false) == 0) + { + Format(buffer, sizeof(buffer), "%t", "Extend Map"); + return view_as<int>(NativeVotes_RedrawVoteItem(buffer)); + } + else if (strcmp(map, VOTE_DONTCHANGE, false) == 0) + { + Format(buffer, sizeof(buffer), "%t", "Dont Change"); + return view_as<int>(NativeVotes_RedrawVoteItem(buffer)); + } + } + } + case MenuAction_VoteCancel: + { + // If we receive 0 votes, pick at random. + if (param1 == VoteCancel_NoVotes && g_ConVars[mapvote_novote].BoolValue) + { + int count = menu.ItemCount; + char map[PLATFORM_MAX_PATH], displayName[PLATFORM_MAX_PATH]; + menu.GetItem(0, map, sizeof(map)); + + // Make sure the first map in the menu isn't one of the special items. + // This would mean there are no real maps in the menu, because the special items are added after all maps. Don't do anything if that's the case. + if (strcmp(map, VOTE_EXTEND, false) != 0 && strcmp(map, VOTE_DONTCHANGE, false) != 0) + { + // Get a random map from the list. + int item = GetRandomInt(0, count - 1); + menu.GetItem(item, map, sizeof(map), displayName, sizeof(displayName)); + + // Make sure it's not one of the special items. + while (strcmp(map, VOTE_EXTEND, false) == 0 || strcmp(map, VOTE_DONTCHANGE, false) == 0) + { + item = GetRandomInt(0, count - 1); + menu.GetItem(item, map, sizeof(map), displayName, sizeof(displayName)); + } + + SetNextMap(map); + g_MapVoteCompleted = true; + menu.DisplayPass(displayName); + } + } + else if (param1 == VoteCancel_NoVotes) + { + // We didn't have enough votes. Display the note enough votes fail message. + menu.DisplayFail(NativeVotesFail_NotEnoughVotes); + } + else + { + // We were actually cancelled. Display the generic fail message + menu.DisplayFail(NativeVotesFail_Generic); + } + g_HasVoteStarted = false; + } + } + return 0; +} + +public Action Timer_ChangeMap(Handle hTimer, DataPack dp) +{ + g_ChangeMapInProgress = false; + + char map[PLATFORM_MAX_PATH]; + + if (dp == null) + { + if (!GetNextMap(map, sizeof(map))) + { + //No passed map and no set nextmap. fail! + return Plugin_Stop; + } + } + else + { + dp.Reset(); + dp.ReadString(map, sizeof(map)); + } + + ForceChangeLevel(map, "Map Vote"); + + return Plugin_Stop; +} + +bool RemoveStringFromArray(ArrayList array, char[] str) +{ + int index = array.FindString(str); + if (index != -1) + { + array.Erase(index); + return true; + } + + return false; +} + +void CreateNextVote() +{ + g_NextMapList.Clear(); + + char map[PLATFORM_MAX_PATH]; + // tempMaps is a resolved map list + ArrayList tempMaps = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + + for (int i = 0; i < g_MapList.Length; i++) + { + g_MapList.GetString(i, map, sizeof(map)); + if (FindMap(map, map, sizeof(map)) != FindMap_NotFound) + { + tempMaps.PushString(map); + } + } + + //GetCurrentMap always returns a resolved map + GetCurrentMap(map, sizeof(map)); + RemoveStringFromArray(tempMaps, map); + + if (g_ConVars[mapvote_exclude].IntValue && tempMaps.Length > g_ConVars[mapvote_exclude].IntValue) + { + for (int i = 0; i < g_OldMapList.Length; i++) + { + g_OldMapList.GetString(i, map, sizeof(map)); + RemoveStringFromArray(tempMaps, map); + } + } + + int limit = (g_ConVars[mapvote_include].IntValue < tempMaps.Length ? g_ConVars[mapvote_include].IntValue : tempMaps.Length); + for (int i = 0; i < limit; i++) + { + int b = GetRandomInt(0, tempMaps.Length - 1); + tempMaps.GetString(b, map, sizeof(map)); + g_NextMapList.PushString(map); + tempMaps.Erase(b); + } + + delete tempMaps; +} + +bool CanVoteStart() +{ + return !(g_WaitingForVote || g_HasVoteStarted); +} + +NominateResult InternalNominateMap(char[] map, bool force, int owner) +{ + if (!IsMapValid(map)) + { + return Nominate_InvalidMap; + } + + /* Map already in the vote */ + if (g_NominateList.FindString(map) != -1) + { + return Nominate_AlreadyInVote; + } + + int index; + + /* Look to replace an existing nomination by this client - Nominations made with owner = 0 aren't replaced */ + if (owner && ((index = g_NominateOwners.FindValue(owner)) != -1)) + { + char oldmap[PLATFORM_MAX_PATH]; + g_NominateList.GetString(index, oldmap, sizeof(oldmap)); + Call_StartForward(g_NominationsResetForward); + Call_PushString(oldmap); + Call_PushCell(owner); + Call_Finish(); + + g_NominateList.SetString(index, map); + return Nominate_Replaced; + } + + /* Too many nominated maps. */ + int maxIncludes = 0; + if (g_NativeVotes) + { + maxIncludes = NativeVotes_GetMaxItems(); + + if (g_ConVars[mapvote_include].IntValue < maxIncludes) + { + maxIncludes = g_ConVars[mapvote_include].IntValue; + } + + if (g_ConVars[mapvote_extend].BoolValue && g_Extends < g_ConVars[mapvote_extend].IntValue) + { + maxIncludes--; + } + } + else + { + maxIncludes = g_ConVars[mapvote_include].IntValue; + } + + if (!g_ConVars[mapvote_shuffle_nominations].BoolValue && g_NominateList.Length >= maxIncludes && !force) + { + return Nominate_VoteFull; + } + + g_NominateList.PushString(map); + g_NominateOwners.Push(owner); + + if (!g_ConVars[mapvote_shuffle_nominations].BoolValue) + { + while (g_NominateList.Length > g_ConVars[mapvote_include].IntValue) + { + char oldmap[PLATFORM_MAX_PATH]; + g_NominateList.GetString(0, oldmap, sizeof(oldmap)); + Call_StartForward(g_NominationsResetForward); + Call_PushString(oldmap); + Call_PushCell(g_NominateOwners.Get(0)); + Call_Finish(); + + g_NominateList.Erase(0); + g_NominateOwners.Erase(0); + } + } + + return Nominate_Added; +} + +/* Add natives to allow nominate and initiate vote to be call */ + +/* native NominateResult NominateMap(const char[] map, bool force, int owner); */ +public int Native_NominateMap(Handle plugin, int numParams) +{ + int len; + GetNativeStringLength(1, len); + + if (len <= 0) + { + return false; + } + + char[] map = new char[len+1]; + GetNativeString(1, map, len+1); + + return view_as<int>(InternalNominateMap(map, GetNativeCell(2), GetNativeCell(3))); +} + +bool InternalRemoveNominationByMap(char[] map) +{ + for (int i = 0; i < g_NominateList.Length; i++) + { + char oldmap[PLATFORM_MAX_PATH]; + g_NominateList.GetString(i, oldmap, sizeof(oldmap)); + + if(strcmp(map, oldmap, false) == 0) + { + Call_StartForward(g_NominationsResetForward); + Call_PushString(oldmap); + Call_PushCell(g_NominateOwners.Get(i)); + Call_Finish(); + + g_NominateList.Erase(i); + g_NominateOwners.Erase(i); + + return true; + } + } + + return false; +} + +/* native bool RemoveNominationByMap(const char[] map); */ +public int Native_RemoveNominationByMap(Handle plugin, int numParams) +{ + int len; + GetNativeStringLength(1, len); + + if (len <= 0) + { + return false; + } + + char[] map = new char[len+1]; + GetNativeString(1, map, len+1); + + return InternalRemoveNominationByMap(map); +} + +bool InternalRemoveNominationByOwner(int owner) +{ + int index; + + if (owner && ((index = g_NominateOwners.FindValue(owner)) != -1)) + { + char oldmap[PLATFORM_MAX_PATH]; + g_NominateList.GetString(index, oldmap, sizeof(oldmap)); + + Call_StartForward(g_NominationsResetForward); + Call_PushString(oldmap); + Call_PushCell(owner); + Call_Finish(); + + g_NominateList.Erase(index); + g_NominateOwners.Erase(index); + + return true; + } + + return false; +} + +/* native bool RemoveNominationByOwner(int owner); */ +public int Native_RemoveNominationByOwner(Handle plugin, int numParams) +{ + return InternalRemoveNominationByOwner(GetNativeCell(1)); +} + +/* native void InitiateMapChooserVote(MapChange when, ArrayList inputarray=null); */ +public int Native_InitiateVote(Handle plugin, int numParams) +{ + MapChange when = view_as<MapChange>(GetNativeCell(1)); + ArrayList inputarray = view_as<ArrayList>(GetNativeCell(2)); + + LogAction(-1, -1, "Starting map vote because outside request"); + InitiateVote(when, inputarray); + + return 0; +} + +/* native bool CanMapChooserStartVote(); */ +public int Native_CanVoteStart(Handle plugin, int numParams) +{ + return CanVoteStart(); +} + +/* native bool HasEndOfMapVoteFinished(); */ +public int Native_CheckVoteDone(Handle plugin, int numParams) +{ + return g_MapVoteCompleted; +} + +/* native bool EndOfMapVoteEnabled(); */ +public int Native_EndOfMapVoteEnabled(Handle plugin, int numParams) +{ + return g_ConVars[mapvote_endvote].BoolValue; +} + +/* native void GetExcludeMapList(ArrayList array); */ +public int Native_GetExcludeMapList(Handle plugin, int numParams) +{ + ArrayList array = view_as<ArrayList>(GetNativeCell(1)); + + if (array == null) + { + return 0; + } + int size = g_OldMapList.Length; + char map[PLATFORM_MAX_PATH]; + + for (int i = 0; i < size; i++) + { + g_OldMapList.GetString(i, map, sizeof(map)); + array.PushString(map); + } + + return 0; +} + +/* native void GetNominatedMapList(ArrayList maparray, ArrayList ownerarray = null); */ +public int Native_GetNominatedMapList(Handle plugin, int numParams) +{ + ArrayList maparray = view_as<ArrayList>(GetNativeCell(1)); + ArrayList ownerarray = view_as<ArrayList>(GetNativeCell(2)); + + if (maparray == null) + return 0; + + char map[PLATFORM_MAX_PATH]; + + for (int i = 0; i < g_NominateList.Length; i++) + { + g_NominateList.GetString(i, map, sizeof(map)); + maparray.PushString(map); + + // If the optional parameter for an owner list was passed, then we need to fill that out as well + if(ownerarray != null) + { + int index = g_NominateOwners.Get(i); + ownerarray.Push(index); + } + } + + return 0; +} + +/* Add functions for persistent previous map storage */ +void ReadPreviousMapsFromText() +{ + File file = OpenFile(GetTextFilePath(), "r"); + if (file == null) + { + return; + } + + g_OldMapList.Clear(); + char map[PLATFORM_MAX_PATH]; + do + { + if (file.ReadLine(map, sizeof(map))) + { + TrimString(map); + g_OldMapList.PushString(map); + } + } + while (!file.EndOfFile()); + file.Close(); +} + +void WritePreviousMapsToText() +{ + File file = OpenFile(GetTextFilePath(), "w"); + if (file == null) + { + return; + } + + char lastMap[PLATFORM_MAX_PATH]; + for (int idx=0; idx<g_OldMapList.Length; idx++) + { + g_OldMapList.GetString(idx, lastMap, sizeof(lastMap)); + TrimString(lastMap); + file.WriteLine(lastMap); + } + file.Close(); +} + +char[] GetTextFilePath() +{ + static char path[PLATFORM_MAX_PATH]; + if (path[0] == '\0') + BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "data/mapchooser_history.txt"); + return path; +} + +/* Mapcycle generator */ +void PopulateMapList() +{ + char mapcycleFile[PLATFORM_MAX_PATH]; + g_ConVars[mapcyclefile].GetString(mapcycleFile, sizeof(mapcycleFile)); + Format(mapcycleFile, sizeof(mapcycleFile), "cfg/%s", mapcycleFile); + + File file = OpenFile(mapcycleFile, "w"); + if (file == null) + { + return; + } + + char excludePattern[512]; + g_ConVars[mapcycle_exclude].GetString(excludePattern, sizeof(excludePattern)); + Regex regex = new Regex(excludePattern); + + DirectoryListing dir = OpenDirectory("maps"); + if (dir == null) + { + delete regex; + file.Close(); + return; + } + + char mapName[PLATFORM_MAX_PATH]; + FileType type; + int len; + + ArrayList fastdl = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + + // FastDL + while (dir.GetNext(mapName, sizeof(mapName), type)) + { + if (type != FileType_File) + continue; + + len = strlen(mapName); + if (len <= 4 || !StrEqual(mapName[len-4], ".bsp")) + continue; + + mapName[len-4] = '\0'; + + if (regex.Match(mapName) >= 1) + continue; + + fastdl.PushString(mapName); + } + delete dir; + delete regex; + + fastdl.Sort(Sort_Ascending, Sort_String); + + file.WriteLine("// Generated with NativeVotes MapChooser."); + file.WriteLine("// https://github.com/Heapons/sourcemod-nativevotes-updated"); + + for (int i = 0; i < fastdl.Length; i++) + { + fastdl.GetString(i, mapName, sizeof(mapName)); + file.WriteLine("%s", mapName); + } + delete fastdl; + + // Workshop + if (g_ConVars[workshop_map_collection] != null) + { + char workshopCollection[64]; + g_ConVars[workshop_map_collection].GetString(workshopCollection, sizeof(workshopCollection)); + if (g_RestInPawn && workshopCollection[0] != '\0') + { + HTTPRequest req = new HTTPRequest("https://api.steampowered.com/ISteamRemoteStorage/GetCollectionDetails/v1/"); + req.AppendFormParam("collectioncount", "1"); + req.AppendFormParam("publishedfileids[0]", workshopCollection); + req.PostForm(HTTPResponse_GetCollectionDetails, file); + } + else + { + file.Close(); + } + } + else + { + file.Close(); + } + + CreateTimer(3.0, Timer_ReadMapList, _, TIMER_FLAG_NO_MAPCHANGE|TIMER_REPEAT); +} + +public Action Timer_ReadMapList(Handle timer, any data) +{ + if (ReadMapList(g_MapList, g_mapFileSerial, "mapchooser", MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER) == null) + { + if (g_mapFileSerial == -1) + { + LogError("Unable to create a valid map list."); + return Plugin_Continue; + } + } + return Plugin_Stop; +} + +void HTTPResponse_GetCollectionDetails(HTTPResponse response, File file) +{ + if (response.Status != HTTPStatus_OK || response.Data == null) + { + file.Close(); + return; + } + + JSONObject root = view_as<JSONObject>(response.Data); + if (root == null) + { + file.Close(); + return; + } + + JSONObject responseObj = view_as<JSONObject>(root.Get("response")); + if (responseObj == null) + { + delete root; + file.Close(); + return; + } + + JSONArray collectionDetailsArray = view_as<JSONArray>(responseObj.Get("collectiondetails")); + if (collectionDetailsArray == null || collectionDetailsArray.Length <= 0) + { + delete responseObj; + delete root; + file.Close(); + return; + } + + JSONObject collectionDetails = view_as<JSONObject>(collectionDetailsArray.Get(0)); + if (collectionDetails == null) + { + delete collectionDetailsArray; + delete responseObj; + delete root; + file.Close(); + return; + } + + JSONArray children = view_as<JSONArray>(collectionDetails.Get("children")); + if (children != null) + { + bool workshopMapFix = g_ConVars[sig_etc_workshop_map_fix] != null ? g_ConVars[sig_etc_workshop_map_fix].BoolValue : false; + ArrayList workshop = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + for (int i = 0; i < children.Length; i++) + { + JSONObject child = view_as<JSONObject>(children.Get(i)); + if (child == null) + { + continue; + } + + if (child.GetInt("filetype") == 0) // Map + { + char publishedfileid[64]; + if (child.GetString("publishedfileid", publishedfileid, sizeof(publishedfileid))) + { + if (workshopMapFix) + { + ServerCommand("tf_workshop_map_sync %s", publishedfileid); + } + else + { + char mapName[PLATFORM_MAX_PATH]; + Format(mapName, sizeof(mapName), "workshop/%s", publishedfileid); + if (g_MapList.FindString(mapName) == -1) + { + g_MapList.PushString(mapName); + workshop.PushString(mapName); + } + } + } + } + delete child; + } + delete children; + + workshop.Sort(Sort_Ascending, Sort_String); + for (int i = 0; i < workshop.Length; i++) + { + char mapName[PLATFORM_MAX_PATH]; + workshop.GetString(i, mapName, sizeof(mapName)); + file.WriteLine("%s", mapName); + } + delete workshop; + } + delete collectionDetails; + delete collectionDetailsArray; + delete responseObj; + delete root; + file.Close(); +} + +void CleanupWorkshopMaps() +{ + char workshopDir[PLATFORM_MAX_PATH]; + Format(workshopDir, sizeof(workshopDir), "../steamapps/workshop/content/%d", g_AppID); + + DirectoryListing dir = OpenDirectory(workshopDir); + if (dir != null) + { + char ugcid[PLATFORM_MAX_PATH], path[PLATFORM_MAX_PATH]; + FileType type; + while (dir.GetNext(ugcid, sizeof(ugcid), type)) + { + if (type != FileType_Directory) + continue; + + Format(path, sizeof(path), "%s/%s", workshopDir, ugcid); + DirectoryListing ugcDir = OpenDirectory(path); + if (ugcDir != null) + { + char file[PLATFORM_MAX_PATH]; + FileType fileType; + char filePath[PLATFORM_MAX_PATH]; + while (ugcDir.GetNext(file, sizeof(file), fileType)) + { + if (fileType == FileType_File) + { + Format(filePath, sizeof(filePath), "%s/%s", path, file); + DeleteFile(filePath); + } + } + delete ugcDir; + } + RemoveDir(path); + } + delete dir; + } + RemoveDir(workshopDir); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/nativevotes_nominations.sp b/addons/sourcemod/scripting/nativevotes_nominations.sp index 1a232a9..a92f21c 100644 --- a/addons/sourcemod/scripting/nativevotes_nominations.sp +++ b/addons/sourcemod/scripting/nativevotes_nominations.sp @@ -40,34 +40,42 @@ #include <nativevotes> #define REQUIRE_PLUGIN -#pragma semicolon 1 -#pragma newdecls required - -#define VERSION "1.8.0 beta 4" +#define PLUGIN_PREFIX "[\x04Nominations\x01]" public Plugin myinfo = { - name = "NativeVotes Map Nominations", + name = "NativeVotes | Map Nominations", author = "AlliedModders LLC and Powerlord", description = "Provides Map Nominations", - version = VERSION, - url = "https://forums.alliedmods.net/showthread.php?t=208010" + version = "26w15a", + url = "https://github.com/Heapons/sourcemod-nativevotes-updated/" }; -ConVar g_Cvar_ExcludeOld; -ConVar g_Cvar_ExcludeCurrent; +enum +{ + excludeold, + excludecurrent, + maxmatches, + allow_workshop, + + nextlevel_allowed, + + MAX_CONVARS +} + +ConVar g_ConVars[MAX_CONVARS]; Menu g_MapMenu = null; ArrayList g_MapList = null; int g_mapFileSerial = -1; -#define MAPSTATUS_ENABLED (1<<0) -#define MAPSTATUS_DISABLED (1<<1) -#define MAPSTATUS_EXCLUDE_CURRENT (1<<2) -#define MAPSTATUS_EXCLUDE_PREVIOUS (1<<3) -#define MAPSTATUS_EXCLUDE_NOMINATED (1<<4) +#define MAPSTATUS_ENABLED (1<<0) +#define MAPSTATUS_DISABLED (1<<1) +#define MAPSTATUS_EXCLUDE_CURRENT (1<<2) +#define MAPSTATUS_EXCLUDE_PREVIOUS (1<<3) +#define MAPSTATUS_EXCLUDE_NOMINATED (1<<4) -StringMap g_mapTrie = null; +StringMap g_MapTrie = null; // NativeVotes bool g_NativeVotes; @@ -81,19 +89,21 @@ public void OnPluginStart() LoadTranslations("common.phrases"); LoadTranslations("nominations.phrases"); - int arraySize = ByteCountToCells(PLATFORM_MAX_PATH); - g_MapList = new ArrayList(arraySize); - - CreateConVar("nativevotes_nominations_version", VERSION, "NativeVotes Nominations version", FCVAR_DONTRECORD|FCVAR_NOTIFY|FCVAR_SPONLY); + g_MapList = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); + + g_ConVars[excludeold] = CreateConVar("sm_nominate_excludeold", "1", "Specifies if the current map should be excluded from the Nominations list", _, true, 0.0, true, 1.0); + g_ConVars[excludecurrent] = CreateConVar("sm_nominate_excludecurrent", "1", "Specifies if the MapChooser excluded maps should also be excluded from Nominations", _, true, 0.0, true, 1.0); + g_ConVars[maxmatches] = CreateConVar("sm_nominate_maxfound", "0", "Maximum number of nomination matches to add to the menu. 0 = infinite.", _, true, 0.0); + //g_ConVars[allow_workshop] = CreateConVar("sm_nominate_allow_workshop", "0", "Specifies if unlisted workshop maps can be nominated", _, true, 0.0, true, 1.0); + + g_ConVars[nextlevel_allowed] = FindConVar("sv_vote_issue_nextlevel_allowed"); - g_Cvar_ExcludeOld = CreateConVar("sm_nominate_excludeold", "1", "Specifies if the current map should be excluded from the Nominations list", 0, true, 0.00, true, 1.0); - g_Cvar_ExcludeCurrent = CreateConVar("sm_nominate_excludecurrent", "1", "Specifies if the MapChooser excluded maps should also be excluded from Nominations", 0, true, 0.00, true, 1.0); - RegConsoleCmd("sm_nominate", Command_Nominate); RegAdminCmd("sm_nominate_addmap", Command_Addmap, ADMFLAG_CHANGEMAP, "sm_nominate_addmap <mapname> - Forces a map to be on the next mapvote."); - - g_mapTrie = new StringMap(); + RegAdminCmd("sm_reload_nominations", Command_ReloadNominations, ADMFLAG_RCON, "Reload the nomination map cycle in-place"); + + g_MapTrie = new StringMap(); } public void OnPluginEnd() @@ -120,8 +130,8 @@ public void OnAllPluginsLoaded() } g_NativeVotes = LibraryExists(LIBRARY) && - GetFeatureStatus(FeatureType_Native, "NativeVotes_AreVoteCommandsSupported") == FeatureStatus_Available && - NativeVotes_AreVoteCommandsSupported(); + GetFeatureStatus(FeatureType_Native, "NativeVotes_AreVoteCommandsSupported") == FeatureStatus_Available && + NativeVotes_AreVoteCommandsSupported(); if (g_NativeVotes) RegisterVoteHandler(); @@ -129,7 +139,7 @@ public void OnAllPluginsLoaded() public void OnLibraryAdded(const char[] name) { - if (StrEqual(name, LIBRARY, false) && + if (StrEqual(name, "nativevotes", false) && GetFeatureStatus(FeatureType_Native, "NativeVotes_AreVoteCommandsSupported") == FeatureStatus_Available && NativeVotes_AreVoteCommandsSupported()) { @@ -140,7 +150,7 @@ public void OnLibraryAdded(const char[] name) public void OnLibraryRemoved(const char[] name) { - if (StrEqual(name, LIBRARY, false)) + if (StrEqual(name, "nativevotes", false)) { g_NativeVotes = false; RemoveVoteHandler(); @@ -149,11 +159,7 @@ public void OnLibraryRemoved(const char[] name) public void OnConfigsExecuted() { - if (ReadMapList(g_MapList, - g_mapFileSerial, - "nominations", - MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER) - == null) + if (ReadMapList(g_MapList, g_mapFileSerial, "nominations", MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER) == null) { if (g_mapFileSerial == -1) { @@ -161,7 +167,7 @@ public void OnConfigsExecuted() } } - delete g_MapMenu; + BuildMapMenu(); } public void OnNominationRemoved(const char[] map, int owner) @@ -172,7 +178,7 @@ public void OnNominationRemoved(const char[] map, int owner) FindMap(map, resolvedMap, sizeof(resolvedMap)); /* Is the map in our list? */ - if (!g_mapTrie.GetValue(resolvedMap, status)) + if (!g_MapTrie.GetValue(resolvedMap, status)) { return; } @@ -183,35 +189,35 @@ public void OnNominationRemoved(const char[] map, int owner) return; } - g_mapTrie.SetValue(resolvedMap, MAPSTATUS_ENABLED); + g_MapTrie.SetValue(resolvedMap, MAPSTATUS_ENABLED); } public Action Command_Addmap(int client, int args) { if (args < 1) { - ReplyToCommand(client, "[SM] Usage: sm_nominate_addmap <mapname>"); + CReplyToCommand(client, PLUGIN_PREFIX ... " Usage: sm_nominate_addmap <mapname>"); return Plugin_Handled; } - char mapname[PLATFORM_MAX_PATH]; - char resolvedMap[PLATFORM_MAX_PATH]; + char mapname[PLATFORM_MAX_PATH], resolvedMap[PLATFORM_MAX_PATH]; GetCmdArg(1, mapname, sizeof(mapname)); if (FindMap(mapname, resolvedMap, sizeof(resolvedMap)) == FindMap_NotFound) { // We couldn't resolve the map entry to a filename, so... - ReplyToCommand(client, "%t", "Map was not found", mapname); + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Map was not found", mapname); return Plugin_Handled; } char displayName[PLATFORM_MAX_PATH]; GetMapDisplayName(resolvedMap, displayName, sizeof(displayName)); + Format(displayName, sizeof(displayName), "\x05%s\x01", displayName); int status; - if (!g_mapTrie.GetValue(resolvedMap, status)) + if (!g_MapTrie.GetValue(resolvedMap, status)) { - ReplyToCommand(client, "%t", "Map was not found", displayName); + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Map Not In Pool", displayName); return Plugin_Handled; } @@ -220,24 +226,28 @@ public Action Command_Addmap(int client, int args) if (result > Nominate_Replaced) { /* We assume already in vote is the casue because the maplist does a Map Validity check and we forced, so it can't be full */ - ReplyToCommand(client, "%t", "Map Already In Vote", displayName); + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Map Already In Vote", displayName); return Plugin_Handled; } + + g_MapTrie.SetValue(resolvedMap, MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_NOMINATED); - - g_mapTrie.SetValue(resolvedMap, MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_NOMINATED); - - - ReplyToCommand(client, "%t", "Map Inserted", displayName); + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Map Inserted", displayName); LogAction(client, -1, "\"%L\" inserted map \"%s\".", client, mapname); return Plugin_Handled; } +Action Command_ReloadNominations(int client, int args) +{ + OnConfigsExecuted(); + return Plugin_Handled; +} + public void OnClientSayCommand_Post(int client, const char[] command, const char[] sArgs) { - if (!client) + if (!client || IsChatTrigger()) { return; } @@ -246,7 +256,7 @@ public void OnClientSayCommand_Post(int client, const char[] command, const char { ReplySource old = SetCmdReplySource(SM_REPLY_TO_CHAT); - AttemptNominate(client); + Command_Nominate(client, 0); SetCmdReplySource(old); } @@ -259,48 +269,127 @@ public Action Command_Nominate(int client, int args) return Plugin_Handled; } + ReplySource source = GetCmdReplySource(); + if (args == 0) { - AttemptNominate(client); - return Plugin_Handled; + OpenNominationMenu(client); + return Plugin_Handled; } - + char mapname[PLATFORM_MAX_PATH]; GetCmdArg(1, mapname, sizeof(mapname)); - - return Internal_CommandNominate(client, mapname, false); + + ArrayList results = new ArrayList(); + int matches = FindMatchingMaps(g_MapList, results, mapname); + + char mapResult[PLATFORM_MAX_PATH]; + + if (matches <= 0) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Map was not found", mapname); + } + // One result + else if (matches == 1) + { + // Get the result and nominate it + g_MapList.GetString(results.Get(0), mapResult, sizeof(mapResult)); + AttemptNominate(client, mapResult, sizeof(mapResult), false); + } + else if (matches > 1) + { + if (source == SM_REPLY_TO_CONSOLE) + { + // if source is console, attempt instead of displaying menu. + AttemptNominate(client, mapname, sizeof(mapname), false); + delete results; + return Plugin_Handled; + } + + // Display results to the client and end + Menu menu = new Menu(MenuHandler_MapSelect, MENU_ACTIONS_DEFAULT|MenuAction_DrawItem|MenuAction_DisplayItem); + menu.SetTitle("Select map"); + + for (int i = 0; i < results.Length; i++) + { + g_MapList.GetString(results.Get(i), mapResult, sizeof(mapResult)); + + char displayName[PLATFORM_MAX_PATH]; + GetMapDisplayName(mapResult, displayName, sizeof(displayName)); + + menu.AddItem(mapResult, displayName); + } + + menu.Display(client, 30); + } + + delete results; + + return Plugin_Handled; } -Action Internal_CommandNominate(int client, const char[] mapname, bool isVoteMenu) +int FindMatchingMaps(ArrayList mapList, ArrayList results, const char[] input) { - char resolvedMap[PLATFORM_MAX_PATH]; - - if (FindMap(mapname, resolvedMap, sizeof(resolvedMap)) == FindMap_NotFound) + int map_count = mapList.Length; + + if (!map_count) + { + return -1; + } + + int matches = 0; + char map[PLATFORM_MAX_PATH]; + + int maxmatches = g_ConVars[maxmatches].IntValue; + + for (int i = 0; i < map_count; i++) + { + mapList.GetString(i, map, sizeof(map)); + if (StrContains(map, input) != -1) + { + results.Push(i); + matches++; + + if (maxmatches > 0 && matches >= maxmatches) + { + break; + } + } + } + + return matches; +} + +void AttemptNominate(int client, const char[] map, int size, bool isVoteMenu) +{ + char mapname[PLATFORM_MAX_PATH]; + if (FindMap(map, mapname, size) == FindMap_NotFound) { // We couldn't resolve the map entry to a filename, so... - ReplyToCommand(client, "%t", "Map was not found", mapname); + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Map was not found", mapname); if (isVoteMenu && g_NativeVotes) { NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_MapNotFound); } - return Plugin_Handled; + return; } char displayName[PLATFORM_MAX_PATH]; - GetMapDisplayName(resolvedMap, displayName, sizeof(displayName)); + GetMapDisplayName(mapname, displayName, sizeof(displayName)); + Format(displayName, sizeof(displayName), "\x05%s\x01", displayName); int status; - if (!g_mapTrie.GetValue(resolvedMap, status)) - { - ReplyToCommand(client, "%t", "Map was not found", displayName); - if (isVoteMenu && g_NativeVotes) + if (!g_MapTrie.GetValue(mapname, status)) { - NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_MapNotValid); - } - return Plugin_Handled; + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Map Not In Pool", displayName); + if (isVoteMenu && g_NativeVotes) + { + NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_MapNotValid); + } + return; } - + if ((status & MAPSTATUS_DISABLED) == MAPSTATUS_DISABLED) { if ((status & MAPSTATUS_EXCLUDE_CURRENT) == MAPSTATUS_EXCLUDE_CURRENT) @@ -309,7 +398,7 @@ Action Internal_CommandNominate(int client, const char[] mapname, bool isVoteMen { NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_MapNotValid); } - ReplyToCommand(client, "[SM] %t", "Can't Nominate Current Map"); + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Can't Nominate Current Map"); } if ((status & MAPSTATUS_EXCLUDE_PREVIOUS) == MAPSTATUS_EXCLUDE_PREVIOUS) @@ -318,7 +407,7 @@ Action Internal_CommandNominate(int client, const char[] mapname, bool isVoteMen { NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_MapNotValid); } - ReplyToCommand(client, "[SM] %t", "Map in Exclude List"); + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Map in Exclude List"); } if ((status & MAPSTATUS_EXCLUDE_NOMINATED) == MAPSTATUS_EXCLUDE_NOMINATED) @@ -327,14 +416,14 @@ Action Internal_CommandNominate(int client, const char[] mapname, bool isVoteMen { NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_MapNotValid); } - ReplyToCommand(client, "[SM] %t", "Map Already Nominated"); + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Map Already Nominated"); } - return Plugin_Handled; + return; } - NominateResult result = NominateMap(resolvedMap, false, client); - + NominateResult result = NominateMap(mapname, false, client); + if (result > Nominate_Replaced) { if (result == Nominate_AlreadyInVote) @@ -343,7 +432,7 @@ Action Internal_CommandNominate(int client, const char[] mapname, bool isVoteMen { NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_MapNotValid); } - ReplyToCommand(client, "%t", "Map Already In Vote", displayName); + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Map Already In Vote", displayName); } else { @@ -351,53 +440,56 @@ Action Internal_CommandNominate(int client, const char[] mapname, bool isVoteMen { NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_MapNotValid); } - ReplyToCommand(client, "[SM] %t", "Map Already Nominated"); + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Max Nominations"); } - return Plugin_Handled; + return; } - + /* Map was nominated! - Disable the menu item and update the trie */ - - g_mapTrie.SetValue(resolvedMap, MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_NOMINATED); - - char name[MAX_NAME_LENGTH]; - GetClientName(client, name, sizeof(name)); - PrintToChatAll("[SM] %t", "Map Nominated", name, displayName); - - return Plugin_Handled; -} + g_MapTrie.SetValue(mapname, MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_NOMINATED); + + char name[MAX_NAME_LENGTH]; + GetPlayerName(client, name, sizeof(name)); -void AttemptNominate(int client) -{ - if (g_MapMenu == null) + if (result == Nominate_Added) { - BuildMapMenu(); + CPrintToChatAllEx(client, PLUGIN_PREFIX ... " %t", "Map Nominated", name, displayName); + } + else + { + CReplyToCommandEx(client, client, PLUGIN_PREFIX ... " %t", "Map Nominated", name, displayName); } - g_MapMenu.SetTitle("%T", "Nominate Title", client); - g_MapMenu.Display(client, MENU_TIME_FOREVER); return; } +void OpenNominationMenu(int client) +{ + g_MapMenu.SetTitle("%t", "Nominate Title", client); + g_MapMenu.Display(client, MENU_TIME_FOREVER); +} + void BuildMapMenu() { - g_mapTrie.Clear(); + delete g_MapMenu; + + g_MapTrie.Clear(); - g_MapMenu = new Menu(Handler_MapSelectMenu, MENU_ACTIONS_DEFAULT|MenuAction_DrawItem|MenuAction_DisplayItem); + g_MapMenu = new Menu(MenuHandler_MapSelect, MENU_ACTIONS_DEFAULT|MenuAction_DrawItem|MenuAction_DisplayItem); char map[PLATFORM_MAX_PATH]; ArrayList excludeMaps; char currentMap[PLATFORM_MAX_PATH]; - if (g_Cvar_ExcludeOld.BoolValue) + if (g_ConVars[excludeold].BoolValue) { excludeMaps = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH)); GetExcludeMapList(excludeMaps); } - if (g_Cvar_ExcludeCurrent.BoolValue) + if (g_ConVars[excludecurrent].BoolValue) { GetCurrentMap(currentMap, sizeof(currentMap)); } @@ -413,7 +505,7 @@ void BuildMapMenu() char displayName[PLATFORM_MAX_PATH]; GetMapDisplayName(map, displayName, sizeof(displayName)); - if (g_Cvar_ExcludeCurrent.BoolValue) + if (g_ConVars[excludecurrent].BoolValue) { if (StrEqual(map, currentMap)) { @@ -422,7 +514,7 @@ void BuildMapMenu() } /* Dont bother with this check if the current map check passed */ - if (g_Cvar_ExcludeOld.BoolValue && status == MAPSTATUS_ENABLED) + if (g_ConVars[excludeold].BoolValue && status == MAPSTATUS_ENABLED) { if (excludeMaps.FindString(map) != -1) { @@ -431,7 +523,7 @@ void BuildMapMenu() } g_MapMenu.AddItem(map, displayName); - g_mapTrie.SetValue(map, status); + g_MapTrie.SetValue(map, status); } g_MapMenu.ExitButton = true; @@ -439,50 +531,24 @@ void BuildMapMenu() delete excludeMaps; } -public int Handler_MapSelectMenu(Menu menu, MenuAction action, int param1, int param2) +public int MenuHandler_MapSelect(Menu menu, MenuAction action, int param1, int param2) { switch (action) { case MenuAction_Select: { - char map[PLATFORM_MAX_PATH], name[MAX_NAME_LENGTH], displayName[PLATFORM_MAX_PATH]; - menu.GetItem(param2, map, sizeof(map), _, displayName, sizeof(displayName)); - - GetClientName(param1, name, sizeof(name)); - - NominateResult result = NominateMap(map, false, param1); - - /* Don't need to check for InvalidMap because the menu did that already */ - if (result == Nominate_AlreadyInVote) - { - PrintToChat(param1, "[SM] %t", "Map Already Nominated"); - return 0; - } - else if (result == Nominate_VoteFull) - { - PrintToChat(param1, "[SM] %t", "Max Nominations"); - return 0; - } - - g_mapTrie.SetValue(map, MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_NOMINATED); - - if (result == Nominate_Replaced) - { - PrintToChatAll("[SM] %t", "Map Nomination Changed", name, displayName); - return 0; - } - - PrintToChatAll("[SM] %t", "Map Nominated", name, displayName); + char mapname[PLATFORM_MAX_PATH]; + // Get the map name and attempt to nominate it + menu.GetItem(param2, mapname, sizeof(mapname)); + AttemptNominate(param1, mapname, sizeof(mapname), false); } - case MenuAction_DrawItem: { char map[PLATFORM_MAX_PATH]; menu.GetItem(param2, map, sizeof(map)); int status; - - if (!g_mapTrie.GetValue(map, status)) + if (!g_MapTrie.GetValue(map, status)) { LogError("Menu selection of item not in trie. Major logic problem somewhere."); return ITEMDRAW_DEFAULT; @@ -494,49 +560,54 @@ public int Handler_MapSelectMenu(Menu menu, MenuAction action, int param1, int p } return ITEMDRAW_DEFAULT; - } - case MenuAction_DisplayItem: { - char map[PLATFORM_MAX_PATH], displayName[PLATFORM_MAX_PATH]; - menu.GetItem(param2, map, sizeof(map), _, displayName, sizeof(displayName)); + char mapname[PLATFORM_MAX_PATH]; + menu.GetItem(param2, mapname, sizeof(mapname)); int status; - if (!g_mapTrie.GetValue(map, status)) + if (!g_MapTrie.GetValue(mapname, status)) { LogError("Menu selection of item not in trie. Major logic problem somewhere."); return 0; } - char display[PLATFORM_MAX_PATH + 64]; - if ((status & MAPSTATUS_DISABLED) == MAPSTATUS_DISABLED) { + char displayName[PLATFORM_MAX_PATH]; + GetMapDisplayName(mapname, displayName, sizeof(displayName)); + if ((status & MAPSTATUS_EXCLUDE_CURRENT) == MAPSTATUS_EXCLUDE_CURRENT) { - Format(display, sizeof(display), "%s (%T)", displayName, "Current Map", param1); - return RedrawMenuItem(display); + Format(mapname, sizeof(mapname), "%s (%T)", displayName, "Current Map", param1); + return RedrawMenuItem(mapname); } if ((status & MAPSTATUS_EXCLUDE_PREVIOUS) == MAPSTATUS_EXCLUDE_PREVIOUS) { - Format(display, sizeof(display), "%s (%T)", displayName, "Recently Played", param1); - return RedrawMenuItem(display); + Format(mapname, sizeof(mapname), "%s (%T)", displayName, "Recently Played", param1); + return RedrawMenuItem(mapname); } if ((status & MAPSTATUS_EXCLUDE_NOMINATED) == MAPSTATUS_EXCLUDE_NOMINATED) { - Format(display, sizeof(display), "%s (%T)", displayName, "Nominated", param1); - return RedrawMenuItem(display); + Format(mapname, sizeof(mapname), "%s (%T)", displayName, "Nominated", param1); + return RedrawMenuItem(mapname); } } - - return 0; + } + case MenuAction_End: + { + // This check allows the plugin to use the same callback + // for the main menu and the match menu. + if (menu != g_MapMenu) + { + delete menu; + } } } - return 0; } @@ -593,11 +664,11 @@ public Action Menu_Nominate(int client, NativeVotesOverride overrideType, const ReplySource old = SetCmdReplySource(SM_REPLY_TO_CHAT); - Action myReturn = Internal_CommandNominate(client, voteArgument, true); + AttemptNominate(client, voteArgument, PLATFORM_MAX_PATH, true); SetCmdReplySource(old); - return myReturn; + return Plugin_Handled; } public Action NativeVotes_OverrideMaps(StringMap mapList) @@ -607,7 +678,7 @@ public Action NativeVotes_OverrideMaps(StringMap mapList) BuildMapMenu(); } - if (g_mapTrie.Size == 0) + if (g_MapTrie.Size == 0) { LogMessage("No maps loaded."); return Plugin_Continue; @@ -616,7 +687,7 @@ public Action NativeVotes_OverrideMaps(StringMap mapList) // We don't care about the current list, replace it with our own mapList.Clear(); - StringMapSnapshot snapshot = g_mapTrie.Snapshot(); + StringMapSnapshot snapshot = g_MapTrie.Snapshot(); int length = snapshot.Length; for (int i = 0; i < length; i++) @@ -627,24 +698,34 @@ public Action NativeVotes_OverrideMaps(StringMap mapList) int flags; - if (!g_mapTrie.GetValue(map, flags) || flags & MAPSTATUS_DISABLED == MAPSTATUS_DISABLED) + if (!g_MapTrie.GetValue(map, flags) || flags & MAPSTATUS_DISABLED == MAPSTATUS_DISABLED) { continue; } char displayName[PLATFORM_MAX_PATH]; GetMapDisplayName(map, displayName, sizeof(displayName)); - + // Display name first mapList.SetString(displayName, map); } delete snapshot; - - if (mapList.Size > 0) + + return mapList.Size > 0 ? Plugin_Changed : Plugin_Continue; +} + +void GetPlayerName(int client, char[] name, int maxlen) +{ + int r, g, b, a, color; + GetEntityRenderColor(client, r, g, b, a); + color = (r << 16) | (g << 8) | b; + if (color != 0xFFFFFF) { - return Plugin_Changed; + Format(name, maxlen, "{#%06X}%N\x01", color, client); } - - return Plugin_Continue; -} + else + { + Format(name, maxlen, "{teamcolor}%N\x01", client); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/nativevotes_rockthevote.sp b/addons/sourcemod/scripting/nativevotes_rockthevote.sp index de79dfd..62c1a6e 100644 --- a/addons/sourcemod/scripting/nativevotes_rockthevote.sp +++ b/addons/sourcemod/scripting/nativevotes_rockthevote.sp @@ -47,32 +47,35 @@ #include <tf2> #define REQUIRE_EXTENSIONS -#pragma semicolon 1 -#pragma newdecls required - -#define VERSION "1.8.0 beta 1" +#define PLUGIN_PREFIX "[\x04Rock The Vote\x01]" public Plugin myinfo = { - name = "NativeVotes Rock The Vote", + name = "NativeVotes | Rock The Vote", author = "AlliedModders LLC and Powerlord", description = "Provides RTV Map Voting", - version = VERSION, - url = "https://forums.alliedmods.net/showthread.php?t=208010" + version = "26w15a", + url = "https://github.com/Heapons/sourcemod-nativevotes-updated/" }; -ConVar g_Cvar_Needed; -ConVar g_Cvar_MinPlayers; -ConVar g_Cvar_InitialDelay; -ConVar g_Cvar_Interval; -ConVar g_Cvar_ChangeTime; -ConVar g_Cvar_RTVPostVoteAction; - -bool g_CanRTV = false; // True if RTV loaded maps and is active. -bool g_RTVAllowed = false; // True if RTV is available to players. Used to delay rtv votes. -int g_Voters = 0; // Total voters connected. Doesn't include fake clients. -int g_Votes = 0; // Total number of "say rtv" votes -int g_VotesNeeded = 0; // Necessary votes before map vote begins. (voters * percent_needed) +enum +{ + needed, + minplayers, + initialdelay, + interval, + changetime, + postvoteaction, + + MAX_CONVARS +} + +ConVar g_ConVars[MAX_CONVARS]; + +bool g_RTVAllowed = false; // True if RTV is available to players. Used to delay rtv votes. +int g_Voters = 0; // Total voters connected. Doesn't include fake clients. +int g_Votes = 0; // Total number of "say rtv" votes +int g_VotesNeeded = 0; // Necessary votes before map vote begins. (voters * percent_needed) bool g_Voted[MAXPLAYERS+1] = {false, ...}; bool g_InChange = false; @@ -83,24 +86,35 @@ bool g_RegisteredMenusChangeLevel = false; int g_RTVTime = 0; bool g_Warmup = false; -#define LIBRARY "nativevotes" - - public void OnPluginStart() { LoadTranslations("common.phrases"); LoadTranslations("rockthevote.phrases"); - g_Cvar_Needed = CreateConVar("sm_rtv_needed", "0.60", "Percentage of players needed to rockthevote (Def 60%)", 0, true, 0.05, true, 1.0); - g_Cvar_MinPlayers = CreateConVar("sm_rtv_minplayers", "0", "Number of players required before RTV will be enabled.", 0, true, 0.0, true, float(MAXPLAYERS)); - g_Cvar_InitialDelay = CreateConVar("sm_rtv_initialdelay", "30.0", "Time (in seconds) before first RTV can be held", 0, true, 0.00); - g_Cvar_Interval = CreateConVar("sm_rtv_interval", "240.0", "Time (in seconds) after a failed RTV before another can be held", 0, true, 0.00); - g_Cvar_ChangeTime = CreateConVar("sm_rtv_changetime", "0", "When to change the map after a succesful RTV: 0 - Instant, 1 - RoundEnd, 2 - MapEnd", _, true, 0.0, true, 2.0); - g_Cvar_RTVPostVoteAction = CreateConVar("sm_rtv_postvoteaction", "0", "What to do with RTV's after a mapvote has completed. 0 - Allow, success = instant change, 1 - Deny", _, true, 0.0, true, 1.0); + g_ConVars[needed] = CreateConVar("sm_rtv_needed", "0.60", "Percentage of players needed to rockthevote (Def 60%)", 0, true, 0.05, true, 1.0); + g_ConVars[minplayers] = CreateConVar("sm_rtv_minplayers", "0", "Number of players required before RTV will be enabled.", 0, true, 0.0, true, float(MAXPLAYERS)); + g_ConVars[initialdelay] = CreateConVar("sm_rtv_initialdelay", "30.0", "Time (in seconds) before first RTV can be held", 0, true, 0.00); + g_ConVars[interval] = CreateConVar("sm_rtv_interval", "240.0", "Time (in seconds) after a failed RTV before another can be held", 0, true, 0.00); + g_ConVars[changetime] = CreateConVar("sm_rtv_changetime", "0", "When to change the map after a succesful RTV: 0 - Instant, 1 - RoundEnd, 2 - MapEnd", _, true, 0.0, true, 2.0); + g_ConVars[postvoteaction] = CreateConVar("sm_rtv_postvoteaction", "0", "What to do with RTV's after a mapvote has completed. 0 - Allow, success = instant change, 1 - Deny", _, true, 0.0, true, 1.0); RegConsoleCmd("sm_rtv", Command_RTV); + RegConsoleCmd("sm_rockthevote", Command_RTV); + RegAdminCmd("sm_forcertv", Command_ForceRTV, ADMFLAG_VOTE|ADMFLAG_CHANGEMAP); + RegAdminCmd("sm_resetrtv", Command_ResetRTV, ADMFLAG_VOTE|ADMFLAG_CHANGEMAP); AutoExecConfig(true, "rtv"); + + OnMapEnd(); + + /* Handle late load */ + for (int i = 1; i <= MaxClients; i++) + { + if (IsClientConnected(i)) + { + OnClientConnected(i); + } + } } public void OnPluginEnd() @@ -126,7 +140,7 @@ public void OnAllPluginsLoaded() } } - g_NativeVotes = LibraryExists(LIBRARY) && + g_NativeVotes = LibraryExists("nativevotes") && GetFeatureStatus(FeatureType_Native, "NativeVotes_AreVoteCommandsSupported") == FeatureStatus_Available && NativeVotes_AreVoteCommandsSupported(); @@ -136,7 +150,7 @@ public void OnAllPluginsLoaded() public void OnLibraryAdded(const char[] name) { - if (StrEqual(name, LIBRARY, false) && + if (StrEqual(name, "nativevotes", false) && GetFeatureStatus(FeatureType_Native, "NativeVotes_AreVoteCommandsSupported") == FeatureStatus_Available && NativeVotes_AreVoteCommandsSupported()) { @@ -147,7 +161,7 @@ public void OnLibraryAdded(const char[] name) public void OnLibraryRemoved(const char[] name) { - if (StrEqual(name, LIBRARY, false)) + if (StrEqual(name, "nativevotes", false)) { g_NativeVotes = false; RemoveVoteHandler(); @@ -164,68 +178,46 @@ public void TF2_OnWaitingForPlayersEnd() g_Warmup = false; } -public void OnMapStart() +public void OnMapEnd() { + g_RTVAllowed = false; g_Voters = 0; g_Votes = 0; g_VotesNeeded = 0; g_InChange = false; - - /* Handle late load */ - for (int i=1; i<=MaxClients; i++) - { - if (IsClientConnected(i)) - { - OnClientConnected(i); - } - } -} - -public void OnMapEnd() -{ - g_Warmup = false; - g_CanRTV = false; - g_RTVAllowed = false; } public void OnConfigsExecuted() { - g_CanRTV = true; - g_RTVAllowed = false; - g_RTVTime = GetTime() + g_Cvar_Interval.IntValue; - CreateTimer(g_Cvar_InitialDelay.FloatValue, Timer_DelayRTV, _, TIMER_FLAG_NO_MAPCHANGE); + if (g_ConVars[initialdelay].FloatValue <= 0.0) + { + g_RTVAllowed = true; + return; + } + CreateTimer(g_ConVars[initialdelay].FloatValue, Timer_DelayRTV, _, TIMER_FLAG_NO_MAPCHANGE); } public void OnClientConnected(int client) { - if(IsFakeClient(client)) - return; - - g_Voted[client] = false; - - g_Voters++; - g_VotesNeeded = RoundToFloor(float(g_Voters) * g_Cvar_Needed.FloatValue); - - return; + if (!IsFakeClient(client)) + { + g_Voters++; + g_VotesNeeded = RoundToCeil(float(g_Voters) * g_ConVars[needed].FloatValue); + } } public void OnClientDisconnect(int client) -{ - if(IsFakeClient(client)) - return; - - if(g_Voted[client]) +{ + if (g_Voted[client]) { g_Votes--; + g_Voted[client] = false; } - g_Voters--; - - g_VotesNeeded = RoundToFloor(float(g_Voters) * g_Cvar_Needed.FloatValue); - - if (!g_CanRTV) + if (!IsFakeClient(client)) { - return; + g_Voters--; + g_VotesNeeded = RoundToCeil(float(g_Voters) * g_ConVars[needed].FloatValue); } if (g_Votes && @@ -233,7 +225,7 @@ public void OnClientDisconnect(int client) g_Votes >= g_VotesNeeded && g_RTVAllowed ) { - if (g_Cvar_RTVPostVoteAction.IntValue == 1 && HasEndOfMapVoteFinished()) + if (g_ConVars[postvoteaction].IntValue == 1 && HasEndOfMapVoteFinished()) { return; } @@ -244,7 +236,7 @@ public void OnClientDisconnect(int client) public void OnClientSayCommand_Post(int client, const char[] command, const char[] sArgs) { - if (!g_CanRTV || !client) + if (!client || IsChatTrigger()) { return; } @@ -253,7 +245,7 @@ public void OnClientSayCommand_Post(int client, const char[] command, const char { ReplySource old = SetCmdReplySource(SM_REPLY_TO_CHAT); - AttemptRTV(client); + Command_RTV(client, 0); SetCmdReplySource(old); } @@ -261,7 +253,7 @@ public void OnClientSayCommand_Post(int client, const char[] command, const char public Action Command_RTV(int client, int args) { - if (!g_CanRTV || !client) + if (!client) { return Plugin_Handled; } @@ -271,11 +263,75 @@ public Action Command_RTV(int client, int args) return Plugin_Handled; } +public Action Command_ForceRTV(int client, int args) +{ + if (!g_RTVAllowed) + { + g_RTVAllowed = true; + } + + StartRTV(); + + return Plugin_Handled; +} + +public Action Command_ResetRTV(int client, int args) +{ + ResetRTV(); + + CPrintToChatAll(PLUGIN_PREFIX ... " %t", "Cancelled Vote"); + + return Plugin_Handled; +} + +void UndoRTV(int client) +{ + char yes[64], no[64]; + Format(yes, sizeof(yes), "%T", "Yes", client); + Format(no, sizeof(no), "%T", "No", client); + + char title[128]; + Format(title, sizeof(title), "%T", "Cancel vote", client); + + Menu menu = new Menu(MenuHandler_UndoRTV); + menu.SetTitle(title); + menu.AddItem("yes", yes); + menu.AddItem("no", no); + menu.ExitButton = false; + menu.Display(client, MENU_TIME_FOREVER); +} + +void MenuHandler_UndoRTV(Menu menu, MenuAction action, int client, int param2) +{ + switch (action) + { + case MenuAction_Select: + { + if (param2 == 0) // Yes + { + if (g_Voted[client]) + { + char name[MAX_NAME_LENGTH]; + GetPlayerName(client, name, sizeof(name)); + + g_Voted[client] = false; + if (g_Votes > 0) g_Votes--; + CPrintToChatAllEx(client, PLUGIN_PREFIX ... " %s: %t", name, "Cancelled Vote"); + } + } + } + case MenuAction_End: + { + delete menu; + } + } +} + void AttemptRTV(int client, bool isVoteMenu=false) { - if (!g_RTVAllowed || (g_Cvar_RTVPostVoteAction.IntValue == 1 && HasEndOfMapVoteFinished())) + if (!g_RTVAllowed || (g_ConVars[postvoteaction].IntValue == 1 && HasEndOfMapVoteFinished())) { - ReplyToCommand(client, "[SM] %t", "RTV Not Allowed"); + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "RTV Not Allowed"); if (isVoteMenu && g_NativeVotes) { if (g_Warmup) @@ -297,16 +353,16 @@ void AttemptRTV(int client, bool isVoteMenu=false) } return; } - + if (!CanMapChooserStartVote()) { - ReplyToCommand(client, "[SM] %t", "RTV Started"); + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "RTV Started"); return; } - if (GetClientCount(true) < g_Cvar_MinPlayers.IntValue) + if (GetClientCount(true) < g_ConVars[minplayers].IntValue) { - ReplyToCommand(client, "[SM] %t", "Minimal Players Not Met"); + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Minimal Players Not Met"); if (isVoteMenu && g_NativeVotes) { NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_Loading); @@ -316,21 +372,18 @@ void AttemptRTV(int client, bool isVoteMenu=false) if (g_Voted[client]) { - ReplyToCommand(client, "[SM] %t", "Already Voted", g_Votes, g_VotesNeeded); - if (isVoteMenu && g_NativeVotes) - { - NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_AlreadyActive); - } - return; - } + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Already Voted", g_Votes, g_VotesNeeded); + UndoRTV(client); + return; + } char name[MAX_NAME_LENGTH]; - GetClientName(client, name, sizeof(name)); - + GetPlayerName(client, name, sizeof(name)); + g_Votes++; g_Voted[client] = true; - PrintToChatAll("[SM] %t", "RTV Requested", name, g_Votes, g_VotesNeeded); + CPrintToChatAllEx(client, PLUGIN_PREFIX ... " %t", "RTV Requested", name, g_Votes, g_VotesNeeded); if (g_Votes >= g_VotesNeeded) { @@ -341,6 +394,8 @@ void AttemptRTV(int client, bool isVoteMenu=false) public Action Timer_DelayRTV(Handle timer) { g_RTVAllowed = true; + + return Plugin_Continue; } void StartRTV() @@ -357,8 +412,9 @@ void StartRTV() if (GetNextMap(map, sizeof(map))) { GetMapDisplayName(map, map, sizeof(map)); + Format(map, sizeof(map), "\x05%s\x01", map); - PrintToChatAll("[SM] %t", "Changing Maps", map); + CPrintToChatAll(PLUGIN_PREFIX ... " %t", "Changing Maps", map); CreateTimer(5.0, Timer_ChangeMap, _, TIMER_FLAG_NO_MAPCHANGE); g_InChange = true; @@ -371,15 +427,13 @@ void StartRTV() if (CanMapChooserStartVote()) { - MapChange when = view_as<MapChange>(g_Cvar_ChangeTime.IntValue); + MapChange when = view_as<MapChange>(g_ConVars[changetime].IntValue); InitiateMapChooserVote(when); ResetRTV(); g_RTVAllowed = false; - g_RTVTime = GetTime() + g_Cvar_Interval.IntValue; - - CreateTimer(g_Cvar_Interval.FloatValue, Timer_DelayRTV, _, TIMER_FLAG_NO_MAPCHANGE); + CreateTimer(g_ConVars[interval].FloatValue, Timer_DelayRTV, _, TIMER_FLAG_NO_MAPCHANGE); } } @@ -387,7 +441,7 @@ void ResetRTV() { g_Votes = 0; - for (int i=1; i<=MAXPLAYERS; i++) + for (int i = 1; i <= MaxClients; i++) { g_Voted[i] = false; } @@ -432,9 +486,20 @@ void RemoveVoteHandler() } +public Action Timer_Menu_RTV(Handle timer, int client) +{ + ReplySource old = SetCmdReplySource(SM_REPLY_TO_CHAT); + + AttemptRTV(client, true); + + SetCmdReplySource(old); + + return Plugin_Handled; +} + public Action Menu_RTV(int client, NativeVotesOverride overrideType, const char[] voteArgument) { - if (!client || NativeVotes_IsVoteInProgress()) + if (client <= 0 || (g_NativeVotes && NativeVotes_IsVoteInProgress()) || (!g_NativeVotes && IsVoteInProgress())) { return Plugin_Handled; } @@ -444,12 +509,33 @@ public Action Menu_RTV(int client, NativeVotesOverride overrideType, const char[ NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_SpecifyMap); return Plugin_Handled; } - + + if (CommandExists("sm_nominate")) + { + CreateTimer(0.1, Timer_Menu_RTV, client); + return Plugin_Handled; + } + ReplySource old = SetCmdReplySource(SM_REPLY_TO_CHAT); - + AttemptRTV(client, true); SetCmdReplySource(old); return Plugin_Handled; } + +void GetPlayerName(int client, char[] name, int maxlen) +{ + int r, g, b, a, color; + GetEntityRenderColor(client, r, g, b, a); + color = (r << 16) | (g << 8) | b; + if (color != 0xFFFFFF) + { + Format(name, maxlen, "{#%06X}%N\x01", color, client); + } + else + { + Format(name, maxlen, "{teamcolor}%N\x01", client); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/nativevotes_votemanager_test.sp b/addons/sourcemod/scripting/nativevotes_votemanager_test.sp deleted file mode 100644 index db992b6..0000000 --- a/addons/sourcemod/scripting/nativevotes_votemanager_test.sp +++ /dev/null @@ -1,252 +0,0 @@ -/** - * vim: set ts=4 : - * ============================================================================= - * NativeVotes VoteManager Test - * Test the VoteManger functionality of NativeVotes - * - * NativeVotes VoteManager Test (C)2014 Powerlord (Ross Bemrose). All rights - * reserved. - * ============================================================================= - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License, version 3.0, as published by the - * Free Software Foundation. - * - * This program 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 the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see <http://www.gnu.org/licenses/>. - * - * As a special exception, AlliedModders LLC gives you permission to link the - * code of this program (as well as its derivative works) to "Half-Life 2," the - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software - * by the Valve Corporation. You must obey the GNU General Public License in - * all respects for all other code used. Additionally, AlliedModders LLC grants - * this exception to all derivative works. AlliedModders LLC defines further - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), - * or <http://www.sourcemod.net/license.php>. - * - * Version: $Id$ - */ -#include <sourcemod> - -#pragma newdecls required -#include "include/nativevotes" // Not optional - -#pragma semicolon 1 - -#define VERSION "1.2.0" - -ConVar g_Cvar_Enabled; - -public Plugin myinfo = { - name = "NativeVotes VoteManager Test", - author = "Powerlord", - description = "Test the VoteManger functionality of NativeVotes", - version = VERSION, - url = "https://forums.alliedmods.net/showthread.php?t=208008" -}; - -public void OnPluginStart() -{ - CreateConVar("nativevotes_votemanagertest_version", VERSION, "NativeVotes VoteManager Test version", FCVAR_NOTIFY|FCVAR_DONTRECORD|FCVAR_SPONLY); - g_Cvar_Enabled = CreateConVar("nativevotes_votemanagertest_enable", "1", "Enable NativeVotes VoteManager Test?", FCVAR_NOTIFY|FCVAR_DONTRECORD, true, 0.0, true, 1.0); - HookConVarChange(g_Cvar_Enabled, EnabledChanged); -} - -public void OnAllPluginsLoaded() -{ - if (g_Cvar_Enabled.BoolValue) - { - NativeVotes_RegisterVoteCommand(NativeVotesOverride_Restart, CallVoteTestHandler); - NativeVotes_RegisterVoteCommand(NativeVotesOverride_Kick, CallKickVoteHandler); - NativeVotes_RegisterVoteCommand(NativeVotesOverride_Scramble, CallVoteAdminTestHandler, CallVoteAdminVisHandler); - } -} - -public void OnPluginEnd() -{ - NativeVotes_UnregisterVoteCommand(NativeVotesOverride_Restart, CallVoteTestHandler); - NativeVotes_UnregisterVoteCommand(NativeVotesOverride_Kick, CallKickVoteHandler); - NativeVotes_UnregisterVoteCommand(NativeVotesOverride_Scramble, CallVoteAdminTestHandler, CallVoteAdminVisHandler); -} - -public void EnabledChanged(ConVar convar, const char[] oldValue, const char[] newValue) -{ - if (convar.BoolValue) - { - NativeVotes_RegisterVoteCommand(NativeVotesOverride_Restart, CallVoteTestHandler); - NativeVotes_RegisterVoteCommand(NativeVotesOverride_Kick, CallKickVoteHandler); - NativeVotes_RegisterVoteCommand(NativeVotesOverride_Scramble, CallVoteAdminTestHandler, CallVoteAdminVisHandler); - } - else - { - NativeVotes_UnregisterVoteCommand(NativeVotesOverride_Restart, CallVoteTestHandler); - NativeVotes_UnregisterVoteCommand(NativeVotesOverride_Kick, CallKickVoteHandler); - NativeVotes_UnregisterVoteCommand(NativeVotesOverride_Scramble, CallVoteAdminTestHandler, CallVoteAdminVisHandler); - } -} - -public Action CallVoteTestHandler(int client, NativeVotesOverride overrideType) -{ - ReplySource old = SetCmdReplySource(SM_REPLY_TO_CHAT); - - ReplyToCommand(client, "Attempted to call Restart vote"); - - SetCmdReplySource(old); - - return Plugin_Handled; -} - -public Action CallVoteAdminVisHandler(int client, NativeVotesOverride overrideType) -{ - if (CheckCommandAccess(client, "adminvotetest", ADMFLAG_VOTE, true)) - { - return Plugin_Continue; - } - - return Plugin_Handled; -} - -public Action CallVoteAdminTestHandler(int client, NativeVotesOverride overrideType) -{ - ReplySource old = SetCmdReplySource(SM_REPLY_TO_CHAT); - - ReplyToCommand(client, "Attempted to call Admin-protected Scramble vote"); - - SetCmdReplySource(old); - - return Plugin_Handled; -} - -public Action CallKickVoteHandler(int client, NativeVotesOverride overrideType, const char[] voteArgument, NativeVotesKickType kickType, int target) -{ - ReplySource old = SetCmdReplySource(SM_REPLY_TO_CHAT); - - int targetClient = GetClientOfUserId(target); - - char sKickType[32]; - NativeVotesType voteType = GetKickVoteTypeFromKickType(kickType, sKickType, sizeof(sKickType)); - - if (voteType == NativeVotesType_None) - { - ReplyToCommand(client, "No kick type found"); - return Plugin_Handled; - } - - if (targetClient == 0) - { - NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_PlayerNotFound, target); - ReplyToCommand(client, "Attempted to call Kick (%s) vote on unknown userid %d", sKickType, target); - return Plugin_Handled; - } - - if (!CanUserTarget(client, targetClient)) - { - NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_CantKickAdmin, target); - ReplyToCommand(client, "Attempted to call Kick (%s) vote on %N, but they have a higher immunity level than you.", sKickType, targetClient); - return Plugin_Handled; - } - - ReplyToCommand(client, "Calling Kick (%s) vote on %N", sKickType, targetClient); - - NativeVote vote = new NativeVote(KickVoteHandler, voteType); - vote.Initiator = client; - vote.SetTarget(targetClient); - vote.DisplayVoteToAll(20); - - SetCmdReplySource(old); - - return Plugin_Handled; -} - -public int KickVoteHandler(NativeVote vote, MenuAction action, int param1, int param2) -{ - switch (action) - { - case MenuAction_End: - { - vote.Close(); - } - - case MenuAction_VoteEnd: - { - int target = vote.GetTarget(); - - if (param1 == NATIVEVOTES_VOTE_YES) - { - if (target == 0) - { - vote.DisplayFail(NativeVotesFail_Generic); - PrintToChatAll("User disconnected before kick."); - } - char name[MAX_NAME_LENGTH+1]; - GetClientName(target, name, sizeof(name)); - vote.DisplayPass(name); - PrintToChatAll("Kick vote on %N passed.", target); - } - else - { - vote.DisplayFail(NativeVotesFail_Loses); - PrintToChatAll("Kick vote failed."); - } - } - - case MenuAction_VoteCancel: - { - if (param1 == VoteCancel_NoVotes) - { - vote.DisplayFail(NativeVotesFail_NotEnoughVotes); - PrintToChatAll("Kick vote had no votes."); - } - else - { - vote.DisplayFail(NativeVotesFail_Generic); - PrintToChatAll("Kick vote was cancelled."); - } - } - } -} - -NativeVotesType GetKickVoteTypeFromKickType(NativeVotesKickType kickType, char[] sKickType, int maxlength) -{ - NativeVotesType voteType; - - switch (kickType) - { - case NativeVotesKickType_Generic: - { - strcopy(sKickType, maxlength, "Generic"); - voteType = NativeVotesType_Kick; - } - - case NativeVotesKickType_Idle: - { - strcopy(sKickType, maxlength, "Idle"); - voteType = NativeVotesType_KickIdle; - } - - case NativeVotesKickType_Scamming: - { - strcopy(sKickType, maxlength, "Scamming"); - voteType = NativeVotesType_KickScamming; - } - - case NativeVotesKickType_Cheating: - { - strcopy(sKickType, maxlength, "Cheating"); - voteType = NativeVotesType_KickCheating; - } - - default: - { - strcopy(sKickType, maxlength, ""); - voteType = NativeVotesType_None; - } - } - - return voteType; -} \ No newline at end of file diff --git a/addons/sourcemod/scripting/nativevotes_voterp.sp b/addons/sourcemod/scripting/nativevotes_voterp.sp new file mode 100644 index 0000000..2352f90 --- /dev/null +++ b/addons/sourcemod/scripting/nativevotes_voterp.sp @@ -0,0 +1,154 @@ +#include <sourcemod> +#include <multicolors> +#include <sdktools> + +#undef REQUIRE_PLUGIN +#include <nativevotes> +#define REQUIRE_PLUGIN + +#define PLUGIN_PREFIX "[\x04NativeVotes\x01]" + +public Plugin myinfo = +{ + name = "NativeVotes | Medieval Auto-RP", + author = "Heapons", + description = "Provides Medieval Auto-RP voting.", + version = "26w15a", + url = "https://github.com/Heapons/sourcemod-nativevotes-updated/" +}; + +enum +{ + tf_medieval, + tf_medieval_autorp, + sv_vote_timer_duration, + + MAX_CONVARS +} + +ConVar g_ConVars[MAX_CONVARS]; + +bool g_NativeVotes; + +public void OnPluginStart() +{ + g_ConVars[tf_medieval] = FindConVar("tf_medieval"); + g_ConVars[tf_medieval_autorp] = FindConVar("tf_medieval_autorp"); + if (FindSendPropInfo("CTFGameRulesProxy", "m_bPlayingMedieval") <= 0) + { + SetFailState("This game doesn't support Medieval Mode."); + } + + g_ConVars[sv_vote_timer_duration] = FindConVar("sv_vote_timer_duration"); + + RegConsoleCmd("sm_voterp", Command_VoteRP, "Vote to toggle 'tf_medieval_autorp'."); +} + +public void OnAllPluginsLoaded() +{ + g_NativeVotes = LibraryExists("nativevotes"); +} + +public void OnLibraryAdded(const char[] name) +{ + if (StrEqual(name, "nativevotes", false)) + { + g_NativeVotes = true; + } +} + +public void OnLibraryRemoved(const char[] name) +{ + if (StrEqual(name, "nativevotes", false)) + { + g_NativeVotes = false; + } +} + +int MenuHandler_VoteRP(Menu menu, MenuAction action, int param1, int param2) +{ + switch (action) + { + case MenuAction_VoteEnd: + { + int item = param1; + char info[8]; + menu.GetItem(item, info, sizeof(info)); + int value = StringToInt(info); + g_ConVars[tf_medieval_autorp].SetInt(value); + CPrintToChatAll(PLUGIN_PREFIX ... " tf_medieval_autorp set to %d", value); + } + case MenuAction_End: + { + delete menu; + } + } + return 0; +} + +Action Command_VoteRP(int client, int args) +{ + if (!client || !IsClientInGame(client)) + { + return Plugin_Handled; + } + + if ((g_NativeVotes && NativeVotes_IsVoteInProgress()) || (!g_NativeVotes && IsVoteInProgress())) + { + CPrintToChat(client, PLUGIN_PREFIX ... " A vote is already in progress."); + return Plugin_Handled; + } + + bool isMedieval = g_ConVars[tf_medieval].BoolValue || GameRules_GetProp("m_bPlayingMedieval") || FindEntityByClassname(-1, "tf_logic_medieval") != -1; + if (!isMedieval) + { + CPrintToChat(client, PLUGIN_PREFIX ... " This vote is only available in Medieval Mode."); + return Plugin_Handled; + } + + bool enabled = g_ConVars[tf_medieval_autorp].BoolValue; + char title[64]; + Format(title, sizeof(title), "%s Medieval Mode auto-roleplaying?", enabled ? "Disable" : "Enable"); + + if (g_NativeVotes) + { + NativeVote vote = NativeVotes_Create(VoteHandler, NativeVotesType_Custom_YesNo, MENU_ACTIONS_ALL); + NativeVotes_SetTitle(vote, title); + NativeVotes_SetInitiator(vote, client); + NativeVotes_AddItem(vote, enabled ? "0" : "1", enabled ? "Disable" : "Enable"); + NativeVotes_AddItem(vote, enabled ? "1" : "0", enabled ? "Enable" : "Disable"); + NativeVotes_DisplayToAll(vote, g_ConVars[sv_vote_timer_duration].IntValue); + } + else + { + Menu menu = new Menu(MenuHandler_VoteRP); + menu.SetTitle(title); + menu.AddItem(enabled ? "0" : "1", enabled ? "Disable" : "Enable"); + menu.AddItem(enabled ? "1" : "0", enabled ? "Enable" : "Disable"); + menu.ExitButton = false; + menu.DisplayVoteToAll(g_ConVars[sv_vote_timer_duration].IntValue); + } + + return Plugin_Handled; +} + +int VoteHandler(NativeVote vote, MenuAction action, int param1, int param2) +{ + switch (action) + { + case MenuAction_VoteEnd: + { + int item = param1; + char info[8]; + NativeVotes_GetItem(vote, item, info, sizeof(info)); + int value = StringToInt(info); + g_ConVars[tf_medieval_autorp].SetInt(value); + NativeVotes_DisplayPass(vote, "Setting 'tf_medieval_autorp' to %s...", value ? "true" : "false"); + } + case MenuAction_End: + { + NativeVotes_Close(vote); + } + } + return Plugin_Continue; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/nativevotes_votescramble.sp b/addons/sourcemod/scripting/nativevotes_votescramble.sp new file mode 100644 index 0000000..4f1a6ae --- /dev/null +++ b/addons/sourcemod/scripting/nativevotes_votescramble.sp @@ -0,0 +1,547 @@ +#include <sourcemod> +#include <multicolors> +#include <sdktools> + +#undef REQUIRE_PLUGIN +#include <nativevotes> +#define REQUIRE_PLUGIN + +#define PLUGIN_PREFIX "[\x04Scramble Teams\x01]" + +public Plugin myinfo = +{ + name = "NativeVotes | Scramble Teams", + author = "Heapons", + description = "Provides RTV-style Scramble Teams Voting", + version = "26w15a", + url = "https://github.com/Heapons/sourcemod-nativevotes-updated/" +}; + +enum +{ + needed, + minplayers, + initialdelay, + interval, + full_reset, + mp_match_end_at_timelimit, + mp_timelimit, + sv_vote_timer_duration, + + MAX_CONVARS +} + +ConVar g_ConVars[MAX_CONVARS]; + +bool g_ScrambleAllowed = false; +int g_Voters = 0; +int g_Votes = 0; +int g_VotesNeeded = 0; +bool g_Voted[MAXPLAYERS+1] = {false, ...}; +bool g_NativeVotes; +bool g_RegisteredScramble = false; +int g_ScrambleTime = 0; +float g_MapResetTime = -1.0; +int g_SetRoundsPlayedTo = -1; + +public void OnPluginStart() +{ + LoadTranslations("common.phrases"); + LoadTranslations("rockthevote.phrases"); + LoadTranslations("votescramble.phrases"); + + KeyValues kv = new KeyValues("GameInfo"); + kv.ImportFromFile("gameinfo.txt"); + + char gameDir[128]; + GetGameFolderName(gameDir, sizeof(gameDir)); + + EngineVersion engine = GetEngineVersion(); + if (!StrEqual(gameDir, "tf") && + (kv.GetNum("DependsOnAppID") == 440 || + (engine == Engine_SDK2013 && FileExists("resource/tf.ttf")))) + { + engine = Engine_TF2; + } + + delete kv; + + g_ConVars[needed] = CreateConVar("sm_scrambleteams_needed", "0.60", "Percentage of players needed to scramble teams (Def 60%)", 0, true, 0.05, true, 1.0); + g_ConVars[minplayers] = CreateConVar("sm_scrambleteams_minplayers", "0", "Number of players required before scramble will be enabled.", 0, true, 0.0, true, float(MAXPLAYERS)); + g_ConVars[initialdelay] = CreateConVar("sm_scrambleteams_initialdelay", "30.0", "Time (in seconds) before first scramble can be held", 0, true, 0.00); + g_ConVars[interval] = CreateConVar("sm_scrambleteams_interval", "240.0", "Time (in seconds) after a failed scramble before another can be held", 0, true, 0.00); + g_ConVars[full_reset] = CreateConVar("sm_scrambleteams_full_reset", "1", "Whether time/rounds played should reset after a scramble is triggered ", 0, true, 0.0, true, 1.0); + g_ConVars[mp_match_end_at_timelimit] = FindConVar("mp_match_end_at_timelimit"); + g_ConVars[mp_timelimit] = FindConVar("mp_timelimit"); + g_ConVars[sv_vote_timer_duration] = FindConVar("sv_vote_timer_duration"); + + RegConsoleCmd("sm_votescramble", Command_VoteScramble); + RegConsoleCmd("sm_scramble", Command_VoteScramble); + + RegAdminCmd("sm_forcescramble", Command_ForceScramble, ADMFLAG_VOTE); + RegAdminCmd("sm_resetscramble", Command_ResetScramble, ADMFLAG_VOTE); + + AutoExecConfig(true, "votescramble"); + + OnMapEnd(); + + for (int i = 1; i <= MaxClients; i++) + { + if (IsClientConnected(i)) + { + OnClientConnected(i); + } + } + + switch (engine) + { + case Engine_TF2: + { + HookEvent("teamplay_round_start", Event_TeamplayRoundStart); + } + } +} + +public void OnPluginEnd() +{ + RemoveVoteHandler(); +} + +public void OnAllPluginsLoaded() +{ + g_NativeVotes = LibraryExists("nativevotes") && + GetFeatureStatus(FeatureType_Native, "NativeVotes_AreVoteCommandsSupported") == FeatureStatus_Available && + NativeVotes_AreVoteCommandsSupported(); + + if (g_NativeVotes) + RegisterVoteHandler(); +} + +public void OnLibraryAdded(const char[] name) +{ + if (StrEqual(name, "nativevotes", false) && + GetFeatureStatus(FeatureType_Native, "NativeVotes_AreVoteCommandsSupported") == FeatureStatus_Available && + NativeVotes_AreVoteCommandsSupported()) + { + g_NativeVotes = true; + RegisterVoteHandler(); + } +} + +public void OnLibraryRemoved(const char[] name) +{ + if (StrEqual(name, "nativevotes", false)) + { + g_NativeVotes = false; + RemoveVoteHandler(); + } +} + +public void OnMapEnd() +{ + g_ScrambleAllowed = false; + g_Voters = 0; + g_Votes = 0; + g_VotesNeeded = 0; +} + +public void OnConfigsExecuted() +{ + if (g_ConVars[initialdelay].FloatValue <= 0.0) + { + g_ScrambleAllowed = true; + return; + } + CreateTimer(g_ConVars[initialdelay].FloatValue, Timer_DelayScramble, _, TIMER_FLAG_NO_MAPCHANGE); +} + +public void OnClientConnected(int client) +{ + if (!IsFakeClient(client)) + { + g_Voters++; + g_VotesNeeded = RoundToCeil(float(g_Voters) * g_ConVars[needed].FloatValue); + } +} + +public void OnClientDisconnect(int client) +{ + if (g_Voted[client]) + { + g_Votes--; + g_Voted[client] = false; + } + + if (!IsFakeClient(client)) + { + g_Voters--; + g_VotesNeeded = RoundToCeil(float(g_Voters) * g_ConVars[needed].FloatValue); + } + + if (g_Votes && + g_Voters && + g_Votes >= g_VotesNeeded && + g_ScrambleAllowed) + { + if (g_NativeVotes && NativeVotes_IsVoteInProgress()) + { + return; + } + + StartScramble(); + } +} + +public void OnClientSayCommand_Post(int client, const char[] command, const char[] sArgs) +{ + if (!client || IsChatTrigger()) + { + return; + } + + if (strcmp(sArgs, "scramble", false) == 0) + { + ReplySource old = SetCmdReplySource(SM_REPLY_TO_CHAT); + + Command_VoteScramble(client, 0); + + SetCmdReplySource(old); + } +} + +public void Event_TeamplayRoundStart(Event event, const char[] name, bool dontBroadcast) +{ + CreateTimer(g_ConVars[initialdelay].FloatValue, Timer_DelayScramble, _, TIMER_FLAG_NO_MAPCHANGE); + if (!g_ConVars[full_reset].BoolValue) + { + // If this isn't -1 we know our plugin just triggered a scramble, gotta change it back + if (g_MapResetTime >= 0) { + GameRules_SetPropFloat("m_flMapResetTime", g_MapResetTime); + + // Some gamemodes create a team_round_timer that mimics mp_timelimit, we can update these by setting mp_timelimit to 0 and back. + if (g_ConVars[mp_timelimit].IntValue > 0) { + int flags = g_ConVars[mp_timelimit].Flags; + int oldFlags = flags; + int oldValue = g_ConVars[mp_timelimit].IntValue; + + // Don't notify about this cvar change. + flags = flags & ~FCVAR_NOTIFY; + g_ConVars[mp_timelimit].Flags = flags; + g_ConVars[mp_timelimit].IntValue = 0; + + DataPack pack; + CreateDataTimer(0.1, Timer_FixCTFTimer, pack, TIMER_FLAG_NO_MAPCHANGE); + pack.WriteCell(oldValue); + pack.WriteCell(oldFlags); + } + } + + // If this isn't -1 we know our plugin just triggered a scramble, gotta change it back + if (g_SetRoundsPlayedTo >= 0) + { + GameRules_SetProp("m_nRoundsPlayed", g_SetRoundsPlayedTo); + } + } + g_SetRoundsPlayedTo = -1; + g_MapResetTime = -1.0; +} + +public Action Command_VoteScramble(int client, int args) +{ + if (!client) + return Plugin_Handled; + + AttemptScramble(client); + return Plugin_Handled; +} + +public Action Command_ForceScramble(int client, int args) +{ + if (!g_ScrambleAllowed) + g_ScrambleAllowed = true; + + StartScramble(); + return Plugin_Handled; +} + +public Action Command_ResetScramble(int client, int args) +{ + ResetScramble(); + CPrintToChatAll(PLUGIN_PREFIX ... " %t", "Cancelled Vote"); + return Plugin_Handled; +} + +void UndoScramble(int client) +{ + char yes[64], no[64]; + Format(yes, sizeof(yes), "%T", "Yes", client); + Format(no, sizeof(no), "%T", "No", client); + + char title[128]; + Format(title, sizeof(title), "%T", "Cancel vote", client); + + Menu menu = new Menu(MenuHandler_UndoScramble); + menu.SetTitle(title); + menu.AddItem("yes", yes); + menu.AddItem("no", no); + menu.ExitButton = false; + menu.Display(client, MENU_TIME_FOREVER); +} + +void MenuHandler_UndoScramble(Menu menu, MenuAction action, int client, int param2) +{ + switch (action) + { + case MenuAction_Select: + { + if (param2 == 0) // Yes + { + if (g_Voted[client]) + { + char name[MAX_NAME_LENGTH]; + GetPlayerName(client, name, sizeof(name)); + + g_Voted[client] = false; + if (g_Votes > 0) g_Votes--; + CPrintToChatAllEx(client, PLUGIN_PREFIX ... " %s: %t", name, "Cancelled Vote"); + } + } + } + case MenuAction_End: + { + delete menu; + } + } +} + +void AttemptScramble(int client, bool isVoteMenu=false) +{ + if (!g_ScrambleAllowed) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Scramble Not Allowed"); + if (isVoteMenu && g_NativeVotes) + { + int timeleft = g_ScrambleTime - GetTime(); + if (timeleft > 0) + { + NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_Failed, timeleft); + } + else + { + NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_Generic); + } + } + return; + } + + if (g_NativeVotes && NativeVotes_IsVoteInProgress()) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Vote in Progress"); + return; + } + + if (GetClientCount(true) < g_ConVars[minplayers].IntValue) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Minimal Players Not Met"); + if (isVoteMenu && g_NativeVotes) + { + NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFail_Loading); + } + return; + } + + if (g_Voted[client]) + { + CReplyToCommand(client, PLUGIN_PREFIX ... " %t", "Already Voted", g_Votes, g_VotesNeeded); + UndoScramble(client); + return; + } + + char name[MAX_NAME_LENGTH]; + GetPlayerName(client, name, sizeof(name)); + + g_Votes++; + g_Voted[client] = true; + + CPrintToChatAllEx(client, PLUGIN_PREFIX ... " %t", "Scramble Requested", name, g_Votes, g_VotesNeeded); + + if (g_Votes >= g_VotesNeeded) + { + StartScramble(); + } +} + +void StartScramble() +{ + char yes[64], no[64]; + Format(yes, sizeof(yes), "%T", "Yes", LANG_SERVER); + Format(no, sizeof(no), "%T", "No", LANG_SERVER); + if (g_NativeVotes) + { + NativeVote vote = NativeVotes_Create(ScrambleVoteHandler, NativeVotesType_ScrambleNow); + NativeVotes_SetTitle(vote, "%t", "Scramble Teams"); + NativeVotes_DisplayToAll(vote, g_ConVars[sv_vote_timer_duration].IntValue); + NativeVotes_SetResultCallback(vote, ScrambleVoteResult); + } + else + { + Menu menu = new Menu(MenuHandler_Scramble); + menu.SetTitle("%t", "Scramble Teams"); + menu.AddItem("yes", yes); + menu.AddItem("no", no); + menu.ExitButton = false; + for (int i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i) && !IsFakeClient(i)) + { + menu.Display(i, g_ConVars[sv_vote_timer_duration].IntValue); + } + } + } + ResetScramble(); + g_ScrambleAllowed = false; + + g_ScrambleTime = GetTime() + g_ConVars[interval].IntValue; + CreateTimer(g_ConVars[interval].FloatValue, Timer_DelayScramble, _, TIMER_FLAG_NO_MAPCHANGE); +} + +void ResetScramble() +{ + g_Votes = 0; + for (int i = 1; i <= MAXPLAYERS; i++) + { + g_Voted[i] = false; + } +} + +void RegisterVoteHandler() +{ + if (!g_NativeVotes) + return; + + if (!g_RegisteredScramble) + { + NativeVotes_RegisterVoteCommand(NativeVotesOverride_Scramble, Menu_ScrambleTeams); + g_RegisteredScramble = true; + } +} + +void RemoveVoteHandler() +{ + if (g_RegisteredScramble) + { + if (g_NativeVotes) + NativeVotes_UnregisterVoteCommand(NativeVotesOverride_Scramble, Menu_ScrambleTeams); + g_RegisteredScramble = false; + } +} + +public Action Menu_ScrambleTeams(int client, NativeVotesOverride overrideType, const char[] voteArgument) +{ + if (client <= 0 || NativeVotes_IsVoteInProgress()) + return Plugin_Stop; + + AttemptScramble(client, true); + return Plugin_Stop; +} + +int ScrambleVoteHandler(NativeVote vote, MenuAction action, int param1, int param2) +{ + if (action == MenuAction_End) + { + NativeVotes_Close(vote); + } + return 0; +} + +void ScrambleVoteResult(NativeVote vote, int num_votes, int num_clients, const int[] client_indexes, const int[] client_votes, int num_items, const int[] item_indexes, const int[] item_votes) +{ + int yesVotes = 0; + int noVotes = 0; + + for (int i = 0; i < num_items; i++) + { + if (item_indexes[i] == 0) + { + yesVotes = item_votes[i]; + } + else if (item_indexes[i] == 1) + { + noVotes = item_votes[i]; + } + } + + if (yesVotes > noVotes) + { + NativeVotes_DisplayPassEx(vote, NativeVotesPass_Scramble); + CPrintToChatAll(PLUGIN_PREFIX ... " %t", "Scrambling Teams"); + + // mp_scrambleteams 2 SHOULD preserve rounds played for mp_maxrounds, but doesn't appear to at the moment. + ServerCommand(g_ConVars[full_reset].BoolValue ? "mp_scrambleteams" : "mp_scrambleteams 2"); + g_MapResetTime = GameRules_GetPropFloat("m_flMapResetTime"); + g_SetRoundsPlayedTo = GameRules_GetProp("m_nRoundsPlayed"); + } + else if (yesVotes == 0 && noVotes == 0) + { + NativeVotes_DisplayFail(vote, NativeVotesFail_NotEnoughVotes); + CPrintToChatAll(PLUGIN_PREFIX ... " %t", "No Votes"); + } + else + { + NativeVotes_DisplayFail(vote, NativeVotesFail_Loses); + } +} + +int MenuHandler_Scramble(Menu menu, MenuAction action, int client, int param2) +{ + switch (action) + { + case MenuAction_Select: + { + if (param2 == 0) + { + CPrintToChatAll(PLUGIN_PREFIX ... " %t", "Scrambling Teams"); + ServerCommand(g_ConVars[full_reset].BoolValue ? "mp_scrambleteams" : "mp_scrambleteams 2"); + } + else + { + CPrintToChatAll(PLUGIN_PREFIX ... " %t", "No Votes"); + } + } + case MenuAction_End: + { + delete menu; + } + } + return 0; +} + +public Action Timer_DelayScramble(Handle timer) +{ + g_ScrambleAllowed = true; + return Plugin_Continue; +} + +public Action Timer_FixCTFTimer(Handle timer, DataPack pack) +{ + pack.Reset(); + int oldValue = pack.ReadCell(); + int oldFlags = pack.ReadCell(); + g_ConVars[mp_timelimit].IntValue = oldValue; + g_ConVars[mp_timelimit].Flags = oldFlags; +} + +void GetPlayerName(int client, char[] name, int maxlen) +{ + int r, g, b, a, color; + GetEntityRenderColor(client, r, g, b, a); + color = (r << 16) | (g << 8) | b; + if (color != 0xFFFFFF) + { + Format(name, maxlen, "{#%06X}%N\x01", color, client); + } + else + { + Format(name, maxlen, "{teamcolor}%N\x01", client); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/nativevotes_votetest.sp b/addons/sourcemod/scripting/nativevotes_votetest.sp deleted file mode 100644 index 8450c92..0000000 --- a/addons/sourcemod/scripting/nativevotes_votetest.sp +++ /dev/null @@ -1,437 +0,0 @@ -/** - * vim: set ts=4 : - * ============================================================================= - * NativeVotes Vote Tester - * Copyright (C) 2011-2013 Ross Bemrose (Powerlord). All rights reserved. - * ============================================================================= - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License, version 3.0, as published by the - * Free Software Foundation. - * - * This program 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 the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see <http://www.gnu.org/licenses/>. - * - * As a special exception, AlliedModders LLC gives you permission to link the - * code of this program (as well as its derivative works) to "Half-Life 2," the - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software - * by the Valve Corporation. You must obey the GNU General Public License in - * all respects for all other code used. Additionally, AlliedModders LLC grants - * this exception to all derivative works. AlliedModders LLC defines further - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), - * or <http://www.sourcemod.net/license.php>. - * - * Version: $Id$ - */ - -#include <sourcemod> - -#pragma newdecls required -#include "include/nativevotes" - -#define VERSION "1.1" - -public Plugin myinfo = -{ - name = "NativeVotes Vote Tester", - author = "Powerlord", - description = "Various NativeVotes vote type tests", - version = "1.0", - url = "https://forums.alliedmods.net/showthread.php?t=208008" -} - -public void OnPluginStart() -{ - CreateConVar("nativevotestest_version", VERSION, "NativeVotes Vote Tester version", FCVAR_NOTIFY|FCVAR_DONTRECORD); - - RegAdminCmd("voteyesno", Cmd_TestYesNo, ADMFLAG_VOTE, "Test Yes/No votes"); - RegAdminCmd("votemult", Cmd_TestMult, ADMFLAG_VOTE, "Test Multiple Choice votes"); - RegAdminCmd("voteyesnocustom", Cmd_TestYesNoCustom, ADMFLAG_VOTE, "Test Multiple Choice vote with Custom Display text"); - RegAdminCmd("votemultcustom", Cmd_TestMultCustom, ADMFLAG_VOTE, "Test Multiple Choice vote with Custom Display text"); - RegAdminCmd("votenovote", Cmd_TestNoVote, ADMFLAG_VOTE, "Test Multiple Choice vote with \"No Vote\" option"); - RegAdminCmd("votenovotecustom", Cmd_TestNoVoteCustom, ADMFLAG_VOTE, "Test Multiple Choice vote with \"No Vote\" option and Custom Display text"); -} - -public Action Cmd_TestYesNo(int client, int args) -{ - if (!NativeVotes_IsVoteTypeSupported(NativeVotesType_Custom_YesNo)) - { - ReplyToCommand(client, "Game does not support Custom Yes/No votes."); - return Plugin_Handled; - } - - if (!NativeVotes_IsNewVoteAllowed()) - { - int seconds = NativeVotes_CheckVoteDelay(); - ReplyToCommand(client, "Vote is not allowed for %d more seconds", seconds); - return Plugin_Handled; - } - - NativeVote vote = new NativeVote(YesNoHandler, NativeVotesType_Custom_YesNo); - - vote.Initiator = client; - vote.SetDetails("Test Yes/No Vote"); - vote.DisplayVoteToAll(30); - - return Plugin_Handled; -} - -public int YesNoHandler(NativeVote vote, MenuAction action, int param1, int param2) -{ - switch (action) - { - case MenuAction_End: - { - vote.Close(); - } - - case MenuAction_VoteCancel: - { - if (param1 == VoteCancel_NoVotes) - { - vote.DisplayFail(NativeVotesFail_NotEnoughVotes); - } - else - { - vote.DisplayFail(NativeVotesFail_Generic); - } - } - - case MenuAction_VoteEnd: - { - if (param1 == NATIVEVOTES_VOTE_NO) - { - vote.DisplayFail(NativeVotesFail_Loses); - } - else - { - vote.DisplayPass("Test Yes/No Vote Passed!"); - // Do something because it passed - } - } - } -} - -public Action Cmd_TestMult(int client, int args) -{ - if (!NativeVotes_IsVoteTypeSupported(NativeVotesType_Custom_Mult)) - { - ReplyToCommand(client, "Game does not support Custom Multiple Choice votes."); - return Plugin_Handled; - } - - if (!NativeVotes_IsNewVoteAllowed()) - { - int seconds = NativeVotes_CheckVoteDelay(); - ReplyToCommand(client, "Vote is not allowed for %d more seconds", seconds); - return Plugin_Handled; - } - - NativeVote vote = new NativeVote(MultHandler, NativeVotesType_Custom_Mult); - - vote.Initiator = client; - vote.SetDetails("Test Mult Vote"); - vote.AddItem("choice1", "Choice 1"); - vote.AddItem("choice2", "Choice 2"); - vote.AddItem("choice3", "Choice 3"); - vote.AddItem("choice4", "Choice 4"); - vote.AddItem("choice5", "Choice 5"); - // 5 is currently the maximum number of choices in any game - vote.DisplayVoteToAll(30); - - return Plugin_Handled; -} - -public int MultHandler(NativeVote vote, MenuAction action, int param1, int param2) -{ - switch (action) - { - case MenuAction_End: - { - vote.Close(); - } - - case MenuAction_VoteCancel: - { - if (param1 == VoteCancel_NoVotes) - { - vote.DisplayFail(NativeVotesFail_NotEnoughVotes); - } - else - { - vote.DisplayFail(NativeVotesFail_Generic); - } - } - - case MenuAction_VoteEnd: - { - char info[64]; - char display[64]; - vote.GetItem(param1, info, sizeof(info), display, sizeof(display)); - - vote.DisplayPass(display); - - // Do something with info - } - } -} - -public Action Cmd_TestYesNoCustom(int client, int args) -{ - if (!NativeVotes_IsVoteTypeSupported(NativeVotesType_Custom_YesNo)) - { - ReplyToCommand(client, "Game does not support Custom Yes/No votes."); - return Plugin_Handled; - } - - if (!NativeVotes_IsNewVoteAllowed()) - { - int seconds = NativeVotes_CheckVoteDelay(); - ReplyToCommand(client, "Vote is not allowed for %d more seconds", seconds); - return Plugin_Handled; - } - - NativeVote vote = new NativeVote(YesNoCustomHandler, NativeVotesType_Custom_YesNo, NATIVEVOTES_ACTIONS_DEFAULT|MenuAction_Display); - - vote.Initiator = client; - vote.SetDetails("Test Yes/No Vote"); - vote.DisplayVoteToAll(30); - - return Plugin_Handled; -} - -public int YesNoCustomHandler(NativeVote vote, MenuAction action, int param1, int param2) -{ - switch (action) - { - case MenuAction_End: - { - vote.Close(); - } - - case MenuAction_Display: - { - char display[64]; - Format(display, sizeof(display), "%N Test Yes/No Vote", param1); - PrintToChat(param1, "New Menu Title: %s", display); - NativeVotes_RedrawVoteTitle(display); - return view_as<int>(Plugin_Changed); - } - - case MenuAction_VoteCancel: - { - if (param1 == VoteCancel_NoVotes) - { - vote.DisplayFail(NativeVotesFail_NotEnoughVotes); - } - else - { - vote.DisplayFail(NativeVotesFail_Generic); - } - } - - case MenuAction_VoteEnd: - { - if (param1 == NATIVEVOTES_VOTE_NO) - { - vote.DisplayFail(NativeVotesFail_Loses); - } - else - { - vote.DisplayPass("Test Custom Yes/No Vote Passed!"); - // Do something because it passed - } - } - } - - return 0; -} - -public Action Cmd_TestMultCustom(int client, int args) -{ - if (!NativeVotes_IsVoteTypeSupported(NativeVotesType_Custom_Mult)) - { - ReplyToCommand(client, "Game does not support Custom Multiple Choice votes."); - return Plugin_Handled; - } - - if (!NativeVotes_IsNewVoteAllowed()) - { - int seconds = NativeVotes_CheckVoteDelay(); - ReplyToCommand(client, "Vote is not allowed for %d more seconds", seconds); - return Plugin_Handled; - } - - NativeVote vote = new NativeVote(MultCustomHandler, NativeVotesType_Custom_Mult, NATIVEVOTES_ACTIONS_DEFAULT|MenuAction_Display|MenuAction_DisplayItem); - - vote.Initiator = client; - vote.SetDetails("Test Mult Vote"); - vote.AddItem("choice1", "Choice 1"); - vote.AddItem("choice2", "Choice 2"); - vote.AddItem("choice3", "Choice 3"); - vote.AddItem("choice4", "Choice 4"); - vote.AddItem("choice5", "Choice 5"); - // 5 is currently the maximum number of choices in any game - vote.DisplayVoteToAll(30); - - return Plugin_Handled; -} - -public int MultCustomHandler(NativeVote vote, MenuAction action, int param1, int param2) -{ - switch (action) - { - case MenuAction_End: - { - vote.Close(); - } - - case MenuAction_Display: - { - char display[64]; - Format(display, sizeof(display), "%N Test Mult Vote", param1); - PrintToChat(param1, "New Menu Title: %s", display); - NativeVotes_RedrawVoteTitle(display); - return view_as<int>(Plugin_Changed); - } - - case MenuAction_VoteCancel: - { - if (param1 == VoteCancel_NoVotes) - { - vote.DisplayFail(NativeVotesFail_NotEnoughVotes); - } - else - { - vote.DisplayFail(NativeVotesFail_Generic); - } - } - - case MenuAction_VoteEnd: - { - char info[64]; - char display[64]; - vote.GetItem(param1, info, sizeof(info), display, sizeof(display)); - - // Do something with info - //NativeVotes_DisplayPassCustom(vote, "%t Mult passed", "Translation Phrase"); - vote.DisplayPassCustom("%s passed", display); - } - - case MenuAction_DisplayItem: - { - char info[64]; - char display[64]; - - char buffer[64]; - - vote.GetItem(param2, info, sizeof(info), display, sizeof(display)); - - // This is generally how you'd do translations, but normally with %T and a format phrase - bool bReplace = false; - if (StrEqual(info, "choice1")) - { - Format(buffer, sizeof(buffer), "%N %s", param1, display); - bReplace = true; - } - else if (StrEqual(info, "choice2")) - { - Format(buffer, sizeof(buffer), "%N %s", param1, display); - bReplace = true; - } - else if (StrEqual(info, "choice3")) - { - Format(buffer, sizeof(buffer), "%N %s", param1, display); - bReplace = true; - } - else if (StrEqual(info, "choice4")) - { - Format(buffer, sizeof(buffer), "%N %s", param1, display); - bReplace = true; - } - else if (StrEqual(info, "choice5")) - { - Format(buffer, sizeof(buffer), "%N %s", param1, display); - bReplace = true; - } - - PrintToChat(param1, "New Menu Item %d: %s", param2, buffer); - - if (bReplace) - { - NativeVotes_RedrawVoteItem(buffer); - return view_as<int>(Plugin_Changed); - } - } - } - - return 0; -} - -public Action Cmd_TestNoVote(int client, int args) -{ - if (!NativeVotes_IsVoteTypeSupported(NativeVotesType_Custom_Mult)) - { - ReplyToCommand(client, "Game does not support Custom Multiple Choice votes."); - return Plugin_Handled; - } - - if (!NativeVotes_IsNewVoteAllowed()) - { - int seconds = NativeVotes_CheckVoteDelay(); - ReplyToCommand(client, "Vote is not allowed for %d more seconds", seconds); - return Plugin_Handled; - } - - NativeVote vote = new NativeVote(MultHandler, NativeVotesType_Custom_Mult); - - vote.NoVoteButton = true; - vote.Initiator = client; - vote.SetDetails("Test Mult Vote with NoVote"); - vote.AddItem("choice1", "Choice 1"); - vote.AddItem("choice2", "Choice 2"); - vote.AddItem("choice3", "Choice 3"); - vote.AddItem("choice4", "Choice 4"); - vote.AddItem("choice5", "Choice 5"); - // 5 is currently the maximum number of choices in any game, but No Vote should make the max 4... - vote.DisplayVoteToAll(30); - - return Plugin_Handled; -} - -public Action Cmd_TestNoVoteCustom(int client, int args) -{ - if (!NativeVotes_IsVoteTypeSupported(NativeVotesType_Custom_Mult)) - { - ReplyToCommand(client, "Game does not support Custom Multiple Choice votes."); - return Plugin_Handled; - } - - if (!NativeVotes_IsNewVoteAllowed()) - { - int seconds = NativeVotes_CheckVoteDelay(); - ReplyToCommand(client, "Vote is not allowed for %d more seconds", seconds); - return Plugin_Handled; - } - - NativeVote vote = new NativeVote(MultCustomHandler, NativeVotesType_Custom_Mult, NATIVEVOTES_ACTIONS_DEFAULT|MenuAction_Display|MenuAction_DisplayItem); - - vote.NoVoteButton = true; - vote.Initiator = client; - vote.SetDetails("Test Mult Vote"); - vote.AddItem("choice1", "Choice 1"); - vote.AddItem("choice2", "Choice 2"); - vote.AddItem("choice3", "Choice 3"); - vote.AddItem("choice4", "Choice 4"); - vote.AddItem("choice5", "Choice 5"); - // 5 is currently the maximum number of choices in any game, but No Vote should make the max 4... - vote.DisplayVoteToAll(30); - - return Plugin_Handled; -} - diff --git a/addons/sourcemod/scripting/votedelay_changelevel.sp b/addons/sourcemod/scripting/votedelay_changelevel.sp deleted file mode 100644 index c370d30..0000000 --- a/addons/sourcemod/scripting/votedelay_changelevel.sp +++ /dev/null @@ -1,223 +0,0 @@ -/** - * vim: set ts=4 : - * ============================================================================= - * Vote Delay: Changelevel - * Delay ChangeLevel and NextLevel votes until X rounds have passed - * - * Vote Delay: Changelevel (C)2014 Powerlord (Ross Bemrose). All rights reserved. - * ============================================================================= - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License, version 3.0, as published by the - * Free Software Foundation. - * - * This program 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 the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see <http://www.gnu.org/licenses/>. - * - * As a special exception, AlliedModders LLC gives you permission to link the - * code of this program (as well as its derivative works) to "Half-Life 2," the - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software - * by the Valve Corporation. You must obey the GNU General Public License in - * all respects for all other code used. Additionally, AlliedModders LLC grants - * this exception to all derivative works. AlliedModders LLC defines further - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), - * or <http://www.sourcemod.net/license.php>. - * - * Version: $Id$ - */ -#include <sourcemod> -#include <sdktools> -#pragma semicolon 1 - -#define VERSION "1.0.1" - -#define VOTE_STRING_SIZE 32 - -new Handle:g_Cvar_Enabled; -new Handle:g_Cvar_FullRounds; -new Handle:g_Cvar_Rounds; - -new g_VoteController = -1; -new g_bUserBuf = false; - -// Stolen from NativeVotes -enum NativeVotesCallFailType -{ - NativeVotesCallFail_Generic = 0, /**< Generic fail. */ - NativeVotesCallFail_Loading = 1, /**< L4D/L4D2: Players are still loading. */ - NativeVotesCallFail_Recent = 2, /**< TF2/CS:GO: You can't call another vote yet: Argument is seconds until you can call another vote. */ - NativeVotesCallFail_Disabled = 5, /**< TF2/CS:GO: Server has disabled that issue. */ - NativeVotesCallFail_MapNotFound = 6, /**< TF2/CS:GO: Server does not have that map. */ - NativeVotesCallFail_SpecifyMap = 7, /**< TF2/CS:GO: You must specify a map. */ - NativeVotesCallFail_Failed = 8, /**< TF2/CS:GO: This vote failed recently: Argument is seconds until this vote can be called again. */ - NativeVotesCallFail_WrongTeam = 9, /**< TF2/CS:GO: Team can't call this vote. */ - NativeVotesCallFail_Waiting = 10, /**< TF2/CS:GO: Vote can't be called during Waiting For Players. */ - NativeVotesCallFail_PlayerNotFound = 11, /**< TF2/CS:GO: Player to kick can't be found. Buggy in TF2. */ - NativeVotesCallFail_Unknown = 11, - NativeVotesCallFail_CantKickAdmin = 12, /**< TF2/CS:GO: Can't kick server admin. */ - NativeVotesCallFail_ScramblePending = 13, /**< TF2/CS:GO: Team Scramble is pending. */ - NativeVotesCallFail_Spectators = 14, /**< TF2/CS:GO: Spectators aren't allowed to call votes. */ - NativeVotesCallFail_LevelSet = 15, /**< TF2: Next level already set. */ - NativeVotesCallFail_Warmup = 15, /**< CSGO: Vote can't be called during Warmup */ - NativeVotesCallFail_MapNotValid = 16, /**< TF2: Map is not in MapCycle. */ - NativeVotesCallFail_KickTime = 17, /**< TF2: Cannot kick at this time: Argument is seconds until you can call another kick vote. */ - NativeVotesCallFail_KickDuringRound = 18, /**< TF2: Cannot kick during a round. */ -}; - -public Plugin:myinfo = { - name = "Vote Delay: Changelevel", - author = "Powerlord", - description = "Delay vote change until after X rounds have gone by", - version = VERSION, - url = "" -}; - -new roundCount = 1; - -public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max) -{ - new EngineVersion:engine = GetEngineVersion(); - - if (engine != Engine_TF2 && engine != Engine_CSGO) - { - strcopy(error, err_max, "Only works on TF2 and CS:GO"); - return APLRes_Failure; - } - return APLRes_Success; -} - -public OnPluginStart() -{ - CreateConVar("votedelay_changelevel_version", VERSION, "Vote Delay: Changelevel version", FCVAR_NOTIFY|FCVAR_DONTRECORD|FCVAR_SPONLY); - g_Cvar_Enabled = CreateConVar("votedelay_changelevel_enable", "1", "Enable Vote Delay: Changelevel?", FCVAR_NOTIFY|FCVAR_DONTRECORD, true, 0.0, true, 1.0); - g_Cvar_FullRounds = CreateConVar("votedelay_changelevel_fullrounds", "1", "Full rounds only? Only applies to TF2.", FCVAR_NONE, true, 0.0, true, 1.0); - g_Cvar_Rounds = CreateConVar("votedelay_changelevel_rounds", "4", "During what round should votes become available?", FCVAR_NONE, true, 0.0, true, 10.0); - - g_bUserBuf = (GetUserMessageType() == UM_Protobuf); - - AddCommandListener(Cmd_CallVote, "callvote"); - HookEvent("round_end", Event_RoundEnd); - HookEventEx("teamplay_round_win", Event_RoundWin); - - AutoExecConfig(true, "votedelay_changelevel"); -} - -public OnMapStart() -{ - roundCount = 1; -} - -public Event_RoundEnd(Handle:event, const String:name[], bool:dontBroadcast) -{ - if (!GameRules_GetProp("m_bWarmupPeriod")) - { - roundCount++; - } -} - -public Event_RoundWin(Handle:event, const String:name[], bool:dontBroadcast) -{ - if (GetConVarBool(g_Cvar_FullRounds) && !GetEventInt(event, "full_round")) - { - return; - } - - roundCount++; -} - -public Action:Cmd_CallVote(client, const String:command[], argc) -{ - if (client == 0) - { - return Plugin_Continue; - } - - if (!GetConVarBool(g_Cvar_Enabled) || argc == 0 || IsNativeVoteInProgress()) - { - return Plugin_Continue; - } - - decl String:voteCommand[VOTE_STRING_SIZE]; - GetCmdArg(1, voteCommand, VOTE_STRING_SIZE); - - if (!StrEqual(voteCommand, "ChangeLevel", false) && !StrEqual(voteCommand, "NextLevel", false)) - { - return Plugin_Continue; - } - - new voteRound = GetConVarInt(g_Cvar_Rounds); - - if (roundCount >= voteRound) - { - return Plugin_Continue; - } - - new ReplySource:source = SetCmdReplySource(SM_REPLY_TO_CHAT); - - // For lack of a better reason - TF2CSGO_CallVoteFail(client, NativeVotesCallFail_Disabled); - ReplyToCommand(client, "%s vote is disabled until after round %d, current round is %d", voteCommand, voteRound, roundCount); - - SetCmdReplySource(source); - - return Plugin_Stop; -} - -bool:IsNativeVoteInProgress() -{ - if (CheckVoteController()) - { - new activeIndex = GetEntProp(g_VoteController, Prop_Send, "m_iActiveIssueIndex"); - if (activeIndex > -1) - { - return true; - } - } - - return false; -} - -bool:CheckVoteController() -{ - new entity = -1; - if (g_VoteController != -1) - { - entity = EntRefToEntIndex(g_VoteController); - } - - if (entity == -1) - { - entity = FindEntityByClassname(-1, "vote_controller"); - if (entity == -1) - { - LogError("Could not find Vote Controller."); - return false; - } - - g_VoteController = EntIndexToEntRef(entity); - } - return true; -} - -TF2CSGO_CallVoteFail(client, NativeVotesCallFailType:reason, time=0) -{ - new Handle:callVoteFail = StartMessageOne("CallVoteFailed", client, USERMSG_RELIABLE); - - if(g_bUserBuf) - { - PbSetInt(callVoteFail, "reason", _:reason); - PbSetInt(callVoteFail, "time", time); - } - else - { - BfWriteByte(callVoteFail, _:reason); - BfWriteShort(callVoteFail, time); - } - EndMessage(); -} - diff --git a/addons/sourcemod/scripting/votediagnostics.sp b/addons/sourcemod/scripting/votediagnostics.sp deleted file mode 100644 index 78a88b9..0000000 --- a/addons/sourcemod/scripting/votediagnostics.sp +++ /dev/null @@ -1,790 +0,0 @@ -/** - * vim: set ts=4 : - * ============================================================================= - * Sniff L4D; L4D2; TF2; and CS:GO vote events, user messages, and commands - * - * NativeVotes (C) 2011-2014 Ross Bemrose (Powerlord). All rights reserved. - * ============================================================================= - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License, version 3.0, as published by the - * Free Software Foundation. - * - * This program 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 the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see <http://www.gnu.org/licenses/>. - * - * As a special exception, AlliedModders LLC gives you permission to link the - * code of this program (as well as its derivative works) to "Half-Life 2," the - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software - * by the Valve Corporation. You must obey the GNU General Public License in - * all respects for all other code used. Additionally, AlliedModders LLC grants - * this exception to all derivative works. AlliedModders LLC defines further - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), - * or <http://www.sourcemod.net/license.php>. - * - * Version: $Id$ - */ - -#pragma semicolon 1 - -#include <sourcemod> -#include <sdktools> - -#define LOGFILE "vote_diagnostics.txt" - -#pragma newdecls required - -EngineVersion g_EngineVersion = Engine_Unknown; - -int g_VoteController = -1; - -#define MAX_ARG_SIZE 65 - -#define DELAY 6.0 - -public Plugin myinfo = -{ - name = "L4D,L4D2,TF2,CS:GO Vote Sniffer", - author = "Powerlord", - description = "Sniff voting commands, events, and usermessages", - version = "1.2.5", - url = "http://www.sourcemod.net/" -} - -public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) -{ - if (!Game_IsGameSupported()) - { - strcopy(error, err_max, "Unsupported game"); - return APLRes_Failure; - } - - if (!late) - { - switch (g_EngineVersion) - { - case Engine_Left4Dead, Engine_Left4Dead2: - { - CreateTimer(30.0, L4DL4D2_LogControllerValues, _, TIMER_FLAG_NO_MAPCHANGE); - } - - case Engine_CSGO, Engine_TF2, Engine_SDK2013, Engine_Insurgency: - { - CreateTimer(30.0, TF2CSGO_LogControllerValues, _, TIMER_FLAG_NO_MAPCHANGE); - } - } - } - return APLRes_Success; -} - -bool CheckVoteController() -{ - int entity = -1; - if (g_VoteController != -1) - { - entity = EntRefToEntIndex(g_VoteController); - } - - if (entity == -1) - { - entity = FindEntityByClassname(-1, "vote_controller"); - if (entity == -1) - { - LogError("Could not find Vote Controller."); - return false; - } - - g_VoteController = EntIndexToEntRef(entity); - } - return true; -} - -bool Game_IsGameSupported() -{ - g_EngineVersion = GetEngineVersion(); - - switch (g_EngineVersion) - { - case Engine_Left4Dead, Engine_Left4Dead2, Engine_CSGO, Engine_TF2, Engine_SDK2013, Engine_Insurgency: - { - return true; - } - } - - return false; -} - -public void OnPluginStart() -{ - switch (g_EngineVersion) - { - case Engine_Left4Dead: - { - HookEventEx("vote_changed", L4DL4D2_EventVoteChanged); - HookEventEx("vote_ended", L4D_EventVoteEnded); - HookEventEx("vote_started", L4D_EventVoteStarted); - HookEventEx("vote_passed", L4D_EventVotePassed); - HookEventEx("vote_failed", L4D_EventVoteFailed); - HookEventEx("vote_cast_yes", L4D_EventVoteYes); - HookEventEx("vote_cast_no", L4D_EventVoteNo); - - HookUserMessage(GetUserMessageId("VoteRegistered"), L4DL4D2_MessageVoteRegistered); - HookUserMessage(GetUserMessageId("CallVoteFailed"), L4DL4D2_MessageCallVoteFailed); - } - - case Engine_Left4Dead2: - { - HookEventEx("vote_changed", L4DL4D2_EventVoteChanged); - - HookUserMessage(GetUserMessageId("VoteRegistered"), L4DL4D2_MessageVoteRegistered); - HookUserMessage(GetUserMessageId("VoteStart"), L4D2_MessageVoteStart); - HookUserMessage(GetUserMessageId("VotePass"), L4D2_MessageVotePass); - HookUserMessage(GetUserMessageId("VoteFail"), L4D2_MessageVoteFail); - HookUserMessage(GetUserMessageId("CallVoteFailed"), L4DL4D2_MessageCallVoteFailed); - } - - case Engine_CSGO, Engine_TF2, Engine_SDK2013, Engine_Insurgency: - { - HookEventEx("vote_cast", TF2CSGO_EventVoteCast); - HookEventEx("vote_options", TF2CSGO_EventVoteOptions); - - if (GetUserMessageType() == UM_Protobuf) - { - HookUserMessage(GetUserMessageId("VoteSetup"), CSGO_MessageVoteSetup); - HookUserMessage(GetUserMessageId("VoteStart"), CSGO_MessageVoteStart); - HookUserMessage(GetUserMessageId("VotePass"), CSGO_MessageVotePass); - HookUserMessage(GetUserMessageId("VoteFailed"), CSGO_MessageVoteFail); - HookUserMessage(GetUserMessageId("CallVoteFailed"), CSGO_MessageCallVoteFailed); - } - else - { - HookUserMessage(GetUserMessageId("VoteSetup"), TF2_MessageVoteSetup); - HookUserMessage(GetUserMessageId("VoteStart"), TF2_MessageVoteStart); - HookUserMessage(GetUserMessageId("VotePass"), TF2_MessageVotePass); - HookUserMessage(GetUserMessageId("VoteFailed"), TF2_MessageVoteFail); - HookUserMessage(GetUserMessageId("CallVoteFailed"), TF2_MessageCallVoteFailed); - } - } - } - - AddCommandListener(CommandVote, "vote"); - AddCommandListener(CommandCallVote, "callvote"); - - char gameName[64]; - GetGameFolderName(gameName, sizeof(gameName)); - - LogToFile(LOGFILE, "Game: %s", gameName); -} - -/* -"vote_changed" -{ - "yesVotes" "byte" - "noVotes" "byte" - "potentialVotes" "byte" -} -*/ -public void L4DL4D2_EventVoteChanged(Event event, const char[] name, bool dontBroadcast) -{ - int yesVotes = event.GetInt("yesVotes"); - int noVotes = event.GetInt("noVotes"); - int potentialVotes = event.GetInt("potentialVotes"); - LogMessage("Vote Changed event: yesVotes: %d, noVotes: %d, potentialVotes: %d", - yesVotes, noVotes, potentialVotes); - -} - -/* -VoteRegistered Structure - - Byte Choice voted for, 0 = No, 1 = Yes - -*/ -public Action L4DL4D2_MessageVoteRegistered(UserMsg msg_id, BfRead message, const int[] players, int playersNum, bool reliable, bool init) -{ - int choice = BfReadByte(message); - - LogToFile(LOGFILE, "VoteRegistered Usermessage: choice: %d", choice); - return Plugin_Continue; -} - -/* -CallVoteFailed - - Byte Failure reason code (1-2, 5-15) - - Short Time until new vote allowed for code 2 - -message CCSUsrMsg_CallVoteFailed -{ - optional int32 reason = 1; - optional int32 time = 2; -} -*/ -public Action L4DL4D2_MessageCallVoteFailed(UserMsg msg_id, BfRead message, const int[] players, int playersNum, bool reliable, bool init) -{ - int reason; - int time; - - reason = BfReadByte(message); - - LogToFile(LOGFILE, "CallVoteFailed Usermessage: reason: %d", reason, time); - return Plugin_Continue; -} - -/* - "vote_started" -{ - "issue" "string" - "param1" "string" - "team" "byte" - "initiator" "long" // entity id of the player who initiated the vote -} -*/ -public void L4D_EventVoteStarted(Event event, const char[] name, bool dontBroadcast) -{ - char issue[MAX_ARG_SIZE]; - char param1[MAX_ARG_SIZE]; - event.GetString("issue", issue, sizeof(issue)); - event.GetString("param1", param1, sizeof(param1)); - int team = event.GetInt("team"); - int initiator = event.GetInt("initiator"); - LogToFile(LOGFILE, "Vote Start Event: issue: \"%s\", param1: \"%s\", team: %d, initiator: %d", issue, param1, team, initiator); - - if (CheckVoteController()) - { - LogToFile(LOGFILE, "Active Index for issue %s: %d", issue, GetEntProp(g_VoteController, Prop_Send, "m_activeIssueIndex")); - } -} - -/* -"vote_ended" -{ -} -*/ -public void L4D_EventVoteEnded(Event event, const char[] name, bool dontBroadcast) -{ - LogToFile(LOGFILE, "Vote Ended Event"); - - CreateTimer(DELAY, L4DL4D2_LogControllerValues, _, TIMER_FLAG_NO_MAPCHANGE); -} - -/* -"vote_passed" -{ - "details" "string" - "param1" "string" - "team" "byte" -} -*/ -public void L4D_EventVotePassed(Event event, const char[] name, bool dontBroadcast) -{ - char details[MAX_ARG_SIZE]; - char param1[MAX_ARG_SIZE]; - event.GetString("details", details, sizeof(details)); - event.GetString("param1", param1, sizeof(param1)); - int team = event.GetInt("team"); - LogToFile(LOGFILE, "Vote Passed event: details: %s, param1: %s, team: %d", details, param1, team); -} - -/* -"vote_failed" -{ - "team" "byte" -} -*/ -public void L4D_EventVoteFailed(Event event, const char[] name, bool dontBroadcast) -{ - int team = event.GetInt("team"); - LogToFile(LOGFILE, "Vote Failed event: team: %d", team); -} - -/* -"vote_cast_yes" -{ - "team" "byte" - "entityid" "long" // entity id of the voter -} -*/ -public void L4D_EventVoteYes(Event event, const char[] name, bool dontBroadcast) -{ - int team = event.GetInt("team"); - int client = event.GetInt("entityid"); - LogToFile(LOGFILE, "Vote Cast Yes event: team: %d, client: %N", team, client); -} - -/* -"vote_cast_no" -{ - "team" "byte" - "entityid" "long" // entity id of the voter -} -*/ -public void L4D_EventVoteNo(Event event, const char[] name, bool dontBroadcast) -{ - int team = event.GetInt("team"); - int client = event.GetInt("entityid"); - LogToFile(LOGFILE, "Vote Cast No event: team: %d, client: %N", team, client); -} - -/* -VoteStart Structure - - Byte Team index or -1 for all - - Byte Initiator client index (or 99 for Server?) - - String Vote issue phrase - - String Vote issue phrase argument - - String Initiator name - -*/ -public Action L4D2_MessageVoteStart(UserMsg msg_id, BfRead message, const int[] players, int playersNum, bool reliable, bool init) -{ - char issue[MAX_ARG_SIZE]; - char param1[MAX_ARG_SIZE]; - char initiatorName[MAX_NAME_LENGTH]; - - int team = message.ReadByte(); - int initiator = message.ReadByte(); - - message.ReadString(issue, MAX_ARG_SIZE); - message.ReadString(param1, MAX_ARG_SIZE); - message.ReadString(initiatorName, MAX_NAME_LENGTH); - - LogToFile(LOGFILE, "VoteStart Usermessage: team: %d, initiator: %d, issue: %s, param1: %s, player count: %d, initiatorName: %s", team, initiator, issue, param1, playersNum, initiatorName); - if (CheckVoteController()) - { - LogToFile(LOGFILE, "Active Index for issue %s: %d", issue, GetEntProp(g_VoteController, Prop_Send, "m_activeIssueIndex")); - } - - return Plugin_Continue; -} - -/* -VotePass Structure - - Byte Team index or -1 for all - - String Vote issue pass phrase - - String Vote issue pass phrase argument - -*/ -public Action L4D2_MessageVotePass(UserMsg msg_id, BfRead message, const int[] players, int playersNum, bool reliable, bool init) -{ - char issue[MAX_ARG_SIZE]; - char param1[MAX_ARG_SIZE]; - int team = message.ReadByte(); - - message.ReadString(issue, MAX_ARG_SIZE); - message.ReadString(param1, MAX_ARG_SIZE); - - LogToFile(LOGFILE, "VotePass Usermessage: team: %d, issue: %s, param1: %s", team, issue, param1); - - CreateTimer(DELAY, L4DL4D2_LogControllerValues, _, TIMER_FLAG_NO_MAPCHANGE); - return Plugin_Continue; -} - -/* -VoteFail Structure - - Byte Team index or -1 for all - -*/ -public Action L4D2_MessageVoteFail(UserMsg msg_id, BfRead message, const int[] players, int playersNum, bool reliable, bool init) -{ - int team = message.ReadByte(); - - LogToFile(LOGFILE, "VoteFail Usermessage: team: %d", team); - - CreateTimer(DELAY, L4DL4D2_LogControllerValues, _, TIMER_FLAG_NO_MAPCHANGE); - - return Plugin_Continue; -} - -/* -"vote_cast" -{ - "vote_option" "byte" // which option the player voted on - "team" "short" - "entityid" "long" // entity id of the voter -} -*/ -public void TF2CSGO_EventVoteCast(Event event, const char[] name, bool dontBroadcast) -{ - int vote_option = event.GetInt("vote_option"); - int team = event.GetInt("team"); - int entityid = event.GetInt("entityid"); - LogToFile(LOGFILE, "Vote Cast event: vote_option: %d, team: %d, client: %N", vote_option, team, entityid); -} - -/* -"vote_options" -{ - "count" "byte" // Number of options - up to MAX_VOTE_OPTIONS - "option1" "string" - "option2" "string" - "option3" "string" - "option4" "string" - "option5" "string" -} -*/ -public void TF2CSGO_EventVoteOptions(Event event, const char[] name, bool dontBroadcast) -{ - char option1[MAX_ARG_SIZE]; - char option2[MAX_ARG_SIZE]; - char option3[MAX_ARG_SIZE]; - char option4[MAX_ARG_SIZE]; - char option5[MAX_ARG_SIZE]; - - int count = event.GetInt("count"); - event.GetString("option1", option1, sizeof(option1)); - event.GetString("option2", option2, sizeof(option2)); - event.GetString("option3", option3, sizeof(option3)); - event.GetString("option4", option4, sizeof(option4)); - event.GetString("option5", option5, sizeof(option5)); - LogToFile(LOGFILE, "Vote Options event: count: %d, option1: %s, option2: %s, option3: %s, option4: %s, option5: %s", - count, option1, option2, option3, option4, option5); -} - -/* -message CCSUsrMsg_VoteSetup -{ - repeated string potential_issues = 1; -} -*/ -public Action CSGO_MessageVoteSetup(UserMsg msg_id, Protobuf message, const int[] players, int playersNum, bool reliable, bool init) -{ - char options[2049]; - int count = message.GetRepeatedFieldCount("potential_issues"); - for(int i = 0; i < count; i++) - { - char option[MAX_ARG_SIZE]; - message.ReadString("potential_issues", option, MAX_ARG_SIZE, i); - StrCat(options, sizeof(options), option); - StrCat(options, sizeof(options), " "); - } - - LogToFile(LOGFILE, "VoteSetup Usermessage: count: %d, options: %s", count, options); - - return Plugin_Continue; -} - -/* -VoteSetup - - Byte Option count - * issue multiple issues matching the number in the first byte - -issue - - String Vote name - - String Vote translation string - - Byte Whether a vote is enabled or not. -*/ -public Action TF2_MessageVoteSetup(UserMsg msg_id, BfRead message, const int[] players, int playersNum, bool reliable, bool init) -{ - char options[2049]; - int count = message.ReadByte(); - for (int i = 0; i < count; i++) - { - char option[MAX_ARG_SIZE*2+1]; - char potential_issue[MAX_ARG_SIZE]; - char translation[MAX_ARG_SIZE]; - message.ReadString(potential_issue, MAX_ARG_SIZE); - message.ReadString(translation, MAX_ARG_SIZE); - - int enabled = message.ReadByte(); - Format(option, sizeof(option), "(%s, %s, %d) ", potential_issue, translation, enabled); - - StrCat(options, sizeof(options), option); - } - - LogToFile(LOGFILE, "VoteSetup Usermessage: count: %d, options: %s", count, options); - - return Plugin_Continue; -} - -/* -message CCSUsrMsg_VoteStart -{ - optional int32 team = 1; - optional int32 ent_idx = 2; - optional int32 vote_type = 3; - optional string disp_str = 4; - optional string details_str = 5; - optional string other_team_str = 6; - optional bool is_yes_no_vote = 7; - -} -*/ -public Action CSGO_MessageVoteStart(UserMsg msg_id, Protobuf message, const int[] players, int playersNum, bool reliable, bool init) -{ - char issue[MAX_ARG_SIZE]; - char otherTeamIssue[MAX_ARG_SIZE]; - char param1[MAX_ARG_SIZE]; - int team; - int initiator; - bool yesNo; - int voteType; - - team = message.ReadInt("team"); - initiator = message.ReadInt("ent_idx"); - message.ReadString("disp_str", issue, MAX_ARG_SIZE); - message.ReadString("details_str", param1, MAX_ARG_SIZE); - yesNo = message.ReadBool("is_yes_no_vote"); - message.ReadString("other_team_str", otherTeamIssue, MAX_ARG_SIZE); - voteType = message.ReadInt("vote_type"); - - LogToFile(LOGFILE, "VoteStart Usermessage: team: %d, initiator: %d, issue: %s, otherTeamIssue: %s, param1: %s, yesNo: %d, player count: %d, voteType: %d", team, initiator, issue, otherTeamIssue, param1, yesNo, playersNum, voteType); - - CreateTimer(0.0, TF2CSGO_LogControllerValues, _, TIMER_FLAG_NO_MAPCHANGE); - - return Plugin_Continue; -} - -/* -VoteStart Structure - - Byte Team index or -1 for all - - Byte Initiator client index or 99 for Server - - String Vote issue phrase - - String Vote issue phrase argument - - Bool false for Yes/No, true for Multiple choice -*/ -public Action TF2_MessageVoteStart(UserMsg msg_id, BfRead message, const int[] players, int playersNum, bool reliable, bool init) -{ - char issue[MAX_ARG_SIZE]; - char param1[MAX_ARG_SIZE]; - int team; - int initiator; - bool yesNo; - - team = message.ReadByte(); - initiator = message.ReadByte(); - message.ReadString(issue, MAX_ARG_SIZE); - message.ReadString(param1, MAX_ARG_SIZE); - yesNo = message.ReadBool(); - - LogToFile(LOGFILE, "VoteStart Usermessage: team: %d, initiator: %d, issue: %s, param1: %s, yesNo: %d, player count: %d", team, initiator, issue, param1, yesNo, playersNum); - - CreateTimer(0.0, TF2CSGO_LogControllerValues, _, TIMER_FLAG_NO_MAPCHANGE); - - return Plugin_Continue; -} - -/* -message CCSUsrMsg_VotePass -{ - optional int32 team = 1; - optional int32 vote_type = 2; - optional string disp_str= 3; - optional string details_str = 4; -} -*/ -public Action CSGO_MessageVotePass(UserMsg msg_id, Protobuf message, const int[] players, int playersNum, bool reliable, bool init) -{ - char issue[MAX_ARG_SIZE]; - char param1[MAX_ARG_SIZE]; - int team; - int voteType; - - team = message.ReadInt("team"); - message.ReadString("disp_str", issue, MAX_ARG_SIZE); - message.ReadString("details_str", param1, MAX_ARG_SIZE); - voteType = message.ReadInt("vote_type"); - - LogToFile(LOGFILE, "VotePass Usermessage: team: %d, issue: %s, param1: %s, voteType: %d", team, issue, param1, voteType); - - CreateTimer(DELAY, TF2CSGO_LogControllerValues, _, TIMER_FLAG_NO_MAPCHANGE); - - return Plugin_Continue; -} - -/* -VotePass Structure - - Byte Team index or -1 for all - - String Vote issue pass phrase - - String Vote issue pass phrase argument -*/ -public Action TF2_MessageVotePass(UserMsg msg_id, BfRead message, const int[] players, int playersNum, bool reliable, bool init) -{ - char issue[MAX_ARG_SIZE]; - char param1[MAX_ARG_SIZE]; - int team; - - team = message.ReadByte(); - message.ReadString(issue, MAX_ARG_SIZE); - message.ReadString(param1, MAX_ARG_SIZE); - - LogToFile(LOGFILE, "VotePass Usermessage: team: %d, issue: %s, param1: %s", team, issue, param1); - - CreateTimer(DELAY, TF2CSGO_LogControllerValues, _, TIMER_FLAG_NO_MAPCHANGE); - - return Plugin_Continue; -} - -/* -message CCSUsrMsg_VoteFailed -{ - optional int32 team = 1; - optional int32 reason = 2; -} -*/ -public Action CSGO_MessageVoteFail(UserMsg msg_id, Protobuf message, const int[] players, int playersNum, bool reliable, bool init) -{ - int team = message.ReadInt("team"); - int reason = message.ReadInt("reason"); - - LogToFile(LOGFILE, "VoteFail Usermessage: team: %d, reason: %d", team, reason); - - CreateTimer(DELAY, TF2CSGO_LogControllerValues, _, TIMER_FLAG_NO_MAPCHANGE); - - return Plugin_Continue; -} - -/* -VoteFailed Structure - - Byte Team index or -1 for all - - Byte Failure reason code (0, 3-4) -*/ -public Action TF2_MessageVoteFail(UserMsg msg_id, BfRead message, const int[] players, int playersNum, bool reliable, bool init) -{ - int team = message.ReadByte(); - int reason = message.ReadByte(); - - LogToFile(LOGFILE, "VoteFail Usermessage: team: %d, reason: %d", team, reason); - - CreateTimer(DELAY, TF2CSGO_LogControllerValues, _, TIMER_FLAG_NO_MAPCHANGE); - - return Plugin_Continue; -} - -public Action TF2CSGO_LogControllerValues(Handle timer) -{ - if (!CheckVoteController()) - { - return Plugin_Continue; - } - - int team = GetEntProp(g_VoteController, Prop_Send, "m_iOnlyTeamToVote"); - int activeIssue = GetEntProp(g_VoteController, Prop_Send, "m_iActiveIssueIndex"); - int potentialVotes = GetEntProp(g_VoteController, Prop_Send, "m_nPotentialVotes"); - bool isYesNo = view_as<bool>(GetEntProp(g_VoteController, Prop_Send, "m_bIsYesNoVote")); - int voteCounts[5]; - for (int i = 0; i < 5; ++i) - { - voteCounts[i] = GetEntProp(g_VoteController, Prop_Send, "m_nVoteOptionCount", _, i); - } - - LogToFile(LOGFILE, "Vote Controller, issue: %d, team: %d, potentialVotes: %d, yesNo: %d, count1: %d, count2: %d, count3: %d, count4: %d, count5: %d", - activeIssue, team, potentialVotes, isYesNo, voteCounts[0], voteCounts[1], voteCounts[2], voteCounts[3], voteCounts[4]); - return Plugin_Continue; -} - -public Action L4DL4D2_LogControllerValues(Handle timer) -{ - if (!CheckVoteController()) - { - return Plugin_Continue; - } - - int team = GetEntProp(g_VoteController, Prop_Send, "m_onlyTeamToVote"); - int activeIssue = GetEntProp(g_VoteController, Prop_Send, "m_activeIssueIndex"); - int potentialVotes = GetEntProp(g_VoteController, Prop_Send, "potentialVotes"); - int voteCounts[2]; - voteCounts[0] = GetEntProp(g_VoteController, Prop_Send, "m_votesYes"); - voteCounts[1] = GetEntProp(g_VoteController, Prop_Send, "m_votesNo"); - - LogToFile(LOGFILE, "Vote Controller, issue: %d, team: %d, potentialVotes: %d, countYes: %d, countNo: %d", - activeIssue, team, potentialVotes, voteCounts[0], voteCounts[1]); - return Plugin_Continue; -} - -/* -message CCSUsrMsg_CallVoteFailed -{ - optional int32 reason = 1; - optional int32 time = 2; -} -*/ -public Action CSGO_MessageCallVoteFailed(UserMsg msg_id, Protobuf message, const int[] players, int playersNum, bool reliable, bool init) -{ - int reason = message.ReadInt("reason"); - int time = message.ReadInt("time"); - - LogToFile(LOGFILE, "CallVoteFailed Usermessage: reason: %d, time: %d", reason, time); - return Plugin_Continue; -} - -/* -CallVoteFailed - - Byte Failure reason code (1-2, 5-15) - - Short Time until new vote allowed for code 2 -*/ -public Action TF2_MessageCallVoteFailed(UserMsg msg_id, BfRead message, const int[] players, int playersNum, bool reliable, bool init) -{ - int reason = message.ReadByte(); - int time = message.ReadShort(); - - LogToFile(LOGFILE, "CallVoteFailed Usermessage: reason: %d, time: %d", reason, time); - return Plugin_Continue; -} - -/* - This is likely a client-side event as it never produced any values. - - "endmatch_mapvote_selecting_map" - { - "count" "byte" // Number of "ties" - "slot1" "byte" - "slot2" "byte" - "slot3" "byte" - "slot4" "byte" - "slot5" "byte" - "slot6" "byte" - "slot7" "byte" - "slot8" "byte" - "slot9" "byte" - "slot10" "byte" - } -*/ -/* -public void CSGO_EventMapVote(Event event, const char[] name, bool dontBroadcast) -{ - int count = event.GetInt("count"); - int slot1 = event.GetInt("slot1"); - int slot2 = event.GetInt("slot2"); - int slot3 = event.GetInt("slot3"); - int slot4 = event.GetInt("slot4"); - int slot5 = event.GetInt("slot5"); - int slot6 = event.GetInt("slot6"); - int slot7 = event.GetInt("slot7"); - int slot8 = event.GetInt("slot8"); - int slot9 = event.GetInt("slot9"); - int slot10 = event.GetInt("slot10"); - - LogToFile(LOGFILE, "Endmatch_Mapvote_SelectingMap event: count: %d, slot1: %d, slot2: %d, slot3: %d, slot4: %d, slot5: %d, slot6: %d, slot7: %d, slot8: %d, slot9: %d, slot10: %d ", - count, slot1, slot2, slot3, slot4, slot5, slot6, slot7, slot8, slot9, slot10); -} -*/ - -/* -Vote command - - String option1 through option5 (for TF2/CS:GO); Yes or No (for L4D/L4D2) - */ -public Action CommandVote(int client, const char[] command, int argc) -{ - char vote[MAX_ARG_SIZE]; - GetCmdArg(1, vote, sizeof(vote)); - - LogToFile(LOGFILE, "%N used vote command: %s %s", client, command, vote); - return Plugin_Continue; -} - -/* -callvote command - - String Vote type (Valid types are sent in the VoteSetup message) - - String target (or type - target for Kick) -*/ -public Action CommandCallVote(int client, const char[] command, int argc) -{ - char args[255]; - GetCmdArgString(args, sizeof(args)); - - LogToFile(LOGFILE, "callvote command: client: %N, command: %s", client, args); - return Plugin_Continue; -} diff --git a/addons/sourcemod/scripting/votefailed.sp b/addons/sourcemod/scripting/votefailed.sp deleted file mode 100644 index c684825..0000000 --- a/addons/sourcemod/scripting/votefailed.sp +++ /dev/null @@ -1,151 +0,0 @@ -/** - * vim: set ts=4 : - * ============================================================================= - * Vote Failed - * Send Vote Failed commands for TF2 and CS:GO - * Used to test the VoteFailed and CallVoteFailed usermessages - * - * Vote Failed (C)2014 Powerlord (Ross Bemrose). All rights reserved. - * ============================================================================= - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License, version 3.0, as published by the - * Free Software Foundation. - * - * This program 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 the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see <http://www.gnu.org/licenses/>. - * - * As a special exception, AlliedModders LLC gives you permission to link the - * code of this program (as well as its derivative works) to "Half-Life 2," the - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software - * by the Valve Corporation. You must obey the GNU General Public License in - * all respects for all other code used. Additionally, AlliedModders LLC grants - * this exception to all derivative works. AlliedModders LLC defines further - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), - * or <http://www.sourcemod.net/license.php>. - * - * Version: $Id$ - */ -#include <sourcemod> -#pragma semicolon 1 -#pragma newdecls required - -#define VERSION "1.0.0" - -bool g_bUserBuf; - -ConVar g_Cvar_Enabled; - -public Plugin myinfo = { - name = "Vote Failed / Call Vote Failed displayer", - author = "Powerlord", - description = "Used to display specific vote failed and call vote failed messages to a user", - version = VERSION, - url = "https://forums.alliedmods.net/showthread.php?t=208008" -}; - -public void OnPluginStart() -{ - LoadTranslations("common.phrases"); - - CreateConVar("votefailed_version", VERSION, "Vote Failed version", FCVAR_NOTIFY|FCVAR_DONTRECORD|FCVAR_SPONLY); - g_Cvar_Enabled = CreateConVar("votefailed_enable", "1", "Enable Vote Failed?", FCVAR_NOTIFY|FCVAR_DONTRECORD, true, 0.0, true, 1.0); - g_bUserBuf = (GetUserMessageType() == UM_Protobuf); - - RegAdminCmd("votefail", Cmd_VoteFailed, ADMFLAG_GENERIC, "Show Call Vote Fail dialog to user"); - RegAdminCmd("callvotefail", Cmd_CallVoteFailed, ADMFLAG_GENERIC, "Show Call Vote Fail dialog to user"); -} - -public Action Cmd_VoteFailed(int client, int args) -{ - if (!g_Cvar_Enabled.BoolValue) - { - return Plugin_Continue; - } - - if (client == 0) - { - ReplyToCommand(client, "%t", "Command is in-game only"); - return Plugin_Handled; - } - - if (args == 0) - { - ReplyToCommand(client, "Requires at least one argument"); - return Plugin_Handled; - } - - char sReason[5]; - GetCmdArg(1, sReason, sizeof(sReason)); - - int reason = StringToInt(sReason); - - Handle voteFailed = StartMessageOne("VoteFailed", client, USERMSG_RELIABLE); - - if(g_bUserBuf) - { - PbSetInt(voteFailed, "team", 0); - PbSetInt(voteFailed, "reason", reason); - } - else - { - BfWriteByte(voteFailed, 0); - BfWriteByte(voteFailed, reason); - } - EndMessage(); - - return Plugin_Handled; -} - -public Action Cmd_CallVoteFailed(int client, int args) -{ - if (!g_Cvar_Enabled.BoolValue) - { - return Plugin_Continue; - } - - if (client == 0) - { - ReplyToCommand(client, "%t", "Command is in-game only"); - return Plugin_Handled; - } - - if (args == 0) - { - ReplyToCommand(client, "Requires at least one argument"); - return Plugin_Handled; - } - - char sReason[5]; - GetCmdArg(1, sReason, sizeof(sReason)); - - int reason = StringToInt(sReason); - int time = 0; - - if (args > 1) - { - char sTime[10]; - GetCmdArg(2, sTime, sizeof(sTime)); - time = StringToInt(sTime); - } - - Handle callVoteFail = StartMessageOne("CallVoteFailed", client, USERMSG_RELIABLE); - if(g_bUserBuf) - { - PbSetInt(callVoteFail, "reason", reason); - PbSetInt(callVoteFail, "time", time); - } - else - { - BfWriteByte(callVoteFail, reason); - BfWriteShort(callVoteFail, time); - } - EndMessage(); - - return Plugin_Handled; -} diff --git a/addons/sourcemod/translations/fr/nativevotes.phrases.txt b/addons/sourcemod/translations/fr/nativevotes.phrases.txt new file mode 100644 index 0000000..e74e73a --- /dev/null +++ b/addons/sourcemod/translations/fr/nativevotes.phrases.txt @@ -0,0 +1,12 @@ +"Phrases" +{ + "NativeVotes Revote" + { + "fr" "Ré-ouverture du vote..." + } + + "NativeVotes Revote No Time" + { + "fr" "Temps insuffisant pour voter à nouveau" + } +} \ No newline at end of file diff --git a/addons/sourcemod/translations/votescramble.phrases.txt b/addons/sourcemod/translations/votescramble.phrases.txt new file mode 100644 index 0000000..4c38d7e --- /dev/null +++ b/addons/sourcemod/translations/votescramble.phrases.txt @@ -0,0 +1,39 @@ +"Phrases" +{ + "Scramble Teams" + { + "en" "Scramble Teams:" + } + + "Scramble Not Allowed" + { + "en" "Scramble is not allowed yet." + } + + "Already Voted" + { + "#format" "{1:d},{2:d}" + "en" "You have already voted to scramble teams. ({1} votes, {2} required)" + } + + "Scramble Requested" + { + "#format" "{1:s},{2:d},{3:d}" + "en" "{1} wants to scramble teams. ({2} votes, {3} required)" + } + + "Scramble Vote Ready" + { + "en" "Scrambling Teams!" + } + + "No Votes" + { + "en" "No votes received for Scramble Teams, keeping current team compositions." + } + + "Scrambling Teams" + { + "en" "Scrambling Teams! Vote has spoken!" + } +} \ No newline at end of file