1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/plugins/mapchooser.sp Sun Mar 30 07:00:22 2008 +0000
1.3 @@ -0,0 +1,539 @@
1.4 +/**
1.5 + * vim: set ts=4 :
1.6 + * =============================================================================
1.7 + * SourceMod Mapchooser Plugin
1.8 + * Creates a map vote at appropriate times, setting sm_nextmap to the winning
1.9 + * vote
1.10 + *
1.11 + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved.
1.12 + * =============================================================================
1.13 + *
1.14 + * This program is free software; you can redistribute it and/or modify it under
1.15 + * the terms of the GNU General Public License, version 3.0, as published by the
1.16 + * Free Software Foundation.
1.17 + *
1.18 + * This program is distributed in the hope that it will be useful, but WITHOUT
1.19 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
1.20 + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
1.21 + * details.
1.22 + *
1.23 + * You should have received a copy of the GNU General Public License along with
1.24 + * this program. If not, see <http://www.gnu.org/licenses/>.
1.25 + *
1.26 + * As a special exception, AlliedModders LLC gives you permission to link the
1.27 + * code of this program (as well as its derivative works) to "Half-Life 2," the
1.28 + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
1.29 + * by the Valve Corporation. You must obey the GNU General Public License in
1.30 + * all respects for all other code used. Additionally, AlliedModders LLC grants
1.31 + * this exception to all derivative works. AlliedModders LLC defines further
1.32 + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
1.33 + * or <http://www.sourcemod.net/license.php>.
1.34 + *
1.35 + * Version: $Id$
1.36 + */
1.37 +
1.38 +#pragma semicolon 1
1.39 +#include <sourcemod>
1.40 +
1.41 +public Plugin:myinfo =
1.42 +{
1.43 + name = "MapChooser",
1.44 + author = "AlliedModders LLC",
1.45 + description = "Automated Map Voting",
1.46 + version = SOURCEMOD_VERSION,
1.47 + url = "http://www.sourcemod.net/"
1.48 +};
1.49 +
1.50 +new Handle:g_Cvar_Winlimit = INVALID_HANDLE;
1.51 +new Handle:g_Cvar_Maxrounds = INVALID_HANDLE;
1.52 +new Handle:g_Cvar_Fraglimit = INVALID_HANDLE;
1.53 +
1.54 +new Handle:g_Cvar_Nextmap = INVALID_HANDLE;
1.55 +
1.56 +new Handle:g_Cvar_StartTime = INVALID_HANDLE;
1.57 +new Handle:g_Cvar_StartRounds = INVALID_HANDLE;
1.58 +new Handle:g_Cvar_StartFrags = INVALID_HANDLE;
1.59 +new Handle:g_Cvar_ExtendTimeStep = INVALID_HANDLE;
1.60 +new Handle:g_Cvar_ExtendRoundStep = INVALID_HANDLE;
1.61 +new Handle:g_Cvar_ExtendFragStep = INVALID_HANDLE;
1.62 +new Handle:g_Cvar_ExcludeMaps = INVALID_HANDLE;
1.63 +new Handle:g_Cvar_IncludeMaps = INVALID_HANDLE;
1.64 +new Handle:g_Cvar_NoVoteMode = INVALID_HANDLE;
1.65 +new Handle:g_Cvar_Extend = INVALID_HANDLE;
1.66 +
1.67 +new Handle:g_VoteTimer = INVALID_HANDLE;
1.68 +new Handle:g_RetryTimer = INVALID_HANDLE;
1.69 +
1.70 +new Handle:g_MapList = INVALID_HANDLE;
1.71 +new Handle:g_OldMapList = INVALID_HANDLE;
1.72 +new Handle:g_NextMapList = INVALID_HANDLE;
1.73 +new Handle:g_TeamScores = INVALID_HANDLE;
1.74 +new Handle:g_VoteMenu = INVALID_HANDLE;
1.75 +
1.76 +new g_Extends;
1.77 +new g_TotalRounds;
1.78 +new bool:g_HasVoteStarted;
1.79 +new g_mapFileSerial = -1;
1.80 +
1.81 +#define VOTE_EXTEND "##extend##"
1.82 +
1.83 +public OnPluginStart()
1.84 +{
1.85 + LoadTranslations("mapchooser.phrases");
1.86 +
1.87 + new arraySize = ByteCountToCells(33);
1.88 + g_MapList = CreateArray(arraySize);
1.89 + g_OldMapList = CreateArray(arraySize);
1.90 + g_NextMapList = CreateArray(arraySize);
1.91 +
1.92 + g_Cvar_StartTime = CreateConVar("sm_mapvote_start", "3.0", "Specifies when to start the vote based on time remaining.", _, true, 1.0);
1.93 + g_Cvar_StartRounds = CreateConVar("sm_mapvote_startround", "2.0", "Specifies when to start the vote based on rounds remaining.", _, true, 1.0);
1.94 + g_Cvar_StartFrags = CreateConVar("sm_mapvote_startfrags", "5.0", "Specifies when to start the vote base on frags remaining.", _, true, 1.0);
1.95 + g_Cvar_ExtendTimeStep = CreateConVar("sm_extendmap_timestep", "15", "Specifies how much many more minutes each extension makes", _, true, 5.0);
1.96 + g_Cvar_ExtendRoundStep = CreateConVar("sm_extendmap_roundstep", "5", "Specifies how many more rounds each extension makes", _, true, 5.0);
1.97 + g_Cvar_ExtendFragStep = CreateConVar("sm_extendmap_fragstep", "10", "Specifies how many more frags are allowed when map is extended.", _, true, 5.0);
1.98 + g_Cvar_ExcludeMaps = CreateConVar("sm_mapvote_exclude", "5", "Specifies how many past maps to exclude from the vote.", _, true, 0.0);
1.99 + g_Cvar_IncludeMaps = CreateConVar("sm_mapvote_include", "5", "Specifies how many maps to include in the vote.", _, true, 2.0, true, 6.0);
1.100 + g_Cvar_NoVoteMode = CreateConVar("sm_mapvote_novote", "1", "Specifies whether or not MapChooser should pick a map if no votes are received.", _, true, 0.0, true, 1.0);
1.101 + g_Cvar_Extend = CreateConVar("sm_mapvote_extend", "0", "Number of extensions allowed each map.", _, true, 0.0);
1.102 +
1.103 + RegAdminCmd("sm_mapvote", Command_Mapvote, ADMFLAG_CHANGEMAP, "sm_mapvote - Forces MapChooser to attempt to run a map vote now.");
1.104 +
1.105 + g_Cvar_Winlimit = FindConVar("mp_winlimit");
1.106 + g_Cvar_Maxrounds = FindConVar("mp_maxrounds");
1.107 + g_Cvar_Fraglimit = FindConVar("mp_fraglimit");
1.108 +
1.109 + if (g_Cvar_Winlimit != INVALID_HANDLE || g_Cvar_Maxrounds != INVALID_HANDLE)
1.110 + {
1.111 + HookEvent("round_end", Event_RoundEnd);
1.112 + }
1.113 +
1.114 + if (g_Cvar_Fraglimit != INVALID_HANDLE)
1.115 + {
1.116 + HookEvent("player_death", Event_PlayerDeath);
1.117 + }
1.118 +
1.119 + AutoExecConfig(true, "mapchooser");
1.120 +}
1.121 +
1.122 +public OnConfigsExecuted()
1.123 +{
1.124 + g_Cvar_Nextmap = FindConVar("sm_nextmap");
1.125 +
1.126 + if (g_Cvar_Nextmap == INVALID_HANDLE)
1.127 + {
1.128 + LogError("FATAL: Cannot find sm_nextmap cvar. Mapchooser not loaded.");
1.129 + SetFailState("sm_nextmap not found");
1.130 + }
1.131 +
1.132 + if (ReadMapList(g_MapList,
1.133 + g_mapFileSerial,
1.134 + "mapchooser",
1.135 + MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER)
1.136 + != INVALID_HANDLE)
1.137 +
1.138 + {
1.139 + if (g_mapFileSerial == -1)
1.140 + {
1.141 + LogError("Unable to create a valid map list.");
1.142 + }
1.143 + }
1.144 +
1.145 + CreateNextVote();
1.146 + SetupTimeleftTimer();
1.147 + SetConVarString(g_Cvar_Nextmap, "Pending Vote");
1.148 +
1.149 + if (g_TeamScores != INVALID_HANDLE)
1.150 + {
1.151 + CloseHandle(g_TeamScores);
1.152 + }
1.153 +
1.154 + g_TeamScores = CreateArray(2);
1.155 + g_TotalRounds = 0;
1.156 +
1.157 + g_Extends = 0;
1.158 +}
1.159 +
1.160 +public OnMapEnd()
1.161 +{
1.162 + g_HasVoteStarted = false;
1.163 +
1.164 + if (g_VoteTimer != INVALID_HANDLE)
1.165 + {
1.166 + KillTimer(g_VoteTimer);
1.167 + g_VoteTimer = INVALID_HANDLE;
1.168 + }
1.169 +
1.170 + if (g_RetryTimer != INVALID_HANDLE)
1.171 + {
1.172 + KillTimer(g_RetryTimer);
1.173 + g_RetryTimer = INVALID_HANDLE;
1.174 + }
1.175 +}
1.176 +
1.177 +public OnMapTimeLeftChanged()
1.178 +{
1.179 + if (GetArraySize(g_MapList))
1.180 + {
1.181 + SetupTimeleftTimer();
1.182 + }
1.183 +}
1.184 +
1.185 +SetupTimeleftTimer()
1.186 +{
1.187 + new time;
1.188 + if (GetMapTimeLeft(time) && time > 0)
1.189 + {
1.190 + new startTime = GetConVarInt(g_Cvar_StartTime) * 60;
1.191 + if (time - startTime < 0)
1.192 + {
1.193 + InitiateVote();
1.194 + }
1.195 + else
1.196 + {
1.197 + if (g_VoteTimer != INVALID_HANDLE)
1.198 + {
1.199 + KillTimer(g_VoteTimer);
1.200 + g_VoteTimer = INVALID_HANDLE;
1.201 + }
1.202 +
1.203 + g_VoteTimer = CreateTimer(float(time - startTime), Timer_StartMapVote);
1.204 + }
1.205 + }
1.206 +}
1.207 +
1.208 +public Action:Timer_StartMapVote(Handle:timer)
1.209 +{
1.210 + if (!GetArraySize(g_MapList))
1.211 + {
1.212 + return Plugin_Stop;
1.213 + }
1.214 +
1.215 + if (timer == g_RetryTimer)
1.216 + {
1.217 + g_RetryTimer = INVALID_HANDLE;
1.218 + }
1.219 + else
1.220 + {
1.221 + g_VoteTimer = INVALID_HANDLE;
1.222 + }
1.223 +
1.224 + InitiateVote();
1.225 +
1.226 + return Plugin_Stop;
1.227 +}
1.228 +
1.229 +/* You ask, why don't you just use team_score event? And I answer... Because CSS doesn't. */
1.230 +public Event_RoundEnd(Handle:event, const String:name[], bool:dontBroadcast)
1.231 +{
1.232 + if (!GetArraySize(g_MapList) || g_HasVoteStarted)
1.233 + {
1.234 + return;
1.235 + }
1.236 +
1.237 + new winner = GetEventInt(event, "winner");
1.238 +
1.239 + if (winner == 0 || winner == 1)
1.240 + {
1.241 + return;
1.242 + }
1.243 +
1.244 + g_TotalRounds++;
1.245 +
1.246 + new team[2], teamPos = -1;
1.247 + for (new i; i < GetArraySize(g_TeamScores); i++)
1.248 + {
1.249 + GetArrayArray(g_TeamScores, i, team);
1.250 + if (team[0] == winner)
1.251 + {
1.252 + teamPos = i;
1.253 + break;
1.254 + }
1.255 + }
1.256 +
1.257 + if (teamPos == -1)
1.258 + {
1.259 + team[0] = winner;
1.260 + team[1] = 1;
1.261 + PushArrayArray(g_TeamScores, team);
1.262 + }
1.263 + else
1.264 + {
1.265 + team[1]++;
1.266 + SetArrayArray(g_TeamScores, teamPos, team);
1.267 + }
1.268 +
1.269 + if (g_Cvar_Winlimit != INVALID_HANDLE)
1.270 + {
1.271 + new winlimit = GetConVarInt(g_Cvar_Winlimit);
1.272 + if (winlimit)
1.273 + {
1.274 + if (team[1] >= (winlimit - GetConVarInt(g_Cvar_StartRounds)))
1.275 + {
1.276 + InitiateVote();
1.277 + }
1.278 + }
1.279 + }
1.280 +
1.281 + if (g_Cvar_Maxrounds != INVALID_HANDLE)
1.282 + {
1.283 + new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
1.284 + if (maxrounds)
1.285 + {
1.286 + if (g_TotalRounds >= (maxrounds - GetConVarInt(g_Cvar_StartRounds)))
1.287 + {
1.288 + InitiateVote();
1.289 + }
1.290 + }
1.291 + }
1.292 +}
1.293 +
1.294 +public Event_PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast)
1.295 +{
1.296 + if (!GetArraySize(g_MapList) || g_Cvar_Fraglimit == INVALID_HANDLE || g_HasVoteStarted)
1.297 + {
1.298 + return;
1.299 + }
1.300 +
1.301 + if (!GetConVarInt(g_Cvar_Fraglimit))
1.302 + {
1.303 + return;
1.304 + }
1.305 +
1.306 + new fragger = GetClientOfUserId(GetEventInt(event, "attacker"));
1.307 + if (fragger && GetClientFrags(fragger) >= (GetConVarInt(g_Cvar_Fraglimit) - GetConVarInt(g_Cvar_StartFrags)))
1.308 + {
1.309 + InitiateVote();
1.310 + }
1.311 +}
1.312 +
1.313 +public Action:Command_Mapvote(client, args)
1.314 +{
1.315 + InitiateVote();
1.316 +
1.317 + return Plugin_Handled;
1.318 +}
1.319 +
1.320 +InitiateVote()
1.321 +{
1.322 + if (g_HasVoteStarted || g_RetryTimer != INVALID_HANDLE)
1.323 + {
1.324 + return;
1.325 + }
1.326 +
1.327 + if (IsVoteInProgress())
1.328 + {
1.329 + // Can't start a vote, try again in 5 seconds.
1.330 + g_RetryTimer = CreateTimer(5.0, Timer_StartMapVote);
1.331 + return;
1.332 + }
1.333 +
1.334 + g_HasVoteStarted = true;
1.335 + g_VoteMenu = CreateMenu(Handler_MapVoteMenu, MenuAction:MENU_ACTIONS_ALL);
1.336 + SetMenuTitle(g_VoteMenu, "Vote Nextmap");
1.337 +
1.338 + decl String:map[32];
1.339 + for (new i = 0; i < GetArraySize(g_NextMapList); i++)
1.340 + {
1.341 + GetArrayString(g_NextMapList, i, map, sizeof(map));
1.342 + AddMenuItem(g_VoteMenu, map, map);
1.343 + }
1.344 +
1.345 + if (GetConVarBool(g_Cvar_Extend) && g_Extends < GetConVarInt(g_Cvar_Extend))
1.346 + {
1.347 + AddMenuItem(g_VoteMenu, VOTE_EXTEND, "Extend Map");
1.348 + }
1.349 +
1.350 + SetMenuExitButton(g_VoteMenu, false);
1.351 + VoteMenuToAll(g_VoteMenu, 20);
1.352 +
1.353 + LogMessage("Voting for next map has started.");
1.354 + PrintToChatAll("[SM] %t", "Nextmap Voting Started");
1.355 +}
1.356 +
1.357 +public Handler_MapVoteMenu(Handle:menu, MenuAction:action, param1, param2)
1.358 +{
1.359 + switch (action)
1.360 + {
1.361 + case MenuAction_End:
1.362 + {
1.363 + g_VoteMenu = INVALID_HANDLE;
1.364 + CloseHandle(menu);
1.365 + }
1.366 +
1.367 + case MenuAction_Display:
1.368 + {
1.369 + decl String:buffer[255];
1.370 + Format(buffer, sizeof(buffer), "%T", "Vote Nextmap", param1);
1.371 +
1.372 + new Handle:panel = Handle:param2;
1.373 + SetPanelTitle(panel, buffer);
1.374 + }
1.375 +
1.376 + case MenuAction_DisplayItem:
1.377 + {
1.378 + if (GetMenuItemCount(menu) - 1 == param2)
1.379 + {
1.380 + decl String:map[64], String:buffer[255];
1.381 + GetMenuItem(menu, param2, map, sizeof(map));
1.382 + if (strcmp(map, VOTE_EXTEND, false) == 0)
1.383 + {
1.384 + Format(buffer, sizeof(buffer), "%T", "Extend Map", param1);
1.385 + return RedrawMenuItem(buffer);
1.386 + }
1.387 + }
1.388 + }
1.389 +
1.390 + // Why am I commented out? Because BAIL hasn't decided yet if
1.391 + // vote notification will be built into the Vote API.
1.392 + /*case MenuAction_Select:
1.393 + {
1.394 + decl String:Name[32], String:Map[32];
1.395 + GetClientName(param1, Name, sizeof(Name));
1.396 + GetMenuItem(menu, param2, Map, sizeof(Map));
1.397 +
1.398 + PrintToChatAll("[SM] %s has voted for map '%s'", Name, Map);
1.399 + }*/
1.400 +
1.401 + case MenuAction_VoteCancel:
1.402 + {
1.403 + // If we receive 0 votes, pick at random.
1.404 + if (param1 == VoteCancel_NoVotes && GetConVarBool(g_Cvar_NoVoteMode))
1.405 + {
1.406 + new count = GetMenuItemCount(menu);
1.407 + new item = GetRandomInt(0, count - 1);
1.408 + decl String:map[32];
1.409 + GetMenuItem(menu, item, map, sizeof(map));
1.410 +
1.411 + while (strcmp(map, VOTE_EXTEND, false) == 0)
1.412 + {
1.413 + item = GetRandomInt(0, count - 1);
1.414 + GetMenuItem(menu, item, map, sizeof(map));
1.415 + }
1.416 +
1.417 + SetNextMap(map);
1.418 + }
1.419 + else
1.420 + {
1.421 + // We were actually cancelled. What should we do?
1.422 + }
1.423 + }
1.424 +
1.425 + case MenuAction_VoteEnd:
1.426 + {
1.427 + decl String:map[32];
1.428 + GetMenuItem(menu, param1, map, sizeof(map));
1.429 +
1.430 + if (strcmp(map, VOTE_EXTEND, false) == 0)
1.431 + {
1.432 + g_Extends++;
1.433 +
1.434 + new time;
1.435 + if (GetMapTimeLimit(time))
1.436 + {
1.437 + if (time > 0)
1.438 + {
1.439 + ExtendMapTimeLimit(GetConVarInt(g_Cvar_ExtendTimeStep)*60);
1.440 + }
1.441 + }
1.442 +
1.443 + if (g_Cvar_Winlimit != INVALID_HANDLE)
1.444 + {
1.445 + new winlimit = GetConVarInt(g_Cvar_Winlimit);
1.446 + if (winlimit)
1.447 + {
1.448 + SetConVarInt(g_Cvar_Winlimit, winlimit + GetConVarInt(g_Cvar_ExtendRoundStep));
1.449 + }
1.450 + }
1.451 +
1.452 + if (g_Cvar_Maxrounds != INVALID_HANDLE)
1.453 + {
1.454 + new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
1.455 + if (maxrounds)
1.456 + {
1.457 + SetConVarInt(g_Cvar_Maxrounds, maxrounds + GetConVarInt(g_Cvar_ExtendRoundStep));
1.458 + }
1.459 + }
1.460 +
1.461 + if (g_Cvar_Fraglimit != INVALID_HANDLE)
1.462 + {
1.463 + new fraglimit = GetConVarInt(g_Cvar_Fraglimit);
1.464 + if (fraglimit)
1.465 + {
1.466 + SetConVarInt(g_Cvar_Fraglimit, fraglimit + GetConVarInt(g_Cvar_ExtendFragStep));
1.467 + }
1.468 + }
1.469 +
1.470 + PrintToChatAll("[SM] %t", "Current Map Extended");
1.471 + LogMessage("Voting for next map has finished. The current map has been extended.");
1.472 +
1.473 + // We extended, so we'll have to vote again.
1.474 + g_HasVoteStarted = false;
1.475 + CreateNextVote();
1.476 + SetupTimeleftTimer();
1.477 + }
1.478 + else
1.479 + {
1.480 + SetNextMap(map);
1.481 + }
1.482 + }
1.483 + }
1.484 +
1.485 + return 0;
1.486 +}
1.487 +
1.488 +SetNextMap(const String:map[])
1.489 +{
1.490 + SetConVarString(g_Cvar_Nextmap, map);
1.491 + PushArrayString(g_OldMapList, map);
1.492 +
1.493 + if (GetArraySize(g_OldMapList) > GetConVarInt(g_Cvar_ExcludeMaps))
1.494 + {
1.495 + RemoveFromArray(g_OldMapList, 0);
1.496 + }
1.497 +
1.498 + PrintToChatAll("[SM] %t", "Nextmap Voting Finished", map);
1.499 + LogMessage("Voting for next map has finished. Nextmap: %s.", map);
1.500 +}
1.501 +
1.502 +CreateNextVote()
1.503 +{
1.504 + if(g_NextMapList != INVALID_HANDLE)
1.505 + {
1.506 + ClearArray(g_NextMapList);
1.507 + }
1.508 +
1.509 + decl String:map[32];
1.510 + new index, Handle:tempMaps = CloneArray(g_MapList);
1.511 +
1.512 + GetCurrentMap(map, sizeof(map));
1.513 + index = FindStringInArray(tempMaps, map);
1.514 + if (index != -1)
1.515 + {
1.516 + RemoveFromArray(tempMaps, index);
1.517 + }
1.518 +
1.519 + if (GetConVarInt(g_Cvar_ExcludeMaps) && GetArraySize(tempMaps) > GetConVarInt(g_Cvar_ExcludeMaps))
1.520 + {
1.521 + for (new i = 0; i < GetArraySize(g_OldMapList); i++)
1.522 + {
1.523 + GetArrayString(g_OldMapList, i, map, sizeof(map));
1.524 + index = FindStringInArray(tempMaps, map);
1.525 + if (index != -1)
1.526 + {
1.527 + RemoveFromArray(tempMaps, index);
1.528 + }
1.529 + }
1.530 + }
1.531 +
1.532 + new limit = (GetConVarInt(g_Cvar_IncludeMaps) < GetArraySize(tempMaps) ? GetConVarInt(g_Cvar_IncludeMaps) : GetArraySize(tempMaps));
1.533 + for (new i = 0; i < limit; i++)
1.534 + {
1.535 + new b = GetRandomInt(0, GetArraySize(tempMaps) - 1);
1.536 + GetArrayString(tempMaps, b, map, sizeof(map));
1.537 + PushArrayString(g_NextMapList, map);
1.538 + RemoveFromArray(tempMaps, b);
1.539 + }
1.540 +
1.541 + CloseHandle(tempMaps);
1.542 +}
1.543 \ No newline at end of file