Instead of using this extension, help us test NativeVotes!
Any servers running SourceMod 1.4 or 1.5 hg3754 or below need version 0.5.7. Linux TF2 servers running 3754 or below will need the .so file from this post.
I'm seeing an error "[BV] Unknown fatal error while translating a phrase."
These errors are related to the vote progress cvars. One of the SourceMod Core translation phrases has the wrong number of arguments. You can find out which phrase and which language number it's for using a Debug version of the extension. You can then get the language name and code using the plugin in this post.
Please, post the error in this thread and we'll see if we can get the translation phrase fixed in the next SourceMod version.
Currently known translation issues (1.4.1):
Dutch (nl): "Voted For", fix expected in 1.4.2
Previous known translation issues (if you see these errors, update your translation files):
Swedish (sv) : "Vote Count", fixed in 1.4.1
Danish (da): "Vote Count", fixed in 1.4.0
I'm getting a FAILED error for your extension, but I'm not running L4D/L4D2/TF2 or am not [interested in] running your extension!
Assuming you mean this error:
Code:
[4] <FAILED> file "builtinvotes.ext.dll": The specified module could not be found.
This is a known SourceMod issue. Any extension or plugin that you're not running that is listed as an optional dependency for a plugin will show this error.
You'll run into this error if you're using MapChooser Extended 1.8 or newer.
For End Users:
What is BuiltinVotes?
BuiltinVotes is a SourceMod 1.4 Extension that lets plugins use the L4D/L4D2/TF2 built-in vote screens.
Why use it?
The SourceMod vote system uses the 0-9 keys to register votes, which prevent weapon changes while the vote is displayed. This system uses F1-F2 for Yes/No votes and F1-F5 for multiple choice votes (TF2 only).
Better yet, most non-custom votes have their vote message, success message, and failure message translated by Valve for all languages the games support.
What are the Downsides?
/revote and !revote display a failure message and have a 3 second delay before redisplaying the vote. The technical reason for this is explained in the Plugin Developers section.
L4D and L4D2 are limited to Yes/No votes only. TF2 votes are limited to 5 choices.
For votes with more than these, you still need to use the SourceMod voting system.
Settings
BuiltinVotes hooks five SourceMod vote-related cvars for displaying ongoing votes:
sm_vote_delay
sm_vote_progress_hintbox
sm_vote_progress_chat
sm_vote_progress_console
sm_vote_progress_client_console
These are configured in cfg/sourcemod/sourcemod.cfg and are also used by the SourceMod vote system.
The rest of this documentation is for developers, so you probably want to skip down to the builtinvotes.zip link.
For Plugin Developers:
Spoiler
What is BuiltinVotes?
BuiltinVotes is a SourceMod 1.4 Extension that implements a voting API (modeled after the SourceMod Menu API) that impersonates the L4D, L4D2, or TF2 vote controller. It can be used to call votes of every type these games support, plus a custom vote.
Why use it?
The SourceMod vote system uses the 0-9 keys to register votes. This system uses F1-F2 for Yes/No votes and F1-F5 for multiple choice votes (TF2 only).
Standard vote types have their translations done by the game itself. This means you don't have to write translations for kick votes, or map changes votes, or any number of other votes (detailed below).
Supported vote translations:
L4D
Change Campaign
Return to Lobby
Change Difficulty (needs more work as to what the arguments for this are)
Custom Yes/No
Kick
Restart
Change Level [Level number]
L4D2
Change Campaign
Return to Lobby
Change Difficulty (needs more work as to what the arguments for this are)
Custom Yes/No
Kick
Restart
Change Level [Level number] (can someone verify this one?)
Toggle Alltalk (new in version 0.5.0)
TF2
Kick (Generic)
Restart
Change Level [map name]
Custom Yes/No
Kick Idle
Kick Scamming
Kick Cheating
NextLevel Yes/No
NextLevel Multiple Choice
Scramble Teams Now
Scramble Teams at Round End
Custom Multiple Choice
What are the Downsides?
/revote and !revote show the vote failure screen and have a 3 second delay. This is a client-side limitation due to a required Vote Failed screen between votes. Once sent, the Failure screen takes just over 2 seconds before displaying. Therefore, we wait 3 seconds before redisplaying the vote screen.
Paginated menus are not supported. It was deemed that having Prev, Next, and Exit/Back wouldn't work well with only 5 option slots on TF2.
There is a display bug when revoting. The numbers on the hintbox are sometimes wrong when revotes are done, even when the numbers used to tally the results are correct. Strangely, the code for this is taken straight from SourceMod... For example, I saw a vote that said there were 2/2 votes for cp_degrootkeep, even though the first client typed /revote before the second client voted. When the vote timer ended, the winning map won with 1 vote (the first client never revoted). [This may be fixed now in 0.5.6]
There are still occasional translation errors. A new Debug build was released on 2012-03-06 to help track these down.
More info
Much of the code is taken from SourceMod itself. Adaptations have been made to make it work as a standalone extension rather than part of SourceMod.
This is very much a work in progress. It is based heavily on SourceMod's voting system, but built to use the TF2 built-in Voting system that I reverse-engineered with the help of Afronanny and Mr. Zero. L4D and L4D2 voting have also been implemented based on available information.
Data Structures:
PHP Code:
#define BUILTINVOTES_EXTEND "Extend current Map" /** Defined in TF2, but doesn't appear to be localized */
#define BUILTINVOTES_ALL_TEAMS -1 // Defined by TF2, may be the same in L4D/L4D2 #define BUILTINVOTES_SERVER_INDEX 99 // Defined by TF2, may be the same in L4D/L4D2
/** * Reasons a vote can end. */ enum { BuiltinVoteEnd_Done = -1, /**< Voting finished. Corresponds to MenuEnd_VotingDone. */ BuiltinVoteEnd_Cancelled = -2, /**< Voting was cancelled. Corresponds to MenuEnd_VotingCancelled */ }
/** * Vote types. These are mapped to translation strings and pass strings by VoteStart and VotePass handlers * BuiltinVotes does *not* do anything with this other than control the start/pass translations... your plugin is expected to apply any * logic needed to process the vote results. */ enum BuiltinVoteType { BuiltinVoteType_ChgCampaign = 0, /**< L4D/L4D2: Yes/No, argument is campaign name */ BuiltinVoteType_ReturnToLobby = 1, /**< L4D/L4D2: Yes/No, argument ignored */ BuiltinVoteType_ChgDifficulty = 2, /**< L4D/L4D2: Yes/No, argument is difficulty level */ BuiltinVoteType_Custom_YesNo = 3, /**< Yes/No, argument is vote text. */ BuiltinVoteType_Kick = 4, /**< Yes/No, argument is player userid */ BuiltinVoteType_Restart = 5, /**< Yes/No, argument ignored */ BuiltinVoteType_ChangeLevel = 6, /**< Yes/No, argument is level number in L4D/L4D2 or map name in TF2 */ BuiltinVoteType_KickIdle = 7, /**< TF2: Yes/No, argument is player userid */ BuiltinVoteType_KickScamming = 8, /**< TF2: Yes/No, argument is player userid */ BuiltinVoteType_KickCheating = 9, /**< TF2: Yes/No, argument is player userid */ BuiltinVoteType_NextLevel = 10, /**< TF2: Yes/No, argument is map name */ BuiltinVoteType_NextLevelMult = 11, /**< TF2: Multiple-choice, argument ignored */ BuiltinVoteType_ScrambleNow = 12, /**< TF2: Yes/No, argument ignored */ BuiltinVoteType_ScrambleEnd = 13, /**< TF2: Yes/No, argument ignored */ BuiltinVoteType_Custom_Mult = 14, /**< TF2: Multiple-choice, argument is vote text. */ }
/** * Different actions for the vote "pump" callback */ enum BuiltinVoteAction { BuiltinVoteAction_Start = (1<<0), /**< A vote display/sequence has started */ BuiltinVoteAction_End = (1<<1), /**< A vote display has fully ended. param1 is the BuiltinVoteEnd reason */ BuiltinVoteAction_Select = (1<<2), /**< An item was selected (param1=client, param2=item) */ BuiltinVoteAction_Cancel = (1<<3), /**< A vote sequence has been cancelled (nothing passed) */ BuiltinVoteAction_VoteEnd = (1<<4), /**< A vote sequence has ended (param1=chosen item). */ }
/** Interchangeable with their Menu counterparts */ #define BUILTINVOTEINFO_CLIENT_INDEX 0 /**< Client index */ #define BUILTINVOTEINFO_CLIENT_ITEM 1 /**< Item the client selected, or -1 for none */ #define BUILTINVOTEINFO_ITEM_INDEX 0 /**< Item index */ #define BUILTINVOTEINFO_ITEM_VOTES 1 /**< Number of votes for the item */
#define BUILTINVOTES_VOTE_NO 0 /**< Vote was no */ #define BUILTINVOTES_VOTE_YES 1 /**< Vote was yes */
/** * Reasons a vote was canceled. Not used for L4D/L4D2, as they don't care */ enum BuiltinVoteFailReason { BuiltinVoteFail_Generic = 0, /**< Vote was generically cancelled. */ BuiltinVoteFail_Loses = 3, /**< No votes outnumbered Yes votes */ BuiltinVoteFail_NotEnoughVotes = 4, /**< Vote did not receive enough votes. */ }
// User vote to kick user. #define TRANSLATION_L4D_VOTE_KICK_START "#L4D_vote_kick_player" #define TRANSLATION_L4D_VOTE_KICK_PASSED "#L4D_vote_passed_kick_player"
// User vote to restart map. #define TRANSLATION_L4D_VOTE_RESTART_START "#L4D_vote_restart_game" #define TRANSLATION_L4D_VOTE_RESTART_PASSED "#L4D_vote_passed_restart_game"
// User vote to change maps. #define TRANSLATION_L4D_VOTE_CHANGECAMPAIGN_START "#L4D_vote_mission_change" #define TRANSLATION_L4D_VOTE_CHANGECAMPAIGN_PASSED "#L4D_vote_passed_mission_change" #define TRANSLATION_L4D_VOTE_CHANGELEVEL_START "#L4D_vote_chapter_change" #define TRANSLATION_L4D_VOTE_CHANGELEVEL_PASSED "#L4D_vote_passed_chapter_change"
// User vote to return to lobby. #define TRANSLATION_L4D_VOTE_RETURNTOLOBBY_START "#L4D_vote_return_to_lobby" #define TRANSLATION_L4D_VOTE_RETURNTOLOBBY_PASSED "#L4D_vote_passed_return_to_lobby"
// User vote to change difficulty. #define TRANSLATION_L4D_VOTE_CHANGEDIFFICULTY_START "#L4D_vote_change_difficulty" #define TRANSLATION_L4D_VOTE_CHANGEDIFFICULTY_PASSED "#L4D_vote_passed_change_difficulty"
// User vote to change alltalk. #define TRANSLATION_L4D_VOTE_ALLTALK_START "#L4D_vote_alltalk_change" #define TRANSLATION_L4D_VOTE_ALLTALK_PASSED "#L4D_vote_passed_alltalk_change" #define TRANSLATION_L4D_VOTE_ALLTALK_ENABLE "#L4D_vote_alltalk_enable" #define TRANSLATION_L4D_VOTE_ALLTALK_DISABLE "#L4D_vote_alltalk_disable"
// While not a vote string, it works just as well. #define TRANSLATION_L4D_VOTE_CUSTOM "#L4D_TargetID_Player"
// User vote to restart map. #define TRANSLATION_TF2_VOTE_RESTART_START "#TF_vote_restart_game" #define TRANSLATION_TF2_VOTE_RESTART_PASSED "#TF_vote_passed_restart_game"
// User vote to change maps. #define TRANSLATION_TF2_VOTE_CHANGELEVEL_START "#TF_vote_changelevel" #define TRANSLATION_TF2_VOTE_CHANGELEVEL_PASSED "#TF_vote_passed_changelevel"
// User vote to change next level. #define TRANSLATION_TF2_VOTE_NEXTLEVEL_SINGLE_START "#TF_vote_nextlevel" #define TRANSLATION_TF2_VOTE_NEXTLEVEL_MULTIPLE_START "#TF_vote_nextlevel_choices" // Started by server #define TRANSLATION_TF2_VOTE_NEXTLEVEL_EXTEND_PASSED "#TF_vote_passed_nextlevel_extend" #define TRANSLATION_TF2_VOTE_NEXTLEVEL_PASSED "#TF_vote_passed_nextlevel"
// User vote to scramble teams. Can be immediate or end of round. #define TRANSLATION_TF2_VOTE_SCRAMBLE_IMMEDIATE_START "#TF_vote_scramble_teams" #define TRANSLATION_TF2_VOTE_SCRAMBLE_ROUNDEND_START "#TF_vote_should_scramble_round" #define TRANSLATION_TF2_VOTE_SCRAMBLE_PASSED "#TF_vote_passed_scramble_teams"
// While not a vote string, it works just as well. #define TRANSLATION_TF2_VOTE_CUSTOM "#TF_playerid_noteam"
Callbacks:
PHP Code:
/** * Called when a vote action is completed. * Based on MenuHandler * * @param vote The vote being acted upon. * @param action The action of the vote. * @param param1 First action parameter (usually the client). * @param param2 Second action parameter (usually the item). * @noreturn */ functag public BuiltinVoteActionHandler(Handle:vote, BuiltinVoteAction:action, param1, param2);
/** * Callback for when a vote has ended and results are available. * * Identical to (and thus interchangeable with) VoteHandler * * @param vote The vote being voted on. * @param num_votes Number of votes tallied in total. * @param num_clients Number of clients who could vote. * @param client_info Array of clients. Use VOTEINFO_CLIENT_ defines. * @param num_items Number of unique items that were selected. * @param item_info Array of items, sorted by count. Use VOTEINFO_ITEM * defines. * @noreturn */ functag public BuiltinVoteHandler(Handle:vote, num_votes, num_clients, const client_info[][2], num_items, const item_info[][2]);
Functions/Natives:
PHP Code:
/** * Creates a new, empty vote. * * @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. Start, * Cancel, and End will always be received regardless * of whether they are set or not. They are also * the only default actions. * @return A new vote Handle. */ native Handle:CreateBuiltinVote(BuiltinVoteActionHandler:handler, BuiltinVoteType:voteType, BuiltinVoteAction:actions=BUILTINVOTE_ACTIONS_DEFAULT);
/** * Broadcasts a vote to a list of clients. The most selected item will be * returned through BuiltinVoteAction_End. On a tie, a random item will be returned * from a list of the tied items. * * Note that BuiltinVoteAction_End and BuiltinVoteAction_Start are both * default callbacks and do not need to be enabled. * * @param vote Vote Handle. * @param clients Array of clients to broadcast to. * @param numClients Number of clients in the array. * @param time Maximum time to leave menu on the screen. * @return True on success, false if a vote is already in progress. * @error Invalid Handle, or a vote is already in progress. */ native bool:DisplayBuiltinVote(Handle:vote, clients[], numClients, time);
/** * Sends a vote menu to all clients. See VoteMenu() for more information. * * @param vote Vote Handle. * @param time Maximum time to leave menu on the screen. * @return True on success, false if this menu already has a vote session * in progress. * @error Invalid Handle. */ stock DisplayBuiltinVoteToAll(Handle:vote, time) { new total; decl players[MaxClients];
for (new i=1; i<=MaxClients; i++) { if (!IsClientInGame(i) || IsFakeClient(i)) { continue; } players[total++] = i; }
/** * Appends a new item to the end of a vote. Only valid for Multiple Choice votes * * @param vote BuiltinVote Handle. * @param info Item information string. * @return True on success, false on failure. * @error Invalid Handle, item limit reached, or if the vote is not multiple choice. */ native bool:AddBuiltinVoteItem(Handle:vote, const String:info[], const String:display[]);
/** * Inserts an item into the menu before a certain position; the new item will * be at the given position and all next items pushed forward. * * @param vote Vote Handle. * @param position Position, starting from 0. * @param info Item information string. * @return True on success, false on failure. * @error Invalid Handle or vote position, or if the vote is not multiple choice. */ native bool:InsertBuiltinVoteItem(Handle:vote, position, const String:info[], const String:display[]);
/** * Removes an item from the menu. * * @param vote Vote Handle. * @param position Position, starting from 0. * @return True on success, false on failure. * @error Invalid Handle or vote position, or if the vote is not multiple choice. */ native bool:RemoveBuiltinVoteItem(Handle:vote, position);
/** * Removes all items from a vote. * * @param vote Vote Handle. * @noreturn * @error Invalid Handle or vote position, or if the vote is not multiple choice. */ native RemoveAllBuiltinVoteItems(Handle:vote);
/** * Retrieves information about a vote item. * * @param vote Vote Handle. * @param position Position, starting from 0. * @param infoBuf Info buffer. * @param infoBufLen Maximum length of the info buffer. * @return True on success, false if position is invalid. * @error Invalid Handlem */ native bool:GetBuiltinVoteItem(Handle:vote, position, String:infoBuf[], infoBufLen, String:dispBuf[]="", dispBufLen=0);
/** * Returns the number of items in a vote. * * @param vote Vote Handle. * @return Number of items in the vote. * @error Invalid Handle. */ native GetBuiltinVoteItemCount(Handle:vote);
/** * Sets the vote's argument for votes that support arguments * * @param vote Vote Handle. * @param argument Argument string. See vote types for what each argument stands for. * @noreturn * @error Invalid Handle. */ native SetBuiltinVoteArgument(Handle:vote, const String:argument[]);
/** * Returns the text of a vote's argument if set. * * @param vote Vote Handle. * @param buffer Buffer to store argument. * @param maxlength Maximum length of the buffer. * @return Number of bytes written. * @error Invalid Handle. */ native GetBuiltinVoteArgument(Handle:vote, String:buffer[], maxlength);
/** * Returns whether a vote is in progress. * * @return True if a BuiltinVote is in progress, false otherwise. */ native bool:IsBuiltinVoteInProgress();
/** * Returns a style's maximum items * * @return Maximum items */ native GetBuiltinVoteMaxItems();
/** * Sets a vote's option flags. * * If a certain bit is not supported, it will be stripped before being set. * * NOTE: This is currently unused, but reserved for future use. * * @param menu Builtin Vote Handle. * @param flags A new bitstring of VOTEFLAG bits. * @noreturn * @error Invalid Handle. */ native SetBuiltinVoteOptionFlags(Handle:vote, flags);
/** * Retrieves a menu's option flags. * * NOTE: This is currently unused, but reserved for future use. * * @param vote Builtin Vote Handle. * @return A bitstring of VOTEFLAG bits. * @error Invalid Handle. */ native GetBuiltinVoteOptionFlags(Handle:vote);
/** * Cancels the vote in progress. * * @noreturn * @error If no vote is in progress. */ native CancelBuiltinVote();
/** * Sets an advanced vote handling callback. If this callback is set, * BuiltinVoteAction_VoteEnd will not be called. * * @param vote BuiltinVote Handle. * @param callback Callback function. * @noreturn * @error Invalid Handle or callback. */ native SetBuiltinVoteResultCallback(Handle:vote, BuiltinVoteHandler:callback);
/** * Returns the number of seconds you should "wait" before displaying * a public vote. This number is the time remaining until * (last_vote + sm_vote_delay). * * @return Number of seconds to wait, or 0 for none. */ native CheckBuiltinVoteDelay();
/** * Returns whether a client is in the pool of clients allowed * to participate in the current vote. This is determined by * the client list passed to DisplayBuiltinVote(). * * @param client Client index. * @return True if client is allowed to vote, false otherwise. * @error If no vote is in progress or client index is invalid. */ native bool:IsClientInBuiltinVotePool(client);
/** * Redraws the current vote to a client in the voting pool. * * @param client Client index. * @param revotes True to allow revotes, false otherwise. * @return True on success, false if the client is in the vote pool * but cannot vote again. * @error No vote in progress, client is not in the voting pool, * or client index is invalid. */ native bool:RedrawClientBuiltinVote(client, bool:revotes=true);
/** * Retrieve the vote type * * @return The built in vote type * @error Invalid Handle */ native BuiltinVoteType:GetBuiltinVoteType(Handle:vote);
/** * Set the team this vote is for, or BUILTINVOTES_ALL_TEAMS for all teams. * * Defaults to BUILTINVOTES_ALL_TEAMS if not explicitly set. * * @noreturn * @error Invalid Handle */ native SetBuiltinVoteTeam(Handle:vote, team);
/** * Retrieve the team this vote is for * * @return Team index or BUILTINVOTES_ALL_TEAMS for all teams. * @error Invalid Handle */ native GetBuiltinVoteTeam(Handle:vote);
/** * Set the player who initiated the vote. * Use BUILTINVOTES_SERVER_INDEX if initiated by the server itself. * * Defaults to BUILTINVOTES_SERVER_INDEX if not explicitly set. * * @noreturn * @error Invalid Handle */ native SetBuiltinVoteInitiator(Handle:vote, client);
/** * Retrieve the client index who initiated the vote or BUILTINVOTES_SERVER_INDEX if * initiated by the server itself. * * @return Client index or BUILTINVOTES_SERVER_INDEX * @error Invalid Handle */ native GetBuiltinVoteInitiator(Handle:vote);
/** * Display vote passed screen * * You MUST call one of DisplayBuiltinVotePass, DisplayBuiltinVotePass2, or DisplayBuiltinVoteFail to hide the vote screen * for users who didn't vote, and to clear out their selection for the next vote. * * @param param1 Normally the item that won the vote. Also used for custom vote winners */ native DisplayBuiltinVotePass(Handle:vote, String:param1[]="");
/** * Display vote passed screen with a custom type. * * A sample usage of this would be for an RTV vote pass: DisplayBuiltinVotePass2(vote, TRANSLATION_TF2_VOTE_CHANGELEVEL_PASSED, map); * * You MUST call one of DisplayBuiltinVotePass, DisplayBuiltinVotePass2, or DisplayBuiltinVoteFail to hide the vote screen * for users who didn't vote, and to clear out their selection for the next vote. * * @param translation One of the translation passed constants. * @param param1 Normally the item that won the vote. Also used for custom vote winners */ native DisplayBuiltinVotePass2(Handle:vote, String:translation[], String:param1[]="");
/** * Display failure screen. * * You MUST call one of DisplayBuiltinVotePass, DisplayBuiltinVotePass2, or DisplayBuiltinVoteFail to hide the vote screen * for users who didn't vote, and to clear out their selection for the next vote. * * @param reason Vote failure reason from BuiltinVoteFailed enum */ native DisplayBuiltinVoteFail(Handle:vote, BuiltinVoteFailReason:reason=BuiltinVoteFail_Generic);
/** * Quick stock to determine whether voting is allowed. This doesn't let you * fine-tune a reason for not voting, so it's not recommended for lazily * telling clients that voting isn't allowed. * * @return True if voting is allowed, false if voting is in progress * or the cooldown is active. */ stock bool:IsNewBuiltinVoteAllowed() { if (IsBuiltinVoteInProgress() || CheckBuiltinVoteDelay() != 0) { return false; }
return true; }
/** * Retrieves voting information from BuiltinVoteAction_VoteEnd. * * @param param2 Second parameter of BuiltinVoteAction_VoteEnd. * @param winningVotes Number of votes received by the winning option. * @param totalVotes Number of total votes received. * @noreturn */ stock GetBuiltinVoteInfo(param2, &winningVotes, &totalVotes) { winningVotes = param2 & 0xFFFF; totalVotes = param2 >> 16; }
/** * Sends a vote menu to a single team. See DisplayBuiltinVote() for more information. * * @param vote Vote Handle. * @param team Team to send vote to. 1 = spectators, 2 = RED/Survivors, 3 = BLU/Infected * @param time Maximum time to leave menu on the screen. * @return True on success, false if this menu already has a vote session * in progress. * @error Invalid Handle. */ stock bool:DisplayBuiltinVoteToTeam(Handle:vote, team, time) { SetBuiltinVoteTeam(vote, team);
new total; decl players[MaxClients];
for (new i=1; i<=MaxClients; i++) { if (!IsClientInGame(i) || IsFakeClient(i) || (GetClientTeam(i) != team)) { continue; } players[total++] = i; }
/** * Sends a vote menu to all clients except one. See DisplayBuiltinVote() for more information. * * @param vote Vote Handle. * @param client Client index not to send vote to * @param time Maximum time to leave menu on the screen. * @return True on success, false if this menu already has a vote session * in progress or if the client isn't in the game. * @error Invalid Handle. */ stock bool:DisplayBuiltinVoteToAllButOne(Handle:vote, client, time) { if (!IsClientInGame(client)) { return false; }
new total; decl players[MaxClients];
for (new i=1; i<=MaxClients; i++) { if (!IsClientInGame(i) || IsFakeClient(i) || (i == client)) { continue; } players[total++] = i; }
/** * Sends a vote menu to a single team except one player. See DisplayBuiltinVote() for more information. * * @param vote Vote Handle. * @param client Client index not to send vote to. Team is derived from this client. * @param time Maximum time to leave menu on the screen. * @return True on success, false if this menu already has a vote session * in progress or the client isn't in the game. * @error Invalid Handle. */ stock bool:DisplayBuiltinVoteToTeamButOne(Handle:vote, client, time) { if (!IsClientInGame(client)) { return false; }
new team = GetClientTeam(client);
#if 0 if (team < 2) { return false; } #endif
SetBuiltinVoteTeam(vote, team);
new total; decl players[MaxClients];
for (new i=1; i<=MaxClients; i++) { if (!IsClientInGame(i) || IsFakeClient(i) || (i == client) || (GetClientTeam(i) != team)) { continue; } players[total++] = i; }
/** * Sends a vote menu to all clients who are not spectators or waiting to choose a team. See DisplayBuiltinVote() for more information. * * @param vote Vote Handle. * @param time Maximum time to leave menu on the screen. * @return True on success, false if this menu already has a vote session * in progress. * @error Invalid Handle. */ stock bool:DisplayBuiltinVoteToAllNonSpectators(Handle:vote, time) { new total; decl players[MaxClients];
for (new i=1; i<=MaxClients; i++) { if (!IsClientInGame(i) || IsFakeClient(i) || (GetClientTeam(i) < 2)) { continue; } players[total++] = i; }
Generally usage:
For most votes, you'll call CreateBuiltinVote, then (for multiple choice votes) AddBuiltinVoteItem, then DisplayBuiltinVoteToAll.
Note: When the vote is finished, you *must* call DisplayBuiltinVotePass, DisplayBuiltinVotePass2, or DisplayBuiltinVoteFail. If one of these messages isn't sent, clients who voted will not be able to cast future votes while clients who didn't vote will be stuck with the vote screen up forever. As a failsafe, if you close the vote handle but didn't send one of the above messages, a Generic Failure vote will pop up.
Changelog:
0.5.8 (2013-01-29)
Updated for breaking changes in the SourceMod IUserMessages extension interface.
This is the FINAL version of BuiltinVotes. Its replacement, the plugin NativeVotes, is nearly ready to begin testing.
builtinvotes.inc update #2 (2012-03-20)
Added DisplayBuiltinVoteToAllNonSpectators
Fixed the previously added team vote functions.
builtinvotes.inc update #1 (2012-03-16)
Added DisplayBuiltinVoteToTeam, DisplayBuiltinVoteToAllButOne, and DisplayBuiltinVoteToTeamButOne.
0.5.7a (2012-03-06):
Modified debugging code to help track down translation issues. Main extension unchanged.
0.5.7 (2012-02-20):
Reworked Cancel method to call CancelVoting internally
Cancel now properly kills the Display timer, which prevents a crash.
Rolled back DecrementPlayerCount change from previous version because it potentially introduced new issues.
The Cancel screen is no longer automatically shown when a vote object is disposed. This was necessary to fix the issues with Canceling votes causing errors.
Votes that were canceled will no longer be redisplayed if a user uses !revote unless another vote is called first. Hopefully you won't do that, as the vote may have the old vote type or title with the new vote's options.
Oh hey, it looks like Cancel is finally working and stable. It only took from 0.5.2 to 0.5.7 to iron out the bugs... now to debug the translations issue.
0.5.4 (2012-02-19):
Fixed issue with crashes on map change. Be aware that the Cancel and End callbacks will be called on map start for the vote object.
Fixed an issue where disconnecting players were not being removed from the maximum vote count.
0.5.1 (2012-01-19):
Fixed two potential memory leaks.
0.5.0 (2011-12-04):
The L4D2 Alltalk vote is now present as a vote translation text.
Now includes a plugin to enable !revote and /revote for BuiltinVotes. NOTE: Because of the way this plugin is written, any Radio or Valve menus will not have /revote or !revote available when a BuiltinVotes vote is showing.
There is a forced 3 second delay between when you type !revote and the revote screen appearing. This is required due to how Valve handles votes in TF2; a vote failed screen is required to re-enable the vote keys. Due to this, !revote isn't allowed within the last 3 seconds of a vote.
Only tested on TF2, although it should also work on L4D and L4D2.
Fixed an error in the BuiltinVotes revoting logic which would end votes early if people revoted.
Fixed a bug in cvar handling that would sometimes cause a crash when the extension was unloaded. Unfortunately, there's still a OnClientDisconnected bug that sometimes causes the extension to crash when unloaded that I haven't been able to track down. (Technically, it's SourceMod that's crashing, but it only happens when the BuiltinVotes extension is reloaded)
New function, DisplayBuiltinVotePass2. This allows you to specify the translation constant that the Vote Passed screen should use. This should only be done for game-specific plugins, as the translation phrases for L4D/L4D2 don't work in TF2 (and vice versa).
Updated builtinvotes.inc that includes the new function mentioned above plus all the translation constants from the Extension side... including the START translations which aren't actually used for anything on the plugin side... yet...
0.4.1 (2011-11-08)
Fixed a bug in L4D/L4D2 were recording votes backwards. Turns out that missing an == 0 is bad. L4D still untested, but L4D2 support appears to work.
Fixed a bug where vote counts weren't cleared before the start of a vote in L4D/L4D2.
Updated Makefile and Visual C++ projects to SourceMod 1.4 versions. The net result of this is that Linux builds are now slightly smaller and the Makefile will now work in OSX.
0.4.0 (2011-11-07)
Preliminary (and untested) support for L4D and L4D2
Fixed issues with Yes/No votes in TF2
Updated builtinvotes.inc
Added two new constants: BUILTINVOTES_VOTE_YES and BUILTINVOTES_VOTE_NO, which are returned as param1 for a Yes/No vote's BuiltinVoteAction_VoteEnd callback.
Removed BUILTINVOTES_NO_VOTE, which is from the C++ side and not exposed to the public.
Updated documentation for BuiltinVoteAction_Cancel to document that yes, it does return a param1 value.
0.0.3.0 (2011-11-05 (am))
Fix bug where players could vote multiple times.
Modified when the vote command is hooked. Normal built-in votes should now work when no vote is being displayed. Later versions may also hook callvote to prevent built-in votes from being called while the vote is displayed.
OnClientCommand moved to Vote classes from VoteStyle classes. BuiltinVoteHandler now gets passed vote item first and does sanity checks.
Fixed dumb mistake in marking users as having pending votes.
0.0.2.0 - Initial Public TF2 Release
__________________
Not currently working on SourceMod plugin development.
Last edited by Powerlord; 02-14-2013 at 12:09.
Reason: Updated builtinvotes.inc
Oh my gosh you don't have VS2010?! It's so much better.
Also, I can see a possible place where a crash might happen in CreateBuiltinVote: You create a pointer but never check to see that it's valid before making a call to one of its members.
Oh my gosh you don't have VS2010?! It's so much better.
I do at home, but not at work.
Quote:
Originally Posted by Afronanny
Also, I can see a possible place where a crash might happen in CreateBuiltinVote: You create a pointer but never check to see that it's valid before making a call to one of its members.
I thought you could already access tf2 votes via usermessages? Does this ext plan to allow for more customization of the vote strings?
__________________
Bread EOTL GunMettle Invasion Jungle Inferno will break everything. Don't even ask.
All plugins: Randomizer/GiveWeapon, ModelManager, etc.
Post in plugin threads with questions. Steam is for playing games.
You will be fed to javalia otherwise. Psyduck likes replays.
I thought you could already access tf2 votes via usermessages? Does this ext plan to allow for more customization of the vote strings?
You can. That's what this extension does, except it acts as a "vote manager" for plugins. If you read the extension's source, you'll see that it does indeed use usermessages.
I need to do more testing in TF2 to make sure the vote_changed event really isn't used. Hopefully I'll get some friends together tonight to test it on my plugin test server. I only tested with 2 people before, and I have a feeling it won't show a vote_changed event until at least 3 people are present due to the first vote automatically being placed and the second vote ending the vote with only 2.
As for vote objects, I hate doing this because it seems messy, but I've replaced
It was either that or convert it to a pointer and change all the calls it made... which, upon further consideration, I will likely do instead.
I'm also in the middle of creating those classes. The TF2 version was easy, as it's essentially the behavior that's already going on. I'm working on L4D's now, as it's fairly well documented on the Wiki. I'm going to turn to Mr. Zero's documentation in the L4D Vote Manager 2 plugin thread for the L4D2 handling when I finish the L4D handling.
So, far, I've declared BuildinVoteHandler an abstract class by marking 7 functions as pure virtual functions:
All formerly-private functions and variables are now marked as protected. I'm not sure if that's necessary, but I'm a Java coder, and it is necessary there.
DisplayAllVotes is new, in order to display L4D(/L4D2?)'s vote_changed event post-vote tally... and TF2's event of the same name, should it be used.
SendOptions returns a bool now. It returns false for L4D and L4D2, as well as if the TF2 vote has no vote options listed, which indicates an error if it's a multiple choice vote.
As a side note, there will be some new code going on in CreateVote... or possibly in CBuildinVote's constructor... to add Yes and No votes to the vote item list in order to make vote processing consistent among the 3 handlers. I'm thinking the constructor is the best place to do this, as it has access to which type of vote is going on and I can make the Add/Insert/Remove item functions return an error should it be marked as a Yes/No vote.
__________________
Not currently working on SourceMod plugin development.
I think I'm going to try simplifying this a bit. The Vote object + Vote Handler object + Vote Manager object + separate natives objects may work for the SourceMod built-in Menu system, but it's overkill for this. I find that every time I make the slightest change, I have to go across 8 different files for fixes.
Sadly, the first thing to get the ax is the interface file. I'm not sure any other extensions would want to use this anyway, though. This won't affect plugins, as they use natives.
Alternatively, I'd love to see if I can add this to the SourceMod built-in menu system (meaning I'd have to create it as a MenuStye). However, some of the things SourceMod does now would end up being no-ops in the new system. Unless I made pagination work in TF2, which would be really sad, as it only supports 5 items.
P.S. If anyone can post the L4D/L4D2 translation strings for votes, plus a translation string that consists of nothing but "%s1" for L4D2, I'd be greatful. I found out the hard way that L4D2 doesn't install its translation file on the dedicated server, and it's like a 16+GB download to install L4D and L4D2.
__________________
Not currently working on SourceMod plugin development.
I was hoping to complete a new test version this weekend, but it turns out I've run into a problem. Said problem likely stems from my poor C++ skills...
Anyway, I've attached the current source tree. There are some issues with extension.cpp because I need to write some methods yet, and there is still some logic missing from the various vote classes, but that's not what I need help with.
The real problem is the "error C2259: 'L4DVote' : cannot instantiate abstract class" I'm getting from Visual Studio 2010 (note: This happens for TF2Vote, L4DVote, and L4D2Vote classes as I switch configs).
I've tried Go To Definition for every function defined in Votes.h, and every function I can see exists in Votes.cpp. Can anyone see why VC++ thinks they're all abstract classes (BaseVote's the only one that's supposed to be abstract), despite having every method I can see defined?
P.S. Is there any way to get it to tell me which method isn't defined?
P.P.S. Both the msvc9 and msvc10 projects should contain all the files necessary to compile. The msvc8 project needs Votes.h and Votes.cpp added.
__________________
Not currently working on SourceMod plugin development.