Left 4 Voting 2

From AlliedModders Wiki
Jump to: navigation, search

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