diff --git a/AFKManager/scripting/AFKManager.sp b/AFKManager/scripting/AFKManager.sp new file mode 100644 index 00000000..7f135d68 --- /dev/null +++ b/AFKManager/scripting/AFKManager.sp @@ -0,0 +1,441 @@ +#include <sourcemod> +#include <sdktools> +#include <cstrike> +#include <AFKManager> + +#undef REQUIRE_PLUGIN +#include <zombiereloaded> +#define REQUIRE_PLUGIN + +#pragma semicolon 1 +#pragma newdecls required + +#define AFK_CHECK_INTERVAL 5.0 + +bool g_Players_bEnabled[MAXPLAYERS + 1]; +bool g_Players_bFlagged[MAXPLAYERS + 1]; +int g_Players_iLastAction[MAXPLAYERS + 1]; +float g_Players_fEyePosition[MAXPLAYERS + 1][3]; +int g_Players_iButtons[MAXPLAYERS + 1]; +int g_Players_iSpecMode[MAXPLAYERS + 1]; +int g_Players_iSpecTarget[MAXPLAYERS + 1]; +int g_Players_iIgnore[MAXPLAYERS + 1]; + +enum +{ + IGNORE_EYEPOSITION = 1, + IGNORE_TEAMSWITCH = 2, + IGNORE_OBSERVER = 4 +} + +float g_fKickTime; +float g_fMoveTime; +float g_fWarnTime; +int g_iKickMinPlayers; +int g_iMoveMinPlayers; +int g_iImmunity; + +public Plugin myinfo = +{ + name = "Good AFK Manager", + author = "BotoX", + description = "A good AFK manager?", + version = "1.3", + url = "" +}; + +public void Cvar_KickTime(ConVar convar, const char[] oldValue, const char[] newValue) +{ + g_fKickTime = GetConVarFloat(convar); +} +public void Cvar_MoveTime(ConVar convar, const char[] oldValue, const char[] newValue) +{ + g_fMoveTime = GetConVarFloat(convar); +} +public void Cvar_WarnTime(ConVar convar, const char[] oldValue, const char[] newValue) +{ + g_fWarnTime = GetConVarFloat(convar); +} +public void Cvar_KickMinPlayers(ConVar convar, const char[] oldValue, const char[] newValue) +{ + g_iKickMinPlayers = GetConVarInt(convar); +} +public void Cvar_MoveMinPlayers(ConVar convar, const char[] oldValue, const char[] newValue) +{ + g_iMoveMinPlayers = GetConVarInt(convar); +} +public void Cvar_Immunity(ConVar convar, const char[] oldValue, const char[] newValue) +{ + g_iImmunity = GetConVarInt(convar); +} + +public void OnPluginStart() +{ + ConVar cvar; + HookConVarChange((cvar = CreateConVar("sm_afk_move_min", "10", "Min players for AFK move")), Cvar_MoveMinPlayers); + g_iMoveMinPlayers = GetConVarInt(cvar); + + HookConVarChange((cvar = CreateConVar("sm_afk_kick_min", "30", "Min players for AFK kick")), Cvar_KickMinPlayers); + g_iKickMinPlayers = GetConVarInt(cvar); + + HookConVarChange((cvar = CreateConVar("sm_afk_move_time", "60.0", "Time in seconds for AFK Move. 0 = DISABLED")), Cvar_MoveTime); + g_fMoveTime = GetConVarFloat(cvar); + + HookConVarChange((cvar = CreateConVar("sm_afk_kick_time", "120.0", "Time in seconds to AFK Kick. 0 = DISABLED")), Cvar_KickTime); + g_fKickTime = GetConVarFloat(cvar); + + HookConVarChange((cvar = CreateConVar("sm_afk_warn_time", "30.0", "Time in seconds remaining before warning")), Cvar_WarnTime); + g_fWarnTime = GetConVarFloat(cvar); + + HookConVarChange((cvar = CreateConVar("sm_afk_immunity", "1", "AFK admins immunity: 0 = DISABLED, 1 = COMPLETE, 2 = KICK, 3 = MOVE")), Cvar_Immunity); + g_iImmunity = GetConVarInt(cvar); + + CloseHandle(cvar); + + AddCommandListener(Command_Say, "say"); + AddCommandListener(Command_Say, "say_team"); + HookEvent("player_team", Event_PlayerTeamPost, EventHookMode_Post); + HookEvent("player_spawn", Event_PlayerSpawnPost, EventHookMode_Post); + HookEvent("player_death", Event_PlayerDeathPost, EventHookMode_Post); + HookEvent("round_end", Event_RoundEnd, EventHookMode_Pre); + + HookEntityOutput("trigger_teleport", "OnEndTouch", Teleport_OnEndTouch); + + AutoExecConfig(true, "plugin.AFKManager"); +} + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) +{ + CreateNative("GetClientIdleTime", Native_GetClientIdleTime); + RegPluginLibrary("AFKManager"); + + return APLRes_Success; +} + +public void OnMapStart() +{ + CreateTimer(AFK_CHECK_INTERVAL, Timer_CheckPlayer, _, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE); + + /* Handle late load */ + for(int i = 1; i <= MaxClients; i++) + { + if(IsClientConnected(i)) + { + OnClientConnected(i); + if(IsClientInGame(i) && IsClientAuthorized(i)) + OnClientPostAdminCheck(i); + } + } +} + +public void OnClientConnected(int client) +{ + ResetPlayer(client); +} + +public void OnClientPostAdminCheck(int client) +{ + if(!IsFakeClient(client)) + InitializePlayer(client); +} + +public void OnClientDisconnect(int client) +{ + ResetPlayer(client); +} + +int CheckAdminImmunity(int client) +{ + if(!IsClientAuthorized(client)) + return false; + + AdminId Id = GetUserAdmin(client); + return GetAdminFlag(Id, Admin_Generic); +} + +void ResetPlayer(int client) +{ + g_Players_bEnabled[client] = false; + g_Players_bFlagged[client] = false; + g_Players_iLastAction[client] = 0; + g_Players_fEyePosition[client] = view_as<float>({0.0, 0.0, 0.0}); + g_Players_iButtons[client] = 0; + g_Players_iIgnore[client] = 0; +} + +void InitializePlayer(int client) +{ + if(!(g_iImmunity == 1 && CheckAdminImmunity(client))) + { + ResetPlayer(client); + g_Players_iLastAction[client] = GetTime(); + g_Players_bEnabled[client] = true; + } +} + +public void Event_PlayerTeamPost(Handle event, const char[] name, bool dontBroadcast) +{ + int client = GetClientOfUserId(GetEventInt(event, "userid")); + if(client > 0 && !IsFakeClient(client)) + { + if(g_Players_iIgnore[client] & IGNORE_TEAMSWITCH) + g_Players_iIgnore[client] &= ~IGNORE_TEAMSWITCH; + else + g_Players_iLastAction[client] = GetTime(); + } +} + +public void Event_PlayerSpawnPost(Handle event, const char[] name, bool dontBroadcast) +{ + int client = GetClientOfUserId(GetEventInt(event, "userid")); + if(client > 0 && !IsFakeClient(client)) + g_Players_iIgnore[client] |= IGNORE_EYEPOSITION; +} + +public void Event_PlayerDeathPost(Handle event, const char[] name, bool dontBroadcast) +{ + int client = GetClientOfUserId(GetEventInt(event, "userid")); + if(client > 0 && !IsFakeClient(client)) + g_Players_iIgnore[client] |= IGNORE_OBSERVER; +} + +public void Event_RoundEnd(Handle event, const char[] name, bool dontBroadcast) +{ + for(int client = 1; client <= MaxClients; client++) + { + if(g_Players_bEnabled[client]) + g_Players_iIgnore[client] |= IGNORE_TEAMSWITCH; + } +} + +public Action Command_Say(int client, const char[] Command, int Args) +{ + g_Players_iLastAction[client] = GetTime(); + + return Plugin_Continue; +} + +public Action OnPlayerRunCmd(int client, int &iButtons, int &iImpulse, float fVel[3], float fAngles[3], int &iWeapon) +{ + if(IsClientObserver(client)) + { + int iSpecMode = g_Players_iSpecMode[client]; + int iSpecTarget = g_Players_iSpecTarget[client]; + + g_Players_iSpecMode[client] = GetEntProp(client, Prop_Send, "m_iObserverMode"); + g_Players_iSpecTarget[client] = GetEntPropEnt(client, Prop_Send, "m_hObserverTarget"); + + if(g_Players_iSpecMode[client] == 1) // OBS_MODE_DEATHCAM + g_Players_iIgnore[client] |= IGNORE_OBSERVER; + + if(iSpecTarget && g_Players_iSpecTarget[client] != iSpecTarget) + { + if(iSpecTarget == -1 || g_Players_iSpecTarget[client] == -1 || + !IsClientInGame(iSpecTarget) || !IsPlayerAlive(iSpecTarget)) + g_Players_iIgnore[client] |= IGNORE_OBSERVER; + } + + if((iSpecMode && g_Players_iSpecMode[client] != iSpecMode) || (iSpecTarget && g_Players_iSpecTarget[client] != iSpecTarget)) + { + if(g_Players_iIgnore[client] & IGNORE_OBSERVER) + g_Players_iIgnore[client] &= ~IGNORE_OBSERVER; + else + g_Players_iLastAction[client] = GetTime(); + } + } + + if(((g_Players_fEyePosition[client][0] != fAngles[0]) || + (g_Players_fEyePosition[client][1] != fAngles[1]) || + (g_Players_fEyePosition[client][2] != fAngles[2])) && + (!IsClientObserver(client) || + g_Players_iSpecMode[client] != 4)) // OBS_MODE_IN_EYE + { + if(!((iButtons & IN_LEFT) || (iButtons & IN_RIGHT))) + { + if(g_Players_iIgnore[client] & IGNORE_EYEPOSITION) + g_Players_iIgnore[client] &= ~IGNORE_EYEPOSITION; + else + g_Players_iLastAction[client] = GetTime(); + } + + g_Players_fEyePosition[client] = fAngles; + } + + if(g_Players_iButtons[client] != iButtons) + { + g_Players_iLastAction[client] = GetTime(); + g_Players_iButtons[client] = iButtons; + } + + return Plugin_Continue; +} + +public Action Teleport_OnEndTouch(const char[] output, int caller, int activator, float delay) +{ + if(activator < 1 || activator > MaxClients) + return Plugin_Continue; + + g_Players_iIgnore[activator] |= IGNORE_EYEPOSITION; + + return Plugin_Continue; +} + +public Action ZR_OnClientInfect(int &client, int &attacker, bool &motherInfect, bool &respawnOverride, bool &respawn) +{ + g_Players_iIgnore[client] |= IGNORE_TEAMSWITCH; +} + +public Action ZR_OnClientHuman(int &client, bool &respawn, bool &protect) +{ + g_Players_iIgnore[client] |= IGNORE_TEAMSWITCH; +} + +public Action Timer_CheckPlayer(Handle Timer, any Data) +{ + int client; + int Clients = 0; + + for(client = 1; client <= MaxClients; client++) + { + if(IsClientInGame(client) && !IsFakeClient(client)) + Clients++; + } + + bool bMovePlayers = (Clients >= g_iMoveMinPlayers && g_fMoveTime > 0.0); + bool bKickPlayers = (Clients >= g_iKickMinPlayers && g_fKickTime > 0.0); + + if(!bMovePlayers && !bKickPlayers) + return Plugin_Continue; + + for(client = 1; client <= MaxClients; client++) + { + if(!g_Players_bEnabled[client]) + continue; + + int iTeamNum = GetClientTeam(client); + + int IdleTime = GetTime() - g_Players_iLastAction[client]; + + if(g_Players_bFlagged[client] && (g_fKickTime - IdleTime) > 0.0) + { + PrintCenterText(client, "Welcome back!"); + PrintToChat(client, "\x04[AFK]\x01 You have been un-flagged for being inactive."); + g_Players_bFlagged[client] = false; + } + + if(bMovePlayers && iTeamNum > CS_TEAM_SPECTATOR && (!g_iImmunity || g_iImmunity == 2 || !CheckAdminImmunity(client))) + { + float iTimeleft = g_fMoveTime - IdleTime; + if(iTimeleft > 0.0) + { + if(iTimeleft <= g_fWarnTime) + { + PrintCenterText(client, "Warning: If you do not move in %d seconds, you will be moved to spectate.", RoundToFloor(iTimeleft)); + PrintToChat(client, "\x04[AFK]\x01 Warning: If you do not move in %d seconds, you will be moved to spectate.", RoundToFloor(iTimeleft)); + } + } + else + { + PrintToChatAll("\x04[AFK] \x03%N\x01 was moved to spectate for being AFK too long.", client); + ForcePlayerSuicide(client); + g_Players_iIgnore[client] |= IGNORE_TEAMSWITCH; + ChangeClientTeam(client, CS_TEAM_SPECTATOR); + } + } + else if(g_fKickTime > 0.0 && (!g_iImmunity || g_iImmunity == 3 || !CheckAdminImmunity(client))) + { + float iTimeleft = g_fKickTime - IdleTime; + if(iTimeleft > 0.0) + { + if(iTimeleft <= g_fWarnTime) + { + PrintCenterText(client, "Warning: If you do not move in %d seconds, you will be kick-flagged for being inactive.", RoundToFloor(iTimeleft)); + PrintToChat(client, "\x04[AFK]\x01 Warning: If you do not move in %d seconds, you will be kick-flagged for being inactive.", RoundToFloor(iTimeleft)); + } + } + else + { + if(!g_Players_bFlagged[client]) + { + PrintToChat(client, "\x04[AFK]\x01 You have been kick-flagged for being inactive."); + g_Players_bFlagged[client] = true; + } + int FlaggedPlayers = 0; + int Position = 1; + for(int client_ = 1; client_ <= MaxClients; client_++) + { + if(!g_Players_bFlagged[client_]) + continue; + + FlaggedPlayers++; + int IdleTime_ = GetTime() - g_Players_iLastAction[client_]; + + if(IdleTime_ > IdleTime) + Position++; + } + PrintCenterText(client, "You have been kick-flagged for being inactive. [%d/%d]", Position, FlaggedPlayers); + } + } + } + + while(bKickPlayers) + { + int InactivePlayer = -1; + int InactivePlayerTime = 0; + + for(client = 1; client <= MaxClients; client++) + { + if(!g_Players_bEnabled[client] || !g_Players_bFlagged[client]) + continue; + + int IdleTime = GetTime() - g_Players_iLastAction[client]; + if(IdleTime >= g_fKickTime && IdleTime > InactivePlayerTime) + { + InactivePlayer = client; + InactivePlayerTime = IdleTime; + } + } + + if(InactivePlayer == -1) + break; + else + { + PrintToChatAll("\x04[AFK] \x03%N\x01 was kicked for being AFK too long. (%d seconds)", InactivePlayer, InactivePlayerTime); + KickClient(InactivePlayer, "[AFK] You were kicked for being AFK too long. (%d seconds)", InactivePlayerTime); + Clients--; + g_Players_bFlagged[InactivePlayer] = false; + } + + bKickPlayers = (Clients >= g_iKickMinPlayers && g_fKickTime > 0.0); + } + + return Plugin_Continue; +} + +public int Native_GetClientIdleTime(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + + if(client > MaxClients || client <= 0) + { + ThrowNativeError(SP_ERROR_NATIVE, "Client is not valid."); + return -1; + } + + if(!IsClientInGame(client)) + { + ThrowNativeError(SP_ERROR_NATIVE, "Client is not in-game."); + return -1; + } + + if(IsFakeClient(client)) + { + ThrowNativeError(SP_ERROR_NATIVE, "Client is fake-client."); + return -1; + } + + if(!g_Players_bEnabled[client]) + return 0; + + return GetTime() - g_Players_iLastAction[client]; +} diff --git a/AFKManager/scripting/include/AFKManager.inc b/AFKManager/scripting/include/AFKManager.inc new file mode 100644 index 00000000..b2414a57 --- /dev/null +++ b/AFKManager/scripting/include/AFKManager.inc @@ -0,0 +1,24 @@ +#if defined _AFKManager_Included + #endinput +#endif +#define _AFKManager_Included + +native int GetClientIdleTime(int client); + +public SharedPlugin __pl_AFKManager = +{ + name = "AFKManager", + file = "AFKManager.smx", +#if defined REQUIRE_PLUGIN + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_PLUGIN +public __pl_AFKManager_SetNTVOptional() +{ + MarkNativeAsOptional("GetClientIdleTime"); +} +#endif diff --git a/ReservedSlot/scripting/ReservedSlot.sp b/ReservedSlot/scripting/ReservedSlot.sp new file mode 100644 index 00000000..57d23b14 --- /dev/null +++ b/ReservedSlot/scripting/ReservedSlot.sp @@ -0,0 +1,233 @@ +#include <sourcemod> +#include <cstrike> +#include <connect> + +#undef REQUIRE_PLUGIN +#include <AFKManager> +#include <GFLClanru> +#include <entWatch> +#define REQUIRE_PLUGIN + +#pragma semicolon 1 +#pragma newdecls required + +bool g_Plugin_AFKManager; +bool g_Plugin_GFLClanru; +bool g_Plugin_entWatch; + +int g_Client_Reservation[MAXPLAYERS + 1] = {0, ...}; + +public Plugin myinfo = +{ + name = "Reserved Slot", + author = "BotoX", + description = "Kicks someone to make space for a connecting donator.", + version = "1.0", + url = "" +}; + +public void OnPluginStart() +{ + /* Late load */ + for(int client = 1; client <= MaxClients; client++) + { + if(IsClientInGame(client) && !IsFakeClient(client) && IsClientAuthorized(client)) + OnClientPostAdminCheck(client); + } +} + +public void OnAllPluginsLoaded() +{ + g_Plugin_AFKManager = LibraryExists("AFKManager"); + g_Plugin_GFLClanru = LibraryExists("GFLClanru"); + g_Plugin_entWatch = LibraryExists("entWatch"); + + LogMessage("ReservedSlots capabilities:\nAFKManager: %s\nGFLClanru: %s\nentWatch: %s", + (g_Plugin_AFKManager ? "loaded" : "not loaded"), + (g_Plugin_GFLClanru ? "loaded" : "not loaded"), + (g_Plugin_entWatch ? "loaded" : "not loaded")); +} + +public void OnClientPostAdminCheck(int client) +{ + AdminId admin = GetUserAdmin(client); + if(admin == INVALID_ADMIN_ID) + return; + + if(GetAdminFlag(admin, Admin_Reservation)) + { + g_Client_Reservation[client] = GetAdminImmunityLevel(admin); + if(!g_Client_Reservation[client]) + g_Client_Reservation[client] = 1; + } +} + +public void OnClientDisconnect(int client) +{ + g_Client_Reservation[client] = 0; +} + +public EConnect OnClientPreConnectEx(const char[] sName, char sPassword[255], const char[] sIP, const char[] sSteam32ID, char sRejectReason[255]) +{ + // Server not full, nothing to do... + if(GetClientCount(false) < MaxClients) + return k_OnClientPreConnectEx_Accept; + + // Try to get precached admin id. + AdminId admin = FindAdminByIdentity(AUTHMETHOD_STEAM, sSteam32ID); + int Immunity = 0; + + // Valid and has reserved slot? + if(admin != INVALID_ADMIN_ID && GetAdminFlag(admin, Admin_Reservation)) + { + Immunity = GetAdminImmunityLevel(admin); + + if(!KickValidClient(sName, sSteam32ID, admin, Immunity)) + { + Format(sRejectReason, sizeof(sRejectReason), "No reserved slot available, sorry."); + return k_OnClientPreConnectEx_Reject; + } + else + return k_OnClientPreConnectEx_Accept; + } + + if(g_Plugin_GFLClanru) + { + DataPack pack = new DataPack(); + pack.WriteCell(admin); + pack.WriteCell(Immunity); + pack.WriteString(sName); + + AsyncHasSteamIDReservedSlot(sSteam32ID, AsyncHasSteamIDReservedSlotCallback, pack); + return k_OnClientPreConnectEx_Async; + } + + // Let the engine handle the rest. + return k_OnClientPreConnectEx_Accept; +} + +public void AsyncHasSteamIDReservedSlotCallback(const char[] sSteam32ID, int Result, any Data) +{ + DataPack pack = view_as<DataPack>(Data); + // Slot free'd up while waiting or doesn't have a reserved slot? + if(GetClientCount(false) < MaxClients || Result <= 0) + { + delete pack; + ClientPreConnectEx(sSteam32ID, k_OnClientPreConnectEx_Accept, ""); + return; + } + + pack.Reset(); + + AdminId admin = view_as<AdminId>(pack.ReadCell()); + int Immunity = pack.ReadCell(); + char sName[MAX_NAME_LENGTH]; + pack.ReadString(sName, sizeof(sName)); + + delete pack; + + if(Result > Immunity) + Immunity = Result; + + if(!KickValidClient(sName, sSteam32ID, admin, Immunity)) + ClientPreConnectEx(sSteam32ID, k_OnClientPreConnectEx_Reject, "No reserved slot available, sorry."); + else + ClientPreConnectEx(sSteam32ID, k_OnClientPreConnectEx_Accept, ""); +} + +stock bool KickValidClient(const char[] sName, const char[] sSteam32ID, AdminId admin, int Immunity) +{ + int HighestValue[4] = {0, ...}; + int HighestValueClient[4] = {0, ...}; + + for(int client = 1; client <= MaxClients; client++) + { + if(!IsClientInGame(client) || IsFakeClient(client)) + continue; + + int Donator = g_Client_Reservation[client]; + int ConnectionTime = RoundToNearest(GetClientTime(client)); + + int IdleTime; + if(g_Plugin_AFKManager) + IdleTime = GetClientIdleTime(client); + else // Fall back to highest connection time. + IdleTime = ConnectionTime; + + bool HasItem = false; + if(g_Plugin_entWatch) + HasItem = entWatch_HasSpecialItem(client); + + /* Spectators + * Sort by idle time and also kick donators if IdleTime > 30 + */ + if(GetClientTeam(client) <= CS_TEAM_SPECTATOR) + { + if(!Donator || IdleTime > 30) + { + if(IdleTime > HighestValue[0]) + { + HighestValue[0] = IdleTime; + HighestValueClient[0] = client; + } + } + } + /* Spectators */ + + /* Dead non-donator with IdleTime > 30 + * Sort by idle time and don't kick donators. + */ + if(!Donator && GetClientTeam(client) > CS_TEAM_SPECTATOR && !IsPlayerAlive(client)) + { + if(IdleTime > 30 && IdleTime > HighestValue[1]) + { + HighestValue[1] = IdleTime; + HighestValueClient[1] = client; + } + } + /* Dead non-donator with IdleTime > 30 */ + + /* Alive non-donator with IdleTime > 30 + * Sort by idle time and don't kick donators and item owners. + */ + if(!Donator && IsPlayerAlive(client) && !HasItem) + { + if(IdleTime > 30 && IdleTime > HighestValue[2]) + { + HighestValue[2] = IdleTime; + HighestValueClient[2] = client; + } + } + /* Alive non-donator with IdleTime > 30 */ + + /* Non-donator with highest connection time + * Sort by connection time and don't kick donators and item owners. + */ + if(!Donator && !HasItem) + { + if(ConnectionTime > HighestValue[3]) + { + HighestValue[3] = ConnectionTime; + HighestValueClient[3] = client; + } + } + /* Non-donator with highest connection time */ + } + + // Check if any condition was met in the correct order and perform kick + for(int i = 0; i < sizeof(HighestValue); i++) + { + if(HighestValue[i]) + { + ExecuteKickValidClient(HighestValueClient[i], sName, sSteam32ID, admin, Immunity); + return true; + } + } + + return false; +} + +stock void ExecuteKickValidClient(int client, const char[] sName, const char[] sSteam32ID, AdminId admin, int Immunity) +{ + KickClientEx(client, "Kicked for reserved slot (%s joined).", sName); +} diff --git a/ReservedSlot/scripting/include/AFKManager.inc b/ReservedSlot/scripting/include/AFKManager.inc new file mode 100644 index 00000000..4340bcbe --- /dev/null +++ b/ReservedSlot/scripting/include/AFKManager.inc @@ -0,0 +1 @@ +../../../AFKManager/scripting/include/AFKManager.inc \ No newline at end of file