diff --git a/extensions/sdktools/extension.cpp b/extensions/sdktools/extension.cpp index a8e0d025..061010cf 100644 --- a/extensions/sdktools/extension.cpp +++ b/extensions/sdktools/extension.cpp @@ -66,6 +66,7 @@ IPlayerInfoManager *playerinfomngr = NULL; ICvar *icvar = NULL; IServer *iserver = NULL; CGlobalVars *gpGlobals; +ISoundEmitterSystemBase *soundemitterbase = NULL; #if SOURCE_ENGINE >= SE_ORANGEBOX IServerTools *servertools = NULL; @@ -258,6 +259,7 @@ bool SDKTools::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlen, bool #if SOURCE_ENGINE >= SE_ORANGEBOX GET_V_IFACE_ANY(GetServerFactory, servertools, IServerTools, VSERVERTOOLS_INTERFACE_VERSION); #endif + GET_V_IFACE_ANY(GetEngineFactory, soundemitterbase, ISoundEmitterSystemBase, SOUNDEMITTERSYSTEM_INTERFACE_VERSION); gpGlobals = ismm->GetCGlobals(); enginePatch = SH_GET_CALLCLASS(engine); diff --git a/extensions/sdktools/extension.h b/extensions/sdktools/extension.h index d1c99d47..97b9f45b 100644 --- a/extensions/sdktools/extension.h +++ b/extensions/sdktools/extension.h @@ -50,6 +50,7 @@ #include #include #include +#include "SoundEmitterSystem/isoundemittersystembase.h" #if SOURCE_ENGINE >= SE_ORANGEBOX #include @@ -124,6 +125,7 @@ extern CGlobalVars *gpGlobals; #if SOURCE_ENGINE >= SE_ORANGEBOX extern IServerTools *servertools; #endif +extern ISoundEmitterSystemBase *soundemitterbase; /* Interfaces from SourceMod */ extern IBinTools *g_pBinTools; extern IGameConfig *g_pGameConf; diff --git a/extensions/sdktools/vsound.cpp b/extensions/sdktools/vsound.cpp index 3e190abb..a015c385 100644 --- a/extensions/sdktools/vsound.cpp +++ b/extensions/sdktools/vsound.cpp @@ -512,6 +512,74 @@ RETURN_META_NEWPARAMS( #endif } +bool GetSoundParams(CSoundParameters *soundParams, const char *soundname, cell_t entindex) +{ + if ( !soundname[0] ) + return false; + +#if SOURCE_ENGINE >= SE_PORTAL2 + HSOUNDSCRIPTHASH index = (HSOUNDSCRIPTHASH)soundemitterbase->GetSoundIndex(soundname); +#else + HSOUNDSCRIPTHANDLE index = (HSOUNDSCRIPTHANDLE)soundemitterbase->GetSoundIndex(soundname); +#endif + if (!soundemitterbase->IsValidIndex(index)) + return false; + + gender_t gender = GENDER_NONE; + + // I don't know if gender applies to any mutliplayer games, but just in case... + // Of course, if it's SOUND_FROM_PLAYER, we have no idea which gender it is + int ent = SoundReferenceToIndex(entindex); + if (ent > 0) + { + edict_t *edict = gamehelpers->EdictOfIndex(ent); + if (edict != NULL && !edict->IsFree()) + { + IServerEntity *serverEnt = edict->GetIServerEntity(); + if (serverEnt != NULL) + { + const char *actormodel = STRING(serverEnt->GetModelName()); + gender = soundemitterbase->GetActorGender(actormodel); + } + } + } + + return soundemitterbase->GetParametersForSoundEx(soundname, index, *soundParams, gender); +} + +bool InternalPrecacheScriptSound(const char *soundname) +{ + int soundIndex = soundemitterbase->GetSoundIndex(soundname); + if (!soundemitterbase->IsValidIndex(soundIndex)) + { + return false; + } + + CSoundParametersInternal *internal = soundemitterbase->InternalGetParametersForSound(soundIndex); + + if (!internal) + return false; + + int waveCount = internal->NumSoundNames(); + + if (!waveCount) + { + return false; + } + + for (int wave = 0; wave < waveCount; wave++) + { + const char* waveName = soundemitterbase->GetWaveName(internal->GetSoundNames()[wave].symbol); + // return true even if we precache no new wavs + if (!engsound->IsSoundPrecached(waveName)) + { + engsound->PrecacheSound(waveName); + } + } + + return true; +} + /************************ * * * Sound Related Natives * @@ -1135,6 +1203,51 @@ static cell_t smn_GetDistGainFromSoundLevel(IPluginContext *pContext, const cell return sp_ftoc(engsound->GetDistGainFromSoundLevel((soundlevel_t)decibel, distance)); } +// native bool:GetGameSoundParams(const String:gameSound[], &channel, &soundLevel, &Float:volume, &pitch, String:sample[], maxlength, entity=SOUND_FROM_WORLD) +static cell_t smn_GetGameSoundParams(IPluginContext *pContext, const cell_t *params) +{ + char *soundname; + pContext->LocalToString(params[1], &soundname); + + CSoundParameters soundParams; + + if (!GetSoundParams(&soundParams, soundname, params[8])) + return false; + + cell_t *channel; + cell_t *fakeVolume; + cell_t *pitch; + cell_t *soundLevel; + + pContext->LocalToPhysAddr(params[2], &channel); + pContext->LocalToPhysAddr(params[3], &soundLevel); + pContext->LocalToPhysAddr(params[4], &fakeVolume); + pContext->LocalToPhysAddr(params[5], &pitch); + + *channel = soundParams.channel; + *pitch = soundParams.pitch; + *soundLevel = (cell_t)soundParams.soundlevel; + *fakeVolume = sp_ftoc(soundParams.volume); + + pContext->StringToLocal(params[6], params[7], soundParams.soundname); + + // Precache the sound we're returning + if (!engsound->IsSoundPrecached(soundParams.soundname)) + { + InternalPrecacheScriptSound(soundname); + } + + return true; +} + +// native bool:PrecacheScriptSound(const String:soundname[]) +static cell_t smn_PrecacheScriptSound(IPluginContext *pContext, const cell_t *params) +{ + char *soundname; + pContext->LocalToString(params[1], &soundname); + return InternalPrecacheScriptSound(soundname); +} + sp_nativeinfo_t g_SoundNatives[] = { {"EmitAmbientSound", EmitAmbientSound}, @@ -1149,5 +1262,7 @@ sp_nativeinfo_t g_SoundNatives[] = {"RemoveAmbientSoundHook", smn_RemoveAmbientSoundHook}, {"RemoveNormalSoundHook", smn_RemoveNormalSoundHook}, {"GetDistGainFromSoundLevel", smn_GetDistGainFromSoundLevel}, + {"GetGameSoundParams", smn_GetGameSoundParams}, + {"PrecacheScriptSound", smn_PrecacheScriptSound}, {NULL, NULL}, }; diff --git a/plugins/include/sdktools_sound.inc b/plugins/include/sdktools_sound.inc index 8189ee55..59d14917 100644 --- a/plugins/include/sdktools_sound.inc +++ b/plugins/include/sdktools_sound.inc @@ -448,3 +448,229 @@ stock ATTN_TO_SNDLEVEL(Float:attn) } return 0; } + +/** + * Retrieves the parameters for a game sound. + * + * Game sounds are found in a game's scripts/game_sound.txt or other files + * referenced from it + * + * Note that if a game sound has a rndwave section, one of them will be returned + * at random. + * + * @param gameSound Name of game sound. + * @param channel Channel to emit with. + * @param level Sound level. + * @param volume Sound volume. + * @param pitch Sound pitch. + * @param sample Sound file name relative to the "sounds" folder. + * @param maxlength Maximum length of sample string buffer. + * @param entity Entity the sound is being emitted from. + * @return True if the sound was successfully retrieved, false if it + * was not found + */ +native bool:GetGameSoundParams(const String:gameSound[], + &channel, + &soundLevel, + &Float:volume, + &pitch, + String:sample[], + maxlength, + entity=SOUND_FROM_PLAYER); + +/** + * Emits a game sound to a list of clients. + * + * Game sounds are found in a game's scripts/game_sound.txt or other files + * referenced from it + * + * Note that if a game sound has a rndwave section, one of them will be returned + * at random. + * + * @param clients Array of client indexes. + * @param numClients Number of clients in the array. + * @param gameSound Name of game sound. + * @param entity Entity to emit from. + * @param flags Sound flags. + * @param speakerentity Unknown. + * @param origin Sound origin. + * @param dir Sound direction. + * @param updatePos Unknown (updates positions?) + * @param soundtime Alternate time to play sound for. + * @return True if the sound was played successfully, false if it failed + * @error Invalid client index. + */ +stock bool:EmitGameSound(const clients[], + numClients, + const String:gameSound[], + entity = SOUND_FROM_PLAYER, + flags = SND_NOFLAGS, + speakerentity = -1, + const Float:origin[3] = NULL_VECTOR, + const Float:dir[3] = NULL_VECTOR, + bool:updatePos = true, + Float:soundtime = 0.0) +{ + new channel; + new level; + new Float:volume; + new pitch; + new String:sample[PLATFORM_MAX_PATH]; + + if (GetGameSoundParams(gameSound, channel, level, volume, pitch, sample, sizeof(sample), entity)) + { + EmitSound(clients, numClients, sample, entity, channel, level, flags, volume, pitch, speakerentity, origin, dir, updatePos, soundtime); + return true; + } + else + { + return false; + } +} + +/** + * Emits an ambient game sound. + * + * Game sounds are found in a game's scripts/game_sound.txt or other files + * referenced from it + * + * Note that if a game sound has a rndwave section, one of them will be returned + * at random. + * + * @param gameSound Name of game sound. + * @param pos Origin of sound. + * @param entity Entity index to associate sound with. + * @param flags Sound flags. + * @param delay Play delay. + * @noreturn + */ +stock bool:EmitAmbientGameSound(const String:gameSound[], + const Float:pos[3], + entity = SOUND_FROM_WORLD, + flags = SND_NOFLAGS, + Float:delay = 0.0) +{ + new channel; // This is never actually used for Ambients, but it's a mandatory field to GetGameSoundParams + new level; + new Float:volume; + new pitch; + new String:sample[PLATFORM_MAX_PATH]; + + if (GetGameSoundParams(gameSound, channel, level, volume, pitch, sample, sizeof(sample), entity)) + { + EmitAmbientSound(sample, pos, entity, level, flags, volume, pitch, delay); + return true; + } + else + { + return false; + } +} + +/** + * Wrapper to emit a game sound to one client. + * + * Game sounds are found in a game's scripts/game_sound.txt or other files + * referenced from it + * + * Note that if a game sound has a rndwave section, one of them will be returned + * at random. + * + * @param client Client index. + * @param gameSound Name of game sound. + * @param entity Entity to emit from. + * @param flags Sound flags. + * @param speakerentity Unknown. + * @param origin Sound origin. + * @param dir Sound direction. + * @param updatePos Unknown (updates positions?) + * @param soundtime Alternate time to play sound for. + * @noreturn + * @error Invalid client index. + */ +stock bool:EmitGameSoundToClient(client, + const String:gameSound[], + entity = SOUND_FROM_PLAYER, + flags = SND_NOFLAGS, + speakerentity = -1, + const Float:origin[3] = NULL_VECTOR, + const Float:dir[3] = NULL_VECTOR, + bool:updatePos = true, + Float:soundtime = 0.0) +{ + new clients[1]; + clients[0] = client; + /* Save some work for SDKTools and remove SOUND_FROM_PLAYER references */ + entity = (entity == SOUND_FROM_PLAYER) ? client : entity; + return EmitGameSound(clients, 1, gameSound, entity, flags, + speakerentity, origin, dir, updatePos, soundtime); +} + +/** + * Wrapper to emit game sound to all clients. + * + * Game sounds are found in a game's scripts/game_sound.txt or other files + * referenced from it + * + * Note that if a game sound has a rndwave section, one of them will be returned + * at random. + * + * @param gameSound Name of game sound. + * @param entity Entity to emit from. + * @param flags Sound flags. + * @param speakerentity Unknown. + * @param origin Sound origin. + * @param dir Sound direction. + * @param updatePos Unknown (updates positions?) + * @param soundtime Alternate time to play sound for. + * @noreturn + * @error Invalid client index. + */ +stock bool:EmitGameSoundToAll(const String:gameSound[], + entity = SOUND_FROM_PLAYER, + flags = SND_NOFLAGS, + speakerentity = -1, + const Float:origin[3] = NULL_VECTOR, + const Float:dir[3] = NULL_VECTOR, + bool:updatePos = true, + Float:soundtime = 0.0) +{ + new clients[MaxClients]; + new total = 0; + + for (new i=1; i<=MaxClients; i++) + { + if (IsClientInGame(i)) + { + clients[total++] = i; + } + } + + if (!total) + { + return false; + } + + return EmitGameSound(clients, total, gameSound, entity, flags, + speakerentity, origin, dir, updatePos, soundtime); +} + +/** + * Precache a game sound. + * + * Most games will precache all game sounds on map start, but this is not guaranteed... + * Team Fortress 2 is known to not pre-cache MvM game mode sounds on non-MvM maps. + * + * Due to the above, this native should be called before any calls to GetGameSoundParams, + * EmitGameSound*, or EmitAmbientGameSound. + * + * It should be safe to pass already precached game sounds to this function. + * + * Note: It precaches all files for a game sound. + * + * @param soundname Game sound to precache + * + * @return True if the game sound was found, false if sound did not exist + * or had no files + */ +native bool:PrecacheScriptSound(const String:soundname[]);