plugins/basevotes.sp
author David Anderson <dvander@alliedmods.net>
Wed Oct 31 05:09:07 2007 +0000 (2007-10-31)
changeset 1657 2487db52a7fb
parent 1625 8e4a6a8c3256
child 1661 b0a4850a8cc2
permissions -rw-r--r--
fixed build
     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 #undef REQUIRE_PLUGIN
    38 #include <adminmenu>
    39 
    40 public Plugin:myinfo =
    41 {
    42 	name = "Basic Votes",
    43 	author = "AlliedModders LLC",
    44 	description = "Basic Vote Commands",
    45 	version = SOURCEMOD_VERSION,
    46 	url = "http://www.sourcemod.net/"
    47 };
    48 
    49 #define VOTE_NO "###no###"
    50 #define VOTE_YES "###yes###"
    51 
    52 new Handle:g_hVoteMenu = INVALID_HANDLE;
    53 
    54 new Handle:g_hBanForward = INVALID_HANDLE;
    55 
    56 new Handle:g_Cvar_Limits[3] = {INVALID_HANDLE, ...};
    57 //new Handle:g_Cvar_VoteSay = INVALID_HANDLE;
    58 
    59 enum voteType
    60 {
    61 	map,
    62 	kick,
    63 	ban,
    64 	question
    65 }
    66 
    67 new voteType:g_voteType = voteType:question;
    68 
    69 // Menu API does not provide us with a way to pass multiple peices of data with a single
    70 // choice, so some globals are used to hold stuff.
    71 //
    72 #define VOTE_CLIENTID	0
    73 #define VOTE_USERID	1
    74 new g_voteClient[2];		/* Holds the target's client id and user id */
    75 
    76 #define VOTE_NAME	0
    77 #define VOTE_AUTHID	1
    78 #define	VOTE_IP		2
    79 new String:g_voteInfo[3][65];	/* Holds the target's name, authid, and IP */
    80 
    81 new String:g_voteArg[256];	/* Used to hold ban/kick reasons or vote questions */
    82 
    83 
    84 new Handle:hTopMenu = INVALID_HANDLE;
    85 
    86 new Handle:g_MapList = INVALID_HANDLE;
    87 new g_mapFileTime;
    88 new g_mapCount;
    89 
    90 new Handle:g_SelectedMaps;
    91 new g_SelectedCount;
    92 
    93 new bool:g_VoteMapInUse;
    94 
    95 #include "basevotes/votekick.sp"
    96 #include "basevotes/voteban.sp"
    97 #include "basevotes/votemap.sp"
    98 
    99 public OnPluginStart()
   100 {
   101 	LoadTranslations("common.phrases");
   102 	LoadTranslations("basevotes.phrases");
   103 	LoadTranslations("plugin.basecommands");
   104 	LoadTranslations("basebans.phrases");
   105 	
   106 	RegAdminCmd("sm_votemap", Command_Votemap, ADMFLAG_VOTE|ADMFLAG_CHANGEMAP, "sm_votemap <mapname> [mapname2] ... [mapname5] ");
   107 	RegAdminCmd("sm_votekick", Command_Votekick, ADMFLAG_VOTE|ADMFLAG_KICK, "sm_votekick <player> [reason]");
   108 	RegAdminCmd("sm_voteban", Command_Voteban, ADMFLAG_VOTE|ADMFLAG_BAN, "sm_voteban <player> [reason]");
   109 	RegAdminCmd("sm_vote", Command_Vote, ADMFLAG_VOTE, "sm_vote <question> [Answer1] [Answer2] ... [Answer5]");
   110 
   111 	/*
   112 	g_Cvar_Show = FindConVar("sm_vote_show");
   113 	if (g_Cvar_Show == INVALID_HANDLE)
   114 	{
   115 		g_Cvar_Show = CreateConVar("sm_vote_show", "1", "Show player's votes? Default on.", 0, true, 0.0, true, 1.0);
   116 	}
   117 	*/
   118 
   119 	g_Cvar_Limits[0] = CreateConVar("sm_vote_map", "0.60", "percent required for successful map vote.", 0, true, 0.05, true, 1.0);
   120 	g_Cvar_Limits[1] = CreateConVar("sm_vote_kick", "0.60", "percent required for successful kick vote.", 0, true, 0.05, true, 1.0);	
   121 	g_Cvar_Limits[2] = CreateConVar("sm_vote_ban", "0.60", "percent required for successful ban vote.", 0, true, 0.05, true, 1.0);		
   122 	
   123 	g_hBanForward = CreateGlobalForward("OnClientBanned", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_String);
   124 	
   125 	/* Account for late loading */
   126 	new Handle:topmenu;
   127 	if (LibraryExists("adminmenu") && ((topmenu = GetAdminTopMenu()) != INVALID_HANDLE))
   128 	{
   129 		OnAdminMenuReady(topmenu);
   130 	}
   131 	
   132 	g_SelectedMaps = CreateArray(33);
   133 	
   134 	g_MapList = CreateMenu(MenuHandler_Map);
   135 	SetMenuTitle(g_MapList, "Please select a map");
   136 	SetMenuExitBackButton(g_MapList, true);
   137 }
   138 
   139 public OnMapStart()
   140 {
   141 	g_mapCount = LoadMapList(g_MapList);
   142 }
   143 
   144 public OnAdminMenuReady(Handle:topmenu)
   145 {
   146 	/* Block us from being called twice */
   147 	if (topmenu == hTopMenu)
   148 	{
   149 		return;
   150 	}
   151 	
   152 	/* Save the Handle */
   153 	hTopMenu = topmenu;
   154 	
   155 	/* Build the "Voting Commands" category */
   156 	new TopMenuObject:voting_commands = FindTopMenuCategory(hTopMenu, ADMINMENU_VOTINGCOMMANDS);
   157 
   158 	if (voting_commands != INVALID_TOPMENUOBJECT)
   159 	{
   160 		AddToTopMenu(hTopMenu,
   161 			"sm_votekick",
   162 			TopMenuObject_Item,
   163 			AdminMenu_VoteKick,
   164 			voting_commands,
   165 			"sm_votekick",
   166 			ADMFLAG_VOTE|ADMFLAG_KICK);
   167 			
   168 		AddToTopMenu(hTopMenu,
   169 			"sm_voteban",
   170 			TopMenuObject_Item,
   171 			AdminMenu_VoteBan,
   172 			voting_commands,
   173 			"sm_voteban",
   174 			ADMFLAG_VOTE|ADMFLAG_BAN);
   175 			
   176 		AddToTopMenu(hTopMenu,
   177 			"sm_votemap",
   178 			TopMenuObject_Item,
   179 			AdminMenu_VoteMap,
   180 			voting_commands,
   181 			"sm_votemap",
   182 			ADMFLAG_VOTE|ADMFLAG_CHANGEMAP);
   183 	}
   184 }
   185 
   186 public Action:Command_Vote(client, args)
   187 {
   188 	if (args < 1)
   189 	{
   190 		ReplyToCommand(client, "[SM] Usage: sm_vote <question> [Answer1] [Answer2] ... [Answer5]");
   191 		return Plugin_Handled;	
   192 	}
   193 	
   194 	if (IsVoteInProgress())
   195 	{
   196 		ReplyToCommand(client, "[SM] %t", "Vote in Progress");
   197 		return Plugin_Handled;
   198 	}
   199 		
   200 	if (!TestVoteDelay(client))
   201 	{
   202 		return Plugin_Handled;
   203 	}
   204 	
   205 	decl String:text[256];
   206 	GetCmdArgString(text, sizeof(text));
   207 
   208 	decl String:answers[5][64];
   209 	new answerCount;	
   210 	new len = BreakString(text, g_voteArg, sizeof(g_voteArg));
   211 	new pos = len;
   212 	
   213 	while (args > 1 && pos != -1 && answerCount < 5)
   214 	{	
   215 		pos = BreakString(text[len], answers[answerCount], sizeof(answers[]));
   216 		answerCount++;
   217 		
   218 		if (pos != -1)
   219 		{
   220 			len += pos;
   221 		}	
   222 	}
   223 
   224 	LogAction(client, -1, "\"%L\" initiated a generic vote.", client);
   225 	ShowActivity2(client, "[SM] ", "%t", "Initiate Vote", g_voteArg);
   226 	
   227 	g_voteType = voteType:question;
   228 	
   229 	g_hVoteMenu = CreateMenu(Handler_VoteCallback, MenuAction:MENU_ACTIONS_ALL);
   230 	SetMenuTitle(g_hVoteMenu, "%s?", g_voteArg);
   231 	
   232 	if (answerCount < 2)
   233 	{
   234 		AddMenuItem(g_hVoteMenu, VOTE_YES, "Yes");
   235 		AddMenuItem(g_hVoteMenu, VOTE_NO, "No");
   236 	}
   237 	else
   238 	{
   239 		for (new i = 0; i < answerCount; i++)
   240 		{
   241 			AddMenuItem(g_hVoteMenu, answers[i], answers[i]);
   242 		}	
   243 	}
   244 	
   245 	SetMenuExitButton(g_hVoteMenu, false);
   246 	VoteMenuToAll(g_hVoteMenu, 20);		
   247 	
   248 	return Plugin_Handled;	
   249 }
   250 
   251 public Handler_VoteCallback(Handle:menu, MenuAction:action, param1, param2)
   252 {
   253 	if (action == MenuAction_End)
   254 	{
   255 		VoteMenuClose();
   256 	}
   257 	else if (action == MenuAction_Display)
   258 	{
   259 	 	if (g_voteType != voteType:question)
   260 	 	{
   261 			decl String:title[64];
   262 			GetMenuTitle(menu, title, sizeof(title));
   263 			
   264 	 		decl String:buffer[255];
   265 			Format(buffer, sizeof(buffer), "%T", title, param1, g_voteInfo[VOTE_NAME]);
   266 
   267 			new Handle:panel = Handle:param2;
   268 			SetPanelTitle(panel, buffer);
   269 		}
   270 	}
   271 	else if (action == MenuAction_DisplayItem)
   272 	{
   273 		decl String:display[64];
   274 		GetMenuItem(menu, param2, "", 0, _, display, sizeof(display));
   275 	 
   276 	 	if (strcmp(display, "No") == 0 || strcmp(display, "Yes") == 0)
   277 	 	{
   278 			decl String:buffer[255];
   279 			Format(buffer, sizeof(buffer), "%T", display, param1);
   280 
   281 			return RedrawMenuItem(buffer);
   282 		}
   283 	}
   284 	/* else if (action == MenuAction_Select)
   285 	{
   286 		VoteSelect(menu, param1, param2);
   287 	}*/
   288 	else if (action == MenuAction_VoteCancel && param1 == VoteCancel_NoVotes)
   289 	{
   290 		PrintToChatAll("[SM] %t", "No Votes Cast");
   291 	}	
   292 	else if (action == MenuAction_VoteEnd)
   293 	{
   294 		decl String:item[64], String:display[64];
   295 		new Float:percent, Float:limit, votes, totalVotes;
   296 
   297 		GetMenuVoteInfo(param2, votes, totalVotes);
   298 		GetMenuItem(menu, param1, item, sizeof(item), _, display, sizeof(display));
   299 		
   300 		if (strcmp(item, VOTE_NO) == 0 && param1 == 1)
   301 		{
   302 			votes = totalVotes - votes; // Reverse the votes to be in relation to the Yes option.
   303 		}
   304 		
   305 		percent = GetVotePercent(votes, totalVotes);
   306 		
   307 		if (g_voteType != voteType:question)
   308 		{
   309 			limit = GetConVarFloat(g_Cvar_Limits[g_voteType]);
   310 		}
   311 		
   312 		/* :TODO: g_voteClient[userid] needs to be checked */
   313 
   314 		// A multi-argument vote is "always successful", but have to check if its a Yes/No vote.
   315 		if ((strcmp(item, VOTE_YES) == 0 && FloatCompare(percent,limit) < 0 && param1 == 0) || (strcmp(item, VOTE_NO) == 0 && param1 == 1))
   316 		{
   317 			/* :TODO: g_voteClient[userid] should be used here and set to -1 if not applicable.
   318 			 */
   319 			LogAction(-1, -1, "Vote failed.");
   320 			PrintToChatAll("[SM] %t", "Vote Failed", RoundToNearest(100.0*limit), RoundToNearest(100.0*percent), totalVotes);
   321 		}
   322 		else
   323 		{
   324 			PrintToChatAll("[SM] %t", "Vote Successful", RoundToNearest(100.0*percent), totalVotes);
   325 			
   326 			switch (g_voteType)
   327 			{
   328 				case (voteType:question):
   329 				{
   330 					if (strcmp(item, VOTE_NO) == 0 || strcmp(item, VOTE_YES) == 0)
   331 					{
   332 						strcopy(item, sizeof(item), display);
   333 					}
   334 					
   335 					PrintToChatAll("[SM] %t", "Vote End", g_voteArg, item);
   336 				}
   337 				
   338 				case (voteType:map):
   339 				{
   340 					LogAction(-1, -1, "Changing map to %s due to vote.", item);
   341 					PrintToChatAll("[SM] %t", "Changing map", item);
   342 					new Handle:dp;
   343 					CreateDataTimer(5.0, Timer_ChangeMap, dp);
   344 					WritePackString(dp, item);		
   345 				}
   346 					
   347 				case (voteType:kick):
   348 				{
   349 					if (g_voteArg[0] == '\0')
   350 					{
   351 						strcopy(g_voteArg, sizeof(g_voteArg), "Votekicked");
   352 					}
   353 					
   354 					PrintToChatAll("[SM] %t", "Kicked target", "_s", g_voteInfo[VOTE_NAME]);					
   355 					LogAction(-1, g_voteClient[VOTE_CLIENTID], "Vote kick successful, kicked \"%L\" (reason \"%s\")", g_voteClient[VOTE_CLIENTID], g_voteArg);
   356 					
   357 					ServerCommand("kickid %d \"%s\"", g_voteClient[VOTE_USERID], g_voteArg);					
   358 				}
   359 					
   360 				case (voteType:ban):
   361 				{
   362 					/* Fire the ban forward */
   363 					Call_StartForward(g_hBanForward);
   364 					Call_PushCell(0);
   365 					Call_PushCell(g_voteClient[VOTE_USERID]);
   366 					Call_PushCell(30);
   367 					Call_PushString(g_voteArg);
   368 					Call_Finish();
   369 				
   370 					if (g_voteArg[0] == '\0')
   371 					{
   372 						strcopy(g_voteArg, sizeof(g_voteArg), "Votebanned");
   373 					}
   374 					
   375 					PrintToChatAll("[SM] %t", "Banned player", g_voteInfo[VOTE_NAME], 30);
   376 					LogAction(-1, g_voteClient[VOTE_CLIENTID], "Vote ban successful, banned \"%L\" (minutes \"30\") (reason \"%s\")", g_voteClient[VOTE_CLIENTID], g_voteArg);
   377 					
   378 					ServerCommand("banid %d %s", 30, g_voteClient[VOTE_AUTHID]);
   379 					ServerCommand("kickid %d \"%s\"", g_voteClient[VOTE_USERID], g_voteArg);				
   380 				}
   381 			}
   382 		}
   383 	}
   384 	
   385 	return 0;
   386 }
   387 
   388 /*
   389 VoteSelect(Handle:menu, param1, param2 = 0)
   390 {
   391 	if (GetConVarInt(g_Cvar_VoteShow) == 1)
   392 	{
   393 		decl String:voter[64], String:junk[64], String:choice[64];
   394 		GetClientName(param1, voter, sizeof(voter));
   395 		GetMenuItem(menu, param2, junk, sizeof(junk), _, choice, sizeof(choice));
   396 		PrintToChatAll("[SM] %T", "Vote Select", LANG_SERVER, voter, choice);
   397 	}
   398 }
   399 */
   400 
   401 VoteMenuClose()
   402 {
   403 	CloseHandle(g_hVoteMenu);
   404 	g_hVoteMenu = INVALID_HANDLE;
   405 }
   406 
   407 Float:GetVotePercent(votes, totalVotes)
   408 {
   409 	return FloatDiv(float(votes),float(totalVotes));
   410 }
   411 
   412 bool:TestVoteDelay(client)
   413 {
   414  	new delay = CheckVoteDelay();
   415  	
   416  	if (delay > 0)
   417  	{
   418  		if (delay > 60)
   419  		{
   420  			ReplyToCommand(client, "[SM] %t", "Vote Delay Minutes", delay % 60);
   421  		}
   422  		else
   423  		{
   424  			ReplyToCommand(client, "[SM] %t", "Vote Delay Seconds", delay);
   425  		}
   426  		
   427  		return false;
   428  	}
   429  	
   430 	return true;
   431 }
   432 
   433 public Action:Timer_ChangeMap(Handle:timer, Handle:dp)
   434 {
   435 	decl String:mapname[65];
   436 	
   437 	ResetPack(dp);
   438 	ReadPackString(dp, mapname, sizeof(mapname));
   439 	
   440 	ServerCommand("changelevel \"%s\"", mapname);
   441 	
   442 	return Plugin_Stop;
   443 }