Difference between revisions of "Left 4 Voting 2"

From AlliedModders Wiki
Jump to: navigation, search
(Based on the L4D and TF2 Voting pages.)
 
(Example voting plugin: Fix ret on command)
 
(11 intermediate revisions by 4 users not shown)
Line 3: Line 3:
 
== How voting works ==
 
== How voting works ==
 
# A client issues a callvote with the vote type and argument.
 
# A client issues a callvote with the vote type and argument.
# The VoteStart User Message is sent, the last argument determines the vote type.
+
# 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.
 
# 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 User Message.
+
# 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 27: Line 39:
 
{{hl2msg|string|issue|Vote issue translation string}}
 
{{hl2msg|string|issue|Vote issue translation string}}
 
{{hl2msg|string|param1|Vote issue text}}
 
{{hl2msg|string|param1|Vote issue text}}
{{hl2msg|string|param1|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.
  
<pre>#include <sourcemod>
+
<sourcepawn>#include <sourcemod>
  
 
#define L4D2_TEAM_ALL -1
 
#define L4D2_TEAM_ALL -1
  
new yesvotes;
+
int g_iYesVotes;
new novotes;
+
int g_iNoVotes;
#define MAX_VOTES 4
+
int g_iPlayersCount;
 +
bool VoteInProgress;
 +
bool CanPlayerVote[MAXPLAYERS + 1];
  
public OnPluginStart()
+
 
 +
public void OnPluginStart()
 
{
 
{
RegConsoleCmd("testvote",Callvote_Handler);
+
RegConsoleCmd("sm_testvote", cmdTestVote);
RegConsoleCmd("Vote",vote);
+
RegConsoleCmd("sm_vote", cmdVote);
 
}
 
}
public Action:Callvote_Handler(client, args)
+
 
 +
public Action cmdTestVote(int client, int args)
 
{
 
{
new Handle:bf = StartMessageAll("VoteStart", USERMSG_RELIABLE);
+
BfWrite bf = UserMessageToBfWrite(StartMessageAll("VoteStart", USERMSG_RELIABLE));
  
BfWriteByte(bf, L4D2_TEAM_ALL);
+
bf.WriteByte(L4D2_TEAM_ALL);
BfWriteByte(bf, 0);
+
bf.WriteByte(0);
BfWriteString(bf, "#TF_playerid_noteam");
+
bf.WriteString("#L4D_TargetID_Player");
BfWriteString(bf, "Is gaben fat?");
+
bf.WriteString("Is gaben fat?");
BfWriteString(bf, "Server");
+
bf.WriteString("Server");
 
EndMessage();
 
EndMessage();
+
 
yesvotes = 0;
+
g_iYesVotes = 0;
novotes = 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 UpdateVotes()
+
 
 +
public Action timerVoteCheck(Handle timer)
 
{
 
{
new Handle:msg = CreateEvent("vote_changed");
+
if (VoteInProgress)
SetEventInt(msg,"yesVotes",yesvotes);
+
{
SetEventInt(msg,"noVotes",novotes);
+
VoteInProgress = false;
SetEventInt(msg,"potentialVotes",MAX_VOTES);
+
UpdateVotes();
FireEvent(msg);
+
}
 +
 
 +
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 (yesvotes+novotes == MAX_VOTES)
+
if ((g_iYesVotes + g_iNoVotes == g_iPlayersCount) || !VoteInProgress)
 
{
 
{
 
PrintToServer("voting complete!");
 
PrintToServer("voting complete!");
if (yesvotes > novotes)
+
 
 +
for (int i = 1; i <= MaxClients; i++)
 +
{
 +
if (IsClientInGame(i) && !IsFakeClient(i))
 +
{
 +
CanPlayerVote[i] = false;
 +
}
 +
}
 +
 
 +
VoteInProgress = false;
 +
 
 +
if (g_iYesVotes > g_iNoVotes)
 
{
 
{
new Handle:bf = StartMessageAll("VotePass");
+
BfWrite bf = UserMessageToBfWrite(StartMessageAll("VotePass"));
BfWriteByte(bf, L4D2_TEAM_ALL);
+
bf.WriteByte(L4D2_TEAM_ALL);
BfWriteString(bf, "#L4D_TargetID_Player");
+
bf.WriteString("#L4D_TargetID_Player");
BfWriteString(bf, "Gaben is fat");
+
bf.WriteString("Gaben is fat");
 
EndMessage();
 
EndMessage();
 
}
 
}
 
else
 
else
 
{
 
{
new Handle:bf = StartMessageAll("VoteFailed");
+
BfWrite bf = UserMessageToBfWrite(StartMessageAll("VoteFail"));
BfWriteByte(bf, L4D2_TEAM_ALL);
+
bf.WriteByte(L4D2_TEAM_ALL);
 
EndMessage();
 
EndMessage();
 
}
 
}
 
}
 
}
 
}
 
}
public Action:vote(client, args)
+
 
 +
public Action cmdVote(int client, int args)
 
{
 
{
new String:arg[8];
+
if (VoteInProgress && CanPlayerVote[client])
GetCmdArg(1,arg,8);
 
PrintToServer("Got vote %s from %i",arg,client);
 
if (strcmp(arg,"Yes",true) == 0)
 
 
{
 
{
yesvotes++;
+
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();
 
}
 
}
else if (strcmp(arg,"No",true) == 0)
+
 
{
+
return Plugin_Handled;
novotes++;
+
}
}
+
</sourcepawn>
 
UpdateVotes();
 
return Plugin_Continue;
 
}</pre>
 
  
 
==See Also==
 
==See Also==

Latest revision as of 08: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.

How voting works

  1. A client issues a callvote with the vote type and argument.
  2. The VoteStart User Message is sent.
  3. 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.
  4. 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:
int m_activeIssueIndex Number of the active issue
int m_onlyTeamToVote Corresponds to VoteStart's team argument.
int m_votesYes Current Yes votes
int m_votesNo Current No votes
int m_potentialVotes Number of players eligible to vote


Console Commands

Vote

Note: This command is only valid when a vote is ongoing.

Name: Vote
Structure:
string option "Yes" or "No"


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:
byte team Team index or 255 for all
byte initiator Client index (NOT USERID) of person who started the vote, or 99 for the server.
string issue Vote issue translation string
string param1 Vote issue text
string initiatorName Name of person who started the vote


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:
byte vote 0 for No, 1 for Yes


VotePass

Note: Sent to all players after a vote passes.

Name: VotePass
Structure:
byte team Team index or 255 for all
string details Vote success translation string
string param1 Vote winner


VoteFail

Note: Sent to all players after a vote fails.

Name: VoteFail
Structure:
byte team Team index or 255 for all


Events

Note: team is -1 when sent to all players)

vote_changed

Name: vote_changed
Structure:
byte yesVotes
byte noVotes
byte potentialVotes


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