#include #include #include #include #include #include #include #include #include #pragma semicolon 1 #pragma newdecls required bool g_bBlockAll = false; bool g_bDisabled[MAXPLAYERS+1] = false; float g_fSoundVolume[MAXPLAYERS+1] = {1.0, ...}; float g_fMusicVolume[MAXPLAYERS+1] = {1.0, ...}; Handle hOnStartSoundForward = INVALID_HANDLE; Basic g_aSounds; //---------------------------------------------------------------------------------------------------- // Purpose: MyInfo //---------------------------------------------------------------------------------------------------- public Plugin myinfo = { name = "Map Music Control Interface", author = "Mitch & SHUFEN from POSSESSION.tokyo & New Methods by PerfectLaugh from POSSESSION.tokyo & Testing by CrazyKid", description = "Allows clients to adjust ambient sounds played by the map", version = "4.0", url = "http://www.sourcemod.net/" }; //---------------------------------------------------------------------------------------------------- // Purpose: API //---------------------------------------------------------------------------------------------------- public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { RegPluginLibrary("mapmusic_interface"); CreateNative("MapMusicInterface_GetSoundVolume", Native_GetSoundVolume); CreateNative("MapMusicInterface_GetMusicVolume", Native_GetMusicVolume); CreateNative("MapMusicInterface_SetSoundVolume", Native_SetSoundVolume); CreateNative("MapMusicInterface_SetMusicVolume", Native_SetMusicVolume); CreateNative("MapMusicInterface_IsDisabled", Native_IsDisabled); CreateNative("MapMusicInterface_SetDisabled", Native_SetDisabled); CreateNative("MapMusicInterface_GetBlockAll", Native_GetBlockAll); CreateNative("MapMusicInterface_SetBlockAll", Native_SetBlockAll); CreateNative("MapMusicInterface_HasExceedingLengthSound", Native_HasExceedingLengthSound); return APLRes_Success; } public any Native_GetSoundVolume(Handle myself, int numParams) { return GetSoundVolume(GetNativeCell(1)); } public any Native_GetMusicVolume(Handle myself, int numParams) { return GetMusicVolume(GetNativeCell(1)); } public any Native_SetSoundVolume(Handle myself, int numParams) { SetSoundVolume(GetNativeCell(1), GetNativeCell(2)); } public any Native_SetMusicVolume(Handle myself, int numParams) { SetMusicVolume(GetNativeCell(1), GetNativeCell(2)); } public any Native_IsDisabled(Handle myself, int numParams) { return IsClientDisabled(GetNativeCell(1)); } public any Native_SetDisabled(Handle myself, int numParams) { SetClientDisabled(GetNativeCell(1), GetNativeCell(2)); } public any Native_GetBlockAll(Handle myself, int numParams) { return g_bBlockAll; } public any Native_SetBlockAll(Handle myself, int numParams) { g_bBlockAll = GetNativeCell(1); } public any Native_HasExceedingLengthSound(Handle myself, int numParams) { return HasExceedingLengthSound(GetNativeCell(1)); } //---------------------------------------------------------------------------------------------------- // Purpose: General //---------------------------------------------------------------------------------------------------- public void OnPluginStart() { hOnStartSoundForward = CreateGlobalForward("MapMusicInterface_OnStartSound", ET_Hook, Param_String, Param_Float, Param_CellByRef, Param_CellByRef); g_aSounds = new Basic(); for (int client = 1; client <= MaxClients; client++) { g_bDisabled[client] = false; g_fSoundVolume[client] = 1.0; g_fMusicVolume[client] = 1.0; } } //---------------------------------------------------------------------------------------------------- // Purpose: Clients //---------------------------------------------------------------------------------------------------- public void OnClientConnected(int client) { g_bDisabled[client] = false; g_fSoundVolume[client] = 1.0; g_fMusicVolume[client] = 1.0; } public void OnClientDisconnect_Post(int client) { g_bDisabled[client] = false; g_fSoundVolume[client] = 1.0; g_fMusicVolume[client] = 1.0; } CSound GetCSound(int entity, int soundnum, bool is_ambient) { char key[64]; FormatEx(key, sizeof(key), "%x_%x_%d", entity, soundnum, is_ambient? 1 : 0); CSound soundmap = view_as(g_aSounds.GetHandle(key)); if (soundmap == null) { soundmap = new CSound(); g_aSounds.SetHandle(key, soundmap); } return soundmap; } CSound GetCSoundFromEntity(int entity) { char prefix[64]; FormatEx(prefix, sizeof(prefix), "%x_", entity); CSound soundmap = null; StringMapSnapshot snap = g_aSounds.Snapshot(); char elem_key[64]; for (int i = 0; i < snap.Length; i++) { snap.GetKey(i, elem_key, sizeof(elem_key)); if (strncmp(prefix, elem_key, strlen(prefix)) == 0) { soundmap = view_as(g_aSounds.GetHandle(elem_key)); break; } } delete snap; return soundmap; } #define EMIT_SND_NOFLAGS 0 #define EMIT_SND_CHANGE_VOL (1 << 0) #define EMIT_SND_STOP (1 << 2) #define EMIT_SND_STOP_LOOPING (1 << 5) //---------------------------------------------------------------------------------------------------- // Purpose: HookSoundMsg //---------------------------------------------------------------------------------------------------- public Action OnSoundEmittingToClient(int client, int origin[3], int& volume, float& delay, int& seq, int& entity, int& channel, int& pitch, int& flags, int& soundnum, int& soundnum_handle, int& speaker_entity, int& sound_level, bool is_sentence, bool is_ambient) { if (is_sentence || soundnum == -1) { return Plugin_Continue; } CSound soundmap = GetCSound(entity, soundnum, is_ambient); if (flags & EMIT_SND_STOP || flags & EMIT_SND_STOP_LOOPING) { volume = 0; } else if (volume == -1) { volume = 100; } if (soundmap.iPassTickCount != GetGameTickCount()) { char sample[256]; char soundfile[256]; ReadStringTable(FindStringTable("soundprecache"), soundnum, sample, sizeof(sample)); if (sample[0] == '*' || sample[0] == '#') strcopy(soundfile, sizeof(soundfile), sample[1]); else strcopy(soundfile, sizeof(soundfile), sample); if (soundmap.fLength <= 0.0) { soundmap.fLength = GetSoundLengthFloat(soundfile); } if (flags == EMIT_SND_NOFLAGS || ((flags & EMIT_SND_CHANGE_VOL) && soundmap.fEndTime > 0.0 && soundmap.fEndTime <= GetTickedTime() && volume > 0)) { bool isMusic = false; Action res = APIOnStartSoundForward(soundfile, soundmap.fLength, isMusic, volume); if (res != Plugin_Continue) { return Plugin_Handled; } if (isMusic && g_bBlockAll) { return Plugin_Handled; } soundmap.fEndTime = GetTickedTime() + soundmap.fLength; soundmap.bIsMusic = isMusic; } else if (flags & EMIT_SND_STOP || flags & EMIT_SND_STOP_LOOPING) { soundmap.fEndTime = GetTickedTime(); } // Volumes should be binded on entity, rather than sound file path. // This is the reason of why sounds were bugged on ze_deadcore. // Airvulpes uses multiple entities with same sound file to play with huge volume. soundmap.iVolume = volume; } if ((flags & EMIT_SND_CHANGE_VOL) && soundmap.iPassTickCount == GetGameTickCount()) { if (soundmap.iPassClient != client) { // When called AcceptEntityInput(entity, "Volume"); via void UpdateAllGenericSound(int client), // that should be ignored because it was called even though needless except caller client. return Plugin_Handled; } } volume = GetAdjustedVolumeOfClient(client, volume, soundmap.bIsMusic); return Plugin_Changed; } int GetAdjustedVolumeOfClient(int client, int sourcevol, bool isMusic) { if (isMusic && IsClientDisabled(client)) { return 0; } float fvolume; if (isMusic) { fvolume = GetMusicVolume(client); } else { fvolume = GetSoundVolume(client); } if (fvolume <= 0.0) { return 0; } return RoundToCeil(float(sourcevol) * fvolume); } Action APIOnStartSoundForward(const char[] sample, float length, bool& isMusic, int& volume) { Call_StartForward(hOnStartSoundForward); Call_PushString(sample); Call_PushFloat(length); Call_PushCellRef(isMusic); Call_PushCellRef(volume); Action res = Plugin_Continue; Call_Finish(res); return res; } //---------------------------------------------------------------------------------------------------- // Purpose: Internal //---------------------------------------------------------------------------------------------------- float GetSoundVolume(int client) { return g_fSoundVolume[client]; } float GetMusicVolume(int client) { return g_fMusicVolume[client]; } void SetSoundVolume(int client, float volume) { if (volume < 0.0) { volume = 0.0; } else if (volume > 1.0) { volume = 1.0; } g_fSoundVolume[client] = volume; if (IsClientInGame(client)) { UpdateAllGenericSound(client); } } void SetMusicVolume(int client, float volume) { SetClientDisabled(client, false); if (volume < 0.0) { volume = 0.0; } else if (volume > 1.0) { volume = 1.0; } g_fMusicVolume[client] = volume; if (IsClientInGame(client)) { UpdateAllGenericSound(client); } } bool IsClientDisabled(int client) { return g_bDisabled[client]; } void SetClientDisabled(int client, bool disabled) { g_bDisabled[client] = disabled; if (IsClientInGame(client)) { UpdateAllGenericSound(client); } } bool HasExceedingLengthSound(float searchlength) { int entity = INVALID_ENT_REFERENCE; while ((entity = FindEntityByClassname(entity, "ambient_generic")) != INVALID_ENT_REFERENCE) { char soundfile[256]; GetEntPropString(entity, Prop_Data, "m_iszSound", soundfile, sizeof(soundfile)); if (soundfile[0] == '*' || soundfile[0] == '#') strcopy(soundfile, sizeof(soundfile), soundfile[1]); float length = GetSoundLengthFloat(soundfile); if (searchlength > 0.0 && length >= searchlength) { return true; } } return false; } float GetSoundLengthFloat(const char[] soundFile) { char path[PLATFORM_MAX_PATH]; FormatEx(path, sizeof(path), "sound/%s", soundFile); if (!FileExists(path, true)) { return 0.0; } SoundFile hSoundFile = new SoundFile(path, true); if (hSoundFile == null) return 0.0; float result = float(hSoundFile.LengthInMilliseconds) / 1000.0; delete hSoundFile; return result; } public void OnClientDisconnect(int client) { OnEntityDestroyed(client); } public void OnEntityDestroyed(int entity) { char prefix[32]; FormatEx(prefix, sizeof(prefix), "%x_", entity); StringMapSnapshot snap = g_aSounds.Snapshot(); char elem_key[32]; for (int i = 0; i < snap.Length; i++) { snap.GetKey(i, elem_key, sizeof(elem_key)); if (strncmp(prefix, elem_key, strlen(prefix)) == 0) { delete g_aSounds.GetHandle(elem_key); g_aSounds.Remove(elem_key); } } delete snap; } void UpdateAllGenericSound(int client) { int entity = INVALID_ENT_REFERENCE; while ((entity = FindEntityByClassname(entity, "ambient_generic")) != INVALID_ENT_REFERENCE) { CSound soundmap = GetCSoundFromEntity(entity); if (soundmap == null) { continue; } soundmap.iPassTickCount = GetGameTickCount(); soundmap.iPassClient = client; SetVariantInt(soundmap.iVolume / 10); AcceptEntityInput(entity, "Volume"); } }