Difference between revisions of "Left 4 Voting 2"
(Fixed VoteStart having two param1s.) |
Joinedsenses (talk | contribs) (→Example voting plugin: Fix ret on command) |
||
(8 intermediate revisions by 4 users not shown) | |||
Line 6: | Line 6: | ||
# Clients use the "Vote" command to register their votes ("Yes" or "No"), after which the server sends a VoteRegistered UserMessage to that player to acknowledge their vote. A vote_changed event is sent to all players with updated numbers. | # Clients use the "Vote" command to register their votes ("Yes" or "No"), after which the server sends a VoteRegistered UserMessage to that player to acknowledge their vote. A vote_changed event is sent to all players with updated numbers. | ||
# When the vote is complete, the server sends either a VotePass or VoteFail UserMessage. | # When the vote is complete, the server sends either a VotePass or VoteFail UserMessage. | ||
+ | |||
+ | == Server Entity == | ||
+ | |||
+ | The server should update this as appropriate. Unfortunately, the valid values for m_iActiveIssueIndex is unknown. | ||
+ | |||
+ | {{begin-hl2msg|vote_controller (CVoteController)|string}} | ||
+ | {{hl2msg|int|m_activeIssueIndex|Number of the active issue}} | ||
+ | {{hl2msg|int|m_onlyTeamToVote|Corresponds to VoteStart's team argument.}} | ||
+ | {{hl2msg|int|m_votesYes|Current Yes votes}} | ||
+ | {{hl2msg|int|m_votesNo|Current No votes}} | ||
+ | {{hl2msg|int|m_potentialVotes|Number of players eligible to vote}} | ||
+ | {{end-hl2msg}} | ||
== Console Commands == | == Console Commands == | ||
Line 29: | Line 41: | ||
{{hl2msg|string|initiatorName|Name of person who started the vote}} | {{hl2msg|string|initiatorName|Name of person who started the vote}} | ||
{{end-hl2msg}} | {{end-hl2msg}} | ||
+ | ====Values for "issue" and "param1" in standard votes==== | ||
+ | {| class="wikitable" | ||
+ | |- | ||
+ | ! Vote type !! issue !! param1 | ||
+ | |- | ||
+ | | Kick || #L4D_vote_kick_player || Nickname of the person to be kicked without "#" | ||
+ | |- | ||
+ | | ReturnToLobby || #L4D_vote_return_to_lobby || | ||
+ | |- | ||
+ | | ChangeCampaign || #L4D_vote_mission_change || #L4D360UI_CampaignName_C5 | ||
+ | |- | ||
+ | | RestartChapter || #L4D_vote_passed_versus_level_restart || | ||
+ | |- | ||
+ | | ChangeDifficulty (easy) || #L4D_vote_change_difficulty || #L4D_DifficultyEasy | ||
+ | |- | ||
+ | | ChangeDifficulty (hard) || #L4D_vote_change_difficulty || #L4D_DifficultyHard | ||
+ | |- | ||
+ | | Alltalk On || #L4D_vote_alltalk_change || #L4D_vote_alltalk_enable | ||
+ | |- | ||
+ | | Alltalk Off || #L4D_vote_alltalk_change || #L4D_vote_alltalk_disable | ||
+ | |} | ||
+ | *Campaigns aren't specified by map name but by the index the campaign's entry has in the game menus. Example: "5" specifies the campaign "The Parish" | ||
===VoteRegistered=== | ===VoteRegistered=== | ||
Line 64: | Line 98: | ||
This is a basic plugin that starts a vote, "Is gaben fat?". It does not ensure the same client does not vote multiple times, nor does it actually kick the user. | This is a basic plugin that starts a vote, "Is gaben fat?". It does not ensure the same client does not vote multiple times, nor does it actually kick the user. | ||
− | < | + | <sourcepawn>#include <sourcemod> |
#define L4D2_TEAM_ALL -1 | #define L4D2_TEAM_ALL -1 | ||
− | + | int g_iYesVotes; | |
− | + | int g_iNoVotes; | |
− | + | int g_iPlayersCount; | |
+ | bool VoteInProgress; | ||
+ | bool CanPlayerVote[MAXPLAYERS + 1]; | ||
− | public OnPluginStart() | + | |
+ | public void OnPluginStart() | ||
{ | { | ||
− | RegConsoleCmd(" | + | RegConsoleCmd("sm_testvote", cmdTestVote); |
− | RegConsoleCmd(" | + | RegConsoleCmd("sm_vote", cmdVote); |
} | } | ||
− | public Action | + | |
+ | public Action cmdTestVote(int client, int args) | ||
{ | { | ||
− | + | BfWrite bf = UserMessageToBfWrite(StartMessageAll("VoteStart", USERMSG_RELIABLE)); | |
− | + | bf.WriteByte(L4D2_TEAM_ALL); | |
− | + | bf.WriteByte(0); | |
− | + | bf.WriteString("#L4D_TargetID_Player"); | |
− | + | bf.WriteString("Is gaben fat?"); | |
− | + | bf.WriteString("Server"); | |
EndMessage(); | EndMessage(); | ||
− | + | ||
− | + | g_iYesVotes = 0; | |
− | + | g_iNoVotes = 0; | |
+ | g_iPlayersCount = 0; | ||
+ | VoteInProgress = true; | ||
+ | |||
+ | for (int i = 1; i <= MaxClients; i++) | ||
+ | { | ||
+ | if (IsClientInGame(i) && !IsFakeClient(i)) | ||
+ | { | ||
+ | CanPlayerVote[i] = true; | ||
+ | g_iPlayersCount ++; | ||
+ | } | ||
+ | } | ||
+ | |||
UpdateVotes(); | UpdateVotes(); | ||
+ | CreateTimer(10.0, timerVoteCheck, TIMER_FLAG_NO_MAPCHANGE); | ||
return Plugin_Handled; | return Plugin_Handled; | ||
} | } | ||
− | public | + | |
+ | public Action timerVoteCheck(Handle timer) | ||
{ | { | ||
− | + | if (VoteInProgress) | |
− | + | { | |
− | + | VoteInProgress = false; | |
− | + | UpdateVotes(); | |
− | + | } | |
+ | |||
+ | return Plugin_Continue; | ||
+ | } | ||
+ | |||
+ | public void UpdateVotes() | ||
+ | { | ||
+ | Event event = CreateEvent("vote_changed"); | ||
+ | event.SetInt("yesVotes", g_iYesVotes); | ||
+ | event.SetInt("noVotes", g_iNoVotes); | ||
+ | event.SetInt("potentialVotes", g_iPlayersCount); | ||
+ | event.Fire(); | ||
− | if ( | + | if ((g_iYesVotes + g_iNoVotes == g_iPlayersCount) || !VoteInProgress) |
{ | { | ||
PrintToServer("voting complete!"); | PrintToServer("voting complete!"); | ||
− | if ( | + | |
+ | for (int i = 1; i <= MaxClients; i++) | ||
+ | { | ||
+ | if (IsClientInGame(i) && !IsFakeClient(i)) | ||
+ | { | ||
+ | CanPlayerVote[i] = false; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | VoteInProgress = false; | ||
+ | |||
+ | if (g_iYesVotes > g_iNoVotes) | ||
{ | { | ||
− | + | BfWrite bf = UserMessageToBfWrite(StartMessageAll("VotePass")); | |
− | + | bf.WriteByte(L4D2_TEAM_ALL); | |
− | + | bf.WriteString("#L4D_TargetID_Player"); | |
− | + | bf.WriteString("Gaben is fat"); | |
EndMessage(); | EndMessage(); | ||
} | } | ||
else | else | ||
{ | { | ||
− | + | BfWrite bf = UserMessageToBfWrite(StartMessageAll("VoteFail")); | |
− | + | bf.WriteByte(L4D2_TEAM_ALL); | |
EndMessage(); | EndMessage(); | ||
} | } | ||
} | } | ||
} | } | ||
− | public Action | + | |
+ | public Action cmdVote(int client, int args) | ||
{ | { | ||
− | + | if (VoteInProgress && CanPlayerVote[client]) | |
− | |||
− | |||
− | |||
{ | { | ||
− | + | char arg[8]; | |
+ | GetCmdArg(1, arg, sizeof arg); | ||
+ | |||
+ | PrintToServer("Got vote %s from %i", arg, client); | ||
+ | |||
+ | if (strcmp(arg, "Yes", true) == 0) | ||
+ | { | ||
+ | g_iYesVotes++; | ||
+ | } | ||
+ | else if (strcmp(arg, "No", true) == 0) | ||
+ | { | ||
+ | g_iNoVotes++; | ||
+ | } | ||
+ | |||
+ | UpdateVotes(); | ||
} | } | ||
− | + | ||
− | + | return Plugin_Handled; | |
− | + | } | |
− | + | </sourcepawn> | |
− | |||
− | |||
− | |||
− | |||
==See Also== | ==See Also== |
Latest revision as of 09:26, 16 September 2022
Left 4 Dead 2 has a VGUI voting system controller by a bunch of Events and UserMessages. You can use either a string from the resource file, or L4D_TargetID_Player which will let you create any vote you want.
Contents
How voting works
- A client issues a callvote with the vote type and argument.
- The VoteStart User Message is sent.
- Clients use the "Vote" command to register their votes ("Yes" or "No"), after which the server sends a VoteRegistered UserMessage to that player to acknowledge their vote. A vote_changed event is sent to all players with updated numbers.
- When the vote is complete, the server sends either a VotePass or VoteFail UserMessage.
Server Entity
The server should update this as appropriate. Unfortunately, the valid values for m_iActiveIssueIndex is unknown.
Name: | vote_controller (CVoteController) | |||||||||||||||
Structure: |
|
Console Commands
Vote
Note: This command is only valid when a vote is ongoing.
Name: | Vote | |||
Structure: |
|
User Messages
Note: These user messages use unsigned bytes for the team number. Since Valve represents no team as -1, no team is instead represented as its two's complement, 255.
VoteStart
Note: Sent to all players in the vote group, corresponding with teams. The default implementation also sends it to bots.
Name: | VoteStart | |||||||||||||||
Structure: |
|
Values for "issue" and "param1" in standard votes
Vote type | issue | param1 |
---|---|---|
Kick | #L4D_vote_kick_player | Nickname of the person to be kicked without "#" |
ReturnToLobby | #L4D_vote_return_to_lobby | |
ChangeCampaign | #L4D_vote_mission_change | #L4D360UI_CampaignName_C5 |
RestartChapter | #L4D_vote_passed_versus_level_restart | |
ChangeDifficulty (easy) | #L4D_vote_change_difficulty | #L4D_DifficultyEasy |
ChangeDifficulty (hard) | #L4D_vote_change_difficulty | #L4D_DifficultyHard |
Alltalk On | #L4D_vote_alltalk_change | #L4D_vote_alltalk_enable |
Alltalk Off | #L4D_vote_alltalk_change | #L4D_vote_alltalk_disable |
- Campaigns aren't specified by map name but by the index the campaign's entry has in the game menus. Example: "5" specifies the campaign "The Parish"
VoteRegistered
Note: Only sent to player who voted
Name: | VoteRegistered | |||
Structure: |
|
VotePass
Note: Sent to all players after a vote passes.
Name: | VotePass | |||||||||
Structure: |
|
VoteFail
Note: Sent to all players after a vote fails.
Name: | VoteFail | |||
Structure: |
|
Events
Note: team is -1 when sent to all players)
vote_changed
Name: | vote_changed | |||||||||
Structure: |
|
Example voting plugin
This is a basic plugin that starts a vote, "Is gaben fat?". It does not ensure the same client does not vote multiple times, nor does it actually kick the user.
#include <sourcemod> #define L4D2_TEAM_ALL -1 int g_iYesVotes; int g_iNoVotes; int g_iPlayersCount; bool VoteInProgress; bool CanPlayerVote[MAXPLAYERS + 1]; public void OnPluginStart() { RegConsoleCmd("sm_testvote", cmdTestVote); RegConsoleCmd("sm_vote", cmdVote); } public Action cmdTestVote(int client, int args) { BfWrite bf = UserMessageToBfWrite(StartMessageAll("VoteStart", USERMSG_RELIABLE)); bf.WriteByte(L4D2_TEAM_ALL); bf.WriteByte(0); bf.WriteString("#L4D_TargetID_Player"); bf.WriteString("Is gaben fat?"); bf.WriteString("Server"); EndMessage(); g_iYesVotes = 0; g_iNoVotes = 0; g_iPlayersCount = 0; VoteInProgress = true; for (int i = 1; i <= MaxClients; i++) { if (IsClientInGame(i) && !IsFakeClient(i)) { CanPlayerVote[i] = true; g_iPlayersCount ++; } } UpdateVotes(); CreateTimer(10.0, timerVoteCheck, TIMER_FLAG_NO_MAPCHANGE); return Plugin_Handled; } public Action timerVoteCheck(Handle timer) { if (VoteInProgress) { VoteInProgress = false; UpdateVotes(); } return Plugin_Continue; } public void UpdateVotes() { Event event = CreateEvent("vote_changed"); event.SetInt("yesVotes", g_iYesVotes); event.SetInt("noVotes", g_iNoVotes); event.SetInt("potentialVotes", g_iPlayersCount); event.Fire(); if ((g_iYesVotes + g_iNoVotes == g_iPlayersCount) || !VoteInProgress) { PrintToServer("voting complete!"); for (int i = 1; i <= MaxClients; i++) { if (IsClientInGame(i) && !IsFakeClient(i)) { CanPlayerVote[i] = false; } } VoteInProgress = false; if (g_iYesVotes > g_iNoVotes) { BfWrite bf = UserMessageToBfWrite(StartMessageAll("VotePass")); bf.WriteByte(L4D2_TEAM_ALL); bf.WriteString("#L4D_TargetID_Player"); bf.WriteString("Gaben is fat"); EndMessage(); } else { BfWrite bf = UserMessageToBfWrite(StartMessageAll("VoteFail")); bf.WriteByte(L4D2_TEAM_ALL); EndMessage(); } } } public Action cmdVote(int client, int args) { if (VoteInProgress && CanPlayerVote[client]) { char arg[8]; GetCmdArg(1, arg, sizeof arg); PrintToServer("Got vote %s from %i", arg, client); if (strcmp(arg, "Yes", true) == 0) { g_iYesVotes++; } else if (strcmp(arg, "No", true) == 0) { g_iNoVotes++; } UpdateVotes(); } return Plugin_Handled; }
See Also
- Left 4 Voting
- TF2 Voting
- BuiltinVotes, a SourceMod extension that exposes a voting API that uses this voting system.