Page 1 of 1

[CSGO] TempEntityPreHook not working

Posted: Fri Mar 01, 2019 3:25 am
by VinciT
I'm trying to hook the TempEntity (Shotgun Shot) used for weapon tracers and sounds, but it doesn't seem to work.

Syntax: Select all

# ../temp_ent_hook_test/temp_ent_hook_test.py

# Source.Python
from core import echo_console
from effects.hooks import TempEntityPreHook


@TempEntityPreHook('Shotgun Shot')
def pre_shotgun_shot(temp_entity, recipient_filter):
echo_console('Someone fired a shot!')
This works in HL2DM, but not in CSGO.
I thought that maybe hooking "Shotgun Shot" was broken in CSGO only, but I found a SourceMod plugin that hooks the same TempEntity and it works.

Syntax: Select all

if (StrEqual(sGame, "cstrike") || StrEqual(sGame, "csgo"))
AddTempEntHook("Shotgun Shot", CSS_Hook_ShotgunShot);

Re: [CSGO] TempEntityPreHook not working

Posted: Fri Mar 01, 2019 4:05 pm
by L'In20Cible
VinciT wrote:I'm trying to hook the TempEntity (Shotgun Shot) used for weapon tracers and sounds, but it doesn't seem to work.

Syntax: Select all

# ../temp_ent_hook_test/temp_ent_hook_test.py

# Source.Python
from core import echo_console
from effects.hooks import TempEntityPreHook


@TempEntityPreHook('Shotgun Shot')
def pre_shotgun_shot(temp_entity, recipient_filter):
echo_console('Someone fired a shot!')
This works in HL2DM, but not in CSGO.

I've tested just now and the hook works, but that effect is not emitted on CS:GO. You can verify it by enabling net_graph 3 on your client and monitor the graph when a player is shooting nearby; there is no tempent being networked. My guess here is that this is done client-side when the clients are made aware of the shot (perhaps via the weapon_fire game event and they handle it themselves from there or something).
VinciT wrote:I thought that maybe hooking "Shotgun Shot" was broken in CSGO only, but I found a SourceMod plugin that hooks the same TempEntity and it works.

Syntax: Select all

if (StrEqual(sGame, "cstrike") || StrEqual(sGame, "csgo"))
AddTempEntHook("Shotgun Shot", CSS_Hook_ShotgunShot);
In the thread you linked, there is also people claiming it doesn't work for CS:GO, though.

Re: [CSGO] TempEntityPreHook not working

Posted: Fri Mar 01, 2019 5:40 pm
by VinciT
That's odd. There are no TempEntities being created but the hook does get called in SourceMod. Only when other players are firing their weapons though.
The plugin I linked does work. I even added a debug print line to make sure the TempEntHook gets called.

Here's the compiled plugin (couldn't attach to the post due to the file extension) and the source:

Syntax: Select all

#pragma semicolon 1

#include <sourcemod>
#include <sdktools>
//#undef REQUIRE_PLUGIN
//#include <csgo_colors>
#include <clientprefs>

#define PLUGIN_NAME "Toggle Weapon Sounds clientprefs"
#define PLUGIN_VERSION "1.0.3 fix m_iWeaponID + new syntax"

//#define UPDATE_URL "http://godtony.mooo.com/stopsound/stopsound.txt"

bool g_bStopSound[MAXPLAYERS+1]; bool g_bHooked;

Handle g_hClientCookie = INVALID_HANDLE;

public Plugin myinfo =
{
name = PLUGIN_NAME,
author = "GoD-Tony",
description = "Allows clients to stop hearing weapon sounds",
version = PLUGIN_VERSION,
url = "http://www.sourcemod.net/"
};

public OnPluginStart()
{
LoadTranslations("common.phrases");

g_hClientCookie = RegClientCookie("sm_stopsound", "Toggle hearing weapon sounds", CookieAccess_Private);
//SetCookiePrefabMenu(g_hClientCookie, CookieMenu_OnOff_Int, "Toggle Weapon Sounds", StopSoundCookieHandler);
SetCookieMenuItem(StopSoundCookieHandler, g_hClientCookie, "Toggle Weapon Sounds");

// Detect game and hook appropriate tempent.
char sGame[32];
GetGameFolderName(sGame, sizeof(sGame));

if (StrEqual(sGame, "cstrike") || StrEqual(sGame, "csgo"))
AddTempEntHook("Shotgun Shot", CSS_Hook_ShotgunShot);
else if (StrEqual(sGame, "dod"))
AddTempEntHook("FireBullets", DODS_Hook_FireBullets);

// TF2/HL2:DM and misc weapon sounds will be caught here.
AddNormalSoundHook(Hook_NormalSound);

CreateConVar("sm_stopsound_version", PLUGIN_VERSION, "Toggle Weapon Sounds", FCVAR_NOTIFY|FCVAR_DONTRECORD);
RegConsoleCmd("sm_stopsound", Command_StopSound, "Toggle hearing weapon sounds");

// Updater.
//if (LibraryExists("updater"))
//{
// Updater_AddPlugin(UPDATE_URL);
//}

for (new i = 1; i <= MaxClients; ++i)
{
if (!AreClientCookiesCached(i))
{
continue;
}

OnClientCookiesCached(i);
}
}

/*
public OnLibraryAdded(const String:name[])
{
if (StrEqual(name, "updater"))
{
Updater_AddPlugin(UPDATE_URL);
}
}
*/

public StopSoundCookieHandler(client, CookieMenuAction:action, any:info, char []buffer, maxlen)
{
switch (action)
{
case CookieMenuAction_DisplayOption:
{
}

case CookieMenuAction_SelectOption:
{
if(CheckCommandAccess(client, "sm_stopsound", 0))
{
PrepareMenu(client);
}
else
{
ReplyToCommand(client, "[SM] You have no access!");
}
}
}
}

PrepareMenu(client)
{
Handle menu = CreateMenu(YesNoMenu, MENU_ACTIONS_DEFAULT|MenuAction_DrawItem|MenuAction_DisplayItem|MenuAction_Display);
SetMenuTitle(menu, "Weapon Sounds -> ");
AddMenuItem(menu, "0", "On");
AddMenuItem(menu, "1", "Off");
SetMenuExitButton(menu, true);
DisplayMenu(menu, client, 20);
}

public YesNoMenu(Handle menu, MenuAction:action, param1, param2)
{
switch(action)
{
case MenuAction_DrawItem:
{
//PrintToServer("MenuAction_DrawItem");
if(_:g_bStopSound[param1] == param2)
{
//return ITEMDRAW_DISABLED;
return ITEMDRAW_SPACER;
}
}
case MenuAction_DisplayItem:
{
//PrintToServer("MenuAction_DisplayItem");
// Translate
char dispBuf[50];
GetMenuItem(menu, param2, "", 0, _, dispBuf, sizeof(dispBuf));
Format(dispBuf, sizeof(dispBuf), "%T", dispBuf, param1);
return RedrawMenuItem(dispBuf);
}
case MenuAction_Display:
{
//PrintToServer("MenuAction_Display");
char buffer[100];
GetMenuTitle(menu, buffer, sizeof(buffer));
Format(buffer, sizeof(buffer), "%s : %T", buffer, g_bStopSound[param1] ? "Off":"On", param1);
SetMenuTitle(menu, buffer);
}
case MenuAction_Select:
{
// Can still choose menu option using console cmd "menuselect" "1", lets recheck
// Or cvar change in this moment before player choose option

//PrintToServer("MenuAction_Select");
char info[50];
if( GetMenuItem(menu, param2, info, sizeof(info)) )
{
SetClientCookie(param1, g_hClientCookie, info);
g_bStopSound[param1] = StringToInt(info) != 0;
//CPrintToChat(param1, "{GREEN}[{BLUE}Weapons{GREEN}] {OLIVE}Sound from other players guns is now {RED}%s.", g_bStopSound[param1] ? "Off" : "On");
CheckHooks();
PrepareMenu(param1);
}
}
case MenuAction_Cancel:
{
//PrintToServer("MenuAction_Cancel");
if( param2 == MenuCancel_Exit ) // Exit go back !settings
{
ShowCookieMenu(param1);
}
}
case MenuAction_End:
{
//PrintToServer("MenuAction_End");
CloseHandle(menu);
}
}

return 0;
}

public OnClientCookiesCached(client)
{
char sValue[8];
GetClientCookie(client, g_hClientCookie, sValue, sizeof(sValue));

g_bStopSound[client] = (sValue[0] != '\0' && StringToInt(sValue));
CheckHooks();
}

public Action Command_StopSound(client, args)
{
if(AreClientCookiesCached(client))
{
PrepareMenu(client);
}
else
{
ReplyToCommand(client, "[SM] Your Cookies are not yet cached. Please try again later...");
}
//g_bStopSound[client] = !g_bStopSound[client];
//ReplyToCommand(client, "[SM] Weapon sounds %s.", g_bStopSound[client] ? "disabled" : "enabled");
//CheckHooks();

return Plugin_Handled;
}

public OnClientDisconnect_Post(client)
{
g_bStopSound[client] = false;
CheckHooks();
}

CheckHooks()
{
bool bShouldHook = false;

for (new i = 1; i <= MaxClients; i++)
{
if (g_bStopSound[i])
{
bShouldHook = true;
break;
}
}

// Fake (un)hook because toggling actual hooks will cause server instability.
g_bHooked = bShouldHook;
}

public Action Hook_NormalSound(clients[64], &numClients, char sample[PLATFORM_MAX_PATH], &entity, &channel, &Float:volume, &level, &pitch, &flags)
{
// Ignore non-weapon sounds.
if (!g_bHooked || !(strncmp(sample, "weapons", 7) == 0 || strncmp(sample[1], "weapons", 7) == 0))
return Plugin_Continue;

int i, j;

for (i = 0; i < numClients; i++)
{
if (g_bStopSound[clients[i]])
{
// Remove the client from the array.
for (j = i; j < numClients-1; j++)
{
clients[j] = clients[j+1];
}

numClients--;
i--;
}
}

return (numClients > 0) ? Plugin_Changed : Plugin_Stop;
}

public Action CSS_Hook_ShotgunShot(const char []te_name, const Players[], numClients, float delay)
{
if (!g_bHooked)
return Plugin_Continue;

// Check which clients need to be excluded.
decl newClients[MaxClients], client, i;
int newTotal = 0;

for (i = 0; i < numClients; i++)
{
client = Players[i];

if (!g_bStopSound[client])
{
newClients[newTotal++] = client;
}
}

// No clients were excluded.
if (newTotal == numClients)
return Plugin_Continue;

// All clients were excluded and there is no need to broadcast.
else if (newTotal == 0)
return Plugin_Stop;

// Re-broadcast to clients that still need it.
float vTemp[3];
TE_Start("Shotgun Shot");
TE_ReadVector("m_vecOrigin", vTemp);
TE_WriteVector("m_vecOrigin", vTemp);
TE_WriteFloat("m_vecAngles[0]", TE_ReadFloat("m_vecAngles[0]"));
TE_WriteFloat("m_vecAngles[1]", TE_ReadFloat("m_vecAngles[1]"));
TE_WriteNum("m_weapon", TE_ReadNum("m_weapon"));
TE_WriteNum("m_iMode", TE_ReadNum("m_iMode"));
TE_WriteNum("m_iSeed", TE_ReadNum("m_iSeed"));
TE_WriteNum("m_iPlayer", TE_ReadNum("m_iPlayer"));
TE_WriteFloat("m_fInaccuracy", TE_ReadFloat("m_fInaccuracy"));
TE_WriteFloat("m_fSpread", TE_ReadFloat("m_fSpread"));
TE_Send(newClients, newTotal, delay);

// Debug
PrintToServer("Someone fired a shot!");

return Plugin_Stop;
}

public Action DODS_Hook_FireBullets(const char []te_name, const Players[], numClients, float delay)
{
if (!g_bHooked)
return Plugin_Continue;

// Check which clients need to be excluded.
decl newClients[MaxClients], client, i;
int newTotal = 0;

for (i = 0; i < numClients; i++)
{
client = Players[i];

if (!g_bStopSound[client])
{
newClients[newTotal++] = client;
}
}

// No clients were excluded.
if (newTotal == numClients)
return Plugin_Continue;

// All clients were excluded and there is no need to broadcast.
else if (newTotal == 0)
return Plugin_Stop;

// Re-broadcast to clients that still need it.
float vTemp[3];
TE_Start("FireBullets");
TE_ReadVector("m_vecOrigin", vTemp);
TE_WriteVector("m_vecOrigin", vTemp);
TE_WriteFloat("m_vecAngles[0]", TE_ReadFloat("m_vecAngles[0]"));
TE_WriteFloat("m_vecAngles[1]", TE_ReadFloat("m_vecAngles[1]"));
TE_WriteNum("m_weapon", TE_ReadNum("m_weapon"));
TE_WriteNum("m_iMode", TE_ReadNum("m_iMode"));
TE_WriteNum("m_iSeed", TE_ReadNum("m_iSeed"));
TE_WriteNum("m_iPlayer", TE_ReadNum("m_iPlayer"));
TE_WriteFloat("m_flSpread", TE_ReadFloat("m_flSpread"));
TE_Send(newClients, newTotal, delay);

return Plugin_Stop;
}

Re: [CSGO] TempEntityPreHook not working

Posted: Fri Mar 01, 2019 6:10 pm
by L'In20Cible
VinciT wrote:That's odd. There are no TempEntities being created but the hook does get called in SourceMod. Only when other players are firing their weapons though.
The plugin I linked does work. I even added a debug print line to make sure the TempEntHook gets called.

Hm, Interesting. I will make some more testing tonight.

Re: [CSGO] TempEntityPreHook not working

Posted: Fri Mar 01, 2019 6:58 pm
by L'In20Cible
Okay, after further investigation; seems like they removed an extra layer call in their temp entities creation macro for CS:GO. The way it works on other games, is that they call CBaseTempEntity::Create:

Syntax: Select all

void CBaseTempEntity::Create( IRecipientFilter& filter, float delay )
{
// temp entities can't be reliable or part of the signon message, use real entities instead
Assert( !filter.IsReliable() && !filter.IsInitMessage() );
Assert( delay >= -1 && delay <= 1); // 1 second max delay

engine->PlaybackTempEntity( filter, delay,
(void *)this, GetServerClass()->m_pTable, GetServerClass()->m_ClassID );
}

Which does some assertions and forward the call to CEngineServer::PlaybackTempEntity. However, they seems to directly call the playback function on CS:GO. Since TempEntity[Pre/Post]Hook are registering on the former call, they are now silenced for some temp entities that are created by the engine. The following should gets called:

Syntax: Select all

from core import echo_console
from effects.base import TempEntity
from engines.server import engine_server
from memory import get_object_pointer
from memory import get_virtual_function
from memory.hooks import PreHook

@PreHook(get_virtual_function(engine_server, 'PlaybackTempEntity'))
def pre_playback_temp_entity(stack_data):
te = TempEntity(stack_data[3])
if te.name != 'Shotgun Shot':
return
echo_console('Someone fired a shot!')

They also seems to have removed the m_iWeaponID property from that class so retrieving weapon_id for the temp entity will throw an exception. Still doesn't explains why the net_graph is not catching any tempents. Perhaps because I was testing with bots, I don't know. Anyways, I will try to update the decorators so that they register on the last call in the chain but for now you can hook it yourself.

Re: [CSGO] TempEntityPreHook not working

Posted: Fri Mar 01, 2019 9:13 pm
by VinciT
Awesome! That's exactly what I needed. Thanks L'In20Cible.

Re: [CSGO] TempEntityPreHook not working

Posted: Tue Mar 05, 2019 1:48 am
by L'In20Cible
The hooks should be fixed in the next build.