sourcemod/plugins/mapchooser.sp
David Anderson 377888276e Update to the new SourcePawn compiler.
This imports the brand new SourcePawn compiler. The new compiler is much
faster to compile and generates significantly improved code around
array generation and array access.

There are a number of compatibility changes in the new compiler. Most of
these are due to improved type checking and error detection. The full
list of notes can be found here:

https://github.com/alliedmodders/sourcepawn/blob/master/docs/upgrading-1.11.md

Additionally, .smx files generated by the new compiler will NOT load on
earlier versions of SourceMod, including earlier versions of 1.11. Old
plugins will continue to load as normal.
2023-08-30 22:08:45 +02:00

1296 lines
35 KiB
SourcePawn

/**
* vim: set ts=4 :
* =============================================================================
* SourceMod Mapchooser Plugin
* Creates a map vote at appropriate times, setting sm_nextmap to the winning
* vote
*
* SourceMod (C)2004-2014 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* 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>.
*
* Version: $Id$
*/
#pragma semicolon 1
#include <sourcemod>
#include <mapchooser>
#include <nextmap>
#pragma newdecls required
public Plugin myinfo =
{
name = "MapChooser",
author = "AlliedModders LLC",
description = "Automated Map Voting",
version = SOURCEMOD_VERSION,
url = "http://www.sourcemod.net/"
};
/* Valve ConVars */
ConVar g_Cvar_Winlimit;
ConVar g_Cvar_Maxrounds;
ConVar g_Cvar_Fraglimit;
ConVar g_Cvar_Bonusroundtime;
/* Plugin ConVars */
ConVar g_Cvar_StartTime;
ConVar g_Cvar_StartRounds;
ConVar g_Cvar_StartFrags;
ConVar g_Cvar_ExtendTimeStep;
ConVar g_Cvar_ExtendRoundStep;
ConVar g_Cvar_ExtendFragStep;
ConVar g_Cvar_ExcludeMaps;
ConVar g_Cvar_IncludeMaps;
ConVar g_Cvar_PersistentMaps;
ConVar g_Cvar_NoVoteMode;
ConVar g_Cvar_Extend;
ConVar g_Cvar_DontChange;
ConVar g_Cvar_EndOfMapVote;
ConVar g_Cvar_VoteDuration;
ConVar g_Cvar_RunOff;
ConVar g_Cvar_RunOffPercent;
Handle g_VoteTimer = null;
Handle g_RetryTimer = null;
// g_MapList stores unresolved names so we can resolve them after every map change in the workshop updates.
// g_OldMapList and g_NextMapList are resolved. g_NominateList depends on the nominations implementation.
/* Data Handles */
ArrayList g_MapList;
ArrayList g_NominateList;
ArrayList g_NominateOwners;
ArrayList g_OldMapList;
ArrayList g_NextMapList;
Menu g_VoteMenu;
int g_Extends;
int g_TotalRounds;
bool g_HasVoteStarted;
bool g_WaitingForVote;
bool g_MapVoteCompleted;
bool g_ChangeMapAtRoundEnd;
bool g_ChangeMapInProgress;
int g_mapFileSerial = -1;
MapChange g_ChangeTime;
GlobalForward g_NominationsResetForward;
GlobalForward g_MapVoteStartedForward;
/* Upper bound of how many team there could be */
#define MAXTEAMS 10
int g_winCount[MAXTEAMS];
#define VOTE_EXTEND "##extend##"
#define VOTE_DONTCHANGE "##dontchange##"
public void OnPluginStart()
{
LoadTranslations("mapchooser.phrases");
LoadTranslations("common.phrases");
int arraySize = ByteCountToCells(PLATFORM_MAX_PATH);
g_MapList = new ArrayList(arraySize);
g_NominateList = new ArrayList(arraySize);
g_NominateOwners = new ArrayList();
g_OldMapList = new ArrayList(arraySize);
g_NextMapList = new ArrayList(arraySize);
g_Cvar_EndOfMapVote = CreateConVar("sm_mapvote_endvote", "1", "Specifies if MapChooser should run an end of map vote", _, true, 0.0, true, 1.0);
g_Cvar_StartTime = CreateConVar("sm_mapvote_start", "3.0", "Specifies when to start the vote based on time remaining.", _, true, 1.0);
g_Cvar_StartRounds = CreateConVar("sm_mapvote_startround", "2.0", "Specifies when to start the vote based on rounds remaining. Use 0 on TF2 to start vote during bonus round time", _, true, 0.0);
g_Cvar_StartFrags = CreateConVar("sm_mapvote_startfrags", "5.0", "Specifies when to start the vote base on frags remaining.", _, true, 1.0);
g_Cvar_ExtendTimeStep = CreateConVar("sm_extendmap_timestep", "15", "Specifies how much many more minutes each extension makes", _, true, 5.0);
g_Cvar_ExtendRoundStep = CreateConVar("sm_extendmap_roundstep", "5", "Specifies how many more rounds each extension makes", _, true, 1.0);
g_Cvar_ExtendFragStep = CreateConVar("sm_extendmap_fragstep", "10", "Specifies how many more frags are allowed when map is extended.", _, true, 5.0);
g_Cvar_ExcludeMaps = CreateConVar("sm_mapvote_exclude", "5", "Specifies how many past maps to exclude from the vote.", _, true, 0.0);
g_Cvar_IncludeMaps = CreateConVar("sm_mapvote_include", "5", "Specifies how many maps to include in the vote.", _, true, 2.0, true, 6.0);
g_Cvar_PersistentMaps = CreateConVar("sm_mapvote_persistentmaps", "0", "Specifies if previous maps should be stored persistently.", _, true, 0.0, true, 1.0);
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);
g_Cvar_Extend = CreateConVar("sm_mapvote_extend", "0", "Number of extensions allowed each map.", _, true, 0.0);
g_Cvar_DontChange = CreateConVar("sm_mapvote_dontchange", "1", "Specifies if a 'Don't Change' option should be added to early votes", _, true, 0.0);
g_Cvar_VoteDuration = CreateConVar("sm_mapvote_voteduration", "20", "Specifies how long the mapvote should be available for.", _, true, 5.0);
g_Cvar_RunOff = CreateConVar("sm_mapvote_runoff", "0", "Hold runoff votes if winning choice is less than a certain margin", _, true, 0.0, true, 1.0);
g_Cvar_RunOffPercent = CreateConVar("sm_mapvote_runoffpercent", "50", "If winning choice has less than this percent of votes, hold a runoff", _, true, 0.0, true, 100.0);
RegAdminCmd("sm_mapvote", Command_Mapvote, ADMFLAG_CHANGEMAP, "sm_mapvote - Forces MapChooser to attempt to run a map vote now.");
RegAdminCmd("sm_setnextmap", Command_SetNextmap, ADMFLAG_CHANGEMAP, "sm_setnextmap <map>");
g_Cvar_Winlimit = FindConVar("mp_winlimit");
g_Cvar_Maxrounds = FindConVar("mp_maxrounds");
g_Cvar_Fraglimit = FindConVar("mp_fraglimit");
g_Cvar_Bonusroundtime = FindConVar("mp_bonusroundtime");
if (g_Cvar_Winlimit || g_Cvar_Maxrounds)
{
char folder[64];
GetGameFolderName(folder, sizeof(folder));
if (strcmp(folder, "tf") == 0)
{
HookEvent("teamplay_win_panel", Event_TeamPlayWinPanel);
HookEvent("teamplay_restart_round", Event_TFRestartRound);
HookEvent("arena_win_panel", Event_TeamPlayWinPanel);
}
else if (strcmp(folder, "nucleardawn") == 0)
{
HookEvent("round_win", Event_RoundEnd);
}
else if (strcmp(folder, "empires") == 0)
{
HookEvent("game_end", Event_RoundEnd);
}
else
{
HookEvent("round_end", Event_RoundEnd);
}
}
if (g_Cvar_Fraglimit)
{
HookEvent("player_death", Event_PlayerDeath);
}
AutoExecConfig(true, "mapchooser");
//Change the mp_bonusroundtime max so that we have time to display the vote
//If you display a vote during bonus time good defaults are 17 vote duration and 19 mp_bonustime
if (g_Cvar_Bonusroundtime)
{
g_Cvar_Bonusroundtime.SetBounds(ConVarBound_Upper, true, 30.0);
}
g_NominationsResetForward = new GlobalForward("OnNominationRemoved", ET_Ignore, Param_String, Param_Cell);
g_MapVoteStartedForward = new GlobalForward("OnMapVoteStarted", ET_Ignore);
}
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{
RegPluginLibrary("mapchooser");
CreateNative("NominateMap", Native_NominateMap);
CreateNative("RemoveNominationByMap", Native_RemoveNominationByMap);
CreateNative("RemoveNominationByOwner", Native_RemoveNominationByOwner);
CreateNative("InitiateMapChooserVote", Native_InitiateVote);
CreateNative("CanMapChooserStartVote", Native_CanVoteStart);
CreateNative("HasEndOfMapVoteFinished", Native_CheckVoteDone);
CreateNative("GetExcludeMapList", Native_GetExcludeMapList);
CreateNative("GetNominatedMapList", Native_GetNominatedMapList);
CreateNative("EndOfMapVoteEnabled", Native_EndOfMapVoteEnabled);
return APLRes_Success;
}
public void OnConfigsExecuted()
{
if (ReadMapList(g_MapList,
g_mapFileSerial,
"mapchooser",
MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER)
!= null)
{
if (g_mapFileSerial == -1)
{
LogError("Unable to create a valid map list.");
}
}
/* First-load previous maps from a text file when persistency is enabled. */
static bool g_FirstConfigExec = true;
if (g_FirstConfigExec)
{
if (g_Cvar_PersistentMaps.BoolValue)
{
ReadPreviousMapsFromText();
}
g_FirstConfigExec = false;
}
CreateNextVote();
SetupTimeleftTimer();
g_TotalRounds = 0;
g_Extends = 0;
g_MapVoteCompleted = false;
g_NominateList.Clear();
g_NominateOwners.Clear();
for (int i=0; i<MAXTEAMS; i++)
{
g_winCount[i] = 0;
}
/* Check if mapchooser will attempt to start mapvote during bonus round time - TF2 Only */
if (g_Cvar_Bonusroundtime && !g_Cvar_StartRounds.IntValue)
{
if (g_Cvar_Bonusroundtime.FloatValue <= g_Cvar_VoteDuration.FloatValue)
{
LogError("Warning - Bonus Round Time shorter than Vote Time. Votes during bonus round may not have time to complete");
}
}
}
public void OnMapEnd()
{
g_HasVoteStarted = false;
g_WaitingForVote = false;
g_ChangeMapAtRoundEnd = false;
g_ChangeMapInProgress = false;
g_VoteTimer = null;
g_RetryTimer = null;
char map[PLATFORM_MAX_PATH];
GetCurrentMap(map, sizeof(map));
RemoveStringFromArray(g_OldMapList, map);
g_OldMapList.PushString(map);
while (g_OldMapList.Length > g_Cvar_ExcludeMaps.IntValue)
{
g_OldMapList.Erase(0);
}
if (g_Cvar_PersistentMaps.BoolValue)
{
WritePreviousMapsToText();
}
}
public void OnClientDisconnect(int client)
{
int index = g_NominateOwners.FindValue(client);
if (index == -1)
{
return;
}
char oldmap[PLATFORM_MAX_PATH];
g_NominateList.GetString(index, oldmap, sizeof(oldmap));
Call_StartForward(g_NominationsResetForward);
Call_PushString(oldmap);
Call_PushCell(g_NominateOwners.Get(index));
Call_Finish();
g_NominateOwners.Erase(index);
g_NominateList.Erase(index);
}
public Action Command_SetNextmap(int client, int args)
{
if (args < 1)
{
ReplyToCommand(client, "[SM] Usage: sm_setnextmap <map>");
return Plugin_Handled;
}
char map[PLATFORM_MAX_PATH];
char displayName[PLATFORM_MAX_PATH];
GetCmdArg(1, map, sizeof(map));
if (FindMap(map, displayName, sizeof(displayName)) == FindMap_NotFound)
{
ReplyToCommand(client, "[SM] %t", "Map was not found", map);
return Plugin_Handled;
}
GetMapDisplayName(displayName, displayName, sizeof(displayName));
ShowActivity2(client, "[SM] ", "%t", "Changed Next Map", displayName);
LogAction(client, -1, "\"%L\" changed nextmap to \"%s\"", client, map);
SetNextMap(map);
g_MapVoteCompleted = true;
return Plugin_Handled;
}
public void OnMapTimeLeftChanged()
{
if (g_MapList.Length)
{
SetupTimeleftTimer();
}
}
void SetupTimeleftTimer()
{
int time;
if (GetMapTimeLeft(time) && time > 0)
{
int startTime = g_Cvar_StartTime.IntValue * 60;
if (time - startTime < 0 && g_Cvar_EndOfMapVote.BoolValue && !g_MapVoteCompleted && !g_HasVoteStarted)
{
InitiateVote(MapChange_MapEnd, null);
}
else
{
if (g_VoteTimer != null)
{
KillTimer(g_VoteTimer);
g_VoteTimer = null;
}
//g_VoteTimer = CreateTimer(float(time - startTime), Timer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE);
DataPack data;
g_VoteTimer = CreateDataTimer(float(time - startTime), Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE);
data.WriteCell(MapChange_MapEnd);
data.WriteCell(INVALID_HANDLE);
data.Reset();
}
}
}
public Action Timer_StartMapVote(Handle timer, DataPack data)
{
if (timer == g_RetryTimer)
{
g_WaitingForVote = false;
g_RetryTimer = null;
}
else
{
g_VoteTimer = null;
}
if (!g_MapList.Length || !g_Cvar_EndOfMapVote.BoolValue || g_MapVoteCompleted || g_HasVoteStarted)
{
return Plugin_Stop;
}
MapChange mapChange = view_as<MapChange>(data.ReadCell());
ArrayList hndl = view_as<ArrayList>(data.ReadCell());
InitiateVote(mapChange, hndl);
return Plugin_Stop;
}
public void Event_TFRestartRound(Event event, const char[] name, bool dontBroadcast)
{
/* Game got restarted - reset our round count tracking */
g_TotalRounds = 0;
}
public void Event_TeamPlayWinPanel(Event event, const char[] name, bool dontBroadcast)
{
if (g_ChangeMapAtRoundEnd)
{
g_ChangeMapAtRoundEnd = false;
CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE);
g_ChangeMapInProgress = true;
}
int bluescore = event.GetInt("blue_score");
int redscore = event.GetInt("red_score");
if (event.GetInt("round_complete") == 1 || StrEqual(name, "arena_win_panel"))
{
g_TotalRounds++;
if (!g_MapList.Length || g_HasVoteStarted || g_MapVoteCompleted || !g_Cvar_EndOfMapVote.BoolValue)
{
return;
}
CheckMaxRounds(g_TotalRounds);
switch(event.GetInt("winning_team"))
{
case 3:
{
CheckWinLimit(bluescore);
}
case 2:
{
CheckWinLimit(redscore);
}
//We need to do nothing on winning_team == 0 this indicates stalemate.
default:
{
return;
}
}
}
}
/* You ask, why don't you just use team_score event? And I answer... Because CSS doesn't. */
public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast)
{
if (g_ChangeMapAtRoundEnd)
{
g_ChangeMapAtRoundEnd = false;
CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE);
g_ChangeMapInProgress = true;
}
int winner;
if (strcmp(name, "round_win") == 0)
{
// Nuclear Dawn
winner = event.GetInt("team");
}
else
{
winner = event.GetInt("winner");
}
if (winner == 0 || winner == 1 || !g_Cvar_EndOfMapVote.BoolValue)
{
return;
}
if (winner >= MAXTEAMS)
{
SetFailState("Mod exceed maximum team count - Please file a bug report.");
}
g_TotalRounds++;
g_winCount[winner]++;
if (!g_MapList.Length || g_HasVoteStarted || g_MapVoteCompleted)
{
return;
}
CheckWinLimit(g_winCount[winner]);
CheckMaxRounds(g_TotalRounds);
}
public void CheckWinLimit(int winner_score)
{
if (g_Cvar_Winlimit)
{
int winlimit = g_Cvar_Winlimit.IntValue;
if (winlimit)
{
if (winner_score >= (winlimit - g_Cvar_StartRounds.IntValue))
{
InitiateVote(MapChange_MapEnd, null);
}
}
}
}
public void CheckMaxRounds(int roundcount)
{
if (g_Cvar_Maxrounds)
{
int maxrounds = g_Cvar_Maxrounds.IntValue;
if (maxrounds)
{
if (roundcount >= (maxrounds - g_Cvar_StartRounds.IntValue))
{
InitiateVote(MapChange_MapEnd, null);
}
}
}
}
public void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast)
{
if (!g_MapList.Length || !g_Cvar_Fraglimit || g_HasVoteStarted)
{
return;
}
if (!g_Cvar_Fraglimit.IntValue || !g_Cvar_EndOfMapVote.BoolValue)
{
return;
}
if (g_MapVoteCompleted)
{
return;
}
int fragger = GetClientOfUserId(event.GetInt("attacker"));
if (!fragger)
{
return;
}
if (GetClientFrags(fragger) >= (g_Cvar_Fraglimit.IntValue - g_Cvar_StartFrags.IntValue))
{
InitiateVote(MapChange_MapEnd, null);
}
}
public Action Command_Mapvote(int client, int args)
{
InitiateVote(MapChange_MapEnd, null);
return Plugin_Handled;
}
/**
* Starts a new map vote
*
* @param when When the resulting map change should occur.
* @param inputlist Optional list of maps to use for the vote, otherwise an internal list of nominations + random maps will be used.
* @param noSpecials Block special vote options like extend/nochange (upgrade this to bitflags instead?)
*/
void InitiateVote(MapChange when, ArrayList inputlist=null)
{
g_WaitingForVote = true;
if (IsVoteInProgress())
{
// Can't start a vote, try again in 5 seconds.
//g_RetryTimer = CreateTimer(5.0, Timer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE);
DataPack data;
g_RetryTimer = CreateDataTimer(5.0, Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE);
data.WriteCell(when);
data.WriteCell(inputlist);
data.Reset();
return;
}
/* If the main map vote has completed (and chosen result) and its currently changing (not a delayed change) we block further attempts */
if (g_MapVoteCompleted && g_ChangeMapInProgress)
{
return;
}
g_ChangeTime = when;
g_WaitingForVote = false;
g_HasVoteStarted = true;
g_VoteMenu = new Menu(Handler_MapVoteMenu, MENU_ACTIONS_ALL);
g_VoteMenu.SetTitle("Vote Nextmap");
g_VoteMenu.VoteResultCallback = Handler_MapVoteFinished;
/* Call OnMapVoteStarted() Forward */
Call_StartForward(g_MapVoteStartedForward);
Call_Finish();
/**
* TODO: Make a proper decision on when to clear the nominations list.
* Currently it clears when used, and stays if an external list is provided.
* Is this the right thing to do? External lists will probably come from places
* like sm_mapvote from the adminmenu in the future.
*/
char map[PLATFORM_MAX_PATH];
/* No input given - User our internal nominations and maplist */
if (inputlist == null)
{
int nominateCount = g_NominateList.Length;
int voteSize = g_Cvar_IncludeMaps.IntValue;
/* Smaller of the two - It should be impossible for nominations to exceed the size though (cvar changed mid-map?) */
int nominationsToAdd = nominateCount >= voteSize ? voteSize : nominateCount;
for (int i=0; i<nominationsToAdd; i++)
{
char displayName[PLATFORM_MAX_PATH];
g_NominateList.GetString(i, map, sizeof(map));
GetMapDisplayName(map, displayName, sizeof(displayName));
g_VoteMenu.AddItem(map, displayName);
RemoveStringFromArray(g_NextMapList, map);
/* Notify Nominations that this map is now free */
Call_StartForward(g_NominationsResetForward);
Call_PushString(map);
Call_PushCell(g_NominateOwners.Get(i));
Call_Finish();
}
/* Clear out the rest of the nominations array */
for (int i=nominationsToAdd; i<nominateCount; i++)
{
g_NominateList.GetString(i, map, sizeof(map));
/* These maps shouldn't be excluded from the vote as they weren't really nominated at all */
/* Notify Nominations that this map is now free */
Call_StartForward(g_NominationsResetForward);
Call_PushString(map);
Call_PushCell(g_NominateOwners.Get(i));
Call_Finish();
}
/* There should currently be 'nominationsToAdd' unique maps in the vote */
int i = nominationsToAdd;
int count = 0;
int availableMaps = g_NextMapList.Length;
while (i < voteSize)
{
if (count >= availableMaps)
{
//Run out of maps, this will have to do.
break;
}
g_NextMapList.GetString(count, map, sizeof(map));
count++;
/* Insert the map and increment our count */
char displayName[PLATFORM_MAX_PATH];
GetMapDisplayName(map, displayName, sizeof(displayName));
g_VoteMenu.AddItem(map, displayName);
i++;
}
/* Wipe out our nominations list - Nominations have already been informed of this */
g_NominateOwners.Clear();
g_NominateList.Clear();
}
else //We were given a list of maps to start the vote with
{
int size = inputlist.Length;
for (int i=0; i<size; i++)
{
inputlist.GetString(i, map, sizeof(map));
if (IsMapValid(map))
{
char displayName[PLATFORM_MAX_PATH];
GetMapDisplayName(map, displayName, sizeof(displayName));
g_VoteMenu.AddItem(map, displayName);
}
}
}
/* Do we add any special items? */
if ((when == MapChange_Instant || when == MapChange_RoundEnd) && g_Cvar_DontChange.BoolValue)
{
g_VoteMenu.AddItem(VOTE_DONTCHANGE, "Don't Change");
}
else if (g_Cvar_Extend.BoolValue && g_Extends < g_Cvar_Extend.IntValue)
{
g_VoteMenu.AddItem(VOTE_EXTEND, "Extend Map");
}
/* There are no maps we could vote for. Don't show anything. */
if (g_VoteMenu.ItemCount == 0)
{
g_HasVoteStarted = false;
delete g_VoteMenu;
return;
}
int voteDuration = g_Cvar_VoteDuration.IntValue;
g_VoteMenu.ExitButton = false;
g_VoteMenu.DisplayVoteToAll(voteDuration);
LogAction(-1, -1, "Voting for next map has started.");
PrintToChatAll("[SM] %t", "Nextmap Voting Started");
}
public void Handler_VoteFinishedGeneric(Menu menu,
int num_votes,
int num_clients,
const int[][] client_info,
int num_items,
const int[][] item_info)
{
char map[PLATFORM_MAX_PATH];
char displayName[PLATFORM_MAX_PATH];
menu.GetItem(item_info[0][VOTEINFO_ITEM_INDEX], map, sizeof(map), _, displayName, sizeof(displayName));
if (strcmp(map, VOTE_EXTEND, false) == 0)
{
g_Extends++;
int time;
if (GetMapTimeLimit(time))
{
if (time > 0)
{
ExtendMapTimeLimit(g_Cvar_ExtendTimeStep.IntValue * 60);
}
}
if (g_Cvar_Winlimit)
{
int winlimit = g_Cvar_Winlimit.IntValue;
if (winlimit)
{
g_Cvar_Winlimit.IntValue = winlimit + g_Cvar_ExtendRoundStep.IntValue;
}
}
if (g_Cvar_Maxrounds)
{
int maxrounds = g_Cvar_Maxrounds.IntValue;
if (maxrounds)
{
g_Cvar_Maxrounds.IntValue = maxrounds + g_Cvar_ExtendRoundStep.IntValue;
}
}
if (g_Cvar_Fraglimit)
{
int fraglimit = g_Cvar_Fraglimit.IntValue;
if (fraglimit)
{
g_Cvar_Fraglimit.IntValue = fraglimit + g_Cvar_ExtendFragStep.IntValue;
}
}
PrintToChatAll("[SM] %t", "Current Map Extended", RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes);
LogAction(-1, -1, "Voting for next map has finished. The current map has been extended.");
// We extended, so we'll have to vote again.
g_HasVoteStarted = false;
CreateNextVote();
SetupTimeleftTimer();
}
else if (strcmp(map, VOTE_DONTCHANGE, false) == 0)
{
PrintToChatAll("[SM] %t", "Current Map Stays", RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes);
LogAction(-1, -1, "Voting for next map has finished. 'No Change' was the winner");
g_HasVoteStarted = false;
CreateNextVote();
SetupTimeleftTimer();
}
else
{
if (g_ChangeTime == MapChange_MapEnd)
{
SetNextMap(map);
}
else if (g_ChangeTime == MapChange_Instant)
{
DataPack data;
CreateDataTimer(2.0, Timer_ChangeMap, data);
data.WriteString(map);
g_ChangeMapInProgress = false;
}
else // MapChange_RoundEnd
{
SetNextMap(map);
g_ChangeMapAtRoundEnd = true;
}
g_HasVoteStarted = false;
g_MapVoteCompleted = true;
PrintToChatAll("[SM] %t", "Nextmap Voting Finished", displayName, RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes);
LogAction(-1, -1, "Voting for next map has finished. Nextmap: %s.", map);
}
}
public void Handler_MapVoteFinished(Menu menu,
int num_votes,
int num_clients,
const int[][] client_info,
int num_items,
const int[][] item_info)
{
if (g_Cvar_RunOff.BoolValue && num_items > 1)
{
float winningvotes = float(item_info[0][VOTEINFO_ITEM_VOTES]);
float required = num_votes * (g_Cvar_RunOffPercent.FloatValue / 100.0);
if (winningvotes < required)
{
/* Insufficient Winning margin - Lets do a runoff */
g_VoteMenu = new Menu(Handler_MapVoteMenu, MENU_ACTIONS_ALL);
g_VoteMenu.SetTitle("Runoff Vote Nextmap");
g_VoteMenu.VoteResultCallback = Handler_VoteFinishedGeneric;
char map[PLATFORM_MAX_PATH];
char info1[PLATFORM_MAX_PATH];
char info2[PLATFORM_MAX_PATH];
menu.GetItem(item_info[0][VOTEINFO_ITEM_INDEX], map, sizeof(map), _, info1, sizeof(info1));
g_VoteMenu.AddItem(map, info1);
menu.GetItem(item_info[1][VOTEINFO_ITEM_INDEX], map, sizeof(map), _, info2, sizeof(info2));
g_VoteMenu.AddItem(map, info2);
int voteDuration = g_Cvar_VoteDuration.IntValue;
g_VoteMenu.ExitButton = false;
g_VoteMenu.DisplayVoteToAll(voteDuration);
/* Notify */
float map1percent = float(item_info[0][VOTEINFO_ITEM_VOTES])/ float(num_votes) * 100;
float map2percent = float(item_info[1][VOTEINFO_ITEM_VOTES])/ float(num_votes) * 100;
PrintToChatAll("[SM] %t", "Starting Runoff", g_Cvar_RunOffPercent.FloatValue, info1, map1percent, info2, map2percent);
LogMessage("Voting for next map was indecisive, beginning runoff vote");
return;
}
}
Handler_VoteFinishedGeneric(menu, num_votes, num_clients, client_info, num_items, item_info);
}
public int Handler_MapVoteMenu(Menu menu, MenuAction action, int param1, int param2)
{
switch (action)
{
case MenuAction_End:
{
g_VoteMenu = null;
delete menu;
}
case MenuAction_Display:
{
char buffer[255];
Format(buffer, sizeof(buffer), "%T", "Vote Nextmap", param1);
Panel panel = view_as<Panel>(param2);
panel.SetTitle(buffer);
}
case MenuAction_DisplayItem:
{
if (menu.ItemCount - 1 == param2)
{
char map[PLATFORM_MAX_PATH], buffer[255];
menu.GetItem(param2, map, sizeof(map));
if (strcmp(map, VOTE_EXTEND, false) == 0)
{
Format(buffer, sizeof(buffer), "%T", "Extend Map", param1);
return RedrawMenuItem(buffer);
}
else if (strcmp(map, VOTE_DONTCHANGE, false) == 0)
{
Format(buffer, sizeof(buffer), "%T", "Dont Change", param1);
return RedrawMenuItem(buffer);
}
}
}
case MenuAction_VoteCancel:
{
// If we receive 0 votes, pick at random.
if (param1 == VoteCancel_NoVotes && g_Cvar_NoVoteMode.BoolValue)
{
int count = menu.ItemCount;
char map[PLATFORM_MAX_PATH];
menu.GetItem(0, map, sizeof(map));
// Make sure the first map in the menu isn't one of the special items.
// This would mean there are no real maps in the menu, because the special items are added after all maps. Don't do anything if that's the case.
if (strcmp(map, VOTE_EXTEND, false) != 0 && strcmp(map, VOTE_DONTCHANGE, false) != 0)
{
// Get a random map from the list.
int item = GetRandomInt(0, count - 1);
menu.GetItem(item, map, sizeof(map));
// Make sure it's not one of the special items.
while (strcmp(map, VOTE_EXTEND, false) == 0 || strcmp(map, VOTE_DONTCHANGE, false) == 0)
{
item = GetRandomInt(0, count - 1);
menu.GetItem(item, map, sizeof(map));
}
SetNextMap(map);
g_MapVoteCompleted = true;
}
}
else
{
// We were actually cancelled. I guess we do nothing.
}
g_HasVoteStarted = false;
}
}
return 0;
}
public Action Timer_ChangeMap(Handle hTimer, DataPack dp)
{
g_ChangeMapInProgress = false;
char map[PLATFORM_MAX_PATH];
if (dp == null)
{
if (!GetNextMap(map, sizeof(map)))
{
//No passed map and no set nextmap. fail!
return Plugin_Stop;
}
}
else
{
dp.Reset();
dp.ReadString(map, sizeof(map));
}
ForceChangeLevel(map, "Map Vote");
return Plugin_Stop;
}
bool RemoveStringFromArray(ArrayList array, char[] str)
{
int index = array.FindString(str);
if (index != -1)
{
array.Erase(index);
return true;
}
return false;
}
void CreateNextVote()
{
g_NextMapList.Clear();
char map[PLATFORM_MAX_PATH];
// tempMaps is a resolved map list
ArrayList tempMaps = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH));
for (int i = 0; i < g_MapList.Length; i++)
{
g_MapList.GetString(i, map, sizeof(map));
if (FindMap(map, map, sizeof(map)) != FindMap_NotFound)
{
tempMaps.PushString(map);
}
}
//GetCurrentMap always returns a resolved map
GetCurrentMap(map, sizeof(map));
RemoveStringFromArray(tempMaps, map);
if (g_Cvar_ExcludeMaps.IntValue && tempMaps.Length > g_Cvar_ExcludeMaps.IntValue)
{
for (int i = 0; i < g_OldMapList.Length; i++)
{
g_OldMapList.GetString(i, map, sizeof(map));
RemoveStringFromArray(tempMaps, map);
}
}
int limit = (g_Cvar_IncludeMaps.IntValue < tempMaps.Length ? g_Cvar_IncludeMaps.IntValue : tempMaps.Length);
for (int i = 0; i < limit; i++)
{
int b = GetRandomInt(0, tempMaps.Length - 1);
tempMaps.GetString(b, map, sizeof(map));
g_NextMapList.PushString(map);
tempMaps.Erase(b);
}
delete tempMaps;
}
bool CanVoteStart()
{
if (g_WaitingForVote || g_HasVoteStarted)
{
return false;
}
return true;
}
NominateResult InternalNominateMap(char[] map, bool force, int owner)
{
if (!IsMapValid(map))
{
return Nominate_InvalidMap;
}
/* Map already in the vote */
if (g_NominateList.FindString(map) != -1)
{
return Nominate_AlreadyInVote;
}
int index;
/* Look to replace an existing nomination by this client - Nominations made with owner = 0 aren't replaced */
if (owner && ((index = g_NominateOwners.FindValue(owner)) != -1))
{
char oldmap[PLATFORM_MAX_PATH];
g_NominateList.GetString(index, oldmap, sizeof(oldmap));
Call_StartForward(g_NominationsResetForward);
Call_PushString(oldmap);
Call_PushCell(owner);
Call_Finish();
g_NominateList.SetString(index, map);
return Nominate_Replaced;
}
/* Too many nominated maps. */
if (g_NominateList.Length >= g_Cvar_IncludeMaps.IntValue && !force)
{
return Nominate_VoteFull;
}
g_NominateList.PushString(map);
g_NominateOwners.Push(owner);
while (g_NominateList.Length > g_Cvar_IncludeMaps.IntValue)
{
char oldmap[PLATFORM_MAX_PATH];
g_NominateList.GetString(0, oldmap, sizeof(oldmap));
Call_StartForward(g_NominationsResetForward);
Call_PushString(oldmap);
Call_PushCell(g_NominateOwners.Get(0));
Call_Finish();
g_NominateList.Erase(0);
g_NominateOwners.Erase(0);
}
return Nominate_Added;
}
/* Add natives to allow nominate and initiate vote to be call */
/* native NominateResult NominateMap(const char[] map, bool force, int owner); */
public int Native_NominateMap(Handle plugin, int numParams)
{
int len;
GetNativeStringLength(1, len);
if (len <= 0)
{
return false;
}
char[] map = new char[len+1];
GetNativeString(1, map, len+1);
return view_as<int>(InternalNominateMap(map, GetNativeCell(2), GetNativeCell(3)));
}
bool InternalRemoveNominationByMap(char[] map)
{
for (int i = 0; i < g_NominateList.Length; i++)
{
char oldmap[PLATFORM_MAX_PATH];
g_NominateList.GetString(i, oldmap, sizeof(oldmap));
if(strcmp(map, oldmap, false) == 0)
{
Call_StartForward(g_NominationsResetForward);
Call_PushString(oldmap);
Call_PushCell(g_NominateOwners.Get(i));
Call_Finish();
g_NominateList.Erase(i);
g_NominateOwners.Erase(i);
return true;
}
}
return false;
}
/* native bool RemoveNominationByMap(const char[] map); */
public int Native_RemoveNominationByMap(Handle plugin, int numParams)
{
int len;
GetNativeStringLength(1, len);
if (len <= 0)
{
return false;
}
char[] map = new char[len+1];
GetNativeString(1, map, len+1);
return InternalRemoveNominationByMap(map);
}
bool InternalRemoveNominationByOwner(int owner)
{
int index;
if (owner && ((index = g_NominateOwners.FindValue(owner)) != -1))
{
char oldmap[PLATFORM_MAX_PATH];
g_NominateList.GetString(index, oldmap, sizeof(oldmap));
Call_StartForward(g_NominationsResetForward);
Call_PushString(oldmap);
Call_PushCell(owner);
Call_Finish();
g_NominateList.Erase(index);
g_NominateOwners.Erase(index);
return true;
}
return false;
}
/* native bool RemoveNominationByOwner(int owner); */
public int Native_RemoveNominationByOwner(Handle plugin, int numParams)
{
return InternalRemoveNominationByOwner(GetNativeCell(1));
}
/* native void InitiateMapChooserVote(MapChange when, ArrayList inputarray=null); */
public int Native_InitiateVote(Handle plugin, int numParams)
{
MapChange when = view_as<MapChange>(GetNativeCell(1));
ArrayList inputarray = view_as<ArrayList>(GetNativeCell(2));
LogAction(-1, -1, "Starting map vote because outside request");
InitiateVote(when, inputarray);
return 0;
}
/* native bool CanMapChooserStartVote(); */
public int Native_CanVoteStart(Handle plugin, int numParams)
{
return CanVoteStart();
}
/* native bool HasEndOfMapVoteFinished(); */
public int Native_CheckVoteDone(Handle plugin, int numParams)
{
return g_MapVoteCompleted;
}
/* native bool EndOfMapVoteEnabled(); */
public int Native_EndOfMapVoteEnabled(Handle plugin, int numParams)
{
return g_Cvar_EndOfMapVote.BoolValue;
}
/* native void GetExcludeMapList(ArrayList array); */
public int Native_GetExcludeMapList(Handle plugin, int numParams)
{
ArrayList array = view_as<ArrayList>(GetNativeCell(1));
if (array == null)
{
return 0;
}
int size = g_OldMapList.Length;
char map[PLATFORM_MAX_PATH];
for (int i=0; i<size; i++)
{
g_OldMapList.GetString(i, map, sizeof(map));
array.PushString(map);
}
return 0;
}
/* native void GetNominatedMapList(ArrayList maparray, ArrayList ownerarray = null); */
public int Native_GetNominatedMapList(Handle plugin, int numParams)
{
ArrayList maparray = view_as<ArrayList>(GetNativeCell(1));
ArrayList ownerarray = view_as<ArrayList>(GetNativeCell(2));
if (maparray == null)
return 0;
char map[PLATFORM_MAX_PATH];
for (int i = 0; i < g_NominateList.Length; i++)
{
g_NominateList.GetString(i, map, sizeof(map));
maparray.PushString(map);
// If the optional parameter for an owner list was passed, then we need to fill that out as well
if(ownerarray != null)
{
int index = g_NominateOwners.Get(i);
ownerarray.Push(index);
}
}
return 0;
}
/* Add functions for persistent previous map storage */
void ReadPreviousMapsFromText()
{
File file = OpenFile(GetTextFilePath(), "r");
if (file == null)
{
return;
}
g_OldMapList.Clear();
char map[PLATFORM_MAX_PATH];
do
{
if (file.ReadLine(map, sizeof(map)))
{
TrimString(map);
g_OldMapList.PushString(map);
}
}
while (!file.EndOfFile());
file.Close();
}
void WritePreviousMapsToText()
{
File file = OpenFile(GetTextFilePath(), "w");
if (file == null)
{
return;
}
char lastMap[PLATFORM_MAX_PATH];
for (int idx=0; idx<g_OldMapList.Length; idx++)
{
g_OldMapList.GetString(idx, lastMap, sizeof(lastMap));
TrimString(lastMap);
file.WriteLine(lastMap);
}
file.Close();
}
char[] GetTextFilePath()
{
static char path[PLATFORM_MAX_PATH];
if (path[0] == '\0')
BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "data/mapchooser_history.txt");
return path;
}