diff --git a/addons/sourcemod/plugins/optional/caster_assister.smx b/addons/sourcemod/plugins/optional/caster_assister.smx index 4ff651e0c..a00b81f6f 100644 Binary files a/addons/sourcemod/plugins/optional/caster_assister.smx and b/addons/sourcemod/plugins/optional/caster_assister.smx differ diff --git a/addons/sourcemod/plugins/optional/caster_system.smx b/addons/sourcemod/plugins/optional/caster_system.smx index c1740ac3b..09dfec393 100644 Binary files a/addons/sourcemod/plugins/optional/caster_system.smx and b/addons/sourcemod/plugins/optional/caster_system.smx differ diff --git a/addons/sourcemod/plugins/optional/readyup.smx b/addons/sourcemod/plugins/optional/readyup.smx index b75e89d23..946148c0d 100644 Binary files a/addons/sourcemod/plugins/optional/readyup.smx and b/addons/sourcemod/plugins/optional/readyup.smx differ diff --git a/addons/sourcemod/plugins/optional/specrates.smx b/addons/sourcemod/plugins/optional/specrates.smx index 7dab49c57..e27797fed 100644 Binary files a/addons/sourcemod/plugins/optional/specrates.smx and b/addons/sourcemod/plugins/optional/specrates.smx differ diff --git a/addons/sourcemod/scripting/caster_assister.sp b/addons/sourcemod/scripting/caster_assister.sp index ee6d72df9..944700af2 100644 --- a/addons/sourcemod/scripting/caster_assister.sp +++ b/addons/sourcemod/scripting/caster_assister.sp @@ -3,11 +3,13 @@ #include #include + #undef REQUIRE_PLUGIN #include -#define MAX_SPEED 2 +#define REQUIRE_PLUGIN -bool readyUpIsAvailable; +#define MAX_SPEED 2 +bool g_cvCasterSystem; public Plugin myinfo = { @@ -35,14 +37,14 @@ public void OnPluginStart() public void OnAllPluginsLoaded() { - readyUpIsAvailable = LibraryExists("caster_system"); + g_cvCasterSystem = LibraryExists("caster_system"); } public void OnLibraryRemoved(const char[] name) { if (StrEqual(name, "caster_system")) { - readyUpIsAvailable = false; + g_cvCasterSystem = false; } } @@ -50,13 +52,13 @@ public void OnLibraryAdded(const char[] name) { if (StrEqual(name, "caster_system")) { - readyUpIsAvailable = true; + g_cvCasterSystem = true; } } public void OnClientPutInServer(int client) { - if (readyUpIsAvailable && IsClientCaster(client)) + if (g_cvCasterSystem && bCaster(kClient, kGet, client)) { FakeClientCommand(client, "sm_spechud"); } diff --git a/addons/sourcemod/scripting/caster_system.sp b/addons/sourcemod/scripting/caster_system.sp index 3ea195095..a66fa6c9b 100644 --- a/addons/sourcemod/scripting/caster_system.sp +++ b/addons/sourcemod/scripting/caster_system.sp @@ -1,510 +1,2206 @@ +#pragma semicolon 1 +#pragma newdecls required + #include #include #include #include +#include -#pragma semicolon 1 -#pragma newdecls required +#define DEBUG_SQL 0 +#define DEBUG_VALUE 0 +#define DEBUG_API 0 + +#define PLUGIN_VERSION "2.0" +#define STEAMID2_LENGTH 32 + +enum eTypeID +{ + kClient = 0, + kAuth = 1, +} + +enum eTypeAction +{ + kGet = 0, + kSet = 1, + kRem = 2 +} + +enum eTypeList +{ + kCaster = 0, + kWhite = 1, + kSQL = 2 +} + +StringMap + g_smCaster, + g_smWhitelist, + g_smSpecInmunity; + +ConVar + g_cvAddonsEnable, + g_cvKickSpecInmunity, + g_cvSefRegEnable, + g_cvSQLEnable, + g_cvSQLServerID, + g_cvWhitelistEnable; + +bool + g_bSQLConnected, + g_bSQLTableExists; + +enum eSQLDriver +{ + kMySQL = 0, + kSQLite = 1, +} + +Database + g_hDatabase; + +eSQLDriver + g_iSQLDriver; + +int + g_iDummy; + +char + g_szTable[] = "caster_whitelist"; + +GlobalForward + g_gfOnCaster, + g_gfOffCaster; + +public Plugin g_myInfo = { + name = "L4D2 Caster System", + author = "CanadaRox, Forgetest, lechuga", + description = "Standalone caster handler.", + version = PLUGIN_VERSION, + url = "https://github.com/SirPlease/L4D2-Competitive-Rework" +}; + +public APLRes AskPluginLoad2(Handle hMyself, bool bLate, char[] szError, int iErrMax) +{ + g_gfOnCaster = CreateGlobalForward("OnCaster", ET_Ignore, Param_Cell, Param_Cell, Param_String); + g_gfOffCaster = CreateGlobalForward("OffCaster", ET_Ignore, Param_Cell, Param_Cell, Param_String); + + CreateNative("bCaster", iCasterNative); + CreateNative("bCasterWhitelist", iWhitelistNative); + CreateNative("bKickSpecInmunity", iInmunityNative); + + RegPluginLibrary("caster_system"); + return APLRes_Success; +} + +public void OnPluginStart() +{ + vLoadTranslation("common.phrases"); + vLoadTranslation("caster_system.phrases"); + + g_smCaster = new StringMap(); + g_smWhitelist = new StringMap(); + g_smSpecInmunity = new StringMap(); + + CreateConVar("caster_version", PLUGIN_VERSION, "Caster System Version", FCVAR_NOTIFY | FCVAR_DONTRECORD); + g_cvWhitelistEnable = CreateConVar("caster_whitelist", "1", "Enable Whitelist, if deactivated, everyone will be able to register as a caster", FCVAR_NOTIFY, true, 0.0, true, 1.0); + g_cvSefRegEnable = CreateConVar("caster_selfreg", "1", "Enables self-registration, it is limited to the user being registered on the whitelist.", FCVAR_NOTIFY, true, 0.0, true, 1.0); + g_cvKickSpecInmunity = CreateConVar("caster_kickspecs_inmunity", "1", "Enable Kick Spec Inmunity", FCVAR_NOTIFY, true, 0.0, true, 1.0); + g_cvAddonsEnable = CreateConVar("caster_addons", "1", "Enable caster addons", FCVAR_NOTIFY, true, 0.0, true, 1.0); + g_cvSQLEnable = CreateConVar("caster_sql", "0", "Enable Whitelist SQL", FCVAR_NOTIFY, true, 0.0, true, 1.0); + g_cvSQLServerID = CreateConVar("caster_sql_serverid", "0", "Server ID, if it is set to 0 it will be disabled", FCVAR_NOTIFY, true, 0.0); + + g_cvSQLEnable.AddChangeHook(vOnSQLSettingChanged); + g_cvAddonsEnable.AddChangeHook(vOnAddonsSettingChanged); + + RegAdminCmd("sm_caster", aCasterRegCmd, ADMFLAG_BAN, "Registers a player to the caster list"); + RegAdminCmd("sm_caster_ls", aCasterListCmd, ADMFLAG_BAN, "Prints the list of casters"); + RegAdminCmd("sm_caster_rm", aCasterRemoveCmd, ADMFLAG_BAN, "Removes a player from the caster list"); + RegAdminCmd("sm_caster_rs", aCasterResetCmd, ADMFLAG_BAN, "Clears the entire caster list"); + + RegAdminCmd("sm_caster_wl", aWhitelistRegCmd, ADMFLAG_BAN, "Adds a player to the whitelist"); + RegAdminCmd("sm_caster_wl_ls", aWhitelistListCmd, ADMFLAG_BAN, "Prints the whitelist"); + RegAdminCmd("sm_caster_wl_rm", aWhitelistRemoveCmd, ADMFLAG_BAN, "Removes a player from the whitelist"); + RegAdminCmd("sm_caster_wl_rs", aWhitelistResetCmd, ADMFLAG_BAN, "Clears the entire whitelist"); + + RegAdminCmd("sm_caster_sql", aSQLRegCmd, ADMFLAG_BAN, "Adds a player to the database whitelist"); + RegAdminCmd("sm_caster_sql_ls", aSQLListCmd, ADMFLAG_BAN, "Downloads and prints the database whitelist"); + RegAdminCmd("sm_caster_sql_rm", aSQLRemoveCmd, ADMFLAG_BAN, "Removes a player from the database whitelist"); + RegAdminCmd("sm_caster_sql_rs", aSQLResetCmd, ADMFLAG_BAN, "Clears the entire database whitelist"); + RegAdminCmd("sm_caster_sql_cache", aSQLCacheCmd, ADMFLAG_BAN, "Downloads the database whitelist"); + + RegConsoleCmd("sm_cast", aSelfRegCastCmd, "Registers the calling player as a caster"); + RegConsoleCmd("sm_uncast", aSelfRemoveCastCmd, "Deregister yourself as a caster or allow admins to deregister other players"); + RegConsoleCmd("sm_kickspecs", aKickSpecsCmd, "Let's vote to kick those Spectators!"); + + HookEvent("player_team", vPlayerTeamEvent); + + AutoExecConfig(true, "caster_system"); +} + +// ======================== +// Natives +// ======================== + +/** + * @brief Add, checks or remove a user from the Casters list. + * + * @param eTypeID Defines how the client will be identified. + * @param eTypeAction What action will be taken. + * @param iClient Required only if eTypeID is kClient, otherwise set it to -1. + * @param szAuthId Required only if eTypeID is kAuth, otherwise it is not necessary to define it. + * @return True if the client or AuthID is a caster (for Get action), false otherwise. + */ +int iCasterNative(Handle hPlugin, int iNumParams) +{ + eTypeID eID = GetNativeCell(1); + eTypeAction eAction = GetNativeCell(2); + int iTarget; + + char + szAuthId[STEAMID2_LENGTH], + szName[32]; + + switch (eID) + { + case kClient: + { + iTarget = GetNativeCell(3); + GetClientAuthId(iTarget, AuthId_Steam2, szAuthId, sizeof(szAuthId)); + GetClientName(iTarget, szName, sizeof(szName)); + } + case kAuth: + { + iTarget = NO_INDEX; + GetNativeString(4, szAuthId, sizeof(szAuthId)); + strcopy(szName, sizeof(szName), szAuthId); + } + } + +#if DEBUG_API + LogMessage("[iCasterNative] eTypeID: %d | eTypeAction: %d | iClient: %d | szAuthId: %s", eID, eAction, iTarget, szAuthId); +#endif + + bool bIsCaster = g_smCaster.GetValue(szAuthId, g_iDummy); + switch (eAction) + { + case kGet: + return bIsCaster; + case kSet: + { + if (bIsCaster) + return 0; + + return g_smCaster.SetValue(szAuthId, true); + } + case kRem: + { + if (!bIsCaster) + return 0; + + return g_smCaster.Remove(szAuthId); + } + } + return 0; +} + +/** + * @brief Add, checks or remove a user from the Casters whitelist. + * + * @param eTypeID Defines how the client will be identified. + * @param eTypeAction What action will be taken. + * @param iClient Required only if eTypeID is kClient, otherwise set it to -1. + * @param szAuthId Required only if eTypeID is kAuth, otherwise it is not necessary to define it. + * @return True if the action was successful, false otherwise. + */ +int iWhitelistNative(Handle hPlugin, int iNumParams) +{ + eTypeID eID = GetNativeCell(1); + eTypeAction eAction = GetNativeCell(2); + int iTarget; + + char + szAuthId[STEAMID2_LENGTH], + szName[32]; + + switch (eID) + { + case kClient: + { + iTarget = GetNativeCell(3); + GetClientAuthId(iTarget, AuthId_Steam2, szAuthId, sizeof(szAuthId)); + GetClientName(iTarget, szName, sizeof(szName)); + } + case kAuth: + { + iTarget = NO_INDEX; + GetNativeString(4, szAuthId, sizeof(szAuthId)); + strcopy(szName, sizeof(szName), szAuthId); + } + } + +#if DEBUG_API + LogMessage("[iWhitelistNative] eTypeID: %d | eTypeAction: %d | iClient: %d | szAuthId: %s", eID, eAction, iTarget, szAuthId); +#endif + + bool bIsWhitelist = g_smWhitelist.GetValue(szAuthId, g_iDummy); + switch (eAction) + { + case kGet: + return bIsWhitelist; + case kSet: + { + if (bIsWhitelist) + return 0; + + return g_smWhitelist.SetValue(szAuthId, true); + } + case kRem: + { + if (!bIsWhitelist) + return 0; + + return g_smWhitelist.Remove(szAuthId); + } + } + return 0; +} + +/** + * @brief Add, checks or remove a user from the spectator immunity list. + * + * @param eTypeID Defines how the client will be identified. + * @param eTypeAction What action will be taken. + * @param iClient Required only if eTypeID is kClient, otherwise set it to -1. + * @param szAuthId Required only if eTypeID is kAuth, otherwise it is not necessary to define it. + * @return True if the client has spectator immunity, false otherwise. + */ +int iInmunityNative(Handle hPlugin, int iNumParams) +{ + if (!g_cvKickSpecInmunity.BoolValue) + return 0; + + eTypeID eID = GetNativeCell(1); + eTypeAction eAction = GetNativeCell(2); + int iTarget; + char szAuthId[STEAMID2_LENGTH]; + bool bInmunity; + + switch (eID) + { + case kClient: + { + iTarget = GetNativeCell(3); + GetClientAuthId(iTarget, AuthId_Steam2, szAuthId, sizeof(szAuthId)); + bInmunity = bSpecInmunity(kClient, iTarget); + } + case kAuth: + { + GetNativeString(4, szAuthId, sizeof(szAuthId)); + bInmunity = bSpecInmunity(kAuth, NO_INDEX, szAuthId); + } + } + +#if DEBUG_API + LogMessage("[iInmunityNative] eTypeID: %d | eTypeAction: %d | iClient: %d | szAuthId: %s", eID, eAction, iTarget, szAuthId); +#endif + + switch (eAction) + { + case kGet: + return bInmunity; + case kSet: + { + if (bInmunity) + return 0; + + return g_smSpecInmunity.SetValue(szAuthId, true); + } + case kRem: + { + if (!bInmunity) + return 0; + + return g_smSpecInmunity.Remove(szAuthId); + } + } + return 0; +} + +// ======================== +// Caster Addons +// ======================== + +void vOnSQLSettingChanged(ConVar cvar, const char[] szOldValue, const char[] szNewValue) +{ + if (g_cvSQLEnable.BoolValue) + { + if (g_hDatabase != null) + delete g_hDatabase; + + OnConfigsExecuted(); + } + else + { + if (g_hDatabase == null) + return; + + delete g_hDatabase; + } +} + +void vOnAddonsSettingChanged(ConVar cvar, const char[] szOldValue, const char[] szNewValue) +{ + bool bDisable = (StringToInt(szNewValue) != 0); + bool bPrevious = (StringToInt(szOldValue) != 0); + + if (bDisable == bPrevious) + return; + + ArrayList hCastersList = (bDisable) ? new ArrayList() : null; + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsClientInGame(i)) + continue; + + if (!bCaster(kClient, i)) + continue; + + if (bDisable) + { + CPrintToChat(i, "%t %t", "Prefix", "ForbidAddons"); + CPrintToChat(i, "%t %t", "Prefix", "Reconnect"); + hCastersList.Push(GetClientUserId(i)); + } + else + { + CPrintToChat(i, "%t %t", "Prefix", "AllowAddons"); + CPrintToChat(i, "%t %t", "Prefix", "CasterReconnect"); + } + } + + if (bDisable) + { + if (hCastersList.Length > 0) + CreateTimer(3.0, aReconnectCastersTimer, hCastersList, TIMER_FLAG_NO_MAPCHANGE | TIMER_DATA_HNDL_CLOSE); + else + delete hCastersList; + } +} + +Action aReconnectCastersTimer(Handle hTimer, ArrayList aCasterList) +{ + int iSize = aCasterList.Length; + for (int i = 0; i < iSize; i++) + { + int iClient = GetClientOfUserId(aCasterList.Get(i)); + if (iClient > SERVER_INDEX) + ReconnectClient(iClient); + } + + return Plugin_Stop; +} + +public Action L4D2_OnClientDisableAddons(const char[] szAuthId) +{ + return (g_cvAddonsEnable.BoolValue && bCaster(kAuth, SERVER_INDEX, szAuthId)) ? Plugin_Handled : Plugin_Continue; +} + +void vPlayerTeamEvent(Event event, const char[] szName, bool bDontBroadcast) +{ + if (view_as(event.GetInt("team")) == L4DTeam_Spectator) + return; + + int iUserId = event.GetInt("userid"); + CreateTimer(1.0, aCasterCheck, iUserId, TIMER_FLAG_NO_MAPCHANGE); +} + +Action aCasterCheck(Handle hTimer, int iUserId) +{ + int iClient = GetClientOfUserId(iUserId); + if (!iClient || !IsClientInGame(iClient)) + return Plugin_Stop; + + if (!bCaster(kClient, iClient)) + return Plugin_Stop; + + if (L4D_GetClientTeam(iClient) == L4DTeam_Spectator) + return Plugin_Stop; + + CPrintToChat(iClient, "%t %t", "Prefix", "CasterPlay"); + CPrintToChat(iClient, "%t %t", "Prefix", "UseNoCast"); + ChangeClientTeam(iClient, view_as(L4DTeam_Spectator)); + + return Plugin_Stop; +} + +// ======================== +// Caster +// ======================== + +Action aCasterRegCmd(int iClient, int iArgs) +{ + ReplySource eRsCmd = GetCmdReplySource(); + if (iArgs == 0) + { + if (eRsCmd == SM_REPLY_TO_CHAT && iClient != SERVER_INDEX) + vDisplayRegMenu(iClient, kCaster); + else + CReplyToCommand(iClient, "%t %t: sm_caster <#userid|name|steamid>", "Prefix", "Use"); + return Plugin_Handled; + } + + char szArguments[64]; + GetCmdArgString(szArguments, sizeof(szArguments)); + + char szArg[STEAMID2_LENGTH]; + BreakString(szArguments, szArg, sizeof(szArg)); + + vProcessReg(iClient, szArg, kCaster, eRsCmd); + + return Plugin_Handled; +} + +void vProcessReg(int iClient, const char[] szArg, eTypeList eList, ReplySource eRsCmd) +{ + if (bIsSteamId(szArg)) + { + vRegister(iClient, NO_INDEX, szArg, szArg, eList, kAuth, eRsCmd); + return; + } + + int iTarget = FindTarget(iClient, szArg, true, false); + if (iTarget == NO_INDEX) + return; + + char szAuthId[STEAMID2_LENGTH]; + if (!GetClientAuthId(iTarget, AuthId_Steam2, szAuthId, sizeof(szAuthId))) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "AuthIdError", szAuthId); + return; + } + + char szName[16]; + GetClientName(iTarget, szName, sizeof(szName)); + vRegister(iClient, iTarget, szAuthId, szName, eList, kClient, eRsCmd); +} + +/** + * Registers a client as a caster or whitelist member. + * + * @param iClient The client index of the player issuing the command. + * @param iTarget The client index of the target player. + * @param szAuthId The authentication ID of the target player. + * @param szDisplayName The display name of the target player. + * @param eList The type of list to register the player. + * @param eID The type of identification to use for the target player. + * @param eRsCmd The reply source for the command. + */ +void vRegister(int iClient, int iTarget, const char[] szAuthId, const char[] szDisplayName, eTypeList eList, eTypeID eID, ReplySource eRsCmd) +{ +#if DEBUG_VALUE + LogMessage("[vRegister] iClient: %d | iTarget: %d | szAuthId: %s | szDisplayName: %s | eTypeList: %d | eTypeID %d | eRsCmd: %d", iClient, iTarget, szAuthId, szDisplayName, eList, eID, eRsCmd); +#endif + + char + szRegMsg[128], + szRegFromMsg[128]; + + switch (eList) + { + case kCaster: + { + if (g_smCaster.GetValue(szAuthId, g_iDummy)) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "CasterFound", szDisplayName); + return; + } + Format(szRegMsg, sizeof(szRegMsg), "%T", "CasterReg", iClient, szDisplayName); + if (eID == kClient) + Format(szRegFromMsg, sizeof(szRegFromMsg), "%T", "CasterRegFrom", iTarget, iClient); + } + case kWhite: + { + if (g_smWhitelist.GetValue(szAuthId, g_iDummy)) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "WhitelistFound", szDisplayName); + return; + } + Format(szRegMsg, sizeof(szRegMsg), "%T", "WhitelistReg", iClient, szDisplayName); + if (eID == kClient) + Format(szRegFromMsg, sizeof(szRegFromMsg), "%T", "WhitelistRegFrom", iTarget, iClient); + } + case kSQL: + { + char szQuery[256]; + g_hDatabase.Format(szQuery, sizeof(szQuery), "SELECT authid FROM `%s` WHERE authid = '%s'", g_szTable, szAuthId); + +#if DEBUG_SQL + LogMessage("[vRegister] Query: %s", szQuery); +#endif + int + iUserId, + iTargetUserId; + + if (iClient != SERVER_INDEX) + iUserId = GetClientUserId(iClient); + + if (iTarget != NO_INDEX) + iTargetUserId = GetClientUserId(iTarget); + + DataPack pDataPack = new DataPack(); + pDataPack.WriteCell(iUserId); + pDataPack.WriteCell(iTargetUserId); + pDataPack.WriteString(szAuthId); + pDataPack.WriteCell(eID); + pDataPack.WriteCell(eRsCmd); + + SQL_TQuery(g_hDatabase, vSQLRegCallback, szQuery, pDataPack); + return; + } + } + + switch (eList) + { + case kCaster: + { + if (!g_smCaster.SetValue(szAuthId, true)) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "CasterRegError", szAuthId); + return; + } + +#if DEBUG_API + LogMessage("[forward OnCaster] eTypeID: %d | iClient: %d | szAuthId: %s", eID, iTarget, szAuthId); +#endif + + Call_StartForward(g_gfOnCaster); + Call_PushCell(eID); + Call_PushCell(iTarget); + Call_PushString(szAuthId); + Call_Finish(); + } + case kWhite: + { + if (!g_smWhitelist.SetValue(szAuthId, true)) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "WhitelistRegError", szAuthId); + return; + } + } + } + + CReplyToCommand(iClient, "%t %s", "Prefix", szRegMsg); + + if (eID == kAuth) + return; + + if ((L4D_GetClientTeam(iTarget) != L4DTeam_Spectator) && (eList == kCaster)) + ChangeClientTeam(iTarget, view_as(L4DTeam_Spectator)); + + CPrintToChat(iTarget, "%t %s", "Prefix", szRegFromMsg); + + if (eList == kCaster) + CPrintToChat(iTarget, "%t %t", "Prefix", "CasterReconnect"); +} + +void vDisplayRegMenu(int iClient, eTypeList eList) +{ + char szTitle[100]; + Format(szTitle, sizeof(szTitle), "%t", "MenuPlayersList"); + Menu hMenu = new Menu(iRegMenuHandler); + hMenu.SetTitle(szTitle); + vListTargets(hMenu, eList); + + hMenu.Display(iClient, MENU_TIME_FOREVER); +} + +/** + * Populates a menu with a list of targets based on the specified list type. + * + * @param hMenu The menu handle to which the targets will be added. + * @param eTypeList The type of list to determine the target selection criteria. + * + * The function iterates through all connected clients and adds them to the menu. + * It skips fake clients and clients that cannot be identified by name or Steam ID. + * Depending on the list type, it checks if the client is in the caster or whitelist. + * If the client is found in the respective list, the menu item is added as disabled. + * Otherwise, the menu item is added as enabled. + */ +void vListTargets(Menu hMenu, eTypeList eList) +{ + char + szName[64], + szInfo[16], + szAuthId[STEAMID2_LENGTH]; + + bool + bFound; + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsClientConnected(i) || IsFakeClient(i)) + continue; + + if (!GetClientName(i, szName, sizeof(szName))) + continue; + + if (!GetClientAuthId(i, AuthId_Steam2, szAuthId, sizeof(szAuthId))) + continue; + + Format(szInfo, sizeof(szInfo), "%d:%d", GetClientUserId(i), view_as(eList)); + + switch (eList) + { + case kCaster: + bFound = g_smCaster.GetValue(szAuthId, g_iDummy); + case kWhite, kSQL: + bFound = g_smWhitelist.GetValue(szAuthId, g_iDummy); + } + + if (bFound) + hMenu.AddItem(szInfo, szName, ITEMDRAW_DISABLED); + else + hMenu.AddItem(szInfo, szName); + } +} + +public int iRegMenuHandler(Menu hMenu, MenuAction eAction, int iClient, int iItem) +{ + switch (eAction) + { + case MenuAction_Select: + { + char + szInfo[32], + szName[32]; + + int + iUserId, + iTarget; + + eTypeList eList; + + hMenu.GetItem(iItem, szInfo, sizeof(szInfo), _, szName, sizeof(szName)); + + char szParts[2][8]; + ExplodeString(szInfo, ":", szParts, 2, 4); + + iUserId = StringToInt(szParts[0]); + eList = view_as(StringToInt(szParts[1])); + + if ((iTarget = GetClientOfUserId(iUserId)) == SERVER_INDEX) + CPrintToChat(iClient, "%t %t", "Prefix", "Player no longer available"); + else + { + char szAuthId[STEAMID2_LENGTH]; + if (!GetClientAuthId(iTarget, AuthId_Steam2, szAuthId, sizeof(szAuthId))) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "AuthIdError", szAuthId); + return 0; + } + + vRegister(iClient, iTarget, szAuthId, szName, eList, kClient, SM_REPLY_TO_CHAT); + } + } + case MenuAction_End: + delete hMenu; + } + return 0; +} + +Action aCasterListCmd(int iClient, int iArgs) +{ + return aListCmd(iClient, kCaster); +} + +Action aListCmd(int iClient, eTypeList type) +{ + PrintListPrinted(iClient); + + switch (type) + { + case kCaster: + { + StringMapSnapshot hSnapshot = g_smCaster.Snapshot(); + PrintSnapshotList(iClient, hSnapshot, "/***********[Casters]***********\\", ">* Total Casters: %i"); + } + case kWhite: + { + StringMapSnapshot hSnapshot = g_smWhitelist.Snapshot(); + PrintSnapshotList(iClient, hSnapshot, "/***********[Whitelist]***********\\", ">* Total Whitelist: %i"); + } + case kSQL: + { + if (!g_cvSQLEnable.BoolValue) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLDisabled"); + return Plugin_Handled; + } + if (!g_bSQLConnected) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLNoConnect"); + return Plugin_Handled; + } + + char szQuery[256]; + g_hDatabase.Format(szQuery, sizeof(szQuery), "SELECT authid, serverid FROM `%s`", g_szTable); + +#if DEBUG_SQL + LogMessage("[aListCmd] Query: %s", szQuery); +#endif + + DataPack pDataPack = new DataPack(); + pDataPack.WriteCell(iClient); + SQL_TQuery(g_hDatabase, vSQLListCallback, szQuery, pDataPack); + } + } + + return Plugin_Handled; +} + +void PrintSnapshotList(int iClient, StringMapSnapshot hSnapshot, const char[] sHeader, const char[] sTotalLabel) +{ + PrintToConsole(iClient, sHeader); + + char szAuthID[128]; + int + iLen = hSnapshot.Length, + iTarget; + for (int i = 0; i < iLen; i++) + { + hSnapshot.GetKey(i, szAuthID, sizeof(szAuthID)); + iTarget = GetClientOfAuthID(szAuthID); + + if (iTarget == NO_INDEX) + PrintToConsole(iClient, "AuthID: %s", szAuthID); + else + PrintToConsole(iClient, "AuthID: %s [%N]", szAuthID, iTarget); + } + PrintToConsole(iClient, sTotalLabel, iLen); + + delete hSnapshot; +} + +void PrintListPrinted(int iClient) +{ + if (GetCmdReplySource() == SM_REPLY_TO_CONSOLE || iClient == SERVER_INDEX) + return; + + CPrintToChat(iClient, "%t %t", "Prefix", "ListPrinted"); +} + +Action aCasterResetCmd(int iClient, int iArgs) +{ + g_smCaster.Clear(); + CReplyToCommand(iClient, "%t %t", "Prefix", "CasterReset"); + return Plugin_Handled; +} + +Action aCasterRemoveCmd(int iClient, int iArgs) +{ + ReplySource eRsCmd = GetCmdReplySource(); + if (iArgs == 0) + { + if (eRsCmd == SM_REPLY_TO_CHAT && iClient != SERVER_INDEX) + vDisplayRemoveMenu(iClient, kCaster); + else + CReplyToCommand(iClient, "%t %t: sm_caster_rm <#userid|name|steamid>", "Prefix", "Use"); + return Plugin_Handled; + } + + char szArguments[64]; + GetCmdArgString(szArguments, sizeof(szArguments)); + + char szArg[STEAMID2_LENGTH]; + BreakString(szArguments, szArg, sizeof(szArg)); + + vProcessRemove(iClient, szArg, kCaster, eRsCmd); + return Plugin_Handled; +} + +/** + * Processes the removal of a client from a specified list. + * + * @param iClient The client index initiating the removal. + * @param szArg The argument provided, which can be a Steam ID or a target name. + * @param eList The list type from which the client should be removed. + * @param eRsCmd The reply source for the command. + */ +void vProcessRemove(int iClient, const char[] szArg, eTypeList eList, ReplySource eRsCmd) +{ + if (bIsSteamId(szArg)) + { + vRemove(iClient, NO_INDEX, szArg, szArg, eList, kAuth, eRsCmd); + return; + } + + int iTarget = FindTarget(iClient, szArg, true, false); + if (iTarget == NO_INDEX) + return; + + char szAuthId[STEAMID2_LENGTH]; + if (!GetClientAuthId(iTarget, AuthId_Steam2, szAuthId, sizeof(szAuthId))) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "AuthIdError", szAuthId); + return; + } + + char szName[16]; + GetClientName(iTarget, szName, sizeof(szName)); + vRemove(iClient, iTarget, szAuthId, szName, eList, kClient, eRsCmd); +} + +/** + * Removes a client from a specified list and sends appropriate messages. + * + * @param iClient The client index who initiated the removal. + * @param iTarget The target client index to be removed. + * @param szAuthId The authentication ID of the target client. + * @param szDisplayName The display name of the target client. + * @param eList The list type from which the client is to be removed. + * @param eID The type of identification to use for the target client. + * @param eRsCmd The reply source for the command. + */ +void vRemove(int iClient, int iTarget, const char[] szAuthId, const char[] szDisplayName, eTypeList eList, eTypeID eID, ReplySource eRsCmd) +{ +#if DEBUG_VALUE + LogMessage("[vRegister] iClient: %d | iTarget: %d | szAuthId: %s | szDisplayName: %s | eTypeList: %d | eTypeID %d | eRsCmd: %d", iClient, iTarget, szAuthId, szDisplayName, eList, eID, eRsCmd); +#endif + + char + szRemoveMsg[128], + szRemoveFromMsg[128]; + + switch (eList) + { + case kCaster: + { + if (!g_smCaster.GetValue(szAuthId, g_iDummy)) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "CasterNoFound", szDisplayName); + return; + } + Format(szRemoveMsg, sizeof(szRemoveMsg), "%T", "CasterRemove", iClient, szDisplayName); + if (eID == kClient) + Format(szRemoveFromMsg, sizeof(szRemoveFromMsg), "%T", "CasterRemoveFrom", iTarget, iClient); + } + case kWhite: + { + if (!g_smWhitelist.GetValue(szAuthId, g_iDummy)) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "WhitelistNoFound", szDisplayName); + return; + } + Format(szRemoveMsg, sizeof(szRemoveMsg), "%T", "WhitelistRemove", iClient, szDisplayName); + if (eID == kClient) + Format(szRemoveFromMsg, sizeof(szRemoveFromMsg), "%T", "WhitelistRemoveFrom", iTarget, iClient); + } + case kSQL: + { + char szQuery[256]; + g_hDatabase.Format(szQuery, sizeof(szQuery), "SELECT authid FROM `%s` WHERE authid = '%s'", g_szTable, szAuthId); + +#if DEBUG_SQL + LogMessage("[vRemove] Query: %s", szQuery); +#endif + int + iUserId, + iTargetUserId; + + if (iClient != SERVER_INDEX) + iUserId = GetClientUserId(iClient); + + if (iTarget != NO_INDEX) + iTargetUserId = GetClientUserId(iTarget); + + DataPack pDataPack = new DataPack(); + pDataPack.WriteCell(iUserId); + pDataPack.WriteCell(iTargetUserId); + pDataPack.WriteString(szAuthId); + pDataPack.WriteCell(eID); + pDataPack.WriteCell(eRsCmd); + + SQL_TQuery(g_hDatabase, vSQLRemoveCallback, szQuery, pDataPack); + return; + } + } + + switch (eList) + { + case kCaster: + { + if (!g_smCaster.Remove(szAuthId)) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "CasterRemoveError", szAuthId); + return; + } + +#if DEBUG_API + LogMessage("[forward OffCaster] eTypeID: %d | iClient: %d | szAuthId: %s", eID, iTarget, szAuthId); +#endif + + Call_StartForward(g_gfOffCaster); + Call_PushCell(eID); + Call_PushCell(iTarget); + Call_PushString(szAuthId); + Call_Finish(); + + if (eID == kClient) + CreateTimer(3.0, aReconnectTimer, iTarget); + } + case kWhite: + { + if (!g_smWhitelist.Remove(szAuthId)) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "WhitelistRemoveError", szAuthId); + return; + } + } + } + + CReplyToCommand(iClient, "%t %s", "Prefix", szRemoveMsg); + + if (eID == kAuth) + return; + + CPrintToChat(iTarget, "%t %s", "Prefix", szRemoveFromMsg); +} + +void vDisplayRemoveMenu(int iClient, eTypeList eList) +{ + char szTitle[100]; + switch (eList) + { + case kCaster: + Format(szTitle, sizeof(szTitle), "%T", "MenuCastersList", iClient); + case kWhite, kSQL: + Format(szTitle, sizeof(szTitle), "%T", "MenuWhitelistList", iClient); + } + + Menu hMenu = new Menu(iMenuRemoveHandler); + hMenu.SetTitle(szTitle); + vRemoveTargets(hMenu, eList, iClient); + + hMenu.Display(iClient, MENU_TIME_FOREVER); +} + +/** + * Removes targets from the specified menu based on the given type list. + * + * @param hMenu The menu handle to which the targets will be added. + * @param eList The type list to determine which targets to remove. + * @param iClient The client index who initiated the removal. + * + * This function iterates through all connected clients, checks if they match + * the criteria specified by the type list, and adds them to the menu if they do. + * If no targets are found, a message indicating no targets to remove is added + * to the menu. + */ +void vRemoveTargets(Menu hMenu, eTypeList eList, int iClient) +{ + char + szName[64], + szInfo[16], + szAuthId[STEAMID2_LENGTH]; + + bool + bFound; + + int + iTargets = 0; + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsClientConnected(i) || IsFakeClient(i)) + continue; + + if (!GetClientName(i, szName, sizeof(szName))) + continue; + + if (!GetClientAuthId(i, AuthId_Steam2, szAuthId, sizeof(szAuthId))) + continue; + + Format(szInfo, sizeof(szInfo), "%d:%d", GetClientUserId(i), view_as(eList)); + + switch (eList) + { + case kCaster: + bFound = g_smCaster.GetValue(szAuthId, g_iDummy); + case kWhite, kSQL: + bFound = g_smWhitelist.GetValue(szAuthId, g_iDummy); + } + + if (bFound) + { + hMenu.AddItem(szInfo, szName); + iTargets++; + } + } + + if (iTargets == 0) + { + char szMsj[64]; + Format(szMsj, sizeof(szMsj), "%T", "NoTargetsToRemove", iClient); + hMenu.AddItem("", szMsj, ITEMDRAW_DISABLED); + } +} + +public int iMenuRemoveHandler(Menu hMenu, MenuAction eAction, int iClient, int iItem) +{ + if (eAction == MenuAction_Select) + { + char + szInfo[32], + szName[32]; + + int + iUserId, + iTarget; + + eTypeList eList; + + hMenu.GetItem(iItem, szInfo, sizeof(szInfo), _, szName, sizeof(szName)); + + char szParts[2][8]; + ExplodeString(szInfo, ":", szParts, 2, 4); + + iUserId = StringToInt(szParts[0]); + eList = view_as(StringToInt(szParts[1])); + + if ((iTarget = GetClientOfUserId(iUserId)) == SERVER_INDEX) + CPrintToChat(iClient, "%t %t", "Prefix", "Player no longer available"); + else + { + char szAuthId[STEAMID2_LENGTH]; + if (!GetClientAuthId(iTarget, AuthId_Steam2, szAuthId, sizeof(szAuthId))) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "AuthIdError", szAuthId); + return 0; + } + + vRemove(iClient, iTarget, szAuthId, szName, eList, kClient, SM_REPLY_TO_CHAT); + } + } + else if (eAction == MenuAction_End) + delete hMenu; + return 0; +} + +// ======================== +// Whitelist +// ======================== + +Action aWhitelistRegCmd(int iClient, int iArgs) +{ + ReplySource eRsCmd = GetCmdReplySource(); + if (iArgs == 0) + { + if (eRsCmd == SM_REPLY_TO_CHAT && iClient != SERVER_INDEX) + vDisplayRegMenu(iClient, kWhite); + else + CReplyToCommand(iClient, "%t %t: sm_caster_wl <#userid|name|steamid>", "Prefix", "Use"); + return Plugin_Handled; + } + + char szArguments[64]; + GetCmdArgString(szArguments, sizeof(szArguments)); + + char szArg[STEAMID2_LENGTH]; + BreakString(szArguments, szArg, sizeof(szArg)); + + vProcessReg(iClient, szArg, kWhite, eRsCmd); + + return Plugin_Handled; +} + +Action aWhitelistListCmd(int iClient, int iArgs) +{ + return aListCmd(iClient, kWhite); +} + +Action aWhitelistResetCmd(int iClient, int iArgs) +{ + g_smWhitelist.Clear(); + CReplyToCommand(iClient, "%t %t", "Prefix", "WhitelistReset"); + return Plugin_Handled; +} + +Action aWhitelistRemoveCmd(int iClient, int iArgs) +{ + ReplySource eRsCmd = GetCmdReplySource(); + if (iArgs == 0) + { + if (eRsCmd == SM_REPLY_TO_CHAT && iClient != SERVER_INDEX) + vDisplayRemoveMenu(iClient, kWhite); + else + CReplyToCommand(iClient, "%t %t: sm_caster_wl_rm <#userid|name|steamid>", "Prefix", "Use"); + return Plugin_Handled; + } + + char szArguments[64]; + GetCmdArgString(szArguments, sizeof(szArguments)); + + char szArg[STEAMID2_LENGTH]; + BreakString(szArguments, szArg, sizeof(szArg)); + + vProcessRemove(iClient, szArg, kWhite, eRsCmd); + return Plugin_Handled; +} + +// ======================== +// SQL +// ======================== + +Action aSQLRegCmd(int iClient, int iArgs) +{ + if (!g_cvSQLEnable.BoolValue) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLDisabled"); + return Plugin_Handled; + } + + if (!g_bSQLConnected) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLNoConnect"); + return Plugin_Handled; + } + + ReplySource eRsCmd = GetCmdReplySource(); + if (iArgs == 0) + { + if (eRsCmd == SM_REPLY_TO_CHAT && iClient != SERVER_INDEX) + vDisplayRegMenu(iClient, kSQL); + else + CReplyToCommand(iClient, "%t %t: sm_caster_sql <#userid|name|steamid>", "Prefix", "Use"); + return Plugin_Handled; + } + + char szArguments[64]; + GetCmdArgString(szArguments, sizeof(szArguments)); + + char szArg[STEAMID2_LENGTH]; + BreakString(szArguments, szArg, sizeof(szArg)); + + vProcessReg(iClient, szArg, kSQL, eRsCmd); + + return Plugin_Handled; +} + +void vSQLRegCallback(Handle hDatabase, Handle hResult, const char[] szError, any data) +{ + DataPack pDataPack = view_as(data); + char szAuthId[STEAMID2_LENGTH]; + + int + iClient, + iTarget, + iUserId, + iTargetUserId; + + pDataPack.Reset(); + iUserId = pDataPack.ReadCell(), + iTargetUserId = pDataPack.ReadCell(); + pDataPack.ReadString(szAuthId, sizeof(szAuthId)); + eTypeID eID = pDataPack.ReadCell(); + ReplySource eRsCmd = pDataPack.ReadCell(); + delete pDataPack; + + if (iUserId != SERVER_INDEX) + iClient = GetClientOfUserId(iUserId); + + if (iTargetUserId != NO_INDEX) + iTarget = GetClientOfUserId(iTargetUserId); + + SetCmdReplySource(eRsCmd); + if (hResult == null) + { + LogError("[vSQLRegCallback] %s", "SQLError", LANG_SERVER, szError); + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLError"); + return; + } + + if (SQL_FetchRow(hResult)) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLFound", szAuthId); + delete hResult; + return; + } + delete hResult; + + char szQuery[256]; + g_hDatabase.Format(szQuery, sizeof(szQuery), "INSERT INTO `%s` (authid, serverid) VALUES ('%s', %d)", g_szTable, szAuthId, g_cvSQLServerID.IntValue); + +#if DEBUG_SQL + LogMessage("[vSQLRegCallback] Query: %s", szQuery); +#endif + + if (!SQL_FastQuery(g_hDatabase, szQuery)) + { + LogError("[vSQLRegCallback] %s", "SQLError", LANG_SERVER, szError); + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLError"); + return; + } + + switch (eID) + { + case kClient: + { + char szName[16]; + GetClientName(iTarget, szName, sizeof(szName)); + + if (!g_smWhitelist.SetValue(szAuthId, 1)) + CReplyToCommand(iClient, "%t %t", "Prefix", "WhitelistRegError", szName); + else + { + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLReg", szName); + CReplyToCommand(iTarget, "%t %t", "Prefix", "SQLRegFrom", iClient); + } + } + case kAuth: + { + if (!g_smWhitelist.SetValue(szAuthId, 1)) + CReplyToCommand(iClient, "%t %t", "Prefix", "WhitelistRegError", szAuthId); + else + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLReg", szAuthId); + } + } +} + +Action aSQLListCmd(int iClient, int iArgs) +{ + return aListCmd(iClient, kSQL); +} + +void vSQLListCallback(Handle hDatabase, Handle hResult, const char[] szError, any data) +{ + DataPack pDataPack = view_as(data); + pDataPack.Reset(); + int iClient = pDataPack.ReadCell(); + delete pDataPack; + + if (hResult == null) + { + char szErrorMsg[128]; + Format(szErrorMsg, sizeof(szErrorMsg), "%T: %s", "SQLError", LANG_SERVER, szError); + + CPrintToChat(iClient, "%t %t", "Prefix", "SQLError"); + CRemoveTags(szErrorMsg, sizeof(szErrorMsg)); + LogError("[vSQLListCallback] %s", szErrorMsg); + return; + } + + PrintToConsole(iClient, "/***********[Whitelist SQL]***********/"); + int iCount = 0; + + while (SQL_FetchRow(hResult)) + { + char szAuthId[STEAMID2_LENGTH]; + SQL_FetchString(hResult, 0, szAuthId, sizeof(szAuthId)); + int iServerId = SQL_FetchInt(hResult, 1); + PrintToConsole(iClient, "AuthID: %s | ServerID: %d", szAuthId, iServerId); + iCount++; + } + + PrintToConsole(iClient, ">* Total Casters: %d", iCount); + + delete hResult; +} + +Action aSQLRemoveCmd(int iClient, int iArgs) +{ + if (!g_cvSQLEnable.BoolValue) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLDisabled"); + return Plugin_Handled; + } + + if (!g_bSQLConnected) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLNoConnect"); + return Plugin_Handled; + } + + ReplySource eRsCmd = GetCmdReplySource(); + if (iArgs == 0) + { + if (eRsCmd == SM_REPLY_TO_CHAT && iClient != SERVER_INDEX) + vDisplayRemoveMenu(iClient, kSQL); + else + CReplyToCommand(iClient, "%t %t: sm_caster_sql_rm <#userid|name|steamid>", "Prefix", "Use"); + return Plugin_Handled; + } + + char szArguments[64]; + GetCmdArgString(szArguments, sizeof(szArguments)); + + char szArg[STEAMID2_LENGTH]; + BreakString(szArguments, szArg, sizeof(szArg)); + + vProcessRemove(iClient, szArg, kSQL, eRsCmd); + + return Plugin_Handled; +} + +public void vSQLRemoveCallback(Handle hDatabase, Handle hResult, const char[] szError, any data) +{ + DataPack pDataPack = view_as(data); + char szAuthId[STEAMID2_LENGTH]; + + int + iClient, + iTarget, + iUserId, + iTargetUserId; + + pDataPack.Reset(); + iUserId = pDataPack.ReadCell(), + iTargetUserId = pDataPack.ReadCell(); + pDataPack.ReadString(szAuthId, sizeof(szAuthId)); + eTypeID eID = pDataPack.ReadCell(); + ReplySource eRsCmd = pDataPack.ReadCell(); + delete pDataPack; + + if (iUserId != SERVER_INDEX) + iClient = GetClientOfUserId(iUserId); + + if (iTargetUserId != NO_INDEX) + iTarget = GetClientOfUserId(iTargetUserId); + + SetCmdReplySource(eRsCmd); + if (hResult == null) + { + LogError("[vSQLRemoveCallback] %s", "SQLError", LANG_SERVER, szError); + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLError"); + return; + } + + if (!SQL_FetchRow(hResult)) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLNoFound", szAuthId); + delete hResult; + return; + } + + delete hResult; + + char szQuery[256]; + g_hDatabase.Format(szQuery, sizeof(szQuery), "DELETE FROM `%s` WHERE authid = '%s'", g_szTable, szAuthId); + +#if DEBUG_SQL + LogMessage("[vSQLRemoveCallback] Query: %s", szQuery); +#endif + + if (!SQL_FastQuery(g_hDatabase, szQuery)) + { + LogError("[vSQLRemoveCallback] %s", "SQLError", LANG_SERVER, szError); + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLError"); + return; + } + + switch (eID) + { + case kClient: + { + char szName[16]; + GetClientName(iTarget, szName, sizeof(szName)); + + if (!g_smWhitelist.Remove(szAuthId)) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "WhitelistRemoveError", szName); + return; + } + + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLRemoved", szName); + CReplyToCommand(iTarget, "%t %t", "Prefix", "SQLRemovedFrom", iClient); + } + case kAuth: + { + if (!g_smWhitelist.Remove(szAuthId)) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "WhitelistRemoveError", szAuthId); + return; + } + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLRemoved", szAuthId); + } + } +} + +Action aSQLResetCmd(int iClient, int iArgs) +{ + if (!g_cvSQLEnable.BoolValue) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLDisabled"); + return Plugin_Handled; + } + + if (!g_bSQLConnected) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLNoConnect"); + return Plugin_Handled; + } + + char szQuery[64]; + g_hDatabase.Format(szQuery, sizeof(szQuery), "DELETE FROM `%s`", g_szTable); + +#if DEBUG_SQL + LogMessage("[aSQLResetCmd] Query: %s", szQuery); +#endif + + if (!SQL_FastQuery(g_hDatabase, szQuery)) + { + logErrorSQL(g_hDatabase, szQuery, "aSQLResetCmd"); + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLQueryError"); + return Plugin_Handled; + } + + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLResetSuccess"); + return Plugin_Handled; +} + +Action aSQLCacheCmd(int iClient, int iArgs) +{ + if (!g_cvSQLEnable.BoolValue) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLDisabled"); + return Plugin_Handled; + } + + if (!g_bSQLConnected) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLNoConnect"); + return Plugin_Handled; + } + + DataPack pDataPack = new DataPack(); + pDataPack.WriteCell(GetClientUserId(iClient)); + + vQueryWhitelist(pDataPack); + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLCacheSuccess"); + return Plugin_Handled; +} + +// ======================== +// Self +// ======================== + +Action aSelfRegCastCmd(int iClient, int iArgs) +{ + bool bIsAdmin = (GetUserAdmin(iClient) != INVALID_ADMIN_ID); + if (iArgs != 0) + { + if (!bIsAdmin) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "SelfRegNoAdmin"); + return Plugin_Handled; + } + + char szArguments[256]; + GetCmdArgString(szArguments, sizeof(szArguments)); + FakeClientCommandEx(iClient, "sm_caster %s", szArguments); + return Plugin_Handled; + } + + if (iClient == SERVER_INDEX) + { + CReplyToCommand(iClient, "%t %t: sm_cast <#userid|name|steamid>", "Prefix", "Use"); + return Plugin_Handled; + } + + if (!g_cvSefRegEnable.BoolValue && !bIsAdmin) + { + CPrintToChat(iClient, "%t %t", "Prefix", "SelfRegDisabled"); + return Plugin_Handled; + } + + char szAuthId[STEAMID2_LENGTH]; + GetClientAuthId(iClient, AuthId_Steam2, szAuthId, sizeof(szAuthId)); -#define PLUGIN_VERSION "1.0" + if (g_cvWhitelistEnable) + { + if (g_smWhitelist.Size == 0 && !bIsAdmin) + { + CPrintToChat(iClient, "%t %t", "Prefix", "WhitelistEmpty"); + return Plugin_Handled; + } -public Plugin myinfo = -{ - name = "L4D2 Caster System (Original built in readyup)", - author = "CanadaRox, Forgetest", - description = "Standalone caster handler.", - version = PLUGIN_VERSION, - url = "https://github.com/Target5150/MoYu_Server_Stupid_Plugins" -}; + if (!g_smWhitelist.GetValue(szAuthId, g_iDummy) && !bIsAdmin) + { + CPrintToChat(iClient, "%t %t", "Prefix", "SelfRegWhitelistNotFound"); + return Plugin_Handled; + } + } -#define L4D2Team_Spectator 1 + if (g_smCaster.GetValue(szAuthId, g_iDummy)) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "SelfRegCasterFound"); + return Plugin_Handled; + } -#define TRANSLATION_COMMON "common.phrases" -#define TRANSLATION_CASTER "caster_system.phrases" + if (!g_smCaster.SetValue(szAuthId, true)) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "CasterRegError", szAuthId); + return Plugin_Handled; + } -// Caster System -StringMap casterTrie; -StringMap allowedCastersTrie; -bool forbidSelfRegister; + if (L4D_GetClientTeam(iClient) != L4DTeam_Spectator) + ChangeClientTeam(iClient, view_as(L4DTeam_Spectator)); -ConVar g_hDisableAddons; +#if DEBUG_API + LogMessage("[forward OnCaster] eTypeID: %d | iClient: %d | szAuthId: %s", kClient, iClient, szAuthId); +#endif -public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) -{ - CreateNative("IsClientCaster", Native_IsClientCaster); - CreateNative("IsIDCaster", Native_IsIDCaster); + Call_StartForward(g_gfOnCaster); + Call_PushCell(kClient); + Call_PushCell(iClient); + Call_PushString(szAuthId); + Call_Finish(); - RegPluginLibrary("caster_system"); - return APLRes_Success; + CPrintToChat(iClient, "%t %t", "Prefix", "SelfRegSuccess"); + CPrintToChat(iClient, "%t %t", "Prefix", "CasterReconnect"); + return Plugin_Handled; } -public void OnPluginStart() +Action aSelfRemoveCastCmd(int iClient, int iArgs) { - LoadPluginTranslation(); - - casterTrie = new StringMap(); - allowedCastersTrie = new StringMap(); - - g_hDisableAddons = CreateConVar("caster_disable_addons", "0", "Whether to disallow addons on casters", FCVAR_NOTIFY, true, 0.0, true, 1.0); - g_hDisableAddons.AddChangeHook(OnAddonsSettingChanged); - - // Caster Registration - RegAdminCmd("sm_caster", Caster_Cmd, ADMFLAG_BAN, "Registers a player as a caster"); - RegAdminCmd("sm_resetcasters", ResetCaster_Cmd, ADMFLAG_BAN, "Used to reset casters between matches. This should be in confogl_off.cfg or equivalent for your system"); - RegAdminCmd("sm_add_caster_id", AddCasterSteamID_Cmd, ADMFLAG_BAN, "Used for adding casters to the whitelist -- i.e. who's allowed to self-register as a caster"); - RegAdminCmd("sm_remove_caster_id", RemoveCasterSteamID_Cmd, ADMFLAG_BAN, "Used for removing casters to the whitelist -- i.e. who's allowed to self-register as a caster"); - RegAdminCmd("sm_printcasters", PrintCasters_Cmd, ADMFLAG_BAN, "Used for print casters in the whitelist"); - RegConsoleCmd("sm_cast", Cast_Cmd, "Registers the calling player as a caster"); - RegConsoleCmd("sm_notcasting", NotCasting_Cmd, "Deregister yourself as a caster or allow admins to deregister other players"); - RegConsoleCmd("sm_uncast", NotCasting_Cmd, "Deregister yourself as a caster or allow admins to deregister other players"); - - // Kick Specs - RegConsoleCmd("sm_kickspecs", KickSpecs_Cmd, "Let's vote to kick those Spectators!"); - - HookEvent("player_team", PlayerTeam_Event); -} + if (iArgs == 0) + { + if (iClient == SERVER_INDEX) + { + CReplyToCommand(iClient, "%t %t: sm_uncast <#userid|name|steamid>", "Prefix", "Use"); + return Plugin_Handled; + } -void LoadPluginTranslation() -{ - char sPath[PLATFORM_MAX_PATH]; - - BuildPath(Path_SM, sPath, sizeof sPath, "translations/" ... TRANSLATION_COMMON ... ".txt"); - if (!FileExists(sPath)) + char szAuthId[STEAMID2_LENGTH]; + GetClientAuthId(iClient, AuthId_Steam2, szAuthId, sizeof(szAuthId)); + + char szName[16]; + GetClientName(iClient, szName, sizeof(szName)); + + if (!g_smCaster.GetValue(szAuthId, g_iDummy)) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "CasterNoFound", szName); + return Plugin_Handled; + } + + CPrintToChat(iClient, "%t %t", "Prefix", "Reconnect"); + g_smCaster.Remove(szAuthId); + +#if DEBUG_API + LogMessage("[forward OffCaster] eTypeID: %d | iClient: %d | szAuthId: %s", kClient, iClient, szAuthId); +#endif + + Call_StartForward(g_gfOffCaster); + Call_PushCell(kClient); + Call_PushCell(iClient); + Call_PushString(szAuthId); + Call_Finish(); + + CreateTimer(3.0, aReconnectTimer, iClient); + return Plugin_Handled; + } + + if (g_smCaster.Size == 0) { - SetFailState("Missing translation file \"" ... TRANSLATION_COMMON ... ".txt\""); + CPrintToChat(iClient, "%t %t", "Prefix", "CasterEmpty"); + return Plugin_Handled; } - LoadTranslations(TRANSLATION_COMMON); - - BuildPath(Path_SM, sPath, sizeof sPath, "translations/" ... TRANSLATION_CASTER ... ".txt"); - if (!FileExists(sPath)) + + AdminId aAdminId = GetUserAdmin(iClient); + if (aAdminId == INVALID_ADMIN_ID || !GetAdminFlag(aAdminId, Admin_Ban)) // Check for specific admin flag { - SetFailState("Missing translation file \"" ... TRANSLATION_CASTER ... ".txt\""); + CReplyToCommand(iClient, "%t %t", "Prefix", "UnRegCasterNonAdmin"); + return Plugin_Handled; } - LoadTranslations(TRANSLATION_CASTER); + + char szArguments[256]; + GetCmdArgString(szArguments, sizeof(szArguments)); + FakeClientCommandEx(iClient, "sm_caster_rm %s", szArguments); + return Plugin_Handled; } +Action aReconnectTimer(Handle timer, int client) +{ + if (IsClientConnected(client)) + ReconnectClient(client); + return Plugin_Stop; +} // ======================== -// Natives +// SQL // ======================== - -int Native_IsClientCaster(Handle plugin, int numParams) +public void OnConfigsExecuted() { - int client = GetNativeCell(1); - return IsClientCaster(client); -} + if (!g_cvSQLEnable.BoolValue) + return; -int Native_IsIDCaster(Handle plugin, int numParams) -{ - char buffer[64]; - GetNativeString(1, buffer, sizeof(buffer)); - return IsIDCaster(buffer); + vConnectDB("castersystem", g_szTable); + + DataPack pDataPack = new DataPack(); + pDataPack.WriteCell(SERVER_INDEX); + pDataPack.WriteCell(SM_REPLY_TO_CONSOLE); + + vQueryWhitelist(pDataPack); } -bool IsClientCaster(int client) +public void OnPluginEnd() { - char buffer[64]; - return GetClientAuthId(client, AuthId_Steam2, buffer, sizeof(buffer)) && IsIDCaster(buffer); + if (!g_cvSQLEnable.BoolValue) + return; + + if (g_hDatabase == null) + return; + + delete g_hDatabase; } -bool IsIDCaster(const char[] AuthID) +void vQueryWhitelist(DataPack pDataPack) { - bool dummy; - return GetTrieValue(casterTrie, AuthID, dummy); -} + if (g_hDatabase == null) + return; + char szQuery[64]; + g_hDatabase.Format(szQuery, sizeof(szQuery), "SELECT * FROM `%s`", g_szTable); +#if DEBUG_SQL + LogMessage("[vQueryWhitelist] Query: %s", szQuery); +#endif -// ======================== -// Caster Addons -// ======================== + SQL_TQuery(g_hDatabase, vSQLCallback, szQuery, pDataPack); +} -void OnAddonsSettingChanged(ConVar convar, const char[] oldValue, const char[] newValue) +public void vSQLCallback(Handle hDatabase, Handle hResult, const char[] szError, any data) { - bool disable = !!StringToInt(newValue); - bool previous = !!StringToInt(oldValue); - - if (disable == previous) return; - - if (disable) + DataPack pDataPack = view_as(data); + pDataPack.Reset(); + int iUserId = pDataPack.ReadCell(); + int iClient = GetClientOfUserId(iUserId); + delete pDataPack; + + if (hResult == null) { - ArrayList hCastersList = new ArrayList(); - for (int i = 1; i <= MaxClients; i++) - { - if (IsClientInGame(i) && IsClientCaster(i)) - { - CPrintToChat(i, "%t", "ForbidAddons"); - CPrintToChat(i, "%t", "Reconnect1"); - CPrintToChat(i, "%t", "Reconnect2"); - hCastersList.Push(GetClientUserId(i)); - } - } - - if (!hCastersList.Length) - { - delete hCastersList; - return; - } - - // Reconnection to disable their addons - CreateTimer(3.0, Timer_ReconnectCasters, hCastersList, TIMER_FLAG_NO_MAPCHANGE | TIMER_DATA_HNDL_CLOSE); + char szErrorMsg[128]; + Format(szErrorMsg, sizeof(szErrorMsg), "%T: %s", "SQLError", LANG_SERVER, szError); + + if (iClient == SERVER_INDEX) + CReplyToCommand(iClient, "%t %t", "Prefix", "SQLError"); + else + CPrintToChat(iClient, "%t %t", "Prefix", "SQLError"); + + CRemoveTags(szErrorMsg, sizeof(szErrorMsg)); + LogError("[vSQLCallback] %s", szErrorMsg); + return; } - else + + g_smWhitelist.Clear(); + while (SQL_FetchRow(hResult)) { - for (int i = 1; i <= MaxClients; i++) + char szAuthId[STEAMID2_LENGTH]; + SQL_FetchString(hResult, 1, szAuthId, sizeof(szAuthId)); + + if (!g_smWhitelist.SetValue(szAuthId, 1)) { - if (IsClientInGame(i) && IsClientCaster(i)) - { - CPrintToChat(i, "%t", "AllowAddons"); - CPrintToChat(i, "%t", "SelfCast2"); - } + if (iClient == SERVER_INDEX) + CReplyToCommand(iClient, "%t %t", "Prefix", "WhitelistRegError", szAuthId); + else + CPrintToChat(iClient, "%t %t", "Prefix", "WhitelistRegError", szAuthId); } } -} - -Action Timer_ReconnectCasters(Handle timer, ArrayList aList) -{ - int size = aList.Length; - for (int i = 0; i < size; i++) - { - int client = GetClientOfUserId(aList.Get(i)); - if (client > 0) ReconnectClient(client); - } - return Plugin_Stop; + delete hResult; } -public Action L4D2_OnClientDisableAddons(const char[] SteamID) -{ - return (!g_hDisableAddons.BoolValue && IsIDCaster(SteamID)) ? Plugin_Handled : Plugin_Continue; -} +// ======================== +// Kick Specs +// ======================== -void PlayerTeam_Event(Event event, const char[] name, bool dontBroadcast) +Action aKickSpecsCmd(int iClient, int iArgs) { - if (event.GetInt("team") != L4D2Team_Spectator) + AdminId aAdminId = GetUserAdmin(iClient); + if (aAdminId != INVALID_ADMIN_ID && GetAdminFlag(aAdminId, Admin_Ban)) { - int userid = event.GetInt("userid"); - CreateTimer(1.0, CasterCheck, userid, TIMER_FLAG_NO_MAPCHANGE); + CreateTimer(2.0, aTimerKickSpecs); + CPrintToChatAll("%t %t", "Prefix", "KickSpecsAdmin", iClient); + return Plugin_Handled; } -} -Action CasterCheck(Handle timer, int userid) -{ - int client = GetClientOfUserId(userid); - if (client && IsClientInGame(client) && GetClientTeam(client) != L4D2Team_Spectator && IsClientCaster(client)) + if (L4D_GetClientTeam(iClient) == L4DTeam_Spectator) { - CPrintToChat(client, "%t", "CasterCheck1"); - CPrintToChat(client, "%t", "CasterCheck2"); - ChangeClientTeam(client, L4D2Team_Spectator); + CPrintToChat(iClient, "%t %t", "Prefix", "KickSpecsVoteSpec"); + return Plugin_Handled; } - return Plugin_Stop; + vStartKickSpecsVote(iClient); + return Plugin_Handled; } // ======================== -// Caster Registration +// Vote // ======================== -Action Cast_Cmd(int client, int args) +void vStartKickSpecsVote(int iClient) { - if (!client) return Plugin_Continue; - - char buffer[64]; - GetClientAuthId(client, AuthId_Steam2, buffer, sizeof(buffer)); - - bool temp; - if (forbidSelfRegister) + if (IsBuiltinVoteInProgress()) { - if (!allowedCastersTrie.GetValue(buffer, temp)) - { - CPrintToChat(client, "%t", "SelfCastNotAllowed"); - return Plugin_Handled; - } + CPrintToChat(iClient, "%t %t", "Prefix", "VoteInProgress"); + return; } - - if (!casterTrie.GetValue(buffer, temp)) + if (CheckBuiltinVoteDelay() > 0) { - if (GetClientTeam(client) != L4D2Team_Spectator) - { - ChangeClientTeam(client, L4D2Team_Spectator); - } - casterTrie.SetValue(buffer, true); - CPrintToChat(client, "%t", "SelfCast1"); - CPrintToChat(client, "%t", "SelfCast2"); + CPrintToChat(iClient, "%t %t", "Prefix", "VoteDelay", CheckBuiltinVoteDelay()); + return; } - - return Plugin_Handled; -} -Action Caster_Cmd(int client, int args) -{ - if (args < 1) + Handle hVote = CreateBuiltinVote(vVoteActionHandler, BuiltinVoteType_Custom_YesNo, + BuiltinVoteAction_Cancel | BuiltinVoteAction_VoteEnd | BuiltinVoteAction_End); + + char szBuffer[128]; + FormatEx(szBuffer, sizeof(szBuffer), "%T", "KickSpecsVoteTitle", LANG_SERVER); + SetBuiltinVoteArgument(hVote, szBuffer); + SetBuiltinVoteInitiator(hVote, iClient); + SetBuiltinVoteResultCallback(hVote, vKickSpecsVoteResultHandler); + + int iTotal = 0; + int[] aiPlayers = new int[MaxClients]; + for (int i = 1; i <= MaxClients; i++) { - ReplyToCommand(client, "[SM] Usage: sm_caster "); - return Plugin_Handled; + if (!IsClientInGame(i) || IsFakeClient(i) || L4D_GetClientTeam(iClient) == L4DTeam_Spectator) + continue; + aiPlayers[iTotal++] = i; } - - char buffer[64]; - GetCmdArg(1, buffer, sizeof(buffer)); - - int target = FindTarget(client, buffer, true, false); - if (target > 0) // If FindTarget fails we don't need to print anything as it prints it for us! + DisplayBuiltinVote(hVote, aiPlayers, iTotal, FindConVar("sv_vote_timer_duration").IntValue); + + FakeClientCommand(iClient, "Vote Yes"); +} + +void vVoteActionHandler(Handle hVote, BuiltinVoteAction eAction, int iParam1, int iParam2) +{ + switch (eAction) { - if (GetClientAuthId(target, AuthId_Steam2, buffer, sizeof(buffer))) + case BuiltinVoteAction_End: { - casterTrie.SetValue(buffer, true); - ReplyToCommand(client, "\x01%t", "RegCasterReply", target); - CPrintToChat(target, "%t", "RegCasterTarget", client); - CPrintToChat(target, "%t", "SelfCast2"); + CloseHandle(hVote); } - else + case BuiltinVoteAction_Cancel: { - ReplyToCommand(client, "\x01%t", "CasterSteamIDError"); + DisplayBuiltinVoteFail(hVote, BuiltinVoteFail_Generic); } } - - return Plugin_Handled; } -Action NotCasting_Cmd(int client, int args) +void vKickSpecsVoteResultHandler(Handle hVote, int iNumVotes, int iNumClients, const int[][] aiClientInfo, int iNumItems, const int[][] aiItemInfo) { - char buffer[64]; - - if (args < 1) // If no target is specified, assumes self-uncasting - { - GetClientAuthId(client, AuthId_Steam2, buffer, sizeof(buffer)); - if (casterTrie.Remove(buffer)) - { - CPrintToChat(client, "%t", "Reconnect1"); - CPrintToChat(client, "%t", "Reconnect2"); - - // Reconnection to disable their addons - CreateTimer(3.0, Reconnect, client); - } - } - else // If a target is specified + for (int i = 0; i < iNumItems; i++) { - AdminId id = GetUserAdmin(client); - if (id == INVALID_ADMIN_ID || !GetAdminFlag(id, Admin_Ban)) // Check for specific admin flag - { - ReplyToCommand(client, "\x01%t", "UnregCasterNonAdmin"); - return Plugin_Handled; - } - - GetCmdArg(1, buffer, sizeof(buffer)); - - int target = FindTarget(client, buffer, true, true); - if (target > 0) // If FindTarget fails we don't need to print anything as it prints it for us! + if (aiItemInfo[i][BUILTINVOTEINFO_ITEM_INDEX] == BUILTINVOTES_VOTE_YES) { - if (GetClientAuthId(target, AuthId_Steam2, buffer, sizeof(buffer))) - { - if (casterTrie.Remove(buffer)) - { - CPrintToChat(target, "%t", "UnregCasterTarget", client); - NotCasting_Cmd(target, 0); - } - ReplyToCommand(client, "\x01%t", "UnregCasterSuccess", target); - } - else + if (aiItemInfo[i][BUILTINVOTEINFO_ITEM_VOTES] > (iNumClients / 2)) { - ReplyToCommand(client, "\x01%t", "CasterSteamIDError"); + char szBuffer[64]; + FormatEx(szBuffer, sizeof(szBuffer), "%T", "KickSpecsVoteSuccess", LANG_SERVER); + DisplayBuiltinVotePass(hVote, szBuffer); + + float fDelay = FindConVar("sv_vote_command_delay").FloatValue; + CreateTimer(fDelay, aTimerKickSpecs); + return; } } } - return Plugin_Handled; + + DisplayBuiltinVoteFail(hVote, BuiltinVoteFail_Loses); } -Action Reconnect(Handle timer, int client) +Action aTimerKickSpecs(Handle hTimer) { - if (IsClientConnected(client)) ReconnectClient(client); + for (int i = 1; i <= MaxClients; i++) + { + if (!IsClientInGame(i) || IsFakeClient(i)) + continue; + if (L4D_GetClientTeam(i) != L4DTeam_Spectator) + continue; + if (bCaster(kClient, i)) + continue; + if (GetUserAdmin(i) != INVALID_ADMIN_ID) + continue; + if (bSpecInmunity(kClient, i)) + continue; + KickClient(i, "%t", "KickSpecsReason"); + } return Plugin_Stop; } -Action ResetCaster_Cmd(int client, int args) +/** + * Check if the translation file exists + * + * @param szTranslation Translation name. + * @noreturn + */ +stock void vLoadTranslation(const char[] szTranslation) { - casterTrie.Clear(); - forbidSelfRegister = false; - ReplyToCommand(client, "\x01%t", "CasterDBReset"); - return Plugin_Handled; + char szPath[PLATFORM_MAX_PATH], + szName[64]; + + Format(szName, sizeof(szName), "translations/%s.txt", szTranslation); + BuildPath(Path_SM, szPath, sizeof(szPath), szName); + if (!FileExists(szPath)) + SetFailState("Missing translation file %s.txt", szTranslation); + + LoadTranslations(szTranslation); } -Action AddCasterSteamID_Cmd(int client, int args) +/** + * Checks if a client or an AuthID is a caster. + * + * @param etType The type of identifier being used (kClient or kAuth). + * @param iClient The client index (optional, default is 0). + * @param szAuthId The AuthID string (optional, default is an empty string). + * @return True if the client or AuthID is a caster, false otherwise. + */ +bool bCaster(eTypeID eID, int iClient = 0, const char[] szAuthId = "") { - char buffer[128]; - GetCmdArg(1, buffer, sizeof(buffer)); - if (buffer[0] != EOS) + switch (eID) { - forbidSelfRegister = true; - if (allowedCastersTrie.SetValue(buffer, 1, false)) + case kClient: + { + char szClientAuthId[STEAMID2_LENGTH]; + GetClientAuthId(iClient, AuthId_Steam2, szClientAuthId, sizeof(szClientAuthId)); + return g_smCaster.GetValue(szClientAuthId, g_iDummy); + } + case kAuth: { - ReplyToCommand(client, "\x01%t", "CasterDBAdd", buffer); + return g_smCaster.GetValue(szAuthId, g_iDummy); } - else ReplyToCommand(client, "\x01%t", "CasterDBFound", buffer); } - else ReplyToCommand(client, "\x01%t", "CasterDBError"); - return Plugin_Handled; + return false; } -Action RemoveCasterSteamID_Cmd(int client, int args) +/** + * Checks if a client or an AuthID has spectator immunity. + * + * @param eTypeID The type of identifier being used (kClient or kAuth). + * @param iClient The client index (only used if eTypeID is kClient). + * @param szAuthId The AuthID string (only used if eTypeID is kAuth). + * @return True if the client or AuthID has spectator immunity, false otherwise. + */ +bool bSpecInmunity(eTypeID eID, int iClient = 0, const char[] szAuthId = "") { - char buffer[128]; - GetCmdArg(1, buffer, sizeof(buffer)); - if (buffer[0] != EOS) + switch (eID) { - int dummy; - if (allowedCastersTrie.GetValue(buffer, dummy)) + case kClient: + { + char szClientAuthId[STEAMID2_LENGTH]; + GetClientAuthId(iClient, AuthId_Steam2, szClientAuthId, sizeof(szClientAuthId)); + return g_smSpecInmunity.GetValue(szClientAuthId, g_iDummy); + } + case kAuth: { - allowedCastersTrie.Remove(buffer); - if (allowedCastersTrie.Size == 0) forbidSelfRegister = false; - ReplyToCommand(client, "\x01%t", "CasterDBRemove", buffer); + return g_smSpecInmunity.GetValue(szAuthId, g_iDummy); } - else ReplyToCommand(client, "\x01%t", "CasterDBFound", buffer); } - else ReplyToCommand(client, "\x01%t", "CasterDBError"); - return Plugin_Handled; + return false; } -Action PrintCasters_Cmd(int client, int args) +/** + * @brief Checks if a given string is a valid Steam ID. + * + * This function verifies if the provided string follows the format of a Steam ID. + * A valid Steam ID should start with "STEAM_" and contain two colons separating + * three numerical components. + * + * @param szAuthId The string to be checked. + * @return True if the string is a valid Steam ID, false otherwise. + */ +bool bIsSteamId(const char[] szAuthId) { - StringMapSnapshot ss = allowedCastersTrie.Snapshot(); - char buffer[128]; - - if (GetCmdReplySource() == SM_REPLY_TO_CHAT) + if (strlen(szAuthId) == 0) + return false; + + if (StrContains(szAuthId, "STEAM_") != 0) + return false; + + int iPos1 = FindCharInString(szAuthId, ':'); + if (iPos1 == NO_INDEX) + return false; + + int iPos2 = FindCharInString(szAuthId, ':', view_as(iPos1 + 1)); + if (iPos2 == NO_INDEX) + return false; + + char szUniverse[8]; + char szAuth[8]; + char szAccount[16]; + + int iLenUniverse = iPos1 - 6; + if (iLenUniverse <= 0 || iLenUniverse >= sizeof(szUniverse)) + return false; + + for (int i = 0; i < iLenUniverse; i++) { - if (client > 0) PrintToChat(client, "[casters_database] List is printed in console"); + szUniverse[i] = szAuthId[6 + i]; } - - PrintToConsole(client, "/***********[casters_database]***********\\"); - - int len = ss.Length; - for (int i = 0; i < len; i++) + szUniverse[iLenUniverse] = '\0'; + + int iLenAuth = iPos2 - iPos1 - 1; + if (iLenAuth <= 0 || iLenAuth >= sizeof(szAuth)) + return false; + + for (int i = 0; i < iLenAuth; i++) { - ss.GetKey(i, buffer, sizeof buffer); - PrintToConsole(client, "Caster #%i: %s", i+1, buffer); + szAuth[i] = szAuthId[iPos1 + 1 + i]; } - PrintToConsole(client, ">* Total Casters: %i", len); - - delete ss; - return Plugin_Handled; -} + szAuth[iLenAuth] = '\0'; + + int iLenAccount = strlen(szAuthId) - iPos2 - 1; + if (iLenAccount <= 0 || iLenAccount >= sizeof(szAccount)) + return false; + for (int i = 0; i < iLenAccount; i++) + { + szAccount[i] = szAuthId[iPos2 + 1 + i]; + } + szAccount[iLenAccount] = '\0'; + if (!bIsInteger(szUniverse) || !bIsInteger(szAuth) || !bIsInteger(szAccount)) + return false; -// ======================== -// Kick Specs -// ======================== + return true; +} -Action KickSpecs_Cmd(int client, int args) +/** + * @brief Checks if the given string represents an integer. + * + * This function iterates through each character of the input string and + * verifies if all characters are numeric. + * + * @param szString The string to be checked. + * @return True if the string represents an integer, false otherwise. + */ +bool bIsInteger(const char[] szString) { - AdminId id = GetUserAdmin(client); - if (id != INVALID_ADMIN_ID && GetAdminFlag(id, Admin_Ban)) // Check for specific admin flag + int iLen = strlen(szString); + for (int i = 0; i < iLen; i++) { - CreateTimer(2.0, Timer_KickSpecs); - CPrintToChatAll("%t", "KickSpecsAdmin", client); - return Plugin_Handled; + if (!IsCharNumeric(szString[i])) + return false; } - - // Filter spectator - if (GetClientTeam(client) == L4D2Team_Spectator) + return true; +} + +/** + * Connects to the database using the specified configuration name. + * + * @param szConfigName The name of the database configuration to use for the connection. + * + * This function checks if the specified database configuration exists. If it does not, + * it logs an error message and sets the global variable `g_bSQLConnected` to false. + * If the configuration exists, it attempts to connect to the database using the provided + * configuration name and calls the `vConnectCallback` function upon completion. + */ +stock void vConnectDB(char[] szConfigName, char[] szTable = "") +{ + if (!SQL_CheckConfig(szConfigName)) { - CPrintToChat(client, "%t", "KickSpecsVoteSpec"); - return Plugin_Handled; + g_bSQLConnected = false; + return; } - - StartKickSpecsVote(client); - return Plugin_Handled; -} -// ======================== -// Vote -// ======================== + if (szTable[0] == '\0') + { + Database.Connect(vConnectCallback, szConfigName); + return; + } + + DataPack pDataPack = new DataPack(); + pDataPack.WriteString(szTable); + Database.Connect(vConnectCallback, szConfigName, pDataPack); +} -void StartKickSpecsVote(int client) +/** + * Callback function for handling database connection. + * + * @param hDatabase The database connection object. + * @param szError The error message if the connection failed. + * @param pData Additional data passed to the callback. + * + * This function is called when a connection to the database is attempted. + * It logs the success or failure of the connection, sets the database charset to UTF-8, + * and determines the SQL driver being used. It also checks if table exists. + */ +stock void vConnectCallback(Database hDatabase, const char[] szError, any pData) { - if (IsBuiltinVoteInProgress()) + if (hDatabase == null) { - CPrintToChat(client, "%t", "VoteInProgress"); + g_bSQLConnected = false; return; } - if (CheckBuiltinVoteDelay() > 0) + + if (szError[0] != '\0') { - CPrintToChat(client, "%t", "VoteDelay", CheckBuiltinVoteDelay()); + g_bSQLConnected = false; return; } - - Handle hVote = CreateBuiltinVote(VoteActionHandler, BuiltinVoteType_Custom_YesNo, BuiltinVoteAction_Cancel | BuiltinVoteAction_VoteEnd | BuiltinVoteAction_End); - char sBuffer[128]; - FormatEx(sBuffer, sizeof(sBuffer), "%T", "KickSpecsVoteTitle", LANG_SERVER); - SetBuiltinVoteArgument(hVote, sBuffer); - SetBuiltinVoteInitiator(hVote, client); - SetBuiltinVoteResultCallback(hVote, KickSpecsVoteResultHandler); - - // Display to players - int total = 0; - int[] players = new int[MaxClients]; - for (int i = 1; i <= MaxClients; i++) + g_bSQLConnected = true; + g_hDatabase = hDatabase; + + char szDriver[64]; + SQL_ReadDriver(hDatabase, szDriver, sizeof(szDriver)); + + if (StrEqual(szDriver, "mysql")) { - if (!IsClientInGame(i) || IsFakeClient(i) || GetClientTeam(i) == L4D2Team_Spectator) - continue; - players[total++] = i; + g_iSQLDriver = kMySQL; + hDatabase.SetCharset("utf8"); } - DisplayBuiltinVote(hVote, players, total, FindConVar("sv_vote_timer_duration").IntValue); + else if (StrEqual(szDriver, "sqlite")) + g_iSQLDriver = kSQLite; + + if (pData == 0) + return; + + char szTable[64]; + DataPack pDataPack = view_as(pData); - // Client is voting for - FakeClientCommand(client, "Vote Yes"); + pDataPack.Reset(); + pDataPack.ReadString(szTable, sizeof(szTable)); + delete pDataPack; + g_bSQLTableExists = bTableExists(szTable); + + if (!g_bSQLTableExists) + vCreateSQL(); } -void VoteActionHandler(Handle vote, BuiltinVoteAction action, int param1, int param2) +/** + * Creates the SQL table if it does not already exist. + * The table structure depends on the SQL driver being used (MySQL or SQLite). + * + * MySQL: + * - Table name: g_szTable + * - Columns: + * - `id`: INT, AUTO_INCREMENT, PRIMARY KEY + * - `authid`: VARCHAR(64), NOT NULL, DEFAULT '', COMMENT 'Client in the whitelistList' + * - `serverid`: INT, NOT NULL, DEFAULT 0, COMMENT 'Server identification' + * + * SQLite: + * - Table name: g_szTable + * - Columns: + * - `id`: INTEGER, PRIMARY KEY AUTOINCREMENT + * - `authid`: TEXT, NOT NULL, DEFAULT '' + * - `serverid`: INTEGER, NOT NULL, DEFAULT 0 + * + * If the table creation fails, an error is logged. + * If the table is created successfully, a message is printed to the server. + * + * @return void + */ +void vCreateSQL() { - switch (action) + char szQuery[600]; + switch (g_iSQLDriver) { - case BuiltinVoteAction_End: + case kMySQL: { - CloseHandle(vote); + g_hDatabase.Format(szQuery, sizeof(szQuery), + "CREATE TABLE IF NOT EXISTS `%s` ( \ + `id` INT AUTO_INCREMENT PRIMARY KEY, \ + `authid` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'Client in the whitelistList', \ + `serverid` INT NOT NULL DEFAULT 0 COMMENT 'Server identification' \ + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci", + g_szTable); } - case BuiltinVoteAction_Cancel: + case kSQLite: { - DisplayBuiltinVoteFail(vote, BuiltinVoteFail_Generic); + g_hDatabase.Format(szQuery, sizeof(szQuery), + "CREATE TABLE IF NOT EXISTS `%s` ( \ + `id` INTEGER PRIMARY KEY AUTOINCREMENT, \ + `authid` TEXT NOT NULL DEFAULT '' \ + `serverid` INTEGER NOT NULL DEFAULT 0, \ + )", + g_szTable); } } + +#if DEBUG_SQL + LogMessage("[CreateSQL] Table created: %s", g_szTable); +#endif + + if (!SQL_FastQuery(g_hDatabase, szQuery)) + { + logErrorSQL(g_hDatabase, szQuery, "CreateSQL"); + return; + } + + g_bSQLTableExists = true; } -void KickSpecsVoteResultHandler(Handle vote, int num_votes, int num_clients, const int[][] client_info, int num_items, const int[][] item_info) +/** + * Checks if a table exists in the database. + * + * @param szTable The name of the table to check. + * @return True if the table exists, false otherwise. + */ +stock bool bTableExists(const char[] szTable) { - for (int i = 0; i < num_items; i++) + char szQuery[255]; + + switch (g_iSQLDriver) { - if (item_info[i][BUILTINVOTEINFO_ITEM_INDEX] == BUILTINVOTES_VOTE_YES) - { - if (item_info[i][BUILTINVOTEINFO_ITEM_VOTES] > (num_clients / 2)) - { - char buffer[64]; - FormatEx(buffer, sizeof(buffer), "%T", "KickSpecsVoteSuccess", LANG_SERVER); - DisplayBuiltinVotePass(vote, buffer); - - float delay = FindConVar("sv_vote_command_delay").FloatValue; - CreateTimer(delay, Timer_KickSpecs); - return; - } - } + case kMySQL: + g_hDatabase.Format(szQuery, sizeof(szQuery), "SHOW TABLES LIKE '%s'", szTable); + case kSQLite: + g_hDatabase.Format(szQuery, sizeof(szQuery), "SELECT name FROM sqlite_master WHERE type='table' AND name='%s'", szTable); } - DisplayBuiltinVoteFail(vote, BuiltinVoteFail_Loses); +#if DEBUG_SQL + LogMessage("[bTableExists] Query: %s", szQuery); +#endif + + DBResultSet hQueryTableExists = SQL_Query(g_hDatabase, szQuery); + if (hQueryTableExists == null) + return false; + + bool bExists = hQueryTableExists.FetchRow(); + delete hQueryTableExists; + + return bExists; +} + +/** + * Logs SQL errors and the corresponding query that caused the error. + * + * @param db The database connection handle. + * @param sQuery The SQL query that failed. + * @param sName The name of the source or context where the error occurred. + */ +void logErrorSQL(Database pDb, const char[] szQuery, const char[] szName) +{ + char szSQLError[250]; + SQL_GetError(pDb, szSQLError, sizeof(szSQLError)); + LogError("[%s] SQL failed: %s", szName, szSQLError); + LogError("[%s] Query dump: %s", szName, szQuery); } -Action Timer_KickSpecs(Handle timer) +/** + * Retrieves the client index of a player based on their Steam2 Auth ID. + * + * @param szAuthId The Steam2 Auth ID of the player to search for. + * @return The client index of the player if found, otherwise NO_INDEX. + */ +int GetClientOfAuthID(const char[] szAuthId) { for (int i = 1; i <= MaxClients; i++) { - if (!IsClientInGame(i) || IsFakeClient(i)) { continue; } - if (GetClientTeam(i) != L4D2Team_Spectator) { continue; } - if (IsClientCaster(i)) { continue; } - if (GetUserAdmin(i) != INVALID_ADMIN_ID) { continue; } - - KickClient(i, "%t", "KickSpecsReason"); - } + if (!IsClientInGame(i) || IsFakeClient(i)) + continue; - return Plugin_Stop; + char szClientAuthId[STEAMID2_LENGTH]; + GetClientAuthId(i, AuthId_Steam2, szClientAuthId, sizeof(szClientAuthId)); + + if (StrEqual(szClientAuthId, szAuthId)) + return i; + } + return NO_INDEX; } \ No newline at end of file diff --git a/addons/sourcemod/scripting/caster_system_test.sp b/addons/sourcemod/scripting/caster_system_test.sp new file mode 100644 index 000000000..bfcce750c --- /dev/null +++ b/addons/sourcemod/scripting/caster_system_test.sp @@ -0,0 +1,710 @@ +#pragma semicolon 1 +#pragma newdecls required + +#include +#include + +#undef REQUIRE_PLUGIN +#include +#define REQUIRE_PLUGIN + +#define STEAMID2_LENGTH 32 +#define PREFIX_TEST "[{olive}Test{default}]" + +bool + g_bLateload, + g_bCasterSystem; + +enum eTypeList +{ + kCaster = 0, + kWhite = 1, + kSQL = 2 +} + +enum L4DTeam +{ + L4DTeam_Unassigned = 0, + L4DTeam_Spectator = 1, + L4DTeam_Survivor = 2, + L4DTeam_Infected = 3 +} + +public Plugin g_myInfo = { + name = "L4D2 Caster System Test", + author = "lechuga", + description = "Testing native and forward", + version = "1.0", + url = "https://github.com/SirPlease/L4D2-Competitive-Rework" +}; + +public APLRes AskPluginLoad2(Handle hMyself, bool bLate, char[] sError, int iErr_max) +{ + g_bLateload = bLate; + return APLRes_Success; +} + +public void OnAllPluginsLoaded() +{ + g_bCasterSystem = LibraryExists("caster_system"); +} + +public void OnLibraryAdded(const char[] sPluginName) +{ + if (StrEqual(sPluginName, "caster_system")) + g_bCasterSystem = true; +} + +public void OnLibraryRemoved(const char[] sPluginName) +{ + if (StrEqual(sPluginName, "caster_system")) + g_bCasterSystem = false; +} + +public void OnPluginStart() +{ + vLoadTranslation("common.phrases"); + vLoadTranslation("caster_system.phrases"); + + RegConsoleCmd("sm_tcaster", aTcasterRegCmd, "Registers a player to the caster list"); + RegConsoleCmd("sm_tcaster_rm", aTcasterRemoveCmd, "Removes a player from the caster list"); + + RegConsoleCmd("sm_tcaster_wl", aTwhitelistRegCmd, "Adds a player to the whitelist"); + RegConsoleCmd("sm_tcaster_wl_rm", aTwhitelistRemoveCmd, "Removes a player from the whitelist"); + + if (!g_bLateload) + return; + + g_bCasterSystem = LibraryExists("caster_system"); +} + +Action aTcasterRegCmd(int iClient, int iArgs) +{ + if (!g_bCasterSystem) + { + CPrintToChatAll("%s {red}Caster System{default} is not loaded", PREFIX_TEST); + return Plugin_Handled; + } + + ReplySource eRsCmd = GetCmdReplySource(); + if (iArgs == 0) + { + if (eRsCmd == SM_REPLY_TO_CHAT && iClient != SERVER_INDEX) + vDisplayRegMenu(iClient, kCaster); + else + CReplyToCommand(iClient, "%s %t: sm_tcaster <#userid|name|steamid>", PREFIX_TEST, "Use"); + return Plugin_Handled; + } + + char szArguments[64]; + GetCmdArgString(szArguments, sizeof(szArguments)); + + char szArg[STEAMID2_LENGTH]; + BreakString(szArguments, szArg, sizeof(szArg)); + + vProcessReg(iClient, szArg, kCaster); + + return Plugin_Handled; +} + +Action aTwhitelistRegCmd(int iClient, int iArgs) +{ + if (!g_bCasterSystem) + { + CPrintToChatAll("%s {red}Caster System{default} is not loaded", PREFIX_TEST); + return Plugin_Handled; + } + + ReplySource eRsCmd = GetCmdReplySource(); + if (iArgs == 0) + { + if (eRsCmd == SM_REPLY_TO_CHAT && iClient != SERVER_INDEX) + vDisplayRegMenu(iClient, kWhite); + else + CReplyToCommand(iClient, "%s %t: sm_tcaster_wl <#userid|name|steamid>", PREFIX_TEST, "Use"); + return Plugin_Handled; + } + + char szArguments[64]; + GetCmdArgString(szArguments, sizeof(szArguments)); + + char szArg[STEAMID2_LENGTH]; + BreakString(szArguments, szArg, sizeof(szArg)); + + vProcessReg(iClient, szArg, kWhite); + + return Plugin_Handled; +} + +/** + * Processes the registration of a client based on the provided argument. + * + * @param iClient The client index who initiated the registration. + * @param szArg The argument provided for registration, which can be a Steam ID or a target name. + * @param eTypeList The type list enumeration specifying the type of registration. + * + * If the provided argument is a Steam ID, the client is registered directly using the Steam ID. + * If the provided argument is a target name, the function attempts to find the target client and register them. + * If the target client is not found or their Steam ID cannot be retrieved, an error message is sent to the client. + */ +void vProcessReg(int iClient, const char[] szArg, eTypeList eList) +{ + if (bIsSteamId(szArg)) + { + vRegister(iClient, NO_INDEX, szArg, szArg, eList, kAuth); + return; + } + + int iTarget = FindTarget(iClient, szArg, true, false); + if (iTarget == NO_INDEX) + return; + + char szAuthId[STEAMID2_LENGTH]; + if (!GetClientAuthId(iTarget, AuthId_Steam2, szAuthId, sizeof(szAuthId))) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "AuthIdError", szAuthId); + return; + } + + char szName[16]; + GetClientName(iTarget, szName, sizeof(szName)); + vRegister(iClient, iTarget, szAuthId, szName, eList, kClient); +} + +void vDisplayRegMenu(int iClient, eTypeList eList) +{ + Menu hMenu; + hMenu = new Menu(iRegMenuHandler); + char szTitle[100]; + Format(szTitle, sizeof(szTitle), "%t", "MenuPlayersList"); + hMenu.SetTitle(szTitle); + vListTargets(hMenu, eList); + + hMenu.Display(iClient, MENU_TIME_FOREVER); +} + +/** + * Populates a menu with a list of clients based on the specified type list. + * + * @param hMenu The menu handle to which the clients will be added. + * @param eTypeList The type of list to populate (e.g., kCaster, kWhite). + * + * This function iterates through all connected clients and adds them to the provided menu. + * It constructs an identifier for each client and appends the specified type list to it. + * Depending on the type list, it checks if the client is a caster or in the whitelist and + * adds them to the menu accordingly, disabling the item if they meet the criteria. + * + * The function skips clients that are not connected, are fake clients, or if their name or + * Steam ID cannot be retrieved. + */ +void vListTargets(Menu hMenu, eTypeList eList) +{ + char szName[64], szBuffer[16], szAuthId[STEAMID2_LENGTH]; + int iUserId; + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsClientConnected(i) || IsFakeClient(i)) + continue; + + if (!GetClientName(i, szName, sizeof(szName))) + continue; + + if (!GetClientAuthId(i, AuthId_Steam2, szAuthId, sizeof(szAuthId))) + continue; + + // Construct an identifier and add the eTypeList to it + iUserId = GetClientUserId(i); + Format(szBuffer, sizeof(szBuffer), "%d:%d", iUserId, view_as(eList)); + + bool bIsCaster = (eList == kCaster) ? bCaster(kClient, kGet, i, szAuthId) : bCasterWhitelist(kClient, kGet, i, szAuthId); + hMenu.AddItem(szBuffer, szName, bIsCaster ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT); + } +} + +public int iRegMenuHandler(Menu hMenu, MenuAction eAction, int iClient, int iItem) +{ + switch (eAction) + { + case MenuAction_Select: + { + char szInfo[32], szName[32]; + int iUserId, iTarget; + + hMenu.GetItem(iItem, szInfo, sizeof(szInfo), _, szName, sizeof(szName)); + + char szPart1[16], szPart2[16]; + int iIndex; + + iIndex = SplitString(szInfo, ":", szPart1, sizeof(szPart1)); + if (iIndex != -1) + SplitString(szInfo[iIndex], ":", szPart2, sizeof(szPart2)); + + int iPart1 = StringToInt(szPart1); + int iPart2 = StringToInt(szPart2); + + iUserId = iPart1; + eTypeList eList = view_as(iPart2); + + if ((iTarget = GetClientOfUserId(iUserId)) == SERVER_INDEX) + CPrintToChat(iClient, "%t %t", "Prefix", "Player no longer available"); + else if (!CanUserTarget(iClient, iTarget)) + CPrintToChat(iClient, "%t %t", "Prefix", "Unable to target"); + else + { + char szAuthId[STEAMID2_LENGTH]; + if (!GetClientAuthId(iTarget, AuthId_Steam2, szAuthId, sizeof(szAuthId))) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "AuthIdError", szAuthId); + return 0; + } + + char szTargetName[16]; + GetClientName(iTarget, szTargetName, sizeof(szTargetName)); + + vRegister(iClient, iTarget, szAuthId, szTargetName, eList, kClient); + } + } + case MenuAction_End: + { + delete hMenu; + } + } + return 0; +} + +/** + * Registers a client or target with the specified type and ID. + * + * @param iClient The client index initiating the registration. + * @param iTarget The target index to be registered. + * @param szAuthId The authentication ID of the target. + * @param szDisplayName The display name of the target. + * @param eTypeList The type list indicating the registration type. + * @param eTypeID The type ID for the registration. + */ +void vRegister(int iClient, int iTarget, const char[] szAuthId, const char[] szDisplayName, eTypeList eList, eTypeID eId) +{ + bool bIndex = (eId == kClient); + bool bFound = false; + + switch (eList) + { + case kCaster: + bFound = bIndex ? bCaster(eId, kGet, iTarget, szAuthId) : bCaster(eId, kGet, NO_INDEX, szAuthId); + case kWhite: + bFound = bIndex ? bCasterWhitelist(eId, kGet, iTarget, szAuthId) : bCasterWhitelist(eId, kGet, NO_INDEX, szAuthId); + } + + if (bFound) + { + CReplyToCommand(iClient, "%t %t", "Prefix", eList == kCaster ? "CasterFound" : "WhitelistFound", szDisplayName); + return; + } + + switch (eList) + { + case kCaster: + bCaster(eId, kSet, iTarget, szAuthId); + case kWhite: + bCasterWhitelist(eId, kSet, iTarget, szAuthId); + } +} + +Action aTcasterRemoveCmd(int iClient, int iArgs) +{ + ReplySource eRsCmd = GetCmdReplySource(); + if (iArgs == 0) + { + if (eRsCmd == SM_REPLY_TO_CHAT && iClient != SERVER_INDEX) + vDisplayRemoveMenu(iClient, kCaster); + else + CReplyToCommand(iClient, "%t %t: sm_tcaster_rm <#userid|name|steamid>", "Prefix", "Use"); + return Plugin_Handled; + } + + char szArguments[64]; + GetCmdArgString(szArguments, sizeof(szArguments)); + + char szArg[STEAMID2_LENGTH]; + BreakString(szArguments, szArg, sizeof(szArg)); + + vProcessRemove(iClient, szArg, kCaster); + return Plugin_Handled; +} + +Action aTwhitelistRemoveCmd(int iClient, int iArgs) +{ + ReplySource eRsCmd = GetCmdReplySource(); + if (iArgs == 0) + { + if (eRsCmd == SM_REPLY_TO_CHAT && iClient != SERVER_INDEX) + vDisplayRemoveMenu(iClient, kWhite); + else + CReplyToCommand(iClient, "%t %t: sm_tcaster_wl_rm <#userid|name|steamid>", "Prefix", "Use"); + return Plugin_Handled; + } + + char szArguments[64]; + GetCmdArgString(szArguments, sizeof(szArguments)); + + char szArg[STEAMID2_LENGTH]; + BreakString(szArguments, szArg, sizeof(szArg)); + + vProcessRemove(iClient, szArg, kWhite); + return Plugin_Handled; +} + +/** + * Processes the removal of a client from a specified list. + * + * @param iClient The client index initiating the removal. + * @param szArg The argument provided, which can be a Steam ID or a target name. + * @param eList The list type from which the client should be removed. + * + * If the provided argument is a Steam ID, it directly calls the removal function. + * Otherwise, it attempts to find the target client by name and retrieves their Steam ID. + * If the Steam ID retrieval fails, it sends an error message to the initiating client. + * Finally, it calls the removal function with the appropriate parameters. + */ +void vProcessRemove(int iClient, const char[] szArg, eTypeList eList) +{ + if (bIsSteamId(szArg)) + { + vRemove(iClient, NO_INDEX, szArg, szArg, eList, kAuth); + return; + } + + int iTarget = FindTarget(iClient, szArg, true, false); + if (iTarget == NO_INDEX) + return; + + char szAuthId[STEAMID2_LENGTH]; + if (!GetClientAuthId(iTarget, AuthId_Steam2, szAuthId, sizeof(szAuthId))) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "AuthIdError", szAuthId); + return; + } + + char szName[16]; + GetClientName(iTarget, szName, sizeof(szName)); + vRemove(iClient, iTarget, szAuthId, szName, eList, kClient); +} + +/** + * Removes a client from the specified list. + * + * @param iClient The client index who initiated the removal. + * @param iTarget The client index of the target to remove. + * @param szAuthId The Steam ID of the target client. + * @param szDisplayName The display name of the target client. + * @param eList The type list from which the client should be removed. + * @param eId The type ID to determine the removal operation. + * + * The function constructs a message to display to the initiating client and the target client. + * It then calls the removal function with the appropriate parameters based on the type list. + * If the removal operation fails, an error message is sent to the initiating client. + */ +void vRemove(int iClient, int iTarget, const char[] szAuthId, const char[] szDisplayName, eTypeList eList, eTypeID eId) +{ + bool bIndex = (iTarget > SERVER_INDEX); + bool bFound = false; + + switch (eList) + { + case kCaster: + bFound = bIndex ? bCaster(eId, kGet, iTarget, szAuthId) : bCaster(eId, kGet, NO_INDEX, szAuthId); + case kWhite: + bFound = bIndex ? bCasterWhitelist(eId, kGet, iTarget, szAuthId) : bCasterWhitelist(eId, kGet, NO_INDEX, szAuthId); + } + + if (!bFound) + { + CReplyToCommand(iClient, "%t %t", "Prefix", eList == kCaster ? "CasterNoFound" : "WhitelistNoFound", szDisplayName); + return; + } + + switch (eList) + { + case kCaster: + bCaster(eId, kRem, iTarget, szAuthId); + case kWhite: + bCasterWhitelist(eId, kRem, iTarget, szAuthId); + } +} + +void vDisplayRemoveMenu(int iClient, eTypeList eList) +{ + char szTitle[100]; + switch (eList) + { + case kCaster: + Format(szTitle, sizeof(szTitle), "%T", "MenuCastersList", iClient); + case kWhite: + Format(szTitle, sizeof(szTitle), "%T", "MenuWhitelistList", iClient); + } + + Menu hMenu = new Menu(iMenuRemove); + hMenu.SetTitle(szTitle); + vRemoveTargets(hMenu, eList, iClient); + + hMenu.Display(iClient, MENU_TIME_FOREVER); +} + +/** + * Removes targets from the specified menu based on the given type list. + * + * @param hMenu The menu handle to which the targets will be added. + * @param eList The type list to determine which targets to remove. + * @param iClient The client index who initiated the removal. + * + * This function iterates through all connected clients, checks if they match + * the criteria specified by the type list, and adds them to the menu if they do. + * If no targets are found, a message indicating no targets to remove is added + * to the menu. + */ +void vRemoveTargets(Menu hMenu, eTypeList eList, int iClient) +{ + char + szName[64], + szInfo[16], + szAuthId[STEAMID2_LENGTH]; + + bool + bFound; + + int + iTargets = 0; + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsClientConnected(i) || IsFakeClient(i)) + continue; + + if (!GetClientName(i, szName, sizeof(szName))) + continue; + + if (!GetClientAuthId(i, AuthId_Steam2, szAuthId, sizeof(szAuthId))) + continue; + + Format(szInfo, sizeof(szInfo), "%d:%d", GetClientUserId(i), view_as(eList)); + + switch (eList) + { + case kCaster: + bFound = bCaster(kClient, kGet, i, szAuthId); + case kWhite: + bFound = bCasterWhitelist(kClient, kGet, i, szAuthId); + } + + if (bFound) + { + hMenu.AddItem(szInfo, szName); + iTargets++; + } + } + + if (iTargets == 0) + { + char szMsj[64]; + Format(szMsj, sizeof(szMsj), "%T", "NoTargetsToRemove", iClient); + hMenu.AddItem("", szMsj, ITEMDRAW_DISABLED); + } +} + +public int iMenuRemove(Menu hMenu, MenuAction eAction, int iClient, int iItem) +{ + if (eAction == MenuAction_Select) + { + char + szInfo[32], + szName[32], + szPart1[16], + szPart2[16]; + + int + iIndex, + iUserId, + iTarget; + + eTypeList + eList; + + hMenu.GetItem(iItem, szInfo, sizeof(szInfo), _, szName, sizeof(szName)); + + iIndex = SplitString(szInfo, ":", szPart1, sizeof(szPart1)); + SplitString(szInfo[iIndex], ":", szPart2, sizeof(szPart2)); + + iUserId = StringToInt(szPart1); + eList = view_as(StringToInt(szPart2)); + + if ((iTarget = GetClientOfUserId(iUserId)) == SERVER_INDEX) + CPrintToChat(iClient, "%t %t", "Prefix", "Player no longer available"); + else + { + char szAuthId[STEAMID2_LENGTH]; + if (!GetClientAuthId(iTarget, AuthId_Steam2, szAuthId, sizeof(szAuthId))) + { + CReplyToCommand(iClient, "%t %t", "Prefix", "AuthIdError", szAuthId); + return 0; + } + + vRemove(iClient, iTarget, szAuthId, szName, eList, kClient); + } + } + else if (eAction == MenuAction_End) + delete hMenu; + return 0; +} + +public void OnCaster(eTypeID eID, int iClient, const char[] szAuthId) +{ + switch (eID) + { + case kClient: + { + LogMessage("[OnCaster] eTypeID: %d | iClient: %N", eID, iClient); + CPrintToChatAll("%s {blue}%N{default} was registered as a caster", PREFIX_TEST, iClient); + } + case kAuth: + { + LogMessage("[OnCaster] eTypeID: %d | szAuthId: %s", eID, szAuthId); + CPrintToChatAll("%s {blue}%s{default} was registered as a caster", PREFIX_TEST, szAuthId); + } + } +} + +public void OffCaster(eTypeID eID, int iClient, const char[] szAuthId) +{ + switch (eID) + { + case kClient: + { + LogMessage("[OffCaster] eTypeID: %d | iClient: %N", eID, iClient); + CPrintToChatAll("%s {red}%N{default} was removed from the casters list", PREFIX_TEST, iClient); + } + case kAuth: + { + LogMessage("[OffCaster] eTypeID: %d | szAuthId: %s", eID, szAuthId); + CPrintToChatAll("%s {red}%s{default} was removed from the casters list", PREFIX_TEST, szAuthId); + } + } +} + +/** + * Returns the clients team using L4DTeam. + * + * @param client Player's index. + * @return Current L4DTeam of player. + * @error Invalid client index. + */ +stock L4DTeam L4D_GetClientTeam(int client) +{ + int team = GetClientTeam(client); + return view_as(team); +} + +/** + * @brief Checks if a given string is a valid Steam ID. + * + * This function verifies if the provided string follows the format of a Steam ID. + * A valid Steam ID should start with "STEAM_" and contain two colons separating + * three numerical components. + * + * @param szAuthId The string to be checked. + * @return True if the string is a valid Steam ID, false otherwise. + */ +bool bIsSteamId(const char[] szAuthId) +{ + if (strlen(szAuthId) == 0) + return false; + + if (StrContains(szAuthId, "STEAM_") != 0) + return false; + + int iPos1 = FindCharInString(szAuthId, ':'); + if (iPos1 == NO_INDEX) + return false; + + int iPos2 = FindCharInString(szAuthId, ':', view_as(iPos1 + 1)); + if (iPos2 == NO_INDEX) + return false; + + char szUniverse[8]; + char szAuth[8]; + char szAccount[16]; + + int iLenUniverse = iPos1 - 6; + if (iLenUniverse <= 0 || iLenUniverse >= sizeof(szUniverse)) + return false; + + for (int i = 0; i < iLenUniverse; i++) + { + szUniverse[i] = szAuthId[6 + i]; + } + szUniverse[iLenUniverse] = '\0'; + + int iLenAuth = iPos2 - iPos1 - 1; + if (iLenAuth <= 0 || iLenAuth >= sizeof(szAuth)) + return false; + + for (int i = 0; i < iLenAuth; i++) + { + szAuth[i] = szAuthId[iPos1 + 1 + i]; + } + szAuth[iLenAuth] = '\0'; + + int iLenAccount = strlen(szAuthId) - iPos2 - 1; + if (iLenAccount <= 0 || iLenAccount >= sizeof(szAccount)) + return false; + + for (int i = 0; i < iLenAccount; i++) + { + szAccount[i] = szAuthId[iPos2 + 1 + i]; + } + szAccount[iLenAccount] = '\0'; + + if (!bIsInteger(szUniverse) || !bIsInteger(szAuth) || !bIsInteger(szAccount)) + return false; + + return true; +} + +/** + * @brief Checks if the given string represents an integer. + * + * This function iterates through each character of the input string and + * verifies if all characters are numeric. + * + * @param szString The string to be checked. + * @return True if the string represents an integer, false otherwise. + */ +bool bIsInteger(const char[] szString) +{ + int iLen = strlen(szString); + for (int i = 0; i < iLen; i++) + { + if (!IsCharNumeric(szString[i])) + return false; + } + return true; +} + +/** + * Check if the translation file exists + * + * @param szTranslation Translation name. + * @noreturn + */ +stock void vLoadTranslation(const char[] szTranslation) +{ + char szPath[PLATFORM_MAX_PATH], + szName[64]; + + Format(szName, sizeof(szName), "translations/%s.txt", szTranslation); + BuildPath(Path_SM, szPath, sizeof(szPath), szName); + if (!FileExists(szPath)) + SetFailState("Missing translation file %s.txt", szTranslation); + + LoadTranslations(szTranslation); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/include/caster_system.inc b/addons/sourcemod/scripting/include/caster_system.inc index e7e982f08..9887b1cf0 100644 --- a/addons/sourcemod/scripting/include/caster_system.inc +++ b/addons/sourcemod/scripting/include/caster_system.inc @@ -3,20 +3,69 @@ #endif #define _caster_system_included +enum eTypeID +{ + kClient = 0, + kAuth = 1, +} + +enum eTypeAction +{ + kGet = 0, + kSet = 1, + kRem = 2 +} + +/** + * @brief Add, checks or remove a user from the Casters list. + * + * @param eTypeID Defines how the client will be identified. + * @param eTypeAction What action will be taken. + * @param iClient Required only if eTypeID is kClient, otherwise set it to -1. + * @param szAuthId Required only if eTypeID is kAuth, otherwise it is not necessary to define it. + * @return True if the client or AuthID is a caster (for Get action), false otherwise. + */ +native bool bCaster(eTypeID eID, eTypeAction eAction, int iClient = -1, const char[] szAuthId = ""); + +/** + * @brief Add, checks or remove a user from the Casters whitelist. + * + * @param eTypeID Defines how the client will be identified. + * @param eTypeAction What action will be taken. + * @param iClient Required only if eTypeID is kClient, otherwise set it to -1. + * @param szAuthId Required only if eTypeID is kAuth, otherwise it is not necessary to define it. + * @return True if the action was successful, false otherwise. + */ +native bool bCasterWhitelist(eTypeID eID, eTypeAction eAction, int iClient = -1, const char[] szAuthId = ""); + +/** + * @brief Add, checks or remove a user from the spectator immunity list. + * + * @param eTypeID Defines how the client will be identified. + * @param eTypeAction What action will be taken. + * @param iClient Required only if eTypeID is kClient, otherwise set it to -1. + * @param szAuthId Required only if eTypeID is kAuth, otherwise it is not necessary to define it. + * @return True if the client has spectator immunity, false otherwise. + */ +native bool bKickSpecInmunity(eTypeID eID, eTypeAction eAction, int iClient = -1, const char[] szAuthId = ""); + /** - * If this in-game and fully connected client is a caster or not + * @brief Called when a client is added to the caster list. * - * @return True if this is a registered caster + * @param eTypeID Defines how the client will be identified. + * @param iClient It will be defined only if eTypeID is kClient. + * @param szAuthId It will be defined only if eTypeID is kAuth. */ -native bool IsClientCaster(int client); +forward void OnCaster(eTypeID eID, int iClient, const char[] szAuthId); /** - * If this Steam ID is a registered caster or not + * @brief Called when a client is removed to the caster list. * - * @param String containing the user's auth id - * @return True if this is a registered caster + * @param eTypeID Defines how the client will be identified. + * @param iClient It will be defined only if eTypeID is kClient. + * @param szAuthId It will be defined only if eTypeID is kAuth. */ -native bool IsIDCaster(const char[] AuthID); +forward void OffCaster(eTypeID eID, int iClient, const char[] szAuthId); public SharedPlugin __pl_caster_system = { @@ -32,7 +81,8 @@ public SharedPlugin __pl_caster_system = #if !defined REQUIRE_PLUGIN public void __pl_caster_system_SetNTVOptional() { - MarkNativeAsOptional("IsClientCaster"); - MarkNativeAsOptional("IsIDCaster"); + MarkNativeAsOptional("bCaster"); + MarkNativeAsOptional("bCasterWhitelist"); + MarkNativeAsOptional("bKickSpecInmunity"); } #endif \ No newline at end of file diff --git a/addons/sourcemod/scripting/readyup/panel.inc b/addons/sourcemod/scripting/readyup/panel.inc index 4e9c4fa0f..099909740 100644 --- a/addons/sourcemod/scripting/readyup/panel.inc +++ b/addons/sourcemod/scripting/readyup/panel.inc @@ -133,7 +133,7 @@ void UpdatePanel() else { ++specCount; - if (casterSystemAvailable && IsClientCaster(client)) + if (casterSystemAvailable && bCaster(kClient, kGet, client)) { ++casterCount; Format(nameBuf, sizeof(nameBuf), "%s\n", nameBuf); diff --git a/addons/sourcemod/scripting/specrates.sp b/addons/sourcemod/scripting/specrates.sp index 50b2e0e97..c91e9cd01 100644 --- a/addons/sourcemod/scripting/specrates.sp +++ b/addons/sourcemod/scripting/specrates.sp @@ -157,7 +157,7 @@ void AdjustRates(int client) g_Players[client].LastAdjusted = GetEngineTime(); L4DTeam team = L4D_GetClientTeam(client); - if (team == L4DTeam_Survivor || team == L4DTeam_Infected || (g_bCasterSystem && IsClientCaster(client))) + if (team == L4DTeam_Survivor || team == L4DTeam_Infected || (g_bCasterSystem && bCaster(kClient, kGet, client))) ResetRates(client); else if (team == L4DTeam_Spectator) { diff --git a/addons/sourcemod/translations/caster_system.phrases.txt b/addons/sourcemod/translations/caster_system.phrases.txt index becd60dfa..33493d1d0 100644 --- a/addons/sourcemod/translations/caster_system.phrases.txt +++ b/addons/sourcemod/translations/caster_system.phrases.txt @@ -1,142 +1,298 @@ "Phrases" { -// The following uses bracket style color tags (see colors.inc) - "SelfCastNotAllowed" + "Prefix" { - "en" "{blue}[{default}Cast{blue}] {default}You are not allowed to register as a caster {green}on this server{default}." + "en" "[{olive}Caster{default}]" } - "SelfCast1" + "AllowAddons" { - "en" "{blue}[{default}Cast{blue}] {default}You have registered yourself as a caster." + "en" "Addons are now {blue}allowed {default}for casters." } - "SelfCast2" + "CasterEmpty" { - "en" "{blue}[{default}Cast{blue}] {default}Reconnect to make your Addons work." + "en" "There are no {red}casters registered{default}." } - "RegCasterTarget" + "CasterFound" { - "#format" "{1:N}" - "en" "{default}[{olive}!{default}] {blue}Admin {default}({olive}{1}{default}) has registered you as a caster" + "#format" "{1:s}" + "en" "{blue}'{1}'{default} is already registered as a caster" } - "Reconnect1" + "CasterNoFound" { - "en" "{blue}[{default}Reconnect{blue}] {default}You will be reconnected to the server.." + "#format" "{1:s}" + "en" "{red}'{1}'{default} is not registered as a caster" } - "Reconnect2" + "CasterPlay" { - "en" "{blue}[{default}Reconnect{blue}] {default}There's a black screen instead of a loading bar!" + "en" "Before playing, you must {red}remove your caster registration{default}" } - "UnregCasterTarget" + "CasterReconnect" { - "#format" "{1:N}" - "en" "{default}[{olive}!{default}] {default}You are unregistered as a caster by {blue}Admin {default}({olive}{1}{default})" + "en" "Reconnect to make your {green}Addons work{default}." } - - "KickSpecsAdmin" + + "CasterReg" + { + "#format" "{1:s}" + "en" "{blue}'{1}'{default} was registered as a caster" + } + + "CasterRegFrom" { "#format" "{1:N}" - "en" "[{green}!{default}] {blue}Spectators {default}are kicked by {blue}Admin {default}({olive}{1}{default})" + "en" "{blue}Admin{default} ({olive}{1}{default}) has registered you as a caster" } - - "KickSpecsVoteSpec" + + "CasterRegError" { - "en" "[{olive}Cast{default}] {blue}Spectators {default}are not allowed to call a vote for {green}kicking specs{default}." + "#format" "{1:s}" + "en" "Error registering caster ({olive}{1}{default})" } - "VoteInProgress" + "CasterRemove" { - "en" "[{olive}Cast{default}] There's {olive}a vote {green}in progress{default}." + "#format" "{1:s}" + "en" "{red}'{1}'{default} was removed from the casters list" } - - "VoteDelay" + + "CasterRemoveFrom" { - "#format" "{1:d}" - "en" "[{olive}Cast{default}] Wait for another {blue}{1}s {default}to call a vote." + "#format" "{1:N}" + "en" "{blue}Admin{default} ({olive}{1}{default}) has unregistered you as a caster" } - + + "CasterRemoveError" + { + "#format" "{1:s}" + "en" "Error removing caster ({olive}{1}{default})" + } + + "CasterReset" + { + "en" "{blue}Casters list{default} successfully reset" + } + "ForbidAddons" { - "en" "{blue}[{default}!{blue}] {default}Addons are now {green}forbidden {default}for casters." + "en" "Addons are now {red}forbidden {default}for casters." } - - "AllowAddons" + + "ListPrinted" { - "en" "{blue}[{default}!{blue}] {default}Addons are now {green}allowed {default}for casters." + "en" "List printed in the {green}console{default}" } - - "CasterCheck1" + + "MenuCastersList" { - "en" "{default}<{olive}Cast{default}> Unregister from casting first before playing." + "en" "List of Casters" } - - "CasterCheck2" + + "MenuPlayersList" { - "en" "{default}<{olive}Cast{default}> Use {green}!notcasting {default}/ {green}!uncast" + "en" "List of Players" } - -// The following are not allowed to use any color tag - "RegCasterReply" + "MenuWhitelistList" { - "#format" "{1:N}" - "en" "Registered {1} as a caster" + "en" "Whitelist" } - - "CasterSteamIDError" + + "Reconnect" + { + "en" "You will be reconnected to the server, now {green}you will see a black screen instead of a loading bar{default}" + } + + "SQLCacheSuccess" + { + "en" "Database cache was {green}successfully{default} updated" + } + + "SQLDisabled" + { + "en" "SQL {red}disabled{default}." + } + + "SQLError" + { + "en" "SQL {red}error{default}" + } + + "SQLFound" + { + "#format" "{1:s}" + "en" "{blue}'{1}'{default} is already registered in the database" + } + + "SQLNoFound" { - "en" "Couldn't find Steam ID. Check for typos and let the player get fully connected." + "#format" "{1:s}" + "en" "{red}'{1}'{default} is not registered in the database" } - "UnregCasterNonAdmin" + "SQLNoConnect" { - "en" "Only admins can remove other casters. Use sm_notcasting without arguments if you wish to remove yourself." + "en" "{red}Could not{default} connect to database." } - "UnregCasterSuccess" + "SQLQueryError" + { + "en" "Database query {red}error{default}" + } + + "SQLReg" + { + "#format" "{1:s}" + "en" "{blue}'{1}'{default} was registered in the database" + } + + "SQLRegFrom" { "#format" "{1:N}" - "en" "{1} is no longer a caster" + "en" "{blue}Admin{default} ({olive}{1}{default}) has registered you in the database" } - - "CasterDBReset" + + "SQLRemoved" { - "en" "[casters_database] Reset successfully" + "#format" "{1:s}" + "en" "{red}'{1}'{default} was removed from the database" + } + + "SQLRemovedFrom" + { + "#format" "{1:N}" + "en" "{blue}Admin{default} ({olive}{1}{default}) has removed you from the database" } - "CasterDBAdd" + "SQLResetSuccess" + { + "en" "Database was {green}successfully{default} reset" + } + + "SQLTableCreated" + { + "en" "SQL table created successfully." + } + + "SelfRegCasterFound" + { + "en" "You are already {blue}registered{default} as a {blue}caster{default}." + } + + "SelfRegDisabled" + { + "en" "Self-registration is {red}disabled{default}." + } + + "SelfRegNoAdmin" + { + "en" "{red}Only admins{default} can register other players." + } + + "SelfRegSuccess" + { + "en" "You have {blue}registered{default} yourself as a {blue}caster{default}." + } + + "SelfRegWhitelistNotFound" + { + "en" "You cannot {green}self-register{default} as {red}you are not on the whitelist{default}." + } + + "AuthIdError" { "#format" "{1:s}" - "en" "[casters_database] Added '{1}'" + "en" "The steamid ({red}'{1}'{default}) is invalid." } - - "CasterDBRemove" + + "UnRegCasterNonAdmin" + { + "en" "Only {blue}admins{default} can remove other casters." + } + + "Use" + { + "en" "Use" + } + + "UseNoCast" + { + "en" "Use {green}!uncast{default}, to remove your registration" + } + + "WhitelistEmpty" + { + "en" "You cannot {green}self-register{default} as there are {red}no players on the whitelist{default}." + } + + "WhitelistFound" { "#format" "{1:s}" - "en" "[casters_database] Removed '{1}'" + "en" "{blue}'{1}'{default} is already on the whitelist" } - - "CasterDBFound" + + "WhitelistNoFound" { "#format" "{1:s}" - "en" "[casters_database] '{1}' already exists" + "en" "{red}'{1}'{default} is not on the whitelist" + } + + "WhitelistReg" + { + "#format" "{1:s}" + "en" "Whitelisted {blue}'{1}'{default}" + } + + "WhitelistRegFrom" + { + "#format" "{1:N}" + "en" "{blue}Admin{default} ({olive}{1}{default}) has whitelisted you" + } + + "WhitelistRegError" + { + "#format" "{1:s}" + "en" "Error whitelisting ({olive}{1}{default})" + } + + "WhitelistRemove" + { + "#format" "{1:s}" + "en" "Removed {red}'{1}'{default} from the whitelist" + } + + "WhitelistRemoveFrom" + { + "#format" "{1:N}" + "en" "{blue}Admin{default} ({olive}{1}{default}) has removed you from the whitelist" } - "CasterDBError" + "WhitelistRemoveError" { - "en" "[casters_database] No args specified / empty buffer" + "#format" "{1:s}" + "en" "Error removing from whitelist ({olive}{1}{default})" } + "WhitelistReset" + { + "en" "{blue}Whitelist{default} successfully reset" + } + + "NoTargetsToRemove" + { + "en" "No players found" + } + "KickSpecsVoteTitle" { "en" "Kick Non-Admin & Non-Casting Spectators?" } - + "KickSpecsVoteSuccess" { "en" "Ciao Spectators!" diff --git a/addons/sourcemod/translations/chi/caster_system.phrases.txt b/addons/sourcemod/translations/chi/caster_system.phrases.txt deleted file mode 100644 index 05edd496d..000000000 --- a/addons/sourcemod/translations/chi/caster_system.phrases.txt +++ /dev/null @@ -1,148 +0,0 @@ -"Phrases" -{ -// The following uses bracket style color tags (see colors.inc) - "SelfCastNotAllowed" - { - "chi" "{blue}[{default}Cast{blue}] {green}该服务器{default}拒绝了你的解说认证" - } - - "SelfCast1" - { - "chi" "{blue}[{default}Cast{blue}] 你已经被认证成为了解说" - } - - "SelfCast2" - { - "chi" "{blue}[{default}Cast{blue}] 重连服务器以加载 MOD" - } - - "RegCasterTarget" - { - "#format" "{1:N}" - "chi" "{default}[{olive}!{default}] {blue}管理员 {default}({olive}{1}{default}) 认证了你为解说" - } - - "Reconnect1" - { - "chi" "{blue}[{default}Reconnect{blue}] 你将会被重连至服务器..." - } - - "Reconnect2" - { - "chi" "{blue}[{default}Reconnect{blue}] 期间会黑屏而没有加载条!" - } - - "UnregCasterTarget" - { - "#format" "{1:N}" - "chi" "{default}[{olive}!{default}] {blue}管理员 {default}({olive}{1}{default}) 解除了你的解说身份" - } - - "KickSpecsAdmin" - { - "#format" "{1:N}" - "chi" "[{green}!{default}] {blue}管理员 {default}({olive}{1}{default}) {default}踢出了{blue}所有旁观者" - } - - "KickSpecsVoteSpec" - { - "chi" "[{olive}Cast{default}] {blue}旁观 {default}不可以投票 {green}踢出旁观{default}" - } - - "VoteInProgress" - { - "chi" "[{olive}Cast{default}] 现在有{olive}投票{green}正在进行{default}" - } - - "VoteDelay" - { - "#format" "{1:d}" - "chi" "[{olive}Cast{default}] 请等待 {blue}{1}秒 {default}以发起下一轮投票" - } - - "ForbidAddons" - { - "chi" "{blue}[{default}!{blue}] {default}管理员{green}禁止了{default}解说使用 MOD" - } - - "AllowAddons" - { - "chi" "{blue}[{default}!{blue}] {default}管理员{green}开启了{default}解说使用 MOD" - } - - "CasterCheck1" - { - "chi" "{default}<{olive}Cast{default}> 请先解除解说身份" - } - - "CasterCheck2" - { - "chi" "{default}<{olive}Cast{default}> 命令为 {green}!notcasting {default}/ {green}!uncast" - } - -// The following are not allowed to use any color tag - "RegCasterReply" - { - "#format" "{1:N}" - "chi" "认证了 {1} 为解说" - } - - "CasterSteamIDError" - { - "chi" "Steam ID查询失败。 请检查拼写错误,等待玩家加载。" - } - - "UnregCasterNonAdmin" - { - "chi" "只有管理员可以撤销其他人的解说身份。 输入不带参数的 sm_notcasting 以解除解说身份。" - } - - "UnregCasterSuccess" - { - "#format" "{1:N}" - "chi" "{1} 已不再是解说。" - } - - "CasterDBReset" - { - "chi" "[casters_database] 重置成功" - } - - "CasterDBAdd" - { - "#format" "{1:s}" - "chi" "[casters_database] 添加了 '{1}'" - } - - "CasterDBRemove" - { - "#format" "{1:s}" - "chi" "[casters_database] 移除了 '{1}'" - } - - "CasterDBFound" - { - "#format" "{1:s}" - "chi" "[casters_database] '{1}' 已存在" - } - - "CasterDBError" - { - "chi" "[casters_database] 缺少参数 / 空字符串" - } - - "KickSpecsVoteTitle" - { - "chi" "踢出所有非管理员和解说的旁观?" - } - - "KickSpecsVoteSuccess" - { - "chi" "撒尤娜拉" - } - - "KickSpecsReason" - { - "chi" "本比赛不允许旁观" - } -} \ No newline at end of file diff --git a/addons/sourcemod/translations/es/caster_system.phrases.txt b/addons/sourcemod/translations/es/caster_system.phrases.txt new file mode 100644 index 000000000..cddc1481e --- /dev/null +++ b/addons/sourcemod/translations/es/caster_system.phrases.txt @@ -0,0 +1,305 @@ +"Phrases" +{ + "Prefix" + { + "es" "[{olive}Caster{default}]" + } + + "AllowAddons" + { + "es" "Los Addons ahora están {blue}permitidos {default}para los casters." + } + + "CasterEmpty" + { + "es" "No hay {red}casters registrados{default}." + } + + "CasterFound" + { + "#format" "{1:s}" + "es" "{blue}'{1}'{default} ya está registrado como caster" + } + + "CasterNoFound" + { + "#format" "{1:s}" + "es" "{red}'{1}'{default} no está registrado como caster" + } + + "CasterPlay" + { + "es" "Antes de jugar, debes {red}eliminar tu registro de caster{default}" + } + + "CasterReconnect" + { + "es" "Reconéctate para que tus {green}Addons funcionen{default}." + } + + "CasterReg" + { + "#format" "{1:s}" + "es" "{blue}'{1}'{default} fue registrado como caster" + } + + "CasterRegFrom" + { + "#format" "{1:N}" + "es" "{blue}Admin{default} ({olive}{1}{default}) te ha registrado como caster" + } + + "CasterRegError" + { + "#format" "{1:s}" + "es" "Error al registrar caster ({olive}{1}{default})" + } + + "CasterRemove" + { + "#format" "{1:s}" + "es" "{red}'{1}'{default} fue eliminado de la lista de casters" + } + + "CasterRemoveFrom" + { + "#format" "{1:N}" + "es" "{blue}Admin{default} ({olive}{1}{default}) te ha desregistrado como caster" + } + + "CasterRemoveError" + { + "#format" "{1:s}" + "es" "Error al eliminar caster ({olive}{1}{default})" + } + + "CasterReset" + { + "es" "Lista de {blue}casters{default} restablecida con éxito" + } + + "ForbidAddons" + { + "es" "Los Addons ahora están {red}prohibidos {default}para los casters." + } + + "ListPrinted" + { + "es" "Lista impresa en la {green}consola{default}" + } + + "MenuCastersList" + { + "es" "Lista de Casters" + } + + "MenuPlayersList" + { + "es" "Lista de Jugadores" + } + + "MenuWhitelistList" + { + "es" "Lista Blanca" + } + + "Reconnect" + { + "es" "Serás reconectado al servidor, ahora {green}verás una pantalla negra en lugar de una barra de carga{default}" + } + + "SQLCacheSuccess" + { + "es" "La caché de base de datos fue actualizada {green}exitosamente{default}" + } + + "SQLDisabled" + { + "es" "SQL {red}deshabilitado{default}." + } + + "SQLError" + { + "es" "{red}Error{default} en la consulta SQL" + } + + "SQLFound" + { + "#format" "{1:s}" + "es" "{blue}'{1}'{default} ya está registrado en la base de datos" + } + + "SQLNoFound" + { + "#format" "{1:s}" + "es" "{red}'{1}'{default} no está registrado en la base de datos" + } + + "SQLNoConnect" + { + "es" "{red}No se pudo{default} conectar a la base de datos." + } + + "SQLQueryError" + { + "es" "Error en la consulta a la base de datos" + } + + "SQLReg" + { + "#format" "{1:s}" + "es" "{blue}'{1}'{default} fue registrado en la base de datos" + } + + "SQLRegFrom" + { + "#format" "{1:N}" + "es" "{blue}Admin{default} ({olive}{1}{default}) te ha registrado en la base de datos" + } + + "SQLRemoved" + { + "#format" "{1:s}" + "es" "{red}'{1}'{default} fue eliminado de la base de datos" + } + + "SQLRemovedFrom" + { + "#format" "{1:N}" + "es" "{blue}Admin{default} ({olive}{1}{default}) te ha eliminado de la base de datos" + } + + "SQLResetSuccess" + { + "es" "La base de datos fue {green}restablecida{default} con éxito" + } + + "SQLTableCreated" + { + "es" "Tabla SQL creada exitosamente." + } + + "SelfRegCasterFound" + { + "es" "Ya estás {blue}registrado{default} como {blue}caster{default}." + } + + "SelfRegDisabled" + { + "es" "El auto-registro está {red}deshabilitado{default}." + } + + "SelfRegNoAdmin" + { + "es" "{red}Solo los admins{default} pueden registrar a otros jugadores." + } + + "SelfRegSuccess" + { + "es" "Te has {blue}registrado{default} como {blue}caster{default}." + } + + "SelfRegWhitelistNotFound" + { + "es" "No puedes {green}auto-registrarte{default} ya que {red}no estás en la lista blanca{default}." + } + + "AuthIdError" + { + "#format" "{1:s}" + "es" "El steamid ({red}'{1}'{default}) es inválido." + } + + "UnRegCasterNonAdmin" + { + "es" "Solo los {blue}admins{default} pueden eliminar a otros casters." + } + + "Use" + { + "es" "Usar" + } + + "UseNoCast" + { + "es" "Usa {green}!uncast{default}, para eliminar tu registro" + } + + "WhitelistEmpty" + { + "es" "No puedes {green}auto-registrarte{default} ya que {red}no hay jugadores en la lista blanca{default}." + } + + "WhitelistFound" + { + "#format" "{1:s}" + "es" "{blue}'{1}'{default} ya está en la lista blanca" + } + + "WhitelistNoFound" + { + "#format" "{1:s}" + "es" "{red}'{1}'{default} no está en la lista blanca" + } + + "WhitelistReg" + { + "#format" "{1:s}" + "es" "{blue}'{1}'{default} añadido a la lista blanca" + } + + "WhitelistRegFrom" + { + "#format" "{1:N}" + "es" "{blue}Admin{default} ({olive}{1}{default}) te ha añadido a la lista blanca" + } + + "WhitelistRegError" + { + "#format" "{1:s}" + "es" "Error al añadir a la lista blanca ({olive}{1}{default})" + } + + "WhitelistRemove" + { + "#format" "{1:s}" + "es" "{red}'{1}'{default} eliminado de la lista blanca" + } + + "WhitelistRemoveFrom" + { + "#format" "{1:N}" + "es" "{blue}Admin{default} ({olive}{1}{default}) te ha eliminado de la lista blanca" + } + + "WhitelistRemoveError" + { + "#format" "{1:s}" + "es" "Error al eliminar de la lista blanca ({olive}{1}{default})" + } + + "WhitelistReset" + { + "es" "{blue}Lista blanca{default} restablecida con éxito" + } + + "NoTargetsToRemove" + { + "es" "No se encontraron jugadores" + } + + "KickSpecsVoteTitle" + { + "es" "Expulsar a los espectadores que no son admins y Caster?" + } + + "KickSpecsVoteSuccess" + { + "es" "Adiós Espectadores!" + } + + "KickSpecsReason" + { + "es" "Sin espectadores, por favor!" + } +} \ No newline at end of file diff --git a/addons/sourcemod/translations/jp/caster_system.phrases.txt b/addons/sourcemod/translations/jp/caster_system.phrases.txt deleted file mode 100644 index 4395ec48b..000000000 --- a/addons/sourcemod/translations/jp/caster_system.phrases.txt +++ /dev/null @@ -1,149 +0,0 @@ -"Phrases" -{ -// The following uses bracket style color tags (see colors.inc) - "SelfCastNotAllowed" - { - "jp" "{blue}[{default}Cast{blue}] {default}あなたはこの{green}サーバー{default}では、キャスターに登録することは出来ません。" - } - - "SelfCast1" - { - "jp" "{blue}[{default}Cast{blue}] {default}あなたをキャスターに登録しました。" - } - - "SelfCast2" - { - "jp" "{blue}[{default}Cast{blue}] {default}アドオンを有効するには、サーバーの再接続が必要です。" - } - - "RegCasterTarget" - { - "#format" "{1:N}" - "jp" "{default}[{olive}!{default}] {blue}管理者{default}({olive}{1}{default}) があなたをキャスターに登録しました。" - } - - "Reconnect1" - { - "jp" "{blue}[{default}Reconnect{blue}] {default}サーバーへ再接続を行います.." - } - - "Reconnect2" - { - "jp" "{blue}[{default}Reconnect{blue}] {default}ローディングバーの代わりに黒い画面が表示されます!" - } - - "UnregCasterTarget" - { - "#format" "{1:N}" - "jp" "{default}[{olive}!{default}] {default}あなたは {blue}管理者{default}({olive}{1}{default}) にキャスターの登録が解除されました。" - } - - "KickSpecsAdmin" - { - "#format" "{1:N}" - "jp" "[{green}!{default}] {blue}観戦者{default}は {blue}管理者{default}({olive}{1}{default}) にキックされました。" - } - - "KickSpecsVoteSpec" - { - "jp" "[{olive}Cast{default}] {blue}観戦者{default}は {green}kicking specs{default} に投票することは出来ません。" - } - - "VoteInProgress" - { - "jp" "[{olive}Cast{default}] {olive}投票{green}行われています。{default}" - } - - "VoteDelay" - { - "#format" "{1:d}" - "jp" "[{olive}Cast{default}] 他の投票が行われており、残り {blue}{1}秒 {default}で投票が行えます。" - } - - "ForbidAddons" - { - "jp" "{blue}[{default}!{blue}] {default}キャスターはアドオンの使用が {green}禁止{default} されました。" - } - - "AllowAddons" - { - "jp" "{blue}[{default}!{blue}] {default}キャスターはアドオンの使用が {green}許可{default} されました。" - } - - "CasterCheck1" - { - "jp" "{default}<{olive}Cast{default}> ゲームをプレイするには、キャスターの登録を解除して下さい。" - } - - "CasterCheck2" - { - "jp" "{default}<{olive}Cast{default}> {green}!notcasting {default} / {green}!uncast {default}コマンドで解除" - } - - -// The following are not allowed to use any color tag - "RegCasterReply" - { - "#format" "{1:N}" - "jp" "{1} はキャスターとして登録済です。" - } - - "CasterSteamIDError" - { - "jp" "SteamIDが見つかりませんでした。 タイプミスが無いかどうか、プレイヤーがサーバーに接続されているかどうかを確認して下さい。" - } - - "UnregCasterNonAdmin" - { - "jp" "管理者のみが他のキャスターの解除が行えます。 あなたのキャスターの解除を行いたい場合は sm_notcasting を使用して下さい。" - } - - "UnregCasterSuccess" - { - "#format" "{1:N}" - "jp" "{1} はキャスターではなくなりました。" - } - - "CasterDBReset" - { - "jp" "[casters_database] リセットに成功しました。" - } - - "CasterDBAdd" - { - "#format" "{1:s}" - "jp" "[casters_database] '{1}' を追加しました。" - } - - "CasterDBRemove" - { - "#format" "{1:s}" - "jp" "[casters_database] '{1}' を削除しました。" - } - - "CasterDBFound" - { - "#format" "{1:s}" - "jp" "[casters_database] '{1}' は既に登録済です。" - } - - "CasterDBError" - { - "jp" "[casters_database] 引数が指定されていないか、バッファが空です。" - } - - "KickSpecsVoteTitle" - { - "jp" "観戦者全員(管理者・キャスターは除く)をキックしますか?" - } - - "KickSpecsVoteSuccess" - { - "jp" "観戦者全員をキックします..." - } - - "KickSpecsReason" - { - "jp" "観戦はお断りです!" - } -} \ No newline at end of file diff --git a/addons/sourcemod/translations/ko/caster_system.phrases.txt b/addons/sourcemod/translations/ko/caster_system.phrases.txt deleted file mode 100644 index d71f5a05d..000000000 --- a/addons/sourcemod/translations/ko/caster_system.phrases.txt +++ /dev/null @@ -1,149 +0,0 @@ -"Phrases" -{ -// The following uses bracket style color tags (see colors.inc) - "SelfCastNotAllowed" - { - "ko" "{blue}[{default}Cast{blue}] {default}당신은 {green}이 서버에{default} 캐스터 등록이 허가되지 않았습니다." - } - - "SelfCast1" - { - "ko" "{blue}[{default}Cast{blue}] {default}캐스터 등록이 완료되었습니다." - } - - "SelfCast2" - { - "ko" "{blue}[{default}Cast{blue}] {default}애드온을 활성화 하려면 재접속하십시오." - } - - "RegCasterTarget" - { - "#format" "{1:N}" - "ko" "{default}[{olive}!{default}] {blue}관리자 {default}({olive}{1}{default}) 가 당신을 캐스터로 등록하였습니다." - } - - "Reconnect1" - { - "ko" "{blue}[{default}Reconnect{blue}] {default}당신은 서버에 재접속 될 것 입니다.." - } - - "Reconnect2" - { - "ko" "{blue}[{default}Reconnect{blue}] {default}로딩화면 대신에 검은화면이 나옵니다!" - } - - "UnregCasterTarget" - { - "#format" "{1:N}" - "ko" "{default}[{olive}!{default}] {default}당신은 {blue}관리자 {default}({olive}{1}{default})에 의해 캐스터 등록이 해제되었습니다." - } - - "KickSpecsAdmin" - { - "#format" "{1:N}" - "ko" "[{green}!{default}] {blue}관리자 {default}({olive}{1}{default})에 의해{blue}관전자가 {default}추방되었습니다." - } - - "KickSpecsVoteSpec" - { - "ko" "[{olive}Cast{default}] {blue}관전자는 {green}관전자 추방 투표를 {default}할 수 없습니다." - } - - "VoteInProgress" - { - "ko" "[{olive}Cast{default}] {olive}투표가 {green}진행중{default} 입니다." - } - - "VoteDelay" - { - "#format" "{1:d}" - "ko" "[{olive}Cast{default}]{blue}{1}초 {default}후 투표를 시작할 수 있습니다." - } - - "ForbidAddons" - { - "ko" "{blue}[{default}!{blue}] 캐스터에 대한 에드온이 {green}금지됩니다{default}." - } - - "AllowAddons" - { - "ko" "{blue}[{default}!{blue}] 이제 캐스터에 대한 에드온이 {green}허용됩니다{default}." - } - - "CasterCheck1" - { - "ko" "{default}<{olive}Cast{default}> 캐스터는 플레이 하기 전에 등록을 취소해야됩니다." - } - - "CasterCheck2" - { - "ko" "{default}<{olive}Cast{default}> 사용 !notcasting / !uncast" - } - - -// The following are not allowed to use any color tag - "RegCasterReply" - { - "#format" "{1:N}" - "ko" "{1} 이 캐스터로 등록됨" - } - - "CasterSteamIDError" - { - "ko" "Steam ID를 찾지 못함. 오탈자 확인과 플레이어가 완전히 연결되게 하십시오." - } - - "UnregCasterNonAdmin" - { - "ko" "관리자만 다른 캐스터를 삭제할 수 있습니다. sm_notcasting 를 사용하여 스스로 캐스터 등록을 해제할 수 있습니다." - } - - "UnregCasterSuccess" - { - "#format" "{1:N}" - "ko" "{1} 은 더 이상 캐스터가 아닙니다." - } - - "CasterDBReset" - { - "ko" "[casters_database] 초기화 성공" - } - - "CasterDBAdd" - { - "#format" "{1:s}" - "ko" "[casters_database] '{1}' 추가됨" - } - - "CasterDBRemove" - { - "#format" "{1:s}" - "ko" "[casters_database] '{1}' 삭제됨" - } - - "CasterDBFound" - { - "#format" "{1:s}" - "ko" "[casters_database] '{1}' 이미 존재함" - } - - "CasterDBError" - { - "ko" "[casters_database] args가 명시되지 않음 / 비어있는 버퍼" - } - - "KickSpecsVoteTitle" - { - "ko" "관리자가 아니고 캐스터가 아닌 관전자를 추방하시겠습니까?" - } - - "KickSpecsVoteSuccess" - { - "ko" "잘가 관전자들!" - } - - "KickSpecsReason" - { - "ko" "관전자를 원하지 않습니다." - } -} \ No newline at end of file