#include #include #include #include #include #include #include #include #include /* BOOLS */ bool g_bHasSomebodyToHide[MAXPLAYERS + 1]; bool g_bHasTobeVisible[MAXPLAYERS + 1]; bool g_bHidePlayers[MAXPLAYERS + 1][MAXPLAYERS + 1]; /* PER-TICK CACHED PLAYER STATE, refreshed at the top of UpdateHide so the pair loops are plain array reads */ bool g_bInGame[MAXPLAYERS + 1]; bool g_bFake[MAXPLAYERS + 1]; bool g_bAlive[MAXPLAYERS + 1]; bool g_bHuman[MAXPLAYERS + 1]; bool g_bZombie[MAXPLAYERS + 1]; bool g_bLeader[MAXPLAYERS + 1]; bool g_bHasItem[MAXPLAYERS + 1]; bool g_bHooked[MAXPLAYERS + 1]; bool g_bTargetEligible[MAXPLAYERS + 1]; float g_fOrigin[MAXPLAYERS + 1][3]; /* INTEGERS */ int g_iHideRange[MAXPLAYERS+1]; int g_iTierHide = 0; int g_iNextClient = 1; /* HANDLES */ Handle g_hTimer; /* CONVARS */ ConVar g_hCVar_HideEnabled; /* COOKIES */ Handle g_hCookie_HideRange; #define DISABLED -1 #define SHORT_RANGE 120 #define MEDIUM_RANGE 250 #define LONG_RANGE 500 int SHORT = SHORT_RANGE * SHORT_RANGE; int MEDIUM = MEDIUM_RANGE * MEDIUM_RANGE; int LONG = LONG_RANGE * LONG_RANGE; //---------------------------------------------------------------------------------------------------- // Purpose: //---------------------------------------------------------------------------------------------------- public Plugin myinfo = { name = "Hide Teammates with Transmit extension", author = "jenz, a bunch of re-used code from Neon. credits to pan for fixing it. and thx to zacade", description = "A plugin that can !hide teammates with individual distances", version = "1.0.0", url = "www.unloze.com" }; public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { MarkNativeAsOptional("GetPlayerTier_native"); return APLRes_Success; } //---------------------------------------------------------------------------------------------------- // Purpose: //---------------------------------------------------------------------------------------------------- public void OnPluginStart() { g_hCVar_HideEnabled = CreateConVar("sm_hide_enabled", "1", "", FCVAR_NONE, true, 0.0, true, 1.0); g_hCVar_HideEnabled.AddChangeHook(OnConVarChanged); AutoExecConfig(true); g_hCookie_HideRange = RegClientCookie("hide_range_transmitext", "", CookieAccess_Private); RegConsoleCmd("sm_hide", OnHideSettings, "Hiding near humans"); for(int client = 1; client <= MaxClients; client++) { if(IsValidClient(client)) { OnClientPutInServer(client); if(AreClientCookiesCached(client)) OnClientCookiesCached(client); } } SetCookieMenuItem(MenuHandler_CookieMenu, 0, "Hide"); //0.1s ticks each processing a tenth of the slots: every client still refreshes about once a second, //but the cost is spread over ten frames instead of one spike g_hTimer = CreateTimer(0.1, UpdateHide, INVALID_HANDLE, TIMER_REPEAT); g_iTierHide = FindTierForCommandOverride("sm_hide"); } public void OnPluginEnd() { if (g_hTimer != INVALID_HANDLE) delete g_hTimer; } //---------------------------------------------------------------------------------------------------- // Purpose: //---------------------------------------------------------------------------------------------------- public void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] newValue) { for(int client = 1; client <= MaxClients; client++) { g_bHasSomebodyToHide[client] = false; for(int target = 1; target <= MaxClients; target++) { g_bHidePlayers[client][target] = false; } } if(g_hCVar_HideEnabled.BoolValue) CPrintToChatAll("{cyan}[Hide] {white}has been allowed."); else CPrintToChatAll("{cyan}[Hide] {white}has been disabled."); } public Action UpdateHide(Handle timer) { if (!g_hCVar_HideEnabled.BoolValue) return Plugin_Continue; //early-out when nobody has hide enabled and no hide state is left to clean up bool bAnyWork = false; for (int i = 1; i <= MaxClients; i++) { if ((g_iHideRange[i] != DISABLED || g_bHasSomebodyToHide[i]) && IsClientInGame(i)) { bAnyWork = true; break; } } if (!bAnyWork) return Plugin_Continue; //cache the per-player state once per tick. everything the pair loops need depends on a single //player, so this costs natives once per player instead of once per pair. for (int i = 1; i <= MaxClients; i++) { g_bInGame[i] = IsClientInGame(i) && !IsClientSourceTV(i); if (!g_bInGame[i]) { g_bTargetEligible[i] = false; continue; } g_bFake[i] = IsFakeClient(i); g_bHooked[i] = TransmitManager_IsEntityHooked(i); g_bAlive[i] = IsPlayerAlive(i); g_bHuman[i] = g_bAlive[i] && ZR_IsClientHuman(i); g_bZombie[i] = g_bAlive[i] && ZR_IsClientZombie(i); g_bLeader[i] = Leader_Is(i); g_bHasItem[i] = EW_ClientHasItem(i); if (g_bAlive[i]) GetClientAbsOrigin(i, g_fOrigin[i]); //the target is not the leader, has no item, is alive and is a human. so consider him for the distance check. g_bTargetEligible[i] = g_bHuman[i] && !g_bLeader[i] && !g_bHasItem[i]; } //process a tenth of the slots per tick; combined with the 0.1s interval every client is //refreshed about once a second, like the old 1.0s full scan, without the frame spike. int iSlice = (MaxClients + 9) / 10; for (int n = 0; n < iSlice; n++) { int client = g_iNextClient; g_iNextClient = (g_iNextClient % MaxClients) + 1; ProcessHideClient(client); } return Plugin_Continue; } void ProcessHideClient(int client) { if (!g_bInGame[client] || g_bFake[client]) return; if (g_iHideRange[client] == DISABLED) //client just disabled hide right now. the bool will pass once until they enabled hide again. { if (g_bHasSomebodyToHide[client] && g_bHooked[client]) { for (int target = 1; target <= MaxClients; target++) { if (client != target && g_bInGame[target] && g_bHooked[target]) { TransmitManager_SetEntityState(target, client, true); //client can see all players again g_bHidePlayers[client][target] = false; } } g_bHasSomebodyToHide[client] = false; } return; } //lazy fix for invisible zm if (g_bHooked[client]) { for (int target = 1; target <= MaxClients; target++) { if (g_bHidePlayers[client][target] && client != target && g_bInGame[target] && g_bZombie[target] && g_bHooked[target]) { g_bHidePlayers[client][target] = false; TransmitManager_SetEntityState(target, client, true); //client can see player again because the player is a zm } } } bool ClientIsAlive = g_bAlive[client]; bool ClientIsHuman = g_bHuman[client]; bool ClientIsLeader = g_bLeader[client]; bool ClientHasItem = g_bHasItem[client]; //player is leader, has an item, is not alive or is not human, set the transmit state once via the bool so all can see the player. if (ClientIsLeader || ClientHasItem || !ClientIsAlive || !ClientIsHuman) { if (!g_bHasTobeVisible[client] && g_bHooked[client]) { for (int target = 1; target <= MaxClients; target++) { if (client != target && g_bInGame[target] && g_bHooked[target]) { //all must see the leader, the zombie and the item user. TransmitManager_SetEntityState(client, target, true); //all can see the player again //if the player is dead or not human they must see all players again if (!ClientIsAlive || !ClientIsHuman) { TransmitManager_SetEntityState(target, client, true); //client can see all players again } } } g_bHasTobeVisible[client] = true; } //but the human leader and human item users may still want to hide the remaining human players. if (ClientIsAlive && ClientIsHuman && (ClientIsLeader || ClientHasItem)) { DoTheTargetCheck(client); } return; } g_bHasTobeVisible[client] = false; //all the remaining checks for clients with hide enabled that are alive humans and not leaders or item users. DoTheTargetCheck(client); } public void DoTheTargetCheck(int client) { g_bHasSomebodyToHide[client] = false; float fRangeSquared = float(g_iHideRange[client]); float fClientX = g_fOrigin[client][0]; float fClientY = g_fOrigin[client][1]; float fClientZ = g_fOrigin[client][2]; bool bClientHooked = g_bHooked[client]; for (int target = 1; target <= MaxClients; target++) { if (target == client || !g_bTargetEligible[target]) { continue; } float fDeltaX = g_fOrigin[target][0] - fClientX; float fDeltaY = g_fOrigin[target][1] - fClientY; float fDeltaZ = g_fOrigin[target][2] - fClientZ; if (fDeltaX * fDeltaX + fDeltaY * fDeltaY + fDeltaZ * fDeltaZ <= fRangeSquared) { //we only have to check the hook when the state of the hidePlayers boolean changed if (!g_bHidePlayers[client][target] && bClientHooked && g_bHooked[target]) { //the client here cannot be a fakeclient. thats already checked. TransmitManager_SetEntityState(target, client, false); //the player is hiding the teammate due to distance g_bHidePlayers[client][target] = true; } g_bHasSomebodyToHide[client] = true; //theres at least one target to hide for the player, the teammate } else if (g_bHidePlayers[client][target] && bClientHooked && g_bHooked[target]) { TransmitManager_SetEntityState(target, client, true); //the player is seeing the teammate due to distance g_bHidePlayers[client][target] = false; } } } public Action OnHideSettings(int client, int args) { ShowSettingsMenu(client); return Plugin_Handled; } //---------------------------------------------------------------------------------------------------- // Purpose: //---------------------------------------------------------------------------------------------------- public void ShowSettingsMenu(int client) { Menu menu = new Menu(MenuHandler_MainMenu); char titlemsg[256]; Format(titlemsg, sizeof(titlemsg), "Hide"); menu.SetTitle(titlemsg, client); char sBuffer[128]; Format(sBuffer, sizeof(sBuffer), "Disabled%s", (g_iHideRange[client] == DISABLED) ? " [Selected]" : ""); menu.AddItem("", sBuffer, (g_iHideRange[client] == DISABLED)); Format(sBuffer, sizeof(sBuffer), "Short Range%s", (g_iHideRange[client] == SHORT) ? " [Selected]" : ""); menu.AddItem("", sBuffer, (g_iHideRange[client] == SHORT)); Format(sBuffer, sizeof(sBuffer), "Medium Range%s", (g_iHideRange[client] == MEDIUM) ? " [Selected]" : ""); menu.AddItem("", sBuffer, (g_iHideRange[client] == MEDIUM)); Format(sBuffer, sizeof(sBuffer), "Long Range%s", (g_iHideRange[client] == LONG) ? " [Selected]" : ""); menu.AddItem("", sBuffer, (g_iHideRange[client] == LONG)); if(!g_hCVar_HideEnabled.BoolValue) menu.AddItem("", "Warning: Hide is currently disabled on the server", ITEMDRAW_DISABLED); menu.ExitBackButton = true; menu.Display(client, MENU_TIME_FOREVER); } //---------------------------------------------------------------------------------------------------- // Purpose: //---------------------------------------------------------------------------------------------------- public int MenuHandler_MainMenu(Menu menu, MenuAction action, int client, int selection) { switch(action) { case(MenuAction_Select): { switch(selection) { case(0): { g_iHideRange[client] = DISABLED } case(1): { g_iHideRange[client] = SHORT } case(2): { g_iHideRange[client] = MEDIUM } case(3): { g_iHideRange[client] = LONG } } char sBuffer[16]; Format(sBuffer, sizeof(sBuffer), "%d", g_iHideRange[client]); SetClientCookie(client, g_hCookie_HideRange, sBuffer); ShowSettingsMenu(client); } case(MenuAction_Cancel): { ShowCookieMenu(client); } case(MenuAction_End): { delete menu; } } return 0; } stock int FindTierForCommandOverride(const char[] command) { char sPath[PLATFORM_MAX_PATH]; BuildPath(Path_SM, sPath, sizeof(sPath), "configs/admin_groups.cfg"); KeyValues kv = new KeyValues("Groups"); if (!kv.ImportFromFile(sPath)) { delete kv; return -1; } // collect tier group names first char groupNames[32][64]; int groupCount = 0; if (kv.GotoFirstSubKey()) { do { char sName[64]; kv.GetSectionName(sName, sizeof(sName)); if (StrContains(sName, "tier", false) == 0) { strcopy(groupNames[groupCount], 64, sName); groupCount++; } } while (kv.GotoNextKey() && groupCount < 32); } int foundTier = -1; for (int i = 0; i < groupCount; i++) { kv.Rewind(); if (!kv.JumpToKey(groupNames[i])) continue; if (!kv.JumpToKey("Overrides")) continue; if (!kv.GotoFirstSubKey(false)) continue; do { char overrideCmd[64]; kv.GetSectionName(overrideCmd, sizeof(overrideCmd)); if (StrEqual(overrideCmd, command, false)) { char tierStr[8]; strcopy(tierStr, sizeof(tierStr), groupNames[i][4]); // skip "tier" foundTier = StringToInt(tierStr); break; } } while (kv.GotoNextKey(false)); if (foundTier != -1) break; } delete kv; return foundTier; } //---------------------------------------------------------------------------------------------------- // Purpose: //---------------------------------------------------------------------------------------------------- public void MenuHandler_CookieMenu(int client, CookieMenuAction action, any info, char[] buffer, int maxlen) { if (GetFeatureStatus(FeatureType_Native, "GetPlayerTier_native") == FeatureStatus_Available) { int client_tier = GetPlayerTier_native(client); if (g_iTierHide > client_tier) { return; } } switch(action) { case(CookieMenuAction_DisplayOption): { Format(buffer, maxlen, "Hide", client); } case(CookieMenuAction_SelectOption): { ShowSettingsMenu(client); } } } public void OnClientDisconnect(int client) { g_iHideRange[client] = 0; g_bHasSomebodyToHide[client] = false; g_bHasTobeVisible[client] = false; for(int target = 1; target <= MaxClients; target++) { g_bHidePlayers[client][target] = false; } } public void OnClientPutInServer(int client) { if(!g_hCVar_HideEnabled.BoolValue) return; g_bHasTobeVisible[client] = false; if (!IsClientSourceTV(client)) { TransmitManager_AddEntityHooks(client); } } public void OnClientCookiesCached(int client) { char sBuffer[16]; GetClientCookie(client, g_hCookie_HideRange, sBuffer, sizeof(sBuffer)); if (sBuffer[0]) { g_iHideRange[client] = StringToInt(sBuffer); if (g_iHideRange[client] != SHORT && g_iHideRange[client] != MEDIUM && g_iHideRange[client] != LONG) g_iHideRange[client] = DISABLED; } else g_iHideRange[client] = DISABLED; } stock bool IsValidClient(int client) { //IsClientInGame implies IsClientConnected return (client > 0 && client <= MaxClients && IsClientInGame(client)); }