Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[All Half-Life 1 games] Weapon empty sounds play twice #3231

Open
SamVanheer opened this issue Jan 23, 2022 · 1 comment
Open

[All Half-Life 1 games] Weapon empty sounds play twice #3231

SamVanheer opened this issue Jan 23, 2022 · 1 comment

Comments

@SamVanheer
Copy link

SamVanheer commented Jan 23, 2022

Half-Life, Opposing Force and Blue Shift have a bug where the empty/click sound for weapons plays twice. This happens because the sound is played by both the client and server:

halflife/dlls/weapons.cpp

Lines 1030 to 1039 in c7240b9

BOOL CBasePlayerWeapon :: PlayEmptySound( void )
{
if (m_iPlayEmptySound)
{
EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/357_cock1.wav", 0.8, ATTN_NORM);
m_iPlayEmptySound = 0;
return 0;
}
return 0;
}

BOOL CBasePlayerWeapon :: PlayEmptySound( void )
{
if (m_iPlayEmptySound)
{
HUD_PlaySound( "weapons/357_cock1.wav", 0.8 );
m_iPlayEmptySound = 0;
return 0;
}
return 0;
}

The higher the ping, the more obvious this becomes. In singleplayer it's unnoticeable, but at 20+ ping it's already apparent and sounds like the click sound is playing twice in succession.

The reason why the sound is played on the server side is because all players are supposed to be able to hear it. It plays on the client side so the predicted weapon behavior isn't affected by ping and plays the sound immediately.

Unfortunately the local player isn't excluded from the server's sound playback command so it plays twice.

To fix this we can use the player physics code's API to play the sound:

//In pm_shared.h
struct playermove_s;

extern "C" playermove_s* pmove;

//In util.h
/**
*	@brief Just like @see EMIT_SOUND_DYN, but will skip the current host player if they have cl_lw turned on.
*	@details entity must be the current host entity for this to work, and must be called only inside a player's PostThink method.
*/
void EMIT_SOUND_PREDICTED(edict_t* entity, int channel, const char* sample, float volume, float attenuation,
	int flags, int pitch);

//In sound.cpp
#include "pm_defs.h"
#include "pm_shared.h"

void EMIT_SOUND_PREDICTED(edict_t* entity, int channel, const char* sample, float volume, float attenuation,
	int flags, int pitch)
{
	//If entity is not a player this will return false.
	if (0 != g_engfuncs.pfnCanSkipPlayer(entity))
	{
		pmove->PM_PlaySound(channel, sample, volume, attenuation, flags, pitch);
	}
	else
	{
		EMIT_SOUND_DYN(entity, channel, sample, volume, attenuation, flags, pitch);
	}
}

//In weapons.cpp
int CBasePlayerWeapon::PlayEmptySound()
{
	if (m_iPlayEmptySound)
	{
		EMIT_SOUND_PREDICTED(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/357_cock1.wav", 0.8, ATTN_NORM, 0, PITCH_NORM);
		m_iPlayEmptySound = 0;
		return 0;
	}
	return 0;
}

PM_PlaySound works almost exactly like EMIT_SOUND does, with the notable difference that the player whose weapons code is currently being executed is excluded from receiving the sound playback command. It always plays the sound on the player entity's channels, which is what the original code did as well so this works perfectly.

This does however mean that PlayEmptySound can never be called outside of code executed by a player's PostThink method since the problems detailed by #3230 will occur.

@SamVanheer
Copy link
Author

Updated the fix to account for players being able to turn cl_lw off.

SamVanheer added a commit to twhl-community/halflife-updated that referenced this issue Jan 24, 2022
@SamVanheer SamVanheer reopened this May 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants