[CSGO] TempEntityPreHook not working

Please post any questions about developing your plugin here. Please use the search function before posting!
User avatar
VinciT
Senior Member
Posts: 331
Joined: Thu Dec 18, 2014 2:41 am

[CSGO] TempEntityPreHook not working

Postby VinciT » Fri Mar 01, 2019 3:25 am

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);
User avatar
L'In20Cible
Project Leader
Posts: 1533
Joined: Sat Jul 14, 2012 9:29 pm
Location: Québec

Re: [CSGO] TempEntityPreHook not working

Postby L'In20Cible » Fri Mar 01, 2019 4:05 pm

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.
User avatar
VinciT
Senior Member
Posts: 331
Joined: Thu Dec 18, 2014 2:41 am

Re: [CSGO] TempEntityPreHook not working

Postby VinciT » Fri Mar 01, 2019 5:40 pm

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;
}
User avatar
L'In20Cible
Project Leader
Posts: 1533
Joined: Sat Jul 14, 2012 9:29 pm
Location: Québec

Re: [CSGO] TempEntityPreHook not working

Postby L'In20Cible » Fri Mar 01, 2019 6:10 pm

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.
User avatar
L'In20Cible
Project Leader
Posts: 1533
Joined: Sat Jul 14, 2012 9:29 pm
Location: Québec

Re: [CSGO] TempEntityPreHook not working

Postby L'In20Cible » Fri Mar 01, 2019 6:58 pm

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.
User avatar
VinciT
Senior Member
Posts: 331
Joined: Thu Dec 18, 2014 2:41 am

Re: [CSGO] TempEntityPreHook not working

Postby VinciT » Fri Mar 01, 2019 9:13 pm

Awesome! That's exactly what I needed. Thanks L'In20Cible.
User avatar
L'In20Cible
Project Leader
Posts: 1533
Joined: Sat Jul 14, 2012 9:29 pm
Location: Québec

Re: [CSGO] TempEntityPreHook not working

Postby L'In20Cible » Tue Mar 05, 2019 1:48 am

The hooks should be fixed in the next build.

Return to “Plugin Development Support”

Who is online

Users browsing this forum: No registered users and 40 guests