Veteran Member
Join Date: Jun 2009
Location: Denmark
|
08-02-2011
, 11:58
Re: [WIP] Builtin Votes
|
#13
|
Since you are doing so much progress more than me, here is my current shitty version of a l4d2 vote handler (far from done).
Have fun
PHP Code:
/**
* =============================================================================
* L4D2 Vote Manager (C)2011 Buster "Mr. Zero" Nielsen
* =============================================================================
*
* 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>.
*/
/*
* ==================================================
* Preprocessor
* ==================================================
*/
#if defined DEBUG_CHANNEL_NAME
#undef DEBUG_CHANNEL_NAME
#endif
#define DEBUG_CHANNEL_NAME "VoteHandler"
#define L4D_MAX_VOTE_ISSUES 8
/**
* Full list of vote issues.
*
* Typing "listissues" in console while playing on a server shows a full list
* over available issues.
*/
enum L4DVoteIssue
{
L4DVoteIssue_None = -1,
L4DVoteIssue_ChangeDifficulty = 0,
L4DVoteIssue_RestartGame = 1,
L4DVoteIssue_Kick = 2,
L4DVoteIssue_ChangeMission = 3,
L4DVoteIssue_ReturnToLobby = 4,
L4DVoteIssue_ChangeChapter = 5,
L4DVoteIssue_ChangeAllTalk = 6,
L4DVoteIssue_Custom = 7,
};
enum L4DVote
{
L4DVote_None = -1,
L4DVote_No = 0,
L4DVote_Yes = 1
}
/*
* ==================================================
* Includes
* ==================================================
*/
/*
* ==================================================
* Variables
* ==================================================
*/
/*
* --------------------
* Public
* --------------------
*/
/*
* --------------------
* Private
* --------------------
*/
static Handle: g_hFwd_OnVoteStart; // Used for internal modules
static Handle: g_hGFwd_OnVoteStart;
static Handle: g_hGFwd_OnVoteStart_Post;
static Handle: g_hGFwd_OnAddClientToVotePool;
static Handle: g_hGFwd_OnAddClientToVotePool_P;
static Handle: g_hGFwd_OnVoteRegister;
static Handle: g_hGFwd_OnVoteRegister_Post;
static Handle: g_hGFwd_OnVoteDisplay;
static Handle: g_hGFwd_OnVoteDisplay_Post;
static Handle: g_hGFwd_OnVotePassDisplay;
static Handle: g_hGFwd_OnVotePassDisplay_Post;
static Handle: g_hGFwd_OnVoteFailDisplay;
static Handle: g_hGFwd_OnVoteEnd;
static Handle: g_hGFwd_OnVoteEnd_Post;
static bool: g_bIsVoteInProgress;
static bool: g_bIsVoteInitiatedByConsole;
static bool: g_bIsShowingVoteCaller;
static bool: g_bIsVoteInCooldown;
static g_iVoteInitiator;
static g_iTeamVoting;
static L4DVoteIssue:g_iCurrentVoteIssue;
static Float: g_fVoteStartTime;
static Float: g_fVoteTime;
static Float: g_fDefaultVoteTime;
static String: g_sVoteIssue[256];
static String: g_sVoteParam[256];
static String: g_sVoteInitiatorName[128];
static String: g_sVoteIssueTranslation[256];
static String: g_sVoteParamTranslation[256];
static String: g_sVotePassIssueTranslation[256];
static String: g_sVotePassParamTranslation[256];
static const VOTE_INITIATOR_CONSOLE = 99; // Initiator index when started by console
static const String: VOTE_INITIATOR_CONSOLE_NAME[] = "Console";
static const Float: VOTE_COOLDOWN_TIME = 6.0; // 6 seconds for vote pass/fail to disappear
static bool: g_bCanVote[MAXPLAYERS + 1];
static bool: g_bChangedVoteIncomming[MAXPLAYERS + 1];
static bool: g_bChangedVoteStartIncomming[MAXPLAYERS + 1];
static bool: g_bChangedVotePassIncomming[MAXPLAYERS + 1];
static L4DVote:g_iVote[MAXPLAYERS + 1];
static g_iTotalVoters;
static g_iYesVotes;
static g_iNoVotes;
static UserMsg:g_umVoteStart;
static UserMsg:g_umVotePass;
static UserMsg:g_umVoteFail;
static UserMsg:g_umVoteRegistered;
static Handle: g_hVoteCreationTimer_Cvar;
static Handle: g_hVoteFailedMapLimit_Cvar;
static Handle: g_hVoteShowCaller_Cvar;
static Handle: g_hVoteTimerDuration_Cvar;
static const String: VOTE_CAST_YES[] = "Yes";
static const String: VOTE_CAST_NO[] = "No";
static const MAX_FAILED_VOTE_MAP_LIMIT = 999999; // Any high arbitrary number will do to allow "unlimited" voting
/* Custom vote translation string.
* An empty translation string that contains nothing but %s1 to allow us
* to fill in whatever we desire via the param text. Can be found in
* resource/left4dead2_english.txt at line 729 */
static const String: VOTE_ISSUE_CUSTOM[] = "#L4D_TargetID_Player";
static const VOTE_TEAMBYTE_SIGNED_ALL_TEAMS = 255; // Sourcemod can't read signed bytes from usermessages right now. The value is really -1.
/* List over call vote issue strings, must match L4DVoteIssue enum. */
static const String: CALLVOTE_ISSUE_STRINGS[][] =
{
"",
"ChangeDifficulty",
"RestartGame",
"Kick",
"ChangeMission",
"ReturnToLobby",
"ChangeChapter",
"ChangeAllTalk",
"Custom"
};
/*
* ==================================================
* Forwards
* ==================================================
*/
/**
* Called on plugin start.
*
* @noreturn
*/
public _VoteHandler_OnPluginStart()
{
g_hFwd_OnVoteStart = CreateForward(ET_Hook, Param_Cell, Param_Cell, Param_String);
/* Global forwards */
g_hGFwd_OnVoteStart = CreateGlobalForward("L4D_OnVoteStart", ET_Hook, Param_Cell, Param_Cell, Param_String);
g_hGFwd_OnVoteStart_Post = CreateGlobalForward("L4D_OnVoteStart_Post", ET_Ignore, Param_Cell, Param_Cell, Param_String, Param_Array, Param_Cell, Param_Float);
g_hGFwd_OnAddClientToVotePool = CreateGlobalForward("L4D_OnAddClientToVotePool", ET_Hook, Param_Cell);
g_hGFwd_OnAddClientToVotePool_P = CreateGlobalForward("L4D_OnAddClientToVotePool_Post", ET_Ignore, Param_Cell);
g_hGFwd_OnVoteRegister = CreateGlobalForward("L4D_OnVoteRegister", ET_Hook, Param_Cell, Param_CellByRef);
g_hGFwd_OnVoteRegister_Post = CreateGlobalForward("L4D_OnVoteRegister_Post", ET_Ignore, Param_Cell, Param_Cell);
g_hGFwd_OnVoteDisplay = CreateGlobalForward("L4D_OnVoteDisplay", ET_Hook, Param_Cell, Param_String, Param_Cell, Param_String, Param_Cell, Param_String, Param_Cell);
g_hGFwd_OnVoteDisplay_Post = CreateGlobalForward("L4D_OnVoteDisplay_Post", ET_Ignore, Param_Cell, Param_String, Param_String, Param_String);
g_hGFwd_OnVotePassDisplay = CreateGlobalForward("L4D_OnVotePassDisplay", ET_Hook, Param_Cell, Param_String, Param_Cell, Param_String, Param_Cell);
g_hGFwd_OnVotePassDisplay_Post = CreateGlobalForward("L4D_OnVotePassDisplay_Post", ET_Ignore, Param_Cell, Param_String, Param_String);
g_hGFwd_OnVoteFailDisplay = CreateGlobalForward("L4D_OnVoteFailDisplay", ET_Ignore, Param_Cell);
/* User messages */
g_umVoteStart = GetUserMessageId("VoteStart");
HookUserMessage(g_umVoteStart, _VH_VoteStart_UserMsg, true);
g_umVotePass = GetUserMessageId("VotePass");
HookUserMessage(g_umVotePass, _VH_VotePass_UserMsg, true);
g_umVoteFail = GetUserMessageId("VoteFail");
HookUserMessage(g_umVoteFail, _VH_VoteFail_UserMsg);
g_umVoteRegistered = GetUserMessageId("VoteRegistered");
HookUserMessage(g_umVoteRegistered, _VH_VoteRegistered_UserMsg);
/* Vote command listeners */
AddCommandListener(_VH_OnCallVote_Command, "callvote");
AddCommandListener(_VH_OnVote_Command, "vote");
/* Remove vote creation timer */
g_hVoteCreationTimer_Cvar = FindConVar("sv_vote_creation_timer");
if (g_hVoteCreationTimer_Cvar == INVALID_HANDLE) SetFailState("Unable to find sv_vote_creation_timer cvar.");
SetConVarFloat(g_hVoteCreationTimer_Cvar, 0.0);
HookConVarChange(g_hVoteCreationTimer_Cvar, _VH_VoteCvars_CvarChange);
/* Remove failed vote limit */
g_hVoteFailedMapLimit_Cvar = FindConVar("sv_vote_plr_map_limit");
if (g_hVoteFailedMapLimit_Cvar == INVALID_HANDLE) SetFailState("Unable to find sv_vote_plr_map_limit cvar.");
SetConVarInt(g_hVoteFailedMapLimit_Cvar, MAX_FAILED_VOTE_MAP_LIMIT);
HookConVarChange(g_hVoteFailedMapLimit_Cvar, _VH_VoteCvars_CvarChange);
/* Get vote show caller value */
g_hVoteShowCaller_Cvar = FindConVar("sv_vote_show_caller");
if (g_hVoteShowCaller_Cvar == INVALID_HANDLE) SetFailState("Unable to find sv_vote_show_caller cvar.");
g_bIsShowingVoteCaller = GetConVarBool(g_hVoteShowCaller_Cvar);
HookConVarChange(g_hVoteShowCaller_Cvar, _VH_VoteCvars_CvarChange);
/* Get vote time */
g_hVoteTimerDuration_Cvar = FindConVar("sv_vote_timer_duration");
if (g_hVoteTimerDuration_Cvar == INVALID_HANDLE) SetFailState("Unable to find sv_vote_timer_duration cvar.");
g_fDefaultVoteTime = GetConVarFloat(g_hVoteTimerDuration_Cvar);
HookConVarChange(g_hVoteTimerDuration_Cvar, _VH_VoteCvars_CvarChange);
Debug_PrintTextEx("Module start");
}
/**
* Called on plugin end.
*
* @noreturn
*/
public _VoteHandler_OnPluginEnd()
{
ResetConVar(g_hVoteCreationTimer_Cvar);
ResetConVar(g_hVoteFailedMapLimit_Cvar);
Debug_PrintTextEx("Module end");
}
/**
* Called on vote cvars changed.
*
* @param convar Handle to the convar that was changed.
* @param oldValue String containing the value of the convar before it was changed.
* @param newValue String containing the new value of the convar.
* @noreturn
*/
public _VH_VoteCvars_CvarChange(Handle:convar, const String:oldValue[], const String:newValue[])
{
SetConVarFloat(g_hVoteCreationTimer_Cvar, 0.0);
SetConVarInt(g_hVoteFailedMapLimit_Cvar, MAX_FAILED_VOTE_MAP_LIMIT);
g_bIsShowingVoteCaller = GetConVarBool(g_hVoteShowCaller_Cvar);
g_fDefaultVoteTime = GetConVarFloat(g_hVoteTimerDuration_Cvar);
}
/**
* Called on map start.
*
* @noreturn
*/
public _VH_OnMapStart()
{
g_bIsVoteInProgress = false;
g_bIsVoteInCooldown = false;
for (new i = 0; i <= MAXPLAYERS; i++)
{
g_bCanVote[i] = false;
g_iVote[i] = L4DVote_None;
g_bChangedVoteIncomming[i] = false;
g_bChangedVoteStartIncomming[i] = false;
g_bChangedVotePassIncomming[i] = false;
}
}
/**
* Called on client disconnect.
*
* @parma client Client index.
* @noreturn
*/
public _VH_OnClientDisconnect(client)
{
g_bCanVote[client] = false;
g_iVote[client] = L4DVote_None;
g_bChangedVoteIncomming[client] = false;
g_bChangedVoteStartIncomming[client] = false;
g_bChangedVotePassIncomming[client] = false;
//UpdateVote();
}
/**
* On callvote command.
*
* @param client Client, or 0 for server. Client will be connected but
* not necessarily in game.
* @param command Command name, lower case. To get name as typed, use
* GetCmdArg() and specify argument 0.
* @param argc Argument count.
* @return Returning Plugin_Handled or Plugin_Stop will prevent
* the original, baseline code from running.
*/
public Action:_VH_OnCallVote_Command(client, const String:command[], argc)
{
if (argc < 1) return Plugin_Continue;
decl String:callvote[64];
GetCmdArg(1, callvote, sizeof(callvote));
TrimString(callvote);
new L4DVoteIssue:issueId;
issueId = L4D_GetVoteIssueIdOfCallVote(callvote);
if (issueId == L4DVoteIssue_None) return Plugin_Handled;
if (client == 0)
{
if (issueId != L4DVoteIssue_Custom)
{
PrintToServer("[SM] %T", "Server can only start custom votes", LANG_SERVER);
return Plugin_Handled;
}
else if (g_bIsVoteInProgress)
{
PrintToServer("[SM] %T", "Another vote is already in progress", LANG_SERVER);
return Plugin_Handled;
}
else if (g_bIsVoteInCooldown)
{
PrintToServer("[SM] %T", "Voting is in cooldown", LANG_SERVER);
return Plugin_Handled;
}
}
if (g_bIsVoteInProgress || g_bIsVoteInCooldown) return Plugin_Handled;
g_iCurrentVoteIssue = issueId;
strcopy(g_sVoteIssue, sizeof(g_sVoteIssue), callvote);
decl String:param[256];
GetCmdArg(2, param, sizeof(param));
TrimString(param);
/* Internal forwarding */
Debug_PrintTextEx("Internal forwarding of OnVoteStart. Params %d, %d, %s", client, issueId, param);
new Action:result = Plugin_Continue;
Call_StartForward(g_hFwd_OnVoteStart);
Call_PushCell(client);
Call_PushCell(issueId);
Call_PushString(param);
Call_Finish(result);
Debug_PrintTextEx("result %d", result);
if (result == Plugin_Handled)
{
Debug_PrintTextEx("Internal module returned handled, stopping vote.");
return Plugin_Handled;
}
/* Global forwarding */
Debug_PrintTextEx("Global forwarding of OnVoteStart. Params %d, %d, %s", client, issueId, param);
result = Plugin_Continue;
Call_StartForward(g_hGFwd_OnVoteStart);
Call_PushCell(client);
Call_PushCell(issueId);
Call_PushString(param);
Call_Finish(result);
Debug_PrintTextEx("result %d", result);
if (result == Plugin_Handled)
{
Debug_PrintTextEx("Global forward returned handled, stopping vote.");
return Plugin_Handled;
}
strcopy(g_sVoteParam, 256, param);
if (client != 0)
{
g_bIsVoteInitiatedByConsole = false;
g_iVoteInitiator = GetClientUserId(client);
GetClientName(client, g_sVoteInitiatorName, sizeof(g_sVoteInitiatorName));
}
else
{
g_bIsVoteInitiatedByConsole = true;
g_iVoteInitiator = VOTE_INITIATOR_CONSOLE;
strcopy(g_sVoteInitiatorName, sizeof(g_sVoteInitiatorName), VOTE_INITIATOR_CONSOLE_NAME);
}
if (issueId == L4DVoteIssue_Custom)
{
new numClients = 0;
new clients[MaxClients];
for (new i = 1; i <= MaxClients; i++)
{
if (!IsClientInGame(i) || IsFakeClient(i)) continue;
clients[numClients++] = i;
}
//StartCustomVote(clients, numClients, param, _, client);
if (client == 0)
{
PrintToServer("[SM] %T", "Custom vote started", LANG_SERVER);
}
LogAction(client, -1, "%L started a custom vote with the issue of \"%s\"", client, param);
return Plugin_Handled;
}
return Plugin_Continue;
}
/**
* Callback for vote command listener.
*
* @param client Client, or 0 for server.
* @param command Command name, lower case.
* @param argc Argument count.
* @return Plugin_Handled to stop command from running, anything
* else to allow command.
*/
public Action:_VH_OnVote_Command(client, const String:command[], argc)
{
if (!g_bIsVoteInProgress || argc != 1) return Plugin_Continue;
if (!g_bCanVote[client] || g_iVote[client] != L4DVote_None) return Plugin_Handled; // Client have already voted or cannot vote
decl String:voteRaw[8];
GetCmdArg(1, voteRaw, sizeof(voteRaw));
new L4DVote:vote;
if (StrEqual(voteRaw, VOTE_CAST_YES, false))
{
vote = L4DVote_Yes;
}
else if (StrEqual(voteRaw, VOTE_CAST_NO, false))
{
vote = L4DVote_No;
}
else
{
return Plugin_Handled;
}
if (!g_bChangedVoteIncomming[client])
{
Debug_PrintTextEx("Client %N (%d) casted their vote. Voted %s.", client, client, (vote == L4DVote_Yes ? VOTE_CAST_YES : VOTE_CAST_NO));
new L4DVote:refVote = vote;
new Action:result = Plugin_Continue;
Call_StartForward(g_hGFwd_OnVoteRegister);
Call_PushCell(client);
Call_PushCellRef(refVote);
Call_Finish(result);
if (result == Plugin_Changed && refVote != vote)
{
Debug_PrintTextEx("Forward return changed vote. New vote casted %s.", (vote == L4DVote_Yes ? VOTE_CAST_YES : VOTE_CAST_NO));
vote = refVote;
g_bChangedVoteIncomming[client] = true;
FakeClientCommand(client, "Vote %s", (vote == L4DVote_Yes ? VOTE_CAST_YES : VOTE_CAST_NO));
return Plugin_Handled;
}
else if (result == Plugin_Handled)
{
Debug_PrintTextEx("Forward return handled, stopping registering vote.");
return Plugin_Handled;
}
}
g_iVote[client] = vote;
g_bChangedVoteIncomming[client] = false;
return Plugin_Continue;
}
/**
* Called when vote start is hooked
*
* @param msg_id Message index.
* @param bf Handle to the input bit buffer of the message.
* @param players Array containing player indexes.
* @param playersNum Number of players in the array.
* @param reliable True if message is reliable, false otherwise.
* @param init True if message is an initmsg, false otherwise.
* @return Plugin_Handled blocks the message from being sent, and
* Plugin_Continue resumes normal functionality.
*/
public Action:_VH_VoteStart_UserMsg(UserMsg:msg_id, Handle:bf, const players[], playersNum, bool:reliable, bool:init)
{
/**
* VoteStart Structure
* - Byte Team index voting
* - Byte Vote initiator index
* - String Vote issue id
* - String Vote issue text
* - String Vote initiator name
*/
/* To prevent endless loop when re-sending the modified vote start to
* client */
if (g_bIsVoteInProgress && playersNum == 1 && g_bChangedVoteStartIncomming[players[0]])
{
g_bChangedVoteStartIncomming[players[0]] = false;
return Plugin_Continue;
}
if (g_bIsVoteInProgress || g_bIsVoteInCooldown)
{
return Plugin_Handled;
}
g_bIsVoteInProgress = true;
g_iTeamVoting = BfReadByte(bf);
BfReadByte(bf); // Ignore initiator, already got that
BfReadString(bf, g_sVoteIssueTranslation, sizeof(g_sVoteIssueTranslation), true);
BfReadString(bf, g_sVoteParamTranslation, sizeof(g_sVoteParamTranslation), true);
g_fVoteStartTime = GetEngineTime();
if (g_iCurrentVoteIssue != L4DVoteIssue_Custom)
{
g_fVoteTime = g_fDefaultVoteTime;
}
else
{
g_fVoteTime = g_fDefaultVoteTime;
}
g_iTotalVoters = 0;
g_iNoVotes = 0;
g_iYesVotes = 0;
for (new i = 0; i <= MAXPLAYERS; i++)
{
g_bCanVote[i] = false;
g_iVote[i] = L4DVote_None;
}
new Handle:pack = CreateDataPack();
new client;
new voters[MAXPLAYERS + 1];
for (new i = 0; i < playersNum; i++)
{
client = players[i];
if (client == 0 || !IsClientInGame(client) || IsFakeClient(client)) continue;
voters[g_iTotalVoters++] = client;
}
WritePackCell(pack, g_iTotalVoters);
for (new i = 0; i < g_iTotalVoters; i++)
{
WritePackCell(pack, voters[i]);
}
/* Resend VoteStart usermessage in next frame. */
CreateTimer(0.0, _VH_VoteStart_Timer, pack, TIMER_FLAG_NO_MAPCHANGE | TIMER_DATA_HNDL_CLOSE);
LogAction((g_bIsVoteInitiatedByConsole ? 0 : g_iVoteInitiator), -1, "%L started a %s vote with the param of %s", (g_bIsVoteInitiatedByConsole ? 0 : g_iVoteInitiator), g_sVoteIssue, g_sVoteParam);
Call_StartForward(g_hGFwd_OnVoteStart_Post);
Call_PushCell((g_bIsVoteInitiatedByConsole ? 0 : g_iVoteInitiator));
Call_PushCell(g_iCurrentVoteIssue);
Call_PushString(g_sVoteParam);
Call_PushArray(players, playersNum);
Call_PushCell(playersNum);
Call_PushFloat(g_fVoteTime);
Call_Finish();
return Plugin_Handled;
}
/**
* Called when vote start timer interval has elapsed.
*
* @param timer Handle to the timer object.
* @param hndl Handle to data pack.
* @return Plugin_Stop.
*/
public Action:_VH_VoteStart_Timer(Handle:timer, Handle:pack)
{
ResetPack(pack);
new totalVoters = ReadPackCell(pack);
decl Handle:bf;
new client;
decl String:initiatorName[128], String:voteIssue[256], String:voteParam[256];
decl String:refInitiatorName[128], String:refVoteIssue[256], String:refVoteParam[256];
new Action:result;
for (new i = 0; i < totalVoters; i++)
{
client = ReadPackCell(pack);
if (client == 0 || !IsClientInGame(client) || IsFakeClient(client)) continue;
result = Plugin_Continue;
Call_StartForward(g_hGFwd_OnAddClientToVotePool);
Call_PushCell(client);
Call_Finish(result);
if (result == Plugin_Handled)
{
continue;
}
g_bCanVote[client] = true;
g_iVote[client] = L4DVote_None;
Call_StartForward(g_hGFwd_OnAddClientToVotePool_P);
Call_PushCell(client);
Call_Finish();
/* Reset */
strcopy(initiatorName, 128, g_sVoteInitiatorName);
strcopy(voteIssue, 256, g_sVoteIssueTranslation);
strcopy(voteParam, 256, g_sVoteParamTranslation);
strcopy(refInitiatorName, 128, g_sVoteInitiatorName);
strcopy(refVoteIssue, 256, g_sVoteIssueTranslation);
strcopy(refVoteParam, 256, g_sVoteParamTranslation);
result = Plugin_Continue;
Call_StartForward(g_hGFwd_OnVoteDisplay);
Call_PushCell(client);
Call_PushStringEx(refInitiatorName, sizeof(refInitiatorName), SM_PARAM_STRING_UTF8 | SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK);
Call_PushCell(sizeof(refInitiatorName));
Call_PushStringEx(refVoteIssue, sizeof(refVoteIssue), SM_PARAM_STRING_UTF8 | SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK);
Call_PushCell(sizeof(refVoteIssue));
Call_PushStringEx(refVoteParam, sizeof(refVoteParam), SM_PARAM_STRING_UTF8 | SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK);
Call_PushCell(sizeof(refVoteParam));
Call_Finish(result);
if (result == Plugin_Changed)
{
strcopy(initiatorName, 128, refInitiatorName);
strcopy(voteIssue, 256, refVoteIssue);
strcopy(voteParam, 256, refVoteParam);
}
Call_StartForward(g_hGFwd_OnVoteDisplay_Post);
Call_PushCell(client);
Call_PushString(initiatorName);
Call_PushString(voteIssue);
Call_PushString(voteParam);
Call_Finish();
g_bChangedVoteStartIncomming[client] = true;
bf = StartMessageExOne(g_umVoteStart, client, USERMSG_RELIABLE);
BfWriteByte(bf, g_iTeamVoting);
BfWriteByte(bf, g_iVoteInitiator);
BfWriteString(bf, voteIssue);
BfWriteString(bf, voteParam);
BfWriteString(bf, initiatorName);
EndMessage();
}
return Plugin_Stop;
}
/**
* Called when vote pass is hooked
*
* @param msg_id Message index.
* @param bf Handle to the input bit buffer of the message.
* @param players Array containing player indexes.
* @param playersNum Number of players in the array.
* @param reliable True if message is reliable, false otherwise.
* @param init True if message is an initmsg, false otherwise.
* @return Plugin_Handled blocks the message from being sent, and
* Plugin_Continue resumes normal functionality.
*/
public Action:_VH_VotePass_UserMsg(UserMsg:msg_id, Handle:bf, const players[], playersNum, bool:reliable, bool:init)
{
/**
* VotePass Structure
* - Byte Team index voting
* - String Vote issue id
* - String Vote issue text
*/
/* To prevent endless loop when re-sending the modified vote pass to
* client */
if (!g_bIsVoteInProgress && playersNum == 1 && g_bChangedVotePassIncomming[players[0]])
{
g_bChangedVotePassIncomming[players[0]] = false;
return Plugin_Continue;
}
BfReadByte(bf); // Ignore team index, already got that
BfReadString(bf, g_sVotePassIssueTranslation, sizeof(g_sVotePassIssueTranslation), true);
BfReadString(bf, g_sVotePassParamTranslation, sizeof(g_sVotePassParamTranslation), true);
new Handle:pack = CreateDataPack();
new client;
new voters[MAXPLAYERS + 1];
new count = 0;
for (new i = 0; i < playersNum; i++)
{
client = players[i];
if (client == 0 || !IsClientInGame(client) || IsFakeClient(client) || !g_bCanVote[client]) continue;
voters[count++] = client;
}
WritePackCell(pack, count);
for (new i = 0; i < count; i++)
{
WritePackCell(pack, voters[i]);
}
/* Resend VotePass usermessage in next frame. */
CreateTimer(0.0, _VH_VotePass_Timer, pack, TIMER_FLAG_NO_MAPCHANGE | TIMER_DATA_HNDL_CLOSE);
g_bIsVoteInProgress = false;
if (!g_bIsVoteInCooldown)
{
CreateTimer(VOTE_COOLDOWN_TIME, _VH_VoteCooldown_Timer, _, TIMER_FLAG_NO_MAPCHANGE);
}
g_bIsVoteInCooldown = true;
return Plugin_Handled;
}
/**
* Called when vote pass timer interval has elapsed.
*
* @param timer Handle to the timer object.
* @param hndl Handle to data pack.
* @return Plugin_Stop.
*/
public Action:_VH_VotePass_Timer(Handle:timer, Handle:pack)
{
ResetPack(pack);
new totalVoters = ReadPackCell(pack);
decl Handle:bf;
new client;
decl String:voteIssue[256], String:voteParam[256];
decl String:refVoteIssue[256], String:refVoteParam[256];
new Action:result;
for (new i = 0; i < totalVoters; i++)
{
client = ReadPackCell(pack);
if (client == 0 || !IsClientInGame(client) || IsFakeClient(client)) continue;
/* Reset */
strcopy(voteIssue, 256, g_sVotePassIssueTranslation);
strcopy(voteParam, 256, g_sVotePassParamTranslation);
strcopy(refVoteIssue, 256, g_sVotePassIssueTranslation);
strcopy(refVoteParam, 256, g_sVotePassParamTranslation);
result = Plugin_Continue;
Call_StartForward(g_hGFwd_OnVotePassDisplay);
Call_PushCell(client);
Call_PushStringEx(refVoteIssue, sizeof(refVoteIssue), SM_PARAM_STRING_UTF8 | SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK);
Call_PushCell(sizeof(refVoteIssue));
Call_PushStringEx(refVoteParam, sizeof(refVoteParam), SM_PARAM_STRING_UTF8 | SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK);
Call_PushCell(sizeof(refVoteParam));
Call_Finish(result);
if (result == Plugin_Changed)
{
strcopy(voteIssue, 256, refVoteIssue);
strcopy(voteParam, 256, refVoteParam);
}
Call_StartForward(g_hGFwd_OnVotePassDisplay_Post);
Call_PushCell(client);
Call_PushString(voteIssue);
Call_PushString(voteParam);
Call_Finish();
g_bChangedVotePassIncomming[client] = true;
bf = StartMessageExOne(g_umVotePass, client, USERMSG_RELIABLE);
BfWriteByte(bf, g_iTeamVoting);
BfWriteString(bf, voteIssue);
BfWriteString(bf, voteParam);
EndMessage();
}
return Plugin_Stop;
}
/**
* Called when vote fail is hooked
*
* @param msg_id Message index.
* @param bf Handle to the input bit buffer of the message.
* @param players Array containing player indexes.
* @param playersNum Number of players in the array.
* @param reliable True if message is reliable, false otherwise.
* @param init True if message is an initmsg, false otherwise.
* @return Plugin_Continue.
*/
public Action:_VH_VoteFail_UserMsg(UserMsg:msg_id, Handle:bf, const players[], playersNum, bool:reliable, bool:init)
{
/**
* VoteFail Structure
* - Byte Team index voting
*/
g_bIsVoteInProgress = false;
if (!g_bIsVoteInCooldown)
{
CreateTimer(VOTE_COOLDOWN_TIME, _VH_VoteCooldown_Timer, _, TIMER_FLAG_NO_MAPCHANGE);
}
g_bIsVoteInCooldown = true;
new client;
for (new i = 0; i < playersNum; i++)
{
client = players[i];
if (!g_bCanVote[client]) continue;
Call_StartForward(g_hGFwd_OnVoteFailDisplay);
Call_PushCell(client);
Call_Finish();
}
return Plugin_Continue;
}
/**
* Called when vote registered is hooked
*
* @param msg_id Message index.
* @param bf Handle to the input bit buffer of the message.
* @param players Array containing player indexes.
* @param playersNum Number of players in the array.
* @param reliable True if message is reliable, false otherwise.
* @param init True if message is an initmsg, false otherwise.
* @return Plugin_Continue.
*/
public Action:_VH_VoteRegistered_UserMsg(UserMsg:msg_id, Handle:bf, const players[], playersNum, bool:reliable, bool:init)
{
/**
* VoteRegistered Structure
* - Byte Vote casted. 0: No, 1: Yes
*
* Note: Only the voting client gets this usermessage which causes their
* HUD to fade out the voting choices.
*/
new L4DVote:vote = L4DVote:BfReadByte(bf);
new client = players[0];
Call_StartForward(g_hGFwd_OnVoteRegister_Post);
Call_PushCell(client);
Call_PushCell(vote);
Call_Finish();
return Plugin_Continue;
}
/**
* Called when vote cooldown timer interval has elapsed.
*
* @param timer Handle to the timer object.
* @return Plugin_Stop.
*/
public Action:_VH_VoteCooldown_Timer(Handle:timer)
{
g_bIsVoteInCooldown = false;
return Plugin_Stop;
}
/*
* ==================================================
* Public API
* ==================================================
*/
/**
* Returns whether a vote is in progress.
*
* @return True if a vote is in progress, false otherwise.
*/
stock bool:L4D_IsVoteInProgress()
{
return g_bIsVoteInProgress;
}
/**
* Returns whether vote is in cooldown.
*
* @return True if vote is in cooldown, false otherwise.
*/
stock bool:L4D_IsVoteInCooldown()
{
return g_bIsVoteInCooldown;
}
/**
* Retrives current vote issue id.
*
* @return Current vote issue id.
*/
stock L4DVoteIssue:L4D_GetVoteIssueId()
{
if (!g_bIsVoteInProgress) return L4DVoteIssue_None;
return g_iCurrentVoteIssue;
}
/**
* Retrives vote issue of issue name.
*
* @param callVoteString Call vote string of issue.
* @return Issue id of name.
*/
stock L4DVoteIssue:L4D_GetVoteIssueIdOfCallVote(const String:callVoteString[])
{
for (new i = 0; i < sizeof(CALLVOTE_ISSUE_STRINGS); i++)
{
if (StrEqual(callVoteString, CALLVOTE_ISSUE_STRINGS[i], false)) return L4DVoteIssue:(i - 1);
}
return L4DVoteIssue_None;
}
/**
* Retrives current no votes for the issue.
*
* @return Count of no votes, -1 if no vote is in progress.
*/
stock L4D_GetNoVotes()
{
if (!g_bIsVoteInProgress) return -1;
return g_iNoVotes;
}
/**
* Retrives current yes votes for the issue.
*
* @return Count of yes votes, -1 if no vote is in progress.
*/
stock L4D_GetYesVotes()
{
if (!g_bIsVoteInProgress) return -1;
return g_iYesVotes;
}
/**
* Retrives total voters that can vote.
*
* @return Count of all voters in vote, -1 if no vote is in
* progress.
*/
stock L4D_GetTotalVoters()
{
if (!g_bIsVoteInProgress) return -1;
return g_iTotalVoters;
}
/**
* Returns whether player is able to vote.
*
* @param client Client index.
* @return True if player can vote, false otherwise.
*/
stock bool:L4D_IsPlayerAbleToVote(client)
{
if (!g_bIsVoteInProgress) return false;
return g_bCanVote[client];
}
/**
* Retrives the players vote.
*
* @param client Client index.
* @return Players vote.
*/
stock L4DVote:L4D_GetPlayerVote(client)
{
if (!g_bIsVoteInProgress) return L4DVote_None;
return g_iVote[client];
}
/**
* Set the players vote.
*
* @param clients Client index.
* @param vote Players vote.
* @noreturn
*/
stock L4D_SetPlayerVote(client, L4DVote:vote)
{
if (!g_bIsVoteInProgress) return;
g_iVote = vote;
}
/**
* Returns whether vote caller is being shown.
*
* @return True if vote caller is being shown, false otherwise.
*/
stock bool:L4D_IsShowingVoteCaller()
{
return g_bIsShowingVoteCaller;
}
/**
* Retrives vote start in engine time.
*
* @return Vote start time. -1.0 if no vote is active.
*/
stock Float:L4D_GetVoteStartTime()
{
if (!g_bIsVoteInProgress) return -1.0;
return g_fVoteStartTime;
}
/**
* Retrives the remaining voting time for current vote, in engine time.
*
* @return Remaining vote time, -1.0 if no vote is active.
*/
stock Float:L4D_GetRemainingVoteTime()
{
if (!g_bIsVoteInProgress) return -1.0;
return g_fVoteTime - (GetEngineTime() - g_fVoteStartTime);
}
/**
* Copies the vote issue string to destination string.
*
* @param dest Destination string buffer to copy to.
* @param destlen Destination buffer length (includes null terminator).
* @return Number of cells written, -1 if no vote is in progress.
*/
stock L4D_GetVoteIssueString(String:dest[], destlen)
{
if (!g_bIsVoteInProgress) return -1;
return strcopy(dest, destlen, g_sVoteIssueTranslation);
}
/**
* Copies the vote param string to destination string.
*
* @param dest Destination string buffer to copy to.
* @param destlen Destination buffer length (includes null terminator).
* @return Number of cells written, -1 if no vote is in progress.
*/
stock L4D_GetVoteParamString(String:dest[], destlen)
{
if (!g_bIsVoteInProgress) return -1;
return strcopy(dest, destlen, g_sVoteParamTranslation);
}
/**
* Copies the vote initiator string to destination string.
*
* @param dest Destination string buffer to copy to.
* @param destlen Destination buffer length (includes null terminator).
* @return Number of cells written, -1 if no vote is in progress.
*/
stock L4D_GetVoteInitiatorString(String:dest[], destlen)
{
if (!g_bIsVoteInProgress) return -1;
return strcopy(dest, destlen, g_sVoteInitiatorName);
}
/**
* Hooks the function to the internal OnVoteStart forward.
*
* @param func The function to add.
* @return True on success, false otherwise.
*/
stock bool:HookOnVoteStart(Function:func)
{
return AddToForward(g_hFwd_OnVoteStart, INVALID_HANDLE, func);
}
/**
* Unhooks the function from the internal OnVoteStart forward.
*
* @param func The function to remove.
* @return True on success, false otherwise.
*/
stock bool:UnhookOnVoteStart(Function:func)
{
return RemoveFromForward(g_hFwd_OnVoteStart, INVALID_HANDLE, func);
}
/*
* ==================================================
* Private API
* ==================================================
*/
/**
* Starts a simpler usermessage (network message) for one client.
* @note See StartMessage or StartMessageEx().
*
* @param msg Message index to start.
* @param client Client to send to.
* @param flags Optional flags to set.
* @return A handle to a bf_write bit packing structure, or
* INVALID_HANDLE on failure.
*/
static Handle:StartMessageExOne(UserMsg:msg, client, flags=0)
{
new players[1];
players[0] = client;
return StartMessageEx(msg, players, 1, flags);
}
/**
* Wrapper for Debug_PrintText without having to define debug channel.
*
* @param format Formatting rules.
* @param ... Variable number of format parameters.
* @noreturn
*/
static Debug_PrintTextEx(const String:format[], any:...)
{
#if !defined DEBUG
#pragma unused format
return;
#else
static channel = -1;
if (channel == -1) channel = Debug_AddChannel(DEBUG_CHANNEL_NAME);
decl String:buffer[256];
VFormat(buffer, sizeof(buffer), format, 2);
Debug_PrintText(channel, buffer);
#endif
}
|
|