#include #include #include #include #include #include #include #include #include #include #pragma semicolon 1 #pragma newdecls required Handle g_hCheckMutes = null; Handle g_hCookieTorchMuted = null; bool g_bWebclient[MAXPLAYERS + 1]; bool g_bReRunWebClientName; char g_sLastWebclientIp[64]; public Plugin myinfo = { name = "NoSteam CELT Voice override", author = "jenz", version = "1.5" }; public void OnPluginStart() { Format(g_sLastWebclientIp, sizeof(g_sLastWebclientIp), ""); g_hCheckMutes = CreateTimer(2.0, check_mutes, _, TIMER_REPEAT); g_hCookieTorchMuted = RegClientCookie("torch_muted", "is torch muted", CookieAccess_Protected); } public void OnPluginEnd() { if (g_hCheckMutes != null) { delete g_hCheckMutes; } } public void set_nosteam_listen_override(int client) { for (int j = 1; j <= MaxClients; j++) { if (!IsValidClient(j) || client == j || IsFakeClient(j)) continue; if (PM_IsPlayerSteam(j)) { //the nosteam client is not allowed to hear the steam client because broadcastvoicedata would ear rape them. //LogMessage("%N has muted %N", client, j); SetListenOverride(client, j, Listen_No); } } } public Action check_mutes(Handle timer, any data) { for (int i = 1; i <= MaxClients; i++) { if (!IsValidClient(i) || IsFakeClient(i)) { continue; } if (!PM_IsPlayerSteam(i)) { set_nosteam_listen_override(i); } for (int j = 1; j <= MaxClients; j++) { if (!IsValidClient(j) || i == j || IsFakeClient(j)) { continue; } bool isMuted = BaseComm_IsClientMuted(j); if (!isMuted) { isMuted = SelfMute_IsIgnoring(i, j); } //PrintToChatAll("i: %N. j: %N. isMuted: %i", i, j, isMuted); ClientMutedOtherClient(i, j, isMuted); } if (g_bReRunWebClientName && g_bWebclient[i]) { char dummy[64]; OnClientAuthorized(i, dummy); g_bReRunWebClientName = false; } } //sourceTV special case, index 0. for (int j = 1; j <= MaxClients; j++) { if (!IsValidClient(j) || IsFakeClient(j)) { continue; } //yeah its monkey work but server can probably handle it fine. char sBuffer[2]; GetClientCookie(j, g_hCookieTorchMuted, sBuffer, sizeof(sBuffer)); //PrintToChatAll("sBuffer: %s. j: %N", sBuffer, j); if(sBuffer[0] != '\0') { ClientMutedOtherClient(j, 0, true); } else { ClientMutedOtherClient(j, 0, false); } } //check if webclient has new IP every 2 seconds through endpoint. PollWebclientCurrentIp(); return Plugin_Continue; } //AI slop public void SendWebclientNameToSignaling(const char[] webclientName) { char url[256]; // Adjust port/path to match your signaling servers actual endpoint FormatEx(url, sizeof(url), "http://127.0.0.1:3000/webclient-name"); Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodPOST, url); if (hRequest == null) { return; } // Send the name as a POST body parameter SteamWorks_SetHTTPRequestGetOrPostParameter(hRequest, "name", webclientName); if (!SteamWorks_SetHTTPCallbacks(hRequest, OnWebclientNamePosted) || !SteamWorks_SendHTTPRequest(hRequest)) { delete hRequest; } } public int OnWebclientNamePosted(Handle hRequest, bool bFailure, bool bRequestSuccessful, EHTTPStatusCode eStatusCode, any data) { // Fire-and-forget: we dont care about the response, just clean up. delete hRequest; return 0; } public void OnMapStart() { g_bReRunWebClientName = true; } public void OnClientDisconnect(int client) { g_bWebclient[client] = false; } public void OnClientPostAdminCheck(int client) { g_bWebclient[client] = false; if (IsFakeClient(client)) { return; } char sIP[32]; GetClientIP(client, sIP, sizeof(sIP)); char allowed_ips[128]; ConVar sv_set_steam_id_ips = FindConVar("sv_set_steam_id_ips"); if (sv_set_steam_id_ips != null) { sv_set_steam_id_ips.GetString(allowed_ips, sizeof(allowed_ips)); if (StrEqual(sIP, allowed_ips)) { g_bWebclient[client] = true; //this works fine with one webclient only existing. char webclientName[64]; GetConVarString(FindConVar("sv_webclient_name"), webclientName, sizeof(webclientName)); SetClientName(client, webclientName); SendWebclientNameToSignaling(webclientName); } } } public void OnClientAuthorized(int client, const char[] auth) { if (IsFakeClient(client)) { return; } SetClientNoSteam(client, false); if (!PM_IsPlayerSteam(client)) { SetClientNoSteam(client, true); //LogMessage("here in onauthorized client: %N", client); //setting it too early does not work, so some delay needed. CreateTimer(10.0, Timer_SendVoiceInit, GetClientSerial(client)); CreateTimer(20.0, Timer_SendVoiceInit, GetClientSerial(client)); CreateTimer(30.0, Timer_SendVoiceInit, GetClientSerial(client)); CreateTimer(40.0, Timer_SendVoiceInit, GetClientSerial(client)); CreateTimer(50.0, Timer_SendVoiceInit, GetClientSerial(client)); CreateTimer(60.0, Timer_SendVoiceInit, GetClientSerial(client)); } } public Action CCC_OnChatMessage(int client, int author, const char[] message) { if (!g_bWebclient[client]) { return Plugin_Continue; } int index = FindCharInString(message, '"', false); int index1 = FindCharInString(message, ';', false); if (index != -1 || index1 != -1) { return Plugin_Handled; } return Plugin_Continue; } public Action Timer_SendVoiceInit(Handle timer, int Serial) { int client; if ((client = GetClientFromSerial(Serial)) == 0) { return Plugin_Handled; } if (IsValidClient(client)) { SendCeltVoiceInit(client); } return Plugin_Handled; } /** * Polls the signaling server for the current webclient controller's real IP. * If the IP has changed since the last * check and a webclient player is currently connected, forces them to * reconnect with the new password via ClientCommand. **/ public void PollWebclientCurrentIp() { Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodGET, "http://127.0.0.1:3000/webclient-current-ip"); if (hRequest == null) { return; } if (!SteamWorks_SetHTTPCallbacks(hRequest, OnWebclientCurrentIpReceived) || !SteamWorks_SendHTTPRequest(hRequest)) { delete hRequest; } } public int OnWebclientCurrentIpReceived(Handle hRequest, bool bFailure, bool bRequestSuccessful, EHTTPStatusCode eStatusCode, any data) { if (bFailure || !bRequestSuccessful || eStatusCode != k_EHTTPStatusCode200OK) { delete hRequest; return 0; } int bodySize; bool gotSize = SteamWorks_GetHTTPResponseBodySize(hRequest, bodySize); char newIp[64]; newIp[0] = '\0'; if (gotSize && bodySize > 0 && bodySize < sizeof(newIp)) { SteamWorks_GetHTTPResponseBodyData(hRequest, newIp, bodySize); } delete hRequest; if (newIp[0] == '\0') { return 0; } if (StrEqual(newIp, g_sLastWebclientIp)) { return 0; } for (int i = 1; i <= MaxClients; i++) { if (IsValidClient(i) && g_bWebclient[i]) { strcopy(g_sLastWebclientIp, sizeof(g_sLastWebclientIp), newIp); LogMessage("Forcing webclient reconnect for client %N with new IP: %s", i, newIp); ClientCommand(i, "retry"); break; } } return 0; } public EConnect OnClientPreConnectEx(const char[] sName, char sPassword[255], const char[] sIP, const char[] sSteam32ID, char sRejectReason[255]) { char allowedIp[64]; ConVar sv_set_steam_id_ips = FindConVar("sv_set_steam_id_ips"); if (sv_set_steam_id_ips != null) { sv_set_steam_id_ips.GetString(allowedIp, sizeof(allowedIp)); //this is to handle that the connect extension receives the password parameter for the webclient as its real connecting IP. //works both for the client command retry and for the case that somebody presses the connect button in the clients Menu as the same password //is applied again. if (StrEqual(sIP, allowedIp) && strlen(g_sLastWebclientIp) > 0) { strcopy(sPassword, 255, g_sLastWebclientIp); LogMessage("Backfilled empty password for webclient with cached IP: %s", g_sLastWebclientIp); } } // ET_LowEvent takes the LOWEST value returned across all plugins hooking this forward. //this plugin returns 1 (Accept), meanwhile the reserved slot plugin still might return 0 (reject) or -1 (async) to overrule the decision from this return value. return k_OnClientPreConnectEx_Accept; } stock bool IsValidClient(int client) { if (client > 0 && client <= MaxClients && IsClientConnected(client) && IsClientInGame(client)) return true; return false; }