518 lines
16 KiB
SourcePawn
518 lines
16 KiB
SourcePawn
#include <clientprefs>
|
|
#include <multicolors>
|
|
#include <sdkhooks>
|
|
#include <sourcemod>
|
|
#include <zombiereloaded>
|
|
#include <unloze_playtime>
|
|
#include <TransmitManager>
|
|
#include <leader>
|
|
#include <entWatch_core>
|
|
|
|
/* 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));
|
|
}
|