#define PLUGIN_VERSION	"1.1.0"
#define MAX_LINE_WIDTH	 64

#define MESSAGE_FOR_PLAYERS_LINE1	""
#define MESSAGE_FOR_PLAYERS_LINE2	"\x04RECEIVED SERVER RESERVATION REQUEST"
#define MESSAGE_FOR_PLAYERS_LINE3	"\x04YOU WILL BE RETURNED TO LOBBY"
#define MESSAGE_FOR_PLAYERS_LINE4	""

#pragma semicolon 1

#include <sourcemod>

#pragma newdecls required

ConVar PluginCvarSearchKey, PluginCvarMode, PluginCvarTimeout, PluginCvarImmuneLevel, 
       SteamGroupExclusiveCvar, SearchKeyCvar, HibernationCvar;

int HibernationCvarValue;
bool isMapChange = false, doRestartMap = false;
char PluginSearchKeyString[MAX_LINE_WIDTH] = "", PluginCvarImmuneFlagString[MAX_LINE_WIDTH] = "", CurrentMapString[MAX_LINE_WIDTH] = "";

public Plugin myinfo =
{
	name = "Reserve The Server",
	author = "Jack'lul [Edited by Dosergen]",
	description = "Frees the server from all players and reserves it.",
	version = PLUGIN_VERSION,
	url = "https://forums.alliedmods.net/showthread.php?p=2084993"
}

public void OnPluginStart() {
	EngineVersion g_Game = GetEngineVersion();
	if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) {
		SetFailState("This plugin is for L4D/L4D2 only.");	
	}
	
	CreateConVar("l4d_rts_version", PLUGIN_VERSION, "Reserve The Server plugin version", 0|FCVAR_NOTIFY|FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_DONTRECORD);	
	PluginCvarMode = CreateConVar("l4d_rts_mode", "1", "0 - only remove players using lobby vote, 1 - remove players using lobby vote and then disconnect server from matchmaking", 0, true, 0.0, true, 1.0);
	PluginCvarSearchKey = CreateConVar("l4d_rts_searchkey", "", "sv_search_key will be set to this while server is reserved", 0);
	PluginCvarTimeout = CreateConVar("l4d_rts_timeout", "30", "How long will the server stay disconnected from matchmaking? 0 - never restore matchmaking connection", 0, true, 0.0, true, 300.0);
	PluginCvarImmuneLevel = CreateConVar("l4d_rts_immunelevel", "1", "Any player >= to this level will cancel the lobby vote.", 0);

	RegAdminCmd("sm_rts", Command_MakeReservation, ADMFLAG_KICK, "Free the server from all players, then reserve it.");
	RegAdminCmd("sm_cr", Command_CancelReservation, ADMFLAG_KICK, "Cancel reservation and make server public again.");
	RegAdminCmd("sm_forcelobby", Command_ForceLobby, ADMFLAG_BAN, "Force call vote to return to lobby");
	
	SteamGroupExclusiveCvar	= FindConVar("sv_steamgroup_exclusive");
	SearchKeyCvar = FindConVar("sv_search_key");
	HibernationCvar = FindConVar("sv_hibernate_when_empty");
	HibernationCvarValue = GetConVarInt(HibernationCvar);
	
	AutoExecConfig(true, "l4d_rts");
}

public void OnClientDisconnect(int client) {
	if (client == 0 || isMapChange || IsFakeClient(client))
		return;
	
	if(doRestartMap == true)
		CreateTimer(1.0, MapReloadCheck);
}

public void OnMapEnd() {
	isMapChange = true;
	doRestartMap = false;
}

public void OnMapStart() {
	isMapChange = false;
}

public Action Command_ForceLobby(int client, int args) {
	Handle bf = StartMessageOne("VoteStart", client, USERMSG_RELIABLE);
	BfWriteByte(bf, 0);
	BfWriteByte(bf, client);
	BfWriteString(bf, "returntolobby");
	BfWriteString(bf, "");
	BfWriteString(bf, "");
	EndMessage();
	PassVote();
}

public Action Command_MakeReservation(int client, int args) {
	bool isAdminOnline, isServerEmpty = true;
	if(client > 0) {
		for (int i = 1; i <= MaxClients; i++) {
			if (IsClientConnected(i) && IsClientInGame(i)) {
				AdminId admin = GetUserAdmin(i);
				if (admin != INVALID_ADMIN_ID && GetAdminImmunityLevel(admin) >= PluginCvarImmuneLevel.IntValue) {
					isAdminOnline = true;
					break;
				}
				if(isServerEmpty) isServerEmpty = false;
			}
		}
	}
	//If there is no admins playing OR request is from server itself then reserve:
	if(!isAdminOnline)
	{
		LogMessage("Received server reservation request.");
		if(!isServerEmpty) {	
			if(GetConVarInt(PluginCvarMode) == 1)
			{
				doRestartMap = true;
				ReplyToCommand(client, "Server will be freed from all players and reserved."); 
			}
			else
				ReplyToCommand(client, "Server will be freed from all players."); 	
			
			PrintToChatAll(MESSAGE_FOR_PLAYERS_LINE1);
			PrintToChatAll(MESSAGE_FOR_PLAYERS_LINE2);
			PrintToChatAll(MESSAGE_FOR_PLAYERS_LINE3);
			PrintToChatAll(MESSAGE_FOR_PLAYERS_LINE4);
			
			CreateTimer(5.0, FreeTheServer);
		} else if(GetConVarInt(PluginCvarMode) == 1) {
			DisconnectFromMatchmaking();
			ReloadMap();
		}
	}
	else
		ReplyToCommand(client, "Server reservation request denied - admin is online!");
	return Plugin_Handled;
}

public Action Command_CancelReservation(int client, int args) {
	CreateTimer(0.1, MakeServerPublic);
}

public Action FreeTheServer(Handle timer) {
	CallLobbyVote();
	PassVote();
	
	if(GetConVarInt(PluginCvarMode) == 1) {
		DisconnectFromMatchmaking();
	}
}

public Action MakeServerPublic(Handle timer) {
	ConnectToMatchmaking();
	
	int notConnected = 0;
	for (int i = 1; i <= MaxClients; i++)
	{
		if (IsClientConnected (i) && IsClientInGame (i))
			break;
		else
			notConnected++;
	}
	
	if(notConnected == MaxClients)
		ReloadMap();
	
	if(HibernationCvarValue != 0 && GetConVarInt(HibernationCvar) == 0)
		SetConVarInt(HibernationCvar, 1);
}

public Action MapReloadCheck(Handle timer) {
	if (!isMapChange && doRestartMap) {
		doRestartMap = false;
		ReloadMap();
	}
}

void CallLobbyVote() {
	for (int iClient = 1; iClient <= MaxClients; iClient++) {
		if (IsClientConnected (iClient) && IsClientInGame (iClient)) {
			FakeClientCommand (iClient, "callvote returntolobby");
		}
	}
}

void PassVote() {
	for(int iClient = 1; iClient <= MaxClients; iClient++) {
		if (IsClientConnected (iClient) && IsClientInGame (iClient)) {
			FakeClientCommand(iClient, "Vote Yes");
		}
	}
}

void ReloadMap() {
	GetCurrentMap(CurrentMapString, sizeof(CurrentMapString));
	ServerCommand("map %s", CurrentMapString);
}

void DisconnectFromMatchmaking() {
	GetConVarString(PluginCvarSearchKey, PluginSearchKeyString, sizeof(PluginSearchKeyString));
	SetConVarInt(SteamGroupExclusiveCvar, 1);
	SetConVarString(SearchKeyCvar, PluginSearchKeyString);
	
	if(HibernationCvarValue != 0)
		SetConVarInt(HibernationCvar, 0);	
	
	if(GetConVarFloat(PluginCvarTimeout)>0)
		CreateTimer(GetConVarFloat(PluginCvarTimeout), MakeServerPublic);
}

void ConnectToMatchmaking() {
	SetConVarInt(SteamGroupExclusiveCvar, 0);
	SetConVarString(SearchKeyCvar, "");
}
