plugins/basevotes.sp
author David Anderson <dvander@alliedmods.net>
Wed Sep 26 14:38:40 2007 +0000 (2007-09-26)
changeset 1488 5562086c65c8
parent 1378 115ba755bcdf
child 1536 fe2146e4975d
permissions -rw-r--r--
initial import of UNTESTED, UNFINISHED topmenus extension
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod Basic Votes Plugin
     5  * Implements basic vote commands.
     6  *
     7  * SourceMod (C)2004-2007 AlliedModders LLC.  All rights reserved.
     8  * =============================================================================
     9  *
    10  * This program is free software; you can redistribute it and/or modify it under
    11  * the terms of the GNU General Public License, version 3.0, as published by the
    12  * Free Software Foundation.
    13  * 
    14  * This program is distributed in the hope that it will be useful, but WITHOUT
    15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    16  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    17  * details.
    18  *
    19  * You should have received a copy of the GNU General Public License along with
    20  * this program.  If not, see <http://www.gnu.org/licenses/>.
    21  *
    22  * As a special exception, AlliedModders LLC gives you permission to link the
    23  * code of this program (as well as its derivative works) to "Half-Life 2," the
    24  * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
    25  * by the Valve Corporation.  You must obey the GNU General Public License in
    26  * all respects for all other code used.  Additionally, AlliedModders LLC grants
    27  * this exception to all derivative works.  AlliedModders LLC defines further
    28  * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
    29  * or <http://www.sourcemod.net/license.php>.
    30  *
    31  * Version: $Id$
    32  */
    33 
    34 #pragma semicolon 1
    35 
    36 #include <sourcemod>
    37 
    38 public Plugin:myinfo =
    39 {
    40 	name = "Basic Votes",
    41 	author = "AlliedModders LLC",
    42 	description = "Basic Vote Commands",
    43 	version = SOURCEMOD_VERSION,
    44 	url = "http://www.sourcemod.net/"
    45 };
    46 
    47 #define VOTE_NO "###no###"
    48 #define VOTE_YES "###yes###"
    49 
    50 new Handle:g_hVoteMenu = INVALID_HANDLE;
    51 
    52 new Handle:g_hBanForward = INVALID_HANDLE;
    53 
    54 new Handle:g_Cvar_Limits[3] = {INVALID_HANDLE, ...};
    55 //new Handle:g_Cvar_VoteSay = INVALID_HANDLE;
    56 
    57 enum voteType
    58 {
    59 	map,
    60 	kick,
    61 	ban,
    62 	question
    63 }
    64 
    65 new voteType:g_voteType = voteType:question;
    66 
    67 // Menu API does not provide us with a way to pass multiple peices of data with a single
    68 // choice, so some globals are used to hold stuff.
    69 //
    70 #define VOTE_CLIENTID	0
    71 #define VOTE_USERID	1
    72 new g_voteClient[2];		/* Holds the target's client id and user id */
    73 
    74 #define VOTE_NAME	0
    75 #define VOTE_AUTHID	1
    76 #define	VOTE_IP		2
    77 new String:g_voteInfo[3][65];	/* Holds the target's name, authid, and IP */
    78 
    79 new String:g_voteArg[256];	/* Used to hold ban/kick reasons or vote questions */
    80 
    81 
    82 public OnPluginStart()
    83 {
    84 	LoadTranslations("common.phrases");
    85 	LoadTranslations("basevotes.phrases");
    86 	
    87 	RegAdminCmd("sm_votemap", Command_Votemap, ADMFLAG_VOTE|ADMFLAG_CHANGEMAP, "sm_votemap <mapname> [mapname2] ... [mapname5] ");
    88 	RegAdminCmd("sm_votekick", Command_Votekick, ADMFLAG_VOTE|ADMFLAG_KICK, "sm_votekick <player> [reason]");
    89 	RegAdminCmd("sm_voteban", Command_Voteban, ADMFLAG_VOTE|ADMFLAG_BAN, "sm_voteban <player> [reason]");
    90 	RegAdminCmd("sm_vote", Command_Vote, ADMFLAG_VOTE, "sm_vote <question> [Answer1] [Answer2] ... [Answer5]");
    91 
    92 	/*
    93 	g_Cvar_Show = FindConVar("sm_vote_show");
    94 	if (g_Cvar_Show == INVALID_HANDLE)
    95 	{
    96 		g_Cvar_Show = CreateConVar("sm_vote_show", "1", "Show player's votes? Default on.", 0, true, 0.0, true, 1.0);
    97 	}
    98 	*/
    99 
   100 	g_Cvar_Limits[0] = CreateConVar("sm_vote_map", "0.60", "percent required for successful map vote.", 0, true, 0.05, true, 1.0);
   101 	g_Cvar_Limits[1] = CreateConVar("sm_vote_kick", "0.60", "percent required for successful kick vote.", 0, true, 0.05, true, 1.0);	
   102 	g_Cvar_Limits[2] = CreateConVar("sm_vote_ban", "0.60", "percent required for successful ban vote.", 0, true, 0.05, true, 1.0);		
   103 	
   104 	g_hBanForward = CreateGlobalForward("OnClientBanned", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_String);
   105 }
   106 
   107 public Action:Command_Votemap(client, args)
   108 {
   109 	if (args < 1)
   110 	{
   111 		ReplyToCommand(client, "[SM] Usage: sm_votemap <mapname> [mapname2] ... [mapname5]");
   112 		return Plugin_Handled;	
   113 	}
   114 	
   115 	if (IsVoteInProgress())
   116 	{
   117 		ReplyToCommand(client, "[SM] %t", "Vote in Progress");
   118 		return Plugin_Handled;
   119 	}
   120 		
   121 	if (!TestVoteDelay(client))
   122 	{
   123 		return Plugin_Handled;
   124 	}
   125 	
   126 	decl String:text[256];
   127 	GetCmdArgString(text, sizeof(text));
   128 
   129 	decl String:maps[5][64];
   130 	new mapCount;	
   131 	new len, pos;
   132 	
   133 	while (pos != -1 && mapCount < 5)
   134 	{	
   135 		pos = BreakString(text[len], maps[mapCount], sizeof(maps[]));
   136 		
   137 		if (!IsMapValid(maps[mapCount]))
   138 		{
   139 			ReplyToCommand(client, "[SM] %t", "Map was not found", maps[mapCount]);
   140 			return Plugin_Handled;
   141 		}		
   142 
   143 		mapCount++;
   144 		
   145 		if (pos != -1)
   146 		{
   147 			len += pos;
   148 		}	
   149 	}
   150 
   151 	LogAction(client, -1, "\"%L\" initiated a map vote.", client);
   152 	ShowActivity(client, "%t", "Initiated Vote Map");
   153 	
   154 	g_voteType = voteType:map;
   155 	
   156 	g_hVoteMenu = CreateMenu(Handler_VoteCallback, MenuAction:MENU_ACTIONS_ALL);
   157 	
   158 	if (mapCount == 1)
   159 	{
   160 		strcopy(g_voteInfo[VOTE_NAME], sizeof(g_voteInfo[]), maps[0]);
   161 			
   162 		SetMenuTitle(g_hVoteMenu, "Change Map To");
   163 		AddMenuItem(g_hVoteMenu, maps[0], "Yes");
   164 		AddMenuItem(g_hVoteMenu, VOTE_NO, "No");
   165 	}
   166 	else
   167 	{
   168 		g_voteInfo[VOTE_NAME][0] = '\0';
   169 		
   170 		SetMenuTitle(g_hVoteMenu, "Map Vote");
   171 		for (new i = 0; i < mapCount; i++)
   172 		{
   173 			AddMenuItem(g_hVoteMenu, maps[i], maps[i]);
   174 		}	
   175 	}
   176 	
   177 	SetMenuExitButton(g_hVoteMenu, false);
   178 	VoteMenuToAll(g_hVoteMenu, 20);		
   179 	
   180 	return Plugin_Handled;	
   181 }
   182 
   183 public Action:Command_Vote(client, args)
   184 {
   185 	if (args < 1)
   186 	{
   187 		ReplyToCommand(client, "[SM] Usage: sm_vote <question> [Answer1] [Answer2] ... [Answer5]");
   188 		return Plugin_Handled;	
   189 	}
   190 	
   191 	if (IsVoteInProgress())
   192 	{
   193 		ReplyToCommand(client, "[SM] %t", "Vote in Progress");
   194 		return Plugin_Handled;
   195 	}
   196 		
   197 	if (!TestVoteDelay(client))
   198 	{
   199 		return Plugin_Handled;
   200 	}
   201 	
   202 	decl String:text[256];
   203 	GetCmdArgString(text, sizeof(text));
   204 
   205 	decl String:answers[5][64];
   206 	new answerCount;	
   207 	new len = BreakString(text, g_voteArg, sizeof(g_voteArg));
   208 	new pos = len;
   209 	
   210 	while (args > 1 && pos != -1 && answerCount < 5)
   211 	{	
   212 		pos = BreakString(text[len], answers[answerCount], sizeof(answers[]));
   213 		answerCount++;
   214 		
   215 		if (pos != -1)
   216 		{
   217 			len += pos;
   218 		}	
   219 	}
   220 
   221 	LogAction(client, -1, "\"%L\" initiated a generic vote.", client);
   222 	ShowActivity(client, "%t", "Initiate Vote", g_voteArg);
   223 	
   224 	g_voteType = voteType:question;
   225 	
   226 	g_hVoteMenu = CreateMenu(Handler_VoteCallback, MenuAction:MENU_ACTIONS_ALL);
   227 	SetMenuTitle(g_hVoteMenu, "%s?", g_voteArg);
   228 	
   229 	if (answerCount < 2)
   230 	{
   231 		AddMenuItem(g_hVoteMenu, VOTE_YES, "Yes");
   232 		AddMenuItem(g_hVoteMenu, VOTE_NO, "No");
   233 	}
   234 	else
   235 	{
   236 		for (new i = 0; i < answerCount; i++)
   237 		{
   238 			AddMenuItem(g_hVoteMenu, answers[i], answers[i]);
   239 		}	
   240 	}
   241 	
   242 	SetMenuExitButton(g_hVoteMenu, false);
   243 	VoteMenuToAll(g_hVoteMenu, 20);		
   244 	
   245 	return Plugin_Handled;	
   246 }
   247 
   248 public Action:Command_Votekick(client, args)
   249 {
   250 	if (args < 1)
   251 	{
   252 		ReplyToCommand(client, "[SM] Usage: sm_votekick <player> [reason]");
   253 		return Plugin_Handled;	
   254 	}
   255 	
   256 	if (IsVoteInProgress())
   257 	{
   258 		ReplyToCommand(client, "[SM] %t", "Vote in Progress");
   259 		return Plugin_Handled;
   260 	}	
   261 	
   262 	if (!TestVoteDelay(client))
   263 	{
   264 		return Plugin_Handled;
   265 	}
   266 	
   267 	decl String:text[256], String:arg[64];
   268 	GetCmdArgString(text, sizeof(text));
   269 	
   270 	new len = BreakString(text, arg, sizeof(arg));
   271 	
   272 	new target = FindTarget(client, arg);
   273 	if (target == -1)
   274 	{
   275 		return Plugin_Handled;
   276 	}
   277 	
   278 	if (len != -1)
   279 	{
   280 		strcopy(g_voteArg, sizeof(g_voteArg), text[len]);
   281 	}
   282 	else
   283 	{
   284 		g_voteArg[0] = '\0';
   285 	}
   286 	
   287 	g_voteClient[VOTE_CLIENTID] = target;
   288 	g_voteClient[VOTE_USERID] = GetClientUserId(target);
   289 
   290 	GetClientName(target, g_voteInfo[VOTE_NAME], sizeof(g_voteInfo[]));
   291 
   292 	LogAction(client, target, "\"%L\" initiated a kick vote against \"%L\"", client, target);
   293 	ShowActivity(client, "%t", "Initiated Vote Kick", g_voteInfo[VOTE_NAME]);
   294 	
   295 	g_voteType = voteType:kick;
   296 	
   297 	g_hVoteMenu = CreateMenu(Handler_VoteCallback, MenuAction:MENU_ACTIONS_ALL);
   298 	SetMenuTitle(g_hVoteMenu, "Votekick Player");
   299 	AddMenuItem(g_hVoteMenu, VOTE_YES, "Yes");
   300 	AddMenuItem(g_hVoteMenu, VOTE_NO, "No");
   301 	SetMenuExitButton(g_hVoteMenu, false);
   302 	VoteMenuToAll(g_hVoteMenu, 20);
   303 	
   304 	return Plugin_Handled;
   305 }
   306 
   307 public Action:Command_Voteban(client, args)
   308 {
   309 	if (args < 1)
   310 	{
   311 		ReplyToCommand(client, "[SM] Usage: sm_voteban <player> [reason]");
   312 		return Plugin_Handled;	
   313 	}
   314 	
   315 	if (IsVoteInProgress())
   316 	{
   317 		ReplyToCommand(client, "[SM] %t", "Vote in Progress");
   318 		return Plugin_Handled;
   319 	}	
   320 	
   321 	if (!TestVoteDelay(client))
   322 	{
   323 		return Plugin_Handled;
   324 	}
   325 	
   326 	decl String:text[256], String:arg[64];
   327 	GetCmdArgString(text, sizeof(text));
   328 	
   329 	new len = BreakString(text, arg, sizeof(arg));
   330 	
   331 	new target = FindTarget(client, arg);
   332 	if (target == -1)
   333 	{
   334 		return Plugin_Handled;
   335 	}
   336 	
   337 	if (len != -1)
   338 	{
   339 		strcopy(g_voteArg, sizeof(g_voteArg), text[len]);
   340 	}
   341 	else
   342 	{
   343 		g_voteArg[0] = '\0';
   344 	}
   345 	
   346 	g_voteClient[VOTE_CLIENTID] = target;
   347 	g_voteClient[VOTE_USERID] = GetClientUserId(target);
   348 
   349 	GetClientName(target, g_voteInfo[VOTE_NAME], sizeof(g_voteInfo[]));
   350 	GetClientAuthString(target, g_voteInfo[VOTE_AUTHID], sizeof(g_voteInfo[]));
   351 	GetClientIP(target, g_voteInfo[VOTE_IP], sizeof(g_voteInfo[]));
   352 
   353 	LogAction(client, target, "\"%L\" initiated a ban vote against \"%L\"", client, target);
   354 	ShowActivity(client, "%t", "Initiated Vote Ban", g_voteInfo[VOTE_NAME]);
   355 
   356 	g_voteType = voteType:ban;
   357 	
   358 	g_hVoteMenu = CreateMenu(Handler_VoteCallback, MenuAction:MENU_ACTIONS_ALL);
   359 	SetMenuTitle(g_hVoteMenu, "Voteban Player");
   360 	AddMenuItem(g_hVoteMenu, VOTE_YES, "Yes");
   361 	AddMenuItem(g_hVoteMenu, VOTE_NO, "No");
   362 	SetMenuExitButton(g_hVoteMenu, false);
   363 	VoteMenuToAll(g_hVoteMenu, 20);
   364 	
   365 	return Plugin_Handled;
   366 }
   367 
   368 public Handler_VoteCallback(Handle:menu, MenuAction:action, param1, param2)
   369 {
   370 	if (action == MenuAction_End)
   371 	{
   372 		VoteMenuClose();
   373 	}
   374 	else if (action == MenuAction_Display)
   375 	{
   376 	 	if (g_voteType != voteType:question)
   377 	 	{
   378 			decl String:title[64];
   379 			GetMenuTitle(menu, title, sizeof(title));
   380 			
   381 	 		decl String:buffer[255];
   382 			Format(buffer, sizeof(buffer), "%T", title, param1, g_voteInfo[VOTE_NAME]);
   383 
   384 			new Handle:panel = Handle:param2;
   385 			SetPanelTitle(panel, buffer);
   386 		}
   387 	}
   388 	else if (action == MenuAction_DisplayItem)
   389 	{
   390 		decl String:display[64];
   391 		GetMenuItem(menu, param2, "", 0, _, display, sizeof(display));
   392 	 
   393 	 	if (strcmp(display, "No") == 0 || strcmp(display, "Yes") == 0)
   394 	 	{
   395 			decl String:buffer[255];
   396 			Format(buffer, sizeof(buffer), "%T", display, param1);
   397 
   398 			return RedrawMenuItem(buffer);
   399 		}
   400 	}
   401 	/* else if (action == MenuAction_Select)
   402 	{
   403 		VoteSelect(menu, param1, param2);
   404 	}*/
   405 	else if (action == MenuAction_VoteCancel && param1 == VoteCancel_NoVotes)
   406 	{
   407 		PrintToChatAll("[SM] %t", "No Votes Cast");
   408 	}	
   409 	else if (action == MenuAction_VoteEnd)
   410 	{
   411 		decl String:item[64], String:display[64];
   412 		new Float:percent, Float:limit, votes, totalVotes;
   413 
   414 		GetMenuVoteInfo(param2, votes, totalVotes);
   415 		GetMenuItem(menu, param1, item, sizeof(item), _, display, sizeof(display));
   416 		
   417 		if (strcmp(item, VOTE_NO) == 0 && param1 == 1)
   418 		{
   419 			votes = totalVotes - votes; // Reverse the votes to be in relation to the Yes option.
   420 		}
   421 		
   422 		percent = GetVotePercent(votes, totalVotes);
   423 		
   424 		if (g_voteType != voteType:question)
   425 		{
   426 			limit = GetConVarFloat(g_Cvar_Limits[g_voteType]);
   427 		}
   428 		
   429 		/* :TODO: g_voteClient[userid] needs to be checked */
   430 
   431 		// A multi-argument vote is "always successful", but have to check if its a Yes/No vote.
   432 		if ((strcmp(item, VOTE_YES) == 0 && FloatCompare(percent,limit) < 0 && param1 == 0) || (strcmp(item, VOTE_NO) == 0 && param1 == 1))
   433 		{
   434 			/* :TODO: g_voteClient[userid] should be used here and set to -1 if not applicable.
   435 			 */
   436 			LogAction(-1, -1, "Vote failed.");
   437 			PrintToChatAll("[SM] %t", "Vote Failed", RoundToNearest(100.0*limit), RoundToNearest(100.0*percent), totalVotes);
   438 		}
   439 		else
   440 		{
   441 			PrintToChatAll("[SM] %t", "Vote Successful", RoundToNearest(100.0*percent), totalVotes);
   442 			
   443 			switch (g_voteType)
   444 			{
   445 				case (voteType:question):
   446 				{
   447 					if (strcmp(item, VOTE_NO) == 0 || strcmp(item, VOTE_YES) == 0)
   448 					{
   449 						strcopy(item, sizeof(item), display);
   450 					}
   451 					
   452 					PrintToChatAll("[SM] %t", "Vote End", g_voteArg, item);
   453 				}
   454 				
   455 				case (voteType:map):
   456 				{
   457 					LogAction(-1, -1, "Changing map to %s due to vote.", item);
   458 					PrintToChatAll("[SM] %t", "Changing map", item);
   459 					new Handle:dp;
   460 					CreateDataTimer(5.0, Timer_ChangeMap, dp);
   461 					WritePackString(dp, item);		
   462 				}
   463 					
   464 				case (voteType:kick):
   465 				{
   466 					if (g_voteArg[0] == '\0')
   467 					{
   468 						strcopy(g_voteArg, sizeof(g_voteArg), "Votekicked");
   469 					}
   470 					
   471 					PrintToChatAll("[SM] %t", "Kicked player", g_voteInfo[VOTE_NAME]);					
   472 					LogAction(-1, g_voteClient[VOTE_CLIENTID], "Vote kick successful, kicked \"%L\" (reason \"%s\")", g_voteClient[VOTE_CLIENTID], g_voteArg);
   473 					
   474 					ServerCommand("kickid %d \"%s\"", g_voteClient[VOTE_USERID], g_voteArg);					
   475 				}
   476 					
   477 				case (voteType:ban):
   478 				{
   479 					/* Fire the ban forward */
   480 					Call_StartForward(g_hBanForward);
   481 					Call_PushCell(0);
   482 					Call_PushCell(g_voteClient[VOTE_USERID]);
   483 					Call_PushCell(30);
   484 					Call_PushString(g_voteArg);
   485 					Call_Finish();
   486 				
   487 					if (g_voteArg[0] == '\0')
   488 					{
   489 						strcopy(g_voteArg, sizeof(g_voteArg), "Votebanned");
   490 					}
   491 					
   492 					PrintToChatAll("[SM] %t", "Banned player", g_voteInfo[VOTE_NAME], 30);
   493 					LogAction(-1, g_voteClient[VOTE_CLIENTID], "Vote ban successful, banned \"%L\" (minutes \"30\") (reason \"%s\")", g_voteClient[VOTE_CLIENTID], g_voteArg);
   494 					
   495 					ServerCommand("banid %d %s", 30, g_voteClient[VOTE_AUTHID]);
   496 					ServerCommand("kickid %d \"%s\"", g_voteClient[VOTE_USERID], g_voteArg);				
   497 				}
   498 			}
   499 		}
   500 	}
   501 	
   502 	return 0;
   503 }
   504 
   505 /*
   506 VoteSelect(Handle:menu, param1, param2 = 0)
   507 {
   508 	if (GetConVarInt(g_Cvar_VoteShow) == 1)
   509 	{
   510 		decl String:voter[64], String:junk[64], String:choice[64];
   511 		GetClientName(param1, voter, sizeof(voter));
   512 		GetMenuItem(menu, param2, junk, sizeof(junk), _, choice, sizeof(choice));
   513 		PrintToChatAll("[SM] %T", "Vote Select", LANG_SERVER, voter, choice);
   514 	}
   515 }
   516 */
   517 
   518 VoteMenuClose()
   519 {
   520 	CloseHandle(g_hVoteMenu);
   521 	g_hVoteMenu = INVALID_HANDLE;
   522 }
   523 
   524 Float:GetVotePercent(votes, totalVotes)
   525 {
   526 	return FloatDiv(float(votes),float(totalVotes));
   527 }
   528 
   529 bool:TestVoteDelay(client)
   530 {
   531  	new delay = CheckVoteDelay();
   532  	
   533  	if (delay > 0)
   534  	{
   535  		if (delay > 60)
   536  		{
   537  			ReplyToCommand(client, "[SM] %t", "Vote Delay Minutes", delay % 60);
   538  		}
   539  		else
   540  		{
   541  			ReplyToCommand(client, "[SM] %t", "Vote Delay Seconds", delay);
   542  		}
   543  		
   544  		return false;
   545  	}
   546  	
   547 	return true;
   548 }
   549 
   550 public Action:Timer_ChangeMap(Handle:timer, Handle:dp)
   551 {
   552 	decl String:mapname[65];
   553 	
   554 	ResetPack(dp);
   555 	ReadPackString(dp, mapname, sizeof(mapname));
   556 	
   557 	ServerCommand("changelevel \"%s\"", mapname);
   558 	
   559 	return Plugin_Stop;
   560 }