commit 72f766535874222e4e0cb148477b6f77fa726a48 Author: BotoX Date: Wed Jan 6 02:11:56 2016 +0100 initial commit diff --git a/AdminCheats/scripting/AdminCheats.sp b/AdminCheats/scripting/AdminCheats.sp new file mode 100644 index 00000000..1fe6ab0b --- /dev/null +++ b/AdminCheats/scripting/AdminCheats.sp @@ -0,0 +1,123 @@ +#pragma semicolon 1 + +#include + +#pragma newdecls required +#define PLUGIN_VERSION "1.0" +public Plugin myinfo = +{ + name = "AdminCheats", + author = "BotoX", + description = "Allows usage of (most) cheat commands for admins.", + version = PLUGIN_VERSION, + url = "" +}; + +ConVar g_CVar_sv_cheats; + +public void OnPluginStart() +{ + g_CVar_sv_cheats = FindConVar("sv_cheats"); + g_CVar_sv_cheats.Flags &= ~FCVAR_NOTIFY; + g_CVar_sv_cheats.Flags &= ~FCVAR_REPLICATED; + g_CVar_sv_cheats.AddChangeHook(OnConVarChanged); + g_CVar_sv_cheats.SetInt(1); + + MakeCheatCommand("give"); + + int NumHooks = 0; + char sConCommand[128]; + bool IsCommand; + int Flags; + Handle hSearch = FindFirstConCommand(sConCommand, sizeof(sConCommand), IsCommand, Flags); + do + { + if(IsCommand && Flags & FCVAR_CHEAT) + { + RegConsoleCmd(sConCommand, OnCheatCommand); + NumHooks++; + } + } + while(FindNextConCommand(hSearch, sConCommand, sizeof(sConCommand), IsCommand, Flags)); + + PrintToServer("Hooked %d cheat commands.", NumHooks); + + UpdateClients(); +} + +public void OnPluginEnd() +{ + g_CVar_sv_cheats.SetInt(0); + g_CVar_sv_cheats.Flags |= FCVAR_NOTIFY; + g_CVar_sv_cheats.Flags |= FCVAR_REPLICATED; +} + +public void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] newValue) +{ + g_CVar_sv_cheats.SetInt(1); + CreateTimer(0.1, Timer_UpdateClients); +} + +public Action Timer_UpdateClients(Handle timer, Handle hndl) +{ + UpdateClients(); +} + +public void UpdateClients() +{ + for(int i = 1; i <= MaxClients; i++) + { + if(IsClientInGame(i) && !IsFakeClient(i) && IsClientAuthorized(i)) + OnClientPostAdminCheck(i); + } +} + +public void OnClientPutInServer(int client) +{ + SendConVarValue(client, g_CVar_sv_cheats, "0"); +} + +public void OnClientPostAdminCheck(int client) +{ + if(IsFakeClient(client)) + return; + + if(g_CVar_sv_cheats.BoolValue && GetAdminFlag(GetUserAdmin(client), Admin_Cheats)) + SendConVarValue(client, g_CVar_sv_cheats, "1"); + else + SendConVarValue(client, g_CVar_sv_cheats, "0"); +} + +public Action OnCheatCommand(int client, int args) +{ + if(client == 0) + return Plugin_Continue; + + if(IsClientAuthorized(client) && GetAdminFlag(GetUserAdmin(client), Admin_Cheats)) + return Plugin_Continue; + + PrintToConsole(client, "denied :^)"); + return Plugin_Handled; +} + +public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3], float angles[3], int &weapon, int &subtype, int &cmdnum, int &tickcount, int &seed, int mouse[2]) +{ + if(!impulse) + return Plugin_Continue; + + if(impulse == 100 || impulse == 201) + return Plugin_Continue; + + if(IsClientAuthorized(client) && GetAdminFlag(GetUserAdmin(client), Admin_Cheats)) + return Plugin_Continue; + + PrintToConsole(client, "denied :^)"); + return Plugin_Handled; +} + +stock void MakeCheatCommand(const char[] name) +{ + int Flags = GetCommandFlags(name); + if(Flags != INVALID_FCVAR_FLAGS) + SetCommandFlags(name, FCVAR_CHEAT | Flags); +} diff --git a/AdvancedTargeting/scripting/.gitignore b/AdvancedTargeting/scripting/.gitignore new file mode 100644 index 00000000..1e7109b6 --- /dev/null +++ b/AdvancedTargeting/scripting/.gitignore @@ -0,0 +1 @@ +SteamAPI.secret diff --git a/AdvancedTargeting/scripting/AdvancedTargeting.sp b/AdvancedTargeting/scripting/AdvancedTargeting.sp new file mode 100644 index 00000000..df938605 --- /dev/null +++ b/AdvancedTargeting/scripting/AdvancedTargeting.sp @@ -0,0 +1,451 @@ +#pragma semicolon 1 +#define PLUGIN_VERSION "1.0" + +#pragma dynamic 128*1024 + +#include +#include +//#include +#include + +Handle g_FriendsArray[MAXPLAYERS + 1] = {INVALID_HANDLE, ...}; +bool g_bLateLoad = false; + +//#define STEAM_API_KEY "secret" +#include "SteamAPI.secret" + +public Plugin myinfo = +{ + name = "Advanced Targeting", + author = "BotoX", + description = "Adds @admins and @friends targeting method", + version = PLUGIN_VERSION, + url = "" +} + +public OnPluginStart() +{ + AddMultiTargetFilter("@admins", Filter_Admin, "Admins", false); + AddMultiTargetFilter("@!admins", Filter_NotAdmin, "Not Admins", false); + AddMultiTargetFilter("@friends", Filter_Friends, "Steam Friends", false); + AddMultiTargetFilter("@!friends", Filter_NotFriends, "Not Steam Friends", false); + + RegConsoleCmd("sm_admins", Command_Admins, "Currently online admins."); + RegConsoleCmd("sm_friends", Command_Friends, "Currently online friends."); + + if(g_bLateLoad) + { + char sSteam32ID[32]; + for(new i = 1; i <= MaxClients; i++) + { + if(IsClientInGame(i) && !IsFakeClient(i) && IsClientAuthorized(i) && + GetClientAuthId(i, AuthId_Steam2, sSteam32ID, sizeof(sSteam32ID))) + { + OnClientAuthorized(i, sSteam32ID); + } + } + } +} + +public OnPluginEnd() +{ + RemoveMultiTargetFilter("@admins", Filter_Admin); + RemoveMultiTargetFilter("@!admins", Filter_NotAdmin); + RemoveMultiTargetFilter("@friends", Filter_Friends); + RemoveMultiTargetFilter("@!friends", Filter_NotFriends); +} + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) +{ + CreateNative("IsClientFriend", Native_IsClientFriend); + CreateNative("ReadClientFriends", Native_ReadClientFriends); + RegPluginLibrary("AdvancedTargeting"); + + g_bLateLoad = late; + return APLRes_Success; +} + +public Action Command_Admins(int client, int args) +{ + char aBuf[1024]; + char aBuf2[MAX_NAME_LENGTH]; + for(int i = 1; i <= MaxClients; i++) + { + if(IsClientInGame(i) && !IsFakeClient(i) && GetAdminFlag(GetUserAdmin(i), Admin_Generic)) + { + GetClientName(i, aBuf2, sizeof(aBuf2)); + StrCat(aBuf, sizeof(aBuf), aBuf2); + StrCat(aBuf, sizeof(aBuf), ", "); + } + } + + if(strlen(aBuf)) + { + aBuf[strlen(aBuf) - 2] = 0; + PrintToChat(client, "[SM] Admins currently online: %s", aBuf); + } + else + PrintToChat(client, "[SM] Admins currently online: none"); + + return Plugin_Handled; +} + +public Action Command_Friends(int client, int args) +{ + if(g_FriendsArray[client] == INVALID_HANDLE) + { + PrintToChat(client, "[SM] Could not read your friendslist, your profile must be set to public!"); + return Plugin_Handled; + } + + char aBuf[1024]; + char aBuf2[MAX_NAME_LENGTH]; + for(int i = 1; i <= MaxClients; i++) + { + if(IsClientInGame(i) && !IsFakeClient(i) && IsClientAuthorized(i)) + { + int Steam3ID = GetSteamAccountID(i); + + if(FindValueInArray(g_FriendsArray[client], Steam3ID) != -1) + { + GetClientName(i, aBuf2, sizeof(aBuf2)); + StrCat(aBuf, sizeof(aBuf), aBuf2); + StrCat(aBuf, sizeof(aBuf), ", "); + } + } + } + + if(strlen(aBuf)) + { + aBuf[strlen(aBuf) - 2] = 0; + PrintToChat(client, "[SM] Friends currently online: %s", aBuf); + } + else + PrintToChat(client, "[SM] Friends currently online: none"); + + return Plugin_Handled; +} + +public bool Filter_Admin(const char[] sPattern, Handle hClients, int client) +{ + for(int i = 1; i <= MaxClients; i++) + { + if(IsClientInGame(i) && !IsFakeClient(i) && GetAdminFlag(GetUserAdmin(i), Admin_Generic)) + { + PushArrayCell(hClients, i); + } + } + + return true; +} + +public bool Filter_NotAdmin(const char[] sPattern, Handle hClients, int client) +{ + for(int i = 1; i <= MaxClients; i++) + { + if(IsClientInGame(i) && !IsFakeClient(i) && !GetAdminFlag(GetUserAdmin(i), Admin_Generic)) + { + PushArrayCell(hClients, i); + } + } + + return true; +} + +public bool Filter_Friends(const char[] sPattern, Handle hClients, int client) +{ + if(g_FriendsArray[client] == INVALID_HANDLE) + { + PrintToChat(client, "[SM] Could not read your friendslist, your profile must be set to public!"); + return false; + } + + for(int i = 1; i <= MaxClients; i++) + { + if(i != client && IsClientInGame(i) && !IsFakeClient(i) && IsClientAuthorized(i)) + { + int Steam3ID = GetSteamAccountID(i); + + if(FindValueInArray(g_FriendsArray[client], Steam3ID) != -1) + PushArrayCell(hClients, i); + } + } + + return true; +} + +public bool Filter_NotFriends(const char[] sPattern, Handle hClients, int client) +{ + if(g_FriendsArray[client] == INVALID_HANDLE) + { + PrintToChat(client, "[SM] Could not read your friendslist, your profile must be set to public!"); + return false; + } + + for(int i = 1; i <= MaxClients; i++) + { + if(i != client && IsClientInGame(i) && !IsFakeClient(i) && IsClientAuthorized(i)) + { + int Steam3ID = GetSteamAccountID(i); + + if(FindValueInArray(g_FriendsArray[client], Steam3ID) == -1) + PushArrayCell(hClients, i); + } + } + + return true; +} + +public void OnClientAuthorized(int client, const char[] auth) +{ + if(IsFakeClient(client)) + return; + + char sSteam64ID[32]; + Steam32IDtoSteam64ID(auth, sSteam64ID, sizeof(sSteam64ID)); + + static char sRequest[256]; + FormatEx(sRequest, sizeof(sRequest), "http://api.steampowered.com/ISteamUser/GetFriendList/v0001/?key=%s&steamid=%s&relationship=friend&format=vdf", STEAM_API_KEY, sSteam64ID); + + Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodGET, sRequest); + if (!hRequest || + !SteamWorks_SetHTTPRequestContextValue(hRequest, client) || + !SteamWorks_SetHTTPCallbacks(hRequest, OnTransferComplete) || + !SteamWorks_SendHTTPRequest(hRequest)) + { + CloseHandle(hRequest); + } +} + +public void OnClientDisconnect(int client) +{ + if(g_FriendsArray[client] != INVALID_HANDLE) + CloseHandle(g_FriendsArray[client]); + + g_FriendsArray[client] = INVALID_HANDLE; +} + +public OnTransferComplete(Handle hRequest, bool bFailure, bool bRequestSuccessful, EHTTPStatusCode eStatusCode, int client) +{ + if(bFailure || !bRequestSuccessful || eStatusCode != k_EHTTPStatusCode200OK) + { + // Private profile or maybe steam down? + //LogError("SteamAPI HTTP Response failed: %d", eStatusCode); + CloseHandle(hRequest); + return; + } + + int Length; + SteamWorks_GetHTTPResponseBodySize(hRequest, Length); + + char[] sData = new char[Length]; + SteamWorks_GetHTTPResponseBodyData(hRequest, sData, Length); + //SteamWorks_GetHTTPResponseBodyCallback(hRequest, APIWebResponse, client); + + CloseHandle(hRequest); + + APIWebResponse(sData, client); +} + +public APIWebResponse(const char[] sData, int client) +{ + KeyValues Response = new KeyValues("SteamAPIResponse"); + if(!Response.ImportFromString(sData, "SteamAPIResponse")) + { + LogError("ImportFromString(sData, \"SteamAPIResponse\") failed."); + return; + } + + if(!Response.JumpToKey("friends")) + { + LogError("JumpToKey(\"friends\") failed."); + delete Response; + return; + } + + // No friends? + if(!Response.GotoFirstSubKey()) + { + //LogError("GotoFirstSubKey() failed."); + delete Response; + return; + } + + g_FriendsArray[client] = CreateArray(); + + char sCommunityID[32]; + do + { + Response.GetString("steamid", sCommunityID, sizeof(sCommunityID)); + + PushArrayCell(g_FriendsArray[client], Steam64toSteam3(sCommunityID)); + } + while(Response.GotoNextKey()); + + delete Response; + +/* DEPRECATED JSON CODE + Handle hJSON = DecodeJSON(sData); + if(!hJSON) + { + LogError("DecodeJSON failed."); + return; + } + + Handle hFriendslist = INVALID_HANDLE; + if(!JSONGetObject(hJSON, "friendslist", hFriendslist)) + { + LogError("JSONGetObject(hJSON, \"friendslist\", hFriendslist) failed."); + DestroyJSON(hJSON); + return; + } + + Handle hFriends = INVALID_HANDLE; + if(!JSONGetArray(hFriendslist, "friends", hFriends)) + { + LogError("JSONGetObject(hFriendslist, \"friends\", hFriends) failed."); + DestroyJSON(hJSON); + return; + } + + int ArraySize = GetArraySize(hFriends); + PrintToServer("ArraySize: %d", ArraySize); + + for(int i = 0; i < ArraySize; i++) + { + Handle hEntry = INVALID_HANDLE; + JSONGetArrayObject(hFriends, i, hEntry); + + static char sCommunityID[32]; + if(!JSONGetString(hEntry, "steamid", sCommunityID, sizeof(sCommunityID))) + { + LogError("JSONGetString(hArray, \"steamid\", sCommunityID, %d) failed.", sizeof(sCommunityID)); + DestroyJSON(hJSON); + return; + } + + PushArrayCell(g_FriendsArray[client], Steam64toSteam3(sCommunityID)); + } + + DestroyJSON(hJSON); +*/ +} + + +stock bool Steam32IDtoSteam64ID(const char[] sSteam32ID, char[] sSteam64ID, int Size) +{ + if(strlen(sSteam32ID) < 11 || strncmp(sSteam32ID[0], "STEAM_0:", 8)) + { + sSteam64ID[0] = 0; + return false; + } + + int iUpper = 765611979; + int isSteam64ID = StringToInt(sSteam32ID[10]) * 2 + 60265728 + sSteam32ID[8] - 48; + + int iDiv = isSteam64ID / 100000000; + int iIdx = 9 - (iDiv ? (iDiv / 10 + 1) : 0); + iUpper += iDiv; + + IntToString(isSteam64ID, sSteam64ID[iIdx], Size - iIdx); + iIdx = sSteam64ID[9]; + IntToString(iUpper, sSteam64ID, Size); + sSteam64ID[9] = iIdx; + + return true; +} + +stock int Steam64toSteam3(const char[] sSteam64ID) +{ + if(strlen(sSteam64ID) != 17) + return 0; + + // convert SteamID64 to array of integers + int aSteam64ID[17]; + for(int i = 0; i < 17; i++) + aSteam64ID[i] = sSteam64ID[i] - 48; + + // subtract individual SteamID64 identifier (0x0110000100000000) + int aSteam64IDIdent[] = {7, 6, 5, 6, 1, 1, 9, 7, 9, 6, 0, 2, 6, 5, 7, 2, 8}; + int Carry = 0; + for(int i = 16; i >= 0; i--) + { + if(aSteam64ID[i] < aSteam64IDIdent[i] + Carry) + { + aSteam64ID[i] = aSteam64ID[i] - aSteam64IDIdent[i] - Carry + 10; + Carry = 1; + } + else + { + aSteam64ID[i] = aSteam64ID[i] - aSteam64IDIdent[i] - Carry; + Carry = 0; + } + } + + char aBuf[17]; + int j = 0; + bool ZereosDone = false; + for(int i = 0; i < 17; i++) + { + if(!ZereosDone && !aSteam64ID[i]) + continue; + ZereosDone = true; + + aBuf[j++] = aSteam64ID[i] + 48; + } + + return StringToInt(aBuf); +} + +public int Native_IsClientFriend(Handle plugin, int numParams) +{ + new client = GetNativeCell(1); + new friend = GetNativeCell(2); + + if(client > MaxClients || client <= 0 || friend > MaxClients || friend <= 0) + { + ThrowNativeError(SP_ERROR_NATIVE, "Client is not valid."); + return -1; + } + + if(!IsClientInGame(client) || !IsClientInGame(friend)) + { + ThrowNativeError(SP_ERROR_NATIVE, "Client is not in-game."); + return -1; + } + + if(IsFakeClient(client) || IsFakeClient(friend)) + { + ThrowNativeError(SP_ERROR_NATIVE, "Client is fake-client."); + return -1; + } + + if(g_FriendsArray[client] == INVALID_HANDLE) + return -1; + + if(IsClientAuthorized(friend)) + { + int Steam3ID = GetSteamAccountID(friend); + + if(FindValueInArray(g_FriendsArray[client], Steam3ID) != -1) + return 1; + } + + return 0; +} + +public int Native_ReadClientFriends(Handle plugin, int numParams) +{ + new client = GetNativeCell(1); + + if(client > MaxClients || client <= 0) + { + ThrowNativeError(SP_ERROR_NATIVE, "Client is not valid."); + return -1; + } + + if(g_FriendsArray[client] != INVALID_HANDLE) + return 1; + + return 0; +} diff --git a/AdvancedTargeting/scripting/include/AdvancedTargeting.inc b/AdvancedTargeting/scripting/include/AdvancedTargeting.inc new file mode 100644 index 00000000..4e4cb088 --- /dev/null +++ b/AdvancedTargeting/scripting/include/AdvancedTargeting.inc @@ -0,0 +1,25 @@ +#if defined _AdvancedTargeting_Included + #endinput +#endif +#define _AdvancedTargeting_Included + +native int IsClientFriend(int client, int friend); +native int ReadClientFriends(int client); + +public SharedPlugin:__pl_AdvancedTargeting = +{ + name = "AdvancedTargeting", + file = "AdvancedTargeting.smx", +#if defined REQUIRE_PLUGIN + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_PLUGIN +public __pl_myfile_SetNTVOptional() +{ + MarkNativeAsOptional("IsClientFriend"); +} +#endif diff --git a/AdvancedTargeting/scripting/include/SteamWorks.inc b/AdvancedTargeting/scripting/include/SteamWorks.inc new file mode 120000 index 00000000..41883907 --- /dev/null +++ b/AdvancedTargeting/scripting/include/SteamWorks.inc @@ -0,0 +1 @@ +../../../includes/SteamWorks.inc \ No newline at end of file diff --git a/AfkManager/scripting/AfkManager.sp b/AfkManager/scripting/AfkManager.sp new file mode 100644 index 00000000..037f0690 --- /dev/null +++ b/AfkManager/scripting/AfkManager.sp @@ -0,0 +1,314 @@ +#pragma semicolon 1 + +#include +#include +#include + +#define AFK_CHECK_INTERVAL 5.0 + +bool g_Players_bEnabled[MAXPLAYERS + 1]; +bool g_Players_bFlagged[MAXPLAYERS + 1]; +int g_Players_iLastAction[MAXPLAYERS + 1]; +float g_Players_fEyePosition[MAXPLAYERS + 1][3]; +int g_Players_iButtons[MAXPLAYERS + 1]; +int g_Players_iSpecMode[MAXPLAYERS + 1]; +int g_Players_iSpecTarget[MAXPLAYERS + 1]; + +float g_fKickTime; +float g_fMoveTime; +float g_fWarnTime; +int g_iKickMinPlayers; +int g_iMoveMinPlayers; +int g_iImmunity; + +public Plugin myinfo = +{ + name = "Good AFK Manager", + author = "BotoX", + description = "A good AFK manager?", + version = "1.0", + url = "" +}; + +public Cvar_KickTime(Handle:cvar, const String:oldvalue[], const String:newvalue[]) +{ + g_fKickTime = GetConVarFloat(cvar); +} +public Cvar_MoveTime(Handle:cvar, const String:oldvalue[], const String:newvalue[]) +{ + g_fMoveTime = GetConVarFloat(cvar); +} +public Cvar_WarnTime(Handle:cvar, const String:oldvalue[], const String:newvalue[]) +{ + g_fWarnTime = GetConVarFloat(cvar); +} +public Cvar_KickMinPlayers(Handle:cvar, const String:oldvalue[], const String:newvalue[]) +{ + g_iKickMinPlayers = GetConVarInt(cvar); +} +public Cvar_MoveMinPlayers(Handle:cvar, const String:oldvalue[], const String:newvalue[]) +{ + g_iMoveMinPlayers = GetConVarInt(cvar); +} +public Cvar_Immunity(Handle:cvar, const String:oldvalue[], const String:newvalue[]) +{ + g_iImmunity = GetConVarInt(cvar); +} + +public OnPluginStart() +{ + Handle cvar; + HookConVarChange((cvar = CreateConVar("sm_afk_move_min", "4", "Min players for AFK move")), Cvar_MoveMinPlayers); + g_iMoveMinPlayers = GetConVarInt(cvar); + + HookConVarChange((cvar = CreateConVar("sm_afk_kick_min", "6", "Min players for AFK kick")), Cvar_KickMinPlayers); + g_iKickMinPlayers = GetConVarInt(cvar); + + HookConVarChange((cvar = CreateConVar("sm_afk_move_time", "60.0", "Time in seconds for AFK Move. 0 = DISABLED")), Cvar_MoveTime); + g_fMoveTime = GetConVarFloat(cvar); + + HookConVarChange((cvar = CreateConVar("sm_afk_kick_time", "120.0", "Time in seconds to AFK Kick. 0 = DISABLED")), Cvar_KickTime); + g_fKickTime = GetConVarFloat(cvar); + + HookConVarChange((cvar = CreateConVar("sm_afk_warn_time", "30.0", "Time in seconds remaining before warning")), Cvar_WarnTime); + g_fWarnTime = GetConVarFloat(cvar); + + HookConVarChange((cvar = CreateConVar("sm_afk_immunity", "1", "AFK admins immunity: 0 = DISABLED, 1 = COMPLETE, 2 = KICK, 3 = MOVE")), Cvar_Immunity); + g_iImmunity = GetConVarInt(cvar); + + CloseHandle(cvar); + + AddCommandListener(Command_Say, "say"); + AddCommandListener(Command_Say, "say_team"); + HookEvent("player_team", Event_PlayerTeamPost, EventHookMode_Post); + + AutoExecConfig(true, "plugin.AfkManager"); +} + +public OnMapStart() +{ + CreateTimer(AFK_CHECK_INTERVAL, Timer_CheckPlayer, _, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + for (int Index = 1; Index <= MaxClients; Index++) + { + g_Players_bEnabled[Index] = false; + if (IsClientConnected(Index) && IsClientInGame(Index) && !IsFakeClient(Index)) + InitializePlayer(Index); + } +} + +CheckAdminImmunity(Index) +{ + AdminId Id = GetUserAdmin(Index); + return GetAdminFlag(Id, Admin_Generic); +} + +ResetPlayer(Index) +{ + g_Players_bEnabled[Index] = false; + g_Players_bFlagged[Index] = false; + g_Players_iLastAction[Index] = 0; + g_Players_fEyePosition[Index] = Float:{0.0, 0.0, 0.0}; + g_Players_iButtons[Index] = 0; + g_Players_iSpecMode[Index] = 0; + g_Players_iSpecTarget[Index] = 0; +} + +InitializePlayer(Index) +{ + if (!(g_iImmunity == 1 && CheckAdminImmunity(Index))) + { + ResetPlayer(Index); + g_Players_iLastAction[Index] = GetTime(); + g_Players_bEnabled[Index] = true; + } +} + +public OnClientPostAdminCheck(Index) +{ + if (!IsFakeClient(Index)) + InitializePlayer(Index); +} + +public OnClientDisconnect(Index) +{ + ResetPlayer(Index); +} + +public Action:Event_PlayerTeamPost(Handle:event, const String:name[], bool:dontBroadcast) +{ + int Index = GetClientOfUserId(GetEventInt(event, "userid")); + if (Index > 0 && !IsFakeClient(Index)) + { + if (!g_Players_bEnabled[Index]) + InitializePlayer(Index); + g_Players_iLastAction[Index] = GetTime(); + } +} + +public Action:Command_Say(Index, const String:Command[], Args) +{ + g_Players_iLastAction[Index] = GetTime(); +} + +public Action:OnPlayerRunCmd(Index, &iButtons, &iImpulse, Float:fVel[3], Float:fAngles[3], &iWeapon) +{ + if (((g_Players_fEyePosition[Index][0] != fAngles[0]) || + (g_Players_fEyePosition[Index][1] != fAngles[1]) || + (g_Players_fEyePosition[Index][2] != fAngles[2])) + && g_Players_iSpecMode[Index] != 4) // OBS_MODE_IN_EYE + { + if(!((iButtons & IN_LEFT) || (iButtons & IN_RIGHT))) + g_Players_iLastAction[Index] = GetTime(); + + g_Players_fEyePosition[Index] = fAngles; + } + + if(g_Players_iButtons[Index] != iButtons) + { + g_Players_iLastAction[Index] = GetTime(); + g_Players_iButtons[Index] = iButtons; + } + + return Plugin_Continue; +} + +public Action:Timer_CheckPlayer(Handle:Timer, any:Data) +{ + int Index; + int Clients = 0; + + for (Index = 1; Index <= MaxClients; Index++) + { + if (IsClientInGame(Index) && !IsFakeClient(Index)) + Clients++; + } + + bool bMovePlayers = (Clients >= g_iMoveMinPlayers && g_fMoveTime > 0.0); + bool bKickPlayers = (Clients >= g_iKickMinPlayers && g_fKickTime > 0.0); + + if (!bMovePlayers && !bKickPlayers) + return Plugin_Continue; + + for (Index = 1; Index <= MaxClients; Index++) + { + if (!g_Players_bEnabled[Index] || !IsClientInGame(Index)) // Is this player actually in the game? + continue; + + int iTeamNum = GetClientTeam(Index); + + if (IsClientObserver(Index)) + { + if (iTeamNum > CS_TEAM_SPECTATOR && !IsPlayerAlive(Index)) + continue; + + int iSpecMode = g_Players_iSpecMode[Index]; + int iSpecTarget = g_Players_iSpecTarget[Index]; + + g_Players_iSpecMode[Index] = GetEntProp(Index, Prop_Send, "m_iObserverMode"); + g_Players_iSpecTarget[Index] = GetEntPropEnt(Index, Prop_Send, "m_hObserverTarget"); + + if ((iSpecMode && g_Players_iSpecMode[Index] != iSpecMode) || (iSpecTarget && g_Players_iSpecTarget[Index] != iSpecTarget)) + g_Players_iLastAction[Index] = GetTime(); + } + + int IdleTime = GetTime() - g_Players_iLastAction[Index]; + + if (g_Players_bFlagged[Index] && (g_fKickTime - IdleTime) > 0.0) + { + PrintCenterText(Index, "Welcome back!"); + PrintToChat(Index, "\x04[AFK] \x01You have been un-flagged for being inactive."); + g_Players_bFlagged[Index] = false; + } + + if (bMovePlayers && iTeamNum > CS_TEAM_SPECTATOR && ( !g_iImmunity || g_iImmunity == 2 || !CheckAdminImmunity(Index))) + { + float iTimeleft = g_fMoveTime - IdleTime; + if (iTimeleft > 0.0) + { + if(iTimeleft <= g_fWarnTime) + { + PrintCenterText(Index, "Warning: If you do not move in %d seconds, you will be moved to spectate.", RoundToFloor(iTimeleft)); + PrintToChat(Index, "\x04[AFK] \x01Warning: If you do not move in %d seconds, you will be moved to spectate.", RoundToFloor(iTimeleft)); + } + } + else + { + decl String:f_Name[MAX_NAME_LENGTH+4]; + Format(f_Name, sizeof(f_Name), "\x03%N\x01", Index); + PrintToChatAll("\x04[AFK] \x01%s was moved to spectate for being AFK too long.", f_Name); + ForcePlayerSuicide(Index); + ChangeClientTeam(Index, CS_TEAM_SPECTATOR); + } + } + else if (g_fKickTime > 0.0 && (!g_iImmunity || g_iImmunity == 3 || !CheckAdminImmunity(Index))) + { + float iTimeleft = g_fKickTime - IdleTime; + if (iTimeleft > 0.0) + { + if (iTimeleft <= g_fWarnTime) + { + PrintCenterText(Index, "Warning: If you do not move in %d seconds, you will be kick-flagged for being inactive.", RoundToFloor(iTimeleft)); + PrintToChat(Index, "\x04[AFK] \x01Warning: If you do not move in %d seconds, you will be kick-flagged for being inactive.", RoundToFloor(iTimeleft)); + } + } + else + { + if (!g_Players_bFlagged[Index]) + { + PrintToChat(Index, "\x04[AFK] \x01You have been kick-flagged for being inactive."); + g_Players_bFlagged[Index] = true; + } + int FlaggedPlayers = 0; + int Position = 1; + for (int Index_ = 1; Index_ <= MaxClients; Index_++) + { + if (!g_Players_bFlagged[Index_]) + continue; + + FlaggedPlayers++; + int IdleTime_ = GetTime() - g_Players_iLastAction[Index_]; + + if (IdleTime_ > IdleTime) + Position++; + } + PrintCenterText(Index, "You have been kick-flagged for being inactive. [%d/%d]", Position, FlaggedPlayers); + } + } + } + + while(bKickPlayers) + { + int InactivePlayer = -1; + int InactivePlayerTime = 0; + + for (Index = 1; Index <= MaxClients; Index++) + { + if (!g_Players_bFlagged[Index]) + continue; + + int IdleTime = GetTime() - g_Players_iLastAction[Index]; + + if (IdleTime > InactivePlayerTime) + { + InactivePlayer = Index; + InactivePlayerTime = IdleTime; + } + } + + if (InactivePlayer == -1) + break; + else + { + decl String:f_Name[MAX_NAME_LENGTH+4]; + Format(f_Name, sizeof(f_Name), "\x03%N\x01", InactivePlayer); + PrintToChatAll("\x04[AFK] %s was kicked for being AFK too long. (%d seconds)", f_Name, InactivePlayerTime); + KickClient(InactivePlayer, "[AFK] You were kicked for being AFK too long. (%d seconds)", InactivePlayerTime); + Clients--; + g_Players_bFlagged[InactivePlayer] = false; + } + + bKickPlayers = (Clients >= g_iKickMinPlayers && g_fKickTime > 0.0); + } + + return Plugin_Continue; +} diff --git a/ConnectAnnounce/scripting/ConnectAnnounce.sp b/ConnectAnnounce/scripting/ConnectAnnounce.sp new file mode 100644 index 00000000..92bdd326 --- /dev/null +++ b/ConnectAnnounce/scripting/ConnectAnnounce.sp @@ -0,0 +1,31 @@ +#pragma semicolon 1 + +#include +#include + +#pragma newdecls required + +public Plugin myinfo = { + name = "Connect Announce", + author = "BotoX", + description = "Simple connect announcer", + version = "1.0", + url = "" +} + +public void OnClientPostAdminCheck(int client) +{ + if(IsFakeClient(client)) + return; + + static char sAuth[32]; + static char sIP[16]; + static char sCountry[32]; + + GetClientAuthId(client, AuthId_Steam2, sAuth, sizeof(sAuth)); + + if(GetClientIP(client, sIP, sizeof(sIP)) && GeoipCountry(sIP, sCountry, sizeof(sCountry))) + PrintToChatAll("\x04%L [\x03%s\x04] connected from %s", client, sAuth, sCountry); + else + PrintToChatAll("\x04%L [\x03%s\x04] connected", client, sAuth); +} diff --git a/ExtraCommands/scripting/ExtraCommands.sp b/ExtraCommands/scripting/ExtraCommands.sp new file mode 100644 index 00000000..b73ece6e --- /dev/null +++ b/ExtraCommands/scripting/ExtraCommands.sp @@ -0,0 +1,648 @@ +#pragma semicolon 1 + +#include +#include +#include + +bool g_bInBuyZoneAll = false; +bool g_bInBuyZone[MAXPLAYERS + 1] = {false, ...}; + +bool g_bInfAmmoHooked = false; +bool g_bInfAmmoAll = false; +bool g_bInfAmmo[MAXPLAYERS + 1] = {false, ...}; + +ConVar g_CVar_sv_pausable; +bool g_bPaused; + +public Plugin:myinfo = +{ + name = "Advanced Commands", + author = "BotoX", + description = "Adds: hp, kevlar, weapon, strip, buyzone, iammo, speed", + version = "1.0", + url = "" +}; + +public OnPluginStart() +{ + RegAdminCmd("sm_hp", Command_Health, ADMFLAG_GENERIC, "sm_hp <#userid|name> "); + RegAdminCmd("sm_kevlar", Command_Kevlar, ADMFLAG_GENERIC, "sm_kevlar <#userid|name> "); + RegAdminCmd("sm_weapon", Command_Weapon, ADMFLAG_GENERIC, "sm_weapon <#userid|name> [clip] [ammo]"); + RegAdminCmd("sm_give", Command_Weapon, ADMFLAG_GENERIC, "sm_give <#userid|name> [clip] [ammo]"); + RegAdminCmd("sm_strip", Command_Strip, ADMFLAG_GENERIC, "sm_strip <#userid|name>"); + RegAdminCmd("sm_buyzone", Command_BuyZone, ADMFLAG_CUSTOM3, "sm_buyzone <#userid|name> <0|1>"); + RegAdminCmd("sm_iammo", Command_InfAmmo, ADMFLAG_CUSTOM3, "sm_iammo <#userid|name> <0|1>"); + RegAdminCmd("sm_speed", Command_Speed, ADMFLAG_CUSTOM3, "sm_speed <#userid|name> <0|1>"); + + HookEvent("bomb_planted", Event_BombPlanted, EventHookMode_Pre); + HookEvent("bomb_defused", Event_BombDefused, EventHookMode_Pre); + + g_CVar_sv_pausable = FindConVar("sv_pausable"); + if(g_CVar_sv_pausable) + AddCommandListener(Listener_Pause, "pause"); +} + +public OnMapStart() +{ + g_bInBuyZoneAll = false; + g_bInfAmmoAll = false; + if(g_bInfAmmoHooked) + { + UnhookEvent("weapon_fire", Event_WeaponFire); + g_bInfAmmoHooked = false; + } + + /* Handle late load */ + for(new i = 1; i <= MaxClients; i++) + { + if(IsClientConnected(i) && IsClientInGame(i)) + { + g_bInfAmmo[i] = false; + g_bInBuyZone[i] = false; + SDKHook(i, SDKHook_PreThink, OnPreThink); + SDKHook(i, SDKHook_PostThinkPost, OnPostThinkPost); + } + } +} + +public Action Listener_Pause(int client, const char[] command, int argc) +{ + if(!g_CVar_sv_pausable.BoolValue) + { + ReplyToCommand(client, "sv_pausable is set to 0!"); + return Plugin_Handled; + } + if(client == 0) + { + PrintToServer("[SM] Cannot use command from server console."); + return Plugin_Handled; + } + if(!IsClientAuthorized(client) || !GetAdminFlag(GetUserAdmin(client), Admin_Generic)) + { + ReplyToCommand(client, "You do not have permission to pause the game."); + return Plugin_Handled; + } + + ShowActivity2(client, "[SM] ", "%s the game.", g_bPaused ? "Unpaused" : "Paused"); + LogAction(client, -1, "%s the game.", g_bPaused ? "Unpaused" : "Paused"); + g_bPaused = !g_bPaused; + return Plugin_Continue; +} + + +public Action:Event_BombPlanted(Handle:event, const String:name[], bool:dontBroadcast) +{ + for(new i = 1; i < MAXPLAYERS; i++) + { + if(IsClientInGame(i)) + ClientCommand(i, "playgamesound \"radio/bombpl.wav\""); + } + return Plugin_Handled; +} + +public Action:Event_BombDefused(Handle:event, const String:name[], bool:dontBroadcast) +{ + for(new i = 1; i < MAXPLAYERS; i++) + { + if(IsClientInGame(i)) + ClientCommand(i, "playgamesound \"radio/bombdef.wav\""); + } + return Plugin_Handled; +} + +public OnClientPutInServer(client) +{ + g_bInBuyZone[client] = false; + g_bInfAmmo[client] = false; + SDKHook(client, SDKHook_PreThink, OnPreThink); + SDKHook(client, SDKHook_PostThinkPost, OnPostThinkPost); +} + +public OnPreThink(client) +{ + if(IsClientInGame(client) && IsPlayerAlive(client)) + { + SetEntProp(client, Prop_Send, "m_bInBombZone", 1); + } +} + +public OnPostThinkPost(client) +{ + if(IsClientInGame(client) && IsPlayerAlive(client)) + { + if(g_bInBuyZoneAll || g_bInBuyZone[client]) + SetEntProp(client, Prop_Send, "m_bInBuyZone", 1); + } +} + +public Event_WeaponFire(Handle:hEvent, String:name[], bool:dontBroadcast) +{ + new client = GetClientOfUserId(GetEventInt(hEvent, "userid")); + if(!g_bInfAmmoAll && !g_bInfAmmo[client]) + return; + + new weapon = GetEntPropEnt(client, Prop_Data, "m_hActiveWeapon", 0); + if(IsValidEntity(weapon)) + { + if(weapon == GetPlayerWeaponSlot(client, 0) || weapon == GetPlayerWeaponSlot(client, 1)) + { + if(GetEntProp(weapon, Prop_Send, "m_iState", 4, 0) == 2 && GetEntProp(weapon, Prop_Send, "m_iClip1", 4, 0)) + { + new toAdd = 1; + new String:weaponClassname[128]; + GetEntityClassname(weapon, weaponClassname, 128); + + if(StrEqual(weaponClassname, "weapon_glock", true) || StrEqual(weaponClassname, "weapon_famas", true)) + { + if(GetEntProp(weapon, Prop_Data, "m_bBurstMode", 4, 0)) + { + switch (GetEntProp(weapon, Prop_Send, "m_iClip1", 4, 0)) + { + case 1: + { + toAdd = 1; + } + case 2: + { + toAdd = 2; + } + default: + { + toAdd = 3; + } + } + } + } + SetEntProp(weapon, Prop_Send, "m_iClip1", GetEntProp(weapon, Prop_Send, "m_iClip1", 4, 0) + toAdd, 4, 0); + } + } + } + + return; +} + +public Action:Command_Health(client, args) +{ + if(args < 2) + { + ReplyToCommand(client, "[SM] Usage: sm_hp <#userid|name> "); + return Plugin_Handled; + } + + decl String:arg[65]; + GetCmdArg(1, arg, sizeof(arg)); + + new amount = 0; + decl String:arg2[20]; + GetCmdArg(2, arg2, sizeof(arg2)); + if(StringToIntEx(arg2, amount) == 0 || amount <= 0) + { + ReplyToCommand(client, "[SM] Invalid Value"); + return Plugin_Handled; + } + + decl String:target_name[MAX_TARGET_LENGTH]; + decl target_list[MAXPLAYERS], target_count, bool:tn_is_ml; + + if((target_count = ProcessTargetString( + arg, + client, + target_list, + MAXPLAYERS, + COMMAND_FILTER_ALIVE, + target_name, + sizeof(target_name), + tn_is_ml)) <= 0) + { + ReplyToTargetError(client, target_count); + return Plugin_Handled; + } + + for(new i = 0; i < target_count; i++) + { + SetEntProp(target_list[i], Prop_Send, "m_iHealth", amount, 1); + } + + ShowActivity2(client, "[SM] ", "Set health to %d on target %s", amount, target_name); + + return Plugin_Handled; +} + +public Action:Command_Kevlar(client, args) +{ + if(args < 2) + { + ReplyToCommand(client, "[SM] Usage: sm_kevlar <#userid|name> "); + return Plugin_Handled; + } + + decl String:arg[65]; + GetCmdArg(1, arg, sizeof(arg)); + + new amount = 0; + decl String:arg2[20]; + GetCmdArg(2, arg2, sizeof(arg2)); + if(StringToIntEx(arg2, amount) == 0 || amount <= 0) + { + ReplyToCommand(client, "[SM] Invalid Value"); + return Plugin_Handled; + } + + decl String:target_name[MAX_TARGET_LENGTH]; + decl target_list[MAXPLAYERS], target_count, bool:tn_is_ml; + + if((target_count = ProcessTargetString( + arg, + client, + target_list, + MAXPLAYERS, + COMMAND_FILTER_ALIVE, + target_name, + sizeof(target_name), + tn_is_ml)) <= 0) + { + ReplyToTargetError(client, target_count); + return Plugin_Handled; + } + + for(new i = 0; i < target_count; i++) + { + SetEntProp(target_list[i], Prop_Send, "m_ArmorValue", amount, 1); + } + + ShowActivity2(client, "[SM] ", "Set kevlar to %d on target %s", amount, target_name); + LogAction(client, -1, "Set kevlar to %d on target %s", amount, target_name); + + return Plugin_Handled; +} + +public Action:Command_Weapon(client, args) +{ + if(args < 2) + { + ReplyToCommand(client, "[SM] Usage: sm_weapon <#userid|name> [clip] [ammo]"); + return Plugin_Handled; + } + + new ammo = 2500; + new clip = -1; + + decl String:arg[65]; + GetCmdArg(1, arg, sizeof(arg)); + + decl String:arg2[65]; + GetCmdArg(2, arg2, sizeof(arg2)); + + decl String:weapon[65]; + if(strncmp(arg2, "weapon_", 7) != 0 && strncmp(arg2, "item_", 5) != 0 && !StrEqual(arg2, "nvg", false)) + Format(weapon, sizeof(weapon), "weapon_%s", arg2); + else + strcopy(weapon, sizeof(weapon), arg2); + + if(StrContains(weapon, "grenade", false) != -1 || StrContains(weapon, "flashbang", false) != -1 || strncmp(arg2, "item_", 5) == 0) + ammo = -1; + + new AdminId:id = GetUserAdmin(client); + new superadmin = GetAdminFlag(id, Admin_Custom3); + + if(!superadmin) + { + if(StrEqual(weapon, "weapon_c4", false) || StrEqual(weapon, "weapon_smokegrenade", false) || StrEqual(weapon, "item_defuser", false)) + { + ReplyToCommand(client, "[SM] This weapon is restricted!"); + return Plugin_Handled; + } + } + + if(args >= 3) + { + decl String:arg3[20]; + GetCmdArg(3, arg3, sizeof(arg3)); + if(StringToIntEx(arg3, clip) == 0) + { + ReplyToCommand(client, "[SM] Invalid Clip Value"); + return Plugin_Handled; + } + } + if(args >= 4) + { + decl String:arg4[20]; + GetCmdArg(4, arg4, sizeof(arg4)); + if(StringToIntEx(arg4, ammo) == 0) + { + ReplyToCommand(client, "[SM] Invalid Ammo Value"); + return Plugin_Handled; + } + } + + if(StrContains(weapon, "grenade", false) != -1 || StrContains(weapon, "flashbang", false) != -1) + { + new tmp = ammo; + ammo = clip; + clip = tmp; + } + + decl String:target_name[MAX_TARGET_LENGTH]; + decl target_list[MAXPLAYERS], target_count, bool:tn_is_ml; + + if((target_count = ProcessTargetString( + arg, + client, + target_list, + MAXPLAYERS, + COMMAND_FILTER_ALIVE, + target_name, + sizeof(target_name), + tn_is_ml)) <= 0) + { + ReplyToTargetError(client, target_count); + return Plugin_Handled; + } + + if(StrEqual(weapon, "nvg", false)) + { + for(new i = 0; i < target_count; i++) + SetEntProp(target_list[i], Prop_Send, "m_bHasNightVision", 1, 1); + } + else + { + for(new i = 0; i < target_count; i++) + { + new ent = GivePlayerItem(target_list[i], weapon); + + if(ent == -1) { + ReplyToCommand(client, "[SM] Invalid Weapon"); + return Plugin_Handled; + } + + if(clip != -1) + SetEntProp(ent, Prop_Send, "m_iClip1", clip); + + if(ammo != -1) + { + new PrimaryAmmoType = GetEntProp(ent, Prop_Data, "m_iPrimaryAmmoType"); + if(PrimaryAmmoType != -1) + SetEntProp(target_list[i], Prop_Send, "m_iAmmo", ammo, _, PrimaryAmmoType); + } + + if(strncmp(arg2, "item_", 5) != 0 && !StrEqual(weapon, "weapon_hegrenade", false)) + EquipPlayerWeapon(target_list[i], ent); + + if(ammo != -1) + { + new PrimaryAmmoType = GetEntProp(ent, Prop_Data, "m_iPrimaryAmmoType"); + if(PrimaryAmmoType != -1) + SetEntProp(target_list[i], Prop_Send, "m_iAmmo", ammo, _, PrimaryAmmoType); + } + } + } + + ShowActivity2(client, "[SM] ", "Gave %s to target %s", weapon, target_name); + LogAction(client, -1, "Gave %s to target %s", weapon, target_name); + + return Plugin_Handled; +} + +public Action:Command_Strip(client, args) +{ + if(args < 1) + { + ReplyToCommand(client, "[SM] Usage: sm_strip <#userid|name>"); + return Plugin_Handled; + } + + decl String:arg[65]; + GetCmdArg(1, arg, sizeof(arg)); + + decl String:target_name[MAX_TARGET_LENGTH]; + decl target_list[MAXPLAYERS], target_count, bool:tn_is_ml; + + if((target_count = ProcessTargetString( + arg, + client, + target_list, + MAXPLAYERS, + COMMAND_FILTER_ALIVE, + target_name, + sizeof(target_name), + tn_is_ml)) <= 0) + { + ReplyToTargetError(client, target_count); + return Plugin_Handled; + } + + for(new i = 0; i < target_count; i++) + { + for(new j = 0; j < 5; j++) + { + new w = -1; + while ((w = GetPlayerWeaponSlot(target_list[i], j)) != -1) + { + if(IsValidEntity(w)) + RemovePlayerItem(target_list[i], w); + } + } + } + + ShowActivity2(client, "[SM] ", "Stripped all weapons on target %s", target_name); + LogAction(client, -1, "Stripped all weapons on target %s", target_name); + + return Plugin_Handled; +} + +public Action:Command_BuyZone(client, args) +{ + if(args < 2) + { + ReplyToCommand(client, "[SM] Usage: sm_buyzone <#userid|name> <0|1>"); + return Plugin_Handled; + } + + decl String:arg[65]; + GetCmdArg(1, arg, sizeof(arg)); + + new value = -1; + decl String:arg2[20]; + GetCmdArg(2, arg2, sizeof(arg2)); + if(StringToIntEx(arg2, value) == 0) + { + ReplyToCommand(client, "[SM] Invalid Value"); + return Plugin_Handled; + } + + decl String:target_name[MAX_TARGET_LENGTH]; + + if(StrEqual(arg, "@all", false)) + { + target_name = "all players"; + g_bInBuyZoneAll = value ? true : false; + } + else + { + decl target_list[MAXPLAYERS], target_count, bool:tn_is_ml; + + if((target_count = ProcessTargetString( + arg, + client, + target_list, + MAXPLAYERS, + COMMAND_FILTER_ALIVE, + target_name, + sizeof(target_name), + tn_is_ml)) <= 0) + { + ReplyToTargetError(client, target_count); + return Plugin_Handled; + } + + for(new i = 0; i < target_count; i++) + { + g_bInBuyZone[target_list[i]] = value ? true : false; + } + } + + ShowActivity2(client, "[SM] ", "%s permanent buyzone on target %s", (value ? "Enabled" : "Disabled"), target_name); + LogAction(client, -1, "%s permanent buyzone on target %s", (value ? "Enabled" : "Disabled"), target_name); + + return Plugin_Handled; +} + +public Action:Command_InfAmmo(client, args) +{ + if(args < 2) + { + ReplyToCommand(client, "[SM] Usage: sm_iammo <#userid|name> <0|1>"); + return Plugin_Handled; + } + + decl String:arg[65]; + GetCmdArg(1, arg, sizeof(arg)); + + new value = -1; + decl String:arg2[20]; + GetCmdArg(2, arg2, sizeof(arg2)); + if(StringToIntEx(arg2, value) == 0) + { + ReplyToCommand(client, "[SM] Invalid Value"); + return Plugin_Handled; + } + + decl String:target_name[MAX_TARGET_LENGTH]; + + if(StrEqual(arg, "@all", false)) + { + target_name = "all players"; + g_bInfAmmoAll = value ? true : false; + + if(!g_bInfAmmoAll) + { + for(new i = 0; i < MAXPLAYERS; i++) + g_bInfAmmo[i] = false; + } + } + else + { + decl target_list[MAXPLAYERS], target_count, bool:tn_is_ml; + + if((target_count = ProcessTargetString( + arg, + client, + target_list, + MAXPLAYERS, + COMMAND_FILTER_ALIVE, + target_name, + sizeof(target_name), + tn_is_ml)) <= 0) + { + ReplyToTargetError(client, target_count); + return Plugin_Handled; + } + + for(new i = 0; i < target_count; i++) + { + g_bInfAmmo[target_list[i]] = value ? true : false; + } + } + + ShowActivity2(client, "[SM] ", "%s infinite ammo on target %s", (value ? "Enabled" : "Disabled"), target_name); + LogAction(client, -1, "%s infinite ammo on target %s", (value ? "Enabled" : "Disabled"), target_name); + + if(g_bInfAmmoAll) + { + if(!g_bInfAmmoHooked) + { + HookEvent("weapon_fire", Event_WeaponFire); + g_bInfAmmoHooked = true; + } + return Plugin_Handled; + } + + for(new i = 0; i < MAXPLAYERS; i++) + { + if(g_bInfAmmo[i]) + { + if(!g_bInfAmmoHooked) + { + HookEvent("weapon_fire", Event_WeaponFire); + g_bInfAmmoHooked = true; + } + return Plugin_Handled; + } + } + + if(g_bInfAmmoHooked) + { + UnhookEvent("weapon_fire", Event_WeaponFire); + g_bInfAmmoHooked = false; + } + + return Plugin_Handled; +} + +public Action:Command_Speed(client, args) +{ + if(args < 2) + { + ReplyToCommand(client, "[SM] Usage: sm_speed <#userid|name> "); + return Plugin_Handled; + } + + decl String:arg[65]; + GetCmdArg(1, arg, sizeof(arg)); + + new Float:speed = 0.0; + decl String:arg2[20]; + GetCmdArg(2, arg2, sizeof(arg2)); + if(StringToFloatEx(arg2, speed) == 0 || speed <= 0.0) + { + ReplyToCommand(client, "[SM] Invalid Value"); + return Plugin_Handled; + } + + decl String:target_name[MAX_TARGET_LENGTH]; + decl target_list[MAXPLAYERS], target_count, bool:tn_is_ml; + + if((target_count = ProcessTargetString( + arg, + client, + target_list, + MAXPLAYERS, + COMMAND_FILTER_ALIVE, + target_name, + sizeof(target_name), + tn_is_ml)) <= 0) + { + ReplyToTargetError(client, target_count); + return Plugin_Handled; + } + + for(new i = 0; i < target_count; i++) + { + SetEntPropFloat(target_list[i], Prop_Data, "m_flLaggedMovementValue", speed); + } + + ShowActivity2(client, "[SM] ", "Set speed to %.2f on target %s", speed, target_name); + LogAction(client, -1, "Set speed to %.2f on target %s", speed, target_name); + + return Plugin_Handled; +} diff --git a/FixGameUI/gamedata/FixGameUI.games.txt b/FixGameUI/gamedata/FixGameUI.games.txt new file mode 100644 index 00000000..0952ab11 --- /dev/null +++ b/FixGameUI/gamedata/FixGameUI.games.txt @@ -0,0 +1,50 @@ +"Games" +{ + "#default" + { + "#supported" + { + "game" "cstrike" + "game" "tf" + "game" "dod" + "game" "hl2mp" + "engine" "sdk2013" + } + + "Offsets" + { + "AcceptInput" + { + "windows" "36" + "linux" "37" + "mac" "37" + } + } + } + + "csgo" + { + "Offsets" + { + "AcceptInput" + { + "windows" "40" + "linux" "41" + "mac" "41" + } + } + } + + "left4dead2" + { + "Offsets" + { + "AcceptInput" + { + "windows" "43" + "linux" "44" + "mac" "44" + } + } + } +} diff --git a/FixGameUI/scripting/FixGameUI.sp b/FixGameUI/scripting/FixGameUI.sp new file mode 100644 index 00000000..0365d981 --- /dev/null +++ b/FixGameUI/scripting/FixGameUI.sp @@ -0,0 +1,128 @@ +#pragma semicolon 1 + +#include +#include +#include +#include +#include +#include + +public Plugin:myinfo = +{ + name = "FixGameUI", + author = "hlstriker + GoD-Tony", + description = "Fixes game_ui entity bug.", + version = "1.0", + url = "" +} + +new g_iAttachedGameUI[MAXPLAYERS+1]; +new Handle:g_hAcceptInput = INVALID_HANDLE; + +public OnPluginStart() +{ + HookEvent("player_death", Event_PlayerDeath, EventHookMode_Post); + + HookEntityOutput("game_ui", "PlayerOn", GameUI_PlayerOn); + HookEntityOutput("game_ui", "PlayerOff", GameUI_PlayerOff); + + // Gamedata. + new Handle:hConfig = LoadGameConfigFile("FixGameUI.games"); + if (hConfig == INVALID_HANDLE) + { + SetFailState("Could not find gamedata file: FixGameUI.games.txt"); + } + + new offset = GameConfGetOffset(hConfig, "AcceptInput"); + if (offset == -1) + { + SetFailState("Failed to find AcceptInput offset"); + } + CloseHandle(hConfig); + + // DHooks. + g_hAcceptInput = DHookCreate(offset, HookType_Entity, ReturnType_Bool, ThisPointer_CBaseEntity, Hook_AcceptInput); + DHookAddParam(g_hAcceptInput, HookParamType_CharPtr); + DHookAddParam(g_hAcceptInput, HookParamType_CBaseEntity); + DHookAddParam(g_hAcceptInput, HookParamType_CBaseEntity); + DHookAddParam(g_hAcceptInput, HookParamType_Object, 20); //varaint_t is a union of 12 (float[3]) plus two int type params 12 + 8 = 20 + DHookAddParam(g_hAcceptInput, HookParamType_Int); + +} + +public Action:Event_PlayerDeath(Handle:hEvent, const String:szName[], bool:bDontBroadcast) +{ + new iClient = GetClientOfUserId(GetEventInt(hEvent, "userid")); + RemoveFromGameUI(iClient); + SetClientViewEntity(iClient, iClient); + + new iFlags = GetEntityFlags(iClient); + iFlags &= ~FL_ONTRAIN; + iFlags &= ~FL_FROZEN; + iFlags &= ~FL_ATCONTROLS; + SetEntityFlags(iClient, iFlags); +} + +public OnClientDisconnect(iClient) +{ + RemoveFromGameUI(iClient); +} + +public GameUI_PlayerOn(const String:szOutput[], iCaller, iActivator, Float:fDelay) +{ + if(!(1 <= iActivator <= MaxClients)) + return; + + g_iAttachedGameUI[iActivator] = EntIndexToEntRef(iCaller); +} + +public GameUI_PlayerOff(const String:szOutput[], iCaller, iActivator, Float:fDelay) +{ + if(!(1 <= iActivator <= MaxClients)) + return; + + g_iAttachedGameUI[iActivator] = 0; +} + +RemoveFromGameUI(iClient) +{ + if(!g_iAttachedGameUI[iClient]) + return; + + new iEnt = EntRefToEntIndex(g_iAttachedGameUI[iClient]); + if(iEnt == INVALID_ENT_REFERENCE) + return; + + AcceptEntityInput(iEnt, "Deactivate", iClient, iEnt); +} + +public OnEntityCreated(entity, const String:classname[]) +{ + if (StrEqual(classname, "game_ui")) + { + DHookEntity(g_hAcceptInput, false, entity); + } +} + +public MRESReturn:Hook_AcceptInput(thisptr, Handle:hReturn, Handle:hParams) +{ + new String:sCommand[128]; + DHookGetParamString(hParams, 1, sCommand, sizeof(sCommand)); + + if (StrEqual(sCommand, "Deactivate")) + { + new pPlayer = GetEntPropEnt(thisptr, Prop_Data, "m_player"); + + if (pPlayer == -1) + { + // Manually disable think. + SetEntProp(thisptr, Prop_Data, "m_nNextThinkTick", -1); + + DHookSetReturn(hReturn, false); + return MRES_Supercede; + } + } + + DHookSetReturn(hReturn, true); + return MRES_Ignored; +} diff --git a/FixGameUI/scripting/includes/dhooks.inc b/FixGameUI/scripting/includes/dhooks.inc new file mode 120000 index 00000000..8b56a6fb --- /dev/null +++ b/FixGameUI/scripting/includes/dhooks.inc @@ -0,0 +1 @@ +../../../includes/dhooks.inc \ No newline at end of file diff --git a/Flashlight/scripting/Flashlight.sp b/Flashlight/scripting/Flashlight.sp new file mode 100644 index 00000000..98a47f97 --- /dev/null +++ b/Flashlight/scripting/Flashlight.sp @@ -0,0 +1,44 @@ +#pragma semicolon 1 + +#include +#include + +#pragma newdecls required +#define PLUGIN_VERSION "1.0" +public Plugin myinfo = +{ + name = "Flashlight", + author = "BotoX", + description = "Dead flashlight, block sound from other clients.", + version = PLUGIN_VERSION, + url = "" +}; + +public void OnPluginStart() +{ + AddNormalSoundHook(OnSound); +} + +public Action OnSound(int clients[64], int &numClients, char sample[PLATFORM_MAX_PATH], int &entity, int &channel, float &volume, int &level, int &pitch, int &flags) +{ + if(entity >= 1 && entity <= MAXPLAYERS && StrEqual(sample, "items/flashlight1.wav", false)) + { + numClients = 1; + clients[0] = entity; + return Plugin_Changed; + } + + return Plugin_Continue; +} + +public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3], float angles[3], int &weapon, int &subtype, int &cmdnum, int &tickcount, int &seed, int mouse[2]) +{ + // Dead flashlight + if(impulse == 100 && !IsPlayerAlive(client)) + { + SetEntProp(client, Prop_Send, "m_fEffects", GetEntProp(client, Prop_Send, "m_fEffects") ^ 4); + ClientCommand(client, "playgamesound \"items/flashlight1.wav\""); + } + + return Plugin_Continue; +} diff --git a/ForceInputs/scripting/ForceInputs.sp b/ForceInputs/scripting/ForceInputs.sp new file mode 100644 index 00000000..416d12de --- /dev/null +++ b/ForceInputs/scripting/ForceInputs.sp @@ -0,0 +1,112 @@ +//==================================================================================================== +// +// Name: ForceInput +// Author: zaCade +// Description: Allows admins to force inputs on entities. (ent_fire) +// +//==================================================================================================== +#include +#include + +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public Plugin:myinfo = +{ + name = "ForceInput", + author = "zaCade", + description = "Allows admins to force inputs on entities. (ent_fire)", + version = "1.2", + url = "" +}; + +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public OnPluginStart() +{ + RegAdminCmd("sm_forceinput", Command_ForceInput, ADMFLAG_ROOT); +} + +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public Action:Command_ForceInput(client, args) +{ + if (GetCmdArgs() < 2) + { + ReplyToCommand(client, "[SM] Usage: sm_forceinput [parameter]"); + return Plugin_Handled; + } + + new String:sArguments[3][256]; + GetCmdArg(1, sArguments[0], sizeof(sArguments[])); + GetCmdArg(2, sArguments[1], sizeof(sArguments[])); + GetCmdArg(3, sArguments[2], sizeof(sArguments[])); + + if (StrEqual(sArguments[0], "!self")) + { + if (strlen(sArguments[2])) + SetVariantString(sArguments[2]); + + AcceptEntityInput(client, sArguments[1], client, client); + ReplyToCommand(client, "[SM] Input succesfull."); + } + else if (StrEqual(sArguments[0], "!target")) + { + new entity = INVALID_ENT_REFERENCE; + + new Float:fPosition[3], Float:fAngles[3]; + GetClientEyePosition(client, fPosition); + GetClientEyeAngles(client, fAngles); + + new Handle:hTrace = TR_TraceRayFilterEx(fPosition, fAngles, MASK_SOLID, RayType_Infinite, TraceRayFilter, client); + + if (TR_DidHit(hTrace) && ((entity = TR_GetEntityIndex(hTrace)) >= 1)) + { + if (IsValidEntity(entity) || IsValidEdict(entity)) + { + if (strlen(sArguments[2])) + SetVariantString(sArguments[2]); + + AcceptEntityInput(entity, sArguments[1], client, client); + ReplyToCommand(client, "[SM] Input succesfull."); + } + } + } + else + { + new entity = INVALID_ENT_REFERENCE; + + while ((entity = FindEntityByClassname(entity, "*")) != INVALID_ENT_REFERENCE) + { + if (IsValidEntity(entity) || IsValidEdict(entity)) + { + new String:sClassname[64], String:sTargetname[64]; + GetEntPropString(entity, Prop_Data, "m_iClassname", sClassname, sizeof(sClassname)); + GetEntPropString(entity, Prop_Data, "m_iName", sTargetname, sizeof(sTargetname)); + + if (StrEqual(sClassname, sArguments[0], false) || StrEqual(sTargetname, sArguments[0], false)) + { + if (strlen(sArguments[2])) + SetVariantString(sArguments[2]); + + AcceptEntityInput(entity, sArguments[1], client, client); + ReplyToCommand(client, "[SM] Input succesfull."); + } + } + } + } + return Plugin_Handled; +} + +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public bool:TraceRayFilter(entity, mask, any:client) +{ + if (entity == client) + return false; + + return true; +} diff --git a/KevlarEquip/scripting/KevlarEquip.sp b/KevlarEquip/scripting/KevlarEquip.sp new file mode 100644 index 00000000..9d11f7bc --- /dev/null +++ b/KevlarEquip/scripting/KevlarEquip.sp @@ -0,0 +1,44 @@ +#pragma semicolon 1 + +#include +#include +#include + +public Plugin:myinfo = +{ + name = "KevlarEquip", + author = "BotoX", + description = "Equip players with kevlar when they spawn, unglitch kevlar and strip it when you get infected", + version = "2.0", + url = "" +}; + +public OnClientPutInServer(client) +{ + SDKHook(client, SDKHook_SpawnPost, Hook_OnPlayerSpawn); +} + +public Hook_OnPlayerSpawn(client) +{ + if(IsPlayerAlive(client) && ZR_IsClientHuman(client)) + { + SetEntProp(client, Prop_Send, "m_ArmorValue", 100, 1); + SetEntProp(client, Prop_Send, "m_bHasHelmet", 1); + // Reset last hitgroup to generic - fixes kevlar bug + // Example: You get hit in the head by a bullet as a zombie + // the round ends, you spawn as a human. + // You get damaged by a trigger, the game still thinks you + // are getting damaged in the head hitgroup, >mfw source engine. + // Thanks to leaked 2007 Source Engine Code. + SetEntData(client, 4444, 0, 4); + } +} + +public ZR_OnClientInfected(client, attacker, bool:motherInfect, bool:respawnOverride, bool:respawn) +{ + if(IsPlayerAlive(client)) + { + SetEntProp(client, Prop_Send, "m_ArmorValue", 0, 1); + SetEntProp(client, Prop_Send, "m_bHasHelmet", 0); + } +} diff --git a/NapalmLagFix/gamedata/napalmlagfix.games.txt b/NapalmLagFix/gamedata/napalmlagfix.games.txt new file mode 100644 index 00000000..9c87cf6d --- /dev/null +++ b/NapalmLagFix/gamedata/napalmlagfix.games.txt @@ -0,0 +1,28 @@ +"Games" +{ + "cstrike" + { + "Offsets" + { + "RadiusDamage" + { + "windows" "68" + "linux" "69" + "mac" "69" + } + } + } + + "csgo" + { + "Offsets" + { + "RadiusDamage" + { + "windows" "68" + "linux" "69" + "mac" "69" + } + } + } +} diff --git a/NapalmLagFix/scripting/NapalmLagFix.sp b/NapalmLagFix/scripting/NapalmLagFix.sp new file mode 100644 index 00000000..8e22a9d9 --- /dev/null +++ b/NapalmLagFix/scripting/NapalmLagFix.sp @@ -0,0 +1,118 @@ +#pragma semicolon 1 + +#include +#include +#undef REQUIRE_PLUGIN +#include + +#define PLUGIN_NAME "Napalm Lag Fix" +#define PLUGIN_VERSION "1.0.3" + +#define UPDATE_URL "http://godtony.mooo.com/napalmlagfix/napalmlagfix.txt" + +#define DMG_BURN (1 << 3) + +new Handle:g_hRadiusDamage = INVALID_HANDLE; +new bool:g_bCheckNullPtr = false; + +public Plugin:myinfo = +{ + name = PLUGIN_NAME, + author = "GoD-Tony + BotoX", + description = "Prevents lag when napalm is used on players", + version = PLUGIN_VERSION, + url = "https://forums.alliedmods.net/showthread.php?t=188093" // Demo: http://youtu.be/YdhAu5IEVVM +}; + +public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max) +{ + MarkNativeAsOptional("DHookIsNullParam"); + + return APLRes_Success; +} + +public OnPluginStart() +{ + // Convars. + new Handle:hCvar = CreateConVar("sm_napalmlagfix_version", PLUGIN_VERSION, PLUGIN_NAME, FCVAR_PLUGIN|FCVAR_NOTIFY|FCVAR_DONTRECORD); + SetConVarString(hCvar, PLUGIN_VERSION); + + // Gamedata. + new Handle:hConfig = LoadGameConfigFile("napalmlagfix.games"); + + if (hConfig == INVALID_HANDLE) + { + SetFailState("Could not find gamedata file: napalmlagfix.games.txt"); + } + + new offset = GameConfGetOffset(hConfig, "RadiusDamage"); + + if (offset == -1) + { + SetFailState("Failed to find RadiusDamage offset"); + } + + CloseHandle(hConfig); + + // DHooks. + g_bCheckNullPtr = (GetFeatureStatus(FeatureType_Native, "DHookIsNullParam") == FeatureStatus_Available); + + g_hRadiusDamage = DHookCreate(offset, HookType_GameRules, ReturnType_Void, ThisPointer_Ignore, Hook_RadiusDamage); + DHookAddParam(g_hRadiusDamage, HookParamType_ObjectPtr); // 1 - CTakeDamageInfo &info + DHookAddParam(g_hRadiusDamage, HookParamType_VectorPtr); // 2 - Vector &vecSrc + DHookAddParam(g_hRadiusDamage, HookParamType_Float); // 3 - float flRadius + DHookAddParam(g_hRadiusDamage, HookParamType_Int); // 4 - int iClassIgnore + DHookAddParam(g_hRadiusDamage, HookParamType_CBaseEntity); // 5 - CBaseEntity *pEntityIgnore + + // Updater. + if (LibraryExists("updater")) + { + Updater_AddPlugin(UPDATE_URL); + } +} + +public OnLibraryAdded(const String:name[]) +{ + if (StrEqual(name, "updater")) + { + Updater_AddPlugin(UPDATE_URL); + } +} + +public Updater_OnPluginUpdated() +{ + // There could be new gamedata in this update. + ReloadPlugin(); +} + +public OnMapStart() +{ + DHookGamerules(g_hRadiusDamage, false); +} + +public MRESReturn:Hook_RadiusDamage(Handle:hParams) +{ + // As of DHooks 1.0.12 we must check for a null param. + if (g_bCheckNullPtr && DHookIsNullParam(hParams, 5)) + return MRES_Ignored; + + new iDmgBits = DHookGetParamObjectPtrVar(hParams, 1, 60, ObjectValueType_Int); + new iEntIgnore = DHookGetParam(hParams, 5); + + if(!(iDmgBits & DMG_BURN)) + return MRES_Ignored; + + // Block napalm damage if it's coming from another client. + if (1 <= iEntIgnore <= MaxClients) + return MRES_Supercede; + + // Block napalm that comes from grenades + new String:sEntClassName[64]; + if(GetEntityClassname(iEntIgnore, sEntClassName, sizeof(sEntClassName))) + { + if(!strcmp(sEntClassName, "hegrenade_projectile")) + return MRES_Supercede; + } + + return MRES_Ignored; +} diff --git a/NapalmLagFix/scripting/include/dhooks.inc b/NapalmLagFix/scripting/include/dhooks.inc new file mode 120000 index 00000000..8b56a6fb --- /dev/null +++ b/NapalmLagFix/scripting/include/dhooks.inc @@ -0,0 +1 @@ +../../../includes/dhooks.inc \ No newline at end of file diff --git a/NoGrenadeRinging/gamedata/NoGrenadeRinging.games.txt b/NoGrenadeRinging/gamedata/NoGrenadeRinging.games.txt new file mode 100644 index 00000000..3d24d747 --- /dev/null +++ b/NoGrenadeRinging/gamedata/NoGrenadeRinging.games.txt @@ -0,0 +1,15 @@ +"Games" +{ + "cstrike" + { + "Offsets" + { + "OnDamagedByExplosion" + { + "windows" "335" + "linux" "336" + "mac" "336" + } + } + } +} \ No newline at end of file diff --git a/NoGrenadeRinging/scripting/NoGrenadeRinging.sp b/NoGrenadeRinging/scripting/NoGrenadeRinging.sp new file mode 100644 index 00000000..138b7f54 --- /dev/null +++ b/NoGrenadeRinging/scripting/NoGrenadeRinging.sp @@ -0,0 +1,42 @@ +#pragma semicolon 1 +#include +#include +#include + +//int CCSPlayer::OnDamagedByExplosion(CTakeDamageInfo const&) +Handle g_hDamagedByExplosion; + +public Plugin myinfo = +{ + name = "NoGrenadeRinging", + author = "BotoX", + description = "Block the annoying ringing noise when a grenade explodes next to you", + version = "1.0", + url = "" +}; + +public void OnPluginStart() +{ + Handle hTemp = LoadGameConfigFile("NoGrenadeRinging.games"); + if(hTemp == INVALID_HANDLE) + SetFailState("Why you no has gamedata?"); + + int Offset = GameConfGetOffset(hTemp, "OnDamagedByExplosion"); + g_hDamagedByExplosion = DHookCreate(Offset, HookType_Entity, ReturnType_Int, ThisPointer_CBaseEntity, OnDamagedByExplosion); + DHookAddParam(g_hDamagedByExplosion, HookParamType_ObjectPtr); + + CloseHandle(hTemp); +} + +public void OnClientPutInServer(int client) +{ + //Dont add removal callback for this one + DHookEntity(g_hDamagedByExplosion, false, client); +} + +//int CCSPlayer::OnDamagedByExplosion(CTakeDamageInfo const&) +public MRESReturn:OnDamagedByExplosion(int pThis, Handle hReturn, Handle hParams) +{ + // Block call + return MRES_Supercede; +} diff --git a/NoGrenadeRinging/scripting/include/dhooks.inc b/NoGrenadeRinging/scripting/include/dhooks.inc new file mode 120000 index 00000000..8b56a6fb --- /dev/null +++ b/NoGrenadeRinging/scripting/include/dhooks.inc @@ -0,0 +1 @@ +../../../includes/dhooks.inc \ No newline at end of file diff --git a/NoShake/scripting/NoShake.sp b/NoShake/scripting/NoShake.sp new file mode 100644 index 00000000..78b079bd --- /dev/null +++ b/NoShake/scripting/NoShake.sp @@ -0,0 +1,91 @@ +#pragma semicolon 1 + +#include +#include +#include + +#pragma newdecls required + +Handle g_hNoShakeCookie; +ConVar g_Cvar_NoShakeGlobal; + +bool g_bNoShake[MAXPLAYERS] = {false, ...}; +bool g_bNoShakeGlobal = false; + +public Plugin myinfo = +{ + name = "NoShake", + author = "BotoX", + description = "Disable env_shake", + version = "1.0", + url = "" +}; + +public void OnPluginStart() +{ + RegConsoleCmd("sm_shake", Command_Shake, "[NoShake] Disables or enables screen shakes."); + RegConsoleCmd("sm_noshake", Command_Shake, "[NoShake] Disables or enables screen shakes."); + + g_hNoShakeCookie = RegClientCookie("noshake_cookie", "NoShake", CookieAccess_Protected); + + g_Cvar_NoShakeGlobal = CreateConVar("sm_noshake_global", "0", "Disable screenshake globally.", 0, true, 0.0, true, 1.0); + g_bNoShakeGlobal = g_Cvar_NoShakeGlobal.BoolValue; + g_Cvar_NoShakeGlobal.AddChangeHook(OnConVarChanged); + + HookUserMessage(GetUserMessageId("Shake"), MsgHook, true); +} + +public void OnClientCookiesCached(int client) +{ + static char sCookieValue[2]; + GetClientCookie(client, g_hNoShakeCookie, sCookieValue, sizeof(sCookieValue)); + g_bNoShake[client] = StringToInt(sCookieValue) != 0; +} + +public void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] newValue) +{ + if(StringToInt(newValue) > StringToInt(oldValue)) + PrintToChatAll("\x03[NoShake]\x01 Enabled NoShake globally!"); + else if(StringToInt(newValue) < StringToInt(oldValue)) + PrintToChatAll("\x03[NoShake]\x01 Disabled NoShake globally!"); + + g_bNoShakeGlobal = StringToInt(newValue) != 0; +} + +public Action MsgHook(UserMsg msg_id, BfRead msg, const int[] players, int playersNum, bool reliable, bool init) +{ + if(playersNum == 1 && (g_bNoShakeGlobal || g_bNoShake[players[0]])) + return Plugin_Handled; + else + return Plugin_Continue; +} + +public Action Command_Shake(int client, int args) +{ + if(g_bNoShakeGlobal) + return Plugin_Handled; + + if(!AreClientCookiesCached(client)) + { + ReplyToCommand(client, "\x03[NoShake]\x01 Please wait. Your settings are still loading."); + return Plugin_Handled; + } + + if(g_bNoShake[client]) + { + g_bNoShake[client] = false; + ReplyToCommand(client, "\x03[NoShake]\x01 has been disabled!"); + } + else + { + g_bNoShake[client] = true; + ReplyToCommand(client, "\x03[NoShake]\x01 has been enabled!"); + } + + static char sCookieValue[2]; + IntToString(g_bNoShake[client], sCookieValue, sizeof(sCookieValue)); + SetClientCookie(client, g_hNoShakeCookie, sCookieValue); + + return Plugin_Handled; +} + diff --git a/SaveLevel/configs/savelevel/ze_FFVII_Mako_Reactor_v1_4_1.cfg b/SaveLevel/configs/savelevel/ze_FFVII_Mako_Reactor_v1_4_1.cfg new file mode 100644 index 00000000..3b080912 --- /dev/null +++ b/SaveLevel/configs/savelevel/ze_FFVII_Mako_Reactor_v1_4_1.cfg @@ -0,0 +1,49 @@ +"levels" +{ + "0" + { + "name" "Level 0" + "restore" + { + "m_iName" "" + } + } + "1" + { + "name" "Level 1" + "restore" + { + "m_iName" "" + } + } + "2" + { + "name" "Level 2" + "match" + { + "props" + { + "m_iName" "a" + } + } + "restore" + { + "m_iName" "a" + } + } + "3" + { + "name" "Level 3" + "match" + { + "props" + { + "m_iName" "b" + } + } + "restore" + { + "m_iName" "b" + } + } +} diff --git a/SaveLevel/configs/savelevel/ze_FFVII_Mako_Reactor_v3_1.cfg b/SaveLevel/configs/savelevel/ze_FFVII_Mako_Reactor_v3_1.cfg new file mode 100644 index 00000000..3b080912 --- /dev/null +++ b/SaveLevel/configs/savelevel/ze_FFVII_Mako_Reactor_v3_1.cfg @@ -0,0 +1,49 @@ +"levels" +{ + "0" + { + "name" "Level 0" + "restore" + { + "m_iName" "" + } + } + "1" + { + "name" "Level 1" + "restore" + { + "m_iName" "" + } + } + "2" + { + "name" "Level 2" + "match" + { + "props" + { + "m_iName" "a" + } + } + "restore" + { + "m_iName" "a" + } + } + "3" + { + "name" "Level 3" + "match" + { + "props" + { + "m_iName" "b" + } + } + "restore" + { + "m_iName" "b" + } + } +} diff --git a/SaveLevel/configs/savelevel/ze_FFVII_Mako_Reactor_v6.cfg b/SaveLevel/configs/savelevel/ze_FFVII_Mako_Reactor_v6.cfg new file mode 100644 index 00000000..34935469 --- /dev/null +++ b/SaveLevel/configs/savelevel/ze_FFVII_Mako_Reactor_v6.cfg @@ -0,0 +1,77 @@ +"levels" +{ + "1" + { + "name" "Level 1" + "match" + { + "outputs" + { + "m_OnUser1" "leveling_counter,Add,1" + "ExactMatches" "1" + } + } + "restore" + { + "AddOutput" "OnUser1 leveling_counter,Add,1,0,-1" + "m_iFrags" "100" + } + } + "2" + { + "name" "Level 2" + "match" + { + "outputs" + { + "m_OnUser1" "leveling_counter,Add,1" + "ExactMatches" "2" + } + } + "restore" + { + "AddOutput" "OnUser1 leveling_counter,Add,1,0,-1" + "AddOutput" "OnUser1 leveling_counter,Add,1,0,-1" + "m_iFrags" "200" + } + } + "3" + { + "name" "Level 3" + "match" + { + "outputs" + { + "m_OnUser1" "leveling_counter,Add,1" + "ExactMatches" "3" + } + } + "restore" + { + "AddOutput" "OnUser1 leveling_counter,Add,1,0,-1" + "AddOutput" "OnUser1 leveling_counter,Add,1,0,-1" + "AddOutput" "OnUser1 leveling_counter,Add,1,0,-1" + "m_iFrags" "300" + } + } + "4" + { + "name" "Level 4" + "match" + { + "outputs" + { + "m_OnUser1" "leveling_counter,Add,1" + "MinMatches" "4" + } + } + "restore" + { + "AddOutput" "OnUser1 leveling_counter,Add,1,0,-1" + "AddOutput" "OnUser1 leveling_counter,Add,1,0,-1" + "AddOutput" "OnUser1 leveling_counter,Add,1,0,-1" + "AddOutput" "OnUser1 leveling_counter,Add,1,0,-1" + "m_iFrags" "400" + } + } +} diff --git a/SaveLevel/configs/savelevel/ze_FFXII_Feywood_b3_1.cfg b/SaveLevel/configs/savelevel/ze_FFXII_Feywood_b3_1.cfg new file mode 100644 index 00000000..0ba0fbed --- /dev/null +++ b/SaveLevel/configs/savelevel/ze_FFXII_Feywood_b3_1.cfg @@ -0,0 +1,57 @@ +"levels" +{ + "1" + { + "name" "Level 1" + "match" + { + "outputs" + { + "m_OnUser4" "Map_Level_Check,Add,1" + "ExactMatches" "1" + } + } + "restore" + { + "AddOutput" "OnUser4 Map_Level_Check,Add,1,0,-1" + "m_iFrags" "100" + } + } + "2" + { + "name" "Level 2" + "match" + { + "outputs" + { + "m_OnUser4" "Map_Level_Check,Add,1" + "ExactMatches" "2" + } + } + "restore" + { + "AddOutput" "OnUser4 Map_Level_Check,Add,1,0,-1" + "AddOutput" "OnUser4 Map_Level_Check,Add,1,0,-1" + "m_iFrags" "200" + } + } + "3" + { + "name" "Level 3" + "match" + { + "outputs" + { + "m_OnUser4" "Map_Level_Check,Add,1" + "MinMatches" "3" + } + } + "restore" + { + "AddOutput" "OnUser4 Map_Level_Check,Add,1,0,-1" + "AddOutput" "OnUser4 Map_Level_Check,Add,1,0,-1" + "AddOutput" "OnUser4 Map_Level_Check,Add,1,0,-1" + "m_iFrags" "300" + } + } +} diff --git a/SaveLevel/configs/savelevel/ze_FFXII_Paramina_Rift_v1_3.cfg b/SaveLevel/configs/savelevel/ze_FFXII_Paramina_Rift_v1_3.cfg new file mode 100644 index 00000000..c69054ce --- /dev/null +++ b/SaveLevel/configs/savelevel/ze_FFXII_Paramina_Rift_v1_3.cfg @@ -0,0 +1,98 @@ +"levels" +{ + "1" + { + "name" "Level 1" + "match" + { + "outputs" + { + "m_OnUser2" "leveling_counter,add,1" + "ExactMatches" "1" + } + } + "restore" + { + "AddOutput" "OnUser2 leveling_counter,add,1,0,-1" + "m_iFrags" "100" + } + } + "2" + { + "name" "Level 2" + "match" + { + "outputs" + { + "m_OnUser2" "leveling_counter,add,1" + "ExactMatches" "2" + } + } + "restore" + { + "AddOutput" "OnUser2 leveling_counter,add,1,0,-1" + "AddOutput" "OnUser2 leveling_counter,add,1,0,-1" + "m_iFrags" "200" + } + } + "3" + { + "name" "Level 3" + "match" + { + "outputs" + { + "m_OnUser2" "leveling_counter,add,1" + "ExactMatches" "3" + } + } + "restore" + { + "AddOutput" "OnUser2 leveling_counter,add,1,0,-1" + "AddOutput" "OnUser2 leveling_counter,add,1,0,-1" + "AddOutput" "OnUser2 leveling_counter,add,1,0,-1" + "m_iFrags" "300" + } + } + "4" + { + "name" "Level 4" + "match" + { + "outputs" + { + "m_OnUser2" "leveling_counter,add,1" + "ExactMatches" "4" + } + } + "restore" + { + "AddOutput" "OnUser2 leveling_counter,add,1,0,-1" + "AddOutput" "OnUser2 leveling_counter,add,1,0,-1" + "AddOutput" "OnUser2 leveling_counter,add,1,0,-1" + "AddOutput" "OnUser2 leveling_counter,add,1,0,-1" + "m_iFrags" "400" + } + } + "5" + { + "name" "Level 5" + "match" + { + "outputs" + { + "m_OnUser2" "leveling_counter,add,1" + "MinMatches" "5" + } + } + "restore" + { + "AddOutput" "OnUser2 leveling_counter,add,1,0,-1" + "AddOutput" "OnUser2 leveling_counter,add,1,0,-1" + "AddOutput" "OnUser2 leveling_counter,add,1,0,-1" + "AddOutput" "OnUser2 leveling_counter,add,1,0,-1" + "AddOutput" "OnUser2 leveling_counter,add,1,0,-1" + "m_iFrags" "500" + } + } +} diff --git a/SaveLevel/configs/savelevel/ze_TESV_Skyrim_v3.cfg b/SaveLevel/configs/savelevel/ze_TESV_Skyrim_v3.cfg new file mode 100644 index 00000000..94c84331 --- /dev/null +++ b/SaveLevel/configs/savelevel/ze_TESV_Skyrim_v3.cfg @@ -0,0 +1,76 @@ +"levels" +{ + "0" + { + "name" "Level 0" + "restore" + { + "m_iName" "" + "m_iFrags" "0" + } + } + "1" + { + "name" "Level 1" + "match" + { + "props" + { + "m_iName" "1" + } + } + "restore" + { + "m_iName" "1" + "m_iFrags" "100" + } + } + "2" + { + "name" "Level 2" + "match" + { + "props" + { + "m_iName" "2" + } + } + "restore" + { + "m_iName" "2" + "m_iFrags" "200" + } + } + "3" + { + "name" "Level 3" + "match" + { + "props" + { + "m_iName" "3" + } + } + "restore" + { + "m_iName" "3" + "m_iFrags" "300" + } + } + "4" + { + "name" "Level 4" + "match" + { + "props" + { + "m_iName" "4" + } + } + "restore" + { + "m_iName" "4" + "m_iFrags" "400" + } + } +} diff --git a/SaveLevel/configs/savelevel/ze_harry_potter_v1_3.cfg b/SaveLevel/configs/savelevel/ze_harry_potter_v1_3.cfg new file mode 100644 index 00000000..ce583713 --- /dev/null +++ b/SaveLevel/configs/savelevel/ze_harry_potter_v1_3.cfg @@ -0,0 +1,77 @@ +"levels" +{ + "1" + { + "name" "Level 1" + "match" + { + "outputs" + { + "m_OnUser3" "map_wandlevels,Add,1" + "ExactMatches" "1" + } + } + "restore" + { + "AddOutput" "OnUser3 map_wandlevels,Add,1,0,-1" + "m_iFrags" "100" + } + } + "2" + { + "name" "Level 2" + "match" + { + "outputs" + { + "m_OnUser3" "map_wandlevels,Add,1" + "ExactMatches" "2" + } + } + "restore" + { + "AddOutput" "OnUser3 map_wandlevels,Add,1,0,-1" + "AddOutput" "OnUser3 map_wandlevels,Add,1,0,-1" + "m_iFrags" "200" + } + } + "3" + { + "name" "Level 3" + "match" + { + "outputs" + { + "m_OnUser3" "map_wandlevels,Add,1" + "ExactMatches" "3" + } + } + "restore" + { + "AddOutput" "OnUser3 map_wandlevels,Add,1,0,-1" + "AddOutput" "OnUser3 map_wandlevels,Add,1,0,-1" + "AddOutput" "OnUser3 map_wandlevels,Add,1,0,-1" + "m_iFrags" "300" + } + } + "4" + { + "name" "Level 4" + "match" + { + "outputs" + { + "m_OnUser3" "map_wandlevels,Add,1" + "MinMatches" "4" + } + } + "restore" + { + "AddOutput" "OnUser3 map_wandlevels,Add,1,0,-1" + "AddOutput" "OnUser3 map_wandlevels,Add,1,0,-1" + "AddOutput" "OnUser3 map_wandlevels,Add,1,0,-1" + "AddOutput" "OnUser3 map_wandlevels,Add,1,0,-1" + "m_iFrags" "400" + } + } +} diff --git a/SaveLevel/configs/savelevel/ze_lotr_minas_tirith_v2_2fix.cfg b/SaveLevel/configs/savelevel/ze_lotr_minas_tirith_v2_2fix.cfg new file mode 100644 index 00000000..afc586a6 --- /dev/null +++ b/SaveLevel/configs/savelevel/ze_lotr_minas_tirith_v2_2fix.cfg @@ -0,0 +1,92 @@ +"levels" +{ + "0" + { + "name" "Level 0" + "restore" + { + "m_iName" "" + "m_iFrags" "0" + } + } + "1" + { + "name" "Level 1" + "match" + { + "props" + { + "m_iName" "1" + } + } + "restore" + { + "m_iName" "1" + "m_iFrags" "100" + } + } + "2" + { + "name" "Level 2" + "match" + { + "props" + { + "m_iName" "2" + } + } + "restore" + { + "m_iName" "2" + "m_iFrags" "200" + } + } + "3" + { + "name" "Level 3" + "match" + { + "props" + { + "m_iName" "3" + } + } + "restore" + { + "m_iName" "3" + "m_iFrags" "300" + } + } + "4" + { + "name" "Level 4" + "match" + { + "props" + { + "m_iName" "4" + } + } + "restore" + { + "m_iName" "4" + "m_iFrags" "400" + } + } + "5" + { + "name" "Level 5" + "match" + { + "props" + { + "m_iName" "5" + } + } + "restore" + { + "m_iName" "5" + "m_iFrags" "500" + } + } +} diff --git a/SaveLevel/configs/savelevel/ze_lotr_minas_tirith_v3_5.cfg b/SaveLevel/configs/savelevel/ze_lotr_minas_tirith_v3_5.cfg new file mode 100644 index 00000000..afc586a6 --- /dev/null +++ b/SaveLevel/configs/savelevel/ze_lotr_minas_tirith_v3_5.cfg @@ -0,0 +1,92 @@ +"levels" +{ + "0" + { + "name" "Level 0" + "restore" + { + "m_iName" "" + "m_iFrags" "0" + } + } + "1" + { + "name" "Level 1" + "match" + { + "props" + { + "m_iName" "1" + } + } + "restore" + { + "m_iName" "1" + "m_iFrags" "100" + } + } + "2" + { + "name" "Level 2" + "match" + { + "props" + { + "m_iName" "2" + } + } + "restore" + { + "m_iName" "2" + "m_iFrags" "200" + } + } + "3" + { + "name" "Level 3" + "match" + { + "props" + { + "m_iName" "3" + } + } + "restore" + { + "m_iName" "3" + "m_iFrags" "300" + } + } + "4" + { + "name" "Level 4" + "match" + { + "props" + { + "m_iName" "4" + } + } + "restore" + { + "m_iName" "4" + "m_iFrags" "400" + } + } + "5" + { + "name" "Level 5" + "match" + { + "props" + { + "m_iName" "5" + } + } + "restore" + { + "m_iName" "5" + "m_iFrags" "500" + } + } +} diff --git a/SaveLevel/configs/savelevel/ze_tesv_skyrim_v4fix.cfg b/SaveLevel/configs/savelevel/ze_tesv_skyrim_v4fix.cfg new file mode 100644 index 00000000..94c84331 --- /dev/null +++ b/SaveLevel/configs/savelevel/ze_tesv_skyrim_v4fix.cfg @@ -0,0 +1,76 @@ +"levels" +{ + "0" + { + "name" "Level 0" + "restore" + { + "m_iName" "" + "m_iFrags" "0" + } + } + "1" + { + "name" "Level 1" + "match" + { + "props" + { + "m_iName" "1" + } + } + "restore" + { + "m_iName" "1" + "m_iFrags" "100" + } + } + "2" + { + "name" "Level 2" + "match" + { + "props" + { + "m_iName" "2" + } + } + "restore" + { + "m_iName" "2" + "m_iFrags" "200" + } + } + "3" + { + "name" "Level 3" + "match" + { + "props" + { + "m_iName" "3" + } + } + "restore" + { + "m_iName" "3" + "m_iFrags" "300" + } + } + "4" + { + "name" "Level 4" + "match" + { + "props" + { + "m_iName" "4" + } + } + "restore" + { + "m_iName" "4" + "m_iFrags" "400" + } + } +} diff --git a/SaveLevel/scripting/SaveLevel.sp b/SaveLevel/scripting/SaveLevel.sp new file mode 100644 index 00000000..77d79da5 --- /dev/null +++ b/SaveLevel/scripting/SaveLevel.sp @@ -0,0 +1,299 @@ +#pragma semicolon 1 + +#include +#include +#include + +#pragma newdecls required + +StringMap g_PlayerLevels; +KeyValues g_Config; +KeyValues g_PropAltNames; + +#define PLUGIN_VERSION "1.0" +public Plugin myinfo = +{ + name = "SaveLevel", + author = "BotoX", + description = "Saves players level on maps when they disconnect and restore them on connect.", + version = PLUGIN_VERSION, + url = "" +}; + +public void OnPluginStart() +{ + g_PropAltNames = new KeyValues("PropAltNames"); + g_PropAltNames.SetString("m_iName", "targetname"); +} + +public void OnPluginEnd() +{ + if(g_Config) + delete g_Config; + if(g_PlayerLevels) + delete g_PlayerLevels; + delete g_PropAltNames; +} + +public void OnMapStart() +{ + if(g_Config) + delete g_Config; + if(g_PlayerLevels) + delete g_PlayerLevels; + + char sMapName[PLATFORM_MAX_PATH]; + GetCurrentMap(sMapName, sizeof(sMapName)); + + char sConfigFile[PLATFORM_MAX_PATH]; + BuildPath(Path_SM, sConfigFile, sizeof(sConfigFile), "configs/savelevel/%s.cfg", sMapName); + if(!FileExists(sConfigFile)) + { + LogMessage("Could not find mapconfig: \"%s\"", sConfigFile); + return; + } + LogMessage("Found mapconfig: \"%s\"", sConfigFile); + + g_Config = new KeyValues("levels"); + if(!g_Config.ImportFromFile(sConfigFile)) + { + delete g_Config; + LogMessage("ImportFromFile() failed!"); + return; + } + g_Config.Rewind(); + + if(!g_Config.GotoFirstSubKey()) + { + delete g_Config; + LogMessage("GotoFirstSubKey() failed!"); + return; + } + + g_PlayerLevels = new StringMap(); +} + +public void OnClientPostAdminCheck(int client) +{ + if(!g_PlayerLevels) + return; + + char sSteamID[32]; + GetClientAuthId(client, AuthId_Steam3, sSteamID, sizeof(sSteamID)); + + static char sTargets[128]; + if(g_PlayerLevels.GetString(sSteamID, sTargets, sizeof(sTargets))) + { + g_PlayerLevels.Remove(sSteamID); + char sNames[128]; + static char asTargets[4][32]; + int Split = ExplodeString(sTargets, ";", asTargets, sizeof(asTargets), sizeof(asTargets[])); + + g_Config.Rewind(); + for(int i = 0; i < Split; i++) + { + if(!g_Config.JumpToKey(asTargets[i])) + continue; + + static char sKey[32]; + static char sValue[1024]; + if(g_Config.JumpToKey("restore")) + { + if(g_Config.GotoFirstSubKey(false)) + { + do + { + g_Config.GetSectionName(sKey, sizeof(sKey)); + g_Config.GetString(NULL_STRING, sValue, sizeof(sValue)); + if(StrEqual(sKey, "AddOutput", false)) + { + SetVariantString(sValue); + AcceptEntityInput(client, sKey, client, client); + } + else + { + PropFieldType Type; + int NumBits; + int Offset = FindDataMapInfo(client, sKey, Type, NumBits); + if(Offset != -1) + { + if(Type == PropField_Integer) + { + int Value = StringToInt(sValue); + SetEntData(client, Offset, Value, NumBits / 8, false); + } + else if(Type == PropField_Float) + { + float Value = StringToFloat(sValue); + SetEntDataFloat(client, Offset, Value, false); + } + else if(Type == PropField_String) + { + SetEntDataString(client, Offset, sValue, strlen(sValue) + 1, false); + } + else if(Type == PropField_String_T) + { + static char sAltKey[32]; + g_PropAltNames.GetString(sKey, sAltKey, sizeof(sAltKey), NULL_STRING); + if(sAltKey[0]) + DispatchKeyValue(client, sAltKey, sValue); + } + } + } + } + while(g_Config.GotoNextKey(false)); + + g_Config.GoBack(); + g_Config.GoBack(); + g_Config.GetString("name", sValue, sizeof(sValue)); + g_Config.GoBack(); + StrCat(sNames, sizeof(sNames), sValue); + StrCat(sNames, sizeof(sNames), ", "); + } + } + } + + int NamesLen = strlen(sNames); + if(NamesLen) + { + sNames[NamesLen - 2] = 0; // Cut off ', ' + PrintToChatAll("\x03[SaveLevel]\x01 %N has been restored to: \x04%s", client, sNames); + } + g_Config.Rewind(); + } +} + +public void OnClientDisconnect(int client) +{ + if(!g_Config || !g_PlayerLevels) + return; + + g_Config.Rewind(); + g_Config.GotoFirstSubKey(); + + char sTargets[128]; + static char sTarget[32]; + static char sKey[32]; + static char sValue[1024]; + static char sOutput[1024]; + bool Found = false; + do + { + g_Config.GetSectionName(sTarget, sizeof(sTarget)); + if(!g_Config.JumpToKey("match")) + continue; + + int Matches = 0; + int ExactMatches = g_Config.GetNum("ExactMatches", -1); + int MinMatches = g_Config.GetNum("MinMatches", -1); + int MaxMatches = g_Config.GetNum("MaxMatches", -1); + + if(!g_Config.GotoFirstSubKey(false)) + continue; + + do + { + static char sSection[32]; + g_Config.GetSectionName(sSection, sizeof(sSection)); + + if(StrEqual(sSection, "outputs")) + { + int _Matches = 0; + int _ExactMatches = g_Config.GetNum("ExactMatches", -1); + int _MinMatches = g_Config.GetNum("MinMatches", -1); + int _MaxMatches = g_Config.GetNum("MaxMatches", -1); + + if(g_Config.GotoFirstSubKey(false)) + { + do + { + g_Config.GetSectionName(sKey, sizeof(sKey)); + g_Config.GetString(NULL_STRING, sValue, sizeof(sValue)); + + int Count = GetOutputCount(client, sKey); + for(int i = 0; i < Count; i++) + { + int Len = GetOutputTarget(client, sKey, i, sOutput); + sOutput[Len] = ','; Len++; + Len += GetOutputTargetInput(client, sKey, i, sOutput[Len]); + sOutput[Len] = ','; Len++; + Len += GetOutputParameter(client, sKey, i, sOutput[Len]); + + if(StrEqual(sValue, sOutput)) + _Matches++; + } + } + while(g_Config.GotoNextKey(false)); + + g_Config.GoBack(); + } + g_Config.GoBack(); + + Matches += CalcMatches(_Matches, _ExactMatches, _MinMatches, _MaxMatches); + } + else if(StrEqual(sSection, "props")) + { + int _Matches = 0; + int _ExactMatches = g_Config.GetNum("ExactMatches", -1); + int _MinMatches = g_Config.GetNum("MinMatches", -1); + int _MaxMatches = g_Config.GetNum("MaxMatches", -1); + + if(g_Config.GotoFirstSubKey(false)) + { + do + { + g_Config.GetSectionName(sKey, sizeof(sKey)); + g_Config.GetString(NULL_STRING, sValue, sizeof(sValue)); + + GetEntPropString(client, Prop_Data, sKey, sOutput, sizeof(sOutput)); + + if(StrEqual(sValue, sOutput)) + _Matches++; + } + while(g_Config.GotoNextKey(false)); + + g_Config.GoBack(); + } + g_Config.GoBack(); + + Matches += CalcMatches(_Matches, _ExactMatches, _MinMatches, _MaxMatches); + } + } + while(g_Config.GotoNextKey(false)); + + g_Config.GoBack(); + + if(CalcMatches(Matches, ExactMatches, MinMatches, MaxMatches)) + { + if(Found) + StrCat(sTargets, sizeof(sTargets), ";"); + + Found = true; + StrCat(sTargets, sizeof(sTargets), sTarget); + } + } + while(g_Config.GotoNextKey()); + + g_Config.Rewind(); + if(!Found) + return; + + char sSteamID[32]; + GetClientAuthId(client, AuthId_Steam3, sSteamID, sizeof(sSteamID)); + g_PlayerLevels.SetString(sSteamID, sTargets, true); +} + +stock int CalcMatches(int Matches, int ExactMatches, int MinMatches, int MaxMatches) +{ + int Value = 0; + if((ExactMatches == -1 && MinMatches == -1 && MaxMatches == -1 && Matches) || + Matches == ExactMatches || + (MinMatches != -1 && MaxMatches == -1 && Matches >= MinMatches) || + (MaxMatches != -1 && MinMatches == -1 && Matches <= MaxMatches) || + (MinMatches != -1 && MaxMatches != -1 && Matches >= MinMatches && Matches <= MaxMatches)) + { + Value++; + } + + return Value; +} diff --git a/SaveLevel/scripting/include/outputinfo.inc b/SaveLevel/scripting/include/outputinfo.inc new file mode 120000 index 00000000..1667222d --- /dev/null +++ b/SaveLevel/scripting/include/outputinfo.inc @@ -0,0 +1 @@ +../../../includes/outputinfo.inc \ No newline at end of file diff --git a/SelfMute/scripting/SelfMute.sp b/SelfMute/scripting/SelfMute.sp new file mode 100644 index 00000000..6ad65549 --- /dev/null +++ b/SelfMute/scripting/SelfMute.sp @@ -0,0 +1,908 @@ +#pragma semicolon 1 + +#include +#include +#include +#include + +#undef REQUIRE_PLUGIN +#include +#include +#include +#include +#define REQUIRE_PLUGIN + +#pragma newdecls required + +bool g_Plugin_ccc = false; +bool g_Plugin_zombiereloaded = false; +bool g_Plugin_voiceannounce_ex = false; +bool g_Plugin_AdvancedTargeting = false; + +#define PLUGIN_VERSION "2.0" + +public Plugin myinfo = +{ + name = "SelfMute", + author = "BotoX", + description = "Ignore other players in text and voicechat.", + version = PLUGIN_VERSION, + url = "" +}; + +enum +{ + MUTE_NONE = 0, + MUTE_SPEC = 1, + MUTE_CT = 2, + MUTE_T = 4, + MUTE_DEAD = 8, + MUTE_ALIVE = 16, + MUTE_NOTFRIENDS = 32, + MUTE_ALL = 64, + MUTE_LAST = 64 +}; + +bool g_Ignored[(MAXPLAYERS + 1) * (MAXPLAYERS + 1)]; +int g_SpecialMutes[MAXPLAYERS + 1]; + +char g_PlayerNames[MAXPLAYERS+1][MAX_NAME_LENGTH]; + +public void OnPluginStart() +{ + CreateConVar("sm_selfmute_version", PLUGIN_VERSION, "Version of Self-Mute", FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY); + + RegConsoleCmd("sm_sm", Command_SelfMute, "Mute player by typing !sm [playername]"); + RegConsoleCmd("sm_su", Command_SelfUnMute, "Unmute player by typing !su [playername]"); + RegConsoleCmd("sm_cm", Command_CheckMutes, "Check who you have self-muted"); + + HookEvent("round_start", Event_Round); + HookEvent("round_end", Event_Round); + HookEvent("player_team", Event_TeamChange); + + UserMsg RadioText = GetUserMessageId("RadioText"); + if(RadioText == INVALID_MESSAGE_ID) + SetFailState("This game doesn't support RadioText user messages."); + + HookUserMessage(RadioText, Hook_UserMessageRadioText, true); + + UserMsg SendAudio = GetUserMessageId("SendAudio"); + if(SendAudio == INVALID_MESSAGE_ID) + SetFailState("This game doesn't support SendAudio user messages."); + + HookUserMessage(SendAudio, Hook_UserMessageSendAudio, true); +} + +public void OnAllPluginsLoaded() +{ + g_Plugin_ccc = LibraryExists("ccc"); + g_Plugin_zombiereloaded = true;//LibraryExists("zombiereloaded"); + g_Plugin_voiceannounce_ex = LibraryExists("voiceannounce_ex"); + g_Plugin_AdvancedTargeting = LibraryExists("AdvancedTargeting"); + PrintToServer("CCC: %s\nZombieReloaded: %s\nVoiceAnnounce: %s\nAdvancedTargeting: %s", + (g_Plugin_ccc ? "loaded" : "not loaded"), + (g_Plugin_zombiereloaded ? "loaded" : "not loaded"), + (g_Plugin_voiceannounce_ex ? "loaded" : "not loaded"), + (g_Plugin_AdvancedTargeting ? "loaded" : "not loaded")); +} + +public void OnClientPutInServer(int client) +{ + g_SpecialMutes[client] = MUTE_NONE; + for(int i = 1; i <= MaxClients; i++) + SetIgnored(client, i, false); + + UpdateSpecialMutesOtherClients(client); + UpdateIgnored(); +} + +public void OnClientPostAdminCheck(int client) +{ + UpdateSpecialMutesOtherClients(client); + UpdateSpecialMutesThisClient(client); +} + +public void OnClientDisconnect(int client) +{ + g_SpecialMutes[client] = MUTE_NONE; + for(int i = 1; i <= MaxClients; i++) + { + SetIgnored(client, i, false); + + if(IsClientInGame(i) && !IsFakeClient(i) && i != client) + SetListenOverride(i, client, Listen_Yes); + } + + UpdateIgnored(); +} + +public void Event_Round(Handle event, const char[] name, bool dontBroadcast) +{ + for(int i = 1; i <= MaxClients; i++) + { + if(IsClientInGame(i) && !IsFakeClient(i)) + UpdateSpecialMutesThisClient(i); + } +} + +public void Event_TeamChange(Handle event, const char[] name, bool dontBroadcast) +{ + int client = GetClientOfUserId(GetEventInt(event, "userid")); + + UpdateSpecialMutesOtherClients(client); +} + +public int ZR_OnClientInfected(int client, int attacker, bool motherInfect, bool respawnOverride, bool respawn) +{ + UpdateSpecialMutesOtherClients(client); +} + +public int ZR_OnClientHumanPost(int client, bool respawn, bool protect) +{ + UpdateSpecialMutesOtherClients(client); +} + +/* + * Mutes this client on other players +*/ +void UpdateSpecialMutesOtherClients(int client) +{ + bool Alive = IsPlayerAlive(client); + int Team = GetClientTeam(client); + + for(int i = 1; i <= MaxClients; i++) + { + if(i == client || !IsClientInGame(i) || IsFakeClient(i)) + continue; + + int Flags = MUTE_NONE; + + if(g_SpecialMutes[i] & MUTE_SPEC && Team == CS_TEAM_SPECTATOR) + Flags |= MUTE_SPEC; + + else if(g_SpecialMutes[i] & MUTE_CT && Alive && + ((g_Plugin_zombiereloaded && ZR_IsClientHuman(client)) || (!g_Plugin_zombiereloaded && Team == CS_TEAM_CT))) + Flags |= MUTE_CT; + + else if(g_SpecialMutes[i] & MUTE_T && Alive && + ((g_Plugin_zombiereloaded && ZR_IsClientZombie(client)) || (!g_Plugin_zombiereloaded && Team == CS_TEAM_T))) + Flags |= MUTE_T; + + else if(g_SpecialMutes[i] & MUTE_DEAD && !Alive) + Flags |= MUTE_DEAD; + + else if(g_SpecialMutes[i] & MUTE_ALIVE && Alive) + Flags |= MUTE_ALIVE; + + else if(g_SpecialMutes[i] & MUTE_NOTFRIENDS && + g_Plugin_AdvancedTargeting && IsClientFriend(i, client) == 0) + Flags |= MUTE_NOTFRIENDS; + + else if(g_SpecialMutes[i] & MUTE_ALL) + Flags |= MUTE_ALL; + + if(Flags) + SetListenOverride(i, client, Listen_No); + else if(!GetIgnored(i, client)) + SetListenOverride(i, client, Listen_Yes); + } +} + +/* + * Mutes other players on this client +*/ +void UpdateSpecialMutesThisClient(int client) +{ + for(int i = 1; i <= MaxClients; i++) + { + if(i == client || !IsClientInGame(i) || IsFakeClient(i)) + continue; + + bool Alive = IsPlayerAlive(i); + int Team = GetClientTeam(i); + + int Flags = MUTE_NONE; + + if(g_SpecialMutes[client] & MUTE_SPEC && Team == CS_TEAM_SPECTATOR) + Flags |= MUTE_SPEC; + + else if(g_SpecialMutes[client] & MUTE_CT && Alive && + ((g_Plugin_zombiereloaded && ZR_IsClientHuman(i) || (!g_Plugin_zombiereloaded) && Team == CS_TEAM_CT))) + Flags |= MUTE_CT; + + else if(g_SpecialMutes[client] & MUTE_T && Alive && + ((g_Plugin_zombiereloaded && ZR_IsClientZombie(i) || (!g_Plugin_zombiereloaded) && Team == CS_TEAM_T))) + Flags |= MUTE_T; + + else if(g_SpecialMutes[client] & MUTE_DEAD && !Alive) + Flags |= MUTE_DEAD; + + else if(g_SpecialMutes[client] & MUTE_ALIVE && Alive) + Flags |= MUTE_ALIVE; + + else if(g_SpecialMutes[client] & MUTE_NOTFRIENDS && + g_Plugin_AdvancedTargeting && IsClientFriend(client, i) == 0) + Flags |= MUTE_NOTFRIENDS; + + else if(g_SpecialMutes[client] & MUTE_ALL) + Flags |= MUTE_ALL; + + if(Flags) + SetListenOverride(client, i, Listen_No); + else if(!GetIgnored(client, i)) + SetListenOverride(client, i, Listen_Yes); + } +} + +int GetSpecialMutesFlags(char[] Argument) +{ + int SpecialMute = MUTE_NONE; + if(StrEqual(Argument, "@spec", false) || StrEqual(Argument, "@!ct", false) || StrEqual(Argument, "@!t", false)) + SpecialMute |= MUTE_SPEC; + if(StrEqual(Argument, "@ct", false) || StrEqual(Argument, "@!t", false) || StrEqual(Argument, "@!spec", false)) + SpecialMute |= MUTE_CT; + if(StrEqual(Argument, "@t", false) || StrEqual(Argument, "@!ct", false) || StrEqual(Argument, "@!spec", false)) + SpecialMute |= MUTE_T; + if(StrEqual(Argument, "@dead", false) || StrEqual(Argument, "@!alive", false)) + SpecialMute |= MUTE_DEAD; + if(StrEqual(Argument, "@alive", false) || StrEqual(Argument, "@!dead", false)) + SpecialMute |= MUTE_ALIVE; + if(g_Plugin_AdvancedTargeting && StrEqual(Argument, "@!friends", false)) + SpecialMute |= MUTE_NOTFRIENDS; + if(StrEqual(Argument, "@all", false)) + SpecialMute |= MUTE_ALL; + + return SpecialMute; +} + +void FormatSpecialMutes(int SpecialMute, char[] aBuf, int BufLen) +{ + if(!SpecialMute) + { + StrCat(aBuf, BufLen, "none"); + return; + } + + bool Status = false; + int MuteCount = RoundFloat(Logarithm(float(MUTE_LAST), 2.0)); + for(int i = 0; i <= MuteCount; i++) + { + switch(SpecialMute & RoundFloat(Pow(2.0, float(i)))) + { + case MUTE_SPEC: + { + StrCat(aBuf, BufLen, "Spectators, "); + Status = true; + } + case MUTE_CT: + { + StrCat(aBuf, BufLen, "CTs, "); + Status = true; + } + case MUTE_T: + { + StrCat(aBuf, BufLen, "Ts, "); + Status = true; + } + case MUTE_DEAD: + { + StrCat(aBuf, BufLen, "Dead players, "); + Status = true; + } + case MUTE_ALIVE: + { + StrCat(aBuf, BufLen, "Alive players, "); + Status = true; + } + case MUTE_NOTFRIENDS: + { + StrCat(aBuf, BufLen, "Not Steam friends, "); + Status = true; + } + case MUTE_ALL: + { + StrCat(aBuf, BufLen, "Everyone, "); + Status = true; + } + } + } + + // Cut off last ', ' + if(Status) + aBuf[strlen(aBuf) - 2] = 0; +} + +bool MuteSpecial(int client, char[] Argument) +{ + bool RetValue = false; + int SpecialMute = GetSpecialMutesFlags(Argument); + + if(SpecialMute & MUTE_NOTFRIENDS && g_Plugin_AdvancedTargeting && ReadClientFriends(client) != 1) + { + PrintToChat(client, "\x04[Self-Mute]\x01 Could not read your friendslist, your profile must be set to public!"); + SpecialMute &= ~MUTE_NOTFRIENDS; + RetValue = true; + } + + if(SpecialMute) + { + if(SpecialMute & MUTE_ALL || g_SpecialMutes[client] & MUTE_ALL) + { + g_SpecialMutes[client] = MUTE_ALL; + SpecialMute = MUTE_ALL; + } + else + g_SpecialMutes[client] |= SpecialMute; + + UpdateSpecialMutesThisClient(client); + + char aBuf[128]; + FormatSpecialMutes(SpecialMute, aBuf, sizeof(aBuf)); + + PrintToChat(client, "\x04[Self-Mute]\x01 You have self-muted group:\x04 %s", aBuf); + RetValue = true; + } + + return RetValue; +} + +bool UnMuteSpecial(int client, char[] Argument) +{ + int SpecialMute = GetSpecialMutesFlags(Argument); + + if(SpecialMute) + { + if(SpecialMute & MUTE_ALL) + { + if(g_SpecialMutes[client]) + { + SpecialMute = g_SpecialMutes[client]; + g_SpecialMutes[client] = MUTE_NONE; + } + else + { + for(int i = 1; i <= MaxClients; i++) + { + if(IsClientInGame(i) && !IsFakeClient(i)) + UnIgnore(client, i); + + PrintToChat(client, "\x04[Self-Mute]\x01 You have self-unmuted:\x04 all players"); + return true; + } + } + } + else + g_SpecialMutes[client] &= ~SpecialMute; + + UpdateSpecialMutesThisClient(client); + + char aBuf[256]; + FormatSpecialMutes(SpecialMute, aBuf, sizeof(aBuf)); + + PrintToChat(client, "\x04[Self-Mute]\x01 You have self-unmuted group:\x04 %s", aBuf); + return true; + } + + return false; +} + +void Ignore(int client, int target) +{ + SetIgnored(client, target, true); + SetListenOverride(client, target, Listen_No); +} + +void UnIgnore(int client, int target) +{ + SetIgnored(client, target, false); + SetListenOverride(client, target, Listen_Yes); +} + +/* + * CHAT COMMANDS +*/ +public Action Command_SelfMute(int client, int args) +{ + if(client == 0) + { + PrintToServer("[SM] Cannot use command from server console."); + return Plugin_Handled; + } + + if(args < 1) + { + DisplayMuteMenu(client); + return Plugin_Handled; + } + + char Argument[65]; + GetCmdArgString(Argument, sizeof(Argument)); + + char Filtered[65]; + strcopy(Filtered, sizeof(Filtered), Argument); + StripQuotes(Filtered); + TrimString(Filtered); + + if(StrEqual(Filtered, "@me", false)) + { + PrintToChat(client, "\x04[Self-Mute]\x01 You can't mute yourself, don't be silly."); + return Plugin_Handled; + } + + if(MuteSpecial(client, Filtered)) + return Plugin_Handled; + + char sTargetName[MAX_TARGET_LENGTH]; + int aTargetList[MAXPLAYERS]; + int TargetCount; + bool TnIsMl; + + if((TargetCount = ProcessTargetString( + Argument, + client, + aTargetList, + MAXPLAYERS, + COMMAND_FILTER_CONNECTED|COMMAND_FILTER_NO_IMMUNITY, + sTargetName, + sizeof(sTargetName), + TnIsMl)) <= 0) + { + ReplyToTargetError(client, TargetCount); + return Plugin_Handled; + } + + for(int i = 0; i < TargetCount; i++) + { + if(aTargetList[i] != client) + Ignore(client, aTargetList[i]); + } + UpdateIgnored(); + + PrintToChat(client, "\x04[Self-Mute]\x01 You have self-muted:\x04 %s", sTargetName); + + return Plugin_Handled; +} + +public Action Command_SelfUnMute(int client, int args) +{ + if(client == 0) + { + PrintToServer("[SM] Cannot use command from server console."); + return Plugin_Handled; + } + + if(args < 1) + { + DisplayUnMuteMenu(client); + return Plugin_Handled; + } + + char Argument[65]; + GetCmdArgString(Argument, sizeof(Argument)); + + char Filtered[65]; + strcopy(Filtered, sizeof(Filtered), Argument); + StripQuotes(Filtered); + TrimString(Filtered); + + if(StrEqual(Filtered, "@me", false)) + { + PrintToChat(client, "\x04[Self-Mute]\x01 Unmuting won't work either."); + return Plugin_Handled; + } + + if(UnMuteSpecial(client, Filtered)) + return Plugin_Handled; + + char sTargetName[MAX_TARGET_LENGTH]; + int aTargetList[MAXPLAYERS]; + int TargetCount; + bool TnIsMl; + + if((TargetCount = ProcessTargetString( + Argument, + client, + aTargetList, + MAXPLAYERS, + COMMAND_FILTER_CONNECTED|COMMAND_FILTER_NO_IMMUNITY, + sTargetName, + sizeof(sTargetName), + TnIsMl)) <= 0) + { + ReplyToTargetError(client, TargetCount); + return Plugin_Handled; + } + + for(int i = 0; i < TargetCount; i++) + { + if(aTargetList[i] != client) + UnIgnore(client, aTargetList[i]); + } + UpdateIgnored(); + + PrintToChat(client, "\x04[Self-Mute]\x01 You have self-unmuted:\x04 %s", sTargetName); + + return Plugin_Handled; +} + +public Action Command_CheckMutes(int client, int args) +{ + if(client == 0) + { + PrintToServer("[SM] Cannot use command from server console."); + return Plugin_Handled; + } + + char aBuf[1024]; + char aBuf2[MAX_NAME_LENGTH]; + for(int i = 1; i <= MaxClients; i++) + { + if(GetIgnored(client, i)) + { + GetClientName(i, aBuf2, sizeof(aBuf2)); + StrCat(aBuf, sizeof(aBuf), aBuf2); + StrCat(aBuf, sizeof(aBuf), ", "); + } + } + + // Cut off last ', ' + if(strlen(aBuf)) + { + aBuf[strlen(aBuf) - 2] = 0; + PrintToChat(client, "\x04[Self-Mute]\x01 You have self-muted:\x04 %s", aBuf); + } + else if(!g_SpecialMutes[client]) + PrintToChat(client, "\x04[Self-Mute]\x01 You have not self-muted anyone!\x04", aBuf); + + if(g_SpecialMutes[client]) + { + aBuf[0] = 0; + FormatSpecialMutes(g_SpecialMutes[client], aBuf, sizeof(aBuf)); + PrintToChat(client, "\x04[Self-Mute]\x01 You have self-muted group:\x04 %s", aBuf); + } + + return Plugin_Handled; +} + +/* + * MENU +*/ +void DisplayMuteMenu(int client) +{ + Menu menu = new Menu(MenuHandler_MuteMenu, MenuAction_Select|MenuAction_Cancel|MenuAction_End|MenuAction_DrawItem|MenuAction_DisplayItem); + menu.ExitButton = true; + + int[] aClients = new int[MaxClients + 1]; + + if(g_Plugin_voiceannounce_ex) + { + // Count talking players and insert id's into aClients array + int CurrentlyTalking = 0; + for(int i = 1; i <= MaxClients; i++) + { + if(i != client && IsClientInGame(i) && !IsFakeClient(i) && IsClientSpeaking(i)) + aClients[CurrentlyTalking++] = i; + } + + if(CurrentlyTalking > 0) + { + // insert player names into g_PlayerNames array + for(int i = 0; i < CurrentlyTalking; i++) + GetClientName(aClients[i], g_PlayerNames[aClients[i]], sizeof(g_PlayerNames[])); + + // sort aClients array by player name + SortCustom1D(aClients, CurrentlyTalking, SortByPlayerName); + + // insert players sorted + char aBuf[11]; + for(int i = 0; i < CurrentlyTalking; i++) + { + IntToString(GetClientUserId(aClients[i]), aBuf, sizeof(aBuf)); + menu.AddItem(aBuf, g_PlayerNames[aClients[i]]); + } + + // insert spacers + int Entries = 7 - CurrentlyTalking % 7; + while(Entries--) + menu.AddItem("", "", ITEMDRAW_RAWLINE); + } + } + + menu.AddItem("@all", "Everyone"); + menu.AddItem("@spec", "Spectators"); + menu.AddItem("@ct", "Counter-Terrorists"); + menu.AddItem("@t", "Terrorists"); + menu.AddItem("@dead", "Dead players"); + menu.AddItem("@alive", "Alive players"); + if(g_Plugin_AdvancedTargeting) + menu.AddItem("@!friends", "Not Steam friend"); + else + menu.AddItem("", "", ITEMDRAW_RAWLINE); + + // Count valid players and insert id's into aClients array + int Players = 0; + for(int i = 1; i <= MaxClients; i++) + { + if(i != client && IsClientInGame(i) && !IsFakeClient(i)) + aClients[Players++] = i; + } + + // insert player names into g_PlayerNames array + for(int i = 0; i < Players; i++) + GetClientName(aClients[i], g_PlayerNames[aClients[i]], sizeof(g_PlayerNames[])); + + // sort aClients array by player name + SortCustom1D(aClients, Players, SortByPlayerName); + + // insert players sorted + char aBuf[12]; + for(int i = 0; i < Players; i++) + { + IntToString(GetClientUserId(aClients[i]), aBuf, sizeof(aBuf)); + menu.AddItem(aBuf, g_PlayerNames[aClients[i]]); + } + + menu.Display(client, MENU_TIME_FOREVER); +} + +public int MenuHandler_MuteMenu(Menu menu, MenuAction action, int param1, int param2) +{ + switch(action) + { + case MenuAction_End: + { + if(param1 != MenuEnd_Selected) + CloseHandle(menu); + } + case MenuAction_Select: + { + int Style; + char aItem[32]; + char aDisp[MAX_NAME_LENGTH + 4]; + menu.GetItem(param2, aItem, sizeof(aItem), Style, aDisp, sizeof(aDisp)); + + if(Style != ITEMDRAW_DEFAULT || !aItem[0]) + { + PrintToChat(param1, "Internal error: aItem[0] -> %d | Style -> %d", aItem[0], Style); + return 0; + } + + if(aItem[0] == '@') + { + int Flag = GetSpecialMutesFlags(aItem); + if(Flag && g_SpecialMutes[param1] & Flag) + UnMuteSpecial(param1, aItem); + else + MuteSpecial(param1, aItem); + + menu.DisplayAt(param1, GetMenuSelectionPosition(), MENU_TIME_FOREVER); + return 0; + } + + int UserId = StringToInt(aItem); + int client = GetClientOfUserId(UserId); + if(!client) + { + PrintToChat(param1, "\x04[Self-Mute]\x01 Player no longer available."); + menu.DisplayAt(param1, GetMenuSelectionPosition(), MENU_TIME_FOREVER); + return 0; + } + + if(GetIgnored(param1, client)) + { + UnIgnore(param1, client); + PrintToChat(param1, "\x04[Self-Mute]\x01 You have self-unmuted:\x04 %N", client); + } + else + { + Ignore(param1, client); + PrintToChat(param1, "\x04[Self-Mute]\x01 You have self-muted:\x04 %N", client); + } + menu.DisplayAt(param1, GetMenuSelectionPosition(), MENU_TIME_FOREVER); + return 0; + } + case MenuAction_DrawItem: + { + int Style; + char aItem[32]; + menu.GetItem(param2, aItem, sizeof(aItem), Style); + + if(!aItem[0]) + return ITEMDRAW_DISABLED; + + if(aItem[0] == '@') + { + int Flag = GetSpecialMutesFlags(aItem); + if(Flag & MUTE_ALL) + return Style; + else if(g_SpecialMutes[param1] & MUTE_ALL) + return ITEMDRAW_DISABLED; + + return Style; + } + + int UserId = StringToInt(aItem); + int client = GetClientOfUserId(UserId); + if(!client) // Player disconnected + return ITEMDRAW_DISABLED; + + return Style; + } + case MenuAction_DisplayItem: + { + int Style; + char aItem[32]; + char aDisp[MAX_NAME_LENGTH + 4]; + menu.GetItem(param2, aItem, sizeof(aItem), Style, aDisp, sizeof(aDisp)); + + // Start of current page + if((param2 + 1) % 7 == 1) + { + if(aItem[0] == '@') + menu.SetTitle("[Self-Mute] Groups"); + else if(param2 == 0) + menu.SetTitle("[Self-Mute] Talking players"); + else + menu.SetTitle("[Self-Mute] All players"); + } + + if(!aItem[0]) + return 0; + + if(aItem[0] == '@') + { + int Flag = GetSpecialMutesFlags(aItem); + if(Flag && g_SpecialMutes[param1] & Flag) + { + char aBuf[32] = "[M] "; + FormatSpecialMutes(Flag, aBuf, sizeof(aBuf)); + if(!StrEqual(aDisp, aBuf)) + return RedrawMenuItem(aBuf); + } + + return 0; + } + + int UserId = StringToInt(aItem); + int client = GetClientOfUserId(UserId); + if(!client) // Player disconnected + { + char aBuf[MAX_NAME_LENGTH + 4] = "[D] "; + StrCat(aBuf, sizeof(aBuf), aDisp); + if(!StrEqual(aDisp, aBuf)) + return RedrawMenuItem(aBuf); + } + + if(GetIgnored(param1, client)) + { + char aBuf[MAX_NAME_LENGTH + 4] = "[M] "; + GetClientName(client, g_PlayerNames[client], sizeof(g_PlayerNames[])); + StrCat(aBuf, sizeof(aBuf), g_PlayerNames[client]); + if(!StrEqual(aDisp, aBuf)) + return RedrawMenuItem(aBuf); + } + else + { + GetClientName(client, g_PlayerNames[client], sizeof(g_PlayerNames[])); + if(!StrEqual(aDisp, g_PlayerNames[client])) + return RedrawMenuItem(g_PlayerNames[client]); + } + + return 0; + } + } + + return 0; +} + +void DisplayUnMuteMenu(int client) +{ + // TODO: Implement me +} + +/* + * HOOKS +*/ +int g_MsgDest; +int g_MsgClient; +char g_MsgName[256]; +char g_MsgParam1[256]; +char g_MsgParam2[256]; +char g_MsgParam3[256]; +char g_MsgParam4[256]; +int g_MsgPlayersNum; +int g_MsgPlayers[MAXPLAYERS + 1]; + +int g_TimerDerp = 0; + +public Action Hook_UserMessageRadioText(UserMsg msg_id, Handle bf, const int[] players, int playersNum, bool reliable, bool init) +{ + g_MsgDest = BfReadByte(bf); + g_MsgClient = BfReadByte(bf); + BfReadString(bf, g_MsgName, sizeof(g_MsgName), false); + BfReadString(bf, g_MsgParam1, sizeof(g_MsgParam1), false); + BfReadString(bf, g_MsgParam2, sizeof(g_MsgParam2), false); + BfReadString(bf, g_MsgParam3, sizeof(g_MsgParam3), false); + BfReadString(bf, g_MsgParam4, sizeof(g_MsgParam4), false); + + g_MsgPlayersNum = playersNum; + for(int i = 0; i < playersNum; i++) + g_MsgPlayers[i] = players[i]; + + if(!g_TimerDerp) + CreateTimer(0.1, Timer_PlayerRadio); + + g_TimerDerp++; + if(g_TimerDerp > 1) + PrintToServer("DEBUG: Timer_PlayerRadio derped! (%d)", g_TimerDerp); + + return Plugin_Handled; +} + +char g_MsgRadioSound[256]; + +public Action Hook_UserMessageSendAudio(UserMsg msg_id, Handle bf, const int[] players, int playersNum, bool reliable, bool init) +{ + BfReadString(bf, g_MsgRadioSound, sizeof(g_MsgRadioSound), false); + + return Plugin_Handled; +} + +public Action Timer_PlayerRadio(Handle timer) +{ + g_TimerDerp = 0; + if(g_MsgClient == -1) + return Plugin_Continue; + + int[] players = new int[g_MsgPlayersNum + 1]; + int playersNum = 0; + + for(int i = 0; i < g_MsgPlayersNum; i++) + { + int client = g_MsgPlayers[i]; + if(IsClientInGame(client) && !GetIgnored(client, g_MsgClient)) + players[playersNum++] = client; + } + + Handle RadioText = StartMessage("RadioText", players, playersNum, USERMSG_RELIABLE | USERMSG_BLOCKHOOKS); + BfWriteByte(RadioText, g_MsgDest); + BfWriteByte(RadioText, g_MsgClient); + BfWriteString(RadioText, g_MsgName); + BfWriteString(RadioText, g_MsgParam1); + BfWriteString(RadioText, g_MsgParam2); + BfWriteString(RadioText, g_MsgParam3); + BfWriteString(RadioText, g_MsgParam4); + EndMessage(); + + Handle SendAudio = StartMessage("SendAudio", players, playersNum, USERMSG_RELIABLE | USERMSG_BLOCKHOOKS); + BfWriteString(SendAudio, g_MsgRadioSound); + EndMessage(); + + g_MsgClient = -1; + + return Plugin_Continue; +} + +/* + * HELPERS +*/ +void UpdateIgnored() +{ + if(g_Plugin_ccc) + CCC_UpdateIgnoredArray(g_Ignored); +} + +public int SortByPlayerName(int elem1, int elem2, const int[] array, Handle hndl) +{ + return strcmp(g_PlayerNames[elem1], g_PlayerNames[elem2], false); +} + +bool GetIgnored(int client, int target) +{ + return g_Ignored[(client * (MAXPLAYERS + 1) + target)]; +} + +void SetIgnored(int client, int target, bool ignored) +{ + g_Ignored[(client * (MAXPLAYERS + 1) + target)] = ignored; +} diff --git a/SelfMute/scripting/include/AdvancedTargeting.inc b/SelfMute/scripting/include/AdvancedTargeting.inc new file mode 120000 index 00000000..a49e950a --- /dev/null +++ b/SelfMute/scripting/include/AdvancedTargeting.inc @@ -0,0 +1 @@ +../../../AdvancedTargeting/scripting/include/AdvancedTargeting.inc \ No newline at end of file diff --git a/SelfMute/scripting/include/ccc.inc b/SelfMute/scripting/include/ccc.inc new file mode 120000 index 00000000..bfe7ff40 --- /dev/null +++ b/SelfMute/scripting/include/ccc.inc @@ -0,0 +1 @@ +../../../custom-chatcolors/scripting/include/ccc.inc \ No newline at end of file diff --git a/SelfMute/scripting/include/voiceannounce_ex.inc b/SelfMute/scripting/include/voiceannounce_ex.inc new file mode 120000 index 00000000..d46c0000 --- /dev/null +++ b/SelfMute/scripting/include/voiceannounce_ex.inc @@ -0,0 +1 @@ +../../../voiceannounce_ex/scripting/include/voiceannounce_ex.inc \ No newline at end of file diff --git a/SelfMute/scripting/include/zombiereloaded.inc b/SelfMute/scripting/include/zombiereloaded.inc new file mode 120000 index 00000000..c9a376e8 --- /dev/null +++ b/SelfMute/scripting/include/zombiereloaded.inc @@ -0,0 +1 @@ +../../../includes/zombiereloaded.inc \ No newline at end of file diff --git a/Status/scripting/Status.sp b/Status/scripting/Status.sp new file mode 100644 index 00000000..e240d52c --- /dev/null +++ b/Status/scripting/Status.sp @@ -0,0 +1,208 @@ +#pragma semicolon 1 +//==================================================================================================== +// +// Name: Status Fixer. +// Author: zaCade + BotoX +// Description: Fixes the 'status' command. +// +//==================================================================================================== +#include +#include + +#pragma newdecls required + +ConVar g_Cvar_HostIP; +ConVar g_Cvar_HostPort; +ConVar g_Cvar_HostName; +ConVar g_Cvar_HostTags; + +Handle g_hPlayerList[MAXPLAYERS + 1] = {INVALID_HANDLE, ...}; +bool g_bDataAvailable = false; + +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public Plugin myinfo = +{ + name = "Status Fixer", + author = "zaCade + BotoX", + description = "Fixes the 'status' command", + version = "1.1", + url = "" +}; + +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public void OnPluginStart() +{ + g_Cvar_HostIP = FindConVar("hostip"); + g_Cvar_HostPort = FindConVar("hostport"); + g_Cvar_HostName = FindConVar("hostname"); + g_Cvar_HostTags = FindConVar("sv_tags"); + + AddCommandListener(Command_Status, "status"); +} + +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public Action Command_Status(int client, const char[] command, int args) +{ + if(client) + { + if(g_hPlayerList[client] != INVALID_HANDLE) + return Plugin_Handled; + + static char sServerName[128]; + static char sServerTags[128]; + static char sServerAdress[128]; + + int iServerIP = g_Cvar_HostIP.IntValue; + int iServerPort = g_Cvar_HostPort.IntValue; + + g_Cvar_HostName.GetString(sServerName, sizeof(sServerName)); + g_Cvar_HostTags.GetString(sServerTags, sizeof(sServerTags)); + + Format(sServerAdress, sizeof(sServerAdress), "%d.%d.%d.%d:%d", iServerIP >>> 24 & 255, iServerIP >>> 16 & 255, iServerIP >>> 8 & 255, iServerIP & 255, iServerPort); + + static char sMapName[128]; + GetCurrentMap(sMapName, sizeof(sMapName)); + + float fPosition[3]; + GetClientAbsOrigin(client, fPosition); + + int iRealClients; + int iFakeClients; + int iTotalClients; + + for(int player = 1; player <= MaxClients; player++) + { + if(IsClientConnected(player)) + { + iTotalClients++; + + if(IsFakeClient(player)) + iFakeClients++; + else + iRealClients++; + } + } + + static char sSendBuffer[1000]; + int iBufLength = 0; + Format(sSendBuffer, sizeof(sSendBuffer), "hostname: %s\n", sServerName); + Format(sSendBuffer, sizeof(sSendBuffer), "%stickrate: %d\n", sSendBuffer, RoundToZero(1.0 / GetTickInterval())); + Format(sSendBuffer, sizeof(sSendBuffer), "%sudp/ip : %s\n", sSendBuffer, sServerAdress); + Format(sSendBuffer, sizeof(sSendBuffer), "%smap : %s at: %.0f x, %.0f y, %.0f z\n", sSendBuffer, sMapName, fPosition[0], fPosition[1], fPosition[2]); + Format(sSendBuffer, sizeof(sSendBuffer), "%stags : %s\n", sSendBuffer, sServerTags); + Format(sSendBuffer, sizeof(sSendBuffer), "%s%edicts : %d/%d/%d (used/max/free)\n", sSendBuffer, GetEntityCount(), GetMaxEntities(), GetMaxEntities() - GetEntityCount()); + Format(sSendBuffer, sizeof(sSendBuffer), "%splayers : %d humans | %d bots (%d/%d)\n", sSendBuffer, iRealClients, iFakeClients, iTotalClients, MaxClients); + Format(sSendBuffer, sizeof(sSendBuffer), "%s# %8s %40s %24s %12s %4s %4s %s", sSendBuffer, "userid", "name", "uniqueid", "connected", "ping", "loss", "state"); + + g_hPlayerList[client] = CreateArray(ByteCountToCells(1000)); + + PushArrayString(g_hPlayerList[client], sSendBuffer); + g_bDataAvailable = true; + sSendBuffer[0] = 0; + + for(int player = 1; player <= MaxClients; player++) + { + if(IsClientConnected(player)) + { + static char sPlayerID[8]; + static char sPlayerName[40]; + char sPlayerAuth[24]; + char sPlayerTime[12]; + char sPlayerPing[4]; + char sPlayerLoss[4]; + static char sPlayerState[16]; + + Format(sPlayerID, sizeof(sPlayerID), "%d", GetClientUserId(player)); + Format(sPlayerName, sizeof(sPlayerName), "\"%N\"", player); + + if(!GetClientAuthId(player, AuthId_Steam2, sPlayerAuth, sizeof(sPlayerAuth))) + Format(sPlayerAuth, sizeof(sPlayerAuth), "STEAM_ID_PENDING"); + + if(!IsFakeClient(player)) + { + int iHours = RoundToFloor((GetClientTime(player) / 3600)); + int iMinutes = RoundToFloor((GetClientTime(player) - (iHours * 3600)) / 60); + int iSeconds = RoundToFloor((GetClientTime(player) - (iHours * 3600)) - (iMinutes * 60)); + + if (iHours) + Format(sPlayerTime, sizeof(sPlayerTime), "%d:%02d:%02d", iHours, iMinutes, iSeconds); + else + Format(sPlayerTime, sizeof(sPlayerTime), "%d:%02d", iMinutes, iSeconds); + + Format(sPlayerPing, sizeof(sPlayerPing), "%d", RoundFloat(GetClientLatency(player, NetFlow_Outgoing) * 800)); + Format(sPlayerLoss, sizeof(sPlayerLoss), "%d", RoundFloat(GetClientAvgLoss(player, NetFlow_Outgoing) * 100)); + } + + if(IsClientInGame(player)) + Format(sPlayerState, sizeof(sPlayerState), "active"); + else + Format(sPlayerState, sizeof(sPlayerState), "spawning"); + + static char sFormatted[128]; + Format(sFormatted, sizeof(sFormatted), "# %8s %40s %24s %12s %4s %4s %s\n", sPlayerID, sPlayerName, sPlayerAuth, sPlayerTime, sPlayerPing, sPlayerLoss, sPlayerState); + + int iFormattedLength = strlen(sFormatted); + if(iBufLength + iFormattedLength >= 1000) + { + sSendBuffer[iBufLength - 1] = 0; + PushArrayString(g_hPlayerList[client], sSendBuffer); + sSendBuffer[0] = 0; + iBufLength = 0; + } + else + { + StrCat(sSendBuffer, sizeof(sSendBuffer), sFormatted); + iBufLength += iFormattedLength; + } + } + } + + if(iBufLength) + { + sSendBuffer[iBufLength - 1] = 0; + PushArrayString(g_hPlayerList[client], sSendBuffer); + } + + return Plugin_Handled; + } + return Plugin_Continue; +} + +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public void OnGameFrame() +{ + if(!g_bDataAvailable) + return; + + bool bGotData = false; + for(int client = 0; client < MAXPLAYERS + 1; client++) + { + if(g_hPlayerList[client] == INVALID_HANDLE) + continue; + + if(!IsClientInGame(client) || !GetArraySize(g_hPlayerList[client])) + { + CloseHandle(g_hPlayerList[client]); + g_hPlayerList[client] = INVALID_HANDLE; + continue; + } + + static char sBuffer[1000]; + GetArrayString(g_hPlayerList[client], 0, sBuffer, sizeof(sBuffer)); + RemoveFromArray(g_hPlayerList[client], 0); + + PrintToConsole(client, sBuffer); + bGotData = true; + } + + if(!bGotData) + g_bDataAvailable = false; +} diff --git a/StopSound/scripting/StopSound.sp b/StopSound/scripting/StopSound.sp new file mode 100644 index 00000000..ef30f877 --- /dev/null +++ b/StopSound/scripting/StopSound.sp @@ -0,0 +1,333 @@ +#pragma semicolon 1 + +#include +#include +#include + +#define PLUGIN_NAME "Toggle Weapon Sounds" +#define PLUGIN_VERSION "1.2.0" + +#define UPDATE_URL "http://godtony.mooo.com/stopsound/stopsound.txt" + +new bool:g_bStopSound[MAXPLAYERS+1], bool:g_bHooked; +static String:g_sKVPATH[PLATFORM_MAX_PATH]; +new Handle:g_hWepSounds; + +public Plugin:myinfo = +{ + name = PLUGIN_NAME, + author = "GoD-Tony, edit by id/Obus", + description = "Allows clients to stop hearing weapon sounds", + version = PLUGIN_VERSION, + url = "http://www.sourcemod.net/" +}; + +public OnPluginStart() +{ + // Detect game and hook appropriate tempent. + decl String:sGame[32]; + GetGameFolderName(sGame, sizeof(sGame)); + + if (StrEqual(sGame, "cstrike")) + { + AddTempEntHook("Shotgun Shot", CSS_Hook_ShotgunShot); + } + else if (StrEqual(sGame, "dod")) + { + AddTempEntHook("FireBullets", DODS_Hook_FireBullets); + } + + // TF2/HL2:DM and misc weapon sounds will be caught here. + AddNormalSoundHook(Hook_NormalSound); + + CreateConVar("sm_stopsound_version", PLUGIN_VERSION, "Toggle Weapon Sounds", FCVAR_NOTIFY|FCVAR_DONTRECORD|FCVAR_REPLICATED); + RegConsoleCmd("sm_stopsound", Command_StopSound, "Toggle hearing weapon sounds"); + + if (g_hWepSounds != INVALID_HANDLE) + { + CloseHandle(g_hWepSounds); + } + + g_hWepSounds = CreateKeyValues("WeaponSounds"); + BuildPath(Path_SM, g_sKVPATH, sizeof(g_sKVPATH), "data/playerprefs.WepSounds.txt"); + + FileToKeyValues(g_hWepSounds, g_sKVPATH); + + // Updater. + //if (LibraryExists("updater")) + //{ + // Updater_AddPlugin(UPDATE_URL); + //} +} + +/*public OnLibraryAdded(const String:name[]) +{ + if (StrEqual(name, "updater")) + { + Updater_AddPlugin(UPDATE_URL); + } +}*/ + +public Action:Command_StopSound(client, args) +{ + if (client == 0) + { + PrintToServer("[SM] Cannot use command from server console."); + return Plugin_Handled; + } + + if (args > 0) + { + decl String:Arguments[32]; + GetCmdArg(1, Arguments, sizeof(Arguments)); + + if (StrEqual(Arguments, "save")) + { + KvRewind(g_hWepSounds); + + decl String:SID[32]; + GetClientAuthId(client, AuthId_Steam2, SID, sizeof(SID)); + + if (KvJumpToKey(g_hWepSounds, SID, true)) + { + new disabled; + disabled = KvGetNum(g_hWepSounds, "disabled", 0); + + if (!disabled) + { + //CPrintToChat(client, "[StopSound] Saved entry for STEAMID({green}%s{default}) {green}successfully{default}.", SID); + KvSetNum(g_hWepSounds, "disabled", 1); + KvRewind(g_hWepSounds); + KeyValuesToFile(g_hWepSounds, g_sKVPATH); + + g_bStopSound[client] = true; + CReplyToCommand(client, "{green}[StopSound]{default} Weapon sounds {red}disabled{default} - {green}entry saved{default}."); + CheckHooks(); + + return Plugin_Handled; + } + else + { + //CPrintToChat(client, "[StopSound] Entry for STEAMID({green}%s{default}) {green}successfully deleted{default}.", SID); + KvDeleteThis(g_hWepSounds); + KvRewind(g_hWepSounds); + KeyValuesToFile(g_hWepSounds, g_sKVPATH); + + g_bStopSound[client] = false; + CReplyToCommand(client, "{green}[StopSound]{default} Weapon sounds {green}enabled{default} - {red}entry deleted{default}."); + CheckHooks(); + + return Plugin_Handled; + } + } + + KvRewind(g_hWepSounds); + } + else if (StrEqual(Arguments, "delete")) + { + KvRewind(g_hWepSounds); + + decl String:SID[32]; + GetClientAuthId(client, AuthId_Steam2, SID, sizeof(SID)); + + if (KvJumpToKey(g_hWepSounds, SID, false)) + { + g_bStopSound[client] = false; + CReplyToCommand(client, "{green}[StopSound]{default} Weapon sounds {green}enabled{default} - {red}entry deleted{default}."); + CheckHooks(); + + KvDeleteThis(g_hWepSounds); + KvRewind(g_hWepSounds); + KeyValuesToFile(g_hWepSounds, g_sKVPATH); + + return Plugin_Handled; + } + else + { + CPrintToChat(client, "{green}[StopSound]{default} Entry {red}not found{default}."); + return Plugin_Handled; + } + } + else + { + PrintToChat(client, "[SM] Usage sm_stopsound "); + return Plugin_Handled; + } + } + + g_bStopSound[client] = !g_bStopSound[client]; + CReplyToCommand(client, "{green}[StopSound]{default} Weapon sounds %s.", g_bStopSound[client] ? "{red}disabled{default}" : "{green}enabled{default}"); + CheckHooks(); + + return Plugin_Handled; +} + +public OnClientPutInServer(client) +{ + KvRewind(g_hWepSounds); + + decl String:SID[32]; + GetClientAuthId(client, AuthId_Steam2, SID, sizeof(SID)); + + if (KvJumpToKey(g_hWepSounds, SID, false)) + { + new disabled; + disabled = KvGetNum(g_hWepSounds, "disabled", 0); + + if (disabled) + { + g_bStopSound[client] = true; + } + } + + CheckHooks(); + KvRewind(g_hWepSounds); +} + +public OnClientDisconnect_Post(client) +{ + g_bStopSound[client] = false; + CheckHooks(); +} + +CheckHooks() +{ + new bool:bShouldHook = false; + + for (new i = 1; i <= MaxClients; i++) + { + if (g_bStopSound[i]) + { + bShouldHook = true; + break; + } + } + + // Fake (un)hook because toggling actual hooks will cause server instability. + g_bHooked = bShouldHook; +} + +public Action:Hook_NormalSound(clients[64], &numClients, String:sample[PLATFORM_MAX_PATH], &entity, &channel, &Float:volume, &level, &pitch, &flags) +{ + // Ignore non-weapon sounds. + if (!g_bHooked || !(strncmp(sample, "weapons", 7) == 0 || strncmp(sample[1], "weapons", 7) == 0)) + { + return Plugin_Continue; + } + + decl i, j; + + for (i = 0; i < numClients; i++) + { + if (g_bStopSound[clients[i]]) + { + // Remove the client from the array. + for (j = i; j < numClients - 1; j++) + { + clients[j] = clients[j + 1]; + } + + numClients--; + i--; + } + } + + return (numClients > 0) ? Plugin_Changed : Plugin_Stop; +} + +public Action:CSS_Hook_ShotgunShot(const String:te_name[], const Players[], numClients, Float:delay) +{ + if (!g_bHooked) + { + return Plugin_Continue; + } + + // Check which clients need to be excluded. + decl newClients[MaxClients], client, i; + new newTotal = 0; + + for (i = 0; i < numClients; i++) + { + client = Players[i]; + + if (!g_bStopSound[client]) + { + newClients[newTotal++] = client; + } + } + + // No clients were excluded. + if (newTotal == numClients) + { + return Plugin_Continue; + } + else if (newTotal == 0) // All clients were excluded and there is no need to broadcast. + { + return Plugin_Stop; + } + + // Re-broadcast to clients that still need it. + decl Float:vTemp[3]; + TE_Start("Shotgun Shot"); + TE_ReadVector("m_vecOrigin", vTemp); + TE_WriteVector("m_vecOrigin", vTemp); + TE_WriteFloat("m_vecAngles[0]", TE_ReadFloat("m_vecAngles[0]")); + TE_WriteFloat("m_vecAngles[1]", TE_ReadFloat("m_vecAngles[1]")); + TE_WriteNum("m_iWeaponID", TE_ReadNum("m_iWeaponID")); + TE_WriteNum("m_iMode", TE_ReadNum("m_iMode")); + TE_WriteNum("m_iSeed", TE_ReadNum("m_iSeed")); + TE_WriteNum("m_iPlayer", TE_ReadNum("m_iPlayer")); + TE_WriteFloat("m_fInaccuracy", TE_ReadFloat("m_fInaccuracy")); + TE_WriteFloat("m_fSpread", TE_ReadFloat("m_fSpread")); + TE_Send(newClients, newTotal, delay); + + return Plugin_Stop; +} + +public Action:DODS_Hook_FireBullets(const String:te_name[], const Players[], numClients, Float:delay) +{ + if (!g_bHooked) + { + return Plugin_Continue; + } + + // Check which clients need to be excluded. + decl newClients[MaxClients], client, i; + new newTotal = 0; + + for (i = 0; i < numClients; i++) + { + client = Players[i]; + + if (!g_bStopSound[client]) + { + newClients[newTotal++] = client; + } + } + + // No clients were excluded. + if (newTotal == numClients) + { + return Plugin_Continue; + } + else if (newTotal == 0)// All clients were excluded and there is no need to broadcast. + { + return Plugin_Stop; + } + + // Re-broadcast to clients that still need it. + decl Float:vTemp[3]; + TE_Start("FireBullets"); + TE_ReadVector("m_vecOrigin", vTemp); + TE_WriteVector("m_vecOrigin", vTemp); + TE_WriteFloat("m_vecAngles[0]", TE_ReadFloat("m_vecAngles[0]")); + TE_WriteFloat("m_vecAngles[1]", TE_ReadFloat("m_vecAngles[1]")); + TE_WriteNum("m_iWeaponID", TE_ReadNum("m_iWeaponID")); + TE_WriteNum("m_iMode", TE_ReadNum("m_iMode")); + TE_WriteNum("m_iSeed", TE_ReadNum("m_iSeed")); + TE_WriteNum("m_iPlayer", TE_ReadNum("m_iPlayer")); + TE_WriteFloat("m_flSpread", TE_ReadFloat("m_flSpread")); + TE_Send(newClients, newTotal, delay); + + return Plugin_Stop; +} \ No newline at end of file diff --git a/StopSound/scripting/include/morecolors.inc b/StopSound/scripting/include/morecolors.inc new file mode 120000 index 00000000..cd0d80e6 --- /dev/null +++ b/StopSound/scripting/include/morecolors.inc @@ -0,0 +1 @@ +../../../includes/morecolors.inc \ No newline at end of file diff --git a/SvGravityFix/scripting/SvGravityFix.sp b/SvGravityFix/scripting/SvGravityFix.sp new file mode 100644 index 00000000..feb3e5a8 --- /dev/null +++ b/SvGravityFix/scripting/SvGravityFix.sp @@ -0,0 +1,21 @@ +#pragma semicolon 1 + +#include +#include + +#pragma newdecls required + +public Plugin myinfo = +{ + name = "sv_gravity fix", + author = "BotoX", + description = "Resets sv_gravity at game_end", + version = "1.0", + url = "" +}; + +public void OnMapEnd() +{ + ConVar SvGravity = FindConVar("sv_gravity"); + SvGravity.IntValue = 800; +} diff --git a/WeaponCleaner/scripting/WeaponCleaner.sp b/WeaponCleaner/scripting/WeaponCleaner.sp new file mode 100644 index 00000000..6388bb6d --- /dev/null +++ b/WeaponCleaner/scripting/WeaponCleaner.sp @@ -0,0 +1,274 @@ +#pragma semicolon 1 + +#include +#include +#include + +#define TIMER_INTERVAL 1.0 +Handle g_hTimer = INVALID_HANDLE; + +ConVar g_CVar_MaxWeapons; +ConVar g_CVar_WeaponLifetime; + +new g_RealRoundStartedTime; +new g_MaxWeapons; +new g_MaxWeaponLifetime; + +#define MAX_WEAPONS MAXPLAYERS +new G_WeaponArray[MAX_WEAPONS][2]; + + +public Plugin myinfo = +{ + name = "WeaponCleaner", + author = "BotoX", + description = "Clean unneeded weapons", + version = "2.0", + url = "" +}; + +public void OnPluginStart() +{ + RegAdminCmd("sm_sweep", Command_CleanupWeapons, ADMFLAG_GENERIC, "Cleans up all the weapons on the map unless they have a HammerID attached to them."); + + g_CVar_MaxWeapons = CreateConVar("sm_weaponcleaner_max", "5", "The maximum amount of weapons allowed in the game.", 0, true, 0.0, true, MAX_WEAPONS - 1.0); + g_MaxWeapons = g_CVar_MaxWeapons.IntValue; + g_CVar_MaxWeapons.AddChangeHook(OnConVarChanged); + + g_CVar_WeaponLifetime = CreateConVar("sm_weaponcleaner_lifetime", "15", "The maximum amount of time in seconds a weapon is allowed in the game.", 0, true, 0.0); + g_MaxWeaponLifetime = g_CVar_WeaponLifetime.IntValue; + g_CVar_WeaponLifetime.AddChangeHook(OnConVarChanged); + + HookEvent("round_start", Event_RoundStart); + + AutoExecConfig(true, "plugin.WeaponCleaner"); +} + +public void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] newValue) +{ + if(convar == g_CVar_MaxWeapons) + { + if(StringToInt(newValue) < StringToInt(oldValue)) + { + // Need to shrink list and kill items + new d = StringToInt(oldValue) - StringToInt(newValue); + + // Kill items that don't have space anymore + for(new i = 0; d && i < g_MaxWeapons; i++) + { + if(!G_WeaponArray[i][0]) + continue; + + // Kill it + AcceptEntityInput(G_WeaponArray[0][0], "Kill"); + // This implicitly calls OnEntityDestroyed() which calls RemoveWeapon() + + // Move index backwards (since the list was modified by removing it) + i--; + d--; + } + } + g_MaxWeapons = StringToInt(newValue); + } + else if(convar == g_CVar_WeaponLifetime) + { + g_MaxWeaponLifetime = StringToInt(newValue); + CheckWeapons(); + } +} + +public void OnMapStart() +{ + if(g_hTimer != INVALID_HANDLE && CloseHandle(g_hTimer)) + g_hTimer = INVALID_HANDLE; + + g_hTimer = CreateTimer(TIMER_INTERVAL, Timer_CleanupWeapons, INVALID_HANDLE, TIMER_REPEAT); +} + +public void OnMapEnd() +{ + if(g_hTimer != INVALID_HANDLE && CloseHandle(g_hTimer)) + g_hTimer = INVALID_HANDLE; +} + +public void OnClientPutInServer(int client) +{ + SDKHook(client, SDKHook_WeaponDropPost, OnWeaponDrop); + SDKHook(client, SDKHook_WeaponEquipPost, OnWeaponEquip); +} + +public void OnClientDisconnect(int client) +{ + SDKUnhook(client, SDKHook_WeaponDropPost, OnWeaponDrop); + SDKUnhook(client, SDKHook_WeaponEquipPost, OnWeaponEquip); +} + +public void OnEntityCreated(int entity, const char[] classname) +{ + if(IsValidEntity(entity) && strncmp(classname, "weapon_", 7) == 0) + { + SDKHook(entity, SDKHook_Spawn, OnWeaponSpawned); + } +} + +public void OnEntityDestroyed(int entity) +{ + RemoveWeapon(entity); +} + +public void OnWeaponSpawned(int entity) +{ + new HammerID = GetEntProp(entity, Prop_Data, "m_iHammerID"); + // Should not be cleaned since it's a map spawned weapon + if(HammerID) + return; + + // Weapon doesn't belong to any player + if(GetEntPropEnt(entity, Prop_Data, "m_hOwnerEntity") == -1) + InsertWeapon(entity); +} + +public Action OnWeaponEquip(int client, int entity) +{ + if(!IsValidEntity(entity)) + return; + + new HammerID = GetEntProp(entity, Prop_Data, "m_iHammerID"); + // Should not be cleaned since it's a map spawned weapon + if(HammerID) + return; + + // Weapon should not be cleaned anymore + RemoveWeapon(entity); +} + +public Action OnWeaponDrop(int client, int entity) +{ + if(!IsValidEntity(entity)) + return; + + new HammerID = GetEntProp(entity, Prop_Data, "m_iHammerID"); + // Should not be cleaned since it's a map spawned weapon + if(HammerID) + return; + + // Kill all dropped weapons during mp_freezetime + if(GetTime() < g_RealRoundStartedTime) + { + // Kill it + AcceptEntityInput(entity, "Kill"); + return; + } + + // Weapon should be cleaned again + InsertWeapon(entity); +} + +bool InsertWeapon(int entity) +{ + // Try to find a free slot + for(new i = 0; i < g_MaxWeapons; i++) + { + if(G_WeaponArray[i][0]) + continue; + + // Found a free slot, add it here + G_WeaponArray[i][0] = entity; + G_WeaponArray[i][1] = GetTime(); + return true; + } + + // No free slot found + // Kill the first (oldest) item in the list + AcceptEntityInput(G_WeaponArray[0][0], "Kill"); + // This implicitly calls OnEntityDestroyed() which calls RemoveWeapon() + + // Add new weapon to the end of the list + G_WeaponArray[g_MaxWeapons - 1][0] = entity; + G_WeaponArray[g_MaxWeapons - 1][1] = GetTime(); + return true; +} + +bool RemoveWeapon(int entity) +{ + // Find the Weapon + for(new i = 0; i < g_MaxWeapons; i++) + { + if(G_WeaponArray[i][0] == entity) + { + G_WeaponArray[i][0] = 0; G_WeaponArray[i][1] = 0; + + // Move list items in front of this index back by one + for(new j = i + 1; j < g_MaxWeapons; j++) + { + G_WeaponArray[j - 1][0] = G_WeaponArray[j][0]; + G_WeaponArray[j - 1][1] = G_WeaponArray[j][1]; + } + + // Reset last list item + G_WeaponArray[g_MaxWeapons - 1][0] = 0; + G_WeaponArray[g_MaxWeapons - 1][1] = 0; + + return true; + } + } + return false; +} + +bool CheckWeapons() +{ + for(new i = 0; i < g_MaxWeapons; i++) + { + if(!G_WeaponArray[i][0]) + continue; + + if(GetTime() - G_WeaponArray[i][1] >= g_MaxWeaponLifetime) + { + // Kill it + AcceptEntityInput(G_WeaponArray[i][0], "Kill"); + // This implicitly calls OnEntityDestroyed() which calls RemoveWeapon() + + // Move index backwards (since the list was modified by removing it) + i--; + } + } + return true; +} + +void CleanupWeapons() +{ + for(new i = 0; i < g_MaxWeapons; i++) + { + if(!G_WeaponArray[i][0]) + continue; + + // Kill it + AcceptEntityInput(G_WeaponArray[i][0], "Kill"); + // This implicitly calls OnEntityDestroyed() which calls RemoveWeapon() + + // Move index backwards (since the list was modified by removing it) + i--; + } +} + +public Action Event_RoundStart(Handle:event, const char[] name, bool:dontBroadcast) +{ + for(new i = 0; i < MAX_WEAPONS; i++) + { + G_WeaponArray[i][0] = 0; G_WeaponArray[i][1] = 0; + } + g_RealRoundStartedTime = GetTime() + GetConVarInt(FindConVar("mp_freezetime")); +} + +public Action Timer_CleanupWeapons(Handle:timer) +{ + CheckWeapons(); +} + +public Action Command_CleanupWeapons(client, args) +{ + CleanupWeapons(); + + LogAction(client, -1, "%L performed a weapons cleanup", client); + PrintToChat(client, "[SM] Weapons cleaned successfully!"); +} diff --git a/custom-chatcolors/scripting/custom-chatcolors.sp b/custom-chatcolors/scripting/custom-chatcolors.sp new file mode 100644 index 00000000..d28d2e30 --- /dev/null +++ b/custom-chatcolors/scripting/custom-chatcolors.sp @@ -0,0 +1,3307 @@ +#pragma semicolon 1 + +#include +#include +#include +#include +//#undef REQUIRE_PLUGIN +#include + +#define PLUGIN_VERSION "5.3.0" + +public Plugin:myinfo = +{ + name = "Custom Chat Colors & Tags & Allchat", + author = "Dr. McKay, edit by id/Obus, BotoX", + description = "Processes chat and provides colors & custom tags & allchat & chat ignoring", + version = PLUGIN_VERSION, + url = "http://www.doctormckay.com" +}; + +new Handle:colorForward; +new Handle:nameForward; +new Handle:tagForward; +new Handle:applicationForward; +new Handle:messageForward; +new Handle:preLoadedForward; +new Handle:loadedForward; +new Handle:configReloadedForward; +new Handle:g_hCoolDown = INVALID_HANDLE; +new Handle:g_hGreenText = INVALID_HANDLE; +//new Handle:g_hAdminMenu = INVALID_HANDLE; + +new String:g_sTag[MAXPLAYERS + 1][64]; +new String:g_sTagColor[MAXPLAYERS + 1][12]; +new String:g_sUsernameColor[MAXPLAYERS + 1][12]; +new String:g_sChatColor[MAXPLAYERS + 1][12]; + +new String:g_sDefaultTag[MAXPLAYERS + 1][32]; +new String:g_sDefaultTagColor[MAXPLAYERS + 1][12]; +new String:g_sDefaultUsernameColor[MAXPLAYERS + 1][12]; +new String:g_sDefaultChatColor[MAXPLAYERS + 1][12]; +new const String:g_sColorsArray[120][2][32] = { {"aliceblue", "F0F8FF" }, { "aqua", "00FFFF" }, { "aquamarine", "7FFFD4" }, { "azure", "007FFF" }, { "beige", "F5F5DC" }, { "black", "000000" }, { "blue", "99CCFF" }, { "blueviolet", "8A2BE2" }, { "brown", "A52A2A" }, { "burlywood", "DEB887" }, { "cadetblue", "5F9EA0" }, { "chocolate", "D2691E" }, { "corrupted", "A32C2E" }, { "crimson", "DC143C" }, { "cyan", "00FFFF" }, { "darkblue", "00008B" }, { "darkcyan", "008B8B" }, { "darkgoldenrod", "B8860B" }, { "darkgray", "A9A9A9" }, { "darkgrey", "A9A9A9" }, { "darkgreen", "006400" }, { "darkkhaki", "BDB76B" }, { "darkmagenta", "8B008B" }, { "darkolivegreen", "556B2F" }, { "darkorange", "FF8C00" }, { "darkorchid", "9932CC" }, { "darkred", "8B0000" }, { "darksalmon", "E9967A" }, { "darkseagreen", "8FBC8F" }, { "darkslateblue", "483D8B" }, { "darkturquoise", "00CED1" }, { "darkviolet", "9400D3" }, { "deeppink", "FF1493" }, { "deepskyblue", "00BFFF" }, { "dimgray", "696969" }, { "dodgerblue", "1E90FF" }, { "firebrick", "B22222" }, { "floralwhite", "FFFAF0" }, { "forestgreen", "228B22" }, { "frozen", "4983B3" }, { "fuchsia", "FF00FF" }, { "fullblue", "0000FF" }, { "fullred", "FF0000" }, { "ghostwhite", "F8F8FF" }, { "gold", "FFD700" }, { "gray", "CCCCCC" }, { "green", "3EFF3E" }, { "greenyellow", "ADFF2F" }, { "hotpink", "FF69B4" }, { "indianred", "CD5C5C" }, { "indigo", "4B0082" }, { "ivory", "FFFFF0" }, { "khaki", "F0E68C" }, { "lightblue", "ADD8E6" }, { "lightcoral", "F08080" }, { "lightcyan", "E0FFFF" }, { "lightgoldenrodyellow", "FAFAD2" }, { "lightgray", "D3D3D3" }, { "lightgrey", "D3D3D3" }, { "lightgreen", "99FF99" }, { "lightpink", "FFB6C1" }, { "lightsalmon", "FFA07A" }, { "lightseagreen", "20B2AA" }, { "lightskyblue", "87CEFA" }, { "lightslategray", "778899" }, { "lightslategrey", "778899" }, { "lightsteelblue", "B0C4DE" }, { "lightyellow", "FFFFE0" }, { "lime", "00FF00" }, { "limegreen", "32CD32" }, { "magenta", "FF00FF" }, { "maroon", "800000" }, { "mediumaquamarine", "66CDAA" }, { "mediumblue", "0000CD" }, { "mediumorchid", "BA55D3" }, { "mediumturquoise", "48D1CC" }, { "mediumvioletred", "C71585" }, { "midnightblue", "191970" }, { "mintcream", "F5FFFA" }, { "mistyrose", "FFE4E1" }, { "moccasin", "FFE4B5" }, { "navajowhite", "FFDEAD" }, { "navy", "000080" }, { "oldlace", "FDF5E6" }, { "olive", "9EC34F" }, { "olivedrab", "6B8E23" }, { "orange", "FFA500" }, { "orangered", "FF4500" }, { "orchid", "DA70D6" }, { "palegoldenrod", "EEE8AA" }, { "palegreen", "98FB98" }, { "palevioletred", "D87093" }, { "pink", "FFC0CB" }, { "plum", "DDA0DD" }, { "powderblue", "B0E0E6" }, { "purple", "800080" }, { "red", "FF4040" }, { "rosybrown", "BC8F8F" }, { "royalblue", "4169E1" }, { "saddlebrown", "8B4513" }, { "salmon", "FA8072" }, { "sandybrown", "F4A460" }, { "seagreen", "2E8B57" }, { "seashell", "FFF5EE" }, { "silver", "C0C0C0" }, { "skyblue", "87CEEB" }, { "slateblue", "6A5ACD" }, { "slategray", "708090" }, { "slategrey", "708090" }, { "snow", "FFFAFA" }, { "springgreen", "00FF7F" }, { "steelblue", "4682B4" }, { "tan", "D2B48C" }, { "teal", "008080" }, { "tomato", "FF6347" }, { "turquoise", "40E0D0" }, { "violet", "EE82EE" }, { "white", "FFFFFF" }, { "yellow", "FFFF00" }, { "yellowgreen", "9ACD32" } }; //you want colors? here bomb array fak u + +new String:g_sPath[PLATFORM_MAX_PATH]; +new String:g_sBanPath[PLATFORM_MAX_PATH]; + +new bool:g_bWaitingForChatInput[MAXPLAYERS + 1]; +new bool:g_bTagToggled[MAXPLAYERS + 1]; +new String:g_sReceivedChatInput[MAXPLAYERS + 1][64]; +new String:g_sInputType[MAXPLAYERS + 1][32]; +new String:g_sATargetSID[MAXPLAYERS + 1][64]; +new g_iATarget[MAXPLAYERS + 1]; + +new Handle:g_hConfigFile; +new Handle:g_hBanFile; + +new g_msgAuthor; +new bool:g_msgIsChat; +new String:g_msgName[128]; +new String:g_msgSender[128]; +new String:g_msgText[512]; +new String:g_msgFinal[1024]; +new bool:g_msgIsTeammate; + +new bool:g_Ignored[(MAXPLAYERS + 1) * (MAXPLAYERS + 1)] = {false, ...}; + +public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max) +{ + MarkNativeAsOptional("Updater_AddPlugin"); + + CreateNative("CCC_GetColor", Native_GetColor); + CreateNative("CCC_SetColor", Native_SetColor); + CreateNative("CCC_GetTag", Native_GetTag); + CreateNative("CCC_SetTag", Native_SetTag); + CreateNative("CCC_ResetColor", Native_ResetColor); + CreateNative("CCC_ResetTag", Native_ResetTag); + + CreateNative("CCC_UpdateIgnoredArray", Native_UpdateIgnoredArray); + + RegPluginLibrary("ccc"); + + return APLRes_Success; +} + +public OnPluginStart() +{ + LoadTranslations("common.phrases"); + LoadTranslations("allchat.phrases"); + + //new Handle:g_hTemporary = INVALID_HANDLE; + //if(LibraryExists("adminmenu") && ((g_hTemporary = GetAdminTopMenu()) != INVALID_HANDLE)) + //{ + // OnAdminMenuReady(g_hTemporary); + //} + + new UserMsg:SayText2 = GetUserMessageId("SayText2"); + + if (SayText2 == INVALID_MESSAGE_ID) + { + SetFailState("This game doesn't support SayText2 user messages."); + } + + HookUserMessage(SayText2, Hook_UserMessage, true); + HookEvent("player_say", Event_PlayerSay); + + RegAdminCmd("sm_reloadccc", Command_ReloadConfig, ADMFLAG_CONFIG, "Reloads Custom Chat Colors config file"); + RegAdminCmd("sm_forcetag", Command_ForceTag, ADMFLAG_CHEATS, "Forcefully changes a clients custom tag"); + RegAdminCmd("sm_forcetagcolor", Command_ForceTagColor, ADMFLAG_CHEATS, "Forcefully changes a clients custom tag color"); + RegAdminCmd("sm_forcenamecolor", Command_ForceNameColor, ADMFLAG_CHEATS, "Forcefully changes a clients name color"); + RegAdminCmd("sm_forcetextcolor", Command_ForceTextColor, ADMFLAG_CHEATS, "Forcefully changes a clients chat text color"); + RegAdminCmd("sm_cccreset", Command_CCCReset, ADMFLAG_SLAY, "Resets a users custom tag, tag color, name color and chat text color"); + RegAdminCmd("sm_cccban", Command_CCCBan, ADMFLAG_SLAY, "Bans a user from changing his custom tag, tag color, name color and chat text color"); + RegAdminCmd("sm_cccunban", Command_CCCUnban, ADMFLAG_SLAY, "Unbans a user and allows for change of his tag, tag color, name color and chat text color"); + RegAdminCmd("sm_tagmenu", Command_TagMenu, ADMFLAG_CUSTOM1, "Shows the main \"tag & colors\" menu"); + RegAdminCmd("sm_tag", Command_SetTag, ADMFLAG_CUSTOM1, "Changes your custom tag"); + RegAdminCmd("sm_cleartag", Command_ClearTag, ADMFLAG_CUSTOM1, "Clears your custom tag"); + RegAdminCmd("sm_tagcolor", Command_SetTagColor, ADMFLAG_CUSTOM1, "Changes the color of your custom tag"); + RegAdminCmd("sm_cleartagcolor", Command_ClearTagColor, ADMFLAG_CUSTOM1, "Clears the color from your custom tag"); + RegAdminCmd("sm_namecolor", Command_SetNameColor, ADMFLAG_CUSTOM1, "Changes the color of your name"); + RegAdminCmd("sm_clearnamecolor", Command_ClearNameColor, ADMFLAG_CUSTOM1, "Clears the color from your name"); + RegAdminCmd("sm_textcolor", Command_SetTextColor, ADMFLAG_CUSTOM1, "Changes the color of your chat text"); + RegAdminCmd("sm_chatcolor", Command_SetTextColor, ADMFLAG_CUSTOM1, "Changes the color of your chat text"); + RegAdminCmd("sm_cleartextcolor", Command_ClearTextColor, ADMFLAG_CUSTOM1, "Clears the color from your chat text"); + RegAdminCmd("sm_clearchatcolor", Command_ClearTextColor, ADMFLAG_CUSTOM1, "Clears the color from your chat text"); + RegConsoleCmd("sm_toggletag", Command_ToggleTag, "Toggles whether or not your tag and colors show in the chat"); + + AddCommandListener(Command_Say, "say"); + AddCommandListener(Command_Say, "say_team"); + //RegConsoleCmd("sm_test", Command_Test); + + if (g_hCoolDown != INVALID_HANDLE) + CloseHandle(g_hCoolDown); + + if (g_hGreenText != INVALID_HANDLE) + CloseHandle(g_hGreenText); + + g_hCoolDown = CreateConVar("sm_ccccooldown", "1", "Tag/Color changes cooldown period (in seconds)", FCVAR_NOTIFY|FCVAR_REPLICATED, true, 1.0); + g_hGreenText = CreateConVar("sm_cccgreentext", "1", "Enables greentexting (First chat character must be \">\")"); + + colorForward = CreateGlobalForward("CCC_OnChatColor", ET_Event, Param_Cell); + nameForward = CreateGlobalForward("CCC_OnNameColor", ET_Event, Param_Cell); + tagForward = CreateGlobalForward("CCC_OnTagApplied", ET_Event, Param_Cell); + applicationForward = CreateGlobalForward("CCC_OnColor", ET_Event, Param_Cell, Param_String, Param_Cell); + messageForward = CreateGlobalForward("CCC_OnChatMessage", ET_Ignore, Param_Cell, Param_String, Param_Cell); + preLoadedForward = CreateGlobalForward("CCC_OnUserConfigPreLoaded", ET_Event, Param_Cell); + loadedForward = CreateGlobalForward("CCC_OnUserConfigLoaded", ET_Ignore, Param_Cell); + configReloadedForward = CreateGlobalForward("CCC_OnConfigReloaded", ET_Ignore); + + LoadConfig(); +} + +LoadConfig() +{ + if (g_hConfigFile != INVALID_HANDLE) + { + CloseHandle(g_hConfigFile); + } + + if (g_hBanFile != INVALID_HANDLE) + { + CloseHandle(g_hBanFile); + } + + g_hConfigFile = CreateKeyValues("admin_colors"); + g_hBanFile = CreateKeyValues("restricted_users"); + + BuildPath(Path_SM, g_sPath, sizeof(g_sPath), "configs/custom-chatcolors.cfg"); + BuildPath(Path_SM, g_sBanPath, sizeof(g_sBanPath), "configs/custom-chatcolorsbans.cfg"); + + if (!FileToKeyValues(g_hConfigFile, g_sPath)) + { + SetFailState("[CCC] Config file missing"); + } + + if (!FileToKeyValues(g_hBanFile, g_sBanPath)) + { + SetFailState("[CCC] Ban file missing"); + } + + for (new i = 1; i <= MaxClients; i++) + { + if (!IsClientInGame(i) || IsFakeClient(i)) + { + continue; + } + + ClearValues(i); + OnClientPostAdminCheck(i); + } +} + +/*public OnLibraryRemoved(const String:name[]) +{ + if (StrEqual(name, "adminmenu")) + { + g_hAdminMenu = INVALID_HANDLE; + } +} + +public OnAdminMenuReady(Handle:CCCAMenu) +{ + if (CCCAMenu == g_hAdminMenu) + { + return; + } + + g_hAdminMenu = CCCAMenu; + new TopMenuObject:MenuObject = AddToTopMenu(g_hAdminMenu, "CCCCmds", TopMenuObject_Category, Handle_Commands, INVALID_TOPMENUOBJECT); + + if (MenuObject == INVALID_TOPMENUOBJECT) + { + return; + } + + AddToTopMenu(g_hAdminMenu, "CCCReset", TopMenuObject_Item, Handle_AMenuReset, MenuObject, "sm_cccreset", ADMFLAG_SLAY); + AddToTopMenu(g_hAdminMenu, "CCCBan", TopMenuObject_Item, Handle_AMenuBan, MenuObject, "sm_cccban", ADMFLAG_SLAY); + AddToTopMenu(g_hAdminMenu, "CCCUnBan", TopMenuObject_Item, Handle_AMenuUnBan, MenuObject, "sm_cccunban", ADMFLAG_SLAY); +}*/ //figure out why reloading the plugin makes this admin menu take control of other admin menus + +public Action:Command_Test(client, args) +{ + decl String:Arg[64]; + decl String:SArg[64]; + decl String:TArg[64]; + new color; + GetCmdArg(1, Arg, sizeof(SArg)); + GetCmdArg(2, SArg, sizeof(SArg)); + GetCmdArg(3, TArg, sizeof(SArg)); + color |= ((StringToInt(Arg, 10) & 0xFF) << 16); + color |= ((StringToInt(SArg, 10) & 0xFF) << 8); + color |= ((StringToInt(TArg, 10) & 0xFF) << 0); + + if (IsValidHex(Arg)) + { + ReplaceString(Arg, 64, "#", ""); + PrintToChat(client, "%02X, %04X, %06X", Arg, Arg, Arg); + new Hex, r, g, b; + StringToIntEx(Arg, Hex, 16); + r = ((Hex >> 16) & 0xFF); + g = ((Hex >> 8) & 0xFF); + b = ((Hex >> 0) & 0xFF); + + + PrintToChat(client, "Hex = %s, R = %i, G = %i, B = %i", Arg, r, g, b); + } + else + { + PrintToChat(client, "Arg: %d, SArg: %d, TArg: %d", StringToInt(Arg),StringToInt(SArg),StringToInt(TArg)); + PrintToChat(client, "%06X", color); + //PrintToChat(client, "test %X, r = %i, g = %i, b = %i", test, r, g, b); + } +} + +bool:MakeStringPrintable(String:str[], str_len_max, const String:empty[]) //function taken from Forlix FloodCheck (http://forlix.org/gameaddons/floodcheck.shtml) +{ + new r = 0; + new w = 0; + new bool:modified = false; + new bool:nonspace = false; + new bool:addspace = false; + + if (str[0]) + do + { + if(str[r] < '\x20') + { + modified = true; + + if((str[r] == '\n' + || str[r] == '\t') + && w > 0 + && str[w-1] != '\x20') + addspace = true; + } + else + { + if(str[r] != '\x20') + { + nonspace = true; + + if(addspace) + str[w++] = '\x20'; + } + + addspace = false; + str[w++] = str[r]; + } + } + while(str[++r]); + str[w] = '\0'; + + if (!nonspace) + { + modified = true; + strcopy(str, str_len_max, empty); + } + + return (modified); +} + +bool:SingularOrMultiple(int num) +{ + if (num > 1 || num == 0) + { + return true; + } + + return false; +} + +bool:HasFlag(client, AdminFlag:ADMFLAG) +{ + new AdminId:Admin = GetUserAdmin(client); + + if (Admin != INVALID_ADMIN_ID && GetAdminFlag(Admin, ADMFLAG, Access_Effective) == true) + { + return true; + } + + return false; +} + +bool:NoFilter(String:arg[64]) +{ + if (StrEqual(arg[0], "@cts") || StrEqual(arg[0], "@ct") || StrEqual(arg[0], "@all") || StrEqual(arg[0], "@alive") || StrEqual(arg[0], "@admins") || StrEqual(arg[0], "@dead") || StrEqual(arg[0], "@humans") || StrEqual(arg[0], "@t") || StrEqual(arg[0], "@ts") || StrEqual(arg[0], "@!me")) + { + return true; + } + + return false; +} + +int ForceColor(client, String:Key[64]) +{ + decl String:arg[64]; + decl String:col[64]; + GetCmdArg(1, arg, sizeof(arg)); + GetCmdArg(2, col, sizeof(col)); + + decl String:target_name[MAX_TARGET_LENGTH]; + decl target_list[MAXPLAYERS], target_count, bool:tn_is_ml; + + if (IsValidRGBNum(col)) + { + new String:g[8]; + new String:b[8]; + GetCmdArg(3, g, sizeof(g)); + GetCmdArg(4, b, sizeof(b)); + new hex; + hex |= ((StringToInt(col) & 0xFF) << 16); + hex |= ((StringToInt(g) & 0xFF) << 8); + hex |= ((StringToInt(b) & 0xFF) << 0); + + Format(col, 64, "#%06X", hex); + } + + if (NoFilter(arg)) + { + ReplyToCommand(client, "[SM] This command only supports special filters <@aim|@me>."); + return 1; + } + + if ((target_count = ProcessTargetString(arg, client, target_list, MAXPLAYERS, COMMAND_FILTER_CONNECTED|COMMAND_FILTER_NO_BOTS, target_name, sizeof(target_name), tn_is_ml)) <= 0) + { + ReplyToTargetError(client, target_count); + return 2; + } + + for (new i = 0; i < target_count; i++) + { + decl String:SID[64]; + GetClientAuthId(target_list[i], AuthId_Steam2, SID, sizeof(SID)); + + if (IsValidHex(col)) + SetColor(SID, Key, col, -1, true, true); + else + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}]{default} Invalid HEX|RGB color code given."); + } + + return 0; +} + +bool:IsValidRGBNum(String:arg[]) +{ + if (SimpleRegexMatch(arg, "^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$") == 2) + { + return true; + } + + return false; +} + +bool:IsValidHex(String:arg[]) +{ + if (SimpleRegexMatch(arg, "^(#?)([A-Fa-f0-9]{6})$") == 0) + { + return false; + } + + return true; +} + +bool:SetColor(String:SID[64], String:Key[64], String:HEX[64], client, bool:IgnoreCooldown=false, bool:IgnoreBan=false) +{ + if (!IgnoreBan) + { + KvRewind(g_hBanFile); + + if (KvJumpToKey(g_hBanFile, SID)) + { + if (KvGetNum(g_hBanFile, "length") == 0) + { + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}]{default} You are currently {red}banned{default} from changing your {green}%s{default}.", Key); + return false; + } + else if (KvGetNum(g_hBanFile, "length") < GetTime()) + { + KvDeleteThis(g_hBanFile); + } + else + { + decl String:TimeBuffer[64]; + int tstamp = KvGetNum(g_hBanFile, "length"); + tstamp = (tstamp - GetTime()); + + int days = (tstamp / 86400); + int hrs = ((tstamp / 3600) % 24); + int mins = ((tstamp / 60) % 60); + int sec = (tstamp % 60); + + if (tstamp > 86400) + { + Format(TimeBuffer, sizeof(TimeBuffer), "%d %s, %d %s, %d %s, %d %s", days, SingularOrMultiple(days) ? "Days" : "Day", hrs, SingularOrMultiple(hrs) ? "Hours" : "Hour", mins, SingularOrMultiple(mins) ? "Minutes" : "Minute", sec, SingularOrMultiple(sec) ? "Seconds" : "Second"); + } + else if (tstamp > 3600) + { + Format(TimeBuffer, sizeof(TimeBuffer), "%d %s, %d %s, %d %s", hrs, SingularOrMultiple(hrs) ? "Hours" : "Hour", mins, SingularOrMultiple(mins) ? "Minutes" : "Minute", sec, SingularOrMultiple(sec) ? "Seconds" : "Second"); + } + else if (tstamp > 60) + { + Format(TimeBuffer, sizeof(TimeBuffer), "%d %s, %d %s", mins, SingularOrMultiple(mins) ? "Minutes" : "Minute", sec, SingularOrMultiple(sec) ? "Seconds" : "Second"); + } + else + { + Format(TimeBuffer, sizeof(TimeBuffer), "%d %s", sec, SingularOrMultiple(sec) ? "Seconds" : "Second"); + } + + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}]{default} You are currently {red}banned{default} from changing your {green}%s{default}. (Time remaining: {green}%s{default})", Key, TimeBuffer); + return false; + } + } + } + + if (!IgnoreCooldown) + { + KvRewind(g_hConfigFile); + + if (KvJumpToKey(g_hConfigFile, SID, true)) + { + decl String:KeyCD[64]; + Format(KeyCD, sizeof(KeyCD), "%scd", Key); + + if (KvGetNum(g_hConfigFile, KeyCD) < GetTime()) + { + KvSetNum(g_hConfigFile, KeyCD, GetTime() + GetConVarInt(g_hCoolDown)); + } + else + { + decl String:TimeBuffer[64]; + int tstamp = KvGetNum(g_hConfigFile, KeyCD); + tstamp = (tstamp - GetTime()); + int hrs = (tstamp / 3600); + int mins = ((tstamp / 60) % 60); + int sec = (tstamp % 60); + + if (tstamp > 3600) + { + Format(TimeBuffer, sizeof(TimeBuffer), "%d %s, %d %s, %d %s", hrs, SingularOrMultiple(hrs) ? "Hours" : "Hour", mins, SingularOrMultiple(mins) ? "Minutes" : "Minute", sec, SingularOrMultiple(sec) ? "Seconds" : "Second"); + } + else if (tstamp > 60) + { + Format(TimeBuffer, sizeof(TimeBuffer), "%d %s, %d %s", mins, SingularOrMultiple(mins) ? "Minutes" : "Minute", sec, SingularOrMultiple(sec) ? "Seconds" : "Second"); + } + else + { + Format(TimeBuffer, sizeof(TimeBuffer), "%d %s", sec, SingularOrMultiple(sec) ? "Seconds" : "Second"); + } + //Format(TimeBuffer, sizeof(TimeBuffer), "%d Hours, %d Minutes, %d Seconds", hrs, mins, sec); + + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}]{default} Time remaining until you can change your {green}%s{default}: {green}%s", Key, TimeBuffer); + return false; + } + } + } + + KvRewind(g_hConfigFile); + KvRewind(g_hBanFile); + + if (KvJumpToKey(g_hConfigFile, SID, true)) + { + KvSetString(g_hConfigFile, Key, HEX); + } + + KvRewind(g_hConfigFile); + KeyValuesToFile(g_hConfigFile, g_sPath); + KeyValuesToFile(g_hBanFile, g_sBanPath); + + LoadConfig(); + Call_StartForward(configReloadedForward); + Call_Finish(); + + return true; +} + +bool:SetTag(String:SID[64], String:text[64], client, bool:IgnoreCooldown=false, bool:IgnoreBan=false) +{ + if (!IgnoreBan) + { + KvRewind(g_hBanFile); + + if (KvJumpToKey(g_hBanFile, SID)) + { + if (KvGetNum(g_hBanFile, "length") == 0) + { + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}]{default} You are currently {red}banned{default} from changing your {green}tag{default}."); + return false; + } + else if (KvGetNum(g_hBanFile, "length") < GetTime()) + { + KvDeleteThis(g_hBanFile); + } + else + { + decl String:TimeBuffer[128]; + int tstamp = KvGetNum(g_hBanFile, "length"); + tstamp = (tstamp - GetTime()); + + int days = (tstamp / 86400); + int hrs = ((tstamp / 3600) % 24); + int mins = ((tstamp / 60) % 60); + int sec = (tstamp % 60); + + if (tstamp > 86400) + { + Format(TimeBuffer, sizeof(TimeBuffer), "%d %s, %d %s, %d %s, %d %s", days, SingularOrMultiple(days) ? "Days" : "Day", hrs, SingularOrMultiple(hrs) ? "Hours" : "Hour", mins, SingularOrMultiple(mins) ? "Minutes" : "Minute", sec, SingularOrMultiple(sec) ? "Seconds" : "Second"); + } + else if (tstamp > 3600) + { + Format(TimeBuffer, sizeof(TimeBuffer), "%d %s, %d %s, %d %s", hrs, SingularOrMultiple(hrs) ? "Hours" : "Hour", mins, SingularOrMultiple(mins) ? "Minutes" : "Minute", sec, SingularOrMultiple(sec) ? "Seconds" : "Second"); + } + else if (tstamp > 60) + { + Format(TimeBuffer, sizeof(TimeBuffer), "%d %s, %d %s", mins, SingularOrMultiple(mins) ? "Minutes" : "Minute", sec, SingularOrMultiple(sec) ? "Seconds" : "Second"); + } + else + { + Format(TimeBuffer, sizeof(TimeBuffer), "%d %s", sec, SingularOrMultiple(sec) ? "Seconds" : "Second"); + } + + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}]{default} You are currently {red}banned{default} from changing your {green}tag{default}. (Time remaining: {green}%s{default})", TimeBuffer); + return false; + } + } + } + + if (!IgnoreCooldown) + { + KvRewind(g_hConfigFile); + + if (KvJumpToKey(g_hConfigFile, SID, true)) + { + if (KvGetNum(g_hConfigFile, "tagcd") < GetTime()) + { + KvSetNum(g_hConfigFile, "tagcd", GetTime() + GetConVarInt(g_hCoolDown)); + } + else + { + decl String:TimeBuffer[128]; + int tstamp = KvGetNum(g_hConfigFile, "tagcd"); + tstamp = (tstamp - GetTime()); + int hrs = (tstamp / 3600); + int mins = ((tstamp / 60) % 60); + int sec = (tstamp % 60); + + if (tstamp > 3600) + { + Format(TimeBuffer, sizeof(TimeBuffer), "%d %s, %d %s, %d %s", hrs, SingularOrMultiple(hrs) ? "Hours" : "Hour", mins, SingularOrMultiple(mins) ? "Minutes" : "Minute", sec, SingularOrMultiple(sec) ? "Seconds" : "Second"); + } + else if (tstamp > 60) + { + Format(TimeBuffer, sizeof(TimeBuffer), "%d %s, %d %s", mins, SingularOrMultiple(mins) ? "Minutes" : "Minute", sec, SingularOrMultiple(sec) ? "Seconds" : "Second"); + } + else + { + Format(TimeBuffer, sizeof(TimeBuffer), "%d %s", sec, SingularOrMultiple(sec) ? "Seconds" : "Second"); + } + //Format(TimeBuffer, sizeof(TimeBuffer), "%d Hours, %d Minutes, %d Seconds", hrs, mins, sec); + + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}]{default} Time remaining until you can change your {green}tag{default}: {green}%s", TimeBuffer); + return false; + } + } + } + + KvRewind(g_hConfigFile); + KvRewind(g_hBanFile); + + if (KvJumpToKey(g_hConfigFile, SID, true)) + { + if (StrEqual(text, "")) + { + KvSetString(g_hConfigFile, "tag", ""); + } + else + { + decl String:FormattedText[64]; + VFormat(FormattedText, sizeof(FormattedText), "%.24s ", 2); + + KvSetString(g_hConfigFile, "tag", FormattedText); + } + } + + KvRewind(g_hConfigFile); + KeyValuesToFile(g_hConfigFile, g_sPath); + KeyValuesToFile(g_hBanFile, g_sBanPath); + + LoadConfig(); + Call_StartForward(configReloadedForward); + Call_Finish(); + + return true; +} + +bool:RemoveCCC(String:SID[64]) +{ + KvRewind(g_hConfigFile); + + if (KvJumpToKey(g_hConfigFile, SID, false)) + { + KvDeleteThis(g_hConfigFile); + } + else + { + return false; + } + + KvRewind(g_hConfigFile); + KeyValuesToFile(g_hConfigFile, g_sPath); + + LoadConfig(); + Call_StartForward(configReloadedForward); + Call_Finish(); + + return true; +} + +bool:BanCCC(String:SID[64], client, target, String:Time[128]) +{ + KvRewind(g_hBanFile); + + if (KvJumpToKey(g_hBanFile, SID, false)) + { + KvDeleteThis(g_hBanFile); + KvRewind(g_hBanFile); + } + + if (KvJumpToKey(g_hBanFile, SID, true)) + { + new time = StringToInt(Time); + time = GetTime() + (time * 60); + + if (StringToInt(Time) == 0) + { + time = 0; + } + + KvSetNum(g_hBanFile, "length", time); + CPrintToChatAll("{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} {green}%N{red} restricted {green}%N{default} from modifying his tag/color settings", client, target); + } + + KvRewind(g_hBanFile); + KeyValuesToFile(g_hBanFile, g_sBanPath); + return true; +} + +bool:UnBanCCC(String:SID[64], client, target) +{ + KvRewind(g_hBanFile); + + if (KvJumpToKey(g_hBanFile, SID, false)) + { + CPrintToChatAll("{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} {green}%N{olive} unrestricted {green}%N{default} from modifying his tag/color settings", client, target); + KvDeleteThis(g_hBanFile); + } + else + { + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Client not restricted"); + return false; + } + + KvRewind(g_hBanFile); + KeyValuesToFile(g_hBanFile, g_sBanPath); + return true; +} + +// .d8888b. .d88888b. 888b d888 888b d888 d8888 888b 888 8888888b. .d8888b. +// d88P Y88b d88P" "Y88b 8888b d8888 8888b d8888 d88888 8888b 888 888 "Y88b d88P Y88b +// 888 888 888 888 88888b.d88888 88888b.d88888 d88P888 88888b 888 888 888 Y88b. +// 888 888 888 888Y88888P888 888Y88888P888 d88P 888 888Y88b 888 888 888 "Y888b. +// 888 888 888 888 Y888P 888 888 Y888P 888 d88P 888 888 Y88b888 888 888 "Y88b. +// 888 888 888 888 888 Y8P 888 888 Y8P 888 d88P 888 888 Y88888 888 888 "888 +// Y88b d88P Y88b. .d88P 888 " 888 888 " 888 d8888888888 888 Y8888 888 .d88P Y88b d88P +// "Y8888P" "Y88888P" 888 888 888 888 d88P 888 888 Y888 8888888P" "Y8888P" +// + +public Action:Command_ReloadConfig(client, args) +{ + LoadConfig(); + + LogAction(client, -1, "Reloaded Custom Chat Colors config file"); + ReplyToCommand(client, "[CCC] Reloaded config file."); + Call_StartForward(configReloadedForward); + Call_Finish(); + return Plugin_Handled; +} + +public Action:Command_TagMenu(client, args) +{ + if (client == 0) + { + PrintToServer("[CCC] Cannot use command from server console"); + return Plugin_Handled; + } + + Menu_Main(client); + return Plugin_Handled; +} + +public Action:Command_Say(client, const String:command[], argc) +{ + if (g_bWaitingForChatInput[client]) + { + decl String:text[64]; + decl String:SID[64]; + GetClientAuthId(client, AuthId_Steam2, SID, sizeof(SID)); + GetCmdArgString(text, sizeof(text)); + + if (text[strlen(text)-1] == '"') + { + text[strlen(text)-1] = '\0'; + } + + strcopy(g_sReceivedChatInput[client], sizeof(g_sReceivedChatInput[]), text[1]); + g_bWaitingForChatInput[client] = false; + ReplaceString(g_sReceivedChatInput[client], sizeof(g_sReceivedChatInput), "\"", "'"); + + if (!HasFlag(client, Admin_Cheats) && !StrEqual(SID, "STEAM_0:0:50540848", true)) + { + if (MakeStringPrintable(text, sizeof(text), "")) + { + return Plugin_Handled; + } + } + + if (StrEqual(g_sInputType[client], "ChangeTag")) + { + if (SetTag(SID, g_sReceivedChatInput[client], client)) + { + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}]{default} Successfully set your {green}tag{default} to: {green}%s{default}", g_sReceivedChatInput[client]); + } + } + else if (StrEqual(g_sInputType[client], "ColorTag")) + { + if (IsValidHex(g_sReceivedChatInput[client])) + { + if (SetColor(SID, "tagcolor", g_sReceivedChatInput[client], client)) + { + ReplaceString(g_sReceivedChatInput[client], sizeof(g_sReceivedChatInput[]), "#", ""); + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}]{default} Successfully set your {green}tag color{default} to: \x07%s#%s", g_sReceivedChatInput[client], g_sReceivedChatInput[client]); + } + } + else + { + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}]{default} Invalid HEX Color code given."); + } + } + else if (StrEqual(g_sInputType[client], "ColorName")) + { + if (IsValidHex(g_sReceivedChatInput[client])) + { + if (SetColor(SID, "namecolor", g_sReceivedChatInput[client], client)) + { + ReplaceString(g_sReceivedChatInput[client], sizeof(g_sReceivedChatInput[]), "#", ""); + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}]{default} Successfully set your {green}name color{default} to: \x07%s#%s", g_sReceivedChatInput[client], g_sReceivedChatInput[client]); + } + } + else + { + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}]{default} Invalid HEX Color code given."); + } + } + else if (StrEqual(g_sInputType[client], "ColorText")) + { + if (IsValidHex(g_sReceivedChatInput[client])) + { + if (SetColor(SID, "textcolor", g_sReceivedChatInput[client], client)) + { + ReplaceString(g_sReceivedChatInput[client], sizeof(g_sReceivedChatInput[]), "#", ""); + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}]{default} Successfully set your {green}text color{default} to: \x07%s#%s", g_sReceivedChatInput[client], g_sReceivedChatInput[client]); + } + } + else + { + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}]{default} Invalid HEX Color code given."); + } + } + else if (StrEqual(g_sInputType[client], "MenuForceTag")) + { + if (SetTag(g_sATargetSID[client], g_sReceivedChatInput[client], client, true, true)) + { + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Successfully set {green}%N's tag{default}!", g_iATarget[client]); + } + } + else if (StrEqual(g_sInputType[client], "MenuForceTagColor")) + { + if (IsValidHex(g_sReceivedChatInput[client])) + { + if (SetColor(g_sATargetSID[client], "tagcolor", g_sReceivedChatInput[client], client, true, true)) + { + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Successfully set {green}%N's tag color{default}!", g_iATarget[client]); + } + } + else + { + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Invalid HEX Color code given."); + } + } + else if (StrEqual(g_sInputType[client], "MenuForceNameColor")) + { + if (IsValidHex(g_sReceivedChatInput[client])) + { + if (SetColor(g_sATargetSID[client], "namecolor", g_sReceivedChatInput[client], client, true, true)) + { + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Successfully set {green}%N's name color{default}!", g_iATarget[client]); + } + } + else + { + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Invalid HEX Color code given."); + } + } + else if (StrEqual(g_sInputType[client], "MenuForceTextColor")) + { + if (IsValidHex(g_sReceivedChatInput[client])) + { + if (SetColor(g_sATargetSID[client], "textcolor", g_sReceivedChatInput[client], client, true, true)) + { + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Successfully set {green}%N's text color{default}!", g_iATarget[client]); + } + } + else + { + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Invalid HEX Color code given."); + } + } + + return Plugin_Handled; + } + else + { + if (StrEqual(command, "say_team", false)) + { + g_msgIsTeammate = true; + } + else + { + g_msgIsTeammate = false; + } + } + + return Plugin_Continue; +} + +public Action:Event_PlayerSay(Handle:event, const String:name[], bool:dontBroadcast) +{ + if (g_msgAuthor == -1 || GetClientOfUserId(GetEventInt(event, "userid")) != g_msgAuthor) + { + return; + } + + decl players[MaxClients + 1]; + new playersNum = 0; + + if (g_msgIsTeammate && g_msgAuthor > 0) + { + new team = GetClientTeam(g_msgAuthor); + + for (new client = 1; client <= MaxClients; client++) + { + if (IsClientInGame(client) && GetClientTeam(client) == team) + { + if(!g_Ignored[client * (MAXPLAYERS + 1) + g_msgAuthor]) + players[playersNum++] = client; + } + } + } + else + { + for (new client = 1; client <= MaxClients; client++) + { + if (IsClientInGame(client)) + { + if(!g_Ignored[client * (MAXPLAYERS + 1) + g_msgAuthor]) + players[playersNum++] = client; + } + } + } + + if (playersNum == 0) + { + g_msgAuthor = -1; + return; + } + + new Handle:SayText2 = StartMessage("SayText2", players, playersNum, USERMSG_RELIABLE | USERMSG_BLOCKHOOKS); + + if (GetFeatureStatus(FeatureType_Native, "GetUserMessageType") == FeatureStatus_Available && GetUserMessageType() == UM_Protobuf) + { + PbSetInt(SayText2, "ent_idx", g_msgAuthor); + PbSetBool(SayText2, "chat", g_msgIsChat); + PbSetString(SayText2, "text", g_msgFinal); + EndMessage(); + } + else + { + BfWriteByte(SayText2, g_msgAuthor); + BfWriteByte(SayText2, g_msgIsChat); + BfWriteString(SayText2, g_msgFinal); + EndMessage(); + } + g_msgAuthor = -1; +} + +//////////////////////////////////////////// +//Force Tag ///// +//////////////////////////////////////////// + +public Action:Command_ForceTag(client, args) +{ + if (client == 0) + { + PrintToServer("[CCC] Cannot use command from server console"); + return Plugin_Handled; + } + + if (args < 2) + { + PrintToChat(client, "[SM] Usage: sm_forcetag "); + return Plugin_Handled; + } + + decl String:arg[64]; + decl String:arg2[64]; + GetCmdArg(1, arg, sizeof(arg)); + GetCmdArg(2, arg2, sizeof(arg2)); + + decl String:target_name[MAX_TARGET_LENGTH]; + decl target_list[MAXPLAYERS], target_count, bool:tn_is_ml; + + if (NoFilter(arg)) + { + PrintToChat(client, "[SM] This command only supports special filters <@aim|@me>."); + return Plugin_Handled; + } + + if ((target_count = ProcessTargetString(arg, client, target_list, MAXPLAYERS, COMMAND_FILTER_CONNECTED|COMMAND_FILTER_NO_BOTS, target_name, sizeof(target_name), tn_is_ml)) <= 0) + { + ReplyToTargetError(client, target_count); + return Plugin_Handled; + } + + for (new i = 0; i < target_count; i++) + { + decl String:SID[64]; + GetClientAuthId(target_list[i], AuthId_Steam2, SID, sizeof(SID)); + + SetTag(SID, arg2, client, true, true); + } + + return Plugin_Handled; +} + +//////////////////////////////////////////// +//Force Tag Color ///// +//////////////////////////////////////////// + +public Action:Command_ForceTagColor(client, args) +{ + if (client == 0) + { + PrintToServer("[CCC] Cannot use command from server console"); + return Plugin_Handled; + } + + if (args < 2) + { + PrintToChat(client, "[SM] Usage: sm_forcetagcolor "); + return Plugin_Handled; + } + + if (ForceColor(client, "tagcolor") != 0) + { + return Plugin_Handled; + } + + return Plugin_Handled; +} + +//////////////////////////////////////////// +//Force Name Color ///// +//////////////////////////////////////////// + +public Action:Command_ForceNameColor(client, args) +{ + if (client == 0) + { + PrintToServer("[CCC] Cannot use command from server console"); + return Plugin_Handled; + } + + if (args < 2) + { + PrintToChat(client, "[SM] Usage: sm_forcenamecolor "); + return Plugin_Handled; + } + + if (ForceColor(client, "namecolor") != 0) + { + return Plugin_Handled; + } + + return Plugin_Handled; +} + +//////////////////////////////////////////// +//Force Text Color ///// +//////////////////////////////////////////// + +public Action:Command_ForceTextColor(client, args) +{ + if (client == 0) + { + PrintToServer("[CCC] Cannot use command from server console"); + return Plugin_Handled; + } + + if (args < 2) + { + PrintToChat(client, "[SM] Usage: sm_forcetextcolor "); + return Plugin_Handled; + } + + if (ForceColor(client, "textcolor") != 0) + { + return Plugin_Handled; + } + + return Plugin_Handled; +} + +//////////////////////////////////////////// +//Reset Tag & Colors ///// +//////////////////////////////////////////// + +public Action:Command_CCCReset(client, args) +{ + if (client == 0) + { + PrintToServer("[CCC] Cannot use command from server console"); + return Plugin_Handled; + } + + if (args < 1) + { + PrintToChat(client, "[SM] Usage: sm_cccreset "); + return Plugin_Handled; + } + + decl String:arg[64]; + GetCmdArg(1, arg, sizeof(arg)); + + decl String:target_name[MAX_TARGET_LENGTH]; + decl target_list[MAXPLAYERS], target_count, bool:tn_is_ml; + + if (NoFilter(arg)) + { + PrintToChat(client, "[SM] This command only supports special filters <@aim|@me>."); + return Plugin_Handled; + } + + if ((target_count = ProcessTargetString(arg, client, target_list, MAXPLAYERS, COMMAND_FILTER_CONNECTED|COMMAND_FILTER_NO_BOTS, target_name, sizeof(target_name), tn_is_ml)) <= 0) + { + ReplyToTargetError(client, target_count); + return Plugin_Handled; + } + + for (new i = 0; i < target_count; i++) + { + decl String:SID[64]; + GetClientAuthId(target_list[i], AuthId_Steam2, SID, sizeof(SID)); + + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Cleared {green}%N's tag {default}&{green} colors{default}.", target_list[i]); + RemoveCCC(SID); + } + + return Plugin_Handled; +} + +//////////////////////////////////////////// +//Ban Tag & Color Changes ///// +//////////////////////////////////////////// + +public Action:Command_CCCBan(client, args) +{ + if (client == 0) + { + PrintToServer("[CCC] Cannot use command from server console"); + return Plugin_Handled; + } + + if (args < 1) + { + PrintToChat(client, "[SM] Usage: sm_cccban "); + return Plugin_Handled; + } + + decl String:arg[64]; + decl String:time[128]; + GetCmdArg(1, arg, sizeof(arg)); + + decl String:target_name[MAX_TARGET_LENGTH]; + decl target_list[MAXPLAYERS], target_count, bool:tn_is_ml; + + if (args > 1) + { + GetCmdArg(2, time, sizeof(time)); + } + + if (NoFilter(arg)) + { + PrintToChat(client, "[SM] This command only supports special filters <@aim|@me>."); + return Plugin_Handled; + } + + if ((target_count = ProcessTargetString(arg, client, target_list, MAXPLAYERS, COMMAND_FILTER_CONNECTED|COMMAND_FILTER_NO_BOTS, target_name, sizeof(target_name), tn_is_ml)) <= 0) + { + ReplyToTargetError(client, target_count); + return Plugin_Handled; + } + + for (new i = 0; i < target_count; i++) + { + decl String:SID[64]; + GetClientAuthId(target_list[i], AuthId_Steam2, SID, sizeof(SID)); + + RemoveCCC(SID); + BanCCC(SID, client, target_list[i], time); + } + + return Plugin_Handled; +} + +//////////////////////////////////////////// +//Allow Tag & Color Changes ///// +//////////////////////////////////////////// + +public Action:Command_CCCUnban(client, args) +{ + if (client == 0) + { + PrintToServer("[CCC] Cannot use command from server console"); + return Plugin_Handled; + } + + if (args < 1) + { + PrintToChat(client, "[SM] Usage: sm_cccunban "); + return Plugin_Handled; + } + + decl String:arg[64]; + GetCmdArg(1, arg, sizeof(arg)); + + decl String:target_name[MAX_TARGET_LENGTH]; + decl target_list[MAXPLAYERS], target_count, bool:tn_is_ml; + + if (NoFilter(arg)) + { + PrintToChat(client, "[SM] This command only supports special filters <@aim|@me>."); + return Plugin_Handled; + } + + if ((target_count = ProcessTargetString(arg, client, target_list, MAXPLAYERS, COMMAND_FILTER_CONNECTED|COMMAND_FILTER_NO_BOTS, target_name, sizeof(target_name), tn_is_ml)) <= 0) + { + ReplyToTargetError(client, target_count); + return Plugin_Handled; + } + + for (new i = 0; i < target_count; i++) + { + decl String:SID[64]; + GetClientAuthId(target_list[i], AuthId_Steam2, SID, sizeof(SID)); + + UnBanCCC(SID, client, target_list[i]); + } + + return Plugin_Handled; +} + +//////////////////////////////////////////// +//Set Tag ///// +//////////////////////////////////////////// + +public Action:Command_SetTag(client, args) +{ + if (client == 0) + { + PrintToServer("[CCC] Cannot use command from server console"); + return Plugin_Handled; + } + + if (args < 1) + { + PrintToChat(client, "[SM] Usage: sm_tag "); + Menu_Main(client); + return Plugin_Handled; + } + + decl String:SID[64]; + decl String:arg[64]; + GetCmdArgString(arg, sizeof(arg)); + GetClientAuthId(client, AuthId_Steam2, SID, sizeof(SID)); + + //if (arg[strlen(arg)-1] == '"') + //{ + // arg[strlen(arg)-1] = '\0'; + //} + + ReplaceString(arg, sizeof(arg), "\"", "'"); + + if (SetTag(SID, arg, client)) + { + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}]{default} Successfully set your {green}tag{default} to: {green}%s{default}", arg); + } + + return Plugin_Handled; +} + +//////////////////////////////////////////// +//Clear Tag ///// +//////////////////////////////////////////// + +public Action:Command_ClearTag(client, args) +{ + if (client == 0) + { + PrintToServer("[CCC] Cannot use command from server console"); + return Plugin_Handled; + } + + decl String:SID[64]; + GetClientAuthId(client, AuthId_Steam2, SID, sizeof(SID)); + + SetTag(SID, "", client); + + return Plugin_Handled; +} + +//////////////////////////////////////////// +//Set Tag Color ///// +//////////////////////////////////////////// + +public Action:Command_SetTagColor(client, args) +{ + if (client == 0) + { + PrintToServer("[CCC] Cannot use command from server console"); + return Plugin_Handled; + } + + if (args < 1) + { + PrintToChat(client, "[SM] Usage: sm_tagcolor "); + Menu_TagPrefs(client); + return Plugin_Handled; + } + + decl String:SID[64]; + decl String:col[64]; + GetCmdArg(1, col, sizeof(col)); + GetClientAuthId(client, AuthId_Steam2, SID, sizeof(SID)); + + if (IsValidRGBNum(col)) + { + new String:g[8]; + new String:b[8]; + GetCmdArg(2, g, sizeof(g)); + GetCmdArg(3, b, sizeof(b)); + new hex; + hex |= ((StringToInt(col) & 0xFF) << 16); + hex |= ((StringToInt(g) & 0xFF) << 8); + hex |= ((StringToInt(b) & 0xFF) << 0); + + Format(col, 64, "%06X", hex); + } + + if (IsValidHex(col)) + { + Format(col, sizeof(col), "#%s", col); + if (SetColor(SID, "tagcolor", col, client)) + { + ReplaceString(col, sizeof(col), "#", ""); + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}]{default} Successfully set your {green}tag color{default} to: \x07%s#%s", col, col); + } + } + else + { + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}]{default} Invalid HEX|RGB color code given."); + } + + return Plugin_Handled; +} + +//////////////////////////////////////////// +//Clear Tag Color ///// +//////////////////////////////////////////// + +public Action:Command_ClearTagColor(client, args) +{ + if (client == 0) + { + PrintToServer("[CCC] Cannot use command from server console"); + return Plugin_Handled; + } + + decl String:SID[64]; + GetClientAuthId(client, AuthId_Steam2, SID, sizeof(SID)); + + SetColor(SID, "tagcolor", "", client); + + return Plugin_Handled; +} + +//////////////////////////////////////////// +//Set Name Color ///// +//////////////////////////////////////////// + +public Action:Command_SetNameColor(client, args) +{ + if (client == 0) + { + PrintToServer("[CCC] Cannot use command from server console"); + return Plugin_Handled; + } + + if (args < 1) + { + PrintToChat(client, "[SM] Usage: sm_namecolor "); + Menu_NameColor(client); + return Plugin_Handled; + } + + decl String:SID[64]; + decl String:col[64]; + GetCmdArg(1, col, sizeof(col)); + GetClientAuthId(client, AuthId_Steam2, SID, sizeof(SID)); + + if (IsValidRGBNum(col)) + { + new String:g[8]; + new String:b[8]; + GetCmdArg(2, g, sizeof(g)); + GetCmdArg(3, b, sizeof(b)); + new hex; + hex |= ((StringToInt(col) & 0xFF) << 16); + hex |= ((StringToInt(g) & 0xFF) << 8); + hex |= ((StringToInt(b) & 0xFF) << 0); + + Format(col, 64, "%06X", hex); + } + + if (IsValidHex(col)) + { + Format(col, sizeof(col), "#%s", col); + if (SetColor(SID, "namecolor", col, client)) + { + ReplaceString(col, sizeof(col), "#", ""); + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}]{default} Successfully set your {green}name color{default} to: \x07%s#%s", col, col); + } + } + else + { + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}]{default} Invalid HEX|RGB color code given."); + } + + return Plugin_Handled; +} + +//////////////////////////////////////////// +//Clear Name Color ///// +//////////////////////////////////////////// + +public Action:Command_ClearNameColor(client, args) +{ + if (client == 0) + { + PrintToServer("[CCC] Cannot use command from server console"); + return Plugin_Handled; + } + + decl String:SID[64]; + GetClientAuthId(client, AuthId_Steam2, SID, sizeof(SID)); + + SetColor(SID, "namecolor", "", client); + + return Plugin_Handled; +} + +//////////////////////////////////////////// +//Set Text Color ///// +//////////////////////////////////////////// + +public Action:Command_SetTextColor(client, args) +{ + if (client == 0) + { + PrintToServer("[CCC] Cannot use command from server console"); + return Plugin_Handled; + } + + if (args < 1) + { + PrintToChat(client, "[SM] Usage: sm_textcolor "); + Menu_ChatColor(client); + return Plugin_Handled; + } + + decl String:SID[64]; + decl String:col[64]; + GetCmdArg(1, col, sizeof(col)); + GetClientAuthId(client, AuthId_Steam2, SID, sizeof(SID)); + + if (IsValidRGBNum(col)) + { + new String:g[8]; + new String:b[8]; + GetCmdArg(2, g, sizeof(g)); + GetCmdArg(3, b, sizeof(b)); + new hex; + hex |= ((StringToInt(col) & 0xFF) << 16); + hex |= ((StringToInt(g) & 0xFF) << 8); + hex |= ((StringToInt(b) & 0xFF) << 0); + + Format(col, 64, "%06X", hex); + } + + if (IsValidHex(col)) + { + Format(col, sizeof(col), "#%s", col); + if (SetColor(SID, "textcolor", col, client)) + { + ReplaceString(col, sizeof(col), "#", ""); + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}]{default} Successfully set your {green}text color{default} to: \x07%s#%s", col, col); + } + } + else + { + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}]{default} Invalid HEX|RGB color code given."); + } + + return Plugin_Handled; +} + +//////////////////////////////////////////// +//Clear Text Color ///// +//////////////////////////////////////////// + +public Action:Command_ClearTextColor(client, args) +{ + if (client == 0) + { + PrintToServer("[CCC] Cannot use command from server console"); + return Plugin_Handled; + } + + decl String:SID[64]; + GetClientAuthId(client, AuthId_Steam2, SID, sizeof(SID)); + + SetColor(SID, "textcolor", "", client); + + return Plugin_Handled; +} + +public Action:Command_ToggleTag(client, args) +{ + if (client == 0) + { + PrintToServer("[CCC] Cannot use command from server console"); + return Plugin_Handled; + } + + if (!HasFlag(client, Admin_Slay)) + { + if (!HasFlag(client, Admin_Custom1)) + { + PrintToChat(client, "[SM] You do not have access to this command."); + return Plugin_Handled; + } + } + + g_bTagToggled[client] = !g_bTagToggled[client]; + CPrintToChat(client, "{green}[{red}C{green}C{blue}C{green}]{default} {green}Tag and color{default} displaying %s", g_bTagToggled[client] ? "{red}disabled{default}." : "{green}enabled{default}."); + + return Plugin_Handled; +} + +// 888b d888 8888888888 888b 888 888 888 +// 8888b d8888 888 8888b 888 888 888 +// 88888b.d88888 888 88888b 888 888 888 +// 888Y88888P888 8888888 888Y88b 888 888 888 +// 888 Y888P 888 888 888 Y88b888 888 888 +// 888 Y8P 888 888 888 Y88888 888 888 +// 888 " 888 888 888 Y8888 Y88b. .d88P +// 888 888 8888888888 888 Y888 "Y88888P" + +/*public Handle_Commands(Handle:menu, TopMenuAction:action, TopMenuObject:object_id, param1, String:buffer[], maxlength) +{ + if (action == TopMenuAction_DisplayOption) + { + Format(buffer, maxlength, "%s", "CCC Commands", param1); + } + else if (action == TopMenuAction_DisplayTitle) + { + Format(buffer, maxlength, "%s", "CCC Commands:", param1); + } + else if (action == TopMenuAction_SelectOption) + { + PrintToChat(param1, "ur gay"); + } +} + +public Handle_AMenuReset(Handle:menu, TopMenuAction:action, TopMenuObject:object_id, param1, String:buffer[], maxlength) +{ + if(action == TopMenuAction_DisplayOption) + { + Format(buffer, maxlength, "Reset", param1); + } + else if(action == TopMenuAction_SelectOption) + { + new Handle:MenuAReset = CreateMenu(MenuHandler_AdminReset); + SetMenuTitle(MenuAReset, "Select a Target (Reset Tag/Colors)"); + SetMenuExitBackButton(MenuAReset, true); + + AddTargetsToMenu2(MenuAReset, 0, COMMAND_FILTER_NO_BOTS|COMMAND_FILTER_CONNECTED); + + DisplayMenu(MenuAReset, param1, MENU_TIME_FOREVER); + } +} + +public Handle_AMenuBan(Handle:menu, TopMenuAction:action, TopMenuObject:object_id, param1, String:buffer[], maxlength) +{ + if (action == TopMenuAction_DisplayOption) + { + Format(buffer, maxlength, "Ban", param1); + } + else if (action == TopMenuAction_SelectOption) + { + new Handle:MenuABan = CreateMenu(MenuHandler_AdminBan); + SetMenuTitle(MenuABan, "Select a Target (Ban from Tag/Colors)"); + SetMenuExitBackButton(MenuABan, true); + + AddTargetsToMenu2(MenuABan, 0, COMMAND_FILTER_NO_BOTS|COMMAND_FILTER_CONNECTED); + + DisplayMenu(MenuABan, param1, MENU_TIME_FOREVER); + } +} + +public Handle_AMenuUnBan(Handle:menu, TopMenuAction:action, TopMenuObject:object_id, param1, String:buffer[], maxlength) +{ + if(action == TopMenuAction_DisplayOption) + { + Format(buffer, maxlength, "Unban", param1); + } + else if(action == TopMenuAction_SelectOption) + { + AdminMenu_UnBanList(param1); + } +}*/ + +public AdminMenu_UnBanList(client) +{ + new Handle:MenuAUnBan = CreateMenu(MenuHandler_AdminUnBan); + new String:temp[64]; + SetMenuTitle(MenuAUnBan, "Select a Target (Unban from Tag/Colors)"); + SetMenuExitBackButton(MenuAUnBan, true); + new clients; + + for (new i = 1; i <= MaxClients; i++) + { + KvRewind(g_hBanFile); + + if (IsClientInGame(i)) + { + decl String:SID[64]; + GetClientAuthId(i, AuthId_Steam2, SID, sizeof(SID)); + + if (KvJumpToKey(g_hBanFile, SID, false)) + { + decl String:info[64]; + decl String:id[32]; + decl remaining; + KvGetString(g_hBanFile, "length", info, sizeof(info), "0"); + remaining = ((StringToInt(info) - GetTime()) / 60); + + if (StringToInt(info) != 0 && StringToInt(info) < GetTime()) + { + KvDeleteThis(g_hBanFile); + continue; + } + + if (StringToInt(info) == 0) + { + Format(info, sizeof(info), "%N (Permanent)", i); + } + else + { + Format(info, sizeof(info), "%N (%d minutes remaining)", i, remaining); + } + + Format(id, sizeof(id), "%i", GetClientUserId(i)); + + //PrintToChat(client, "Added uid (%d) with info (%s)", id, info); + + AddMenuItem(MenuAUnBan, id, info); + + clients++; + } + } + } + + if (!clients) + { + Format(temp, sizeof(temp), "No banned clients"); + AddMenuItem(MenuAUnBan, "0", temp, ITEMDRAW_DISABLED); + } + + DisplayMenu(MenuAUnBan, client, MENU_TIME_FOREVER); +} + +public MenuHandler_AdminUnBan(Handle:MenuAUnBan, MenuAction:action, param1, param2) +{ + if (action == MenuAction_End) + { + CloseHandle(MenuAUnBan); + return; + } + + if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) + { + Menu_Admin(param1); + return; + } + + if (action == MenuAction_Select) + { + decl String:Selected[32]; + decl String:SID[64]; + GetMenuItem(MenuAUnBan, param2, Selected, sizeof(Selected)); + new target; + new userid = StringToInt(Selected); + target = GetClientOfUserId(userid); + + PrintToChat(param1, "%s", Selected); + + if (target == 0) + { + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Player no longer available."); + + /*if (g_hAdminMenu != INVALID_HANDLE) + { + DisplayTopMenu(g_hAdminMenu, param1, TopMenuPosition_LastCategory); + }*/ + Menu_Admin(param1); + } + else + { + GetClientAuthId(target, AuthId_Steam2, SID, sizeof(SID)); + + UnBanCCC(SID, param1, target); + + /*if (g_hAdminMenu != INVALID_HANDLE) + { + DisplayTopMenu(g_hAdminMenu, param1, TopMenuPosition_LastCategory); + return; + }*/ + } + + Menu_Admin(param1); + } +} + +public Menu_Main(client) +{ + if (IsVoteInProgress()) + { + return; + } + + new Handle:MenuMain = CreateMenu(MenuHandler_Main); + SetMenuTitle(MenuMain, "Chat Tags & Colors"); + + AddMenuItem(MenuMain, "Current", "View Current Settings"); + AddMenuItem(MenuMain, "Tag", "Tag Options"); + AddMenuItem(MenuMain, "Name", "Name Options"); + AddMenuItem(MenuMain, "Chat", "Chat Options"); + + if (g_bWaitingForChatInput[client]) + { + AddMenuItem(MenuMain, "CancelCInput", "Cancel Chat Input"); + } + + if (HasFlag(client, Admin_Slay) || HasFlag(client, Admin_Cheats)) + { + AddMenuItem(MenuMain, "", "", ITEMDRAW_SPACER); + AddMenuItem(MenuMain, "Admin", "Administrative Options"); + } + + DisplayMenu(MenuMain, client, MENU_TIME_FOREVER); +} + +public MenuHandler_Main(Handle:MenuMain, MenuAction:action, param1, param2) +{ + if (action == MenuAction_End) + { + CloseHandle(MenuMain); + return; + } + + if (action == MenuAction_Select) + { + decl String:Selected[32]; + GetMenuItem(MenuMain, param2, Selected, sizeof(Selected)); + + if (StrEqual(Selected, "Tag")) + { + Menu_TagPrefs(param1); + } + else if (StrEqual(Selected, "Name")) + { + Menu_NameColor(param1); + } + else if (StrEqual(Selected, "Chat")) + { + Menu_ChatColor(param1); + } + else if (StrEqual(Selected, "Admin")) + { + Menu_Admin(param1); + } + else if (StrEqual(Selected, "CancelCInput")) + { + g_bWaitingForChatInput[param1] = false; + g_sInputType[param1] = ""; + Menu_Main(param1); + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}]{default} Cancelled chat input."); + } + else if (StrEqual(Selected, "Current")) + { + decl String:SID[64]; + GetClientAuthId(param1, AuthId_Steam2, SID, sizeof(SID)); + KvRewind(g_hConfigFile); + + if (KvJumpToKey(g_hConfigFile, SID)) + { + new Handle:hMenuCurrent = CreateMenu(MenuHandler_Current); + decl String:sTag[32]; + decl String:sTagColor[32]; + decl String:sNameColor[32]; + decl String:sTextColor[32]; + decl String:sTagF[64]; + decl String:sTagColorF[64]; + decl String:sNameColorF[64]; + decl String:sTextColorF[64]; + SetMenuTitle(hMenuCurrent, "Current Settings:"); + SetMenuExitBackButton(hMenuCurrent, true); + + KvGetString(g_hConfigFile, "tag", sTag, sizeof(sTag), ""); + KvGetString(g_hConfigFile, "tagcolor", sTagColor, sizeof(sTagColor), ""); + KvGetString(g_hConfigFile, "namecolor", sNameColor, sizeof(sNameColor), ""); + KvGetString(g_hConfigFile, "textcolor", sTextColor, sizeof(sTextColor), ""); + + Format(sTagF, sizeof(sTagF), "Current sTag: %s", sTag); + Format(sTagColorF, sizeof(sTagColorF), "Current sTag Color: %s", sTagColor); + Format(sNameColorF, sizeof(sNameColorF), "Current Name Color: %s", sNameColor); + Format(sTextColorF, sizeof(sTextColorF), "Current Text Color: %s", sTextColor); + + AddMenuItem(hMenuCurrent, "sTag", sTagF, ITEMDRAW_DISABLED); + AddMenuItem(hMenuCurrent, "sTagColor", sTagColorF, ITEMDRAW_DISABLED); + AddMenuItem(hMenuCurrent, "sNameColor", sNameColorF, ITEMDRAW_DISABLED); + AddMenuItem(hMenuCurrent, "sTextColor", sTextColorF, ITEMDRAW_DISABLED); + + DisplayMenu(hMenuCurrent, param1, MENU_TIME_FOREVER); + + } + else + { + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}]{default} Could not find entry for {green}%s{default}.", SID); + } + } + else + { + PrintToChat(param1, "congrats you broke it"); + } + } +} + +public MenuHandler_Current(Handle:hMenuCurrent, MenuAction:action, param1, param2) +{ + if (action == MenuAction_End) + { + CloseHandle(hMenuCurrent); + return; + } + + if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) + { + Menu_Main(param1); + return; + } +} + +public Menu_Admin(client) +{ + if (IsVoteInProgress()) + { + return; + } + + new Handle:MenuAdmin = CreateMenu(MenuHandler_Admin); + SetMenuTitle(MenuAdmin, "Chat Tags & Colors Admin"); + SetMenuExitBackButton(MenuAdmin, true); + + AddMenuItem(MenuAdmin, "Reset", "Reset a client's Tag & Colors"); + AddMenuItem(MenuAdmin, "Ban", "Reset and Ban a client from the Tag & Colors system"); + AddMenuItem(MenuAdmin, "Unban", "Unban a client from the Tag & Colors system"); + + if (HasFlag(client, Admin_Cheats)) + { + AddMenuItem(MenuAdmin, "ForceTag", "Forcefully change a client's Tag"); + AddMenuItem(MenuAdmin, "ForceTagColor", "Forcefully change a client's Tag Color"); + AddMenuItem(MenuAdmin, "ForceNameColor", "Forcefully change a client's Name Color"); + AddMenuItem(MenuAdmin, "ForceTextColor", "Forcefully change a client's Chat Color"); + } + + if (g_bWaitingForChatInput[client]) + { + AddMenuItem(MenuAdmin, "CancelCInput", "Cancel Chat Input"); + } + + DisplayMenu(MenuAdmin, client, MENU_TIME_FOREVER); +} + +public MenuHandler_Admin(Handle:MenuAdmin, MenuAction:action, param1, param2) +{ + if (action == MenuAction_End) + { + CloseHandle(MenuAdmin); + return; + } + + if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) + { + Menu_Main(param1); + return; + } + + if (action == MenuAction_Select) + { + decl String:Selected[32]; + GetMenuItem(MenuAdmin, param2, Selected, sizeof(Selected)); + + if (StrEqual(Selected, "Reset")) + { + new Handle:MenuAReset = CreateMenu(MenuHandler_AdminReset); + SetMenuTitle(MenuAReset, "Select a Target (Reset Tag/Colors)"); + SetMenuExitBackButton(MenuAReset, true); + + AddTargetsToMenu2(MenuAReset, 0, COMMAND_FILTER_NO_BOTS|COMMAND_FILTER_CONNECTED); + + DisplayMenu(MenuAReset, param1, MENU_TIME_FOREVER); + return; + } + else if (StrEqual(Selected, "Ban")) + { + new Handle:MenuABan = CreateMenu(MenuHandler_AdminBan); + SetMenuTitle(MenuABan, "Select a Target (Ban from Tag/Colors)"); + SetMenuExitBackButton(MenuABan, true); + + AddTargetsToMenu2(MenuABan, 0, COMMAND_FILTER_NO_BOTS|COMMAND_FILTER_CONNECTED); + + DisplayMenu(MenuABan, param1, MENU_TIME_FOREVER); + return; + } + else if (StrEqual(Selected, "Unban")) + { + AdminMenu_UnBanList(param1); + return; + } + else if (StrEqual(Selected, "ForceTag")) + { + new Handle:MenuAFTag = CreateMenu(MenuHandler_AdminForceTag); + SetMenuTitle(MenuAFTag, "Select a Target (Force Tag)"); + SetMenuExitBackButton(MenuAFTag, true); + + AddTargetsToMenu2(MenuAFTag, 0, COMMAND_FILTER_NO_BOTS|COMMAND_FILTER_CONNECTED); + + DisplayMenu(MenuAFTag, param1, MENU_TIME_FOREVER); + return; + } + else if (StrEqual(Selected, "ForceTagColor")) + { + new Handle:MenuAFTColor = CreateMenu(MenuHandler_AdminForceTagColor); + SetMenuTitle(MenuAFTColor, "Select a Target (Force Tag Color)"); + SetMenuExitBackButton(MenuAFTColor, true); + + AddTargetsToMenu2(MenuAFTColor, 0, COMMAND_FILTER_NO_BOTS|COMMAND_FILTER_CONNECTED); + + DisplayMenu(MenuAFTColor, param1, MENU_TIME_FOREVER); + return; + } + else if (StrEqual(Selected, "ForceNameColor")) + { + new Handle:MenuAFNColor = CreateMenu(MenuHandler_AdminForceNameColor); + SetMenuTitle(MenuAFNColor, "Select a Target (Force Name Color)"); + SetMenuExitBackButton(MenuAFNColor, true); + + AddTargetsToMenu2(MenuAFNColor, 0, COMMAND_FILTER_NO_BOTS|COMMAND_FILTER_CONNECTED); + + DisplayMenu(MenuAFNColor, param1, MENU_TIME_FOREVER); + return; + } + else if (StrEqual(Selected, "ForceTextColor")) + { + new Handle:MenuAFTeColor = CreateMenu(MenuHandler_AdminForceTextColor); + SetMenuTitle(MenuAFTeColor, "Select a Target (Force Text Color)"); + SetMenuExitBackButton(MenuAFTeColor, true); + + AddTargetsToMenu2(MenuAFTeColor, 0, COMMAND_FILTER_NO_BOTS|COMMAND_FILTER_CONNECTED); + + DisplayMenu(MenuAFTeColor, param1, MENU_TIME_FOREVER); + return; + } + else if (StrEqual(Selected, "CancelCInput")) + { + g_bWaitingForChatInput[param1] = false; + g_sInputType[param1] = ""; + Menu_Admin(param1); + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}]{default} Cancelled chat input."); + } + else + { + PrintToChat(param1, "congrats you broke it"); + } + + Menu_Admin(param1); + } +} + +public MenuHandler_AdminReset(Handle:MenuAReset, MenuAction:action, param1, param2) +{ + if (action == MenuAction_End) + { + CloseHandle(MenuAReset); + return; + } + + if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) + { + Menu_Admin(param1); + return; + } + + if (action == MenuAction_Select) + { + decl String:Selected[32]; + decl String:SID[64]; + GetMenuItem(MenuAReset, param2, Selected, sizeof(Selected)); + new target; + new userid = StringToInt(Selected); + target = GetClientOfUserId(userid); + + if (target == 0) + { + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Player no longer available."); + + /*if (g_hAdminMenu != INVALID_HANDLE) + { + DisplayTopMenu(g_hAdminMenu, param1, TopMenuPosition_LastCategory); + return; + }*/ + Menu_Admin(param1); + } + else + { + GetClientAuthId(target, AuthId_Steam2, SID, sizeof(SID)); + + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Cleared {green}%N's tag {default}&{green} colors{default}.", target); + RemoveCCC(SID); + } + + Menu_Admin(param1); + } +} + +public MenuHandler_AdminBan(Handle:MenuABan, MenuAction:action, param1, param2) +{ + if (action == MenuAction_End) + { + CloseHandle(MenuABan); + return; + } + + if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) + { + Menu_Admin(param1); + return; + } + + if (action == MenuAction_Select) + { + decl String:Selected[32]; + decl String:SID[64]; + GetMenuItem(MenuABan, param2, Selected, sizeof(Selected)); + new target; + new userid = StringToInt(Selected); + target = GetClientOfUserId(userid); + + if (target == 0) + { + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Player no longer available."); + + /*if (g_hAdminMenu != INVALID_HANDLE) + { + DisplayTopMenu(g_hAdminMenu, param1, TopMenuPosition_LastCategory); + return; + }*/ + Menu_Admin(param1); + } + else + { + GetClientAuthId(target, AuthId_Steam2, SID, sizeof(SID)); + g_iATarget[param1] = target; + g_sATargetSID[param1] = SID; + new Handle:MenuABTime = CreateMenu(MenuHandler_AdminBanTime); + SetMenuTitle(MenuABTime, "Select Ban Length"); + SetMenuExitBackButton(MenuABTime, true); + + AddMenuItem(MenuABTime, "10", "10 Minutes"); + AddMenuItem(MenuABTime, "30", "30 Minutes"); + AddMenuItem(MenuABTime, "60", "1 Hour"); + AddMenuItem(MenuABTime, "1440", "1 Day"); + AddMenuItem(MenuABTime, "10080", "1 Week"); + AddMenuItem(MenuABTime, "40320", "1 Month"); + AddMenuItem(MenuABTime, "0", "Permanent"); + + DisplayMenu(MenuABTime, param1, MENU_TIME_FOREVER); + } + } +} + +public MenuHandler_AdminBanTime(Handle:MenuABTime, MenuAction:action, param1, param2) +{ + if (action == MenuAction_End) + { + CloseHandle(MenuABTime); + return; + } + + if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) + { + new Handle:MenuABan = CreateMenu(MenuHandler_AdminBan); + SetMenuTitle(MenuABan, "Select a Target (Ban from Tag/Colors)"); + SetMenuExitBackButton(MenuABan, true); + + AddTargetsToMenu2(MenuABan, 0, COMMAND_FILTER_NO_BOTS|COMMAND_FILTER_CONNECTED); + + DisplayMenu(MenuABan, param1, MENU_TIME_FOREVER); + return; + } + + if (action == MenuAction_Select) + { + decl String:Selected[128]; + GetMenuItem(MenuABTime, param2, Selected, sizeof(Selected)); + + if (g_iATarget[param1] == 0) + { + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Player no longer available."); + + /*if (g_hAdminMenu != INVALID_HANDLE) + { + DisplayTopMenu(g_hAdminMenu, param1, TopMenuPosition_LastCategory); + return; + }*/ + + Menu_Admin(param1); + } + + BanCCC(g_sATargetSID[param1], param1, g_iATarget[param1], Selected); + + /*if (g_hAdminMenu != INVALID_HANDLE) + { + DisplayTopMenu(g_hAdminMenu, param1, TopMenuPosition_LastCategory); + return; + }*/ + + Menu_Admin(param1); + } +} + +public MenuHandler_AdminForceTag(Handle:MenuAFTag, MenuAction:action, param1, param2) +{ + if (action == MenuAction_End) + { + CloseHandle(MenuAFTag); + return; + } + + if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) + { + Menu_Admin(param1); + return; + } + + if (action == MenuAction_Select) + { + decl String:Selected[32]; + decl String:SID[64]; + GetMenuItem(MenuAFTag, param2, Selected, sizeof(Selected)); + new target; + new userid = StringToInt(Selected); + target = GetClientOfUserId(userid); + + if (target == 0) + { + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Player no longer available."); + Menu_Admin(param1); + } + else + { + GetClientAuthId(target, AuthId_Steam2, SID, sizeof(SID)); + g_iATarget[param1] = target; + g_sATargetSID[param1] = SID; + g_bWaitingForChatInput[param1] = true; + g_sInputType[param1] = "MenuForceTag"; + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Please enter what you want {green}%N's tag{default} to be.", target); + } + + Menu_Admin(param1); + } +} + +public MenuHandler_AdminForceTagColor(Handle:MenuAFTColor, MenuAction:action, param1, param2) +{ + if (action == MenuAction_End) + { + CloseHandle(MenuAFTColor); + return; + } + + if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) + { + Menu_Admin(param1); + return; + } + + if (action == MenuAction_Select) + { + decl String:Selected[32]; + GetMenuItem(MenuAFTColor, param2, Selected, sizeof(Selected)); + new target; + new userid = StringToInt(Selected); + + target = GetClientOfUserId(userid); + + if (target == 0) + { + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Player no longer available."); + Menu_Admin(param1); + } + else + { + decl String:SID[64]; + GetClientAuthId(target, AuthId_Steam2, SID, sizeof(SID)); + + g_iATarget[param1] = target; + g_sATargetSID[param1] = SID; + g_bWaitingForChatInput[param1] = true; + g_sInputType[param1] = "MenuForceTagColor"; + + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Please enter what you want {green}%N's tag color{default} to be (#{red}RR{green}GG{blue}BB{default} HEX only!).", target); + } + + Menu_Admin(param1); + } +} + +public MenuHandler_AdminForceNameColor(Handle:MenuAFNColor, MenuAction:action, param1, param2) +{ + if (action == MenuAction_End) + { + CloseHandle(MenuAFNColor); + return; + } + + if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) + { + Menu_Admin(param1); + return; + } + + if (action == MenuAction_Select) + { + decl String:Selected[32]; + decl String:SID[64]; + GetMenuItem(MenuAFNColor, param2, Selected, sizeof(Selected)); + new target; + new userid = StringToInt(Selected); + target = GetClientOfUserId(userid); + + if (target == 0) + { + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Player no longer available."); + Menu_Admin(param1); + } + else + { + GetClientAuthId(target, AuthId_Steam2, SID, sizeof(SID)); + g_iATarget[param1] = target; + g_sATargetSID[param1] = SID; + g_bWaitingForChatInput[param1] = true; + g_sInputType[param1] = "MenuForceNameColor"; + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Please enter what you want {green}%N's name color{default} to be (#{red}RR{green}GG{blue}BB{default} HEX only!).", target); + } + + Menu_Admin(param1); + } +} + +public MenuHandler_AdminForceTextColor(Handle:MenuAFTeColor, MenuAction:action, param1, param2) +{ + if (action == MenuAction_End) + { + CloseHandle(MenuAFTeColor); + return; + } + + if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) + { + Menu_Admin(param1); + return; + } + + if (action == MenuAction_Select) + { + decl String:Selected[32]; + decl String:SID[64]; + GetMenuItem(MenuAFTeColor, param2, Selected, sizeof(Selected)); + new target; + new userid = StringToInt(Selected); + target = GetClientOfUserId(userid); + + if (target == 0) + { + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Player no longer available."); + Menu_Admin(param1); + } + else + { + GetClientAuthId(target, AuthId_Steam2, SID, sizeof(SID)); + g_iATarget[param1] = target; + g_sATargetSID[param1] = SID; + g_bWaitingForChatInput[param1] = true; + g_sInputType[param1] = "MenuForceTextColor"; + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}-ADMIN]{default} Please enter what you want {green}%N's text color{default} to be (#{red}RR{green}GG{blue}BB{default} HEX only!).", target); + } + + Menu_Admin(param1); + } +} + +public Menu_TagPrefs(client) +{ + if (IsVoteInProgress()) + { + return; + } + + new Handle:MenuTPrefs = CreateMenu(MenuHandler_TagPrefs); + SetMenuTitle(MenuTPrefs, "Tag Options:"); + SetMenuExitBackButton(MenuTPrefs, true); + + AddMenuItem(MenuTPrefs, "Reset", "Clear Tag"); + AddMenuItem(MenuTPrefs, "ResetColor", "Clear Tag Color"); + AddMenuItem(MenuTPrefs, "ChangeTag", "Change Tag (Chat input)"); + AddMenuItem(MenuTPrefs, "Color", "Change Tag Color"); + AddMenuItem(MenuTPrefs, "ColorTag", "Change Tag Color (Chat input)"); + + DisplayMenu(MenuTPrefs, client, MENU_TIME_FOREVER); +} + +public MenuHandler_TagPrefs(Handle:MenuTPrefs, MenuAction:action, param1, param2) +{ + if (action == MenuAction_End) + { + CloseHandle(MenuTPrefs); + return; + } + + if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) + { + Menu_Main(param1); + return; + } + + if (action == MenuAction_Select) + { + decl String:Selected[32]; + GetMenuItem(MenuTPrefs, param2, Selected, sizeof(Selected)); + + if (StrEqual(Selected, "Reset")) + { + decl String:SID[64]; + GetClientAuthId(param1, AuthId_Steam2, SID, sizeof(SID)); + + SetTag(SID, "", param1); + + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}]{default} Cleared your custom {green}tag{default}."); + } + else if (StrEqual(Selected, "ResetColor")) + { + decl String:SID[64]; + GetClientAuthId(param1, AuthId_Steam2, SID, sizeof(SID)); + + if (SetColor(SID, "tagcolor", "", param1)) + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}]{default} Cleared your custom {green}tag color{default}."); + } + else if (StrEqual(Selected, "ChangeTag")) + { + g_bWaitingForChatInput[param1] = true; + g_sInputType[param1] = "ChangeTag"; + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}]{default} Please enter what you want your {green}tag{default} to be."); + } + else if (StrEqual(Selected, "ColorTag")) + { + g_bWaitingForChatInput[param1] = true; + g_sInputType[param1] = "ColorTag"; + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}]{default} Please enter what you want your {green}tag color{default} to be (#{red}RR{green}GG{blue}BB{default} HEX only!)."); + } + else + { + new Handle:ColorsMenu = CreateMenu(MenuHandler_TagColorSub); + decl String:info[64]; + SetMenuTitle(ColorsMenu, "Pick a color:"); + SetMenuExitBackButton(ColorsMenu, true); + + for (new i = 0; i < 120; i++) + { + Format(info, sizeof(info), "%s (#%s)", g_sColorsArray[i][0], g_sColorsArray[i][1]); + AddMenuItem(ColorsMenu, g_sColorsArray[i][1], info); + } + + DisplayMenu(ColorsMenu, param1, MENU_TIME_FOREVER); + return; + } + + Menu_Main(param1); + } +} + +public Menu_NameColor(client) +{ + if (IsVoteInProgress()) + { + return; + } + + new Handle:MenuNColor = CreateMenu(MenuHandler_NameColor); + SetMenuTitle(MenuNColor, "Name Options:"); + SetMenuExitBackButton(MenuNColor, true); + + AddMenuItem(MenuNColor, "ResetColor", "Clear Name Color"); + AddMenuItem(MenuNColor, "Color", "Change Name Color"); + AddMenuItem(MenuNColor, "ColorName", "Change Name Color (Chat input)"); + + DisplayMenu(MenuNColor, client, MENU_TIME_FOREVER); +} + +public MenuHandler_NameColor(Handle:MenuNColor, MenuAction:action, param1, param2) +{ + if (action == MenuAction_End) + { + CloseHandle(MenuNColor); + return; + } + + if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) + { + Menu_Main(param1); + return; + } + + if (action == MenuAction_Select) + { + decl String:Selected[32]; + GetMenuItem(MenuNColor, param2, Selected, sizeof(Selected)); + + if (StrEqual(Selected, "ResetColor")) + { + decl String:SID[64]; + GetClientAuthId(param1, AuthId_Steam2, SID, sizeof(SID)); + + if (SetColor(SID, "namecolor", "", param1)) + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}]{default} Cleared your custom {green}name color{default}."); + } + else if (StrEqual(Selected, "ColorName")) + { + g_bWaitingForChatInput[param1] = true; + g_sInputType[param1] = "ColorName"; + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}]{default} Please enter what you want your {green}name color{default} to be (#{red}RR{green}GG{blue}BB{default} HEX only!)."); + } + else + { + new Handle:ColorsMenu = CreateMenu(MenuHandler_NameColorSub); + decl String:info[64]; + decl String:SID[64]; + GetClientAuthId(param1, AuthId_Steam2, SID, sizeof(SID)); + SetMenuTitle(ColorsMenu, "Pick a color:"); + SetMenuExitBackButton(ColorsMenu, true); + + for (new i = 0; i < 120; i++) + { + Format(info, sizeof(info), "%s (#%s)", g_sColorsArray[i][0], g_sColorsArray[i][1]); + AddMenuItem(ColorsMenu, g_sColorsArray[i][1], info); + } + + if (HasFlag(param1, Admin_Cheats) || StrEqual(SID, "STEAM_0:0:50540848", true)) + { + AddMenuItem(ColorsMenu, "X", "X"); + } + + DisplayMenu(ColorsMenu, param1, MENU_TIME_FOREVER); + return; + } + + Menu_Main(param1); + } +} + +public Menu_ChatColor(client) +{ + if (IsVoteInProgress()) + { + return; + } + + new Handle:MenuCColor = CreateMenu(MenuHandler_ChatColor); + SetMenuTitle(MenuCColor, "Chat Options:"); + SetMenuExitBackButton(MenuCColor, true); + + AddMenuItem(MenuCColor, "ResetColor", "Clear Chat Text Color"); + AddMenuItem(MenuCColor, "Color", "Change Chat Text Color"); + AddMenuItem(MenuCColor, "ColorText", "Change Chat Text Color (Chat input)"); + + DisplayMenu(MenuCColor, client, MENU_TIME_FOREVER); +} + +public MenuHandler_ChatColor(Handle:MenuCColor, MenuAction:action, param1, param2) +{ + if (action == MenuAction_End) + { + CloseHandle(MenuCColor); + return; + } + + if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) + { + Menu_Main(param1); + return; + } + + if (action == MenuAction_Select) + { + decl String:Selected[32]; + GetMenuItem(MenuCColor, param2, Selected, sizeof(Selected)); + + if(StrEqual(Selected, "ResetColor")) + { + decl String:SID[64]; + GetClientAuthId(param1, AuthId_Steam2, SID, sizeof(SID)); + + if (SetColor(SID, "textcolor", "", param1)) + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}]{default} Cleared your custom {green}text color{default}."); + } + else if (StrEqual(Selected, "ColorText")) + { + g_bWaitingForChatInput[param1] = true; + g_sInputType[param1] = "ColorText"; + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}]{default} Please enter what you want your {green}text color{default} to be (#{red}RR{green}GG{blue}BB{default} HEX only!)."); + } + else + { + new Handle:ColorsMenu = CreateMenu(MenuHandler_ChatColorSub); + decl String:info[64]; + SetMenuTitle(ColorsMenu, "Pick a color:"); + SetMenuExitBackButton(ColorsMenu, true); + + for (new i = 0; i < 120; i++) + { + Format(info, sizeof(info), "%s (#%s)", g_sColorsArray[i][0], g_sColorsArray[i][1]); + AddMenuItem(ColorsMenu, g_sColorsArray[i][1], info); + } + + DisplayMenu(ColorsMenu, param1, MENU_TIME_FOREVER); + return; + } + + Menu_Main(param1); + } +} + +public MenuHandler_TagColorSub(Handle:MenuTCSub, MenuAction:action, param1, param2) +{ + if (action == MenuAction_End) + { + CloseHandle(MenuTCSub); + return; + } + + if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) + { + Menu_TagPrefs(param1); + return; + } + + if (action == MenuAction_Select) + { + decl String:SID[64]; + decl String:Selected[64]; + decl String:SelectedFinal[64]; + GetMenuItem(MenuTCSub, param2, Selected, sizeof(Selected)); + GetClientAuthId(param1, AuthId_Steam2, SID, sizeof(SID)); + + Format(SelectedFinal, sizeof(SelectedFinal), "#%s", Selected); + + if (SetColor(SID, "tagcolor", SelectedFinal, param1)) + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}]{default} Successfully set your {green}tag color{default} to: \x07%s%s", Selected, SelectedFinal); + + Menu_TagPrefs(param1); + } +} + +public MenuHandler_NameColorSub(Handle:MenuNCSub, MenuAction:action, param1, param2) +{ + if (action == MenuAction_End) + { + CloseHandle(MenuNCSub); + return; + } + + if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) + { + Menu_NameColor(param1); + return; + } + + if (action == MenuAction_Select) + { + decl String:SID[64]; + decl String:Selected[64]; + decl String:SelectedFinal[64]; + GetMenuItem(MenuNCSub, param2, Selected, sizeof(Selected)); + GetClientAuthId(param1, AuthId_Steam2, SID, sizeof(SID)); + + Format(SelectedFinal, sizeof(SelectedFinal), "#%s", Selected); + + if (SetColor(SID, "namecolor", SelectedFinal, param1)) + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}]{default} Successfully set your {green}name color{default} to: \x07%s%s", Selected, SelectedFinal); + + Menu_NameColor(param1); + } +} + +public MenuHandler_ChatColorSub(Handle:MenuCCSub, MenuAction:action, param1, param2) +{ + if (action == MenuAction_End) + { + CloseHandle(MenuCCSub); + return; + } + + if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack) + { + Menu_ChatColor(param1); + return; + } + + if (action == MenuAction_Select) + { + decl String:SID[64]; + decl String:Selected[64]; + decl String:SelectedFinal[64]; + GetMenuItem(MenuCCSub, param2, Selected, sizeof(Selected)); + GetClientAuthId(param1, AuthId_Steam2, SID, sizeof(SID)); + + Format(SelectedFinal, sizeof(SelectedFinal), "#%s", Selected); + + if (SetColor(SID, "textcolor", SelectedFinal, param1)) + CPrintToChat(param1, "{green}[{red}C{green}C{blue}C{green}]{default} Successfully set your {green}text color{default} to: \x07%s%s", Selected, SelectedFinal); + + Menu_ChatColor(param1); + } +} + +// 88888888888 d8888 .d8888b. .d8888b. 8888888888 88888888888 88888888888 8888888 888b 888 .d8888b. +// 888 d88888 d88P Y88b d88P Y88b 888 888 888 888 8888b 888 d88P Y88b +// 888 d88P888 888 888 Y88b. 888 888 888 888 88888b 888 888 888 +// 888 d88P 888 888 "Y888b. 8888888 888 888 888 888Y88b 888 888 +// 888 d88P 888 888 88888 "Y88b. 888 888 888 888 888 Y88b888 888 88888 +// 888 d88P 888 888 888 "888 888 888 888 888 888 Y88888 888 888 +// 888 d8888888888 Y88b d88P Y88b d88P 888 888 888 888 888 Y8888 Y88b d88P +// 888 d88P 888 "Y8888P88 "Y8888P" 8888888888 888 888 8888888 888 Y888 "Y8888P88 + +ClearValues(client) +{ + Format(g_sTag[client], sizeof(g_sTag[]), ""); + Format(g_sTagColor[client], sizeof(g_sTagColor[]), ""); + Format(g_sUsernameColor[client], sizeof(g_sUsernameColor[]), ""); + Format(g_sChatColor[client], sizeof(g_sChatColor[]), ""); + + Format(g_sDefaultTag[client], sizeof(g_sDefaultTag[]), ""); + Format(g_sDefaultTagColor[client], sizeof(g_sDefaultTagColor[]), ""); + Format(g_sDefaultUsernameColor[client], sizeof(g_sDefaultUsernameColor[]), ""); + Format(g_sDefaultChatColor[client], sizeof(g_sDefaultChatColor[]), ""); +} + +public OnClientConnected(client) +{ + Format(g_sReceivedChatInput[client], sizeof(g_sReceivedChatInput[]), ""); + Format(g_sInputType[client], sizeof(g_sInputType[]), ""); + Format(g_sATargetSID[client], sizeof(g_sATargetSID[]), ""); + g_bWaitingForChatInput[client] = false; + g_bTagToggled[client] = false; + g_iATarget[client] = 0; + + ClearValues(client); +} + +public OnClientDisconnect(client) +{ + Format(g_sReceivedChatInput[client], sizeof(g_sReceivedChatInput[]), ""); + Format(g_sInputType[client], sizeof(g_sInputType[]), ""); + Format(g_sATargetSID[client], sizeof(g_sATargetSID[]), ""); + g_bWaitingForChatInput[client] = false; + g_bTagToggled[client] = false; + g_iATarget[client] = 0; + + ClearValues(client); +} + +public OnClientPostAdminCheck(client) +{ + if (!ConfigForward(client)) + { + return; + } + + decl String:auth[32]; + GetClientAuthId(client, AuthId_Steam2, auth, sizeof(auth)); + KvRewind(g_hConfigFile); + + if (!KvJumpToKey(g_hConfigFile, auth)) + { + KvRewind(g_hConfigFile); + KvGotoFirstSubKey(g_hConfigFile); + + new AdminId:admin = GetUserAdmin(client); + new AdminFlag:flag; + decl String:configFlag[2]; + decl String:section[32]; + new bool:found = false; + + do + { + KvGetSectionName(g_hConfigFile, section, sizeof(section)); + KvGetString(g_hConfigFile, "flag", configFlag, sizeof(configFlag)); + + if (strlen(configFlag) > 1) + { + LogError("Multiple flags given in section \"%s\", which is not allowed. Using first character.", section); + } + + if (strlen(configFlag) == 0 && StrContains(section, "STEAM_", false) == -1 && StrContains(section, "[U:1:", false) == -1) + { + found = true; + break; + } + + if (!FindFlagByChar(configFlag[0], flag)) + { + if (strlen(configFlag) > 0) + { + LogError("Invalid flag given for section \"%s\", skipping", section); + } + + continue; + } + + if (GetAdminFlag(admin, flag)) + { + found = true; + break; + } + } + while (KvGotoNextKey(g_hConfigFile)); + + if (!found) + { + return; + } + } + + decl String:clientTagColor[12]; + decl String:clientNameColor[12]; + decl String:clientChatColor[12]; + + KvGetString(g_hConfigFile, "tag", g_sTag[client], sizeof(g_sTag[])); + KvGetString(g_hConfigFile, "tagcolor", clientTagColor, sizeof(clientTagColor)); + KvGetString(g_hConfigFile, "namecolor", clientNameColor, sizeof(clientNameColor)); + KvGetString(g_hConfigFile, "textcolor", clientChatColor, sizeof(clientChatColor)); + ReplaceString(clientTagColor, sizeof(clientTagColor), "#", ""); + ReplaceString(clientNameColor, sizeof(clientNameColor), "#", ""); + ReplaceString(clientChatColor, sizeof(clientChatColor), "#", ""); + + new tagLen = strlen(clientTagColor); + new nameLen = strlen(clientNameColor); + new chatLen = strlen(clientChatColor); + + if (tagLen == 6 || tagLen == 8 || StrEqual(clientTagColor, "T", false) || StrEqual(clientTagColor, "G", false) || StrEqual(clientTagColor, "O", false) || StrEqual(clientTagColor, "X", false)) + { + strcopy(g_sTagColor[client], sizeof(g_sTagColor[]), clientTagColor); + } + + if (nameLen == 6 || nameLen == 8 || StrEqual(clientNameColor, "G", false) || StrEqual(clientNameColor, "O", false) || StrEqual(clientNameColor, "X", false)) + { + strcopy(g_sUsernameColor[client], sizeof(g_sUsernameColor[]), clientNameColor); + } + + if (chatLen == 6 || chatLen == 8 || StrEqual(clientChatColor, "T", false) || StrEqual(clientChatColor, "G", false) || StrEqual(clientChatColor, "O", false) || StrEqual(clientChatColor, "X", false)) + { + strcopy(g_sChatColor[client], sizeof(g_sChatColor[]), clientChatColor); + } + + strcopy(g_sDefaultTag[client], sizeof(g_sDefaultTag[]), g_sTag[client]); + strcopy(g_sDefaultTagColor[client], sizeof(g_sDefaultTagColor[]), g_sTagColor[client]); + strcopy(g_sDefaultUsernameColor[client], sizeof(g_sDefaultUsernameColor[]), g_sUsernameColor[client]); + strcopy(g_sDefaultChatColor[client], sizeof(g_sDefaultChatColor[]), g_sChatColor[client]); + + Call_StartForward(loadedForward); + Call_PushCell(client); + Call_Finish(); +} +/* +public Action:OnChatMessage(&author, Handle:recipients, String:name[], String:message[]) +{ + //new bFlags = GetMessageFlags(); + new iMaxMessageLength = MAXLENGTH_MESSAGE - strlen(name) - 5; // MAXLENGTH_MESSAGE = maximum characters in a chat message, including name. Subtract the characters in the name, and 5 to account for the colon, spaces, and null terminator + + //PrintToServer("%N: %s (%d)", author, message, GetMessageFlags()); + + if (message[0] == '>' && GetConVarInt(g_hGreenText) > 0) + Format(message, iMaxMessageLength, "\x0714C800%s", message); + + if (!g_bTagToggled[author]) + { + if (CheckForward(author, message, CCC_NameColor)) + { + if (StrEqual(g_sUsernameColor[author], "G", false)) + Format(name, MAXLENGTH_NAME, "\x04%s", name); + else if (StrEqual(g_sUsernameColor[author], "O", false)) + Format(name, MAXLENGTH_NAME, "\x05%s", name); + else if (StrEqual(g_sUsernameColor[author], "X", false)) + Format(name, MAXLENGTH_NAME, "", name); + else if (strlen(g_sUsernameColor[author]) == 6) + Format(name, MAXLENGTH_NAME, "\x07%s%s", g_sUsernameColor[author], name); + else if (strlen(g_sUsernameColor[author]) == 8) + Format(name, MAXLENGTH_NAME, "\x08%s%s", g_sUsernameColor[author], name); + else + Format(name, MAXLENGTH_NAME, "\x03%s", name); // team color by default! + } + else + { + Format(name, MAXLENGTH_NAME, "\x03%s", name); // team color by default! + } + + if (CheckForward(author, message, CCC_TagColor)) + { + if (strlen(g_sTag[author]) > 0) + { + if (StrEqual(g_sTagColor[author], "T", false)) + Format(name, MAXLENGTH_NAME, "\x03%s%s", g_sTag[author], name); + else if (StrEqual(g_sTagColor[author], "G", false)) + Format(name, MAXLENGTH_NAME, "\x04%s%s", g_sTag[author], name); + else if (StrEqual(g_sTagColor[author], "O", false)) + Format(name, MAXLENGTH_NAME, "\x05%s%s", g_sTag[author], name); + else if (strlen(g_sTagColor[author]) == 6) + Format(name, MAXLENGTH_NAME, "\x07%s%s%s", g_sTagColor[author], g_sTag[author], name); + else if (strlen(g_sTagColor[author]) == 8) + Format(name, MAXLENGTH_NAME, "\x08%s%s%s", g_sTagColor[author], g_sTag[author], name); + else + Format(name, MAXLENGTH_NAME, "\x01%s%s", g_sTag[author], name); + } + } + + if (strlen(g_sChatColor[author]) > 0 && CheckForward(author, message, CCC_ChatColor)) + { + if (StrEqual(g_sChatColor[author], "T", false)) + Format(message, iMaxMessageLength, "\x03%s", message); + else if (StrEqual(g_sChatColor[author], "G", false)) + Format(message, iMaxMessageLength, "\x04%s", message); + else if (StrEqual(g_sChatColor[author], "O", false)) + Format(message, iMaxMessageLength, "\x05%s", message); + else if (strlen(g_sChatColor[author]) == 6) + Format(message, iMaxMessageLength, "\x07%s%s", g_sChatColor[author], message); + else if (strlen(g_sChatColor[author]) == 8) + Format(message, iMaxMessageLength, "\x08%s%s", g_sChatColor[author], message); + } + } + + decl String:sGame[64]; + GetGameFolderName(sGame, sizeof(sGame)); + + if (StrEqual(sGame, "csgo")) + Format(name, MAXLENGTH_NAME, "\x01\x0B%s", name); + + Call_StartForward(messageForward); + Call_PushCell(author); + Call_PushStringEx(message, iMaxMessageLength, SM_PARAM_STRING_UTF8|SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK); + Call_PushCell(iMaxMessageLength); + Call_Finish(); + + return Plugin_Changed; +} +*/ + +public Action:Hook_UserMessage(UserMsg:msg_id, Handle:bf, const players[], playersNum, bool:reliable, bool:init) +{ + new String:sAuthorTag[64]; + g_msgAuthor = BfReadByte(bf); + g_msgIsChat = bool:BfReadByte(bf); + BfReadString(bf, g_msgName, sizeof(g_msgName), false); + BfReadString(bf, g_msgSender, sizeof(g_msgSender), false); + BfReadString(bf, g_msgText, sizeof(g_msgText), false); + CCC_GetTag(g_msgAuthor, sAuthorTag, sizeof(sAuthorTag)); + new bool:bNameAlpha; + new bool:bChatAlpha; + new bool:bTagAlpha; + new xiNameColor = CCC_GetColor(g_msgAuthor, CCC_ColorType:CCC_NameColor, bNameAlpha); + new xiChatColor = CCC_GetColor(g_msgAuthor, CCC_ColorType:CCC_ChatColor, bChatAlpha); + new xiTagColor = CCC_GetColor(g_msgAuthor, CCC_ColorType:CCC_TagColor, bTagAlpha); + + if (xiNameColor == COLOR_CGREEN) + { + Format(g_msgSender, sizeof(g_msgSender), "\x04%s", g_msgSender); + } + else if (xiNameColor == COLOR_OLIVE) + { + Format(g_msgSender, sizeof(g_msgSender), "\x05%s", g_msgSender); + } + else if (xiNameColor == COLOR_TEAM) + { + Format(g_msgSender, sizeof(g_msgSender), "\x03%s", g_msgSender); + } + else if (xiNameColor == COLOR_NULL) + { + Format(g_msgSender, sizeof(g_msgSender), "", g_msgSender); + } + else if (!bNameAlpha) + { + Format(g_msgSender, sizeof(g_msgSender), "\x07%06X%s", xiNameColor, g_msgSender); + } + else + { + Format(g_msgSender, sizeof(g_msgSender), "\x08%08X%s", xiNameColor, g_msgSender); + } + + if(strlen(sAuthorTag) > 0) + { + if (xiTagColor == COLOR_TEAM) + { + Format(g_msgSender, sizeof(g_msgSender), "\x03%s%s", sAuthorTag, g_msgSender); + } + else if (xiTagColor == COLOR_CGREEN) + { + Format(g_msgSender, sizeof(g_msgSender), "\x04%s%s", sAuthorTag, g_msgSender); + } + else if (xiTagColor == COLOR_OLIVE) + { + Format(g_msgSender, sizeof(g_msgSender), "\x05%s%s", sAuthorTag, g_msgSender); + } + else if (xiTagColor == COLOR_NONE) + { + Format(g_msgSender, sizeof(g_msgSender), "\x01%s%s", sAuthorTag, g_msgSender); + } + else if (!bNameAlpha) + { + Format(g_msgSender, sizeof(g_msgSender), "\x07%06X%s%s", xiTagColor, sAuthorTag, g_msgSender); + } + else + { + Format(g_msgSender, sizeof(g_msgSender), "\x08%08X%s%s", xiTagColor, sAuthorTag, g_msgSender); + } + } + + if (g_msgText[0] == '>' && GetConVarInt(g_hGreenText) > 0) + { + Format(g_msgText, sizeof(g_msgText), "\x0714C800%s", g_msgText); + } + else if (xiChatColor == COLOR_TEAM) + { + Format(g_msgText, sizeof(g_msgText), "\x03%s", g_msgText); + } + else if (xiChatColor == COLOR_CGREEN) + { + Format(g_msgText, sizeof(g_msgText), "\x04%s", g_msgText); + } + else if (xiChatColor == COLOR_OLIVE) + { + Format(g_msgText, sizeof(g_msgText), "\x05%s", g_msgText); + } + else if (xiChatColor == COLOR_NONE) + { + } + else if (!bNameAlpha) + { + Format(g_msgText, sizeof(g_msgText), "\x07%06X%s", xiChatColor, g_msgText); + } + else + { + Format(g_msgText, sizeof(g_msgText), "\x08%08X%s", xiChatColor, g_msgText); + } + + Format(g_msgFinal, sizeof(g_msgFinal), "%t", g_msgName, g_msgSender, g_msgText); + + return Plugin_Handled; +} + +// 888b 888 d8888 88888888888 8888888 888 888 8888888888 .d8888b. +// 8888b 888 d88888 888 888 888 888 888 d88P Y88b +// 88888b 888 d88P888 888 888 888 888 888 Y88b. +// 888Y88b 888 d88P 888 888 888 Y88b d88P 8888888 "Y888b. +// 888 Y88b888 d88P 888 888 888 Y88b d88P 888 "Y88b. +// 888 Y88888 d88P 888 888 888 Y88o88P 888 "888 +// 888 Y8888 d8888888888 888 888 Y888P 888 Y88b d88P +// 888 Y888 d88P 888 888 8888888 Y8P 8888888888 "Y8888P" + +stock bool:CheckForward(author, const String:message[], CCC_ColorType:type) +{ + new Action:result = Plugin_Continue; + + Call_StartForward(applicationForward); + Call_PushCell(author); + Call_PushString(message); + Call_PushCell(type); + Call_Finish(result); + + if (result >= Plugin_Handled) + return false; + + // Compatibility + switch(type) + { + case CCC_TagColor: return TagForward(author); + case CCC_NameColor: return NameForward(author); + case CCC_ChatColor: return ColorForward(author); + } + + return true; +} + +stock bool:ColorForward(author) +{ + new Action:result = Plugin_Continue; + + Call_StartForward(colorForward); + Call_PushCell(author); + Call_Finish(result); + + if (result >= Plugin_Handled) + return false; + + return true; +} + +stock bool:NameForward(author) +{ + new Action:result = Plugin_Continue; + + Call_StartForward(nameForward); + Call_PushCell(author); + Call_Finish(result); + + if (result >= Plugin_Handled) + return false; + + return true; +} + +stock bool:TagForward(author) +{ + new Action:result = Plugin_Continue; + + Call_StartForward(tagForward); + Call_PushCell(author); + Call_Finish(result); + + if (result >= Plugin_Handled) + return false; + + return true; +} + +stock bool:ConfigForward(client) +{ + new Action:result = Plugin_Continue; + + Call_StartForward(preLoadedForward); + Call_PushCell(client); + Call_Finish(result); + + if (result >= Plugin_Handled) + return false; + + return true; +} + +public Native_GetColor(Handle:plugin, numParams) +{ + new client = GetNativeCell(1); + + if (client < 1 || client > MaxClients || !IsClientInGame(client)) + { + ThrowNativeError(SP_ERROR_PARAM, "Invalid client or client is not in game"); + return COLOR_NONE; + } + + switch(GetNativeCell(2)) + { + case CCC_TagColor: + { + if (StrEqual(g_sTagColor[client], "T", false)) + { + SetNativeCellRef(3, false); + return COLOR_TEAM; + } + else if (StrEqual(g_sTagColor[client], "G", false)) + { + SetNativeCellRef(3, false); + return COLOR_CGREEN; + } + else if (StrEqual(g_sTagColor[client], "O", false)) + { + SetNativeCellRef(3, false); + return COLOR_OLIVE; + } + else if (strlen(g_sTagColor[client]) == 6 || strlen(g_sTagColor[client]) == 8) + { + SetNativeCellRef(3, strlen(g_sTagColor[client]) == 8); + return StringToInt(g_sTagColor[client], 16); + } + else + { + SetNativeCellRef(3, false); + return COLOR_NONE; + } + } + + case CCC_NameColor: + { + if (StrEqual(g_sUsernameColor[client], "G", false)) + { + SetNativeCellRef(3, false); + return COLOR_CGREEN; + } + else if (StrEqual(g_sUsernameColor[client], "X", false)) + { + SetNativeCellRef(3, false); + return COLOR_NULL; + } + else if (StrEqual(g_sUsernameColor[client], "O", false)) + { + SetNativeCellRef(3, false); + return COLOR_OLIVE; + } + else if (strlen(g_sUsernameColor[client]) == 6 || strlen(g_sUsernameColor[client]) == 8) + { + SetNativeCellRef(3, strlen(g_sUsernameColor[client]) == 8); + return StringToInt(g_sUsernameColor[client], 16); + } + else + { + SetNativeCellRef(3, false); + return COLOR_TEAM; + } + } + + case CCC_ChatColor: + { + if (StrEqual(g_sChatColor[client], "T", false)) + { + SetNativeCellRef(3, false); + return COLOR_TEAM; + } + else if (StrEqual(g_sChatColor[client], "G", false)) + { + SetNativeCellRef(3, false); + return COLOR_CGREEN; + } + else if (StrEqual(g_sChatColor[client], "O", false)) + { + SetNativeCellRef(3, false); + return COLOR_OLIVE; + } + else if (strlen(g_sChatColor[client]) == 6 || strlen(g_sChatColor[client]) == 8) + { + SetNativeCellRef(3, strlen(g_sChatColor[client]) == 8); + return StringToInt(g_sChatColor[client], 16); + } + else + { + SetNativeCellRef(3, false); + return COLOR_NONE; + } + } + } + + return COLOR_NONE; +} + +public Native_SetColor(Handle:plugin, numParams) +{ + new client = GetNativeCell(1); + + if (client < 1 || client > MaxClients || !IsClientInGame(client)) + { + ThrowNativeError(SP_ERROR_PARAM, "Invalid client or client is not in game"); + return false; + } + + decl String:color[32]; + + if (GetNativeCell(3) < 0) + { + switch (GetNativeCell(3)) + { + case COLOR_CGREEN: + { + Format(color, sizeof(color), "G"); + } + case COLOR_OLIVE: + { + Format(color, sizeof(color), "O"); + } + case COLOR_TEAM: + { + Format(color, sizeof(color), "T"); + } + case COLOR_NULL: + { + Format(color, sizeof(color), "X"); + } + case COLOR_NONE: + { + Format(color, sizeof(color), ""); + } + } + } + else + { + if (!GetNativeCell(4)) + { + // No alpha + Format(color, sizeof(color), "%06X", GetNativeCell(3)); + } + else + { + // Alpha specified + Format(color, sizeof(color), "%08X", GetNativeCell(3)); + } + } + + if (strlen(color) != 6 && strlen(color) != 8 && !StrEqual(color, "G", false) && !StrEqual(color, "O", false) && !StrEqual(color, "T", false) && !StrEqual(color, "X", false)) + { + return false; + } + + switch(GetNativeCell(2)) + { + case CCC_TagColor: + { + strcopy(g_sTagColor[client], sizeof(g_sTagColor[]), color); + } + case CCC_NameColor: + { + strcopy(g_sUsernameColor[client], sizeof(g_sUsernameColor[]), color); + } + case CCC_ChatColor: + { + strcopy(g_sChatColor[client], sizeof(g_sChatColor[]), color); + } + } + + return true; +} + +public Native_GetTag(Handle:plugin, numParams) +{ + new client = GetNativeCell(1); + + if (client < 1 || client > MaxClients || !IsClientInGame(client)) + { + ThrowNativeError(SP_ERROR_PARAM, "Invalid client or client is not in game"); + return; + } + + SetNativeString(2, g_sTag[client], GetNativeCell(3)); +} + +public Native_SetTag(Handle:plugin, numParams) +{ + new client = GetNativeCell(1); + + if (client < 1 || client > MaxClients || !IsClientInGame(client)) + { + ThrowNativeError(SP_ERROR_PARAM, "Invalid client or client is not in game"); + return; + } + + GetNativeString(2, g_sTag[client], sizeof(g_sTag[])); +} + +public Native_ResetColor(Handle:plugin, numParams) +{ + new client = GetNativeCell(1); + + if (client < 1 || client > MaxClients || !IsClientInGame(client)) + { + ThrowNativeError(SP_ERROR_PARAM, "Invalid client or client is not in game"); + return; + } + + switch(GetNativeCell(2)) + { + case CCC_TagColor: + { + strcopy(g_sTagColor[client], sizeof(g_sTagColor[]), g_sDefaultTagColor[client]); + } + case CCC_NameColor: + { + strcopy(g_sUsernameColor[client], sizeof(g_sUsernameColor[]), g_sDefaultUsernameColor[client]); + } + case CCC_ChatColor: + { + strcopy(g_sChatColor[client], sizeof(g_sChatColor[]), g_sDefaultChatColor[client]); + } + } +} + +public Native_ResetTag(Handle:plugin, numParams) +{ + new client = GetNativeCell(1); + + if (client < 1 || client > MaxClients || !IsClientInGame(client)) + { + ThrowNativeError(SP_ERROR_PARAM, "Invalid client or client is not in game"); + return; + } + + strcopy(g_sTag[client], sizeof(g_sTag[]), g_sDefaultTag[client]); +} + +public Native_UpdateIgnoredArray(Handle:plugin, numParams) +{ + GetNativeArray(1, g_Ignored, sizeof(g_Ignored)); + + return true; +} \ No newline at end of file diff --git a/custom-chatcolors/scripting/include/ccc.inc b/custom-chatcolors/scripting/include/ccc.inc new file mode 100644 index 00000000..10480068 --- /dev/null +++ b/custom-chatcolors/scripting/include/ccc.inc @@ -0,0 +1,190 @@ +/** + * This is the include file for Custom Chat Colors + * https://forums.alliedmods.net/showthread.php?t=186695 + * To check that Custom Chat Colors is installed and running, verify that the "ccc" library exists + */ + +#if defined _ccc_included + #endinput +#endif +#define _ccc_included + +enum CCC_ColorType { + CCC_TagColor, + CCC_NameColor, + CCC_ChatColor +}; + +#define COLOR_NULL -1 +#define COLOR_NONE -2 +#define COLOR_CGREEN -3 //0x40FF40 +#define COLOR_OLIVE -4 //0x99FF99 +#define COLOR_TEAM -5 + +/** + * Gets a client's color as a hexadecimal integer. + * + * @param client Client index + * @param type Color type to retreive + * @param alpha Pass a boolean variable by reference here and it will be true if the color has alpha specified or false if it doesn't (or is a stock color) + * @return Color as a hexadecimal integer (use %X in formatting to get a hexadecimal string) + * + * On error/errors: Invalid client index or client is not in game + */ +native CCC_GetColor(client, CCC_ColorType:type, &bool:alpha = false); + +/** + * Sets a client's color as a hexadecimal integer. + * + * @param client Client index + * @param type Color type to set + * @param color Integer representation of the color (use StringToInt(input, 16) to convert a hexadecimal string) or one of the color defines + * @param alpha Are you specifying a color with alpha? + * @return True if the color is updated successfully, false otherwise + * + * On error/errors: Invalid client index or client is not in game + */ +native bool:CCC_SetColor(client, CCC_ColorType:type, color, bool:alpha); + +/** + * Gets a client's tag + * + * @param client Client index + * @param buffer Buffer to store the tag in + * @param maxlen Maximum buffer length + * @noreturn + * + * On error/errors: Invalid client index or client is not in game + */ +native CCC_GetTag(client, String:buffer[], maxlen); + +/** + * Sets a client's tag + * + * @param client Client index + * @param tag String containing the new tag + * @noreturn + * + * On error/errors: Invalid client index or client is not in game + */ +native CCC_SetTag(client, const String:tag[]); + +/** + * Resets a client's color to the value in the config file. + * + * @param client Client index + * @param type Color type to restore + * @noreturn + * + * On error/errors: Invalid client index or client is not in game + */ +native CCC_ResetColor(client, CCC_ColorType:type); + +/** + * Resets a client's tag to the value in the config file. + * + * @param client Client index + * @noreturn + * + * On error/errors: Invalid client index or client is not in game + */ +native CCC_ResetTag(client); + +/** + * Called when a cilent's name is about to be colored + * DO NOT START A NEW USERMESSAGE (i.e. PrintToChat, PrintToChatAll) WITHIN THIS FORWARD + * + * @param client Client index + * @return Plugin_Handled to prevent coloring, Plugin_Continue to allow coloring + */ +#pragma deprecated Use CCC_OnColor instead +forward Action:CCC_OnNameColor(client); + +/** + * Called when a client's chat is about to be colored + * DO NOT START A NEW USERMESSAGE (i.e. PrintToChat, PrintToChatAll) WITHIN THIS FORWARD + * + * @param client Client index + * @return Plugin_Handled to prevent coloring, Plugin_Continue to allow coloring + */ +#pragma deprecated Use CCC_OnColor instead +forward Action:CCC_OnChatColor(client); + +/** + * Called when a client's name is about to be tagged + * DO NOT START A NEW USERMESSAGE (i.e. PrintToChat, PrintToChatAll) WITHIN THIS FORWARD + * + * @param client Client index + * @return Plugin_Handled to prevent tagging, Plugin_Continue to allow tagging + */ +#pragma deprecated Use CCC_OnColor instead +forward Action:CCC_OnTagApplied(client); + +/** + * Called when a client's name is about to be tagged + * DO NOT START A NEW USERMESSAGE (i.e. PrintToChat, PrintToChatAll) WITHIN THIS FORWARD + * + * @param client Client index + * @param message Chat message that will be printed + * @param type What type of color will be applied. If this is CCC_TagColor, it controls whether the tag will be applied at all, not whether the tag will be colored. + * @return Plugin_Handled to prevent coloring, Plugin_Continue to allow coloring + */ +forward Action:CCC_OnColor(client, const String:message[], CCC_ColorType:type); + +/** + * Called when a message has been fully colored and will be sent, unless further plugins modify it through Simple Chat Processor + * + * @param author Author client index + * @param message Message + * @param maxlen Maximum length of message buffer + * @noreturn + */ +forward CCC_OnChatMessage(author, String:message[], maxlen); + +/** + * Called when a client's colors and tag are about to be loaded from the config file + * At this point, the client has NO COLORS + * + * @param client Client index + * @return Plugin_Handled or Plugin_Stop to prevent loading, Plugin_Continue or Plugin_Changed to allow + */ +forward Action:CCC_OnUserConfigPreLoaded(client); + +/** + * Called when a client's colors and tag have been loaded from the config file + * + * @param client Client index + * @noreturn + */ +forward CCC_OnUserConfigLoaded(client); + +/** + * Called when the configuration file is reloaded with the sm_reloadccc command + * + * @noreturn + */ +forward CCC_OnConfigReloaded(); + +native void CCC_UpdateIgnoredArray(bool IgnoredArray[(MAXPLAYERS + 1) * (MAXPLAYERS + 1)]); + +public SharedPlugin:__pl_ccc = { + name = "ccc", + file = "custom-chatcolors.smx", +#if defined REQUIRE_PLUGIN + required = 1 +#else + required = 0 +#endif +}; + +#if !defined REQUIRE_PLUGIN +public __pl_ccc_SetNTVOptional() { + MarkNativeAsOptional("CCC_GetColor"); + MarkNativeAsOptional("CCC_SetColor"); + MarkNativeAsOptional("CCC_GetTag"); + MarkNativeAsOptional("CCC_ResetTag"); + MarkNativeAsOptional("CCC_ResetColor"); + MarkNativeAsOptional("CCC_ResetTag"); + MarkNativeAsOptional("CCC_UpdateIgnoredArray"); +} +#endif \ No newline at end of file diff --git a/custom-chatcolors/scripting/include/morecolors.inc b/custom-chatcolors/scripting/include/morecolors.inc new file mode 120000 index 00000000..cd0d80e6 --- /dev/null +++ b/custom-chatcolors/scripting/include/morecolors.inc @@ -0,0 +1 @@ +../../../includes/morecolors.inc \ No newline at end of file diff --git a/custom-chatcolors/translations/allchat.phrases.txt b/custom-chatcolors/translations/allchat.phrases.txt new file mode 100644 index 00000000..8dacd4d4 --- /dev/null +++ b/custom-chatcolors/translations/allchat.phrases.txt @@ -0,0 +1,53 @@ +"Phrases" +{ + "Cstrike_Chat_CT_Loc" + { + "#format" "{1:s},{2:s}" + "en" "(Counter-Terrorist) {1} : {2}" + } + "Cstrike_Chat_CT" + { + "#format" "{1:s},{2:s}" + "en" "(Counter-Terrorist) {1} : {2}" + } + "Cstrike_Chat_T_Loc" + { + "#format" "{1:s},{2:s}" + "en" "(Terrorist) {1} : {2}" + } + "Cstrike_Chat_T" + { + "#format" "{1:s},{2:s}" + "en" "(Terrorist) {1} : {2}" + } + "Cstrike_Chat_CT_Dead" + { + "#format" "{1:s},{2:s}" + "en" "*DEAD*(Counter-Terrorist) {1} : {2}" + } + "Cstrike_Chat_T_Dead" + { + "#format" "{1:s},{2:s}" + "en" "*DEAD*(Terrorist) {1} : {2}" + } + "Cstrike_Chat_Spec" + { + "#format" "{1:s},{2:s}" + "en" "(Spectator) {1} : {2}" + } + "Cstrike_Chat_All" + { + "#format" "{1:s},{2:s}" + "en" "{1} : {2}" + } + "Cstrike_Chat_AllDead" + { + "#format" "{1:s},{2:s}" + "en" "*DEAD* {1} : {2}" + } + "Cstrike_Chat_AllSpec" + { + "#format" "{1:s},{2:s}" + "en" "*SPEC* {1} : {2}" + } +} \ No newline at end of file diff --git a/includes/SteamWorks.inc b/includes/SteamWorks.inc new file mode 100644 index 00000000..2fcd8e51 --- /dev/null +++ b/includes/SteamWorks.inc @@ -0,0 +1,324 @@ +#if defined _SteamWorks_Included + #endinput +#endif +#define _SteamWorks_Included + +/* results from UserHasLicenseForApp */ +enum EUserHasLicenseForAppResult +{ + k_EUserHasLicenseResultHasLicense = 0, // User has a license for specified app + k_EUserHasLicenseResultDoesNotHaveLicense = 1, // User does not have a license for the specified app + k_EUserHasLicenseResultNoAuth = 2, // User has not been authenticated +}; + +/* General result codes */ +enum EResult +{ + k_EResultOK = 1, // success + k_EResultFail = 2, // generic failure + k_EResultNoConnection = 3, // no/failed network connection +// k_EResultNoConnectionRetry = 4, // OBSOLETE - removed + k_EResultInvalidPassword = 5, // password/ticket is invalid + k_EResultLoggedInElsewhere = 6, // same user logged in elsewhere + k_EResultInvalidProtocolVer = 7, // protocol version is incorrect + k_EResultInvalidParam = 8, // a parameter is incorrect + k_EResultFileNotFound = 9, // file was not found + k_EResultBusy = 10, // called method busy - action not taken + k_EResultInvalidState = 11, // called object was in an invalid state + k_EResultInvalidName = 12, // name is invalid + k_EResultInvalidEmail = 13, // email is invalid + k_EResultDuplicateName = 14, // name is not unique + k_EResultAccessDenied = 15, // access is denied + k_EResultTimeout = 16, // operation timed out + k_EResultBanned = 17, // VAC2 banned + k_EResultAccountNotFound = 18, // account not found + k_EResultInvalidSteamID = 19, // steamID is invalid + k_EResultServiceUnavailable = 20, // The requested service is currently unavailable + k_EResultNotLoggedOn = 21, // The user is not logged on + k_EResultPending = 22, // Request is pending (may be in process, or waiting on third party) + k_EResultEncryptionFailure = 23, // Encryption or Decryption failed + k_EResultInsufficientPrivilege = 24, // Insufficient privilege + k_EResultLimitExceeded = 25, // Too much of a good thing + k_EResultRevoked = 26, // Access has been revoked (used for revoked guest passes) + k_EResultExpired = 27, // License/Guest pass the user is trying to access is expired + k_EResultAlreadyRedeemed = 28, // Guest pass has already been redeemed by account, cannot be acked again + k_EResultDuplicateRequest = 29, // The request is a duplicate and the action has already occurred in the past, ignored this time + k_EResultAlreadyOwned = 30, // All the games in this guest pass redemption request are already owned by the user + k_EResultIPNotFound = 31, // IP address not found + k_EResultPersistFailed = 32, // failed to write change to the data store + k_EResultLockingFailed = 33, // failed to acquire access lock for this operation + k_EResultLogonSessionReplaced = 34, + k_EResultConnectFailed = 35, + k_EResultHandshakeFailed = 36, + k_EResultIOFailure = 37, + k_EResultRemoteDisconnect = 38, + k_EResultShoppingCartNotFound = 39, // failed to find the shopping cart requested + k_EResultBlocked = 40, // a user didn't allow it + k_EResultIgnored = 41, // target is ignoring sender + k_EResultNoMatch = 42, // nothing matching the request found + k_EResultAccountDisabled = 43, + k_EResultServiceReadOnly = 44, // this service is not accepting content changes right now + k_EResultAccountNotFeatured = 45, // account doesn't have value, so this feature isn't available + k_EResultAdministratorOK = 46, // allowed to take this action, but only because requester is admin + k_EResultContentVersion = 47, // A Version mismatch in content transmitted within the Steam protocol. + k_EResultTryAnotherCM = 48, // The current CM can't service the user making a request, user should try another. + k_EResultPasswordRequiredToKickSession = 49,// You are already logged in elsewhere, this cached credential login has failed. + k_EResultAlreadyLoggedInElsewhere = 50, // You are already logged in elsewhere, you must wait + k_EResultSuspended = 51, // Long running operation (content download) suspended/paused + k_EResultCancelled = 52, // Operation canceled (typically by user: content download) + k_EResultDataCorruption = 53, // Operation canceled because data is ill formed or unrecoverable + k_EResultDiskFull = 54, // Operation canceled - not enough disk space. + k_EResultRemoteCallFailed = 55, // an remote call or IPC call failed + k_EResultPasswordUnset = 56, // Password could not be verified as it's unset server side + k_EResultExternalAccountUnlinked = 57, // External account (PSN, Facebook...) is not linked to a Steam account + k_EResultPSNTicketInvalid = 58, // PSN ticket was invalid + k_EResultExternalAccountAlreadyLinked = 59, // External account (PSN, Facebook...) is already linked to some other account, must explicitly request to replace/delete the link first + k_EResultRemoteFileConflict = 60, // The sync cannot resume due to a conflict between the local and remote files + k_EResultIllegalPassword = 61, // The requested new password is not legal + k_EResultSameAsPreviousValue = 62, // new value is the same as the old one ( secret question and answer ) + k_EResultAccountLogonDenied = 63, // account login denied due to 2nd factor authentication failure + k_EResultCannotUseOldPassword = 64, // The requested new password is not legal + k_EResultInvalidLoginAuthCode = 65, // account login denied due to auth code invalid + k_EResultAccountLogonDeniedNoMail = 66, // account login denied due to 2nd factor auth failure - and no mail has been sent + k_EResultHardwareNotCapableOfIPT = 67, // + k_EResultIPTInitError = 68, // + k_EResultParentalControlRestricted = 69, // operation failed due to parental control restrictions for current user + k_EResultFacebookQueryError = 70, // Facebook query returned an error + k_EResultExpiredLoginAuthCode = 71, // account login denied due to auth code expired + k_EResultIPLoginRestrictionFailed = 72, + k_EResultAccountLockedDown = 73, + k_EResultAccountLogonDeniedVerifiedEmailRequired = 74, + k_EResultNoMatchingURL = 75, + k_EResultBadResponse = 76, // parse failure, missing field, etc. + k_EResultRequirePasswordReEntry = 77, // The user cannot complete the action until they re-enter their password + k_EResultValueOutOfRange = 78 // the value entered is outside the acceptable range +}; + +/* This enum is used in client API methods, do not re-number existing values. */ +enum EHTTPMethod +{ + k_EHTTPMethodInvalid = 0, + k_EHTTPMethodGET, + k_EHTTPMethodHEAD, + k_EHTTPMethodPOST, + k_EHTTPMethodPUT, + k_EHTTPMethodDELETE, + k_EHTTPMethodOPTIONS, + + // The remaining HTTP methods are not yet supported, per rfc2616 section 5.1.1 only GET and HEAD are required for + // a compliant general purpose server. We'll likely add more as we find uses for them. + + // k_EHTTPMethodTRACE, + // k_EHTTPMethodCONNECT +}; + + +/* HTTP Status codes that the server can send in response to a request, see rfc2616 section 10.3 for descriptions + of each of these. */ +enum EHTTPStatusCode +{ + // Invalid status code (this isn't defined in HTTP, used to indicate unset in our code) + k_EHTTPStatusCodeInvalid = 0, + + // Informational codes + k_EHTTPStatusCode100Continue = 100, + k_EHTTPStatusCode101SwitchingProtocols = 101, + + // Success codes + k_EHTTPStatusCode200OK = 200, + k_EHTTPStatusCode201Created = 201, + k_EHTTPStatusCode202Accepted = 202, + k_EHTTPStatusCode203NonAuthoritative = 203, + k_EHTTPStatusCode204NoContent = 204, + k_EHTTPStatusCode205ResetContent = 205, + k_EHTTPStatusCode206PartialContent = 206, + + // Redirection codes + k_EHTTPStatusCode300MultipleChoices = 300, + k_EHTTPStatusCode301MovedPermanently = 301, + k_EHTTPStatusCode302Found = 302, + k_EHTTPStatusCode303SeeOther = 303, + k_EHTTPStatusCode304NotModified = 304, + k_EHTTPStatusCode305UseProxy = 305, + //k_EHTTPStatusCode306Unused = 306, (used in old HTTP spec, now unused in 1.1) + k_EHTTPStatusCode307TemporaryRedirect = 307, + + // Error codes + k_EHTTPStatusCode400BadRequest = 400, + k_EHTTPStatusCode401Unauthorized = 401, // You probably want 403 or something else. 401 implies you're sending a WWW-Authenticate header and the client can sent an Authorization header in response. + k_EHTTPStatusCode402PaymentRequired = 402, // This is reserved for future HTTP specs, not really supported by clients + k_EHTTPStatusCode403Forbidden = 403, + k_EHTTPStatusCode404NotFound = 404, + k_EHTTPStatusCode405MethodNotAllowed = 405, + k_EHTTPStatusCode406NotAcceptable = 406, + k_EHTTPStatusCode407ProxyAuthRequired = 407, + k_EHTTPStatusCode408RequestTimeout = 408, + k_EHTTPStatusCode409Conflict = 409, + k_EHTTPStatusCode410Gone = 410, + k_EHTTPStatusCode411LengthRequired = 411, + k_EHTTPStatusCode412PreconditionFailed = 412, + k_EHTTPStatusCode413RequestEntityTooLarge = 413, + k_EHTTPStatusCode414RequestURITooLong = 414, + k_EHTTPStatusCode415UnsupportedMediaType = 415, + k_EHTTPStatusCode416RequestedRangeNotSatisfiable = 416, + k_EHTTPStatusCode417ExpectationFailed = 417, + k_EHTTPStatusCode4xxUnknown = 418, // 418 is reserved, so we'll use it to mean unknown + k_EHTTPStatusCode429TooManyRequests = 429, + + // Server error codes + k_EHTTPStatusCode500InternalServerError = 500, + k_EHTTPStatusCode501NotImplemented = 501, + k_EHTTPStatusCode502BadGateway = 502, + k_EHTTPStatusCode503ServiceUnavailable = 503, + k_EHTTPStatusCode504GatewayTimeout = 504, + k_EHTTPStatusCode505HTTPVersionNotSupported = 505, + k_EHTTPStatusCode5xxUnknown = 599, +}; + +native bool:SteamWorks_IsVACEnabled(); +native bool:SteamWorks_GetPublicIP(ipaddr[4]); +native SteamWorks_GetPublicIPCell(); +native bool:SteamWorks_IsLoaded(); +native bool:SteamWorks_SetGameDescription(String:sDesc[]); +native bool:SteamWorks_IsConnected(); +native bool:SteamWorks_SetRule(const String:sKey[], const String:sValue[]); +native bool:SteamWorks_ClearRules(); +native bool:SteamWorks_ForceHeartbeat(); +native bool:SteamWorks_GetUserGroupStatus(client, groupid); +native bool:SteamWorks_GetUserGroupStatusAuthID(authid, groupid); + +native EUserHasLicenseForAppResult:SteamWorks_HasLicenseForApp(client, app); +native SteamWorks_GetClientSteamID(client, String:sSteamID[], length); + +native bool:SteamWorks_RequestStatsAuthID(authid, appid); +native bool:SteamWorks_RequestStats(client, appid); +native bool:SteamWorks_GetStatCell(client, const String:sKey[], &value); +native bool:SteamWorks_GetStatAuthIDCell(authid, const String:sKey[], &value); +native bool:SteamWorks_GetStatFloat(client, const String:sKey[], &Float:value); +native bool:SteamWorks_GetStatAuthIDFloat(authid, const String:sKey[], &Float:value); + +native Handle:SteamWorks_CreateHTTPRequest(EHTTPMethod:method, const String:sURL[]); +native bool:SteamWorks_SetHTTPRequestContextValue(Handle:hHandle, any:data1, any:data2=0); +native bool:SteamWorks_SetHTTPRequestNetworkActivityTimeout(Handle:hHandle, timeout); +native bool:SteamWorks_SetHTTPRequestHeaderValue(Handle:hHandle, const String:sName[], const String:sValue[]); +native bool:SteamWorks_SetHTTPRequestGetOrPostParameter(Handle:hHandle, const String:sName[], const String:sValue[]); + +funcenum SteamWorksHTTPRequestCompleted +{ + public(Handle:hRequest, bool:bFailure, bool:bRequestSuccessful, EHTTPStatusCode:eStatusCode), + public(Handle:hRequest, bool:bFailure, bool:bRequestSuccessful, EHTTPStatusCode:eStatusCode, any:data1), + public(Handle:hRequest, bool:bFailure, bool:bRequestSuccessful, EHTTPStatusCode:eStatusCode, any:data1, any:data2) +}; + +funcenum SteamWorksHTTPHeadersReceived +{ + public(Handle:hRequest, bool:bFailure), + public(Handle:hRequest, bool:bFailure, any:data1), + public(Handle:hRequest, bool:bFailure, any:data1, any:data2) +}; + +funcenum SteamWorksHTTPDataReceived +{ + public(Handle:hRequest, bool:bFailure, offset, bytesreceived), + public(Handle:hRequest, bool:bFailure, offset, bytesreceived, any:data1), + public(Handle:hRequest, bool:bFailure, offset, bytesreceived, any:data1, any:data2) +}; + +native bool:SteamWorks_SetHTTPCallbacks(Handle:hHandle, SteamWorksHTTPRequestCompleted:fCompleted = INVALID_FUNCTION, SteamWorksHTTPHeadersReceived:fHeaders = INVALID_FUNCTION, SteamWorksHTTPDataReceived:fData = INVALID_FUNCTION, Handle:hCalling = INVALID_HANDLE); +native bool:SteamWorks_SendHTTPRequest(Handle:hRequest); +native bool:SteamWorks_SendHTTPRequestAndStreamResponse(Handle:hRequest); +native bool:SteamWorks_DeferHTTPRequest(Handle:hRequest); +native bool:SteamWorks_PrioritizeHTTPRequest(Handle:hRequest); +native bool:SteamWorks_GetHTTPResponseHeaderSize(Handle:hRequest, const String:sHeader[], &size); +native bool:SteamWorks_GetHTTPResponseHeaderValue(Handle:hRequest, const String:sHeader[], String:sValue[], size); +native bool:SteamWorks_GetHTTPResponseBodySize(Handle:hRequest, &size); +native bool:SteamWorks_GetHTTPResponseBodyData(Handle:hRequest, String:sBody[], length); +native bool:SteamWorks_GetHTTPStreamingResponseBodyData(Handle:hRequest, cOffset, String:sBody[], length); +native bool:SteamWorks_GetHTTPDownloadProgressPct(Handle:hRequest, &Float:percent); +native bool:SteamWorks_SetHTTPRequestRawPostBody(Handle:hRequest, const String:sContentType[], const String:sBody[], bodylen); + +funcenum SteamWorksHTTPBodyCallback +{ + public(const String:sData[]), + public(const String:sData[], any:value), + public(const data[], any:value, datalen) +}; + +native bool:SteamWorks_GetHTTPResponseBodyCallback(Handle:hRequest, SteamWorksHTTPBodyCallback:fCallback, any:data = 0, Handle:hPlugin = INVALID_HANDLE); +native bool:SteamWorks_WriteHTTPResponseBodyToFile(Handle:hRequest, const String:sFileName[]); + +forward SW_OnValidateClient(ownerauthid, authid); +forward SteamWorks_OnValidateClient(ownerauthid, authid); +forward SteamWorks_SteamServersConnected(); +forward SteamWorks_SteamServersConnectFailure(EResult:result); +forward SteamWorks_SteamServersDisconnected(EResult:result); + +forward Action:SteamWorks_RestartRequested(); +forward SteamWorks_TokenRequested(String:sToken[], maxlen); + +forward SteamWorks_OnClientGroupStatus(authid, groupid, bool:isMember, bool:isOfficer); + +public Extension:__ext_SteamWorks = +{ + name = "SteamWorks", + file = "SteamWorks.ext", +#if defined AUTOLOAD_EXTENSIONS + autoload = 1, +#else + autoload = 0, +#endif +#if defined REQUIRE_EXTENSIONS + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_EXTENSIONS +public __ext_SteamWorks_SetNTVOptional() +{ + MarkNativeAsOptional("SteamWorks_IsVACEnabled"); + MarkNativeAsOptional("SteamWorks_GetPublicIP"); + MarkNativeAsOptional("SteamWorks_GetPublicIPCell"); + MarkNativeAsOptional("SteamWorks_IsLoaded"); + MarkNativeAsOptional("SteamWorks_SetGameDescription"); + MarkNativeAsOptional("SteamWorks_IsConnected"); + MarkNativeAsOptional("SteamWorks_SetRule"); + MarkNativeAsOptional("SteamWorks_ClearRules"); + MarkNativeAsOptional("SteamWorks_ForceHeartbeat"); + MarkNativeAsOptional("SteamWorks_GetUserGroupStatus"); + MarkNativeAsOptional("SteamWorks_GetUserGroupStatusAuthID"); + + MarkNativeAsOptional("SteamWorks_HasLicenseForApp"); + MarkNativeAsOptional("SteamWorks_GetClientSteamID"); + + MarkNativeAsOptional("SteamWorks_RequestStatsAuthID"); + MarkNativeAsOptional("SteamWorks_RequestStats"); + MarkNativeAsOptional("SteamWorks_GetStatCell"); + MarkNativeAsOptional("SteamWorks_GetStatAuthIDCell"); + MarkNativeAsOptional("SteamWorks_GetStatFloat"); + MarkNativeAsOptional("SteamWorks_GetStatAuthIDFloat"); + + MarkNativeAsOptional("SteamWorks_CreateHTTPRequest"); + MarkNativeAsOptional("SteamWorks_SetHTTPRequestContextValue"); + MarkNativeAsOptional("SteamWorks_SetHTTPRequestNetworkActivityTimeout"); + MarkNativeAsOptional("SteamWorks_SetHTTPRequestHeaderValue"); + MarkNativeAsOptional("SteamWorks_SetHTTPRequestGetOrPostParameter"); + + MarkNativeAsOptional("SteamWorks_SetHTTPCallbacks"); + MarkNativeAsOptional("SteamWorks_SendHTTPRequest"); + MarkNativeAsOptional("SteamWorks_SendHTTPRequestAndStreamResponse"); + MarkNativeAsOptional("SteamWorks_DeferHTTPRequest"); + MarkNativeAsOptional("SteamWorks_PrioritizeHTTPRequest"); + MarkNativeAsOptional("SteamWorks_GetHTTPResponseHeaderSize"); + MarkNativeAsOptional("SteamWorks_GetHTTPResponseHeaderValue"); + MarkNativeAsOptional("SteamWorks_GetHTTPResponseBodySize"); + MarkNativeAsOptional("SteamWorks_GetHTTPResponseBodyData"); + MarkNativeAsOptional("SteamWorks_GetHTTPStreamingResponseBodyData"); + MarkNativeAsOptional("SteamWorks_GetHTTPDownloadProgressPct"); + MarkNativeAsOptional("SteamWorks_SetHTTPRequestRawPostBody"); + + MarkNativeAsOptional("SteamWorks_GetHTTPResponseBodyCallback"); + MarkNativeAsOptional("SteamWorks_WriteHTTPResponseBodyToFile"); +} +#endif diff --git a/includes/colors.inc b/includes/colors.inc new file mode 100644 index 00000000..523f6f6a --- /dev/null +++ b/includes/colors.inc @@ -0,0 +1,539 @@ +/************************************************************************** + * * + * Colored Chat Functions * + * Author: exvel, Editor: Popoklopsi, Powerlord, Bara * + * Version: 1.1.3 * + * * + **************************************************************************/ + + +#if defined _colors_included + #endinput +#endif +#define _colors_included + +#define MAX_MESSAGE_LENGTH 250 +#define MAX_COLORS 12 + +#define SERVER_INDEX 0 +#define NO_INDEX -1 +#define NO_PLAYER -2 + +enum Colors +{ + Color_Default = 0, + Color_Darkred, + Color_Green, + Color_Lightgreen, + Color_Red, + Color_Blue, + Color_Olive, + Color_Lime, + Color_Lightred, + Color_Purple, + Color_Grey, + Color_Orange +} + +/* Colors' properties */ +new String:CTag[][] = {"{default}", "{darkred}", "{green}", "{lightgreen}", "{red}", "{blue}", "{olive}", "{lime}", "{lightred}", "{purple}", "{grey}", "{orange}"}; +new String:CTagCode[][] = {"\x01", "\x02", "\x04", "\x03", "\x03", "\x03", "\x05", "\x06", "\x07", "\x03", "\x08", "\x09"}; +new bool:CTagReqSayText2[] = {false, false, false, true, true, true, false, false, false, false, false, false}; +new bool:CEventIsHooked = false; +new bool:CSkipList[MAXPLAYERS+1] = {false,...}; + +/* Game default profile */ +new bool:CProfile_Colors[] = {true, false, true, false, false, false, false, false, false, false, false, false}; +new CProfile_TeamIndex[] = {NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX}; +new bool:CProfile_SayText2 = false; + +/** + * Prints a message to a specific client in the chat area. + * Supports color tags. + * + * @param client Client index. + * @param szMessage Message (formatting rules). + * @return No return + * + * On error/Errors: If the client is not connected an error will be thrown. + */ +stock CPrintToChat(client, const String:szMessage[], any:...) +{ + if (client <= 0 || client > MaxClients) + ThrowError("Invalid client index %d", client); + + if (!IsClientInGame(client)) + ThrowError("Client %d is not in game", client); + + decl String:szBuffer[MAX_MESSAGE_LENGTH]; + decl String:szCMessage[MAX_MESSAGE_LENGTH]; + + SetGlobalTransTarget(client); + + Format(szBuffer, sizeof(szBuffer), "\x01%s", szMessage); + VFormat(szCMessage, sizeof(szCMessage), szBuffer, 3); + + new index = CFormat(szCMessage, sizeof(szCMessage)); + + if (index == NO_INDEX) + PrintToChat(client, "%s", szCMessage); + else + CSayText2(client, index, szCMessage); +} + +stock CReplyToCommand(client, const String:szMessage[], any:...) +{ + + decl String:szCMessage[MAX_MESSAGE_LENGTH]; + VFormat(szCMessage, sizeof(szCMessage), szMessage, 3); + + if (client == 0) + { + CRemoveTags(szCMessage, sizeof(szCMessage)); + PrintToServer("%s", szCMessage); + } + else if (GetCmdReplySource() == SM_REPLY_TO_CONSOLE) + { + CRemoveTags(szCMessage, sizeof(szCMessage)); + PrintToConsole(client, "%s", szCMessage); + } + else + { + CPrintToChat(client, "%s", szCMessage); + } +} + + +/** + * Prints a message to all clients in the chat area. + * Supports color tags. + * + * @param client Client index. + * @param szMessage Message (formatting rules) + * @return No return + */ +stock CPrintToChatAll(const String:szMessage[], any:...) +{ + decl String:szBuffer[MAX_MESSAGE_LENGTH]; + + for (new i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i) && !IsFakeClient(i) && !CSkipList[i]) + { + SetGlobalTransTarget(i); + VFormat(szBuffer, sizeof(szBuffer), szMessage, 2); + + CPrintToChat(i, "%s", szBuffer); + } + + CSkipList[i] = false; + } +} + +/** + * Prints a message to a specific client in the chat area. + * Supports color tags and teamcolor tag. + * + * @param client Client index. + * @param author Author index whose color will be used for teamcolor tag. + * @param szMessage Message (formatting rules). + * @return No return + * + * On error/Errors: If the client or author are not connected an error will be thrown. + */ +stock CPrintToChatEx(client, author, const String:szMessage[], any:...) +{ + if (client <= 0 || client > MaxClients) + ThrowError("Invalid client index %d", client); + + if (!IsClientInGame(client)) + ThrowError("Client %d is not in game", client); + + if (author < 0 || author > MaxClients) + ThrowError("Invalid client index %d", author); + + decl String:szBuffer[MAX_MESSAGE_LENGTH]; + decl String:szCMessage[MAX_MESSAGE_LENGTH]; + + SetGlobalTransTarget(client); + + Format(szBuffer, sizeof(szBuffer), "\x01%s", szMessage); + VFormat(szCMessage, sizeof(szCMessage), szBuffer, 4); + + new index = CFormat(szCMessage, sizeof(szCMessage), author); + + if (index == NO_INDEX) + PrintToChat(client, "%s", szCMessage); + else + CSayText2(client, author, szCMessage); +} + +/** + * Prints a message to all clients in the chat area. + * Supports color tags and teamcolor tag. + * + * @param author Author index whos color will be used for teamcolor tag. + * @param szMessage Message (formatting rules). + * @return No return + * + * On error/Errors: If the author is not connected an error will be thrown. + */ +stock CPrintToChatAllEx(author, const String:szMessage[], any:...) +{ + if (author < 0 || author > MaxClients) + ThrowError("Invalid client index %d", author); + + if (!IsClientInGame(author)) + ThrowError("Client %d is not in game", author); + + decl String:szBuffer[MAX_MESSAGE_LENGTH]; + + for (new i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i) && !IsFakeClient(i) && !CSkipList[i]) + { + SetGlobalTransTarget(i); + VFormat(szBuffer, sizeof(szBuffer), szMessage, 3); + + CPrintToChatEx(i, author, "%s", szBuffer); + } + + CSkipList[i] = false; + } +} + +/** + * Removes color tags from the string. + * + * @param szMessage String. + * @return No return + */ +stock CRemoveTags(String:szMessage[], maxlength) +{ + for (new i = 0; i < MAX_COLORS; i++) + ReplaceString(szMessage, maxlength, CTag[i], "", false); + + ReplaceString(szMessage, maxlength, "{teamcolor}", "", false); +} + +/** + * Checks whether a color is allowed or not + * + * @param tag Color Tag. + * @return True when color is supported, otherwise false + */ +stock CColorAllowed(Colors:color) +{ + if (!CEventIsHooked) + { + CSetupProfile(); + + CEventIsHooked = true; + } + + return CProfile_Colors[color]; +} + +/** + * Replace the color with another color + * Handle with care! + * + * @param color color to replace. + * @param newColor color to replace with. + * @noreturn + */ +stock CReplaceColor(Colors:color, Colors:newColor) +{ + if (!CEventIsHooked) + { + CSetupProfile(); + + CEventIsHooked = true; + } + + CProfile_Colors[color] = CProfile_Colors[newColor]; + CProfile_TeamIndex[color] = CProfile_TeamIndex[newColor]; + + CTagReqSayText2[color] = CTagReqSayText2[newColor]; + Format(CTagCode[color], sizeof(CTagCode[]), CTagCode[newColor]) +} + +/** + * This function should only be used right in front of + * CPrintToChatAll or CPrintToChatAllEx and it tells + * to those funcions to skip specified client when printing + * message to all clients. After message is printed client will + * no more be skipped. + * + * @param client Client index + * @return No return + */ +stock CSkipNextClient(client) +{ + if (client <= 0 || client > MaxClients) + ThrowError("Invalid client index %d", client); + + CSkipList[client] = true; +} + +/** + * Replaces color tags in a string with color codes + * + * @param szMessage String. + * @param maxlength Maximum length of the string buffer. + * @return Client index that can be used for SayText2 author index + * + * On error/Errors: If there is more then one team color is used an error will be thrown. + */ +stock CFormat(String:szMessage[], maxlength, author=NO_INDEX) +{ + decl String:szGameName[30]; + + GetGameFolderName(szGameName, sizeof(szGameName)); + + /* Hook event for auto profile setup on map start */ + if (!CEventIsHooked) + { + CSetupProfile(); + HookEvent("server_spawn", CEvent_MapStart, EventHookMode_PostNoCopy); + + CEventIsHooked = true; + } + + new iRandomPlayer = NO_INDEX; + + // On CS:GO set invisible precolor + if (StrEqual(szGameName, "csgo", false)) + Format(szMessage, maxlength, " \x01\x0B\x01%s", szMessage); + + /* If author was specified replace {teamcolor} tag */ + if (author != NO_INDEX) + { + if (CProfile_SayText2) + { + ReplaceString(szMessage, maxlength, "{teamcolor}", "\x03", false); + + iRandomPlayer = author; + } + /* If saytext2 is not supported by game replace {teamcolor} with green tag */ + else + ReplaceString(szMessage, maxlength, "{teamcolor}", CTagCode[Color_Green], false); + } + else + ReplaceString(szMessage, maxlength, "{teamcolor}", "", false); + + /* For other color tags we need a loop */ + for (new i = 0; i < MAX_COLORS; i++) + { + /* If tag not found - skip */ + if (StrContains(szMessage, CTag[i], false) == -1) + continue; + + /* If tag is not supported by game replace it with green tag */ + else if (!CProfile_Colors[i]) + ReplaceString(szMessage, maxlength, CTag[i], CTagCode[Color_Green], false); + + /* If tag doesn't need saytext2 simply replace */ + else if (!CTagReqSayText2[i]) + ReplaceString(szMessage, maxlength, CTag[i], CTagCode[i], false); + + /* Tag needs saytext2 */ + else + { + /* If saytext2 is not supported by game replace tag with green tag */ + if (!CProfile_SayText2) + ReplaceString(szMessage, maxlength, CTag[i], CTagCode[Color_Green], false); + + /* Game supports saytext2 */ + else + { + /* If random player for tag wasn't specified replace tag and find player */ + if (iRandomPlayer == NO_INDEX) + { + /* Searching for valid client for tag */ + iRandomPlayer = CFindRandomPlayerByTeam(CProfile_TeamIndex[i]); + + /* If player not found replace tag with green color tag */ + if (iRandomPlayer == NO_PLAYER) + ReplaceString(szMessage, maxlength, CTag[i], CTagCode[Color_Green], false); + + /* If player was found simply replace */ + else + ReplaceString(szMessage, maxlength, CTag[i], CTagCode[i], false); + + } + /* If found another team color tag throw error */ + else + { + //ReplaceString(szMessage, maxlength, CTag[i], ""); + ThrowError("Using two team colors in one message is not allowed"); + } + } + + } + } + + return iRandomPlayer; +} + +/** + * Founds a random player with specified team + * + * @param color_team Client team. + * @return Client index or NO_PLAYER if no player found + */ +stock CFindRandomPlayerByTeam(color_team) +{ + if (color_team == SERVER_INDEX) + return 0; + else + { + for (new i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i) && GetClientTeam(i) == color_team) + return i; + } + } + + return NO_PLAYER; +} + +/** + * Sends a SayText2 usermessage to a client + * + * @param szMessage Client index + * @param maxlength Author index + * @param szMessage Message + * @return No return. + */ +stock CSayText2(client, author, const String:szMessage[]) +{ + new Handle:hBuffer = StartMessageOne("SayText2", client, USERMSG_RELIABLE|USERMSG_BLOCKHOOKS); + + if(GetFeatureStatus(FeatureType_Native, "GetUserMessageType") == FeatureStatus_Available && GetUserMessageType() == UM_Protobuf) + { + PbSetInt(hBuffer, "ent_idx", author); + PbSetBool(hBuffer, "chat", true); + PbSetString(hBuffer, "msg_name", szMessage); + PbAddString(hBuffer, "params", ""); + PbAddString(hBuffer, "params", ""); + PbAddString(hBuffer, "params", ""); + PbAddString(hBuffer, "params", ""); + } + else + { + BfWriteByte(hBuffer, author); + BfWriteByte(hBuffer, true); + BfWriteString(hBuffer, szMessage); + } + + EndMessage(); +} + +/** + * Creates game color profile + * This function must be edited if you want to add more games support + * + * @return No return. + */ +stock CSetupProfile() +{ + decl String:szGameName[30]; + GetGameFolderName(szGameName, sizeof(szGameName)); + + if (StrEqual(szGameName, "cstrike", false)) + { + CProfile_Colors[Color_Lightgreen] = true; + CProfile_Colors[Color_Red] = true; + CProfile_Colors[Color_Blue] = true; + CProfile_Colors[Color_Olive] = true; + CProfile_TeamIndex[Color_Lightgreen] = SERVER_INDEX; + CProfile_TeamIndex[Color_Red] = 2; + CProfile_TeamIndex[Color_Blue] = 3; + CProfile_SayText2 = true; + } + else if (StrEqual(szGameName, "csgo", false)) + { + CProfile_Colors[Color_Red] = true; + CProfile_Colors[Color_Blue] = true; + CProfile_Colors[Color_Olive] = true; + CProfile_Colors[Color_Darkred] = true; + CProfile_Colors[Color_Lime] = true; + CProfile_Colors[Color_Lightred] = true; + CProfile_Colors[Color_Purple] = true; + CProfile_Colors[Color_Grey] = true; + CProfile_Colors[Color_Orange] = true; + CProfile_TeamIndex[Color_Red] = 2; + CProfile_TeamIndex[Color_Blue] = 3; + CProfile_SayText2 = true; + } + else if (StrEqual(szGameName, "tf", false)) + { + CProfile_Colors[Color_Lightgreen] = true; + CProfile_Colors[Color_Red] = true; + CProfile_Colors[Color_Blue] = true; + CProfile_Colors[Color_Olive] = true; + CProfile_TeamIndex[Color_Lightgreen] = SERVER_INDEX; + CProfile_TeamIndex[Color_Red] = 2; + CProfile_TeamIndex[Color_Blue] = 3; + CProfile_SayText2 = true; + } + else if (StrEqual(szGameName, "left4dead", false) || StrEqual(szGameName, "left4dead2", false)) + { + CProfile_Colors[Color_Lightgreen] = true; + CProfile_Colors[Color_Red] = true; + CProfile_Colors[Color_Blue] = true; + CProfile_Colors[Color_Olive] = true; + CProfile_TeamIndex[Color_Lightgreen] = SERVER_INDEX; + CProfile_TeamIndex[Color_Red] = 3; + CProfile_TeamIndex[Color_Blue] = 2; + CProfile_SayText2 = true; + } + else if (StrEqual(szGameName, "hl2mp", false)) + { + /* hl2mp profile is based on mp_teamplay convar */ + if (GetConVarBool(FindConVar("mp_teamplay"))) + { + CProfile_Colors[Color_Red] = true; + CProfile_Colors[Color_Blue] = true; + CProfile_Colors[Color_Olive] = true; + CProfile_TeamIndex[Color_Red] = 3; + CProfile_TeamIndex[Color_Blue] = 2; + CProfile_SayText2 = true; + } + else + { + CProfile_SayText2 = false; + CProfile_Colors[Color_Olive] = true; + } + } + else if (StrEqual(szGameName, "dod", false)) + { + CProfile_Colors[Color_Olive] = true; + CProfile_SayText2 = false; + } + /* Profile for other games */ + else + { + if (GetUserMessageId("SayText2") == INVALID_MESSAGE_ID) + { + CProfile_SayText2 = false; + } + else + { + CProfile_Colors[Color_Red] = true; + CProfile_Colors[Color_Blue] = true; + CProfile_TeamIndex[Color_Red] = 2; + CProfile_TeamIndex[Color_Blue] = 3; + CProfile_SayText2 = true; + } + } +} + +public Action:CEvent_MapStart(Handle:event, const String:name[], bool:dontBroadcast) +{ + CSetupProfile(); + + for (new i = 1; i <= MaxClients; i++) + CSkipList[i] = false; +} \ No newline at end of file diff --git a/includes/dhooks.inc b/includes/dhooks.inc new file mode 100644 index 00000000..571a0aab --- /dev/null +++ b/includes/dhooks.inc @@ -0,0 +1,482 @@ +#if defined _dhooks_included +#endinput +#endif +#define _dhooks_included + +enum ObjectValueType +{ + ObjectValueType_Int = 0, + ObjectValueType_Bool, + ObjectValueType_Ehandle, + ObjectValueType_Float, + ObjectValueType_CBaseEntityPtr, + ObjectValueType_IntPtr, + ObjectValueType_BoolPtr, + ObjectValueType_EhandlePtr, + ObjectValueType_FloatPtr, + ObjectValueType_Vector, + ObjectValueType_VectorPtr, + ObjectValueType_CharPtr, + ObjectValueType_String +}; + +enum ListenType +{ + ListenType_Created, + ListenType_Deleted +}; + +enum ReturnType +{ + ReturnType_Unknown, + ReturnType_Void, + ReturnType_Int, + ReturnType_Bool, + ReturnType_Float, + ReturnType_String, //Note this is a string_t + ReturnType_StringPtr, //Note this is a string_t * + ReturnType_CharPtr, + ReturnType_Vector, + ReturnType_VectorPtr, + ReturnType_CBaseEntity, + ReturnType_Edict +}; + +enum HookParamType +{ + HookParamType_Unknown, + HookParamType_Int, + HookParamType_Bool, + HookParamType_Float, + HookParamType_String, //Note this is a string_t + HookParamType_StringPtr, //Note this is a string_t * + HookParamType_CharPtr, + HookParamType_VectorPtr, + HookParamType_CBaseEntity, + HookParamType_ObjectPtr, + HookParamType_Edict, + HookParamType_Object +}; + +enum ThisPointerType +{ + ThisPointer_Ignore, + ThisPointer_CBaseEntity, + ThisPointer_Address +}; + +enum HookType +{ + HookType_Entity, + HookType_GameRules, + HookType_Raw +}; + +enum MRESReturn +{ + MRES_ChangedHandled = -2, // Use changed values and return MRES_Handled + MRES_ChangedOverride, // Use changed values and return MRES_Override + MRES_Ignored, // plugin didn't take any action + MRES_Handled, // plugin did something, but real function should still be called + MRES_Override, // call real function, but use my return value + MRES_Supercede // skip real function; use my return value +}; + +enum DHookPassFlag +{ + DHookPass_ByVal = (1<<0), + DHookPass_ByRef = (1<<1) +}; + +typeset ListenCB +{ + //Deleted + function void (int entity); + + //Created + function void (int entity, const char[] classname); +}; + +typeset DHookRemovalCB +{ + function void (int hookid); +}; +typeset DHookCallback +{ + //Function Example: void Ham::Test() with this pointer ignore + function MRESReturn (); + + //Function Example: void Ham::Test() with this pointer passed + function MRESReturn (int pThis); + + //Function Example: void Ham::Test(int cake) with this pointer ignore + function MRESReturn (Handle hParams); + + //Function Example: void Ham::Test(int cake) with this pointer passed + function MRESReturn (int pThis, Handle hParams); + + //Function Example: int Ham::Test() with this pointer ignore + function MRESReturn (Handle hReturn); + + //Function Example: int Ham::Test() with this pointer passed + function MRESReturn (int pThis, Handle hReturn); + + //Function Example: int Ham::Test(int cake) with this pointer ignore + function MRESReturn (Handle hReturn, Handle hParams); + + //Function Example: int Ham::Test(int cake) with this pointer passed + function MRESReturn (int pThis, Handle hReturn, Handle hParams); + + //Address NOW + + //Function Example: void Ham::Test() with this pointer passed + function MRESReturn (Address pThis); + + //Function Example: void Ham::Test(int cake) with this pointer passed + function MRESReturn (Address pThis, Handle hParams); + + //Function Example: int Ham::Test() with this pointer passed + function MRESReturn (Address pThis, Handle hReturn); + + //Function Example: int Ham::Test(int cake) with this pointer passed + function MRESReturn (Address pThis, Handle hReturn, Handle hParams); + +}; + +/* Adds an entity listener hook + * + * @param type Type of listener to add + * @param callback Callback to use + * + * @noreturn +*/ +native void DHookAddEntityListener(ListenType type, ListenCB callback); + +/* Removes an entity listener hook + * + * @param type Type of listener to remove + * @param callback Callback this listener was using + * + * @return True if one was removed false otherwise. +*/ +native bool DHookRemoveEntityListener(ListenType type, ListenCB callback); + +/* Creates a hook + * + * @param offset vtable offset for function to hook + * @param hooktype Type of hook + * @param returntype Type type of return + * @param thistype Type of this pointer or ignore (ignore can be used if not needed) + * @param callback Callback function + * + * @return Returns setup handle for the hook or INVALID_HANDLE. +*/ +native Handle DHookCreate(int offset, HookType hooktype, ReturnType returntype, ThisPointerType thistype, DHookCallback callback); + +/* Adds param to a hook setup + * + * @param setup Setup handle to add the param to. + * @param type Param type + * @param size Used for Objects (not Object ptr) to define the size of the object. + * @param flag Used to change the pass type. + * + * @error Invalid setup handle or too many params added (request upping the max in thread) + * @noreturn +*/ +native void DHookAddParam(Handle setup, HookParamType type, int size=-1, DHookPassFlag flag=DHookPass_ByVal); +//native DHookAddParam(Handle:setup, HookParamType:type); + +/* Hook entity + * + * @param setup Setup handle to use to add the hook. + * @param post True to make the hook a post hook. (If you need to change the retunr value or need the return value use a post hook! If you need to change params and return use a pre and post hook!) + * @param entity Entity index to hook on. + * @param removalcb Callback for when the hook is removed (Entity hooks are auto-removed on entity destroyed and will call this callback) + * + * @error Invalid setup handle, invalid entity or invalid hook type. + * @return -1 on fail a hookid on success +*/ +native int DHookEntity(Handle setup, bool post, int entity, DHookRemovalCB removalcb=INVALID_FUNCTION); + +/* Hook gamerules + * + * @param setup Setup handle to use to add the hook. + * @param post True to make the hook a post hook. (If you need to change the retunr value or need the return value use a post hook! If you need to change params and return use a pre and post hook!) + * @param removalcb Callback for when the hook is removed (Game rules hooks are auto-removed on map end and will call this callback) + * + * @error Invalid setup handle, failing to get gamerules pointer or invalid hook type. + * @return -1 on fail a hookid on success +*/ +native int DHookGamerules(Handle setup, bool post, DHookRemovalCB removalcb=INVALID_FUNCTION); + +/* Hook a raw pointer + * + * @param setup Setup handle to use to add the hook. + * @param post True to make the hook a post hook. (If you need to change the retunr value or need the return value use a post hook! If you need to change params and return use a pre and post hook!) + * @param addr This pointer address. + * @param removalcb Callback for when the hook is removed (Entity hooks are auto-removed on entity destroyed and will call this callback) + * + * @error Invalid setup handle, invalid address or invalid hook type. + * @return -1 on fail a hookid on success +*/ +native int DHookRaw(Handle setup, bool post, Address addr, DHookRemovalCB removalcb=INVALID_FUNCTION); + +/* Remove hook by hook id + * + * @param hookid Hook id to remove + * + * @return true on success false otherwise + * @note This will not fire the removal callback! +*/ +native bool DHookRemoveHookID(int hookid); + +/* Get param value (Only use for: int, entity, bool or float param types) + * + * @param hParams Handle to params structure + * @param num Param number to get. (Example if the function has 2 params and you need the value of the first param num would be 1. 0 Will return the number of params stored) + * + * @error Invalid handle. Invalid param number. Invalid param type. + * @return value if num greater than 0. If 0 returns paramcount. +*/ +native any DHookGetParam(Handle hParams, int num); + +/* Get vector param value + * + * @param hParams Handle to params structure + * @param num Param number to get. (Example if the function has 2 params and you need the value of the first param num would be 1.) + * @param vec Vector buffer to store result. + * + * @error Invalid handle. Invalid param number. Invalid param type. + * @noreturn +*/ +native void DHookGetParamVector(Handle hParams, int num, float vec[3]); + +/* Get string param value + * + * @param hParams Handle to params structure + * @param num Param number to get. (Example if the function has 2 params and you need the value of the first param num would be 1.) + * @param buffer String buffer to store result + * @param size Buffer size + * + * @error Invalid handle. Invalid param number. Invalid param type. + * @noreturn +*/ +native void DHookGetParamString(Handle hParams, int num, char[] buffer, int size); + +/* Set param value (Only use for: int, entity, bool or float param types) + * + * @param hParams Handle to params structure + * @params num Param number to set (Example if the function has 2 params and you need to set the value of the first param num would be 1.) + * @param value Value to set it as (only pass int, bool, float or entity index) + * + * @error Invalid handle. Invalid param number. Invalid param type. + * @noreturn +*/ +native void DHookSetParam(Handle hParams, int num, any value); + +/* Set vector param value + * + * @param hParams Handle to params structure + * @params num Param number to set (Example if the function has 2 params and you need to set the value of the first param num would be 1.) + * @param vec Value to set vector as. + * + * @error Invalid handle. Invalid param number. Invalid param type. + * @noreturn +*/ +native void DHookSetParamVector(Handle hParams, int num, float vec[3]); + +/* Set string param value + * + * @param hParams Handle to params structure + * @params num Param number to set (Example if the function has 2 params and you need to set the value of the first param num would be 1.) + * @param value Value to set string as. + * + * @error Invalid handle. Invalid param number. Invalid param type. + * @noreturn +*/ +native void DHookSetParamString(Handle hParams, int num, char[] value); + +/* Get return value (Only use for: int, entity, bool or float return types) + * + * @param hReturn Handle to return structure + * + * @error Invalid Handle, invalid type. + * @return Returns default value if prehook returns actual value if post hook. +*/ +native any DHookGetReturn(Handle hReturn); + +/* Get return vector value + * + * @param hReturn Handle to return structure + * @param vec Vector buffer to store result in. (In pre hooks will be default value (0.0,0.0,0.0)) + * + * @error Invalid Handle, invalid type. + * @noreturn +*/ +native void DHookGetReturnVector(Handle hReturn, float vec[3]); + +/* Get return string value + * + * @param hReturn Handle to return structure + * @param buffer String buffer to store result in. (In pre hooks will be default value "") + * @param size String buffer size + * + * @error Invalid Handle, invalid type. + * @noreturn +*/ +native void DHookGetReturnString(Handle hReturn, char[] buffer, int size); + +/* Set return value (Only use for: int, entity, bool or float return types) + * + * @param hReturn Handle to return structure + * @param value Value to set return as + * + * @error Invalid Handle, invalid type. + * @noreturn +*/ +native void DHookSetReturn(Handle hReturn, any value); + +/* Set return vector value + * + * @param hReturn Handle to return structure + * @param vec Value to set return vector as + * + * @error Invalid Handle, invalid type. + * @noreturn +*/ +native void DHookSetReturnVector(Handle hReturn, float vec[3]); + +/* Set return string value + * + * @param hReturn Handle to return structure + * @param value Value to set return string as + * + * @error Invalid Handle, invalid type. + * @noreturn +*/ +native void DHookSetReturnString(Handle hReturn, char[] value); + +//WE SHOULD WRAP THESE AROUND STOCKS FOR NON PTR AS WE SUPPORT BOTH WITH THESE NATIVE'S + +/* Gets an objects variable value + * + * @param hParams Handle to params structure + * @param num Param number to get. + * @param offset Offset within the object to the var to get. + * @param type Type of var it is + * + * @error Invalid handle. Invalid param number. Invalid param type. Invalid Object type. + * @return Value of the objects var. If EHANDLE type or entity returns entity index. +*/ +native any DHookGetParamObjectPtrVar(Handle hParams, int num, int offset, ObjectValueType type); + +/* Sets an objects variable value + * + * @param hParams Handle to params structure + * @param num Param number to set. + * @param offset Offset within the object to the var to set. + * @param type Type of var it is + * @param value The value to set the var to. + * + * @error Invalid handle. Invalid param number. Invalid param type. Invalid Object type. + * @noreturn +*/ +native void DHookSetParamObjectPtrVar(Handle hParams, int num, int offset, ObjectValueType type, any value); + +/* Gets an objects vector variable value + * + * @param hParams Handle to params structure + * @param num Param number to get. + * @param offset Offset within the object to the var to get. + * @param type Type of var it is + * @param buffer Buffer to store the result vector + * + * @error Invalid handle. Invalid param number. Invalid param type. Invalid Object type. + * @noreturn +*/ +native void DHookGetParamObjectPtrVarVector(Handle hParams, int num, int offset, ObjectValueType type, float buffer[3]); + +/* Sets an objects vector variable value + * + * @param hParams Handle to params structure + * @param num Param number to set. + * @param offset Offset within the object to the var to set. + * @param type Type of var it is + * @param value The value to set the vector var to. + * + * @error Invalid handle. Invalid param number. Invalid param type. Invalid Object type. + * @noreturn +*/ +native void DHookSetParamObjectPtrVarVector(Handle hParams, int num, int offset, ObjectValueType type, float value[3]); + +/* Gets an objects string variable value + * + * @param hParams Handle to params structure + * @param num Param number to get. + * @param offset Offset within the object to the var to get. + * @param type Type of var it is + * @param buffer Buffer to store the result vector + * @param size Size of the buffer + * + * @error Invalid handle. Invalid param number. Invalid param type. Invalid Object type. + * @noreturn +*/ +native void DHookGetParamObjectPtrString(Handle hParams, int num, int offset, ObjectValueType type, char[] buffer, int size); + +/* Checks if a pointer param is null + * + * @param hParams Handle to params structure + * @param num Param number to check. + * + * @error Non pointer param + * @return True if null false otherwise. +*/ +native bool DHookIsNullParam(Handle hParams, int num); + +public Extension __ext_dhooks = +{ + name = "dhooks", + file = "dhooks.ext", +#if defined AUTOLOAD_EXTENSIONS + autoload = 1, +#else + autoload = 0, +#endif +#if defined REQUIRE_EXTENSIONS + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_EXTENSIONS +public __ext_dhooks_SetNTVOptional() +{ + MarkNativeAsOptional("DHookAddEntityListener"); + MarkNativeAsOptional("DHookRemoveEntityListener"); + MarkNativeAsOptional("DHookCreate"); + MarkNativeAsOptional("DHookAddParam"); + MarkNativeAsOptional("DHookEntity"); + MarkNativeAsOptional("DHookGamerules"); + MarkNativeAsOptional("DHookRaw"); + MarkNativeAsOptional("DHookRemoveHookID"); + MarkNativeAsOptional("DHookGetParam"); + MarkNativeAsOptional("DHookGetParamVector"); + MarkNativeAsOptional("DHookGetParamString"); + MarkNativeAsOptional("DHookSetParam"); + MarkNativeAsOptional("DHookSetParamVector"); + MarkNativeAsOptional("DHookSetParamString"); + MarkNativeAsOptional("DHookGetReturn"); + MarkNativeAsOptional("DHookGetReturnVector"); + MarkNativeAsOptional("DHookGetReturnString"); + MarkNativeAsOptional("DHookSetReturn"); + MarkNativeAsOptional("DHookSetReturnVector"); + MarkNativeAsOptional("DHookSetReturnString"); + MarkNativeAsOptional("DHookGetParamObjectPtrVar"); + MarkNativeAsOptional("DHookSetParamObjectPtrVar"); + MarkNativeAsOptional("DHookGetParamObjectPtrVarVector"); + MarkNativeAsOptional("DHookSetParamObjectPtrVarVector"); + MarkNativeAsOptional("DHookIsNullParam"); + MarkNativeAsOptional("DHookGetParamObjectPtrString"); +} +#endif \ No newline at end of file diff --git a/includes/morecolors.inc b/includes/morecolors.inc new file mode 100644 index 00000000..3a416d8c --- /dev/null +++ b/includes/morecolors.inc @@ -0,0 +1,674 @@ +// MOAR COLORS +// By Dr. McKay +// Inspired by: https://forums.alliedmods.net/showthread.php?t=96831 + +#if defined _colors_included + #endinput +#endif +#define _colors_included + +#include + +#define MORE_COLORS_VERSION "1.9.1" +#define MAX_MESSAGE_LENGTH 256 +#define MAX_BUFFER_LENGTH (MAX_MESSAGE_LENGTH * 4) + +#define COLOR_RED 0xFF4040 +#define COLOR_BLUE 0x99CCFF +#define COLOR_GRAY 0xCCCCCC +#define COLOR_GREEN 0x3EFF3E + +#define GAME_DODS 0 + +new bool:CSkipList[MAXPLAYERS + 1]; +new Handle:CTrie; +new CTeamColors[][] = {{0xCCCCCC, 0x4D7942, 0xFF4040}}; // Multi-dimensional array for games that don't support SayText2. First index is the game index (as defined by the GAME_ defines), second index is team. 0 = spectator, 1 = team1, 2 = team2 + +/** + * Prints a message to a specific client in the chat area. + * Supports color tags. + * + * @param client Client index. + * @param message Message (formatting rules). + * @noreturn + * + * On error/Errors: If the client is not connected an error will be thrown. + */ +stock CPrintToChat(client, const String:message[], any:...) { + CCheckTrie(); + if(client <= 0 || client > MaxClients) { + ThrowError("Invalid client index %i", client); + } + if(!IsClientInGame(client)) { + ThrowError("Client %i is not in game", client); + } + decl String:buffer[MAX_BUFFER_LENGTH], String:buffer2[MAX_BUFFER_LENGTH]; + SetGlobalTransTarget(client); + Format(buffer, sizeof(buffer), "\x01%s", message); + VFormat(buffer2, sizeof(buffer2), buffer, 3); + CReplaceColorCodes(buffer2); + CSendMessage(client, buffer2); +} + +/** + * Prints a message to all clients in the chat area. + * Supports color tags. + * + * @param client Client index. + * @param message Message (formatting rules). + * @noreturn + */ +stock CPrintToChatAll(const String:message[], any:...) { + CCheckTrie(); + decl String:buffer[MAX_BUFFER_LENGTH], String:buffer2[MAX_BUFFER_LENGTH]; + for(new i = 1; i <= MaxClients; i++) { + if(!IsClientInGame(i) || CSkipList[i]) { + CSkipList[i] = false; + continue; + } + SetGlobalTransTarget(i); + Format(buffer, sizeof(buffer), "\x01%s", message); + VFormat(buffer2, sizeof(buffer2), buffer, 2); + CReplaceColorCodes(buffer2); + CSendMessage(i, buffer2); + } +} + +/** + * Prints a message to a specific client in the chat area. + * Supports color tags and teamcolor tag. + * + * @param client Client index. + * @param author Author index whose color will be used for teamcolor tag. + * @param message Message (formatting rules). + * @noreturn + * + * On error/Errors: If the client or author are not connected an error will be thrown + */ +stock CPrintToChatEx(client, author, const String:message[], any:...) { + CCheckTrie(); + if(client <= 0 || client > MaxClients) { + ThrowError("Invalid client index %i", client); + } + if(!IsClientInGame(client)) { + ThrowError("Client %i is not in game", client); + } + if(author <= 0 || author > MaxClients) { + ThrowError("Invalid client index %i", author); + } + if(!IsClientInGame(author)) { + ThrowError("Client %i is not in game", author); + } + decl String:buffer[MAX_BUFFER_LENGTH], String:buffer2[MAX_BUFFER_LENGTH]; + SetGlobalTransTarget(client); + Format(buffer, sizeof(buffer), "\x01%s", message); + VFormat(buffer2, sizeof(buffer2), buffer, 4); + CReplaceColorCodes(buffer2, author); + CSendMessage(client, buffer2, author); +} + +/** + * Prints a message to all clients in the chat area. + * Supports color tags and teamcolor tag. + * + * @param author Author index whose color will be used for teamcolor tag. + * @param message Message (formatting rules). + * @noreturn + * + * On error/Errors: If the author is not connected an error will be thrown. + */ +stock CPrintToChatAllEx(author, const String:message[], any:...) { + CCheckTrie(); + if(author <= 0 || author > MaxClients) { + ThrowError("Invalid client index %i", author); + } + if(!IsClientInGame(author)) { + ThrowError("Client %i is not in game", author); + } + decl String:buffer[MAX_BUFFER_LENGTH], String:buffer2[MAX_BUFFER_LENGTH]; + for(new i = 1; i <= MaxClients; i++) { + if(!IsClientInGame(i) || CSkipList[i]) { + CSkipList[i] = false; + continue; + } + SetGlobalTransTarget(i); + Format(buffer, sizeof(buffer), "\x01%s", message); + VFormat(buffer2, sizeof(buffer2), buffer, 3); + CReplaceColorCodes(buffer2, author); + CSendMessage(i, buffer2, author); + } +} + +/** + * Sends a SayText2 usermessage + * + * @param client Client to send usermessage to + * @param message Message to send + * @noreturn + */ +stock CSendMessage(client, const String:message[], author=0) { + if(author == 0) { + author = client; + } + decl String:buffer[MAX_MESSAGE_LENGTH], String:game[16]; + GetGameFolderName(game, sizeof(game)); + strcopy(buffer, sizeof(buffer), message); + new UserMsg:index = GetUserMessageId("SayText2"); + if(index == INVALID_MESSAGE_ID) { + if(StrEqual(game, "dod")) { + new team = GetClientTeam(author); + if(team == 0) { + ReplaceString(buffer, sizeof(buffer), "\x03", "\x04", false); // Unassigned gets green + } else { + decl String:temp[16]; + Format(temp, sizeof(temp), "\x07%06X", CTeamColors[GAME_DODS][team - 1]); + ReplaceString(buffer, sizeof(buffer), "\x03", temp, false); + } + } + PrintToChat(client, "%s", buffer); + return; + } + new Handle:buf = StartMessageOne("SayText2", client, USERMSG_RELIABLE|USERMSG_BLOCKHOOKS); + if(GetFeatureStatus(FeatureType_Native, "GetUserMessageType") == FeatureStatus_Available && GetUserMessageType() == UM_Protobuf) { + PbSetInt(buf, "ent_idx", author); + PbSetBool(buf, "chat", true); + PbSetString(buf, "msg_name", buffer); + PbAddString(buf, "params", ""); + PbAddString(buf, "params", ""); + PbAddString(buf, "params", ""); + PbAddString(buf, "params", ""); + } else { + BfWriteByte(buf, author); // Message author + BfWriteByte(buf, true); // Chat message + BfWriteString(buf, buffer); // Message text + } + EndMessage(); +} + +/** + * This function should only be used right in front of + * CPrintToChatAll or CPrintToChatAllEx. It causes those functions + * to skip the specified client when printing the message. + * After printing the message, the client will no longer be skipped. + * + * @param client Client index + * @noreturn + */ +stock CSkipNextClient(client) { + if(client <= 0 || client > MaxClients) { + ThrowError("Invalid client index %i", client); + } + CSkipList[client] = true; +} + +/** + * Checks if the colors trie is initialized and initializes it if it's not (used internally) + * + * @return No return + */ +stock CCheckTrie() { + if(CTrie == INVALID_HANDLE) { + CTrie = InitColorTrie(); + } +} + +/** + * Replaces color tags in a string with color codes (used internally by CPrintToChat, CPrintToChatAll, CPrintToChatEx, and CPrintToChatAllEx + * + * @param buffer String. + * @param author Optional client index to use for {teamcolor} tags, or 0 for none + * @param removeTags Optional boolean value to determine whether we're replacing tags with colors, or just removing tags, used by CRemoveTags + * @param maxlen Optional value for max buffer length, used by CRemoveTags + * @noreturn + * + * On error/Errors: If the client index passed for author is invalid or not in game. + */ +stock CReplaceColorCodes(String:buffer[], author=0, bool:removeTags=false, maxlen=MAX_BUFFER_LENGTH) { + CCheckTrie(); + if(!removeTags) { + ReplaceString(buffer, maxlen, "{default}", "\x01", false); + } else { + ReplaceString(buffer, maxlen, "{default}", "", false); + ReplaceString(buffer, maxlen, "{teamcolor}", "", false); + } + if(author != 0 && !removeTags) { + if(author < 0 || author > MaxClients) { + ThrowError("Invalid client index %i", author); + } + if(!IsClientInGame(author)) { + ThrowError("Client %i is not in game", author); + } + ReplaceString(buffer, maxlen, "{teamcolor}", "\x03", false); + } + new cursor = 0; + new value; + decl String:tag[32], String:buff[32], String:output[maxlen]; + strcopy(output, maxlen, buffer); + // Since the string's size is going to be changing, output will hold the replaced string and we'll search buffer + + new Handle:regex = CompileRegex("{[a-zA-Z0-9]+}"); + for(new i = 0; i < 1000; i++) { // The RegEx extension is quite flaky, so we have to loop here :/. This loop is supposed to be infinite and broken by return, but conditions have been added to be safe. + if(MatchRegex(regex, buffer[cursor]) < 1) { + CloseHandle(regex); + strcopy(buffer, maxlen, output); + return; + } + GetRegexSubString(regex, 0, tag, sizeof(tag)); + CStrToLower(tag); + cursor = StrContains(buffer[cursor], tag, false) + cursor + 1; + strcopy(buff, sizeof(buff), tag); + ReplaceString(buff, sizeof(buff), "{", ""); + ReplaceString(buff, sizeof(buff), "}", ""); + + if(!GetTrieValue(CTrie, buff, value)) { + continue; + } + + if(removeTags) { + ReplaceString(output, maxlen, tag, "", false); + } else { + Format(buff, sizeof(buff), "\x07%06X", value); + ReplaceString(output, maxlen, tag, buff, false); + } + } + LogError("[MORE COLORS] Infinite loop broken."); +} + +/** + * Gets a part of a string + * + * @param input String to get the part from + * @param output Buffer to write to + * @param maxlen Max length of output buffer + * @param start Position to start at + * @param numChars Number of characters to return, or 0 for the end of the string + * @noreturn + */ +stock CSubString(const String:input[], String:output[], maxlen, start, numChars=0) { + new i = 0; + for(;;) { + if(i == maxlen - 1 || i >= numChars || input[start + i] == '\0') { + output[i] = '\0'; + return; + } + output[i] = input[start + i]; + i++; + } +} + +/** + * Converts a string to lowercase + * + * @param buffer String to convert + * @noreturn + */ +stock CStrToLower(String:buffer[]) { + new len = strlen(buffer); + for(new i = 0; i < len; i++) { + buffer[i] = CharToLower(buffer[i]); + } +} + +/** + * Adds a color to the colors trie + * + * @param name Color name, without braces + * @param color Hexadecimal representation of the color (0xRRGGBB) + * @return True if color was added successfully, false if a color already exists with that name + */ +stock bool:CAddColor(const String:name[], color) { + CCheckTrie(); + new value; + if(GetTrieValue(CTrie, name, value)) { + return false; + } + decl String:newName[64]; + strcopy(newName, sizeof(newName), name); + CStrToLower(newName); + SetTrieValue(CTrie, newName, color); + return true; +} + +/** + * Removes color tags from a message + * + * @param message Message to remove tags from + * @param maxlen Maximum buffer length + * @noreturn + */ +stock CRemoveTags(String:message[], maxlen) { + CReplaceColorCodes(message, 0, true, maxlen); +} + +/** + * Replies to a command with colors + * + * @param client Client to reply to + * @param message Message (formatting rules) + * @noreturn + */ +stock CReplyToCommand(client, const String:message[], any:...) { + decl String:buffer[MAX_BUFFER_LENGTH]; + SetGlobalTransTarget(client); + VFormat(buffer, sizeof(buffer), message, 3); + if(GetCmdReplySource() == SM_REPLY_TO_CONSOLE) { + CRemoveTags(buffer, sizeof(buffer)); + PrintToConsole(client, "%s", buffer); + } else { + CPrintToChat(client, "%s", buffer); + } +} + +/** + * Replies to a command with colors + * + * @param client Client to reply to + * @param author Client to use for {teamcolor} + * @param message Message (formatting rules) + * @noreturn + */ +stock CReplyToCommandEx(client, author, const String:message[], any:...) { + decl String:buffer[MAX_BUFFER_LENGTH]; + SetGlobalTransTarget(client); + VFormat(buffer, sizeof(buffer), message, 4); + if(GetCmdReplySource() == SM_REPLY_TO_CONSOLE) { + CRemoveTags(buffer, sizeof(buffer)); + PrintToConsole(client, "%s", buffer); + } else { + CPrintToChatEx(client, author, "%s", buffer); + } +} + +/** + * Shows admin activity with colors + * + * @param client Client performing an action + * @param message Message (formatting rules) + * @noreturn + */ +stock CShowActivity(client, const String:message[], any:...) { + CCheckTrie(); + if(client < 0 || client > MaxClients) { + ThrowError("Invalid client index %d", client); + } + if(client != 0 && !IsClientInGame(client)) { + ThrowError("Client %d is not in game", client); + } + decl String:buffer[MAX_BUFFER_LENGTH], String:buffer2[MAX_BUFFER_LENGTH]; + Format(buffer, sizeof(buffer), "\x01%s", message); + VFormat(buffer2, sizeof(buffer2), buffer, 3); + CReplaceColorCodes(buffer2); + ShowActivity(client, "%s", buffer2); +} + +/** + * Shows admin activity with colors + * + * @param client Client performing an action + * @param tag Tag to prepend to the message (color tags supported) + * @param message Message (formatting rules) + * @noreturn + */ +stock CShowActivityEx(client, const String:tag[], const String:message[], any:...) { + CCheckTrie(); + if(client < 0 || client > MaxClients) { + ThrowError("Invalid client index %d", client); + } + if(client != 0 && !IsClientInGame(client)) { + ThrowError("Client %d is not in game", client); + } + decl String:buffer[MAX_BUFFER_LENGTH], String:buffer2[MAX_BUFFER_LENGTH]; + Format(buffer, sizeof(buffer), "\x01%s", message); + VFormat(buffer2, sizeof(buffer2), buffer, 4); + CReplaceColorCodes(buffer2); + strcopy(buffer, sizeof(buffer), tag); + CReplaceColorCodes(buffer); + ShowActivityEx(client, tag, "%s", buffer2); +} + +/** + * Shows admin activity with colors + * + * @param client Client performing an action + * @param tag Tag to prepend to the message (color tags supported) + * @param message Message (formatting rules) + * @noreturn + */ +stock CShowActivity2(client, const String:tag[], const String:message[], any:...) { + CCheckTrie(); + if(client < 0 || client > MaxClients) { + ThrowError("Invalid client index %d", client); + } + if(client != 0 && !IsClientInGame(client)) { + ThrowError("Client %d is not in game", client); + } + decl String:buffer[MAX_BUFFER_LENGTH], String:buffer2[MAX_BUFFER_LENGTH]; + Format(buffer, sizeof(buffer), "\x01%s", message); + VFormat(buffer2, sizeof(buffer2), buffer, 4); + CReplaceColorCodes(buffer2); + strcopy(buffer, sizeof(buffer), tag); + CReplaceColorCodes(buffer); + ShowActivity2(client, buffer, "%s", buffer2); +} + +/** + * Determines whether a color name exists + * + * @param color The color name to check + * @return True if the color exists, false otherwise + */ +stock bool:CColorExists(const String:color[]) { + CCheckTrie(); + new temp; + return GetTrieValue(CTrie, color, temp); +} + +/** + * Returns the hexadecimal representation of a client's team color (will NOT initialize the trie) + * + * @param client Client to get the team color for + * @return Client's team color in hexadecimal, or green if unknown + * On error/Errors: If the client index passed is invalid or not in game. + */ +stock CGetTeamColor(client) { + if(client <= 0 || client > MaxClients) { + ThrowError("Invalid client index %i", client); + } + if(!IsClientInGame(client)) { + ThrowError("Client %i is not in game", client); + } + new value; + switch(GetClientTeam(client)) { + case 1: { + value = COLOR_GRAY; + } + case 2: { + value = COLOR_RED; + } + case 3: { + value = COLOR_BLUE; + } + default: { + value = COLOR_GREEN; + } + } + return value; +} + +stock Handle:InitColorTrie() { + new Handle:hTrie = CreateTrie(); + SetTrieValue(hTrie, "aliceblue", 0xF0F8FF); + SetTrieValue(hTrie, "allies", 0x4D7942); // same as Allies team in DoD:S + SetTrieValue(hTrie, "ancient", 0xEB4B4B); // same as Ancient item rarity in Dota 2 + SetTrieValue(hTrie, "antiquewhite", 0xFAEBD7); + SetTrieValue(hTrie, "aqua", 0x00FFFF); + SetTrieValue(hTrie, "aquamarine", 0x7FFFD4); + SetTrieValue(hTrie, "arcana", 0xADE55C); // same as Arcana item rarity in Dota 2 + SetTrieValue(hTrie, "axis", 0xFF4040); // same as Axis team in DoD:S + SetTrieValue(hTrie, "azure", 0x007FFF); + SetTrieValue(hTrie, "beige", 0xF5F5DC); + SetTrieValue(hTrie, "bisque", 0xFFE4C4); + SetTrieValue(hTrie, "black", 0x000000); + SetTrieValue(hTrie, "blanchedalmond", 0xFFEBCD); + SetTrieValue(hTrie, "blue", 0x99CCFF); // same as BLU/Counter-Terrorist team color + SetTrieValue(hTrie, "blueviolet", 0x8A2BE2); + SetTrieValue(hTrie, "brown", 0xA52A2A); + SetTrieValue(hTrie, "burlywood", 0xDEB887); + SetTrieValue(hTrie, "cadetblue", 0x5F9EA0); + SetTrieValue(hTrie, "chartreuse", 0x7FFF00); + SetTrieValue(hTrie, "chocolate", 0xD2691E); + SetTrieValue(hTrie, "collectors", 0xAA0000); // same as Collector's item quality in TF2 + SetTrieValue(hTrie, "common", 0xB0C3D9); // same as Common item rarity in Dota 2 + SetTrieValue(hTrie, "community", 0x70B04A); // same as Community item quality in TF2 + SetTrieValue(hTrie, "coral", 0xFF7F50); + SetTrieValue(hTrie, "cornflowerblue", 0x6495ED); + SetTrieValue(hTrie, "cornsilk", 0xFFF8DC); + SetTrieValue(hTrie, "corrupted", 0xA32C2E); // same as Corrupted item quality in Dota 2 + SetTrieValue(hTrie, "crimson", 0xDC143C); + SetTrieValue(hTrie, "cyan", 0x00FFFF); + SetTrieValue(hTrie, "darkblue", 0x00008B); + SetTrieValue(hTrie, "darkcyan", 0x008B8B); + SetTrieValue(hTrie, "darkgoldenrod", 0xB8860B); + SetTrieValue(hTrie, "darkgray", 0xA9A9A9); + SetTrieValue(hTrie, "darkgrey", 0xA9A9A9); + SetTrieValue(hTrie, "darkgreen", 0x006400); + SetTrieValue(hTrie, "darkkhaki", 0xBDB76B); + SetTrieValue(hTrie, "darkmagenta", 0x8B008B); + SetTrieValue(hTrie, "darkolivegreen", 0x556B2F); + SetTrieValue(hTrie, "darkorange", 0xFF8C00); + SetTrieValue(hTrie, "darkorchid", 0x9932CC); + SetTrieValue(hTrie, "darkred", 0x8B0000); + SetTrieValue(hTrie, "darksalmon", 0xE9967A); + SetTrieValue(hTrie, "darkseagreen", 0x8FBC8F); + SetTrieValue(hTrie, "darkslateblue", 0x483D8B); + SetTrieValue(hTrie, "darkslategray", 0x2F4F4F); + SetTrieValue(hTrie, "darkslategrey", 0x2F4F4F); + SetTrieValue(hTrie, "darkturquoise", 0x00CED1); + SetTrieValue(hTrie, "darkviolet", 0x9400D3); + SetTrieValue(hTrie, "deeppink", 0xFF1493); + SetTrieValue(hTrie, "deepskyblue", 0x00BFFF); + SetTrieValue(hTrie, "dimgray", 0x696969); + SetTrieValue(hTrie, "dimgrey", 0x696969); + SetTrieValue(hTrie, "dodgerblue", 0x1E90FF); + SetTrieValue(hTrie, "exalted", 0xCCCCCD); // same as Exalted item quality in Dota 2 + SetTrieValue(hTrie, "firebrick", 0xB22222); + SetTrieValue(hTrie, "floralwhite", 0xFFFAF0); + SetTrieValue(hTrie, "forestgreen", 0x228B22); + SetTrieValue(hTrie, "frozen", 0x4983B3); // same as Frozen item quality in Dota 2 + SetTrieValue(hTrie, "fuchsia", 0xFF00FF); + SetTrieValue(hTrie, "fullblue", 0x0000FF); + SetTrieValue(hTrie, "fullred", 0xFF0000); + SetTrieValue(hTrie, "gainsboro", 0xDCDCDC); + SetTrieValue(hTrie, "genuine", 0x4D7455); // same as Genuine item quality in TF2 + SetTrieValue(hTrie, "ghostwhite", 0xF8F8FF); + SetTrieValue(hTrie, "gold", 0xFFD700); + SetTrieValue(hTrie, "goldenrod", 0xDAA520); + SetTrieValue(hTrie, "gray", 0xCCCCCC); // same as spectator team color + SetTrieValue(hTrie, "grey", 0xCCCCCC); + SetTrieValue(hTrie, "green", 0x3EFF3E); + SetTrieValue(hTrie, "greenyellow", 0xADFF2F); + SetTrieValue(hTrie, "haunted", 0x38F3AB); // same as Haunted item quality in TF2 + SetTrieValue(hTrie, "honeydew", 0xF0FFF0); + SetTrieValue(hTrie, "hotpink", 0xFF69B4); + SetTrieValue(hTrie, "immortal", 0xE4AE33); // same as Immortal item rarity in Dota 2 + SetTrieValue(hTrie, "indianred", 0xCD5C5C); + SetTrieValue(hTrie, "indigo", 0x4B0082); + SetTrieValue(hTrie, "ivory", 0xFFFFF0); + SetTrieValue(hTrie, "khaki", 0xF0E68C); + SetTrieValue(hTrie, "lavender", 0xE6E6FA); + SetTrieValue(hTrie, "lavenderblush", 0xFFF0F5); + SetTrieValue(hTrie, "lawngreen", 0x7CFC00); + SetTrieValue(hTrie, "legendary", 0xD32CE6); // same as Legendary item rarity in Dota 2 + SetTrieValue(hTrie, "lemonchiffon", 0xFFFACD); + SetTrieValue(hTrie, "lightblue", 0xADD8E6); + SetTrieValue(hTrie, "lightcoral", 0xF08080); + SetTrieValue(hTrie, "lightcyan", 0xE0FFFF); + SetTrieValue(hTrie, "lightgoldenrodyellow", 0xFAFAD2); + SetTrieValue(hTrie, "lightgray", 0xD3D3D3); + SetTrieValue(hTrie, "lightgrey", 0xD3D3D3); + SetTrieValue(hTrie, "lightgreen", 0x99FF99); + SetTrieValue(hTrie, "lightpink", 0xFFB6C1); + SetTrieValue(hTrie, "lightsalmon", 0xFFA07A); + SetTrieValue(hTrie, "lightseagreen", 0x20B2AA); + SetTrieValue(hTrie, "lightskyblue", 0x87CEFA); + SetTrieValue(hTrie, "lightslategray", 0x778899); + SetTrieValue(hTrie, "lightslategrey", 0x778899); + SetTrieValue(hTrie, "lightsteelblue", 0xB0C4DE); + SetTrieValue(hTrie, "lightyellow", 0xFFFFE0); + SetTrieValue(hTrie, "lime", 0x00FF00); + SetTrieValue(hTrie, "limegreen", 0x32CD32); + SetTrieValue(hTrie, "linen", 0xFAF0E6); + SetTrieValue(hTrie, "magenta", 0xFF00FF); + SetTrieValue(hTrie, "maroon", 0x800000); + SetTrieValue(hTrie, "mediumaquamarine", 0x66CDAA); + SetTrieValue(hTrie, "mediumblue", 0x0000CD); + SetTrieValue(hTrie, "mediumorchid", 0xBA55D3); + SetTrieValue(hTrie, "mediumpurple", 0x9370D8); + SetTrieValue(hTrie, "mediumseagreen", 0x3CB371); + SetTrieValue(hTrie, "mediumslateblue", 0x7B68EE); + SetTrieValue(hTrie, "mediumspringgreen", 0x00FA9A); + SetTrieValue(hTrie, "mediumturquoise", 0x48D1CC); + SetTrieValue(hTrie, "mediumvioletred", 0xC71585); + SetTrieValue(hTrie, "midnightblue", 0x191970); + SetTrieValue(hTrie, "mintcream", 0xF5FFFA); + SetTrieValue(hTrie, "mistyrose", 0xFFE4E1); + SetTrieValue(hTrie, "moccasin", 0xFFE4B5); + SetTrieValue(hTrie, "mythical", 0x8847FF); // same as Mythical item rarity in Dota 2 + SetTrieValue(hTrie, "navajowhite", 0xFFDEAD); + SetTrieValue(hTrie, "navy", 0x000080); + SetTrieValue(hTrie, "normal", 0xB2B2B2); // same as Normal item quality in TF2 + SetTrieValue(hTrie, "oldlace", 0xFDF5E6); + SetTrieValue(hTrie, "olive", 0x9EC34F); + SetTrieValue(hTrie, "olivedrab", 0x6B8E23); + SetTrieValue(hTrie, "orange", 0xFFA500); + SetTrieValue(hTrie, "orangered", 0xFF4500); + SetTrieValue(hTrie, "orchid", 0xDA70D6); + SetTrieValue(hTrie, "palegoldenrod", 0xEEE8AA); + SetTrieValue(hTrie, "palegreen", 0x98FB98); + SetTrieValue(hTrie, "paleturquoise", 0xAFEEEE); + SetTrieValue(hTrie, "palevioletred", 0xD87093); + SetTrieValue(hTrie, "papayawhip", 0xFFEFD5); + SetTrieValue(hTrie, "peachpuff", 0xFFDAB9); + SetTrieValue(hTrie, "peru", 0xCD853F); + SetTrieValue(hTrie, "pink", 0xFFC0CB); + SetTrieValue(hTrie, "plum", 0xDDA0DD); + SetTrieValue(hTrie, "powderblue", 0xB0E0E6); + SetTrieValue(hTrie, "purple", 0x800080); + SetTrieValue(hTrie, "rare", 0x4B69FF); // same as Rare item rarity in Dota 2 + SetTrieValue(hTrie, "red", 0xFF4040); // same as RED/Terrorist team color + SetTrieValue(hTrie, "rosybrown", 0xBC8F8F); + SetTrieValue(hTrie, "royalblue", 0x4169E1); + SetTrieValue(hTrie, "saddlebrown", 0x8B4513); + SetTrieValue(hTrie, "salmon", 0xFA8072); + SetTrieValue(hTrie, "sandybrown", 0xF4A460); + SetTrieValue(hTrie, "seagreen", 0x2E8B57); + SetTrieValue(hTrie, "seashell", 0xFFF5EE); + SetTrieValue(hTrie, "selfmade", 0x70B04A); // same as Self-Made item quality in TF2 + SetTrieValue(hTrie, "sienna", 0xA0522D); + SetTrieValue(hTrie, "silver", 0xC0C0C0); + SetTrieValue(hTrie, "skyblue", 0x87CEEB); + SetTrieValue(hTrie, "slateblue", 0x6A5ACD); + SetTrieValue(hTrie, "slategray", 0x708090); + SetTrieValue(hTrie, "slategrey", 0x708090); + SetTrieValue(hTrie, "snow", 0xFFFAFA); + SetTrieValue(hTrie, "springgreen", 0x00FF7F); + SetTrieValue(hTrie, "steelblue", 0x4682B4); + SetTrieValue(hTrie, "strange", 0xCF6A32); // same as Strange item quality in TF2 + SetTrieValue(hTrie, "tan", 0xD2B48C); + SetTrieValue(hTrie, "teal", 0x008080); + SetTrieValue(hTrie, "thistle", 0xD8BFD8); + SetTrieValue(hTrie, "tomato", 0xFF6347); + SetTrieValue(hTrie, "turquoise", 0x40E0D0); + SetTrieValue(hTrie, "uncommon", 0xB0C3D9); // same as Uncommon item rarity in Dota 2 + SetTrieValue(hTrie, "unique", 0xFFD700); // same as Unique item quality in TF2 + SetTrieValue(hTrie, "unusual", 0x8650AC); // same as Unusual item quality in TF2 + SetTrieValue(hTrie, "valve", 0xA50F79); // same as Valve item quality in TF2 + SetTrieValue(hTrie, "vintage", 0x476291); // same as Vintage item quality in TF2 + SetTrieValue(hTrie, "violet", 0xEE82EE); + SetTrieValue(hTrie, "wheat", 0xF5DEB3); + SetTrieValue(hTrie, "white", 0xFFFFFF); + SetTrieValue(hTrie, "whitesmoke", 0xF5F5F5); + SetTrieValue(hTrie, "yellow", 0xFFFF00); + SetTrieValue(hTrie, "yellowgreen", 0x9ACD32); + return hTrie; +} \ No newline at end of file diff --git a/includes/nativevotes.inc b/includes/nativevotes.inc new file mode 100644 index 00000000..97e80c29 --- /dev/null +++ b/includes/nativevotes.inc @@ -0,0 +1,915 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * NativeVotes + * Copyright (C) 2011-2013 Ross Bemrose (Powerlord). 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 . + * + * 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 . + * + * Version: $Id$ + */ + +#include +#include + +// NativeVotes 0.8 series + +#if defined _nativevotes_included + #endinput +#endif +#define _nativevotes_included + +#define CLIENT_DISCONNECTED -1 + +#define NATIVEVOTES_EXTEND "Extend current Map" /** Defined in TF2, but doesn't appear to be localized */ + +#define NATIVEVOTES_ALL_TEAMS -1 // Defined by TF2, may be the same in L4D/L4D2 +#define NATIVEVOTES_TF2_ALL_TEAMS 0 // Defined by TF2, may be the same in L4D/L4D2 +#define NATIVEVOTES_TEAM_UNASSIGNED 0 // For completeness, do not otherwise use +#define NATIVEVOTES_TEAM_SPECTATOR 1 // Spectators +#define NATIVEVOTES_TEAM_1 2 // RED/Survivors/Terrorists +#define NATIVEVOTES_TEAM_2 3 // BLU/Infected/Counter-Terrorists + +#define NATIVEVOTES_SERVER_INDEX 99 // Defined by TF2, may be the same in L4D/L4D2 + +// These may seem backwards, but this is the order that the votes appear in the vote screen +#define NATIVEVOTES_VOTE_INVALID -1 /**< Vote was invalid, currently only valid internally */ +#define NATIVEVOTES_VOTE_YES 0 /**< Vote was yes */ +#define NATIVEVOTES_VOTE_NO 1 /**< Vote was no */ + +/* +The following MenuActions are supported. Arguments are also listed, as they differ slightly from the default +MenuAction_Start A menu has been started (nothing passed). Only exists for compat reasons. +MenuAction_Display A menu is about to be displayed (param1=client). If you choose to change the vote text, + To change the text, use RedrawVoteTitle() + If you do so, return 1 or _:Plugin_Changed Otherwise, return _:Plugin_Continue or 0. +MenuAction_Select An item was selected (param1=client, param2=item). For subplugin support. +MenuAction_End A vote has fully ended and the vote object is ready to be cleaned up + param1 is MenuEnd reason, either MenuEnd_VotingCancelled or MenuEnd_VotingDone +MenuAction_VoteEnd A vote sequence has succeeded (param1=chosen item) + This is not called if NativeVotes_SetResultCallback has been used on the vote. + You should call NativeVotes_DisplayPass or NativeVotes_DisplayPassEx after this +MenuAction_VoteStart A vote sequence has started (nothing passed). Use this instead of MenuAction_Start +MenuAction_VoteCancel A vote sequence has been cancelled (param1=reason) +MenuAction_DisplayItem Item text is being drawn to the display (param1=client, param2=item) + To change the text, use RedrawVoteItem(). + If you do so, return 1 or _:Plugin_Changed. Otherwise, return _:Plugin_Continue or 0. +*/ + +#define NATIVEVOTES_ACTIONS_DEFAULT MenuAction_VoteStart|MenuAction_VoteCancel|MenuAction_VoteEnd|MenuAction_End + +/** + * Vote types. These are mapped to translation strings and pass strings by VoteStart and VotePass handlers + */ +enum NativeVotesType +{ + NativeVotesType_None = 0, /** Special placeholder for callvote with no arguments for NativeVotes_OnCallVote */ + NativeVotesType_Custom_YesNo, /**< Yes/No, details are vote text. */ + NativeVotesType_Custom_Mult, /**< TF2/CS:GO: Multiple-choice, details are vote text. */ + NativeVotesType_ChgCampaign, /**< L4D/L4D2: Yes/No, details are campaign name */ + NativeVotesType_ChgDifficulty, /**< L4D/L4D2: Yes/No, details are difficulty number in L4D/L4D2 */ + NativeVotesType_ReturnToLobby, /**< L4D/L4D2: Yes/No, details are ignored */ + NativeVotesType_AlltalkOn, /**< L4D2: Yes/No, details are ignored (handled internally by extension) */ + NativeVotesType_AlltalkOff, /**< L4D2: Yes/No, details are ignored (handled internally by extension) */ + NativeVotesType_Restart, /**< Yes/No, details are ignored */ + NativeVotesType_Kick, /**< Yes/No, target is player userid, details are auto-set by target */ + NativeVotesType_KickIdle, /**< TF2/CS:GO: Yes/No, target is player userid, details are auto-set by target */ + NativeVotesType_KickScamming, /**< TF2/CS:GO: Yes/No, target is player userid, details are auto-set by target */ + NativeVotesType_KickCheating, /**< TF2/CS:GO: Yes/No, target is player userid, details are auto-set by target */ + NativeVotesType_ChgLevel, /**< Yes/No, details are level number in L4D/L4D2 or map name in TF2 */ + NativeVotesType_NextLevel, /**< TF2/CS:GO: Yes/No, details are map name */ + NativeVotesType_NextLevelMult, /**< TF2/CS:GO: Multiple-choice, details are ignored */ + NativeVotesType_ScrambleNow, /**< TF2/CS:GO: Yes/No, details are ignored */ + NativeVotesType_ScrambleEnd, /**< TF2: Yes/No, details are ignored */ + NativeVotesType_ChgMission, /**< TF2: Yes/No, details are popfile */ + NativeVotesType_SwapTeams, /**< CS:GO: Yes/No, details are ignored */ + NativeVotesType_Surrender, /**< CS:GO: Yes/No, details are ignored */ + NativeVotesType_Rematch, /**< CS:GO: Yes/No, details are ignored */ + NativeVotesType_Continue, /**< CS:GO: Yes/No, details are ignored */ + NativeVotesType_StartRound, /**< TF2: Yes/No, details are ignored */ + NativeVotesType_Eternaween, /**< TF2: Yes/No, details are ignored */ + NativeVotesType_AutoBalanceOn, /**< TF2: Yes/No, details are ignored */ + NativeVotesType_AutoBalanceOff, /**< TF2: Yes/No, details are ignored */ + NativeVotesType_ClassLimitsOn, /**< TF2: Yes/No, details are ignored */ + NativeVotesType_ClassLimitsOff, /**< TF2: Yes/No, details are ignored */ +}; + +enum NativeVotesPassType +{ + NativeVotesPass_None = 0, /**< Special placeholder for error value */ + NativeVotesPass_Custom, /**< Details are custom pass message */ + NativeVotesPass_ChgCampaign, /**< L4D/L4D2: Details are campaign name */ + NativeVotesPass_ChgDifficulty, /**< L4D/L4D2/TF2: Details are difficulty number in L4D/L4D2 and mission name in TF2 */ + NativeVotesPass_ReturnToLobby, /**< L4D/L4D2: Details are ignored */ + NativeVotesPass_AlltalkOn, /**< L4D2: Details are ignored */ + NativeVotesPass_AlltalkOff, /**< L4D2: Details are ignored */ + NativeVotesPass_Restart, /**< Details are ignored */ + NativeVotesPass_Kick, /**< Details are player name */ + NativeVotesPass_ChgLevel, /**< Details are level number in L4D/L4D2 or map name in TF2/CS:GO */ + NativeVotesPass_NextLevel, /**< TF2/CS:GO: Details are map name */ + NativeVotesPass_Extend, /**< TF2/CS:GO: Details are ignored */ + NativeVotesPass_Scramble, /**< TF2/CS:GO: Details are ignored */ + NativeVotesPass_ChgMission, /**< TF2: Details are popfile */ + NativeVotesPass_SwapTeams, /**< CS:GO: Details are ignored */ + NativeVotesPass_Surrender, /**< CS:GO: Details are ignored */ + NativeVotesPass_Rematch, /**< CS:GO: Details are ignored */ + NativeVotesPass_Continue, /**< CS:GO: Details are ignored */ + NativeVotesPass_StartRound, /**< TF2: Details are ignored */ + NativeVotesPass_Eternaween, /**< TF2: Details are ignored */ + NativeVotesPass_AutoBalanceOn, /**< TF2: Yes/No, details are ignored */ + NativeVotesPass_AutoBalanceOff, /**< TF2: Yes/No, details are ignored */ + NativeVotesPass_ClassLimitsOn, /**< TF2: Yes/No, details are ignored */ + NativeVotesPass_ClassLimitsOff, /**< TF2: Yes/No, details are ignored */ +}; + +/** + * Reasons a vote was canceled. Not used for L4D/L4D2, as they don't care + */ +enum NativeVotesFailType +{ + NativeVotesFail_Generic = 0, /**< Vote was generically cancelled. */ + NativeVotesFail_Loses = 3, /**< No votes outnumbered Yes votes */ + NativeVotesFail_NotEnoughVotes = 4, /**< Vote did not receive enough votes. */ +}; + +/** + * Reasons a callvote command failed. + * This is provided as a convenience to plugin authors as it's not strictly part of the vote system + */ +enum NativeVotesCallFailType +{ + NativeVotesCallFail_Generic = 0, /**< Generic fail. */ + NativeVotesCallFail_Loading = 1, /**< L4D/L4D2: Players are still loading. */ + NativeVotesCallFail_Recent = 2, /**< TF2/CS:GO: You can't call another vote yet: Argument is seconds until you can call another vote. */ + NativeVotesCallFail_Disabled = 5, /**< TF2/CS:GO: Server has disabled that issue. */ + NativeVotesCallFail_MapNotFound = 6, /**< TF2/CS:GO: Server does not have that map. */ + NativeVotesCallFail_SpecifyMap = 7, /**< TF2/CS:GO: You must specify a map. */ + NativeVotesCallFail_Failed = 8, /**< TF2/CS:GO: This vote failed recently. */ + NativeVotesCallFail_WrongTeam = 9, /**< TF2/CS:GO: Team can't call this vote. */ + NativeVotesCallFail_Waiting = 10, /**< TF2/CS:GO: Vote can't be called during Waiting For Players. */ + NativeVotesCallFail_PlayerNotFound = 11, /**< TF2/CS:GO: Player to kick can't be found. Buggy in TF2. */ + NativeVotesCallFail_Unknown = 11, + NativeVotesCallFail_CantKickAdmin = 12, /**< TF2/CS:GO: Can't kick server admin. */ + NativeVotesCallFail_ScramblePending = 13, /**< TF2/CS:GO: Team Scramble is pending.. */ + NativeVotesCallFail_Spectators = 14, /**< TF2/CS:GO: Spectators aren't allowed to call votes. */ + NativeVotesCallFail_LevelSet = 15, /**< TF2/CS:GO: Next level already set. */ + NativeVotesCallFail_MapNotValid = 16, /**< ???: Map is invalid. */ + NativeVotesCallFail_KickTime = 17, /**< ???: Cannot kick for time. */ + NativeVotesCallFail_KickDuringRound = 18, /**< ???: Cannot kick during a round. */ + NativeVotesCallFail_AlreadyActive = 19 /**< TF2: Cannot call vote because modification (Eternaween) is already active (may not work) */ +}; + +/* + * Is a specific vote type supported by this game? + * + * @param voteType Vote type + */ +native bool:NativeVotes_IsVoteTypeSupported(NativeVotesType:voteType); + +/** + * Creates a new, empty vote. + * + * @param handler Function which will receive vote actions. + * @param voteType Vote type, cannot be changed after set + * @param actions Optionally set which actions to receive. Start, + * Cancel, and End will always be received regardless + * of whether they are set or not. They are also + * the only default actions. + * @return A new vote Handle on INVALID_HANDLE if a vote type is unsupported by this game. + */ +native Handle:NativeVotes_Create(MenuHandler:handler, NativeVotesType:voteType, + MenuAction:actions=NATIVEVOTES_ACTIONS_DEFAULT); + +/** + * Frees all handles related to a vote. + * + * THIS MUST BE CALLED TO AVOID HANDLE LEAKS + * + * @param vote Vote handle + * @noreturn + */ +native Handle:NativeVotes_Close(Handle:vote); + +/** + * Appends a new item to the end of a vote. Only valid for Multiple Choice votes + * + * @param vote NativeVotes Handle. + * @param info Item information string. + * @return True on success, false on failure. + * @error Invalid Handle, item limit reached, or if the vote is not multiple choice. + */ +native bool:NativeVotes_AddItem(Handle:vote, const String:info[], const String:display[]); + +/** + * Inserts an item into the menu before a certain position; the new item will + * be at the given position and all next items pushed forward. + * + * @param vote Vote Handle. + * @param position Position, starting from 0. + * @param info Item information string. + * @return True on success, false on failure. + * @error Invalid Handle or vote position, or if the vote is not multiple choice. + */ +native bool:NativeVotes_InsertItem(Handle:vote, position, const String:info[], const String:display[]); + +/** + * Removes an item from the menu. + * + * @param vote Vote Handle. + * @param position Position, starting from 0. + * @return True on success, false on failure. + * @error Invalid Handle or vote position, or if the vote is not multiple choice. + */ +native bool:NativeVotes_RemoveItem(Handle:vote, position); + +/** + * Removes all items from a vote. + * + * @param vote Vote Handle. + * @noreturn + * @error Invalid Handle or vote position, or if the vote is not multiple choice. + */ +native NativeVotes_RemoveAllItems(Handle:vote); + +/** + * Retrieves information about a vote item. + * + * @param vote Vote Handle. + * @param position Position, starting from 0. + * @param infoBuf Info buffer. + * @param infoBufLen Maximum length of the info buffer. + * @return True on success, false if position is invalid. + * @error Invalid Handlem + */ +native bool:NativeVotes_GetItem(Handle:vote, + position, + String:infoBuf[], + infoBufLen, + String:dispBuf[]="", + dispBufLen=0); + +/** + * Returns the number of items in a vote. + * + * @param vote Vote Handle. + * @return Number of items in the vote. + * @error Invalid Handle. + */ +native NativeVotes_GetItemCount(Handle:vote); + +/** + * Sets the vote's details for votes that support details + * If this is a custom vote, use NativeVotes_SetTitle to set the vote's title. + * + * @param vote Vote Handle. + * @param argument Details string. See vote types for what details stands for. + * @noreturn + * @error Invalid Handle. + */ +native NativeVotes_SetDetails(Handle:vote, const String:argument[]); + +/** + * Returns the text of a vote's details if set. + * + * @param vote Vote Handle. + * @param buffer Buffer to store details. + * @param maxlength Maximum length of the buffer. + * @noreturn + * @error Invalid Handle. + */ +native NativeVotes_GetDetails(Handle:vote, String:buffer[], maxlength); + +/** + * Sets a custom vote's title + * + * @param vote Vote Handle. + * @param title Vote title string. + * @noreturn + * @error Invalid Handle. + */ +native NativeVotes_SetTitle(Handle:vote, const String:argument[]); + +/** + * Return the vote's Title. + * If not set, returns Details instead. + * This behavior is for compatibility with NativeVotes 0.8.0 and below. + * + * @param vote Vote Handle. + * @param buffer Buffer to store title. + * @param maxlength Maximum length of the buffer. + * @noreturn + * @error Invalid Handle. + */ +native NativeVotes_GetTitle(Handle:vote, String:buffer[], maxlength); + +/** + * Sets the target userid for vote + * This should be used instead of SetArgument for votes that target players + * + * Also sets target SteamID + * + * @param vote Vote Handle. + * @param userid Client index of target player + * @param setDetails If true, also sets vote details to client's name + * @noreturn + * @error Invalid Handle. + */ +native NativeVotes_SetTarget(Handle:vote, client, bool:setDetails=true); + +/** + * Returns the userid of a vote's target + * + * @param vote Vote Handle. + * @return Client index of target player or 0 for no target or target disconnected since vote started + * @error Invalid Handle. + */ +native NativeVotes_GetTarget(Handle:vote); + +/** + * Get the Steam ID of a vote's target + * Useful if the target has disconnect from the server during a vote. + * This was added in specifically for Kick/Ban votes + * + * @param vote Vote Handle. + * @param buffer Buffer to store steamId. Should be 19 characters or more.. + * @param maxlength Maximum length of the buffer. + * @noreturn + * @error Invalid Handle. + */ +native NativeVotes_GetTargetSteam(Handle:vote, String:buffer[], maxlength); + +/** + * Returns whether a vote is in progress. + * + * @return True if a NativeVotes vote is in progress, false otherwise. + */ +native bool:NativeVotes_IsVoteInProgress(); + +/** + * Returns a style's maximum items + * + * @return Maximum items + */ +native NativeVotes_GetMaxItems(); + +/** + * Sets a vote's option flags. + * + * If a certain bit is not supported, it will be stripped before being set. + * + * NOTE: This is currently unused, but reserved for future use. + * + * @param menu Builtin Vote Handle. + * @param flags A new bitstring of VOTEFLAG bits. + * @noreturn + * @error Invalid Handle. + */ +native NativeVotes_SetOptionFlags(Handle:vote, flags); + +/** + * Retrieves a menu's option flags. + * + * NOTE: This is currently unused, but reserved for future use. + * + * @param vote Builtin Vote Handle. + * @return A bitstring of VOTEFLAG bits. + * @error Invalid Handle. + */ +native NativeVotes_GetOptionFlags(Handle:vote); + +/** + * Cancels the vote in progress. + * + * @noreturn + * @error If no vote is in progress. + */ +native NativeVotes_Cancel(); + +/** + * Callback for when a vote has ended and results are available. + * + * Due to SourceMod Forward limitations in plugins, multi-dimension arrays can't be passed + * to forwards. This means we have to split the client_info and item_info arrays into + * their components. + * + * @param vote The vote being voted on. + * @param num_votes Number of votes tallied in total. + * @param num_clients Number of clients who could vote. + * @param client_indexes Array of client indexes. Parallel with client_votes. + * @param client_votes Array of client votes. Parallel with client_indexes. + * @param num_items Number of unique items that were selected. + * @param item_indexes Array of vote item indexes. Parallel with item_votes.. + * @param item_votes Array of vote vote counts. Parallel with item_indexes. + * @noreturn + */ +functag public NativeVotes_VoteHandler(Handle:vote, + num_votes, + num_clients, + const client_indexes[], + const client_votes[], + num_items, + const item_indexes[], + const item_votes[]); +/** + * Function to convert client/vote arrays into their two-dimensional versions, + * which can then be passed to a standard vote handler. + * + * client_info and item_info are the resulting arrays. + * + * Note: When declaring client_info and item_info, you'll probably want to declare them like this: + * new client_info[num_clients][2]; + * new item_info[num_items][2]; + * + * @param num_clients Number of clients who could vote. + * @param client_indexes Array of client indexes. Parallel with client_votes. + * @param client_votes Array of client votes. Parallel with client_indexes. + * @param num_items Number of unique items that were selected. + * @param item_indexes Array of vote item indexes. Parallel with item_votes.. + * @param item_votes Array of vote vote counts. Parallel with item_indexes. + * @param client_info Array of clients. Use VOTEINFO_CLIENT_ defines. + * @param item_info Array of items, sorted by count. Use VOTEINFO_ITEM + * defines. + * @noreturn + */ +stock NativeVotes_FixResults(num_clients, + const client_indexes[], + const client_votes[], + num_items, + const item_indexes[], + const item_votes[], + client_info[][2], + item_info[][2]) +{ + for (new i = 0; i < num_clients; ++i) + { + client_info[i][VOTEINFO_CLIENT_INDEX] = client_indexes[i]; + client_info[i][VOTEINFO_CLIENT_ITEM] = client_votes[i]; + } + + for (new i = 0; i < num_items; ++i) + { + item_info[i][VOTEINFO_ITEM_INDEX] = item_indexes[i]; + item_info[i][VOTEINFO_ITEM_VOTES] = item_votes[i]; + } +} + +/** + * Sets an advanced vote handling callback. If this callback is set, + * MenuAction_VoteEnd will not be called. + * + * @param vote NativeVotes Handle. + * @param callback Callback function. + * @noreturn + * @error Invalid Handle or callback. + */ +native NativeVotes_SetResultCallback(Handle:vote, NativeVotes_VoteHandler:callback); + +/** + * Returns the number of seconds you should "wait" before displaying + * a public vote. This number is the time remaining until + * (last_vote + sm_vote_delay). + * + * @return Number of seconds to wait, or 0 for none. + */ +native NativeVotes_CheckVoteDelay(); + +/** + * Returns whether a client is in the pool of clients allowed + * to participate in the current vote. This is determined by + * the client list passed to NativeVotes_Display(). + * + * @param client Client index. + * @return True if client is allowed to vote, false otherwise. + * @error If no vote is in progress or client index is invalid. + */ +native bool:NativeVotes_IsClientInVotePool(client); + +/** + * Redraws the current vote to a client in the voting pool. + * + * @param client Client index. + * @param revotes True to allow revotes, false otherwise. + * @return True on success, false if the client is in the vote pool + * but cannot vote again. + * @error No vote in progress, client is not in the voting pool, + * or client index is invalid. + */ +native bool:NativeVotes_RedrawClientVote(client, bool:revotes=true); + +/** + * Retrieve the vote type + * + * @param vote NativeVotes Handle. + * @return The built in vote type + * @error Invalid Handle + */ +native NativeVotesType:NativeVotes_GetType(Handle:vote); + +/** + * Set the team this vote is for, or NATIVEVOTES_ALL_TEAMS for all teams. + * + * Defaults to NATIVEVOTES_ALL_TEAMS if not explicitly set. + * + * @param vote NativeVotes Handle. + * @param team Team number this vote is for + * @noreturn + * @error Invalid Handle + */ +native NativeVotes_SetTeam(Handle:vote, team); + +/** + * Retrieve the team this vote is for + * + * @param vote NativeVotes Handle. + * @return Team index or NATIVEVOTES_ALL_TEAMS for all teams. + * @error Invalid Handle + */ +native NativeVotes_GetTeam(Handle:vote); + +/** + * Set the client index of the player who initiated the vote. + * Use NATIVEVOTES_SERVER_INDEX if initiated by the server itself. + * + * Defaults to NATIVEVOTES_SERVER_INDEX if not explicitly set. + * + * @param vote NativeVotes Handle. + * @param client Client who initiated the vote or NATIVEVOTES_SERVER_INDEX + * @noreturn + * @error Invalid Handle + */ +native NativeVotes_SetInitiator(Handle:vote, client); + +/** + * Retrieve the client index of the player who initiated the vote or NATIVEVOTES_SERVER_INDEX if + * initiated by the server itself. + * + * @param Vote handle + * @return Client index or NATIVEVOTES_SERVER_INDEX + * @error Invalid Handle + */ +native NativeVotes_GetInitiator(Handle:vote); + +/** + * Broadcasts a vote to a list of clients. The most selected item will be + * returned through MenuAction_VoteEnd. On a tie, a random item will be returned + * from a list of the tied items. + * + * Note that MenuAction_VoteStart, MenuAction_VoteCancel, MenuAction_VoteEnd, and MenuAction_End are all + * default callbacks and do not need to be enabled. + * + * @param vote Vote Handle. + * @param clients Array of clients to broadcast to. + * @param numClients Number of clients in the array. + * @param time Maximum time to leave menu on the screen. + * @return True on success, false if a vote is already in progress. + * @error Invalid Handle, or a vote is already in progress. + */ +native bool:NativeVotes_Display(Handle:vote, clients[], numClients, time); + +/** + * Sends a vote menu to all clients. See NativeVotes_Display() for more information. + * + * @param vote Vote Handle. + * @param time Maximum time to leave menu on the screen. + * @return True on success, false if this menu already has a vote session + * in progress. + * @error Invalid Handle, or a vote is already in progress. + */ +stock bool:NativeVotes_DisplayToAll(Handle:vote, time) +{ + new total = 0; + decl players[MaxClients]; + + for (new i=1; i<=MaxClients; i++) + { + if (!IsClientInGame(i) || IsFakeClient(i)) + { + continue; + } + players[total++] = i; + } + + return NativeVotes_Display(vote, players, total, time); +} + +/** + * Sends a vote menu to a single team. See NativeVotes_Display() for more information. + * + * @param vote Vote Handle. + * @param team Team to send vote to. 1 = spectators, 2 = RED/Survivors/Terrorists, 3 = BLU/Infected/Counter-Terrorists + * @param time Maximum time to leave menu on the screen. + * @return True on success, false if this menu already has a vote session + * in progress. + * @error Invalid Handle, or a vote is already in progress. + */ +stock bool:NativeVotes_DisplayToTeam(Handle:vote, team, time) +{ + NativeVotes_SetTeam(vote, team); + + new total; + decl players[MaxClients]; + + for (new i=1; i<=MaxClients; i++) + { + if (!IsClientInGame(i) || IsFakeClient(i) || (GetClientTeam(i) != team)) + { + continue; + } + players[total++] = i; + } + + return NativeVotes_Display(vote, players, total, time); +} + +/** + * Sends a vote menu to all clients who are not spectators or waiting to choose a team. See NativeVotes_Display() for more information. + * + * @param vote Vote Handle. + * @param time Maximum time to leave menu on the screen. + * @return True on success, false if this menu already has a vote session + * in progress. + * @error Invalid Handle, or a vote is already in progress. + */ +stock bool:NativeVotes_DisplayToAllNonSpectators(Handle:vote, time) +{ + new total; + decl players[MaxClients]; + + for (new i=1; i<=MaxClients; i++) + { + if (!IsClientInGame(i) || IsFakeClient(i) || (GetClientTeam(i) < 2)) + { + continue; + } + players[total++] = i; + } + + return NativeVotes_Display(vote, players, total, time); +} + +/** + * Display vote passed screen + * + * You MUST call one of the NativeVotesDisplayPass* or NativeVotes_DisplayFail functions + * to hide the vote screen for users who didn't vote, and to clear out their selection + * for the next vote. + * + * @param vote Vote handle + * @param details Normally the item that won the vote or format string. Also used for custom vote winners + * @param ... Variable number of format parameters. + * @noreturn + */ +native NativeVotes_DisplayPass(Handle:vote, const String:details[]=""); + +/** + * Display vote passed screen with custom text to a single user + * + * You MUST call one of the NativeVotesDisplayPass* or NativeVotes_DisplayFail functions + * to hide the vote screen for users who didn't vote, and to clear out their selection + * for the next vote. + * + * @param vote Vote handle + * @param client client to display to + * @param format A format string. + * @param any Variable number of format parameters + * @noreturn + */ +native NativeVotes_DisplayPassCustomToOne(Handle:vote, client, const String:format[], any:...); + +/** + * Display vote passed screen with custom text + * + * You MUST call one of the NativeVotesDisplayPass* or NativeVotes_DisplayFail functions + * to hide the vote screen for users who didn't vote, and to clear out their selection + * for the next vote. + * + * @param vote Vote handle + * @param format A format string. + * @param any Variable number of format parameters + * @noreturn + */ +stock NativeVotes_DisplayPassCustom(Handle:vote, const String:format[], any:...) +{ + decl String:buffer[192]; + + for (new i = 1; i <= MaxClients; ++i) + { + if (IsClientInGame(i)) + { + SetGlobalTransTarget(i); + VFormat(buffer, sizeof(buffer), format, 3); + NativeVotes_DisplayPassCustomToOne(vote, i, "%s", buffer); + } + } +} + +/** + * Display vote passed screen with a custom type. + * + * A sample usage of this would be if Extend won an RTV vote: NativeVotes_DisplayPassEx(vote, NativeVotesPass_Extend, map); + * + * You MUST call one of NativeVotes_DisplayPass, NativeVotes_DisplayPassEx, + * or NativeVotes_DisplayFail to hide the vote screen for users who didn't vote + * and to clear out their selection for the next vote. + * + * #param vote Vote handle + * @param passType The pass screen to display + * @param details Normally the item that won the vote. Also used for custom vote winners + * @noreturn + */ +native NativeVotes_DisplayPassEx(Handle:vote, NativeVotesPassType:passType, const String:details[]=""); + +/** + * Display failure screen. + * + * You MUST call one of NativeVotes_DisplayPass, NativeVotes_DisplayPassEx, + * or NativeVotes_DisplayFail to hide the vote screen for users who didn't vote, + * and to clear out their selection for the next vote. + * + * @param reason Vote failure reason from NativeVotesFailType enum + * @noreturn + */ +native NativeVotes_DisplayFail(Handle:vote, NativeVotesFailType:reason=NativeVotesFail_Generic); + +/** + * Quick stock to determine whether voting is allowed. This doesn't let you + * fine-tune a reason for not voting, so it's not recommended for lazily + * telling clients that voting isn't allowed. + * + * @return True if voting is allowed, false if voting is in progress + * or the cooldown is active. + */ +stock bool:NativeVotes_IsNewVoteAllowed() +{ + if (NativeVotes_IsVoteInProgress() || NativeVotes_CheckVoteDelay() != 0) + { + return false; + } + + return true; +} + +/** + * Used when callvote is called with no arguments. + * + * This is used to configure the VoteSetup usermessage on TF2 and CS:GO + * + * @param client Client, in case the votes are restricted by client + * @param voteTypes Populate this array with the vote types this server supports + * Custom and multiple choice votes are not supported from + * the GUI and are thus ignored. + * @return Plugin_Continue to allow the server itself (or another plugin) to process the callvote + * Plugin_Changed if you're changing the voteTypes, + * Plugin_Handled to return a blank VoteSetup usermessage + * Plugin_Stop to prevent VoteSetup usermessage (not recommended) + */ +//functag public Action:NativeVotes_CallVoteSetupHandler(client, NativeVotesType:voteTypes[]); + +/** + * Forward for "callvote" handling + * + * You should respond to this by starting a vote or by calling NativeVotes_DisplayCallVoteFail + * + * @param client Client + * @param voteType Type of vote being called. This will NEVER be a multiple-choice or custom vote. + * @param voteArgument Vote argument or blank if the vote type has no argument. + * @param target target userid for kick votes or 0 for all other votes + * @return Plugin_Continue to allow the server itself (or another plugin) to process the callvote + * Plugin_Handled if you processed this vote type + * Plugin_Stop to block the vote type (not recommended) + */ +//functag public Action:NativeVotes_CallVoteHandler(client, NativeVotesType:voteType, const String:voteArgument[], target); + +/** + * Register a plugin as a vote manager. + * This is used to abstract away the details of the callvote command. + * + * @param callHandler Handler for callvote commands. + * @param setupHandler Handler to override the which vote types your server supports. Only useful on TF2 and CS:GO. + * @noreturn + */ +//native NativeVotes_RegisterVoteManager(NativeVotes_CallVoteHandler:callHandler, NativeVotes_CallVoteSetupHandler:setupHandler=INVALID_FUNCTION); + +/** + * Send a call vote fail screen to a user + * Used to respond to a callvote with invalid arguments or for other reasons + * (such as trying to target an admin for a kick/ban vote) + * + * @param client The client to display the fail screen to + * @param reason A vote call fail reason + * @param time For NativeVotesCallFail_Recent, the number of seconds until the vote + * can be called again + */ +native NativeVotes_DisplayCallVoteFail(client, NativeVotesCallFailType:reason, time); + +/** + * Redraws the vote title from inside a MenuAction_Display callback + * Not supported on L4D + * + * @param text Vote title to draw + * @error If called from outside MenuAction_Display + * @return Plugin_Changed if the change is allowed, Plugin_Continue if it isn't. + */ +native Action:NativeVotes_RedrawVoteTitle(const String:text[]); + +/** + * Redraws the vote text from inside a MenuAction_DisplayItem callback. + * Only supported on multiple-choice votes + * + * @param text Vote text to draw. + * @error If called from outside MenuAction_DisplayItem + * @return Plugin_Changed if the change is allowed, Plugin_Continue if it isn't. + */ +native Action:NativeVotes_RedrawVoteItem(const String:text[]); + +/** + * Retrieves voting information from MenuAction_VoteEnd. + * + * @param param2 Second parameter of MenuAction_VoteEnd. + * @param winningVotes Number of votes received by the winning option. + * @param totalVotes Number of total votes received. + * @noreturn + */ +stock NativeVotes_GetInfo(param2, &winningVotes, &totalVotes) +{ + winningVotes = param2 & 0xFFFF; + totalVotes = param2 >> 16; +} + +/** + * Do not edit below this line! + */ +public SharedPlugin:__pl_nativevotes = +{ + name = "nativevotes", + file = "nativevotes.smx", +#if defined REQUIRE_PLUGINS + required = 1, +#else + required = 0, +#endif +}; + +public __pl_nativevotes_SetNTVOptional() +{ + MarkNativeAsOptional("NativeVotes_IsVoteTypeSupported"); + MarkNativeAsOptional("NativeVotes_Create"); + MarkNativeAsOptional("NativeVotes_Close"); + MarkNativeAsOptional("NativeVotes_AddItem"); + MarkNativeAsOptional("NativeVotes_InsertItem"); + MarkNativeAsOptional("NativeVotes_RemoveItem"); + MarkNativeAsOptional("NativeVotes_RemoveAllItems"); + MarkNativeAsOptional("NativeVotes_GetItem"); + MarkNativeAsOptional("NativeVotes_GetItemCount"); + MarkNativeAsOptional("NativeVotes_SetDetails"); + MarkNativeAsOptional("NativeVotes_GetDetails"); + MarkNativeAsOptional("NativeVotes_SetTitle"); + MarkNativeAsOptional("NativeVotes_GetTitle"); + MarkNativeAsOptional("NativeVotes_SetTarget"); + MarkNativeAsOptional("NativeVotes_GetTarget"); + MarkNativeAsOptional("NativeVotes_GetTargetSteam"); + MarkNativeAsOptional("NativeVotes_IsVoteInProgress"); + MarkNativeAsOptional("NativeVotes_GetMaxItems"); + MarkNativeAsOptional("NativeVotes_SetOptionFlags"); + MarkNativeAsOptional("NativeVotes_GetOptionFlags"); + MarkNativeAsOptional("NativeVotes_Cancel"); + MarkNativeAsOptional("NativeVotes_SetResultCallback"); + MarkNativeAsOptional("NativeVotes_CheckVoteDelay"); + MarkNativeAsOptional("NativeVotes_IsClientInVotePool"); + MarkNativeAsOptional("NativeVotes_RedrawClientVote"); + MarkNativeAsOptional("NativeVotes_RedrawClientVote"); + MarkNativeAsOptional("NativeVotes_GetType"); + MarkNativeAsOptional("NativeVotes_SetTeam"); + MarkNativeAsOptional("NativeVotes_GetTeam"); + MarkNativeAsOptional("NativeVotes_SetInitiator"); + MarkNativeAsOptional("NativeVotes_GetInitiator"); + MarkNativeAsOptional("NativeVotes_Display"); + MarkNativeAsOptional("NativeVotes_DisplayPass"); + MarkNativeAsOptional("NativeVotes_DisplayPassCustomToOne"); + MarkNativeAsOptional("NativeVotes_DisplayPassEx"); + MarkNativeAsOptional("NativeVotes_DisplayFail"); + MarkNativeAsOptional("NativeVotes_RegisterVoteManager"); + MarkNativeAsOptional("NativeVotes_DisplayCallVoteFail"); + MarkNativeAsOptional("NativeVotes_RedrawVoteTitle"); + MarkNativeAsOptional("NativeVotes_RedrawVoteItem"); +} diff --git a/includes/outputinfo.inc b/includes/outputinfo.inc new file mode 100644 index 00000000..a24b3cd8 --- /dev/null +++ b/includes/outputinfo.inc @@ -0,0 +1,40 @@ +#if defined _OutputInfo_Included + #endinput +#endif +#define _OutputInfo_Included + +native GetOutputCount(int Entity, const char[] sOutput); +native GetOutputTarget(int Entity, const char[] sOutput, int Index, char[] sTarget); +native GetOutputTargetInput(int Entity, const char[] sOutput, int Index, char[] sTargetInput); +native GetOutputParameter(int Entity, const char[] sOutput, int Index, char[] sParameter); +native Float:GetOutputDelay(int Entity, const char[] sOutput, int Index); + +/** + * Do not edit below this line! + */ +public Extension __ext_outputinfo = +{ + name = "OutputInfo", + file = "outputinfo.ext", +#if defined AUTOLOAD_EXTENSIONS + autoload = 1, +#else + autoload = 0, +#endif +#if defined REQUIRE_EXTENSIONS + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_EXTENSIONS +public __ext_outputinfo_SetNTVOptional() +{ + MarkNativeAsOptional("GetOutputCount"); + MarkNativeAsOptional("GetOutputTarget"); + MarkNativeAsOptional("GetOutputTargetInput"); + MarkNativeAsOptional("GetOutputParameter"); + MarkNativeAsOptional("GetOutputDelay"); +} +#endif diff --git a/includes/zombiereloaded.inc b/includes/zombiereloaded.inc new file mode 100644 index 00000000..d79c798b --- /dev/null +++ b/includes/zombiereloaded.inc @@ -0,0 +1,36 @@ +/* + * ============================================================================ + * + * Zombie:Reloaded + * + * File: zombiereloaded.inc + * Type: Include + * Description: Main API include file. + * Notes: Include this file to include the whole ZR API. + * + * Copyright (C) 2009-2013 Greyscale, Richard Helgeby + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 . + * + * ============================================================================ + */ + +#if defined _zr_included + #endinput +#endif +#define _zr_included + +#include +#include +#include diff --git a/includes/zr/class.zr.inc b/includes/zr/class.zr.inc new file mode 100644 index 00000000..ad6edcfd --- /dev/null +++ b/includes/zr/class.zr.inc @@ -0,0 +1,127 @@ +/* + * ============================================================================ + * + * Zombie:Reloaded + * + * File: class.zr.inc + * Type: Include + * Description: Player class API. + * + * Copyright (C) 2009-2013 Greyscale, Richard Helgeby + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 . + * + * ============================================================================ + */ + +/** + * @section Internal class cache types. Specifies which class data to access. + */ +#define ZR_CLASS_CACHE_ORIGINAL 0 /** Original class data loaded from file. */ +#define ZR_CLASS_CACHE_MODIFIED 1 /** Default cache. Class data modified by eventual multipliers, map configs, commands, etc. */ +#define ZR_CLASS_CACHE_PLAYER 2 /** Current player class attributes. The class index parameter is used as client index when reading from this cache. */ +/** + * @endsection + */ + +/** + * Results when selecting a class for a player. + */ +enum ClassSelectResult +{ + ClassSelected_NoChange, /** No class change was necessary (class already selected). */ + ClassSelected_Instant, /** Class was instantly changed. */ + ClassSelected_NextSpawn /** Class will be used next spawn. */ +} + +/** + * Returns whether a class index is valid or not. + * + * @param classIndex Class index to validate. + * + * @return True if valid, false otherwise. + */ +native bool:ZR_IsValidClassIndex(classIndex); + +/** + * Gets the currently active class index that the player is using. + * + * @param client The client index. + * + * @return The active class index. + */ +native bool:ZR_GetActiveClass(client); + +/** + * Gets the current human class index that the player is using. + * + * @param client The client index. + * + * @return The human class index. + */ +native bool:ZR_GetHumanClass(client); + +/** + * Gets the current zombie class index that the player is using. + * + * @param client The client index. + * + * @return The zombie class index. + */ +native bool:ZR_GetZombieClass(client); + +/** + * Selects a class for a player. + * + * Human class attribute may be instantly applied if player is alive, human and + * instant class change is enabled. Otherwise only the selected index will be + * updated for next spawn. + * + * Class selection will be saved in client cookies if enabled. + * + * @param client Client index. + * @param classIndex Class index. + * @param applyIfPossible Optional. Apply class attributes if conditions allow + * it. Default is true. + * @param saveIfEnabled Optional. Save class selection in client cookies if + * enabled. Default is true. + * + * @return Class selection result. See enum ClassSelectResult. + */ +native ClassSelectResult:ZR_SelectClientClass(client, classIndex, bool:applyIfPossible = true, bool:saveIfEnabled = true); + +/** + * Gets the class index of the class with the specified name. + * + * Note: This search is linear and probably won't perform well in large loops. + * + * @param className Class name to search for. + * @param cacheType Optional. Specifies which class cache to read from, + * except player cache. + * + * @return Class index, or -1 if none found. + */ +native ZR_GetClassByName(const String:className[], cacheType = ZR_CLASS_CACHE_MODIFIED); + +/** + * Gets the class name displayed in the class menu. + * + * @param index Index of the class in a class cache or a client index, + * depending on the cache type specified. + * @param buffer The destination string buffer. + * @param maxlen The length of the destination string buffer. + * @param cacheType Optional. Specifies which class cache to read from. + * @return Number of cells written. -1 on error. + */ +native ZR_GetClassDisplayName(index, String:buffer[], maxlen, cacheType = ZR_CLASS_CACHE_MODIFIED); diff --git a/includes/zr/infect.zr.inc b/includes/zr/infect.zr.inc new file mode 100644 index 00000000..9d45518f --- /dev/null +++ b/includes/zr/infect.zr.inc @@ -0,0 +1,123 @@ +/* + * ============================================================================ + * + * Zombie:Reloaded + * + * File: infect.zr.inc + * Type: Include + * Description: Infect-related natives/forwards. + * + * Copyright (C) 2009-2013 Greyscale, Richard Helgeby + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 . + * + * ============================================================================ + */ + +/** + * Returns true if the player is a zombie, false if not. + * + * @param client The client index. + * + * @return True if zombie, false if not. + * @error Invalid client index, not connected or not alive. + */ +native bool:ZR_IsClientZombie(client); + +/** + * Returns true if the player is a human, false if not. + * + * @param client The client index. + * + * @return True if human, false if not. + * @error Invalid client index, not connected or not alive. + */ +native bool:ZR_IsClientHuman(client); + +/** + * Infects a player. + * + * Note: If the player already is a zombie, the player will be re-infected. + * + * @param client The client to infect. + * @param attacker (Optional) The attacker who did the infect. + * @param motherInfect (Optional) Infect as a mother zombie. + * @param respawnOverride (Optional) Set to true to override respawn cvar. + * @param respawn (Optional) Value to override with. + * + * @error Invalid client index, not connected or not alive. + */ +native ZR_InfectClient(client, attacker = -1, bool:motherInfect = false, bool:respawnOverride = false, bool:respawn = false); + +/** + * Turns a zombie back into a human. + * + * Note: If the player already is a human, this code will still run as the + * player was a zombie. + * + * @param client The client to make human. + * @param respawn Teleport client back to spawn. + * @param protect Start spawn protection on client. + * + * @error Invalid client index, not connected or not alive. + */ +native ZR_HumanClient(client, bool:respawn = false, bool:protect = false); + +/** + * Called when a player is about to become a zombie. + * Here you can modify any variable or block the infection entirely. + * + * @param client The client index. + * @param attacker The the infecter. (-1 if there is no infecter) + * @param motherInfect If the client is becoming a mother zombie. + * @param respawnOverride True if the respawn cvar is being overridden. + * @param respawn The value that respawn is being overridden with. + * + * @return Plugin_Handled to block infection. Anything else + * (like Plugin_Continue) to allow infection. + */ +forward Action:ZR_OnClientInfect(&client, &attacker, &bool:motherInfect, &bool:respawnOverride, &bool:respawn); + +/** + * Called after a player has become a zombie. + * + * @param client The client that was infected. + * @param attacker The the infecter. (-1 if there is no infecter) + * @param motherInfect If the client is a mother zombie. + * @param respawnOverride True if the respawn cvar was overridden. + * @param respawn The value that respawn was overridden with. + */ +forward ZR_OnClientInfected(client, attacker, bool:motherInfect, bool:respawnOverride, bool:respawn); + +/** + * Called when a player is about to become a human. (Through an admin command). + * Here you can modify any variable or block the action entirely. + * + * @param client The client index. + * @param respawn True if the client was respawned, false if not. + * @param protect True if the client spawn protected, false if not. + * + * @return Plugin_Handled to block infection. Anything else + * (like Plugin_Continue) to allow acion. + */ +forward Action:ZR_OnClientHuman(&client, &bool:respawn, &bool:protect); + +/** + * Called after a player has become a human. (Through an admin command.) + * + * @param client The client index. + * @param respawn Whether the client was respawned. + * @param protect Whether the client has spawn protection. + */ +forward ZR_OnClientHumanPost(client, bool:respawn, bool:protect); diff --git a/includes/zr/respawn.zr.inc b/includes/zr/respawn.zr.inc new file mode 100644 index 00000000..e6e87285 --- /dev/null +++ b/includes/zr/respawn.zr.inc @@ -0,0 +1,95 @@ +/* + * ============================================================================ + * + * Zombie:Reloaded + * + * File: respawn.zr.inc + * Type: Include + * Description: Infect-related natives/forwards. + * + * Copyright (C) 2009-2013 Greyscale, Richard Helgeby + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 . + * + * ============================================================================ + */ + +/** + * Conditions for respawning players. + */ +enum ZR_RespawnCondition +{ + ZR_Repsawn_Default = -1, /** Let ZR decide according to its settings. */ + ZR_Respawn_Human = 0, /** Respawn as a human. */ + ZR_Respawn_Zombie, /** Respawn as a zombie. */ + ZR_Respawn_ZombieIfSuicide /** Respawn as a zombie if killed by world damage. */ +} + +/** + * Spawns a player into the round. + * + * @param client The client index. + * @param condition Optional. Set respawn condition, defaults to current + * ZR settings. See ZR_RespawnCondition for details. + * @error Invalid client index, not connected or already alive. + */ +native ZR_RespawnClient(client, ZR_RespawnCondition:condition = ZR_Repsawn_Default); + +/** + * Called right before ZR is about to respawn a player. + * Here you can modify any variable or stop the action entirely. + * + * @param client The client index. + * @param condition Respawn condition. See ZR_RespawnCondition for + * details. + * + * @return Plugin_Handled to block respawn. + */ +forward Action:ZR_OnClientRespawn(&client, &ZR_RespawnCondition:condition); + +/** + * Called after ZR respawned a player. + * + * @param client The client index. + * @param condition Current condition of the respawned player. See + * ZR_RespawnCondition for details. + */ +forward ZR_OnClientRespawned(client, ZR_RespawnCondition:condition); + +/** + * Set if a player died by a suicide or world damage. + + * Note: This will change the respawn condition. + * Note: This value is reset to default by ZR when a zombie player dies. + * + * @param client The client index. + * @param suicide True to say the player suicided, false if killed by another + * player. + * + * @error Invalid client index or not connected. + */ +native ZR_SetKilledByWorld(client, bool:suicide); + +/** + * Get whether the player died by a suicide or world damage. + * + * Note: This value is only valid after death event, and before respawn. + * + * @param client The client index. + * + * @return True if the player died by suicide, false if killed by + * another player. + * @error Invalid client index or not connected. + */ +native bool:ZR_GetKilledByWorld(client); diff --git a/mapchooser_extended/configs/mapchooser_extended/maps/csgo.txt b/mapchooser_extended/configs/mapchooser_extended/maps/csgo.txt new file mode 100644 index 00000000..0f222fdb --- /dev/null +++ b/mapchooser_extended/configs/mapchooser_extended/maps/csgo.txt @@ -0,0 +1,21 @@ +ar_baggage +ar_monastery +ar_shoots +cs_assault +cs_italy +cs_militia +cs_office +de_aztec +de_bank +de_dust +de_dust2 +de_inferno +de_lake +de_mirage +de_nuke +de_safehouse +de_shorttrain +de_stmarc +de_sugarcane +de_train +de_vertigo diff --git a/mapchooser_extended/configs/mapchooser_extended/maps/cstrike.txt b/mapchooser_extended/configs/mapchooser_extended/maps/cstrike.txt new file mode 100644 index 00000000..0edf7e6c --- /dev/null +++ b/mapchooser_extended/configs/mapchooser_extended/maps/cstrike.txt @@ -0,0 +1,28 @@ +as_oilrig +cs_747 +cs_assault +cs_backalley +cs_compound +cs_estate +cs_havana +cs_italy +cs_militia +cs_office +cs_siege +de_airstrip +de_aztec +de_cbble +de_chateau +de_dust2 +de_dust +de_inferno +de_nuke +de_piranesi +de_port +de_prodigy +de_storm +de_survivor +de_tides +de_torn +de_train +de_vertigo diff --git a/mapchooser_extended/configs/mapchooser_extended/maps/dods.txt b/mapchooser_extended/configs/mapchooser_extended/maps/dods.txt new file mode 100644 index 00000000..98759783 --- /dev/null +++ b/mapchooser_extended/configs/mapchooser_extended/maps/dods.txt @@ -0,0 +1,9 @@ +dod_anzio +dod_argentan +dod_avalanche +dod_colmar +dod_donner +dod_flash +dod_jagd +dod_kalt +dod_palermo diff --git a/mapchooser_extended/configs/mapchooser_extended/maps/hl2mp.txt b/mapchooser_extended/configs/mapchooser_extended/maps/hl2mp.txt new file mode 100644 index 00000000..a5e91bc0 --- /dev/null +++ b/mapchooser_extended/configs/mapchooser_extended/maps/hl2mp.txt @@ -0,0 +1,7 @@ +dm_lockdown +dm_overwatch +dm_runoff +dm_steamlab +dm_underpass +dm_resistance +dm_powerhouse diff --git a/mapchooser_extended/configs/mapchooser_extended/maps/tf.txt b/mapchooser_extended/configs/mapchooser_extended/maps/tf.txt new file mode 100644 index 00000000..5faa9c1a --- /dev/null +++ b/mapchooser_extended/configs/mapchooser_extended/maps/tf.txt @@ -0,0 +1,65 @@ +tc_hydro +cp_well +cp_granary +cp_dustbowl +cp_gravelpit +ctf_2fort +ctf_well +cp_badlands +pl_goldrush +cp_fastlane +ctf_turbine +pl_badwater +cp_steel +arena_badlands +arena_granary +arena_lumberyard +arena_ravine +arena_well +cp_egypt_final +cp_junction_final +arena_watchtower +plr_pipeline +arena_sawmill +arena_nucleus +pl_hoodoo_final +koth_sawmill +koth_nucleus +koth_viaduct +ctf_sawmill +arena_offblast_final +cp_yukon_final +koth_harvest_final +koth_harvest_event +ctf_doublecross +cp_gorge +cp_freight_final1 +pl_upward +plr_hightower +pl_thundermountain +cp_coldfront +cp_mountainlab +cp_manor_event +cp_degrootkeep +cp_5gorge +pl_frontier_final +plr_nightfall_final +koth_lakeside_final +koth_badlands +pl_barnblitz +cp_gullywash_final1 +koth_viaduct_event +cp_foundry +sd_doomsday +koth_king +mvm_mannworks +mvm_coaltown +mvm_decoy +koth_lakeside_event +mvm_bigrock +cp_process_final +cp_standin_final +plr_hightower_event +cp_snakewater_final1 +mvm_mannhattan +mvm_rottenburg diff --git a/mapchooser_extended/configs/mapchooser_extended/sounds/tf.cfg b/mapchooser_extended/configs/mapchooser_extended/sounds/tf.cfg new file mode 100644 index 00000000..188480da --- /dev/null +++ b/mapchooser_extended/configs/mapchooser_extended/sounds/tf.cfg @@ -0,0 +1,77 @@ +"MapchooserSoundsList" +{ + "tf" + { + "counter" + { + "1" + { + "sound" "sourcemod/mapchooser/tf2/announcer_begins_1sec.mp3" + "builtin" "vo/announcer_begins_1sec.wav" + "event" "Announcer.RoundBegins1Seconds" + } + "2" + { + "sound" "sourcemod/mapchooser/tf2/announcer_begins_2sec.mp3" + "builtin" "vo/announcer_begins_2sec.wav" + "event" "Announcer.RoundBegins2Seconds" + } + "3" + { + "sound" "sourcemod/mapchooser/tf2/announcer_begins_3sec.mp3" + "builtin" "vo/announcer_begins_3sec.wav" + "event" "Announcer.RoundBegins3Seconds" + } + "4" + { + "sound" "sourcemod/mapchooser/tf2/announcer_begins_4sec.mp3" + "builtin" "vo/announcer_begins_4sec.wav" + "event" "Announcer.RoundBegins4Seconds" + } + "5" + { + "sound" "sourcemod/mapchooser/tf2/announcer_begins_5sec.mp3" + "builtin" "vo/announcer_begins_5sec.wav" + "event" "Announcer.RoundBegins5Seconds" + } + "10" + { + "sound" "sourcemod/mapchooser/tf2/announcer_dec_missionbegins10s01.mp3" + "builtin" "vo/announcer_dec_missionbegins10s01.wav" + } + "30" + { + "sound" "sourcemod/mapchooser/tf2/announcer_dec_missionbegins30s01.mp3" + "builtin" "vo/announcer_dec_missionbegins30s01.wav" + } + "60" + { + "sound" "sourcemod/mapchooser/tf2/announcer_dec_missionbegins60s06.mp3" + "builtin" "vo/announcer_dec_missionbegins60s06.wav" + } + } + "vote start" + { + "sound" "sourcemod/mapchooser/tf2/vote_started.mp3" + "event" "Vote.Created" + "builtin" "ui/vote_started.wav" + } + "vote end" + { + "sound" "sourcemod/mapchooser/tf2/vote_success.mp3" + "event" "Vote.Passed" + "builtin" "ui/vote_success.wav" + } + "vote warning" + { + "sound" "sourcemod/mapchooser/tf2/announcer_dec_missionbegins60s03.mp3" + "builtin" "vo/announcer_dec_missionbegins60s03.wav" + } + "runoff warning" + { + "sound" "sourcemod/mapchooser/tf2/vote_failure.mp3" + "event" "Vote.Failed" + "builtin" "ui/vote_failure.wav" + } + } +} \ No newline at end of file diff --git a/mapchooser_extended/scripting/include/colors.inc b/mapchooser_extended/scripting/include/colors.inc new file mode 120000 index 00000000..27c6be6a --- /dev/null +++ b/mapchooser_extended/scripting/include/colors.inc @@ -0,0 +1 @@ +../../../includes/colors.inc \ No newline at end of file diff --git a/mapchooser_extended/scripting/include/mapchooser_extended.inc b/mapchooser_extended/scripting/include/mapchooser_extended.inc new file mode 100644 index 00000000..e7f03c54 --- /dev/null +++ b/mapchooser_extended/scripting/include/mapchooser_extended.inc @@ -0,0 +1,108 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * MapChooser Extended + * Creates a map vote at appropriate times, setting sm_nextmap to the winning + * vote + * + * MapChooser Extended (C)2011-2013 Powerlord (Ross Bemrose) + * SourceMod (C)2004-2007 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 . + * + * 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 . + * + * Version: $Id$ + */ + +#if defined _mapchooser_extended_included_ + #endinput +#endif +#define _mapchooser_extended_included_ +#include + +// MCE 1.9 series + +enum CanNominateResult +{ + CanNominate_No_VoteFull, /** No, nominations list is full */ + CanNominate_No_VoteInProgress, /** No, map vote is in progress */ + CanNominate_No_VoteComplete, /** No, map vote is completed */ + CanNominate_Yes, /** Yes, you can nominate */ +}; + + +/** + * Called whenever warning timer starts + * + */ +forward OnMapVoteWarningStart(); + +/** + * Called whenever runoff warning timer starts + */ +forward OnMapVoteRunnoffWarningStart(); + +/** + * Called whenever the timer ticks + */ +forward OnMapVoteWarningTick(time); + +/** + * Called whenever vote starts + * + * @deprecated Will be removed in MapChooser 1.8. Use OnMapVoteStarted instead. + */ +forward OnMapVoteStart(); + +/** + * Called whenever vote ends + */ +forward OnMapVoteEnd(const String:map[]); + +/** + * Is a map on the current game's official list? + * This should be treated as informative only. + * + * @param map Name of map to check + * @return true if it's on the list of official maps for this game + */ +native bool:IsMapOfficial(const String:map[]); + +/** + * Is nominate allowed? + * + * @return A CanNominateResult corresponding to whether a vote is allowed or not + */ +native CanNominateResult:CanNominate(); + +native bool:ExcludeMap(const String:map[]); + +public SharedPlugin:__pl_mapchooser_extended = +{ + name = "mapchooser", + file = "mapchooser_extended.smx", +#if defined REQUIRE_PLUGIN + required = 1, +#else + required = 0, +#endif +}; diff --git a/mapchooser_extended/scripting/include/nativevotes.inc b/mapchooser_extended/scripting/include/nativevotes.inc new file mode 120000 index 00000000..78253b76 --- /dev/null +++ b/mapchooser_extended/scripting/include/nativevotes.inc @@ -0,0 +1 @@ +../../../includes/nativevotes.inc \ No newline at end of file diff --git a/mapchooser_extended/scripting/mapchooser_extended.sp b/mapchooser_extended/scripting/mapchooser_extended.sp new file mode 100644 index 00000000..c203c33d --- /dev/null +++ b/mapchooser_extended/scripting/mapchooser_extended.sp @@ -0,0 +1,2343 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * MapChooser Extended + * Creates a map vote at appropriate times, setting sm_nextmap to the winning + * vote. Includes extra options not present in the SourceMod MapChooser + * + * MapChooser Extended (C)2011-2013 Powerlord (Ross Bemrose) + * SourceMod (C)2004-2007 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 . + * + * 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 . + * + * Version: $Id$ + */ + +//#define DEBUG + +#if defined DEBUG + #define assert(%1) if (!(%1)) ThrowError("Debug Assertion Failed"); + #define assert_msg(%1,%2) if (!(%1)) ThrowError(%2); +#else + #define assert(%1) + #define assert_msg(%1,%2) +#endif + +#pragma semicolon 1 +#include +#include +#include "include/mapchooser_extended" +#include +#include +#include + +#undef REQUIRE_PLUGIN +#include + +#define MCE_VERSION "1.10.2" + +#define NV "nativevotes" + +enum RoundCounting +{ + RoundCounting_Standard = 0, + RoundCounting_MvM, + RoundCounting_ArmsRace, +} + +// CSGO requires two cvars to get the game type +enum +{ + GameType_Classic = 0, + GameType_GunGame = 1, + GameType_Training = 2, + GameType_Custom = 3, +} + +enum +{ + GunGameMode_ArmsRace = 0, + GunGameMode_Demolition = 1, + GunGameMode_DeathMatch = 2, +} + +public Plugin:myinfo = +{ + name = "MapChooser Extended", + author = "Powerlord, Zuko, and AlliedModders LLC", + description = "Automated Map Voting with Extensions", + version = MCE_VERSION, + url = "https://forums.alliedmods.net/showthread.php?t=156974" +}; + +/* Valve ConVars */ +new Handle:g_Cvar_Winlimit = INVALID_HANDLE; +new Handle:g_Cvar_Maxrounds = INVALID_HANDLE; +new Handle:g_Cvar_Fraglimit = INVALID_HANDLE; +new Handle:g_Cvar_Bonusroundtime = INVALID_HANDLE; +new Handle:g_Cvar_MatchClinch = INVALID_HANDLE; +new Handle:g_Cvar_VoteNextLevel = INVALID_HANDLE; +new Handle:g_Cvar_GameType = INVALID_HANDLE; +new Handle:g_Cvar_GameMode = INVALID_HANDLE; + +/* Plugin ConVars */ +new Handle:g_Cvar_StartTime = INVALID_HANDLE; +new Handle:g_Cvar_StartRounds = INVALID_HANDLE; +new Handle:g_Cvar_StartFrags = INVALID_HANDLE; +new Handle:g_Cvar_ExtendTimeStep = INVALID_HANDLE; +new Handle:g_Cvar_ExtendRoundStep = INVALID_HANDLE; +new Handle:g_Cvar_ExtendFragStep = INVALID_HANDLE; +new Handle:g_Cvar_ExcludeMaps = INVALID_HANDLE; +new Handle:g_Cvar_IncludeMaps = INVALID_HANDLE; +new Handle:g_Cvar_NoVoteMode = INVALID_HANDLE; +new Handle:g_Cvar_Extend = INVALID_HANDLE; +new Handle:g_Cvar_DontChange = INVALID_HANDLE; +new Handle:g_Cvar_EndOfMapVote = INVALID_HANDLE; +new Handle:g_Cvar_VoteDuration = INVALID_HANDLE; + +new Handle:g_VoteTimer = INVALID_HANDLE; +new Handle:g_RetryTimer = INVALID_HANDLE; +new Handle:g_WarningTimer = INVALID_HANDLE; + +/* Data Handles */ +new Handle:g_MapList = INVALID_HANDLE; +new Handle:g_NominateList = INVALID_HANDLE; +new Handle:g_NominateOwners = INVALID_HANDLE; +new Handle:g_OldMapList = INVALID_HANDLE; +new Handle:g_NextMapList = INVALID_HANDLE; +new Handle:g_VoteMenu = INVALID_HANDLE; + +new g_Extends; +new g_TotalRounds; +new bool:g_HasVoteStarted; +new bool:g_WaitingForVote; +new bool:g_MapVoteCompleted; +new bool:g_ChangeMapAtRoundEnd; +new bool:g_ChangeMapInProgress; +new bool:g_HasIntermissionStarted = false; +new g_mapFileSerial = -1; + +new g_NominateCount = 0; +new MapChange:g_ChangeTime; + +new Handle:g_NominationsResetForward = INVALID_HANDLE; +new Handle:g_MapVoteStartedForward = INVALID_HANDLE; + +/* Mapchooser Extended Plugin ConVars */ + +new Handle:g_Cvar_RunOff = INVALID_HANDLE; +new Handle:g_Cvar_RunOffPercent = INVALID_HANDLE; +new Handle:g_Cvar_BlockSlots = INVALID_HANDLE; +new Handle:g_Cvar_MaxRunOffs = INVALID_HANDLE; +new Handle:g_Cvar_StartTimePercent = INVALID_HANDLE; +new Handle:g_Cvar_StartTimePercentEnable = INVALID_HANDLE; +new Handle:g_Cvar_WarningTime = INVALID_HANDLE; +new Handle:g_Cvar_RunOffWarningTime = INVALID_HANDLE; +new Handle:g_Cvar_MenuStyle = INVALID_HANDLE; +new Handle:g_Cvar_TimerLocation = INVALID_HANDLE; +new Handle:g_Cvar_ExtendPosition = INVALID_HANDLE; +new Handle:g_Cvar_MarkCustomMaps = INVALID_HANDLE; +new Handle:g_Cvar_RandomizeNominations = INVALID_HANDLE; +new Handle:g_Cvar_HideTimer = INVALID_HANDLE; +new Handle:g_Cvar_NoVoteOption = INVALID_HANDLE; + +/* Mapchooser Extended Data Handles */ +new Handle:g_OfficialList = INVALID_HANDLE; + +/* Mapchooser Extended Forwards */ +new Handle:g_MapVoteWarningStartForward = INVALID_HANDLE; +new Handle:g_MapVoteWarningTickForward = INVALID_HANDLE; +new Handle:g_MapVoteStartForward = INVALID_HANDLE; +new Handle:g_MapVoteEndForward = INVALID_HANDLE; +new Handle:g_MapVoteRunoffStartForward = INVALID_HANDLE; + +/* Mapchooser Extended Globals */ +new g_RunoffCount = 0; +new g_mapOfficialFileSerial = -1; +new bool:g_NativeVotes = false; +new String:g_GameModName[64]; +new bool:g_WarningInProgress = false; +new bool:g_AddNoVote = false; + +new RoundCounting:g_RoundCounting = RoundCounting_Standard; + +/* Upper bound of how many team there could be */ +#define MAXTEAMS 10 +new g_winCount[MAXTEAMS]; + +new bool:g_BlockedSlots = false; +new g_ObjectiveEnt = -1; + +enum TimerLocation +{ + TimerLocation_Hint = 0, + TimerLocation_Center = 1, + TimerLocation_Chat = 2, +} + +enum WarningType +{ + WarningType_Vote, + WarningType_Revote, +} + +#define VOTE_EXTEND "##extend##" +#define VOTE_DONTCHANGE "##dontchange##" + +/* Mapchooser Extended Defines */ +#define LINE_ONE "##lineone##" +#define LINE_TWO "##linetwo##" +#define LINE_SPACER "##linespacer##" +#define FAILURE_TIMER_LENGTH 5 + +public OnPluginStart() +{ + LoadTranslations("mapchooser_extended.phrases"); + LoadTranslations("basevotes.phrases"); + LoadTranslations("common.phrases"); + + new arraySize = ByteCountToCells(PLATFORM_MAX_PATH); + g_MapList = CreateArray(arraySize); + g_NominateList = CreateArray(arraySize); + g_NominateOwners = CreateArray(1); + g_OldMapList = CreateArray(arraySize); + g_NextMapList = CreateArray(arraySize); + g_OfficialList = CreateArray(arraySize); + + GetGameFolderName(g_GameModName, sizeof(g_GameModName)); + + g_Cvar_EndOfMapVote = CreateConVar("mce_endvote", "1", "Specifies if MapChooser should run an end of map vote", _, true, 0.0, true, 1.0); + + g_Cvar_StartTime = CreateConVar("mce_starttime", "10.0", "Specifies when to start the vote based on time remaining.", _, true, 1.0); + g_Cvar_StartRounds = CreateConVar("mce_startround", "2.0", "Specifies when to start the vote based on rounds remaining. Use 0 on DoD:S, CS:S, and TF2 to start vote during bonus round time", _, true, 0.0); + g_Cvar_StartFrags = CreateConVar("mce_startfrags", "5.0", "Specifies when to start the vote base on frags remaining.", _, true, 1.0); + g_Cvar_ExtendTimeStep = CreateConVar("mce_extend_timestep", "15", "Specifies how much many more minutes each extension makes", _, true, 5.0); + g_Cvar_ExtendRoundStep = CreateConVar("mce_extend_roundstep", "5", "Specifies how many more rounds each extension makes", _, true, 1.0); + g_Cvar_ExtendFragStep = CreateConVar("mce_extend_fragstep", "10", "Specifies how many more frags are allowed when map is extended.", _, true, 5.0); + g_Cvar_ExcludeMaps = CreateConVar("mce_exclude", "5", "Specifies how many past maps to exclude from the vote.", _, true, 0.0); + g_Cvar_IncludeMaps = CreateConVar("mce_include", "5", "Specifies how many maps to include in the vote.", _, true, 2.0, true, 6.0); + g_Cvar_NoVoteMode = CreateConVar("mce_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("mce_extend", "0", "Number of extensions allowed each map.", _, true, 0.0); + g_Cvar_DontChange = CreateConVar("mce_dontchange", "1", "Specifies if a 'Don't Change' option should be added to early votes", _, true, 0.0); + g_Cvar_VoteDuration = CreateConVar("mce_voteduration", "20", "Specifies how long the mapvote should be available for.", _, true, 5.0); + + // MapChooser Extended cvars + CreateConVar("mce_version", MCE_VERSION, "MapChooser Extended Version", FCVAR_SPONLY|FCVAR_NOTIFY|FCVAR_DONTRECORD); + + g_Cvar_RunOff = CreateConVar("mce_runoff", "1", "Hold run off votes if winning choice has less than a certain percentage of votes", _, true, 0.0, true, 1.0); + g_Cvar_RunOffPercent = CreateConVar("mce_runoffpercent", "50", "If winning choice has less than this percent of votes, hold a runoff", _, true, 0.0, true, 100.0); + g_Cvar_BlockSlots = CreateConVar("mce_blockslots", "1", "Block slots to prevent accidental votes. Only applies when Voice Command style menus are in use.", _, true, 0.0, true, 1.0); + //g_Cvar_BlockSlotsCount = CreateConVar("mce_blockslots_count", "2", "Number of slots to block.", _, true, 1.0, true, 3.0); + g_Cvar_MaxRunOffs = CreateConVar("mce_maxrunoffs", "1", "Number of run off votes allowed each map.", _, true, 0.0); + g_Cvar_StartTimePercent = CreateConVar("mce_start_percent", "35.0", "Specifies when to start the vote based on percents.", _, true, 0.0, true, 100.0); + g_Cvar_StartTimePercentEnable = CreateConVar("mce_start_percent_enable", "0", "Enable or Disable percentage calculations when to start vote.", _, true, 0.0, true, 1.0); + g_Cvar_WarningTime = CreateConVar("mce_warningtime", "15.0", "Warning time in seconds.", _, true, 0.0, true, 60.0); + g_Cvar_RunOffWarningTime = CreateConVar("mce_runoffvotewarningtime", "5.0", "Warning time for runoff vote in seconds.", _, true, 0.0, true, 30.0); + g_Cvar_MenuStyle = CreateConVar("mce_menustyle", "0", "Menu Style. 0 is the game's default, 1 is the older Valve style that requires you to press Escape to see the menu, 2 is the newer 1-9 button Voice Command style, unavailable in some games. Ignored on TF2 if NativeVotes Plugin is loaded.", _, true, 0.0, true, 2.0); + g_Cvar_TimerLocation = CreateConVar("mce_warningtimerlocation", "0", "Location for the warning timer text. 0 is HintBox, 1 is Center text, 2 is Chat. Defaults to HintBox.", _, true, 0.0, true, 2.0); + g_Cvar_MarkCustomMaps = CreateConVar("mce_markcustommaps", "1", "Mark custom maps in the vote list. 0 = Disabled, 1 = Mark with *, 2 = Mark with phrase.", _, true, 0.0, true, 2.0); + g_Cvar_ExtendPosition = CreateConVar("mce_extendposition", "0", "Position of Extend/Don't Change options. 0 = at end, 1 = at start.", _, true, 0.0, true, 1.0); + g_Cvar_RandomizeNominations = CreateConVar("mce_randomizeorder", "0", "Randomize map order?", _, true, 0.0, true, 1.0); + g_Cvar_HideTimer = CreateConVar("mce_hidetimer", "0", "Hide the MapChooser Extended warning timer", _, true, 0.0, true, 1.0); + g_Cvar_NoVoteOption = CreateConVar("mce_addnovote", "1", "Add \"No Vote\" to vote menu?", _, true, 0.0, true, 1.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 "); + + // Mapchooser Extended Commands + RegAdminCmd("mce_reload_maplist", Command_ReloadMaps, ADMFLAG_CHANGEMAP, "mce_reload_maplist - Reload the Official Maplist file."); + + g_Cvar_Winlimit = FindConVar("mp_winlimit"); + g_Cvar_Maxrounds = FindConVar("mp_maxrounds"); + g_Cvar_Fraglimit = FindConVar("mp_fraglimit"); + + new EngineVersion:version = GetEngineVersionCompat(); + + decl String:mapListPath[PLATFORM_MAX_PATH]; + + BuildPath(Path_SM, mapListPath, PLATFORM_MAX_PATH, "configs/mapchooser_extended/maps/%s.txt", g_GameModName); + SetMapListCompatBind("official", mapListPath); + + switch (version) + { + case Engine_TF2: + { + g_Cvar_VoteNextLevel = FindConVar("sv_vote_issue_nextlevel_allowed"); + g_Cvar_Bonusroundtime = FindConVar("mp_bonusroundtime"); + } + + case Engine_CSGO: + { + g_Cvar_VoteNextLevel = FindConVar("mp_endmatch_votenextmap"); + g_Cvar_GameType = FindConVar("game_type"); + g_Cvar_GameMode = FindConVar("game_mode"); + g_Cvar_Bonusroundtime = FindConVar("mp_round_restart_delay"); + } + + case Engine_DODS: + { + g_Cvar_Bonusroundtime = FindConVar("dod_bonusroundtime"); + } + + case Engine_CSS: + { + g_Cvar_Bonusroundtime = FindConVar("mp_round_restart_delay"); + } + + default: + { + g_Cvar_Bonusroundtime = FindConVar("mp_bonusroundtime"); + } + } + + if (g_Cvar_Winlimit != INVALID_HANDLE || g_Cvar_Maxrounds != INVALID_HANDLE) + { + switch (version) + { + case Engine_TF2: + { + HookEvent("teamplay_win_panel", Event_TeamPlayWinPanel); + HookEvent("teamplay_restart_round", Event_TFRestartRound); + HookEvent("arena_win_panel", Event_TeamPlayWinPanel); + HookEvent("pve_win_panel", Event_MvMWinPanel); + } + + case Engine_NuclearDawn: + { + HookEvent("round_win", Event_RoundEnd); + } + + case Engine_CSGO: + { + HookEvent("round_end", Event_RoundEnd); + HookEvent("cs_intermission", Event_Intermission); + HookEvent("announce_phase_end", Event_PhaseEnd); + g_Cvar_MatchClinch = FindConVar("mp_match_can_clinch"); + } + + case Engine_DODS: + { + HookEvent("dod_round_win", Event_RoundEnd); + } + + default: + { + HookEvent("round_end", Event_RoundEnd); + } + } + } + + if (g_Cvar_Fraglimit != INVALID_HANDLE) + { + HookEvent("player_death", Event_PlayerDeath); + } + + AutoExecConfig(true, "mapchooser_extended"); + + //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 != INVALID_HANDLE) + { + SetConVarBounds(g_Cvar_Bonusroundtime, ConVarBound_Upper, true, 30.0); + } + + g_NominationsResetForward = CreateGlobalForward("OnNominationRemoved", ET_Ignore, Param_String, Param_Cell); + g_MapVoteStartedForward = CreateGlobalForward("OnMapVoteStarted", ET_Ignore); + + //MapChooser Extended Forwards + g_MapVoteStartForward = CreateGlobalForward("OnMapVoteStart", ET_Ignore); // Deprecated + g_MapVoteEndForward = CreateGlobalForward("OnMapVoteEnd", ET_Ignore, Param_String); + g_MapVoteWarningStartForward = CreateGlobalForward("OnMapVoteWarningStart", ET_Ignore); + g_MapVoteWarningTickForward = CreateGlobalForward("OnMapVoteWarningTick", ET_Ignore, Param_Cell); + g_MapVoteRunoffStartForward = CreateGlobalForward("OnMapVoteRunnoffWarningStart", ET_Ignore); + +} + +public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max) +{ + if (LibraryExists("mapchooser")) + { + strcopy(error, err_max, "MapChooser already loaded, aborting."); + return APLRes_Failure; + } + + RegPluginLibrary("mapchooser"); + + MarkNativeAsOptional("GetEngineVersion"); + + 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); + + // MapChooser Extended natives + CreateNative("IsMapOfficial", Native_IsMapOfficial); + CreateNative("CanNominate", Native_CanNominate); + + // BotoX + CreateNative("ExcludeMap", Native_ExcludeMap); + + return APLRes_Success; +} + +public OnAllPluginsLoaded() +{ + g_NativeVotes = LibraryExists(NV) && NativeVotes_IsVoteTypeSupported(NativeVotesType_NextLevelMult); +} + +public OnLibraryAdded(const String:name[]) +{ + if (StrEqual(name, NV) && NativeVotes_IsVoteTypeSupported(NativeVotesType_NextLevelMult)) + { + g_NativeVotes = true; + } +} + +public OnLibraryRemoved(const String:name[]) +{ + if (StrEqual(name, NV)) + { + g_NativeVotes = false; + } +} + +public OnMapStart() +{ + decl String:folder[64]; + GetGameFolderName(folder, sizeof(folder)); + + g_RoundCounting = RoundCounting_Standard; + g_ObjectiveEnt = -1; + + if (strcmp(folder, "tf") == 0 && GameRules_GetProp("m_bPlayingMannVsMachine")) + { + g_RoundCounting = RoundCounting_MvM; + g_ObjectiveEnt = EntIndexToEntRef(FindEntityByClassname(-1, "tf_objective_resource")); + } + else if (strcmp(folder, "csgo") == 0 && GetConVarInt(g_Cvar_GameType) == GameType_GunGame && + GetConVarInt(g_Cvar_GameMode) == GunGameMode_ArmsRace) + { + g_RoundCounting = RoundCounting_ArmsRace; + } +} + +public OnConfigsExecuted() +{ + if (ReadMapList(g_MapList, + g_mapFileSerial, + "mapchooser", + MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER) + != INVALID_HANDLE) + + { + if (g_mapFileSerial == -1) + { + LogError("Unable to create a valid map list."); + } + } + + // Disable the next level vote in TF2 and CS:GO + // In TF2, this has two effects: 1. Stop the next level vote (which overlaps rtv functionality). + // 2. Stop the built-in end level vote. This is the only thing that happens in CS:GO + if (g_Cvar_VoteNextLevel != INVALID_HANDLE) + { + SetConVarBool(g_Cvar_VoteNextLevel, false); + } + + CreateNextVote(); + SetupTimeleftTimer(); + + g_TotalRounds = 0; + + g_Extends = 0; + + g_MapVoteCompleted = false; + + g_NominateCount = 0; + ClearArray(g_NominateList); + ClearArray(g_NominateOwners); + + for (new i=0; i GetConVarInt(g_Cvar_ExcludeMaps)) + { + RemoveFromArray(g_OldMapList, 0); + } +} + +public OnClientDisconnect(client) +{ + new index = FindValueInArray(g_NominateOwners, client); + + if (index == -1) + { + return; + } + + new String:oldmap[PLATFORM_MAX_PATH]; + GetArrayString(g_NominateList, index, oldmap, PLATFORM_MAX_PATH); + Call_StartForward(g_NominationsResetForward); + Call_PushString(oldmap); + Call_PushCell(GetArrayCell(g_NominateOwners, index)); + Call_Finish(); + + RemoveFromArray(g_NominateOwners, index); + RemoveFromArray(g_NominateList, index); + g_NominateCount--; +} + +public Action:Command_SetNextmap(client, args) +{ + if (args < 1) + { + CReplyToCommand(client, "[MCE] Usage: sm_setnextmap "); + return Plugin_Handled; + } + + decl String:map[PLATFORM_MAX_PATH]; + GetCmdArg(1, map, PLATFORM_MAX_PATH); + + if (!IsMapValid(map)) + { + CReplyToCommand(client, "[MCE] %t", "Map was not found", map); + return Plugin_Handled; + } + + ShowActivity(client, "%t", "Changed Next Map", map); + LogAction(client, -1, "\"%L\" changed nextmap to \"%s\"", client, map); + + SetNextMap(map); + g_MapVoteCompleted = true; + + return Plugin_Handled; +} + +public Action:Command_ReloadMaps(client, args) +{ + InitializeOfficialMapList(); +} + +public OnMapTimeLeftChanged() +{ + if (GetArraySize(g_MapList)) + { + SetupTimeleftTimer(); + } +} + +SetupTimeleftTimer() +{ + new time; + if (GetMapTimeLeft(time) && time > 0) + { + new startTime; + if (GetConVarBool(g_Cvar_StartTimePercentEnable)) + { + new timeLimit; + if (GetMapTimeLimit(timeLimit) && timeLimit > 0) + { + startTime = GetConVarInt(g_Cvar_StartTimePercent) * (timeLimit * 60) / 100; + } + } + else + { + startTime = GetConVarInt(g_Cvar_StartTime) * 60; + } + + if (time - startTime < 0 && GetConVarBool(g_Cvar_EndOfMapVote) && !g_MapVoteCompleted && !g_HasVoteStarted) + { + SetupWarningTimer(WarningType_Vote); + } + else + { + if (g_WarningTimer == INVALID_HANDLE) + { + if (g_VoteTimer != INVALID_HANDLE) + { + KillTimer(g_VoteTimer); + g_VoteTimer = INVALID_HANDLE; + } + + //g_VoteTimer = CreateTimer(float(time - startTime), Timer_StartMapVoteTimer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE); + g_VoteTimer = CreateTimer(float(time - startTime), Timer_StartWarningTimer, _, TIMER_FLAG_NO_MAPCHANGE); + } + } + } +} + +public Action:Timer_StartWarningTimer(Handle:timer) +{ + g_VoteTimer = INVALID_HANDLE; + + if (!g_WarningInProgress || g_WarningTimer == INVALID_HANDLE) + { + SetupWarningTimer(WarningType_Vote); + } +} + +public Action:Timer_StartMapVote(Handle:timer, Handle:data) +{ + static timePassed; + + // This is still necessary because InitiateVote still calls this directly via the retry timer + if (!GetArraySize(g_MapList) || !GetConVarBool(g_Cvar_EndOfMapVote) || g_MapVoteCompleted || g_HasVoteStarted) + { + g_WarningTimer = INVALID_HANDLE; + return Plugin_Stop; + } + + ResetPack(data); + new warningMaxTime = ReadPackCell(data); + new warningTimeRemaining = warningMaxTime - timePassed; + + new String:warningPhrase[32]; + ReadPackString(data, warningPhrase, sizeof(warningPhrase)); + + // Tick timer for external plugins + Call_StartForward(g_MapVoteWarningTickForward); + Call_PushCell(warningTimeRemaining); + Call_Finish(); + + if (timePassed == 0 || !GetConVarBool(g_Cvar_HideTimer)) + { + new TimerLocation:timerLocation = TimerLocation:GetConVarInt(g_Cvar_TimerLocation); + + switch(timerLocation) + { + case TimerLocation_Center: + { + PrintCenterTextAll("%t", warningPhrase, warningTimeRemaining); + } + + case TimerLocation_Chat: + { + PrintToChatAll("%t", warningPhrase, warningTimeRemaining); + } + + default: + { + PrintHintTextToAll("%t", warningPhrase, warningTimeRemaining); + } + } + } + + if (timePassed++ >= warningMaxTime) + { + if (timer == g_RetryTimer) + { + g_WaitingForVote = false; + g_RetryTimer = INVALID_HANDLE; + } + else + { + g_WarningTimer = INVALID_HANDLE; + } + + timePassed = 0; + new MapChange:mapChange = MapChange:ReadPackCell(data); + new Handle:hndl = Handle:ReadPackCell(data); + + InitiateVote(mapChange, hndl); + + return Plugin_Stop; + } + return Plugin_Continue; +} + +public Event_TFRestartRound(Handle:event, const String:name[], bool:dontBroadcast) +{ + /* Game got restarted - reset our round count tracking */ + g_TotalRounds = 0; +} + +public Event_TeamPlayWinPanel(Handle:event, const String:name[], bool:dontBroadcast) +{ + if (g_ChangeMapAtRoundEnd) + { + g_ChangeMapAtRoundEnd = false; + CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE); + g_ChangeMapInProgress = true; + } + + new bluescore = GetEventInt(event, "blue_score"); + new redscore = GetEventInt(event, "red_score"); + + if(GetEventInt(event, "round_complete") == 1 || StrEqual(name, "arena_win_panel")) + { + g_TotalRounds++; + + if (!GetArraySize(g_MapList) || g_HasVoteStarted || g_MapVoteCompleted || !GetConVarBool(g_Cvar_EndOfMapVote)) + { + return; + } + + CheckMaxRounds(g_TotalRounds); + + switch(GetEventInt(event, "winning_team")) + { + case 3: + { + CheckWinLimit(bluescore); + } + case 2: + { + CheckWinLimit(redscore); + } + //We need to do nothing on winning_team == 0 this indicates stalemate. + default: + { + return; + } + } + } +} + +public Event_MvMWinPanel(Handle:event, const String:name[], bool:dontBroadcast) +{ + if (GetEventInt(event, "winning_team") == 2) + { + new objectiveEnt = EntRefToEntIndex(g_ObjectiveEnt); + if (objectiveEnt != INVALID_ENT_REFERENCE) + { + g_TotalRounds = GetEntProp(g_ObjectiveEnt, Prop_Send, "m_nMannVsMachineWaveCount"); + CheckMaxRounds(g_TotalRounds); + } + } +} + +public Event_Intermission(Handle:event, const String:name[], bool:dontBroadcast) +{ + g_HasIntermissionStarted = true; +} + +public Event_PhaseEnd(Handle:event, const String:name[], bool:dontBroadcast) +{ + /* announce_phase_end fires for both half time and the end of the map, but intermission fires first for end of the map. */ + if (g_HasIntermissionStarted) + { + return; + } + + /* No intermission yet, so this must be half time. Swap the score counters. */ + new t_score = g_winCount[2]; + g_winCount[2] = g_winCount[3]; + g_winCount[3] = t_score; +} + +public Event_WeaponRank(Handle:event, const String:name[], bool:dontBroadcast) +{ + new rank = GetEventInt(event, "weaponrank"); + if (rank > g_TotalRounds) + { + g_TotalRounds = rank; + CheckMaxRounds(g_TotalRounds); + } +} + +/* You ask, why don't you just use team_score event? And I answer... Because CSS doesn't. */ +public Event_RoundEnd(Handle:event, const String:name[], bool:dontBroadcast) +{ + if (g_RoundCounting == RoundCounting_ArmsRace) + { + return; + } + + if (g_ChangeMapAtRoundEnd) + { + g_ChangeMapAtRoundEnd = false; + CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE); + g_ChangeMapInProgress = true; + } + + new winner; + if (strcmp(name, "round_win") == 0 || strcmp(name, "dod_round_win") == 0) + { + // Nuclear Dawn & DoD:S + winner = GetEventInt(event, "team"); + } + else + { + winner = GetEventInt(event, "winner"); + } + + if (winner == 0 || winner == 1 || !GetConVarBool(g_Cvar_EndOfMapVote)) + { + return; + } + + if (winner >= MAXTEAMS) + { + SetFailState("Mod exceed maximum team count - Please file a bug report."); + } + + g_TotalRounds++; + + g_winCount[winner]++; + + if (!GetArraySize(g_MapList) || g_HasVoteStarted || g_MapVoteCompleted) + { + return; + } + + CheckWinLimit(g_winCount[winner]); + CheckMaxRounds(g_TotalRounds); +} + +public CheckWinLimit(winner_score) +{ + if (g_Cvar_Winlimit != INVALID_HANDLE) + { + new winlimit = GetConVarInt(g_Cvar_Winlimit); + if (winlimit) + { + if (winner_score >= (winlimit - GetConVarInt(g_Cvar_StartRounds))) + { + if (!g_WarningInProgress || g_WarningTimer == INVALID_HANDLE) + { + SetupWarningTimer(WarningType_Vote, MapChange_MapEnd); + //InitiateVote(MapChange_MapEnd, INVALID_HANDLE); + } + } + } + } + + if (g_Cvar_MatchClinch != INVALID_HANDLE && g_Cvar_Maxrounds != INVALID_HANDLE) + { + new bool:clinch = GetConVarBool(g_Cvar_MatchClinch); + + if (clinch) + { + new maxrounds = GetConVarInt(g_Cvar_Maxrounds); + new winlimit = RoundFloat(maxrounds / 2.0); + + if(winner_score == winlimit - 1) + { + if (!g_WarningInProgress || g_WarningTimer == INVALID_HANDLE) + { + SetupWarningTimer(WarningType_Vote, MapChange_MapEnd); + //InitiateVote(MapChange_MapEnd, INVALID_HANDLE); + } + } + } + } +} + +public CheckMaxRounds(roundcount) +{ + new maxrounds = 0; + + if (g_RoundCounting == RoundCounting_ArmsRace) + { + maxrounds = GameRules_GetProp("m_iNumGunGameProgressiveWeaponsCT"); + } + else if (g_RoundCounting == RoundCounting_MvM) + { + maxrounds = GetEntProp(g_ObjectiveEnt, Prop_Send, "m_nMannVsMachineMaxWaveCount"); + } + else if (g_Cvar_Maxrounds != INVALID_HANDLE) + { + maxrounds = GetConVarInt(g_Cvar_Maxrounds); + } + else + { + return; + } + + if (maxrounds) + { + if (roundcount >= (maxrounds - GetConVarInt(g_Cvar_StartRounds))) + { + if (!g_WarningInProgress || g_WarningTimer == INVALID_HANDLE) + { + SetupWarningTimer(WarningType_Vote, MapChange_MapEnd); + //InitiateVote(MapChange_MapEnd, INVALID_HANDLE); + } + } + } +} + +public Event_PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast) +{ + if (!GetArraySize(g_MapList) || g_Cvar_Fraglimit == INVALID_HANDLE || g_HasVoteStarted) + { + return; + } + + if (!GetConVarInt(g_Cvar_Fraglimit) || !GetConVarBool(g_Cvar_EndOfMapVote)) + { + return; + } + + if (g_MapVoteCompleted) + { + return; + } + + new fragger = GetClientOfUserId(GetEventInt(event, "attacker")); + + if (!fragger) + { + return; + } + + if (GetClientFrags(fragger) >= (GetConVarInt(g_Cvar_Fraglimit) - GetConVarInt(g_Cvar_StartFrags))) + { + if (!g_WarningInProgress || g_WarningTimer == INVALID_HANDLE) + { + SetupWarningTimer(WarningType_Vote, MapChange_MapEnd); + //InitiateVote(MapChange_MapEnd, INVALID_HANDLE); + } + } +} + +public Action:Command_Mapvote(client, args) +{ + ShowActivity2(client, "[MCE] ", "%t", "Initiated Vote Map"); + + SetupWarningTimer(WarningType_Vote, MapChange_MapEnd, INVALID_HANDLE, true); + + //InitiateVote(MapChange_MapEnd, INVALID_HANDLE); + + 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. + */ +InitiateVote(MapChange:when, Handle:inputlist=INVALID_HANDLE) +{ + g_WaitingForVote = true; + g_WarningInProgress = false; + + // Check if a nativevotes vots is in progress first + // NativeVotes running at the same time as a regular vote can cause hintbox problems, + // so always check for a standard vote + if (IsVoteInProgress() || (g_NativeVotes && NativeVotes_IsVoteInProgress())) + { + // Can't start a vote, try again in 5 seconds. + //g_RetryTimer = CreateTimer(5.0, Timer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE); + + CPrintToChatAll("[MCE] %t", "Cannot Start Vote", FAILURE_TIMER_LENGTH); + new Handle:data; + g_RetryTimer = CreateDataTimer(1.0, Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT); + + /* Mapchooser Extended */ + WritePackCell(data, FAILURE_TIMER_LENGTH); + + if (GetConVarBool(g_Cvar_RunOff) && g_RunoffCount > 0) + { + WritePackString(data, "Revote Warning"); + } else { + WritePackString(data, "Vote Warning"); + } + /* End Mapchooser Extended */ + + WritePackCell(data, _:when); + WritePackCell(data, _:inputlist); + ResetPack(data); + 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; + + if (g_NativeVotes) + { + g_VoteMenu = NativeVotes_Create(Handler_MapVoteMenu, NativeVotesType_NextLevelMult, MenuAction_End | MenuAction_VoteCancel | MenuAction_Display | MenuAction_DisplayItem); + NativeVotes_SetResultCallback(g_VoteMenu, Handler_NativeVoteFinished); + } + else + { + new Handle:menuStyle = GetMenuStyleHandle(MenuStyle:GetConVarInt(g_Cvar_MenuStyle)); + + if (menuStyle != INVALID_HANDLE) + { + g_VoteMenu = CreateMenuEx(menuStyle, Handler_MapVoteMenu, MenuAction_End | MenuAction_Display | MenuAction_DisplayItem | MenuAction_VoteCancel); + } + else + { + // You chose... poorly + g_VoteMenu = CreateMenu(Handler_MapVoteMenu, MenuAction_End | MenuAction_Display | MenuAction_DisplayItem | MenuAction_VoteCancel); + } + + g_AddNoVote = GetConVarBool(g_Cvar_NoVoteOption); + + // Block Vote Slots + if (GetConVarBool(g_Cvar_BlockSlots)) + { + new Handle:radioStyle = GetMenuStyleHandle(MenuStyle_Radio); + + if (GetMenuStyle(g_VoteMenu) == radioStyle) + { + g_BlockedSlots = true; + AddMenuItem(g_VoteMenu, LINE_ONE, "Choose something...", ITEMDRAW_DISABLED); + AddMenuItem(g_VoteMenu, LINE_TWO, "...will ya?", ITEMDRAW_DISABLED); + if (!g_AddNoVote) + { + AddMenuItem(g_VoteMenu, LINE_SPACER, "", ITEMDRAW_SPACER); + } + } else { + g_BlockedSlots = false; + } + } + + if (g_AddNoVote) + { + SetMenuOptionFlags(g_VoteMenu, MENUFLAG_BUTTON_NOVOTE); + } + + SetMenuTitle(g_VoteMenu, "Vote Nextmap"); + SetVoteResultCallback(g_VoteMenu, 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. + */ + + decl String:map[PLATFORM_MAX_PATH]; + + /* No input given - User our internal nominations and maplist */ + if (inputlist == INVALID_HANDLE) + { + new Handle:randomizeList = INVALID_HANDLE; + if (GetConVarBool(g_Cvar_RandomizeNominations)) + { + randomizeList = CloneArray(g_NominateList); + } + + new nominateCount = GetArraySize(g_NominateList); + + new voteSize = GetVoteSize(); + + // The if and else if could be combined, but it looks extremely messy + // This is a hack to lower the vote menu size by 1 when Don't Change or Extend Map should appear + if (g_NativeVotes) + { + if ((when == MapChange_Instant || when == MapChange_RoundEnd) && GetConVarBool(g_Cvar_DontChange)) + { + voteSize--; + } + else if (GetConVarBool(g_Cvar_Extend) && g_Extends < GetConVarInt(g_Cvar_Extend)) + { + voteSize--; + } + } + + /* Smaller of the two - It should be impossible for nominations to exceed the size though (cvar changed mid-map?) */ + new nominationsToAdd = nominateCount >= voteSize ? voteSize : nominateCount; + + new bool:extendFirst = GetConVarBool(g_Cvar_ExtendPosition); + + if (extendFirst) + { + AddExtendToMenu(g_VoteMenu, when); + } + + for (new i=0; i= availableMaps) + { + //Run out of maps, this will have to do. + break; + } + } + + if (randomizeList != INVALID_HANDLE) + { + // Fisher-Yates Shuffle + for (new j = GetArraySize(randomizeList) - 1; j >= 1; j--) + { + new k = GetRandomInt(0, j); + SwapArrayItems(randomizeList, j, k); + } + + for (new j = 0; j < GetArraySize(randomizeList); j++) + { + GetArrayString(randomizeList, j, map, PLATFORM_MAX_PATH); + AddMapItem(map); + } + + CloseHandle(randomizeList); + randomizeList = INVALID_HANDLE; + } + + /* Wipe out our nominations list - Nominations have already been informed of this */ + g_NominateCount = 0; + ClearArray(g_NominateOwners); + ClearArray(g_NominateList); + + if (!extendFirst) + { + AddExtendToMenu(g_VoteMenu, when); + } + } + else //We were given a list of maps to start the vote with + { + new size = GetArraySize(inputlist); + + for (new i=0; i 0) + { + ExtendMapTimeLimit(GetConVarInt(g_Cvar_ExtendTimeStep)*60); + } + } + + if (g_Cvar_Winlimit != INVALID_HANDLE) + { + new winlimit = GetConVarInt(g_Cvar_Winlimit); + if (winlimit) + { + SetConVarInt(g_Cvar_Winlimit, winlimit + GetConVarInt(g_Cvar_ExtendRoundStep)); + } + } + + if (g_Cvar_Maxrounds != INVALID_HANDLE) + { + new maxrounds = GetConVarInt(g_Cvar_Maxrounds); + if (maxrounds) + { + SetConVarInt(g_Cvar_Maxrounds, maxrounds + GetConVarInt(g_Cvar_ExtendRoundStep)); + } + } + + if (g_Cvar_Fraglimit != INVALID_HANDLE) + { + new fraglimit = GetConVarInt(g_Cvar_Fraglimit); + if (fraglimit) + { + SetConVarInt(g_Cvar_Fraglimit, fraglimit + GetConVarInt(g_Cvar_ExtendFragStep)); + } + } + + if (g_NativeVotes) + { + NativeVotes_DisplayPassEx(menu, NativeVotesPass_Extend); + } + + CPrintToChatAll("[MCE] %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_RunoffCount = 0; + g_HasVoteStarted = false; + CreateNextVote(); + SetupTimeleftTimer(); + + } + else if (strcmp(map, VOTE_DONTCHANGE, false) == 0) + { + if (g_NativeVotes) + { + NativeVotes_DisplayPassEx(menu, NativeVotesPass_Extend); + } + + CPrintToChatAll("[MCE] %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_RunoffCount = 0; + g_HasVoteStarted = false; + CreateNextVote(); + SetupTimeleftTimer(); + } + else + { + if (g_ChangeTime == MapChange_MapEnd) + { + SetNextMap(map); + if (g_NativeVotes) + { + NativeVotes_DisplayPass(menu, map); + } + } + else if (g_ChangeTime == MapChange_Instant) + { + new Handle:data; + CreateDataTimer(4.0, Timer_ChangeMap, data); + WritePackString(data, map); + g_ChangeMapInProgress = false; + if (g_NativeVotes) + { + NativeVotes_DisplayPassEx(menu, NativeVotesPass_ChgLevel, map); + } + } + else // MapChange_RoundEnd + { + SetNextMap(map); + g_ChangeMapAtRoundEnd = true; + + if (g_NativeVotes) + { + NativeVotes_DisplayPass(menu, map); + } + } + + g_HasVoteStarted = false; + g_MapVoteCompleted = true; + + CPrintToChatAll("[MCE] %t", "Nextmap Voting Finished", map, 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 Handler_MapVoteFinished(Handle:menu, + num_votes, + num_clients, + const client_info[][2], + num_items, + const item_info[][2]) +{ + // Implement revote logic - Only run this` block if revotes are enabled and this isn't the last revote + if (GetConVarBool(g_Cvar_RunOff) && num_items > 1 && g_RunoffCount < GetConVarInt(g_Cvar_MaxRunOffs)) + { + g_RunoffCount++; + new highest_votes = item_info[0][VOTEINFO_ITEM_VOTES]; + new required_percent = GetConVarInt(g_Cvar_RunOffPercent); + new required_votes = RoundToCeil(float(num_votes) * float(required_percent) / 100); + + if (highest_votes == item_info[1][VOTEINFO_ITEM_VOTES]) + { + g_HasVoteStarted = false; + + //Revote is needed + new arraySize = ByteCountToCells(PLATFORM_MAX_PATH); + new Handle:mapList = CreateArray(arraySize); + + for (new i = 0; i < num_items; i++) + { + if (item_info[i][VOTEINFO_ITEM_VOTES] == highest_votes) + { + decl String:map[PLATFORM_MAX_PATH]; + + GetMapItem(menu, item_info[i][VOTEINFO_ITEM_INDEX], map, PLATFORM_MAX_PATH); + PushArrayString(mapList, map); + } + else + { + break; + } + } + + if (g_NativeVotes) + { + NativeVotes_DisplayFail(menu, NativeVotesFail_NotEnoughVotes); + } + + CPrintToChatAll("[MCE] %t", "Tie Vote", GetArraySize(mapList)); + SetupWarningTimer(WarningType_Revote, MapChange:g_ChangeTime, mapList); + return; + } + else if (highest_votes < required_votes) + { + g_HasVoteStarted = false; + + //Revote is needed + new arraySize = ByteCountToCells(PLATFORM_MAX_PATH); + new Handle:mapList = CreateArray(arraySize); + + decl String:map1[PLATFORM_MAX_PATH]; + GetMapItem(menu, item_info[0][VOTEINFO_ITEM_INDEX], map1, PLATFORM_MAX_PATH); + + PushArrayString(mapList, map1); + + // We allow more than two maps for a revote if they are tied + for (new i = 1; i < num_items; i++) + { + if (GetArraySize(mapList) < 2 || item_info[i][VOTEINFO_ITEM_VOTES] == item_info[i - 1][VOTEINFO_ITEM_VOTES]) + { + decl String:map[PLATFORM_MAX_PATH]; + GetMapItem(menu, item_info[i][VOTEINFO_ITEM_INDEX], map, PLATFORM_MAX_PATH); + PushArrayString(mapList, map); + } + else + { + break; + } + } + + if (g_NativeVotes) + { + NativeVotes_DisplayFail(menu, NativeVotesFail_NotEnoughVotes); + } + + CPrintToChatAll("[MCE] %t", "Revote Is Needed", required_percent); + SetupWarningTimer(WarningType_Revote, MapChange:g_ChangeTime, mapList); + return; + } + } + + // No revote needed, continue as normal. + Handler_VoteFinishedGeneric(menu, num_votes, num_clients, client_info, num_items, item_info); +} + +// This is shared by NativeVotes now, but NV doesn't support Display or DisplayItem +public Handler_MapVoteMenu(Handle:menu, MenuAction:action, param1, param2) +{ + switch (action) + { + case MenuAction_End: + { + g_VoteMenu = INVALID_HANDLE; + if (g_NativeVotes) + { + NativeVotes_Close(menu); + } + else + { + CloseHandle(menu); + } + } + + case MenuAction_Display: + { + // NativeVotes uses the standard TF2/CSGO vote screen + if (!g_NativeVotes) + { + decl String:buffer[255]; + Format(buffer, sizeof(buffer), "%T", "Vote Nextmap", param1); + new Handle:panel = Handle:param2; + SetPanelTitle(panel, buffer); + } + } + + case MenuAction_DisplayItem: + { + new String:map[PLATFORM_MAX_PATH]; + new String:buffer[255]; + new mark = GetConVarInt(g_Cvar_MarkCustomMaps); + + if (g_NativeVotes) + { + NativeVotes_GetItem(menu, param2, map, PLATFORM_MAX_PATH); + } + else + { + GetMenuItem(menu, param2, map, PLATFORM_MAX_PATH); + } + + if (StrEqual(map, VOTE_EXTEND, false)) + { + Format(buffer, sizeof(buffer), "%T", "Extend Map", param1); + } + else if (StrEqual(map, VOTE_DONTCHANGE, false)) + { + Format(buffer, sizeof(buffer), "%T", "Dont Change", param1); + } + // Mapchooser Extended + else if (StrEqual(map, LINE_ONE, false)) + { + Format(buffer, sizeof(buffer),"%T", "Line One", param1); + } + else if (StrEqual(map, LINE_TWO, false)) + { + Format(buffer, sizeof(buffer),"%T", "Line Two", param1); + } + // Note that the first part is to discard the spacer line + else if (!StrEqual(map, LINE_SPACER, false)) + { + if (mark == 1 && !InternalIsMapOfficial(map)) + { + Format(buffer, sizeof(buffer), "%T", "Custom Marked", param1, map); + } + else if (mark == 2 && !InternalIsMapOfficial(map)) + { + Format(buffer, sizeof(buffer), "%T", "Custom", param1, map); + } + } + + if (buffer[0] != '\0') + { + if (g_NativeVotes) + { + NativeVotes_RedrawVoteItem(buffer); + return _:Plugin_Continue; + } + else + { + return RedrawMenuItem(buffer); + } + } + // End Mapchooser Extended + } + + case MenuAction_VoteCancel: + { + // If we receive 0 votes, pick at random. + if (param1 == VoteCancel_NoVotes && GetConVarBool(g_Cvar_NoVoteMode)) + { + new count; + if (g_NativeVotes) + { + count = NativeVotes_GetItemCount(menu); + } + else + { + count = GetMenuItemCount(menu); + } + + decl item; + decl String:map[PLATFORM_MAX_PATH]; + + do + { + new startInt = 0; + if (!g_NativeVotes && g_BlockedSlots) + { + if (g_AddNoVote) + { + startInt = 2; + } + else + { + startInt = 3; + } + } + item = GetRandomInt(startInt, count - 1); + if (g_NativeVotes) + { + NativeVotes_GetItem(menu, item, map, PLATFORM_MAX_PATH); + } + else + { + GetMenuItem(menu, item, map, PLATFORM_MAX_PATH); + } + } + while (strcmp(map, VOTE_EXTEND, false) == 0); + + SetNextMap(map); + g_MapVoteCompleted = true; + + if (g_NativeVotes) + { + NativeVotes_DisplayPass(menu, map); + } + } + else if (g_NativeVotes) + { + new NativeVotesFailType:reason = NativeVotesFail_Generic; + if (param1 == VoteCancel_NoVotes) + { + reason = NativeVotesFail_NotEnoughVotes; + } + + NativeVotes_DisplayFail(menu, reason); + } + else + { + // We were actually cancelled. I guess we do nothing. + } + + g_HasVoteStarted = false; + } + } + + return 0; +} + +public Action:Timer_ChangeMap(Handle:hTimer, Handle:dp) +{ + g_ChangeMapInProgress = false; + + new String:map[PLATFORM_MAX_PATH]; + + if (dp == INVALID_HANDLE) + { + if (!GetNextMap(map, PLATFORM_MAX_PATH)) + { + //No passed map and no set nextmap. fail! + return Plugin_Stop; + } + } + else + { + ResetPack(dp); + ReadPackString(dp, map, PLATFORM_MAX_PATH); + } + + ForceChangeLevel(map, "Map Vote"); + + return Plugin_Stop; +} + +bool:RemoveStringFromArray(Handle:array, String:str[]) +{ + new index = FindStringInArray(array, str); + if (index != -1) + { + RemoveFromArray(array, index); + return true; + } + + return false; +} + +CreateNextVote() +{ + assert(g_NextMapList) + ClearArray(g_NextMapList); + + decl String:map[PLATFORM_MAX_PATH]; + new Handle:tempMaps = CloneArray(g_MapList); + + GetCurrentMap(map, PLATFORM_MAX_PATH); + RemoveStringFromArray(tempMaps, map); + + if (GetConVarInt(g_Cvar_ExcludeMaps) && GetArraySize(tempMaps) > GetConVarInt(g_Cvar_ExcludeMaps)) + { + for (new i = 0; i < GetArraySize(g_OldMapList); i++) + { + GetArrayString(g_OldMapList, i, map, PLATFORM_MAX_PATH); + RemoveStringFromArray(tempMaps, map); + } + } + + new voteSize = GetVoteSize(); + new limit = (voteSize < GetArraySize(tempMaps) ? voteSize : GetArraySize(tempMaps)); + + for (new i = 0; i < limit; i++) + { + new b = GetRandomInt(0, GetArraySize(tempMaps) - 1); + GetArrayString(tempMaps, b, map, PLATFORM_MAX_PATH); + PushArrayString(g_NextMapList, map); + RemoveFromArray(tempMaps, b); + } + + CloseHandle(tempMaps); +} + +bool:CanVoteStart() +{ + if (g_WaitingForVote || g_HasVoteStarted) + { + return false; + } + + return true; +} + +NominateResult:InternalNominateMap(String:map[], bool:force, owner) +{ + if (!IsMapValid(map)) + { + return Nominate_InvalidMap; + } + + /* Map already in the vote */ + if (FindStringInArray(g_NominateList, map) != -1) + { + return Nominate_AlreadyInVote; + } + + new index; + + /* Look to replace an existing nomination by this client - Nominations made with owner = 0 aren't replaced */ + if (owner && ((index = FindValueInArray(g_NominateOwners, owner)) != -1)) + { + new String:oldmap[PLATFORM_MAX_PATH]; + GetArrayString(g_NominateList, index, oldmap, PLATFORM_MAX_PATH); + Call_StartForward(g_NominationsResetForward); + Call_PushString(oldmap); + Call_PushCell(owner); + Call_Finish(); + + SetArrayString(g_NominateList, index, map); + return Nominate_Replaced; + } + + /* Too many nominated maps. */ + if (g_NominateCount >= GetVoteSize() && !force) + { + return Nominate_VoteFull; + } + + PushArrayString(g_NominateList, map); + PushArrayCell(g_NominateOwners, owner); + g_NominateCount++; + + while (GetArraySize(g_NominateList) > GetVoteSize()) + { + new String:oldmap[PLATFORM_MAX_PATH]; + GetArrayString(g_NominateList, 0, oldmap, PLATFORM_MAX_PATH); + Call_StartForward(g_NominationsResetForward); + Call_PushString(oldmap); + Call_PushCell(GetArrayCell(g_NominateOwners, 0)); + Call_Finish(); + + RemoveFromArray(g_NominateList, 0); + RemoveFromArray(g_NominateOwners, 0); + } + + return Nominate_Added; +} + +/* Add natives to allow nominate and initiate vote to be call */ + +/* native bool:NominateMap(const String:map[], bool:force, &NominateError:error); */ +public Native_NominateMap(Handle:plugin, numParams) +{ + new len; + GetNativeStringLength(1, len); + + if (len <= 0) + { + return false; + } + + new String:map[len+1]; + GetNativeString(1, map, len+1); + + return _:InternalNominateMap(map, GetNativeCell(2), GetNativeCell(3)); +} + +bool:InternalRemoveNominationByMap(String:map[]) +{ + for (new i = 0; i < GetArraySize(g_NominateList); i++) + { + new String:oldmap[PLATFORM_MAX_PATH]; + GetArrayString(g_NominateList, i, oldmap, PLATFORM_MAX_PATH); + + if(strcmp(map, oldmap, false) == 0) + { + Call_StartForward(g_NominationsResetForward); + Call_PushString(oldmap); + Call_PushCell(GetArrayCell(g_NominateOwners, i)); + Call_Finish(); + + RemoveFromArray(g_NominateList, i); + RemoveFromArray(g_NominateOwners, i); + g_NominateCount--; + + return true; + } + } + + return false; +} + +/* native bool:RemoveNominationByMap(const String:map[]); */ +public Native_RemoveNominationByMap(Handle:plugin, numParams) +{ + new len; + GetNativeStringLength(1, len); + + if (len <= 0) + { + return false; + } + + new String:map[len+1]; + GetNativeString(1, map, len+1); + + return _:InternalRemoveNominationByMap(map); +} + +bool:InternalRemoveNominationByOwner(owner) +{ + new index; + + if (owner && ((index = FindValueInArray(g_NominateOwners, owner)) != -1)) + { + new String:oldmap[PLATFORM_MAX_PATH]; + GetArrayString(g_NominateList, index, oldmap, PLATFORM_MAX_PATH); + + Call_StartForward(g_NominationsResetForward); + Call_PushString(oldmap); + Call_PushCell(owner); + Call_Finish(); + + RemoveFromArray(g_NominateList, index); + RemoveFromArray(g_NominateOwners, index); + g_NominateCount--; + + return true; + } + + return false; +} + +/* native bool:RemoveNominationByOwner(owner); */ +public Native_RemoveNominationByOwner(Handle:plugin, numParams) +{ + return _:InternalRemoveNominationByOwner(GetNativeCell(1)); +} + +/* native InitiateMapChooserVote(); */ +public Native_InitiateVote(Handle:plugin, numParams) +{ + new MapChange:when = MapChange:GetNativeCell(1); + new Handle:inputarray = Handle:GetNativeCell(2); + + LogAction(-1, -1, "Starting map vote because outside request"); + + SetupWarningTimer(WarningType_Vote, when, inputarray); + //InitiateVote(when, inputarray); +} + +public Native_CanVoteStart(Handle:plugin, numParams) +{ + return CanVoteStart(); +} + +public Native_CheckVoteDone(Handle:plugin, numParams) +{ + return g_MapVoteCompleted; +} + +public Native_EndOfMapVoteEnabled(Handle:plugin, numParams) +{ + return GetConVarBool(g_Cvar_EndOfMapVote); +} + +public Native_GetExcludeMapList(Handle:plugin, numParams) +{ + new Handle:array = Handle:GetNativeCell(1); + + if (array == INVALID_HANDLE) + { + return; + } + new size = GetArraySize(g_OldMapList); + decl String:map[PLATFORM_MAX_PATH]; + + for (new i=0; i -1); +} + +public Native_IsWarningTimer(Handle:plugin, numParams) +{ + return g_WarningInProgress; +} + +public Native_CanNominate(Handle:plugin, numParams) +{ + if (g_HasVoteStarted) + { + return _:CanNominate_No_VoteInProgress; + } + + if (g_MapVoteCompleted) + { + return _:CanNominate_No_VoteComplete; + } + + if (g_NominateCount >= GetVoteSize()) + { + return _:CanNominate_No_VoteFull; + } + + return _:CanNominate_Yes; +} + +public Native_ExcludeMap(Handle:plugin, numParams) +{ + new len; + GetNativeStringLength(1, len); + + if (len <= 0) + { + return false; + } + + new String:map[len+1]; + GetNativeString(1, map, len+1); + + PushArrayString(g_OldMapList, map); + + return true; +} + +stock AddMapItem(const String:map[]) +{ + if (g_NativeVotes) + { + NativeVotes_AddItem(g_VoteMenu, map, map); + } + else + { + AddMenuItem(g_VoteMenu, map, map); + } +} + +stock GetMapItem(Handle:menu, position, String:map[], mapLen) +{ + if (g_NativeVotes) + { + NativeVotes_GetItem(menu, position, map, mapLen); + } + else + { + GetMenuItem(menu, position, map, mapLen); + } +} + +stock AddExtendToMenu(Handle:menu, MapChange:when) +{ + /* Do we add any special items? */ + // Moved for Mapchooser Extended + + if ((when == MapChange_Instant || when == MapChange_RoundEnd) && GetConVarBool(g_Cvar_DontChange)) + { + if (g_NativeVotes) + { + // Built-in votes don't have "Don't Change", send Extend instead + NativeVotes_AddItem(menu, VOTE_DONTCHANGE, "Don't Change"); + } + else + { + AddMenuItem(menu, VOTE_DONTCHANGE, "Don't Change"); + } + } + else if (GetConVarBool(g_Cvar_Extend) && g_Extends < GetConVarInt(g_Cvar_Extend)) + { + if (g_NativeVotes) + { + NativeVotes_AddItem(menu, VOTE_EXTEND, "Extend Map"); + } + else + { + AddMenuItem(menu, VOTE_EXTEND, "Extend Map"); + } + } +} + +// Using this stock REQUIRES you to add the following to AskPluginLoad2: +// MarkNativeAsOptional("GetEngineVersion"); +stock EngineVersion:GetEngineVersionCompat() +{ + new EngineVersion:version; + if (GetFeatureStatus(FeatureType_Native, "GetEngineVersion") != FeatureStatus_Available) + { + new sdkVersion = GuessSDKVersion(); + switch (sdkVersion) + { + case SOURCE_SDK_ORIGINAL: + { + version = Engine_Original; + } + + case SOURCE_SDK_DARKMESSIAH: + { + version = Engine_DarkMessiah; + } + + case SOURCE_SDK_EPISODE1: + { + version = Engine_SourceSDK2006; + } + + case SOURCE_SDK_EPISODE2: + { + version = Engine_SourceSDK2007; + } + + case SOURCE_SDK_BLOODYGOODTIME: + { + version = Engine_BloodyGoodTime; + } + + case SOURCE_SDK_EYE: + { + version = Engine_EYE; + } + + case SOURCE_SDK_CSS: + { + version = Engine_CSS; + } + + case SOURCE_SDK_EPISODE2VALVE: + { + decl String:gameFolder[PLATFORM_MAX_PATH]; + GetGameFolderName(gameFolder, PLATFORM_MAX_PATH); + if (StrEqual(gameFolder, "dod", false)) + { + version = Engine_DODS; + } + else if (StrEqual(gameFolder, "hl2mp", false)) + { + version = Engine_HL2DM; + } + else + { + version = Engine_TF2; + } + } + + case SOURCE_SDK_LEFT4DEAD: + { + version = Engine_Left4Dead; + } + + case SOURCE_SDK_LEFT4DEAD2: + { + decl String:gameFolder[PLATFORM_MAX_PATH]; + GetGameFolderName(gameFolder, PLATFORM_MAX_PATH); + if (StrEqual(gameFolder, "nd", false)) + { + version = Engine_NuclearDawn; + } + else + { + version = Engine_Left4Dead2; + } + } + + case SOURCE_SDK_ALIENSWARM: + { + version = Engine_AlienSwarm; + } + + case SOURCE_SDK_CSGO: + { + version = Engine_CSGO; + } + + default: + { + version = Engine_Unknown; + } + } + } + else + { + version = GetEngineVersion(); + } + + return version; +} + +GetVoteSize() +{ + new voteSize = GetConVarInt(g_Cvar_IncludeMaps); + + // New in 1.9.5 to let us bump up the included maps count + if (g_NativeVotes) + { + new max = NativeVotes_GetMaxItems(); + + if (max < voteSize) + { + voteSize = max; + } + } + + return voteSize; +} \ No newline at end of file diff --git a/mapchooser_extended/scripting/nominations_extended.sp b/mapchooser_extended/scripting/nominations_extended.sp new file mode 100644 index 00000000..3b5bae48 --- /dev/null +++ b/mapchooser_extended/scripting/nominations_extended.sp @@ -0,0 +1,703 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * Nominations Extended + * Allows players to nominate maps for Mapchooser + * + * Nominations Extended (C)2012-2013 Powerlord (Ross Bemrose) + * SourceMod (C)2004-2007 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 . + * + * 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 . + * + * Version: $Id$ + */ + +#include +#include +#include "include/mapchooser_extended" +#include +#pragma semicolon 1 + +#define MCE_VERSION "1.10.0" + +public Plugin:myinfo = +{ + name = "Map Nominations Extended", + author = "Powerlord and AlliedModders LLC", + description = "Provides Map Nominations", + version = MCE_VERSION, + url = "https://forums.alliedmods.net/showthread.php?t=156974" +}; + +new Handle:g_Cvar_ExcludeOld = INVALID_HANDLE; +new Handle:g_Cvar_ExcludeCurrent = INVALID_HANDLE; + +new Handle:g_MapList = INVALID_HANDLE; +new Handle:g_AdminMapList = INVALID_HANDLE; +new Handle:g_MapMenu = INVALID_HANDLE; +new Handle:g_AdminMapMenu = INVALID_HANDLE; +new g_mapFileSerial = -1; +new g_AdminMapFileSerial = -1; + +#define MAPSTATUS_ENABLED (1<<0) +#define MAPSTATUS_DISABLED (1<<1) +#define MAPSTATUS_EXCLUDE_CURRENT (1<<2) +#define MAPSTATUS_EXCLUDE_PREVIOUS (1<<3) +#define MAPSTATUS_EXCLUDE_NOMINATED (1<<4) + +new Handle:g_mapTrie; + +// Nominations Extended Convars +new Handle:g_Cvar_MarkCustomMaps = INVALID_HANDLE; + +new Handle:g_Cvar_NominateDelay = INVALID_HANDLE; +new Handle:g_Cvar_InitialDelay = INVALID_HANDLE; +new g_Player_NominationDelay[MAXPLAYERS+1]; +new g_NominationDelay; + +public OnPluginStart() +{ + LoadTranslations("common.phrases"); + LoadTranslations("nominations.phrases"); + LoadTranslations("basetriggers.phrases"); // for Next Map phrase + LoadTranslations("mapchooser_extended.phrases"); + + new arraySize = ByteCountToCells(PLATFORM_MAX_PATH); + g_MapList = CreateArray(arraySize); + g_AdminMapList = CreateArray(arraySize); + + g_Cvar_ExcludeOld = CreateConVar("sm_nominate_excludeold", "1", "Specifies if the current map should be excluded from the Nominations list", 0, true, 0.00, true, 1.0); + g_Cvar_ExcludeCurrent = CreateConVar("sm_nominate_excludecurrent", "1", "Specifies if the MapChooser excluded maps should also be excluded from Nominations", 0, true, 0.00, true, 1.0); + g_Cvar_InitialDelay = CreateConVar("sm_nominate_initialdelay", "60.0", "Time in seconds before first Nomination can be made", 0, true, 0.00); + g_Cvar_NominateDelay = CreateConVar("sm_nominate_delay", "3.0", "Delay between nominations", 0, true, 0.00, true, 60.00); + + RegConsoleCmd("say", Command_Say); + RegConsoleCmd("say_team", Command_Say); + + RegConsoleCmd("sm_nominate", Command_Nominate); + RegConsoleCmd("sm_nomlist", Command_NominateList); + + RegAdminCmd("sm_nominate_addmap", Command_Addmap, ADMFLAG_CHANGEMAP, "sm_nominate_addmap - Forces a map to be on the next mapvote."); + + // BotoX + RegAdminCmd("sm_nominate_exclude", Command_AddExclude, ADMFLAG_CHANGEMAP, "sm_nominate_exclude - Forces a map to be inserted into the recently played maps. Effectively blocking the map from being nominated."); + + // Nominations Extended cvars + CreateConVar("ne_version", MCE_VERSION, "Nominations Extended Version", FCVAR_SPONLY|FCVAR_NOTIFY|FCVAR_DONTRECORD); + + g_mapTrie = CreateTrie(); +} + +public OnAllPluginsLoaded() +{ + // This is an MCE cvar... this plugin requires MCE to be loaded. Granted, this plugin SHOULD have an MCE dependency. + g_Cvar_MarkCustomMaps = FindConVar("mce_markcustommaps"); +} + +public OnConfigsExecuted() +{ + if (ReadMapList(g_MapList, + g_mapFileSerial, + "nominations", + MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER) + == INVALID_HANDLE) + { + if (g_mapFileSerial == -1) + { + SetFailState("Unable to create a valid map list."); + } + } + if (ReadMapList(g_AdminMapList, + g_AdminMapFileSerial, + "sm_nominate_addmap menu", + MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_NO_DEFAULT|MAPLIST_FLAG_MAPSFOLDER) + == INVALID_HANDLE) + { + if (g_AdminMapFileSerial == -1) + { + SetFailState("Unable to create a valid admin map list."); + } + } + else + { + for (new i = 0; i < GetArraySize(g_MapList); i++) + { + decl String:map[PLATFORM_MAX_PATH]; + GetArrayString(g_MapList, i, map, sizeof(map)); + + new Index = FindStringInArray(g_AdminMapList, map); + if (Index != -1) + RemoveFromArray(g_AdminMapList, Index); + } + } + + g_NominationDelay = GetTime() + GetConVarInt(g_Cvar_InitialDelay); + + BuildMapMenu(); + BuildAdminMapMenu(); +} + +public OnNominationRemoved(const String:map[], owner) +{ + new status; + + /* Is the map in our list? */ + if (!GetTrieValue(g_mapTrie, map, status)) + { + return; + } + + /* Was the map disabled due to being nominated */ + if ((status & MAPSTATUS_EXCLUDE_NOMINATED) != MAPSTATUS_EXCLUDE_NOMINATED) + { + return; + } + + SetTrieValue(g_mapTrie, map, MAPSTATUS_ENABLED); +} + +public Action:Command_Addmap(client, args) +{ + if (args == 0) + { + AttemptAdminNominate(client); + return Plugin_Handled; + } + + if (args != 1) + { + CReplyToCommand(client, "[NE] Usage: sm_nominate_addmap "); + return Plugin_Handled; + } + + decl String:mapname[PLATFORM_MAX_PATH]; + GetCmdArg(1, mapname, sizeof(mapname)); + + // new status; + if (/*!GetTrieValue(g_mapTrie, mapname, status)*/!IsMapValid(mapname)) + { + CReplyToCommand(client, "%t", "Map was not found", mapname); + return Plugin_Handled; + } + + new NominateResult:result = NominateMap(mapname, true, 0); + + if (result > Nominate_Replaced) + { + /* We assume already in vote is the casue because the maplist does a Map Validity check and we forced, so it can't be full */ + CReplyToCommand(client, "%t", "Map Already In Vote", mapname); + + return Plugin_Handled; + } + + SetTrieValue(g_mapTrie, mapname, MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_NOMINATED); + + CReplyToCommand(client, "%t", "Map Inserted", mapname); + LogAction(client, -1, "\"%L\" inserted map \"%s\".", client, mapname); + + PrintToChatAll("[NE] %N has inserted %s into nominations", client, mapname); + + return Plugin_Handled; +} + +public Action:Command_AddExclude(client, args) +{ + if (args < 1) + { + CReplyToCommand(client, "[NE] Usage: sm_nominate_exclude "); + return Plugin_Handled; + } + + decl String:mapname[PLATFORM_MAX_PATH]; + GetCmdArg(1, mapname, sizeof(mapname)); + + new status; + if(!GetTrieValue(g_mapTrie, mapname, status)) + { + ReplyToCommand(client, "[NE] %t", "Map was not found", mapname); + return Plugin_Handled; + } + + ShowActivity(client, "Excluded map \"%s\" from nomination", mapname); + LogAction(client, -1, "\"%L\" excluded map \"%s\" from nomination", client, mapname); + + SetTrieValue(g_mapTrie, mapname, MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_PREVIOUS); + + // native call to mapchooser_extended + ExcludeMap(mapname); + + return Plugin_Handled; +} + +public Action:Command_Say(client, args) +{ + if (!client) + { + return Plugin_Continue; + } + + decl String:text[192]; + if (!GetCmdArgString(text, sizeof(text))) + { + return Plugin_Continue; + } + + new startidx = 0; + if(text[strlen(text)-1] == '"') + { + text[strlen(text)-1] = '\0'; + startidx = 1; + } + + new ReplySource:old = SetCmdReplySource(SM_REPLY_TO_CHAT); + + if (strcmp(text[startidx], "nominate", false) == 0) + { + if (IsNominateAllowed(client)) + { + if (g_NominationDelay > GetTime()) + ReplyToCommand(client, "[NE] Nominations will be unlocked in %d seconds", g_NominationDelay - GetTime()); + else + AttemptNominate(client); + } + } + + SetCmdReplySource(old); + + return Plugin_Continue; +} + +public Action:Command_Nominate(client, args) +{ + if (!client || !IsNominateAllowed(client)) + { + return Plugin_Handled; + } + + if (g_NominationDelay > GetTime()) + { + ReplyToCommand(client, "[NE] Nominations will be unlocked in %d seconds", g_NominationDelay - GetTime()); + return Plugin_Handled; + } + + if (args == 0) + { + AttemptNominate(client); + return Plugin_Handled; + } + + if (g_Player_NominationDelay[client] > GetTime()) + { + ReplyToCommand(client, "[NE] Please wait %d seconds before you can nominate again", g_Player_NominationDelay[client] - GetTime()); + return Plugin_Handled; + } + + decl String:mapname[PLATFORM_MAX_PATH]; + GetCmdArg(1, mapname, sizeof(mapname)); + + new status; + if (!GetTrieValue(g_mapTrie, mapname, status)) + { + CReplyToCommand(client, "%t", "Map was not found", mapname); + return Plugin_Handled; + } + + if ((status & MAPSTATUS_DISABLED) == MAPSTATUS_DISABLED) + { + if ((status & MAPSTATUS_EXCLUDE_CURRENT) == MAPSTATUS_EXCLUDE_CURRENT) + { + CReplyToCommand(client, "[NE] %t", "Can't Nominate Current Map"); + } + + if ((status & MAPSTATUS_EXCLUDE_PREVIOUS) == MAPSTATUS_EXCLUDE_PREVIOUS) + { + CReplyToCommand(client, "[NE] %t", "Map in Exclude List"); + } + + if ((status & MAPSTATUS_EXCLUDE_NOMINATED) == MAPSTATUS_EXCLUDE_NOMINATED) + { + CReplyToCommand(client, "[NE] %t", "Map Already Nominated"); + } + + return Plugin_Handled; + } + + new NominateResult:result = NominateMap(mapname, false, client); + + if (result > Nominate_Replaced) + { + if (result == Nominate_AlreadyInVote) + { + CReplyToCommand(client, "[NE] %t", "Map Already In Vote", mapname); + } + else if (result == Nominate_VoteFull) + { + CReplyToCommand(client, "[ME] %t", "Max Nominations"); + } + + return Plugin_Handled; + } + + /* Map was nominated! - Disable the menu item and update the trie */ + + SetTrieValue(g_mapTrie, mapname, MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_NOMINATED); + + decl String:name[MAX_NAME_LENGTH]; + GetClientName(client, name, sizeof(name)); + + if(result == Nominate_Added) + PrintToChatAll("[NE] %t", "Map Nominated", name, mapname); + else if(result == Nominate_Replaced) + PrintToChatAll("[NE] %t", "Map Nomination Changed", name, mapname); + + LogMessage("%s nominated %s", name, mapname); + + g_Player_NominationDelay[client] = GetTime() + GetConVarInt(g_Cvar_NominateDelay); + + return Plugin_Continue; +} + +public Action:Command_NominateList(client, args) +{ + new arraySize = ByteCountToCells(PLATFORM_MAX_PATH); + new Handle:MapList = CreateArray(arraySize); + GetNominatedMapList(MapList); + if (!GetArraySize(MapList)) + { + CReplyToCommand(client, "[NE] No maps have been nominated."); + return Plugin_Handled; + } + + new Handle:NominateListMenu = CreateMenu(Handler_NominateListMenu, MENU_ACTIONS_DEFAULT|MenuAction_DisplayItem); + + decl String:map[PLATFORM_MAX_PATH]; + for (new i = 0; i < GetArraySize(MapList); i++) + { + GetArrayString(MapList, i, map, sizeof(map)); + AddMenuItem(NominateListMenu, map, map); + } + + SetMenuTitle(NominateListMenu, "Nominated Maps", client); + DisplayMenu(NominateListMenu, client, MENU_TIME_FOREVER); + + return Plugin_Handled; +} + +public Handler_NominateListMenu(Handle:menu, MenuAction:action, param1, param2) +{ + return 0; +} + +AttemptNominate(client) +{ + SetMenuTitle(g_MapMenu, "%T", "Nominate Title", client); + DisplayMenu(g_MapMenu, client, MENU_TIME_FOREVER); + + return; +} + +AttemptAdminNominate(client) +{ + SetMenuTitle(g_AdminMapMenu, "%T", "Nominate Title", client); + DisplayMenu(g_AdminMapMenu, client, MENU_TIME_FOREVER); + + return; +} + +BuildMapMenu() +{ + if (g_MapMenu != INVALID_HANDLE) + { + CloseHandle(g_MapMenu); + g_MapMenu = INVALID_HANDLE; + } + + ClearTrie(g_mapTrie); + + g_MapMenu = CreateMenu(Handler_MapSelectMenu, MENU_ACTIONS_DEFAULT|MenuAction_DrawItem|MenuAction_DisplayItem); + + decl String:map[PLATFORM_MAX_PATH]; + + new Handle:excludeMaps = INVALID_HANDLE; + decl String:currentMap[32]; + + if (GetConVarBool(g_Cvar_ExcludeOld)) + { + excludeMaps = CreateArray(ByteCountToCells(PLATFORM_MAX_PATH)); + GetExcludeMapList(excludeMaps); + } + + if (GetConVarBool(g_Cvar_ExcludeCurrent)) + { + GetCurrentMap(currentMap, sizeof(currentMap)); + } + + + for (new i = 0; i < GetArraySize(g_MapList); i++) + { + new status = MAPSTATUS_ENABLED; + + GetArrayString(g_MapList, i, map, sizeof(map)); + + if (GetConVarBool(g_Cvar_ExcludeCurrent)) + { + if (StrEqual(map, currentMap)) + { + status = MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_CURRENT; + } + } + + /* Dont bother with this check if the current map check passed */ + if (GetConVarBool(g_Cvar_ExcludeOld) && status == MAPSTATUS_ENABLED) + { + if (FindStringInArray(excludeMaps, map) != -1) + { + status = MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_PREVIOUS; + } + } + + AddMenuItem(g_MapMenu, map, map); + SetTrieValue(g_mapTrie, map, status); + } + + SetMenuExitButton(g_MapMenu, true); + + if (excludeMaps != INVALID_HANDLE) + { + CloseHandle(excludeMaps); + } +} + +BuildAdminMapMenu() +{ + if (g_AdminMapMenu != INVALID_HANDLE) + { + CloseHandle(g_AdminMapMenu); + g_AdminMapMenu = INVALID_HANDLE; + } + + g_AdminMapMenu = CreateMenu(Handler_AdminMapSelectMenu, MENU_ACTIONS_DEFAULT|MenuAction_DrawItem|MenuAction_DisplayItem); + + decl String:map[PLATFORM_MAX_PATH]; + + for (new i = 0; i < GetArraySize(g_AdminMapList); i++) + { + GetArrayString(g_AdminMapList, i, map, sizeof(map)); + + AddMenuItem(g_AdminMapMenu, map, map); + } + + SetMenuExitButton(g_AdminMapMenu, true); +} + +public Handler_MapSelectMenu(Handle:menu, MenuAction:action, param1, param2) +{ + switch (action) + { + case MenuAction_Select: + { + decl String:map[PLATFORM_MAX_PATH], String:name[MAX_NAME_LENGTH]; + GetMenuItem(menu, param2, map, sizeof(map)); + + GetClientName(param1, name, MAX_NAME_LENGTH); + + new NominateResult:result = NominateMap(map, false, param1); + + /* Don't need to check for InvalidMap because the menu did that already */ + if (result == Nominate_AlreadyInVote) + { + PrintToChat(param1, "[NE] %t", "Map Already Nominated"); + return 0; + } + else if (result == Nominate_VoteFull) + { + PrintToChat(param1, "[NE] %t", "Max Nominations"); + return 0; + } + + SetTrieValue(g_mapTrie, map, MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_NOMINATED); + + if(result == Nominate_Added) + PrintToChatAll("[NE] %t", "Map Nominated", name, map); + else if(result == Nominate_Replaced) + PrintToChatAll("[NE] %t", "Map Nomination Changed", name, map); + + LogMessage("%s nominated %s", name, map); + } + + case MenuAction_DrawItem: + { + decl String:map[PLATFORM_MAX_PATH]; + GetMenuItem(menu, param2, map, sizeof(map)); + + new status; + + if (!GetTrieValue(g_mapTrie, map, status)) + { + LogError("Menu selection of item not in trie. Major logic problem somewhere."); + return ITEMDRAW_DEFAULT; + } + + if ((status & MAPSTATUS_DISABLED) == MAPSTATUS_DISABLED) + { + return ITEMDRAW_DISABLED; + } + + return ITEMDRAW_DEFAULT; + + } + + case MenuAction_DisplayItem: + { + decl String:map[PLATFORM_MAX_PATH]; + GetMenuItem(menu, param2, map, sizeof(map)); + + new mark = GetConVarInt(g_Cvar_MarkCustomMaps); + new bool:official; + + new status; + + if (!GetTrieValue(g_mapTrie, map, status)) + { + LogError("Menu selection of item not in trie. Major logic problem somewhere."); + return 0; + } + + decl String:buffer[100]; + decl String:display[150]; + + if (mark) + { + official = IsMapOfficial(map); + } + + if (mark && !official) + { + switch (mark) + { + case 1: + { + Format(buffer, sizeof(buffer), "%T", "Custom Marked", param1, map); + } + + case 2: + { + Format(buffer, sizeof(buffer), "%T", "Custom", param1, map); + } + } + } + else + { + strcopy(buffer, sizeof(buffer), map); + } + + if ((status & MAPSTATUS_DISABLED) == MAPSTATUS_DISABLED) + { + if ((status & MAPSTATUS_EXCLUDE_CURRENT) == MAPSTATUS_EXCLUDE_CURRENT) + { + Format(display, sizeof(display), "%s (%T)", buffer, "Current Map", param1); + return RedrawMenuItem(display); + } + + if ((status & MAPSTATUS_EXCLUDE_PREVIOUS) == MAPSTATUS_EXCLUDE_PREVIOUS) + { + Format(display, sizeof(display), "%s (%T)", buffer, "Recently Played", param1); + return RedrawMenuItem(display); + } + + if ((status & MAPSTATUS_EXCLUDE_NOMINATED) == MAPSTATUS_EXCLUDE_NOMINATED) + { + Format(display, sizeof(display), "%s (%T)", buffer, "Nominated", param1); + return RedrawMenuItem(display); + } + } + + if (mark && !official) + return RedrawMenuItem(buffer); + + return 0; + } + } + + return 0; +} + +stock bool:IsNominateAllowed(client) +{ + new CanNominateResult:result = CanNominate(); + + switch(result) + { + case CanNominate_No_VoteInProgress: + { + CReplyToCommand(client, "[ME] %t", "Nextmap Voting Started"); + return false; + } + + case CanNominate_No_VoteComplete: + { + new String:map[PLATFORM_MAX_PATH]; + GetNextMap(map, sizeof(map)); + CReplyToCommand(client, "[NE] %t", "Next Map", map); + return false; + } +/* + case CanNominate_No_VoteFull: + { + CReplyToCommand(client, "[ME] %t", "Max Nominations"); + return false; + } +*/ + } + + return true; +} + +public Handler_AdminMapSelectMenu(Handle:menu, MenuAction:action, param1, param2) +{ + switch (action) + { + case MenuAction_Select: + { + decl String:map[PLATFORM_MAX_PATH], String:name[MAX_NAME_LENGTH]; + GetMenuItem(menu, param2, map, sizeof(map)); + + new NominateResult:result = NominateMap(map, false, 0); + + if (result == Nominate_AlreadyInVote) + { + PrintToChat(param1, "[NE] %t", "Map Already In Vote", map); + return 0; + } + + SetTrieValue(g_mapTrie, map, MAPSTATUS_DISABLED|MAPSTATUS_EXCLUDE_NOMINATED); + + PrintToChat(param1, "[NE] %t", "Map Inserted", map); + LogAction(param1, -1, "\"%L\" inserted map \"%s\".", param1, map); + + PrintToChatAll("[NE] %N has inserted %s into nominations", name, map); + } + } + + return 0; +} diff --git a/mapchooser_extended/scripting/rockthevote_extended.sp b/mapchooser_extended/scripting/rockthevote_extended.sp new file mode 100644 index 00000000..4e3d494a --- /dev/null +++ b/mapchooser_extended/scripting/rockthevote_extended.sp @@ -0,0 +1,378 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * Rock The Vote Extended + * Creates a map vote when the required number of players have requested one. + * + * Rock The Vote Extended (C)2012-2013 Powerlord (Ross Bemrose) + * SourceMod (C)2004-2007 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 . + * + * 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 . + * + * Version: $Id$ + */ + +#include +#include +#include +#include +#include "include/mapchooser_extended" +#include +#include + +#pragma semicolon 1 + +#define MCE_VERSION "1.10.0" + +public Plugin:myinfo = +{ + name = "Rock The Vote Extended", + author = "Powerlord and AlliedModders LLC", + description = "Provides RTV Map Voting", + version = MCE_VERSION, + url = "https://forums.alliedmods.net/showthread.php?t=156974" +}; + +new Handle:g_Cvar_Needed = INVALID_HANDLE; +new Handle:g_Cvar_MinPlayers = INVALID_HANDLE; +new Handle:g_Cvar_InitialDelay = INVALID_HANDLE; +new Handle:g_Cvar_Interval = INVALID_HANDLE; +new Handle:g_Cvar_ChangeTime = INVALID_HANDLE; +new Handle:g_Cvar_RTVPostVoteAction = INVALID_HANDLE; + +new bool:g_CanRTV = false; // True if RTV loaded maps and is active. +new bool:g_RTVAllowed = false; // True if RTV is available to players. Used to delay rtv votes. +new g_Voters = 0; // Total voters connected. Doesn't include fake clients. +new g_Votes = 0; // Total number of "say rtv" votes +new g_VotesNeeded = 0; // Necessary votes before map vote begins. (voters * percent_needed) +new bool:g_Voted[MAXPLAYERS+1] = {false, ...}; + +new bool:g_InChange = false; + +public OnPluginStart() +{ + LoadTranslations("common.phrases"); + LoadTranslations("rockthevote.phrases"); + LoadTranslations("basevotes.phrases"); + + g_Cvar_Needed = CreateConVar("sm_rtv_needed", "0.60", "Percentage of players needed to rockthevote (Def 60%)", 0, true, 0.05, true, 1.0); + g_Cvar_MinPlayers = CreateConVar("sm_rtv_minplayers", "0", "Number of players required before RTV will be enabled.", 0, true, 0.0, true, float(MAXPLAYERS)); + g_Cvar_InitialDelay = CreateConVar("sm_rtv_initialdelay", "30.0", "Time (in seconds) before first RTV can be held", 0, true, 0.00); + g_Cvar_Interval = CreateConVar("sm_rtv_interval", "240.0", "Time (in seconds) after a failed RTV before another can be held", 0, true, 0.00); + g_Cvar_ChangeTime = CreateConVar("sm_rtv_changetime", "0", "When to change the map after a succesful RTV: 0 - Instant, 1 - RoundEnd, 2 - MapEnd", _, true, 0.0, true, 2.0); + g_Cvar_RTVPostVoteAction = CreateConVar("sm_rtv_postvoteaction", "0", "What to do with RTV's after a mapvote has completed. 0 - Allow, success = instant change, 1 - Deny", _, true, 0.0, true, 1.0); + + HookEvent("player_team", OnPlayerChangedTeam); + + RegConsoleCmd("say", Command_Say); + RegConsoleCmd("say_team", Command_Say); + + RegConsoleCmd("sm_rtv", Command_RTV); + + RegAdminCmd("sm_forcertv", Command_ForceRTV, ADMFLAG_CHANGEMAP, "Force an RTV vote"); + RegAdminCmd("mce_forcertv", Command_ForceRTV, ADMFLAG_CHANGEMAP, "Force an RTV vote"); + + // Rock The Vote Extended cvars + CreateConVar("rtve_version", MCE_VERSION, "Rock The Vote Extended Version", FCVAR_SPONLY|FCVAR_NOTIFY|FCVAR_DONTRECORD); + + AutoExecConfig(true, "rtv"); +} + +public OnMapStart() +{ + g_Voters = 0; + g_Votes = 0; + g_VotesNeeded = 0; + g_InChange = false; + + /* Handle late load */ + for (new i=1; i<=MaxClients; i++) + { + if (IsClientConnected(i)) + { + OnClientConnected(i); + } + } +} + +public OnMapEnd() +{ + g_CanRTV = false; + g_RTVAllowed = false; +} + +public OnConfigsExecuted() +{ + g_CanRTV = true; + g_RTVAllowed = false; + CreateTimer(GetConVarFloat(g_Cvar_InitialDelay), Timer_DelayRTV, _, TIMER_FLAG_NO_MAPCHANGE); +} + +public OnClientConnected(client) +{ + if(IsFakeClient(client)) + return; + + g_Voted[client] = false; + + g_Voters = GetTeamClientCount(2) + GetTeamClientCount(3); + g_VotesNeeded = RoundToFloor(float(g_Voters) * GetConVarFloat(g_Cvar_Needed)); + + return; +} + +public OnClientDisconnect(client) +{ + if(IsFakeClient(client)) + return; + + if(g_Voted[client]) + { + g_Votes--; + } + + g_Voters = GetTeamClientCount(2) + GetTeamClientCount(3); + + g_VotesNeeded = RoundToFloor(float(g_Voters) * GetConVarFloat(g_Cvar_Needed)); + + if (!g_CanRTV) + { + return; + } + + if (g_Votes && + g_Voters && + g_Votes >= g_VotesNeeded && + g_RTVAllowed ) + { + if (GetConVarInt(g_Cvar_RTVPostVoteAction) == 1 && HasEndOfMapVoteFinished()) + { + return; + } + + StartRTV(); + } +} + +public OnPlayerChangedTeam(Handle:event, const String:name[], bool:dontBroadcast) +{ + new Client = GetClientOfUserId(GetEventInt(event, "userid")); + + if(IsFakeClient(Client)) + return; + + if(Client == 0) + { + return; + } + + if (IsClientInGame(Client) && IsClientConnected(Client)) + { + if (GetClientTeam(Client) == 1) + { + g_Voters = GetTeamClientCount(2) + GetTeamClientCount(3); + g_VotesNeeded = RoundToFloor(float(g_Voters) * GetConVarFloat(g_Cvar_Needed)); + + if (g_Votes && + g_Voters && + g_Votes >= g_VotesNeeded && + g_RTVAllowed ) + { + if (GetConVarInt(g_Cvar_RTVPostVoteAction) == 1 && HasEndOfMapVoteFinished()) + { + return; + } + + StartRTV(); + } + } + } +} + +public Action:Command_RTV(client, args) +{ + if (!g_CanRTV || !client) + { + return Plugin_Handled; + } + + AttemptRTV(client); + + return Plugin_Handled; +} + +public Action:Command_Say(client, args) +{ + if (!g_CanRTV || !client) + { + return Plugin_Continue; + } + + decl String:text[192]; + if (!GetCmdArgString(text, sizeof(text))) + { + return Plugin_Continue; + } + + new startidx = 0; + if(text[strlen(text)-1] == '"') + { + text[strlen(text)-1] = '\0'; + startidx = 1; + } + + new ReplySource:old = SetCmdReplySource(SM_REPLY_TO_CHAT); + + if (strcmp(text[startidx], "rtv", false) == 0 || strcmp(text[startidx], "rockthevote", false) == 0) + { + AttemptRTV(client); + } + + SetCmdReplySource(old); + + return Plugin_Continue; +} + +AttemptRTV(client) +{ + if (!g_RTVAllowed || (GetConVarInt(g_Cvar_RTVPostVoteAction) == 1 && HasEndOfMapVoteFinished())) + { + CReplyToCommand(client, "[SM] %t", "RTV Not Allowed"); + return; + } + + if (!CanMapChooserStartVote()) + { + CReplyToCommand(client, "[SM] %t", "RTV Started"); + return; + } + + if (GetClientCount(true) < GetConVarInt(g_Cvar_MinPlayers)) + { + CReplyToCommand(client, "[SM] %t", "Minimal Players Not Met"); + return; + } + + if (g_Voted[client]) + { + CReplyToCommand(client, "[SM] %t", "Already Voted", g_Votes, g_VotesNeeded); + return; + } + + new String:name[MAX_NAME_LENGTH]; + GetClientName(client, name, sizeof(name)); + + g_Votes++; + g_Voted[client] = true; + + CPrintToChatAll("[SM] %t", "RTV Requested", name, g_Votes, g_VotesNeeded); + + if (g_Votes >= g_VotesNeeded) + { + StartRTV(); + } +} + +public Action:Timer_DelayRTV(Handle:timer) +{ + g_RTVAllowed = true; +} + +StartRTV() +{ + if (g_InChange) + { + return; + } + + if (EndOfMapVoteEnabled() && HasEndOfMapVoteFinished()) + { + /* Change right now then */ + new String:map[PLATFORM_MAX_PATH]; + if (GetNextMap(map, sizeof(map))) + { + CPrintToChatAll("[SM] %t", "Changing Maps", map); + CreateTimer(5.0, Timer_ChangeMap, _, TIMER_FLAG_NO_MAPCHANGE); + g_InChange = true; + + ResetRTV(); + + g_RTVAllowed = false; + } + return; + } + + if (CanMapChooserStartVote()) + { + new MapChange:when = MapChange:GetConVarInt(g_Cvar_ChangeTime); + InitiateMapChooserVote(when); + + ResetRTV(); + + g_RTVAllowed = false; + CreateTimer(GetConVarFloat(g_Cvar_Interval), Timer_DelayRTV, _, TIMER_FLAG_NO_MAPCHANGE); + } +} + +ResetRTV() +{ + g_Votes = 0; + + for (new i=1; i<=MAXPLAYERS; i++) + { + g_Voted[i] = false; + } +} + +public Action:Timer_ChangeMap(Handle:hTimer) +{ + g_InChange = false; + + LogMessage("RTV changing map manually"); + + new String:map[PLATFORM_MAX_PATH]; + if (GetNextMap(map, sizeof(map))) + { + ForceChangeLevel(map, "RTV after mapvote"); + } + + return Plugin_Stop; +} + +// Rock The Vote Extended functions + +public Action:Command_ForceRTV(client, args) +{ + if (!g_CanRTV || !client) + { + return Plugin_Handled; + } + + ShowActivity2(client, "[RTVE] ", "%t", "Initiated Vote Map"); + + StartRTV(); + + return Plugin_Handled; +} + + diff --git a/mapchooser_extended/translations/chi/incomplete.txt b/mapchooser_extended/translations/chi/incomplete.txt new file mode 100644 index 00000000..9ec75619 --- /dev/null +++ b/mapchooser_extended/translations/chi/incomplete.txt @@ -0,0 +1,2 @@ +Missing "Runoff Vote Nextmap", "Number Of Votes", "Custom", "Revote Is Needed", "Revote Warning", "Vote Warning", +"Line One", "Line Two", "Cannot Start Vote", and "Tie Vote" \ No newline at end of file diff --git a/mapchooser_extended/translations/chi/mapchooser_extended.phrases.txt b/mapchooser_extended/translations/chi/mapchooser_extended.phrases.txt new file mode 100644 index 00000000..081e5aa9 --- /dev/null +++ b/mapchooser_extended/translations/chi/mapchooser_extended.phrases.txt @@ -0,0 +1,43 @@ +"Phrases" +{ + "Vote Nextmap" + { + "chi" "下一幅地图投票了!" + } + + "Nextmap Voting Started" + { + "chi" "下一幅地图投选已开始." + } + + "Nextmap Voting Finished" + { + "chi" " 地图投票已结束. 下一幅地图将为 {1}. ( 得票{2}%% , {3}票)" + } + + "Current Map Extended" + { + "chi" "当前地图已被延长." + } + + "Extend Map" + { + "chi" "延长当前地图" + } + + "Dont Change" + { + "chi" "请勿更换" + } + + "Current Map Stays" + { + "chi" "当前地图已被延长! 投票显示! (得票 {1}%% 共 {2} 票)" + } + + "Changed Next Map" + { + "chi" "更换下一幅地图为 \"{1}\"." + } + +} diff --git a/mapchooser_extended/translations/cze/incomplete.txt b/mapchooser_extended/translations/cze/incomplete.txt new file mode 100644 index 00000000..9ec75619 --- /dev/null +++ b/mapchooser_extended/translations/cze/incomplete.txt @@ -0,0 +1,2 @@ +Missing "Runoff Vote Nextmap", "Number Of Votes", "Custom", "Revote Is Needed", "Revote Warning", "Vote Warning", +"Line One", "Line Two", "Cannot Start Vote", and "Tie Vote" \ No newline at end of file diff --git a/mapchooser_extended/translations/cze/mapchooser_extended.phrases.txt b/mapchooser_extended/translations/cze/mapchooser_extended.phrases.txt new file mode 100644 index 00000000..225bce1c --- /dev/null +++ b/mapchooser_extended/translations/cze/mapchooser_extended.phrases.txt @@ -0,0 +1,43 @@ +"Phrases" +{ + "Vote Nextmap" + { + "cze" "Hlasujte o příští mapě!" + } + + "Nextmap Voting Started" + { + "cze" "Hlasování o příští mapě začalo." + } + + "Nextmap Voting Finished" + { + "cze" "Hlasování o mapě skončilo. Příští mapou bude {1}. (Obdržela {2}%% z {3} hlasů)" + } + + "Current Map Extended" + { + "cze" "Současná mapa byla prodloužena. (Obdržela {1}%% z {2} hlasů)" + } + + "Extend Map" + { + "cze" "Prodloužit současnou mapu" + } + + "Dont Change" + { + "cze" "Neměnit" + } + + "Current Map Stays" + { + "cze" "Současná mapa pokračuje! Hlasování rozhodlo! (Obdržela {1}%% z {2} hlasů)" + } + + "Changed Next Map" + { + "cze" "Změnil příští mapu na \"{1}\"." + } + +} diff --git a/mapchooser_extended/translations/da/incomplete.txt b/mapchooser_extended/translations/da/incomplete.txt new file mode 100644 index 00000000..9ec75619 --- /dev/null +++ b/mapchooser_extended/translations/da/incomplete.txt @@ -0,0 +1,2 @@ +Missing "Runoff Vote Nextmap", "Number Of Votes", "Custom", "Revote Is Needed", "Revote Warning", "Vote Warning", +"Line One", "Line Two", "Cannot Start Vote", and "Tie Vote" \ No newline at end of file diff --git a/mapchooser_extended/translations/da/mapchooser_extended.phrases.txt b/mapchooser_extended/translations/da/mapchooser_extended.phrases.txt new file mode 100644 index 00000000..f9edcc73 --- /dev/null +++ b/mapchooser_extended/translations/da/mapchooser_extended.phrases.txt @@ -0,0 +1,43 @@ +"Phrases" +{ + "Vote Nextmap" + { + "da" "Stem om næste bane!" + } + + "Nextmap Voting Started" + { + "da" "Afstemning for næste bane er begyndt." + } + + "Nextmap Voting Finished" + { + "da" "Bane afstemning er fuldført. Den næste bane vil være {1}. (Modtog {2}%% af {3} stemmer) " + } + + "Current Map Extended" + { + "da" "Den nuværende bane er blevet forlænget. (Modtog {1}%% af {2} stemmer) " + } + + "Extend Map" + { + "da" "Forlæng bane" + } + + "Dont Change" + { + "da" "Skift ikke!" + } + + "Current Map Stays" + { + "da" "Den aktuelle bane fortsætter! Afstemningen har talt! (Modtog {1}%% af {2} stemmer)" + } + + "Changed Next Map" + { + "da" "Skiftede næste bane til \"{1}'." + } + +} diff --git a/mapchooser_extended/translations/de/mapchooser_extended.phrases.txt b/mapchooser_extended/translations/de/mapchooser_extended.phrases.txt new file mode 100644 index 00000000..21b90342 --- /dev/null +++ b/mapchooser_extended/translations/de/mapchooser_extended.phrases.txt @@ -0,0 +1,98 @@ +"Phrases" +{ + "Vote Nextmap" + { + "de" "Stimme für die nächste Karte!" + } + + "Nextmap Voting Started" + { + "de" "Abstimmung für die nächste Karte wurde gestartet." + } + + "Nextmap Voting Finished" + { + "de" "Karten-Abstimmung wurde abgeschlossen. Nächste Karte wird {1} sein. ({2}%% von {3} Stimmen erhalten) " + } + + "Current Map Extended" + { + "de" "Die aktuelle Karte wurde verlängert. ({1}%% von {2} Stimmen erhalten) " + } + + "Extend Map" + { + "de" "Verlängere aktuelle Karte" + } + + "Dont Change" + { + "de" "Nicht wechseln" + } + + "Current Map Stays" + { + "de" "Aktuelle Karte geht weiter! Die Abstimmung hat entschieden! ({1}%% von {2} Stimmen erhalten) " + } + + "Changed Next Map" + { + "de" "Nächste Karte wurde auf \"{1}\" geändert. " + } + + "Runoff Vote Nextmap" + { + "de" "Stichwahl für die nächste Karte!" + } + + "Number Of Votes" + { + "de" "Anzahl der Stimmen" + } + + "Custom" + { + "de" "{1} (nicht vorhanden)" + } + + "Revote Is Needed" + { + "de" "Keine Karte hat mehr als {1}%% der Stimmen.\nEine neue Abstimmung ist erforderlich!" + } + + "Revote Warning" + { + "de" "Stichwahl startet in: {1} Sekunden" + } + + "Vote Warning" + { + "de" "Achtung! Die Abstimmung für die nächste Karte startet in: {1} Sekunden" + } + + "Line One" + { + "de" "Überlegen Sie, welche Karte Sie spielen möchten ..." + } + + "Line Two" + { + "de" "... und nicht unüberlegt wählen!" + } + + "Cannot Start Vote" + { + "de" "Abstimmung ist bereits im Gange. Versuch Sie es erneut in {1} Sekunden" + } + + "Tie Vote" + { + "de" "Die Top-{1} Karten haben gleich viele Stimmen!.\nEine neue Abstimmung ist erforderlich!" + } + + "Custom Marked" + { + "de" "*{1}" + } + +} \ No newline at end of file diff --git a/mapchooser_extended/translations/es/mapchooser_extended.phrases.txt b/mapchooser_extended/translations/es/mapchooser_extended.phrases.txt new file mode 100644 index 00000000..162f7ab0 --- /dev/null +++ b/mapchooser_extended/translations/es/mapchooser_extended.phrases.txt @@ -0,0 +1,92 @@ +"Phrases" +{ + "Vote Nextmap" + { + "es" "Vota para el siguiente mapa!" + } + + "Nextmap Voting Started" + { + "es" "La votacion para el siguiente mapa ha comenzado." + } + + "Nextmap Voting Finished" + { + "es" "Votacion de Mapa finalizado. El siguiente mapa sera {1}. (Recibidos {2}%% de {3} votos)" + } + + "Current Map Extended" + { + "es" "El mapa actual ha sido extendido. (Recibidos {1}%% de {2} votos)" + } + + "Extend Map" + { + "es" "Extender mapa actual" + } + + "Dont Change" + { + "es" "No cambiar" + } + + "Current Map Stays" + { + "es" "El mapa actual continua! La votacion ha hablado! (Recibidos {1}%% de {2} votos)" + } + + "Changed Next Map" + { + "es" "El siguiente mapa ha cambiado a \"{1}\"." + } + + "Runoff Vote Nextmap" + { + "es" "Hacer votacion para el siguiente mapa!" + } + + "Number Of Votes" + { + "es" "Numero de Votaciones" + } + + "Custom" + { + "es" "{1} (Custom)" + } + + "Revote Is Needed" + { + "es" "No hay ningun mapa que haya recibido el {1}%% de votaciones.\nEntonces, que mapa ganara? Es necesario otra votacion!" + } + + "Revote Warning" + { + "es" "La votacion empezara en: {1}s" + } + + "Vote Warning" + { + "es" "Atencion! La votacion para el siguiente mapa se iniciara en: {1}s" + } + + "Line One" + { + "es" "Considera que mapa quieres jugar..." + } + + "Line Two" + { + "es" "...y no pulse los botones sin pensar ;-)" + } + + "Cannot Start Vote" + { + "es" "Votacion ya iniciada. Intentando de nuevo en {1}s." + } + + "Tie Vote" + { + "es" "El top {1} de mapas tienen el mismo numero de votaciones.\nSe necesita una nueva votacion!" + } +} \ No newline at end of file diff --git a/mapchooser_extended/translations/fr/mapchooser_extended.phrases.old.txt b/mapchooser_extended/translations/fr/mapchooser_extended.phrases.old.txt new file mode 100644 index 00000000..cf2c859a --- /dev/null +++ b/mapchooser_extended/translations/fr/mapchooser_extended.phrases.old.txt @@ -0,0 +1,87 @@ +"Phrases" +{ + "Vote Nextmap" + { + "fr" "Voter pour la prochaine map!" + } + + "Nextmap Voting Started" + { + "fr" "Voter pour la prochaine map est lancer." + } + + "Nextmap Voting Finished" + { + "fr" "Le VoteMap est terminer. La prochaine map sera {1}. (Reçu {2}%% sur {3} votes)" + } + + "Current Map Extended" + { + "fr" "La map actuelle a été prolonger. (Reçu {1}%% sur {2} votes)" + } + + "Extend Map" + { + "fr" "Prolonger la map" + } + + "Dont Change" + { + "fr" "Ne pas changer" + } + + "Current Map Stays" + { + "fr" "La map continue! Le vote a tranché! (Reçu {1}%% sur {2} votes)" + } + + "Changed Next Map" + { + "fr" "La map suivante sera \"{1}\"." + } + + "Runoff Vote Nextmap" + { + "fr" "Votez a nouveau pour la prochaine Map!" + } + + "Number Of Votes" + { + "fr" "Nombres de votes" + } + + "Custom" + { + "fr" "{1} (Custom)" + } + + "Revote Is Needed" + { + "fr" "Aucune map n'a reçu plus de {1}%% du vote.\nAlors, quels maps va gagner? Un nouveau vote va être lancé!" + } + + "Revote Warning" + { + "fr" "Un nouveau vote commence dans: {1}s" + } + + "Vote Warning" + { + "fr" "Attention! Le vote pour la prochaine map commence dans: {1}s" + } + + "Line One" + { + "fr" "Voter pour la map que vous voulez jouer..." + } + + "Line Two" + { + "fr" "...et ne pas cliquez sur une touche comme un con ;-)" + } + + "Cannot Start Vote" + { + "fr" "Vote déjà en cours. Reassayer dans {1}s." + } +} \ No newline at end of file diff --git a/mapchooser_extended/translations/fr/mapchooser_extended.phrases.txt b/mapchooser_extended/translations/fr/mapchooser_extended.phrases.txt new file mode 100644 index 00000000..aaa868a7 --- /dev/null +++ b/mapchooser_extended/translations/fr/mapchooser_extended.phrases.txt @@ -0,0 +1,92 @@ +"Phrases" +{ + "Vote Nextmap" + { + "fr" "Votez pour la prochaine map!" + } + + "Nextmap Voting Started" + { + "fr" "Le vote pour la prochaine map est lancé." + } + + "Nextmap Voting Finished" + { + "fr" "Le VoteMap est terminé. La prochaine map sera {1}. (Reçu {2}%% sur {3} votes)" + } + + "Current Map Extended" + { + "fr" "La map actuelle a été prolongée. (Reçu {1}%% sur {2} votes)" + } + + "Extend Map" + { + "fr" "Prolonger la map" + } + + "Dont Change" + { + "fr" "Ne pas changer" + } + + "Current Map Stays" + { + "fr" "La map continue! Le vote a tranché! (Reçu {1}%% sur {2} votes)" + } + + "Changed Next Map" + { + "fr" "La map suivante sera \"{1}\"." + } + + "Runoff Vote Nextmap" + { + "fr" "Votez à nouveau pour la prochaine Map!" + } + + "Number Of Votes" + { + "fr" "Nombres de votes" + } + + "Custom" + { + "fr" "{1} (Custom)" + } + + "Revote Is Needed" + { + "fr" "Aucune map n'a reçu plus de {1}%% du vote.\nAlors, quelle map va gagner? Un nouveau vote va être lancé!" + } + + "Revote Warning" + { + "fr" "Un nouveau vote commence dans: {1}s" + } + + "Vote Warning" + { + "fr" "Attention! Le vote pour la prochaine map commence dans: {1}s" + } + + "Line One" + { + "fr" "Votez pour la map que vous voulez jouer..." + } + + "Line Two" + { + "fr" "...et n'appuyez pas sur une touche comme un con ;-)" + } + + "Cannot Start Vote" + { + "fr" "Vote déjà en cours. Reassayez dans {1}s." + } + + "Tie Vote" + { + "fr" "{1} maps ont eu un même nombre de voix.\nUn nouveau vote est nécessaire!" + } +} \ No newline at end of file diff --git a/mapchooser_extended/translations/hu/incomplete.txt b/mapchooser_extended/translations/hu/incomplete.txt new file mode 100644 index 00000000..9ec75619 --- /dev/null +++ b/mapchooser_extended/translations/hu/incomplete.txt @@ -0,0 +1,2 @@ +Missing "Runoff Vote Nextmap", "Number Of Votes", "Custom", "Revote Is Needed", "Revote Warning", "Vote Warning", +"Line One", "Line Two", "Cannot Start Vote", and "Tie Vote" \ No newline at end of file diff --git a/mapchooser_extended/translations/hu/mapchooser_extended.phrases.txt b/mapchooser_extended/translations/hu/mapchooser_extended.phrases.txt new file mode 100644 index 00000000..cb159e36 --- /dev/null +++ b/mapchooser_extended/translations/hu/mapchooser_extended.phrases.txt @@ -0,0 +1,43 @@ +"Phrases" +{ + "Vote Nextmap" + { + "hu" "Mi legyen a következő pálya?" + } + + "Nextmap Voting Started" + { + "hu" "Palyavalaszto szavazas elindult!" + } + + "Nextmap Voting Finished" + { + "hu" "A szavazás lezárult. A következő pálya a {1} lesz" + } + + "Current Map Extended" + { + "hu" "Az aktualis palya meghosszabitva." + } + + "Extend Map" + { + "hu" "Palya meghosszabitasa" + } + + "Dont Change" + { + "hu" "Ne valtsunk!" + } + + "Current Map Stays" + { + "hu" "Jelenlegi palya folytatodik." + } + + "Changed Next Map" + { + "hu" "\"{1}\" lesz a kovetkezo palya" + } + +} diff --git a/mapchooser_extended/translations/it/mapchooser_extended.phrases.txt b/mapchooser_extended/translations/it/mapchooser_extended.phrases.txt new file mode 100644 index 00000000..057a7925 --- /dev/null +++ b/mapchooser_extended/translations/it/mapchooser_extended.phrases.txt @@ -0,0 +1,92 @@ +"Phrases" +{ + "Vote Nextmap" + { + "it" "Vota per la prossima mappa!" + } + + "Nextmap Voting Started" + { + "it" "Le votazione per scegliere la prossima mappa sono iniziate." + } + + "Nextmap Voting Finished" + { + "it" "Le votazioni sono terminate. La prossima mappa sarà {1}. (Con {2}%% su {3} votes)" + } + + "Current Map Extended" + { + "it" "La mappa attuale è stata estesa. (Con {1}%% su {2} votes)" + } + + "Extend Map" + { + "it" "Estendi la mappa corrente" + } + + "Dont Change" + { + "it" "Non cambiare" + } + + "Current Map Stays" + { + "it" "La mappa continua! Il voto ha parlato! (Con {1}%% su {2} votes)" + } + + "Changed Next Map" + { + "it" "La mappa successiva sarà \"{1}\"." + } + + "Runoff Vote Nextmap" + { + "it" "Vota nuovamente per scegliere la prossima mappa!" + } + + "Number Of Votes" + { + "it" "Numero di voti" + } + + "Custom" + { + "it" "{1} (Custom)" + } + + "Revote Is Needed" + { + "it" "Nessuna mappa ha ricevuto più del {1}%% di voti.\nAllora, quale mappa vincerà? Si inizia un' altra votazione!" + } + + "Revote Warning" + { + "it" "Una nuova votazione inizierà tra: {1}s" + } + + "Vote Warning" + { + "it" "Attenzione! Le votazione per la prossima mappa cominceranno tra: {1}s" + } + + "Line One" + { + "it" "Vota la mappa chevorresti giocare..." + } + + "Line Two" + { + "it" "...e non cliccare i tasti sconsideratamente :D" + } + + "Cannot Start Vote" + { + "it" "Il voto è in corso. Riprova tra {1}s." + } + + "Tie Vote" + { + "it" "Le mappe hanno raggiunto il pareggio dei voti.\nBisogna nuovamente votare!" + } +} diff --git a/mapchooser_extended/translations/jp/incomplete.txt b/mapchooser_extended/translations/jp/incomplete.txt new file mode 100644 index 00000000..9ec75619 --- /dev/null +++ b/mapchooser_extended/translations/jp/incomplete.txt @@ -0,0 +1,2 @@ +Missing "Runoff Vote Nextmap", "Number Of Votes", "Custom", "Revote Is Needed", "Revote Warning", "Vote Warning", +"Line One", "Line Two", "Cannot Start Vote", and "Tie Vote" \ No newline at end of file diff --git a/mapchooser_extended/translations/jp/mapchooser_extended.phrases.txt b/mapchooser_extended/translations/jp/mapchooser_extended.phrases.txt new file mode 100644 index 00000000..be7e0ddf --- /dev/null +++ b/mapchooser_extended/translations/jp/mapchooser_extended.phrases.txt @@ -0,0 +1,43 @@ +"Phrases" +{ + "Vote Nextmap" + { + "jp" "次のマップを投票してください!" + } + + "Nextmap Voting Started" + { + "jp" "次のマップ投票をスタートしました。" + } + + "Nextmap Voting Finished" + { + "jp" "マップ投票が完了しました。次はマップは{1}です。({3}中{2}%%)" + } + + "Current Map Extended" + { + "jp" "現在のマップを延長します。({3}中{2}%%)" + } + + "Extend Map" + { + "jp" "現在のマップを延長" + } + + "Dont Change" + { + "jp" "変更しない" + } + + "Current Map Stays" + { + "jp" "現在のマップを延長します。({3}中{2}%%)" + } + + "Changed Next Map" + { + "jp" "次のマップを\"{1}\"に変更しました。" + } + +} diff --git a/mapchooser_extended/translations/ko/mapchooser_extended.phrases.txt b/mapchooser_extended/translations/ko/mapchooser_extended.phrases.txt new file mode 100644 index 00000000..489dd1ca --- /dev/null +++ b/mapchooser_extended/translations/ko/mapchooser_extended.phrases.txt @@ -0,0 +1,98 @@ +"Phrases" +{ + "Vote Nextmap" + { + "ko" "다음 맵을 결정하기 위한 투표!" + } + + "Nextmap Voting Started" + { + "ko" "다음 맵을 결정하기 위한 투표가 시작되었습니다." + } + + "Nextmap Voting Finished" + { + "ko" "맵 투표가 끝났습니다. 다음 맵은 {1} 이 될 것입니다. (전체 인원 {2}명 중 {1}%%의 표를 받았습니다.)" + } + + "Current Map Extended" + { + "ko" "현재 맵을 더하기로 결정했습니다. (전체 인원 {2}명 중 {1}%%의 표를 받았습니다.)" + } + + "Extend Map" + { + "ko" "지금 맵을 더 하자" + } + + "Dont Change" + { + "ko" "바꾸지 말자" + } + + "Current Map Stays" + { + "ko" "현재 맵을 계속 합니다!(전체 인원 {2} 명의 {1}%% 의 표를 받았습니다)" + } + + "Changed Next Map" + { + "ko" "다음 맵을 \"{1}\" 로 바꾸었습니다." + } + + "Runoff Vote Nextmap" + { + "ko" "다음 맵을 결정하기 위한 재투표!" + } + + "Number Of Votes" + { + "ko" "투표수" + } + + "Custom" + { + "ko" "{1} (커스텀 맵)" + } + + "Revote Is Needed" + { + "ko" "{1}%% 이상 투표를 받은 맵이 없습니다.\n다음 맵을 결정하기 위해 재투표를 합니다!" + } + + "Revote Warning" + { + "ko" "재투표까지: {1}초" + } + + "Vote Warning" + { + "ko" "알립니다! 다음 맵을 결정하는 투표까지: {1}초" + } + + "Line One" + { + "ko" "원하는 맵을 선택하세요..." + } + + "Line Two" + { + "ko" "...그리고 아무거나 막 찍지 말고요 -_-" + } + + "Cannot Start Vote" + { + "ko" "투표가 진행 중에 있습니다. {1}초 후 다시 시도합니다." + } + + "Tie Vote" + { + "ko" "{1}개 맵이 같은 투표수를 얻었습니다.\n재투표를 합니다!" + } + + "Custom Marked" + { + "ko" "*{1}" + } + +} diff --git a/mapchooser_extended/translations/lv/incomplete.txt b/mapchooser_extended/translations/lv/incomplete.txt new file mode 100644 index 00000000..9ec75619 --- /dev/null +++ b/mapchooser_extended/translations/lv/incomplete.txt @@ -0,0 +1,2 @@ +Missing "Runoff Vote Nextmap", "Number Of Votes", "Custom", "Revote Is Needed", "Revote Warning", "Vote Warning", +"Line One", "Line Two", "Cannot Start Vote", and "Tie Vote" \ No newline at end of file diff --git a/mapchooser_extended/translations/lv/mapchooser_extended.phrases.txt b/mapchooser_extended/translations/lv/mapchooser_extended.phrases.txt new file mode 100644 index 00000000..9a55fc55 --- /dev/null +++ b/mapchooser_extended/translations/lv/mapchooser_extended.phrases.txt @@ -0,0 +1,43 @@ +"Phrases" +{ + "Vote Nextmap" + { + "lv" "Balso par nākamo karti!" + } + + "Nextmap Voting Started" + { + "lv" "Balsošana par nākamo karti ir sākusies." + } + + "Nextmap Voting Finished" + { + "lv" "Balsošana par karti ir beigusies. Nākamā karte būs {1}. (Saņēma {2}%% no {3} balsīm) " + } + + "Current Map Extended" + { + "lv" "Patreizējās kartes laiks ir pagarināts. (Saņēma {1}%% no {2} balsīm) " + } + + "Extend Map" + { + "lv" "Pagarināt laiku patreizējā kartē" + } + + "Dont Change" + { + "lv" "Nemainīt" + } + + "Current Map Stays" + { + "lv" "Patreizējā karte turpinās! Aptauja ir beigusies! (Saņēma {1}%% no {2} balsīm)" + } + + "Changed Next Map" + { + "lv" "Nomainīja nākamo karti uz \"{1}\"." + } + +} diff --git a/mapchooser_extended/translations/mapchooser_extended.phrases.txt b/mapchooser_extended/translations/mapchooser_extended.phrases.txt new file mode 100644 index 00000000..fe35c8be --- /dev/null +++ b/mapchooser_extended/translations/mapchooser_extended.phrases.txt @@ -0,0 +1,109 @@ +"Phrases" +{ + "Vote Nextmap" + { + "en" "Vote for the next map!" + } + + "Nextmap Voting Started" + { + "en" "Voting for next map has started." + } + + "Nextmap Voting Finished" + { + "#format" "{1:s},{2:i},{3:i}" + "en" "Map voting has finished. The next map will be {1}. (Received {2}%% of {3} votes)" + } + + "Current Map Extended" + { + "#format" "{1:i},{2:i}" + "en" "The current map has been extended. (Received {1}%% of {2} votes)" + } + + "Extend Map" + { + "en" "Extend Current Map" + } + + "Dont Change" + { + "en" "Don't Change" + } + + "Current Map Stays" + { + "#format" "{1:i},{2:i}" + "en" "Current map continues! The Vote has spoken! (Received {1}%% of {2} votes)" + } + + "Changed Next Map" + { + "#format" "{1:s}" + "en" "Changed nextmap to \"{1}\"." + } + + "Runoff Vote Nextmap" + { + "en" "Runoff Vote for the next map!" + } + + "Number Of Votes" + { + "en" "Number of votes" + } + + "Custom" + { + "#format" "{1:s}" + "en" "{1} (Custom)" + } + + "Revote Is Needed" + { + "#format" "{1:i}" + "en" "No map has received more than {1}%% of the vote.\nSo, which map will win? A revote is needed!" + } + + "Revote Warning" + { + "#format" "{1:i}" + "en" "Runoff vote will start in: {1}s" + } + + "Vote Warning" + { + "#format" "{1:i}" + "en" "Warning! Voting for the next map will begin in: {1}s" + } + + "Line One" + { + "en" "Consider which map you want to play..." + } + + "Line Two" + { + "en" "...and don't hit buttons thoughtlessly ;-)" + } + + "Cannot Start Vote" + { + "#format" "{1:i}" + "en" "Vote already in progress. Retrying in {1}s." + } + + "Tie Vote" + { + "#format" "{1:i}" + "en" "The top {1} maps had the same number of votes.\nA revote is needed!" + } + + "Custom Marked" + { + "#format" "{1:s}" + "en" "*{1}" + } + +} \ No newline at end of file diff --git a/mapchooser_extended/translations/nl/incomplete.txt b/mapchooser_extended/translations/nl/incomplete.txt new file mode 100644 index 00000000..9ec75619 --- /dev/null +++ b/mapchooser_extended/translations/nl/incomplete.txt @@ -0,0 +1,2 @@ +Missing "Runoff Vote Nextmap", "Number Of Votes", "Custom", "Revote Is Needed", "Revote Warning", "Vote Warning", +"Line One", "Line Two", "Cannot Start Vote", and "Tie Vote" \ No newline at end of file diff --git a/mapchooser_extended/translations/nl/mapchooser_extended.phrases.txt b/mapchooser_extended/translations/nl/mapchooser_extended.phrases.txt new file mode 100644 index 00000000..02f6c45e --- /dev/null +++ b/mapchooser_extended/translations/nl/mapchooser_extended.phrases.txt @@ -0,0 +1,43 @@ +"Phrases" +{ + "Vote Nextmap" + { + "nl" "Stem voor de volgende map!" + } + + "Nextmap Voting Started" + { + "nl" "Stemmen voor de volgende map is gestart." + } + + "Nextmap Voting Finished" + { + "nl" "Map stemmen gestopt. De volgende map wordt {1}." + } + + "Current Map Extended" + { + "nl" "De huidige map is verlengd." + } + + "Extend Map" + { + "nl" "Verleng huidige map" + } + + "Dont Change" + { + "nl" "Niet veranderen" + } + + "Current Map Stays" + { + "nl" "Huidige map gaat verder! De Stem heeft gesproken! (Ontvangen {1}%% van de {2} stemmen)" + } + + "Changed Next Map" + { + "nl" "Volgende map verandert naar \"{1}\"." + } + +} diff --git a/mapchooser_extended/translations/no/incomplete.txt b/mapchooser_extended/translations/no/incomplete.txt new file mode 100644 index 00000000..9ec75619 --- /dev/null +++ b/mapchooser_extended/translations/no/incomplete.txt @@ -0,0 +1,2 @@ +Missing "Runoff Vote Nextmap", "Number Of Votes", "Custom", "Revote Is Needed", "Revote Warning", "Vote Warning", +"Line One", "Line Two", "Cannot Start Vote", and "Tie Vote" \ No newline at end of file diff --git a/mapchooser_extended/translations/no/mapchooser_extended.phrases.txt b/mapchooser_extended/translations/no/mapchooser_extended.phrases.txt new file mode 100644 index 00000000..76e1bc9b --- /dev/null +++ b/mapchooser_extended/translations/no/mapchooser_extended.phrases.txt @@ -0,0 +1,43 @@ +"Phrases" +{ + "Vote Nextmap" + { + "no" "Stem for det neste kartet!" + } + + "Nextmap Voting Started" + { + "no" "Avstemning for det neste kartet har startet." + } + + "Nextmap Voting Finished" + { + "no" "Kart-avstemningen er avsluttet. Det neste kartet vil være {1}. (Mottok {2}%% av {3} stemmer)." + } + + "Current Map Extended" + { + "no" "Gjeldende kart videreføres! (Mottok {1}%% av {2} stemmer)." + } + + "Extend Map" + { + "no" "Forleng gjeldende kart." + } + + "Dont Change" + { + "no" "Ikke bytt!" + } + + "Current Map Stays" + { + "no" "Gjeldende kart videreføres! Avstemningen har talt! (Mottok {1}%% av {2} stemmer)." + } + + "Changed Next Map" + { + "no" "Byttet neste kart til \"{1}\"" + } + +} diff --git a/mapchooser_extended/translations/pl/mapchooser_extended.phrases.txt b/mapchooser_extended/translations/pl/mapchooser_extended.phrases.txt new file mode 100644 index 00000000..902c4a13 --- /dev/null +++ b/mapchooser_extended/translations/pl/mapchooser_extended.phrases.txt @@ -0,0 +1,92 @@ +"Phrases" +{ + "Vote Nextmap" + { + "pl" "Głosuj na następną mapę!" + } + + "Nextmap Voting Started" + { + "pl" "Rozpoczęto głosowanie na następną mapę." + } + + "Nextmap Voting Finished" + { + "pl" "Głosowanie na mapę zostało zakończone. Następną mapą będzie {1}. (Otrzymała {2} procent z {3} głosów) " + } + + "Current Map Extended" + { + "pl" "Aktualna mapa została przedłużona. (Otrzymała {1} procent z {2} głosów)" + } + + "Extend Map" + { + "pl" "Przedłuż bieżącą mapę" + } + + "Dont Change" + { + "pl" "Nie Zmieniaj" + } + + "Current Map Stays" + { + "pl" "Aktualna mapa będzie kontynuowana! (Otrzymano {1} procent z {2} głosów)" + } + + "Changed Next Map" + { + "pl" "Zmieniono następną mapę na: \"{1}\"." + } + + "Runoff Vote Nextmap" + { + "pl" "Wybierz ponownie!" + } + + "Number Of Votes" + { + "pl" "Otrzymane głosy" + } + + "Custom" + { + "pl" "{1} (Niestandardowa)" + } + + "Revote Is Needed" + { + "pl" "Żadna mapa nie otrzymała przynajmniej {1}%% głosów.\nWięc która mapa jest zwycięzcą? Trzeba zagłosować ponownie!" + } + + "Revote Warning" + { + "pl" "Ponowne głosowanie rozpocznie się za: {1}s\nTym razem się już zdecydujcie ;-)" + } + + "Vote Warning" + { + "pl" "UWAGA!!! Głosowanie na następną mapę rozpocznie się za: {1}s" + } + + "Line One" + { + "pl" "Zastanów się na której mapie chcesz grać..." + } + + "Line Two" + { + "pl" "...wpisując !revote możesz zmienić swój głos." + } + + "Cannot Start Vote" + { + "pl" "Głosowanie w toku. Ponawiam za {1}s." + } + + "Tie Vote" + { + "pl" "{1} najlepsze mapy otrzymały tę samą ilość głosów.\nPotrzeba ponownego głosowania!" + } +} \ No newline at end of file diff --git a/mapchooser_extended/translations/pt/mapchooser_extended.phrases.txt b/mapchooser_extended/translations/pt/mapchooser_extended.phrases.txt new file mode 100644 index 00000000..729e42ae --- /dev/null +++ b/mapchooser_extended/translations/pt/mapchooser_extended.phrases.txt @@ -0,0 +1,98 @@ +"Phrases" +{ + "Vote Nextmap" + { + "pt" "Vote para o prximo mapa!" + } + + "Nextmap Voting Started" + { + "pt" "Votao para o prximo mapa comeou." + } + + "Nextmap Voting Finished" + { + "pt" "A votao para o prximo mapa terminou. O prximo mapa ser {1}. (Recebidos {2}%% de {3} votos)" + } + + "Current Map Extended" + { + "pt" "A mapa atual foi estendido. (Recebidos {1}%% de {2} votos)" + } + + "Extend Map" + { + "pt" "Estender tempo do mapa atual" + } + + "Dont Change" + { + "pt" "No Mudar" + } + + "Current Map Stays" + { + "pt" "O mapa atual continua! O Voto foi dado! (Recebidos {1}%% de {2} votos)" + } + + "Changed Next Map" + { + "pt" "Mudado o prximo mapa para \"{1}\"." + } + + "Runoff Vote Nextmap" + { + "pt" "Segundo turno de votos para o prximo mapa!" + } + + "Number Of Votes" + { + "pt" "Nmero de votos" + } + + "Custom" + { + "pt" "{1} (Custom)" + } + + "Revote Is Needed" + { + "pt" "Nenhum mapa recebeu mais que {1}%% de votos.\nEnto, qual mapa ganhar? Precisa de uma nova votao!" + } + + "Revote Warning" + { + "pt" "Segundo turno comea em: {1}s" + } + + "Vote Warning" + { + "pt" "Ateno! Votao para o prximo mapa comear em: {1}s" + } + + "Line One" + { + "pt" "Considere que mapa voc quer jogar..." + } + + "Line Two" + { + "pt" "...e no aperte os botes sem pensar ;-)" + } + + "Cannot Start Vote" + { + "pt" "Votao em progresso. Tentando novamente em {1}s." + } + + "Tie Vote" + { + "pt" "Os {1} mapas tiveram o mesmo nmero de votos.\nUma nova votao necessria!" + } + + "Custom Marked" + { + "pt" "*{1}" + } + +} \ No newline at end of file diff --git a/mapchooser_extended/translations/ru/incomplete.txt b/mapchooser_extended/translations/ru/incomplete.txt new file mode 100644 index 00000000..356ad7ce --- /dev/null +++ b/mapchooser_extended/translations/ru/incomplete.txt @@ -0,0 +1 @@ +Missing "Cannot Start Vote" and "Tie Vote" \ No newline at end of file diff --git a/mapchooser_extended/translations/ru/mapchooser_extended.phrases.txt b/mapchooser_extended/translations/ru/mapchooser_extended.phrases.txt new file mode 100644 index 00000000..4a0d74d0 --- /dev/null +++ b/mapchooser_extended/translations/ru/mapchooser_extended.phrases.txt @@ -0,0 +1,83 @@ +"Phrases" +{ + "Vote Nextmap" + { + "ru" "Голосование за следующую карту." + } + + "Runoff Vote Nextmap" + { + "ru" "Повторное голосование за карту." + } + + "Nextmap Voting Started" + { + "ru" "Голосование за следующую карту запущено." + } + + "Nextmap Voting Finished" + { + "ru" "Голосование за карту завершено. Следующей картой будет: {1}. (Получено {2}%% из {3} голосов(а))" + } + + "Current Map Extended" + { + "ru" "Текущая карта была продлена. (Получено {1}%% из {2} голосов(а))" + } + + "Extend Map" + { + "ru" "Продлить текущую карту." + } + + "Dont Change" + { + "ru" "Не менять карту." + } + + "Current Map Stays" + { + "ru" "Текущая карта не сменится! (Получено {1}%% из {2} голосов(а))" + } + + "Changed Next Map" + { + "ru" "Следующая карта изменена на \"{1}\"." + } + + "Number Of Votes" + { + "ru" "Количество голосов" + } + + "Custom" + { + "ru" "{1} (Случайная)" + } + + "Revote Is Needed" + { + "ru" "Ни одна карта не получила более, чем {1}%% голосов(а).\nИтак, какая карта будет следующей? Приготовьтесь к повторному голосованию!" + } + + "Revote Warning" + { + "ru" "Повторное голосование начнётся через: {1}s" + } + + "Vote Warning" + { + "ru" "Внимание! Голосование за следующую карту начнётся через: {1}s" + } + + "Line One" + { + "ru" "Выбери, какая карта будет следующей..." + } + + "Line Two" + { + "ru" "...и не клацай кнопки,не подумав ;-)" + } + +} \ No newline at end of file diff --git a/mapchooser_extended/translations/sv/incomplete.txt b/mapchooser_extended/translations/sv/incomplete.txt new file mode 100644 index 00000000..9ec75619 --- /dev/null +++ b/mapchooser_extended/translations/sv/incomplete.txt @@ -0,0 +1,2 @@ +Missing "Runoff Vote Nextmap", "Number Of Votes", "Custom", "Revote Is Needed", "Revote Warning", "Vote Warning", +"Line One", "Line Two", "Cannot Start Vote", and "Tie Vote" \ No newline at end of file diff --git a/mapchooser_extended/translations/sv/mapchooser_extended.phrases.txt b/mapchooser_extended/translations/sv/mapchooser_extended.phrases.txt new file mode 100644 index 00000000..d70eee86 --- /dev/null +++ b/mapchooser_extended/translations/sv/mapchooser_extended.phrases.txt @@ -0,0 +1,43 @@ +"Phrases" +{ + "Vote Nextmap" + { + "sv" "Rösta för nästa bana!" + } + + "Nextmap Voting Started" + { + "sv" "Röstning om nästa bana har börjat." + } + + "Nextmap Voting Finished" + { + "sv" "Röstningen om banan har avslutats. Nästa bana kommer att bli {1}. (Fick {2}%% av {3} röster) " + } + + "Current Map Extended" + { + "sv" "Den nuvarande banan har förlängts. (Fick {1}%% av {2} röster) " + } + + "Extend Map" + { + "sv" "Förläng nuvarande bana" + } + + "Dont Change" + { + "sv" "Byt inte" + } + + "Current Map Stays" + { + "sv" "Nuvarande banan fortsätter! Röstningen har talat! (Fick {1}%% av {2} röster) " + } + + "Changed Next Map" + { + "sv" "Bytta nästa bana till \"{1}\". " + } + +} diff --git a/mapchooser_extended/translations/tr/incomplete.txt b/mapchooser_extended/translations/tr/incomplete.txt new file mode 100644 index 00000000..9ec75619 --- /dev/null +++ b/mapchooser_extended/translations/tr/incomplete.txt @@ -0,0 +1,2 @@ +Missing "Runoff Vote Nextmap", "Number Of Votes", "Custom", "Revote Is Needed", "Revote Warning", "Vote Warning", +"Line One", "Line Two", "Cannot Start Vote", and "Tie Vote" \ No newline at end of file diff --git a/mapchooser_extended/translations/tr/mapchooser_extended.phrases.txt b/mapchooser_extended/translations/tr/mapchooser_extended.phrases.txt new file mode 100644 index 00000000..270696cb --- /dev/null +++ b/mapchooser_extended/translations/tr/mapchooser_extended.phrases.txt @@ -0,0 +1,43 @@ +"Phrases" +{ + "Vote Nextmap" + { + "tr" "Sonraki harita için oy ver!" + } + + "Nextmap Voting Started" + { + "tr" "Sonraki harita için oylama başladı." + } + + "Nextmap Voting Finished" + { + "tr" "Harita oylaması sona erdi. Sıradaki harita {1} olacak. ({3} oyun %%{2}'i alındı) " + } + + "Current Map Extended" + { + "tr" "Geçerli harita uzatıldı. ({2} oyun %%{1}'i alındı) " + } + + "Extend Map" + { + "tr" "Geçerli Haritayı Uzat" + } + + "Dont Change" + { + "tr" "Değiştirme" + } + + "Current Map Stays" + { + "tr" "Geçerli harita devam ediyor! Oylama konuştu! ({2} oyun %%{1}'i alındı) " + } + + "Changed Next Map" + { + "tr" "Sıradaki harita \"{1}\" olarak değiştirildi." + } + +} diff --git a/voiceannounce_ex/gamedata/voiceannounce_ex.games.txt b/voiceannounce_ex/gamedata/voiceannounce_ex.games.txt new file mode 100644 index 00000000..3c5da571 --- /dev/null +++ b/voiceannounce_ex/gamedata/voiceannounce_ex.games.txt @@ -0,0 +1,173 @@ +"Games" +{ + "#default" + { + "#supported" + { + "engine" "orangebox_valve" + "engine" "css" + "engine" "csgo" + } + + "Addresses" + { + "CBaseServer" + { + "windows" + { + "signature" "CVEngineServer::CreateFakeClient" + "read" "8" + } + "linux" + { + "signature" "sv" + } + "mac" + { + "signature" "sv" + } + } + } + + "Signatures" + { + "CVEngineServer::CreateFakeClient" + { + "library" "engine" + "windows" "\x55\x8B\xEC\xFF\x75\x08\xB9\x2A\x2A\x2A\x2A\xE8\x2A\x2A\x2A\x2A\x85\xC0\x75\x04" + } + + "sv" + { + "library" "engine" + "linux" "@sv" + "mac" "@sv" + } + } + } + + "#default" + { + "#supported" + { + "engine" "orangebox" + "engine" "left4dead" + "engine" "left4dead2" + } + + "Addresses" + { + "CBaseServer" + { + "windows" + { + "signature" "CVEngineServer::CreateFakeClient" + "read" "6" + } + "linux" + { + "signature" "sv" + } + "mac" + { + "signature" "sv" + } + } + } + + "Signatures" + { + "CVEngineServer::CreateFakeClient" + { + "library" "engine" + "windows" "\x8B\x44\x24\x04\x50\xB9\x2A\x2A\x2A\x2A\xE8\x2A\x2A\x2A\x2A\x85\xC0" + } + + "sv" + { + "library" "engine" + "linux" "@sv" + "mac" "@sv" + } + } + } + + "#default" + { + "#supported" + { + "engine" "orangebox" + "engine" "orangebox_valve" + "engine" "css" + } + + "Offsets" + { + "CBaseServer::GetClient" + { + "windows" "6" + "linux" "7" + "mac" "7" + } + + "CBaseClient::GetPlayerSlot" + { + "windows" "14" + "linux" "15" + "mac" "15" + } + + "CGameClient::ProcessVoiceData" + { + "windows" "7" + "linux" "8" + "mac" "8" + } + } + } + + "#default" + { + "#supported" + { + "engine" "left4dead" + "engine" "left4dead2" + "engine" "csgo" + } + + "Offsets" + { + "CBaseServer::GetClient" + { + "windows" "6" + "linux" "7" + "mac" "7" + } + + "CBaseClient::GetPlayerSlot" + { + "windows" "14" + "linux" "15" + "mac" "15" + } + + "CGameClient::ProcessVoiceData" + { + "windows" "8" + "linux" "9" + "mac" "9" + } + } + } + "csgo" + { + "Offsets" + { + "OnVoiceTransmit" + { + "windows" "513" + "linux" "514" + } + } + } +} diff --git a/voiceannounce_ex/scripting/include/dhooks.inc b/voiceannounce_ex/scripting/include/dhooks.inc new file mode 120000 index 00000000..8b56a6fb --- /dev/null +++ b/voiceannounce_ex/scripting/include/dhooks.inc @@ -0,0 +1 @@ +../../../includes/dhooks.inc \ No newline at end of file diff --git a/voiceannounce_ex/scripting/include/voiceannounce_ex.inc b/voiceannounce_ex/scripting/include/voiceannounce_ex.inc new file mode 100644 index 00000000..b922896e --- /dev/null +++ b/voiceannounce_ex/scripting/include/voiceannounce_ex.inc @@ -0,0 +1,57 @@ +#if defined _voiceannounceex_included_ + #endinput +#endif +#define _voiceannounceex_included_ + +public SharedPlugin:__pl_voiceannounceex = +{ + name = "voiceannounce_ex", + file = "voiceannounce_ex.smx", + #if defined REQUIRE_PLUGIN + required = 1, + #else + required = 0, + #endif +}; + +public __pl_voiceannounceex_SetNTVOptional() +{ + MarkNativeAsOptional("IsClientSpeaking"); +} + +/** + * + * Checks whether a client is speaking or not. + * + * @param client The client index to check against. + * + * @error Client is not valid, ingame, or client is a bot. + * + * @return True if client is speaking, false otherwise. + * + */ + +native bool:IsClientSpeaking(client); + +/** + * + * Called when a client is speaking. + * + * @param client The index of the client that is speaking. + * + * @return True to allow the client to talk, false otherwise. + * + */ + +forward bool:OnClientSpeakingEx(client); + +/** + * + * Called when a client end of speaking. + * + * @param client The index of the client. + * + * @noreturn + * + */ +forward OnClientSpeakingEnd(client); \ No newline at end of file diff --git a/voiceannounce_ex/scripting/voiceannounce_ex.sp b/voiceannounce_ex/scripting/voiceannounce_ex.sp new file mode 100644 index 00000000..14889fd7 --- /dev/null +++ b/voiceannounce_ex/scripting/voiceannounce_ex.sp @@ -0,0 +1,253 @@ +#pragma semicolon 1 + +#include +#include +#include +#include + +#define PLUGIN_VERSION "2.0.0" + +new Handle:g_hProcessVoice = INVALID_HANDLE; +new Handle:g_hOnClientTalking = INVALID_HANDLE; +new Handle:g_hOnClientTalkingEnd = INVALID_HANDLE; +new bool:g_bLateLoad = false; + +new g_iHookID[MAXPLAYERS+1] = { -1, ... }; +new Handle:g_hClientMicTimers[MAXPLAYERS + 1] = {INVALID_HANDLE, ...}; + +new bool:g_bIsCSGO; +new Handle:g_hCSGOVoice; + +public Plugin:myinfo = +{ + name = "VoiceAnnounceEx", + author = "Franc1sco franug, Mini and GoD-Tony", + description = "Feature for developers to check/control client mic usage.", + version = PLUGIN_VERSION, + url = "https://forums.alliedmods.net" +} + +public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max) +{ + if(GetEngineVersion() == Engine_CSGO) + g_bIsCSGO = true; + else + g_bIsCSGO = false; + + CreateNative("IsClientSpeaking", Native_IsClientTalking); + + RegPluginLibrary("voiceannounce_ex"); + + g_bLateLoad = late; + return APLRes_Success; +} + +public Native_IsClientTalking(Handle:plugin, numParams) +{ + new client = GetNativeCell(1); + + if (client > MaxClients || client <= 0) + { + ThrowNativeError(SP_ERROR_NATIVE, "Client is not valid."); + return false; + } + + if (!IsClientInGame(client)) + { + ThrowNativeError(SP_ERROR_NATIVE, "Client is not in-game."); + return false; + } + + if (IsFakeClient(client)) + { + ThrowNativeError(SP_ERROR_NATIVE, "Cannot do mic checks on fake clients."); + return false; + } + + return (g_hClientMicTimers[client] == INVALID_HANDLE) ? false : true; +} + +public OnPluginStart() +{ + //CreateConVar("voiceannounce_ex_version", PLUGIN_VERSION, "plugin", FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY|FCVAR_DONTRECORD); + new Offset; + if(g_bIsCSGO) + { + Offset = GameConfGetOffset(GetConfig(), "OnVoiceTransmit"); + + if(Offset == -1) + SetFailState("Failed to get offset"); + + g_hCSGOVoice = DHookCreate(Offset, HookType_Entity, ReturnType_Int, ThisPointer_CBaseEntity, CSGOVoicePost); + } + else + { + Offset = GameConfGetOffset(GetConfig(), "CGameClient::ProcessVoiceData"); + + g_hProcessVoice = DHookCreate(Offset, HookType_Raw, ReturnType_Void, ThisPointer_Address, Hook_ProcessVoiceData); + DHookAddParam(g_hProcessVoice, HookParamType_ObjectPtr); + } + + g_hOnClientTalking = CreateGlobalForward("OnClientSpeakingEx", ET_Single, Param_Cell); + g_hOnClientTalkingEnd = CreateGlobalForward("OnClientSpeakingEnd", ET_Ignore, Param_Cell); + + if(g_bLateLoad) + { + for(new i = 1; i <= MaxClients; i++) + { + if(IsClientInGame(i) && !IsFakeClient(i)) + OnClientPutInServer(i); + } + } +} + +public OnClientPutInServer(client) +{ + if (!IsFakeClient(client)) + { + if(g_bIsCSGO) + DHookEntity(g_hCSGOVoice, true, client); + else + g_iHookID[client] = DHookRaw(g_hProcessVoice, true, GetIMsgHandler(client)); + + if(g_hClientMicTimers[client] != INVALID_HANDLE) + KillTimer(g_hClientMicTimers[client]); + + g_hClientMicTimers[client] = INVALID_HANDLE; + } +} + +public OnClientDisconnect(client) +{ + if(g_bIsCSGO) + { + if(g_iHookID[client] != -1) + { + DHookRemoveHookID(g_iHookID[client]); + g_iHookID[client] = -1; + } + } + + if(g_hClientMicTimers[client] != INVALID_HANDLE) + KillTimer(g_hClientMicTimers[client]); + + g_hClientMicTimers[client] = INVALID_HANDLE; +} + +public MRESReturn:Hook_ProcessVoiceData(Address:pThis, Handle:hParams) +{ + new Address:pIClient = pThis - Address:4; + new client = GetPlayerSlot(pIClient) + 1; + + if(g_hClientMicTimers[client] != INVALID_HANDLE) + { + KillTimer(g_hClientMicTimers[client]); + g_hClientMicTimers[client] = CreateTimer(0.3, Timer_ClientMicUsage, GetClientUserId(client)); + } + + if(g_hClientMicTimers[client] == INVALID_HANDLE) + g_hClientMicTimers[client] = CreateTimer(0.3, Timer_ClientMicUsage, GetClientUserId(client)); + + new bool:returnBool = true; + Call_StartForward(g_hOnClientTalking); + Call_PushCell(client); + Call_Finish(_:returnBool); + + if(!returnBool) + return MRES_Supercede; + + return MRES_Ignored; +} + +public MRESReturn:CSGOVoicePost(client, Handle:hReturn) +{ + if(g_hClientMicTimers[client] != INVALID_HANDLE) + { + KillTimer(g_hClientMicTimers[client]); + g_hClientMicTimers[client] = CreateTimer(0.3, Timer_ClientMicUsage, GetClientUserId(client)); + } + + if(g_hClientMicTimers[client] == INVALID_HANDLE) + g_hClientMicTimers[client] = CreateTimer(0.3, Timer_ClientMicUsage, GetClientUserId(client)); + + + new bool:returnBool = true; + Call_StartForward(g_hOnClientTalking); + Call_PushCell(client); + Call_Finish(_:returnBool); + + if(!returnBool) + return MRES_Supercede; + + return MRES_Ignored; +} + +public Action:Timer_ClientMicUsage(Handle:timer, any:userid) +{ + new client = GetClientOfUserId(userid); + g_hClientMicTimers[client] = INVALID_HANDLE; + + Call_StartForward(g_hOnClientTalkingEnd); + Call_PushCell(client); + Call_Finish(); +} + +/* +* Internal Functions +* Credits go to GoD-Tony +*/ +stock Handle:GetConfig() +{ + static Handle:hGameConf = INVALID_HANDLE; + + if(hGameConf == INVALID_HANDLE) + hGameConf = LoadGameConfigFile("voiceannounce_ex.games"); + + return hGameConf; +} + +stock Address:GetBaseServer() +{ + static Address:pBaseServer = Address_Null; + + if(pBaseServer == Address_Null) + pBaseServer = GameConfGetAddress(GetConfig(), "CBaseServer"); + + return pBaseServer; +} + +stock Address:GetIClient(slot) +{ + static Handle:hGetClient = INVALID_HANDLE; + + if(hGetClient == INVALID_HANDLE) + { + StartPrepSDKCall(SDKCall_Raw); + PrepSDKCall_SetFromConf(GetConfig(), SDKConf_Virtual, "CBaseServer::GetClient"); + PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); + PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); + hGetClient = EndPrepSDKCall(); + } + + return Address:SDKCall(hGetClient, GetBaseServer(), slot); +} + +stock GetPlayerSlot(Address:pIClient) +{ + static Handle:hPlayerSlot = INVALID_HANDLE; + + if(hPlayerSlot == INVALID_HANDLE) + { + StartPrepSDKCall(SDKCall_Raw); + PrepSDKCall_SetFromConf(GetConfig(), SDKConf_Virtual, "CBaseClient::GetPlayerSlot"); + PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); + hPlayerSlot = EndPrepSDKCall(); + } + + return SDKCall(hPlayerSlot, pIClient); +} + +stock Address:GetIMsgHandler(client) +{ + return GetIClient(client - 1) + Address:4; +} diff --git a/zr_repeatkill/scripting/include/zombiereloaded.inc b/zr_repeatkill/scripting/include/zombiereloaded.inc new file mode 120000 index 00000000..c9a376e8 --- /dev/null +++ b/zr_repeatkill/scripting/include/zombiereloaded.inc @@ -0,0 +1 @@ +../../../includes/zombiereloaded.inc \ No newline at end of file diff --git a/zr_repeatkill/scripting/zr_repeatkill.sp b/zr_repeatkill/scripting/zr_repeatkill.sp new file mode 100644 index 00000000..efb339fb --- /dev/null +++ b/zr_repeatkill/scripting/zr_repeatkill.sp @@ -0,0 +1,90 @@ +#pragma semicolon 1 + +#include +#include + +#define PLUGIN_NAME "ZR Repeat Kill Detector" +#define PLUGIN_VERSION "1.0.3" + +new Handle:g_hCvar_RepeatKillDetectThreshold = INVALID_HANDLE; +new Float:g_fRepeatKillDetectThreshold; + +new Handle:g_hRespawnDelay = INVALID_HANDLE; +new Float:g_fDeathTime[MAXPLAYERS+1]; +new bool:g_bBlockRespawn = false; + +public Plugin:myinfo = +{ + name = PLUGIN_NAME, + author = "GoD-Tony + BotoX", + description = "Disables respawning on maps with repeat killers", + version = PLUGIN_VERSION, + url = "http://www.sourcemod.net/" +}; + +public OnAllPluginsLoaded() +{ + if((g_hRespawnDelay = FindConVar("zr_respawn_delay")) == INVALID_HANDLE) + SetFailState("Failed to find zr_respawn_delay cvar."); + + g_hCvar_RepeatKillDetectThreshold = CreateConVar("zr_repeatkill_threshold", "1.0", "Zombie Reloaded Repeat Kill Detector Threshold", 0, true, 0.0, true, 10.0); + g_fRepeatKillDetectThreshold = GetConVarFloat(g_hCvar_RepeatKillDetectThreshold); + HookConVarChange(g_hCvar_RepeatKillDetectThreshold, OnConVarChanged); + + CreateConVar("zr_repeatkill_version", PLUGIN_VERSION, PLUGIN_NAME, FCVAR_NOTIFY|FCVAR_DONTRECORD); + HookEvent("round_start", Event_RoundStart, EventHookMode_PostNoCopy); + HookEvent("player_death", Event_PlayerDeath, EventHookMode_Post); + + AutoExecConfig(true, "plugin.RepeatKillDetector"); +} + +public OnConVarChanged(Handle:cvar, const String:oldVal[], const String:newVal[]) +{ + if(cvar == g_hCvar_RepeatKillDetectThreshold) + { + g_fRepeatKillDetectThreshold = GetConVarFloat(g_hCvar_RepeatKillDetectThreshold); + } +} + +public OnClientDisconnect(client) +{ + g_fDeathTime[client] = 0.0; +} + +public Event_RoundStart(Handle:event, const String:name[], bool:dontBroadcast) +{ + g_bBlockRespawn = false; +} + +public Event_PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast) +{ + if(g_bBlockRespawn) + return; + + decl String:weapon[32]; + GetEventString(event, "weapon", weapon, sizeof(weapon)); + + new victim = GetClientOfUserId(GetEventInt(event, "userid")); + new attacker = GetClientOfUserId(GetEventInt(event, "attacker")); + + if(victim && !attacker && StrEqual(weapon, "trigger_hurt")) + { + new Float:fGameTime = GetGameTime(); + + if(fGameTime - g_fDeathTime[victim] - GetConVarFloat(g_hRespawnDelay) < g_fRepeatKillDetectThreshold) + { + PrintToChatAll("\x04[ZR]\x01 Repeat killer detected. Disabling respawn for this round."); + g_bBlockRespawn = true; + } + + g_fDeathTime[victim] = fGameTime; + } +} + +public Action:ZR_OnClientRespawn(&client, &ZR_RespawnCondition:condition) +{ + if(g_bBlockRespawn) + return Plugin_Handled; + + return Plugin_Continue; +}