summaryrefslogtreecommitdiff
path: root/sourcemod/scripting/gokz-quiet
diff options
context:
space:
mode:
authornavewindre <nw@moneybot.cc>2023-12-04 18:06:10 +0100
committernavewindre <nw@moneybot.cc>2023-12-04 18:06:10 +0100
commitaef0d1c1268ab7d4bc18996c9c6b4da16a40aadc (patch)
tree43e766b51704f4ab8b383583bdc1871eeeb9c698 /sourcemod/scripting/gokz-quiet
parent38f1140c11724da05a23a10385061200b907cf6e (diff)
bbbbbbbbwaaaaaaaaaaa
Diffstat (limited to 'sourcemod/scripting/gokz-quiet')
-rw-r--r--sourcemod/scripting/gokz-quiet/ambient.sp100
-rw-r--r--sourcemod/scripting/gokz-quiet/falldamage.sp40
-rw-r--r--sourcemod/scripting/gokz-quiet/gokz-sounds.sp71
-rw-r--r--sourcemod/scripting/gokz-quiet/hideplayers.sp309
-rw-r--r--sourcemod/scripting/gokz-quiet/options.sp206
-rw-r--r--sourcemod/scripting/gokz-quiet/soundscape.sp30
6 files changed, 756 insertions, 0 deletions
diff --git a/sourcemod/scripting/gokz-quiet/ambient.sp b/sourcemod/scripting/gokz-quiet/ambient.sp
new file mode 100644
index 0000000..a67e20e
--- /dev/null
+++ b/sourcemod/scripting/gokz-quiet/ambient.sp
@@ -0,0 +1,100 @@
+/*
+ Hide sound effect from ambient_generics.
+ Credit to Haze - https://github.com/Haze1337/Sound-Manager
+*/
+
+Handle getPlayerSlot;
+
+void OnPluginStart_Ambient()
+{
+ HookSendSound();
+}
+static void HookSendSound()
+{
+ GameData gd = LoadGameConfigFile("gokz-quiet.games");
+
+ DynamicDetour sendSoundDetour = DHookCreateDetour(Address_Null, CallConv_THISCALL, ReturnType_Void, ThisPointer_Address);
+ DHookSetFromConf(sendSoundDetour, gd, SDKConf_Signature, "CGameClient::SendSound");
+ DHookAddParam(sendSoundDetour, HookParamType_ObjectPtr);
+ DHookAddParam(sendSoundDetour, HookParamType_Bool);
+ if (!DHookEnableDetour(sendSoundDetour, false, DHooks_OnSendSound))
+ {
+ SetFailState("Couldn't enable CGameClient::SendSound detour.");
+ }
+
+ StartPrepSDKCall(SDKCall_Raw);
+ PrepSDKCall_SetFromConf(gd, SDKConf_Virtual, "CBaseClient::GetPlayerSlot");
+ PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain);
+ getPlayerSlot = EndPrepSDKCall();
+ if (getPlayerSlot == null)
+ {
+ SetFailState("Could not initialize call to CBaseClient::GetPlayerSlot.");
+ }
+}
+
+
+/*struct SoundInfo_t
+{
+ Vector vOrigin; Offset: 0 | Size: 12
+ Vector vDirection Offset: 12 | Size: 12
+ Vector vListenerOrigin; Offset: 24 | Size: 12
+ const char *pszName; Offset: 36 | Size: 4
+ float fVolume; Offset: 40 | Size: 4
+ float fDelay; Offset: 44 | Size: 4
+ float fTickTime; Offset: 48 | Size: 4
+ int nSequenceNumber; Offset: 52 | Size: 4
+ int nEntityIndex; Offset: 56 | Size: 4
+ int nChannel; Offset: 60 | Size: 4
+ int nPitch; Offset: 64 | Size: 4
+ int nFlags; Offset: 68 | Size: 4
+ unsigned int nSoundNum; Offset: 72 | Size: 4
+ int nSpeakerEntity; Offset: 76 | Size: 4
+ int nRandomSeed; Offset: 80 | Size: 4
+ soundlevel_t Soundlevel; Offset: 84 | Size: 4
+ bool bIsSentence; Offset: 88 | Size: 1
+ bool bIsAmbient; Offset: 89 | Size: 1
+ bool bLooping; Offset: 90 | Size: 1
+};*/
+
+//void CGameClient::SendSound( SoundInfo_t &sound, bool isReliable )
+public MRESReturn DHooks_OnSendSound(Address pThis, Handle hParams)
+{
+ // Check volume
+ float volume = DHookGetParamObjectPtrVar(hParams, 1, 40, ObjectValueType_Float);
+ if(volume == 0.0)
+ {
+ return MRES_Ignored;
+ }
+
+ Address pIClient = pThis + view_as<Address>(0x4);
+ int client = view_as<int>(SDKCall(getPlayerSlot, pIClient)) + 1;
+
+ if(!IsValidClient(client))
+ {
+ return MRES_Ignored;
+ }
+
+ bool isAmbient = DHookGetParamObjectPtrVar(hParams, 1, 89, ObjectValueType_Bool);
+ if (!isAmbient)
+ {
+ return MRES_Ignored;
+ }
+
+ float newVolume;
+ if (GOKZ_QT_GetOption(client, QTOption_AmbientSounds) == -1 || GOKZ_QT_GetOption(client, QTOption_AmbientSounds) == 10)
+ {
+ newVolume = volume;
+ }
+ else
+ {
+ float volumeFactor = float(GOKZ_QT_GetOption(client, QTOption_AmbientSounds)) * 0.1;
+ newVolume = volume * volumeFactor;
+ }
+
+ if (newVolume <= 0.0)
+ {
+ return MRES_Supercede;
+ }
+ DHookSetParamObjectPtrVar(hParams, 1, 40, ObjectValueType_Float, newVolume);
+ return MRES_ChangedHandled;
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-quiet/falldamage.sp b/sourcemod/scripting/gokz-quiet/falldamage.sp
new file mode 100644
index 0000000..6bd0533
--- /dev/null
+++ b/sourcemod/scripting/gokz-quiet/falldamage.sp
@@ -0,0 +1,40 @@
+/*
+ Toggle player's fall damage sounds.
+*/
+
+void OnPluginStart_FallDamage()
+{
+ AddNormalSoundHook(Hook_NormalSound);
+}
+
+static Action Hook_NormalSound(int clients[MAXPLAYERS], int& numClients, char sample[PLATFORM_MAX_PATH], int& entity, int& channel, float& volume, int& level, int& pitch, int& flags, char soundEntry[PLATFORM_MAX_PATH], int& seed)
+{
+ if (!StrEqual(soundEntry, "Player.FallDamage"))
+ {
+ return Plugin_Continue;
+ }
+
+ for (int i = 0; i < numClients; i++)
+ {
+ int client = clients[i];
+ if (!IsValidClient(client))
+ {
+ continue;
+ }
+ int clientArray[1];
+ clientArray[0] = client;
+ float newVolume;
+ if (GOKZ_QT_GetOption(client, QTOption_FallDamageSound) == -1 || GOKZ_QT_GetOption(client, QTOption_FallDamageSound) == 10)
+ {
+ newVolume = volume;
+ }
+ else
+ {
+ float volumeFactor = float(GOKZ_QT_GetOption(client, QTOption_FallDamageSound)) * 0.1;
+ newVolume = volume * volumeFactor;
+ }
+
+ EmitSoundEntry(clientArray, 1, soundEntry, sample, entity, channel, level, seed, flags, newVolume, pitch);
+ }
+ return Plugin_Handled;
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-quiet/gokz-sounds.sp b/sourcemod/scripting/gokz-quiet/gokz-sounds.sp
new file mode 100644
index 0000000..02cf681
--- /dev/null
+++ b/sourcemod/scripting/gokz-quiet/gokz-sounds.sp
@@ -0,0 +1,71 @@
+/*
+ Volume options for various GOKZ sounds.
+*/
+
+public Action GOKZ_OnEmitSoundToClient(int client, const char[] sample, float &volume, const char[] description)
+{
+ int volumeFactor = 10;
+ if (StrEqual(description, "Checkpoint") || StrEqual(description, "Set Start Position"))
+ {
+ volumeFactor = GOKZ_QT_GetOption(client, QTOption_CheckpointVolume);
+ if (volumeFactor == -1)
+ {
+ return Plugin_Continue;
+ }
+ }
+ else if (StrEqual(description, "Checkpoint"))
+ {
+ volumeFactor = GOKZ_QT_GetOption(client, QTOption_TeleportVolume);
+ if (volumeFactor == -1)
+ {
+ return Plugin_Continue;
+ }
+ }
+ else if (StrEqual(description, "Timer Start") || StrEqual(description, "Timer End") || StrEqual(description, "Timer False End") || StrEqual(description, "Missed PB"))
+ {
+ volumeFactor = GOKZ_QT_GetOption(client, QTOption_TimerVolume);
+ if (volumeFactor == -1)
+ {
+ return Plugin_Continue;
+ }
+ }
+ else if (StrEqual(description, "Error"))
+ {
+ volumeFactor = GOKZ_QT_GetOption(client, QTOption_ErrorVolume);
+ if (volumeFactor == -1)
+ {
+ return Plugin_Continue;
+ }
+ }
+ else if (StrEqual(description, "Server Record"))
+ {
+ volumeFactor = GOKZ_QT_GetOption(client, QTOption_ServerRecordVolume);
+ if (volumeFactor == -1)
+ {
+ return Plugin_Continue;
+ }
+ }
+ else if (StrEqual(description, "World Record"))
+ {
+ volumeFactor = GOKZ_QT_GetOption(client, QTOption_WorldRecordVolume);
+ if (volumeFactor == -1)
+ {
+ return Plugin_Continue;
+ }
+ }
+ else if (StrEqual(description, "Jumpstats"))
+ {
+ volumeFactor = GOKZ_QT_GetOption(client, QTOption_JumpstatsVolume);
+ if (volumeFactor == -1)
+ {
+ return Plugin_Continue;
+ }
+ }
+
+ if (volumeFactor == 10)
+ {
+ return Plugin_Continue;
+ }
+ volume *= float(volumeFactor) * 0.1;
+ return Plugin_Changed;
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-quiet/hideplayers.sp b/sourcemod/scripting/gokz-quiet/hideplayers.sp
new file mode 100644
index 0000000..65736f0
--- /dev/null
+++ b/sourcemod/scripting/gokz-quiet/hideplayers.sp
@@ -0,0 +1,309 @@
+/*
+ Hide sounds and effects from other players.
+*/
+
+void OnPluginStart_HidePlayers()
+{
+ AddNormalSoundHook(Hook_NormalSound);
+ AddTempEntHook("Shotgun Shot", Hook_ShotgunShot);
+ AddTempEntHook("EffectDispatch", Hook_EffectDispatch);
+ HookUserMessage(GetUserMessageId("WeaponSound"), Hook_WeaponSound, true);
+
+ // Lateload support
+ for (int client = 1; client <= MaxClients; client++)
+ {
+ if (IsValidClient(client))
+ {
+ OnJoinTeam_HidePlayers(client, GetClientTeam(client));
+ }
+ }
+}
+
+void OnJoinTeam_HidePlayers(int client, int team)
+{
+ // Make sure client is only ever hooked once
+ SDKUnhook(client, SDKHook_SetTransmit, OnSetTransmitClient);
+
+ if (team == CS_TEAM_T || team == CS_TEAM_CT)
+ {
+ SDKHook(client, SDKHook_SetTransmit, OnSetTransmitClient);
+ }
+}
+
+Action CommandToggleShowPlayers(int client, int args)
+{
+ if (GOKZ_GetOption(client, gC_QTOptionNames[QTOption_ShowPlayers]) == ShowPlayers_Disabled)
+ {
+ GOKZ_SetOption(client, gC_QTOptionNames[QTOption_ShowPlayers], ShowPlayers_Enabled);
+ }
+ else
+ {
+ GOKZ_SetOption(client, gC_QTOptionNames[QTOption_ShowPlayers], ShowPlayers_Disabled);
+ }
+ return Plugin_Handled;
+}
+
+// =====[ PRIVATE ]=====
+
+// Hide most of the other players' actions. This function is expensive.
+static Action OnSetTransmitClient(int entity, int client)
+{
+ if (GOKZ_GetOption(client, gC_QTOptionNames[QTOption_ShowPlayers]) == ShowPlayers_Disabled
+ && entity != client
+ && entity != GetObserverTarget(client))
+ {
+ return Plugin_Handled;
+ }
+ return Plugin_Continue;
+}
+
+// Hide reload sounds. Required if other players were visible at one point during the gameplay.
+static Action Hook_WeaponSound(UserMsg msg_id, Protobuf msg, const int[] players, int playersNum, bool reliable, bool init)
+{
+ int newClients[MAXPLAYERS], newTotal = 0;
+ int entidx = msg.ReadInt("entidx");
+ for (int i = 0; i < playersNum; i++)
+ {
+ int client = players[i];
+ if (GOKZ_GetOption(client, gC_QTOptionNames[QTOption_ShowPlayers]) == ShowPlayers_Enabled
+ || entidx == client
+ || entidx == GetObserverTarget(client))
+ {
+ newClients[newTotal] = client;
+ newTotal++;
+ }
+ }
+
+ // Nothing's changed, let the engine handle it.
+ if (newTotal == playersNum)
+ {
+ return Plugin_Continue;
+ }
+ // No one to send to so it doesn't matter if we block or not. We block just to end the function early.
+ if (newTotal == 0)
+ {
+ return Plugin_Handled;
+ }
+ // Only way to modify the recipient list is to RequestFrame and create our own user message.
+ char path[PLATFORM_MAX_PATH];
+ msg.ReadString("sound", path, sizeof(path));
+ int flags = USERMSG_BLOCKHOOKS;
+ if (reliable)
+ {
+ flags |= USERMSG_RELIABLE;
+ }
+ if (init)
+ {
+ flags |= USERMSG_INITMSG;
+ }
+
+ DataPack dp = new DataPack();
+ dp.WriteCell(msg_id);
+ dp.WriteCell(newTotal);
+ dp.WriteCellArray(newClients, newTotal);
+ dp.WriteCell(flags);
+ dp.WriteCell(entidx);
+ dp.WriteFloat(msg.ReadFloat("origin_x"));
+ dp.WriteFloat(msg.ReadFloat("origin_y"));
+ dp.WriteFloat(msg.ReadFloat("origin_z"));
+ dp.WriteString(path);
+ dp.WriteFloat(msg.ReadFloat("timestamp"));
+
+ RequestFrame(RequestFrame_WeaponSound, dp);
+ return Plugin_Handled;
+}
+
+static void RequestFrame_WeaponSound(DataPack dp)
+{
+ dp.Reset();
+
+ UserMsg msg_id = dp.ReadCell();
+ int newTotal = dp.ReadCell();
+ int newClients[MAXPLAYERS];
+ dp.ReadCellArray(newClients, newTotal);
+ int flags = dp.ReadCell();
+
+ Protobuf newMsg = view_as<Protobuf>(StartMessageEx(msg_id, newClients, newTotal, flags));
+
+ newMsg.SetInt("entidx", dp.ReadCell());
+ newMsg.SetFloat("origin_x", dp.ReadFloat());
+ newMsg.SetFloat("origin_y", dp.ReadFloat());
+ newMsg.SetFloat("origin_z", dp.ReadFloat());
+ char path[PLATFORM_MAX_PATH];
+ dp.ReadString(path, sizeof(path));
+ newMsg.SetString("sound", path);
+ newMsg.SetFloat("timestamp", dp.ReadFloat());
+
+ EndMessage();
+
+ delete dp;
+}
+
+// Hide various sounds that don't get blocked by SetTransmit hook.
+static Action Hook_NormalSound(int clients[MAXPLAYERS], int& numClients, char sample[PLATFORM_MAX_PATH], int& entity, int& channel, float& volume, int& level, int& pitch, int& flags, char soundEntry[PLATFORM_MAX_PATH], int& seed)
+{
+ if (StrContains(sample, "Player.EquipArmor") != -1 || StrContains(sample, "BaseCombatCharacter.AmmoPickup") != -1)
+ {
+ // When the sound is emitted, the owner of these entities are not set yet.
+ // Hence we cannot do the entity parent stuff below.
+ // In that case, we just straight up block armor and ammo pickup sounds.
+ return Plugin_Stop;
+ }
+ int ent = entity;
+ while (ent > MAXPLAYERS)
+ {
+ // Block some gun and knife sounds by trying to find its parent entity.
+ ent = GetEntPropEnt(ent, Prop_Send, "moveparent");
+ if (ent < MAXPLAYERS)
+ {
+ break;
+ }
+ else if (ent == -1)
+ {
+ return Plugin_Continue;
+ }
+ }
+ int numNewClients = 0;
+ for (int i = 0; i < numClients; i++)
+ {
+ int client = clients[i];
+ if (GOKZ_GetOption(client, gC_QTOptionNames[QTOption_ShowPlayers]) == ShowPlayers_Enabled
+ || ent == client
+ || ent == GetObserverTarget(client))
+ {
+ clients[numNewClients] = client;
+ numNewClients++;
+ }
+ }
+
+ if (numNewClients != numClients)
+ {
+ numClients = numNewClients;
+ return Plugin_Changed;
+ }
+
+ return Plugin_Continue;
+}
+
+// Hide firing sounds.
+static Action Hook_ShotgunShot(const char[] te_name, const int[] players, int numClients, float delay)
+{
+ int newClients[MAXPLAYERS], newTotal = 0;
+ for (int i = 0; i < numClients; i++)
+ {
+ int client = players[i];
+ if (GOKZ_GetOption(client, gC_QTOptionNames[QTOption_ShowPlayers]) == ShowPlayers_Enabled
+ || TE_ReadNum("m_iPlayer") + 1 == GetObserverTarget(client))
+ {
+ newClients[newTotal] = client;
+ newTotal++;
+ }
+ }
+
+ // Noone wants the sound
+ if (newTotal == 0)
+ {
+ return Plugin_Stop;
+ }
+
+ // Nothing's changed, let the engine handle it.
+ if (newTotal == numClients)
+ {
+ return Plugin_Continue;
+ }
+
+ float origin[3];
+ TE_ReadVector("m_vecOrigin", origin);
+
+ float angles[2];
+ angles[0] = TE_ReadFloat("m_vecAngles[0]");
+ angles[1] = TE_ReadFloat("m_vecAngles[1]");
+
+ int weapon = TE_ReadNum("m_weapon");
+ int mode = TE_ReadNum("m_iMode");
+ int seed = TE_ReadNum("m_iSeed");
+ int player = TE_ReadNum("m_iPlayer");
+ float inaccuracy = TE_ReadFloat("m_fInaccuracy");
+ float recoilIndex = TE_ReadFloat("m_flRecoilIndex");
+ float spread = TE_ReadFloat("m_fSpread");
+ int itemIdx = TE_ReadNum("m_nItemDefIndex");
+ int soundType = TE_ReadNum("m_iSoundType");
+
+ TE_Start("Shotgun Shot");
+ TE_WriteVector("m_vecOrigin", origin);
+ TE_WriteFloat("m_vecAngles[0]", angles[0]);
+ TE_WriteFloat("m_vecAngles[1]", angles[1]);
+ TE_WriteNum("m_weapon", weapon);
+ TE_WriteNum("m_iMode", mode);
+ TE_WriteNum("m_iSeed", seed);
+ TE_WriteNum("m_iPlayer", player);
+ TE_WriteFloat("m_fInaccuracy", inaccuracy);
+ TE_WriteFloat("m_flRecoilIndex", recoilIndex);
+ TE_WriteFloat("m_fSpread", spread);
+ TE_WriteNum("m_nItemDefIndex", itemIdx);
+ TE_WriteNum("m_iSoundType", soundType);
+
+ // Send the TE and stop the engine from processing its own.
+ TE_Send(newClients, newTotal, delay);
+ return Plugin_Stop;
+}
+
+// Hide knife and blood effect caused by other players.
+static Action Hook_EffectDispatch(const char[] te_name, const int[] players, int numClients, float delay)
+{
+ // Block bullet impact effects.
+ int effIndex = TE_ReadNum("m_iEffectName");
+ if (effIndex != EFFECT_IMPACT && effIndex != EFFECT_KNIFESLASH)
+ {
+ return Plugin_Continue;
+ }
+ int newClients[MAXPLAYERS], newTotal = 0;
+ for (int i = 0; i < numClients; i++)
+ {
+ int client = players[i];
+ if (GOKZ_GetOption(client, gC_QTOptionNames[QTOption_ShowPlayers]) == ShowPlayers_Enabled)
+ {
+ newClients[newTotal] = client;
+ newTotal++;
+ }
+ }
+ // Noone wants the sound
+ if (newTotal == 0)
+ {
+ return Plugin_Stop;
+ }
+
+ // Nothing's changed, let the engine handle it.
+ if (newTotal == numClients)
+ {
+ return Plugin_Continue;
+ }
+ float origin[3], start[3];
+ origin[0] = TE_ReadFloat("m_vOrigin.x");
+ origin[1] = TE_ReadFloat("m_vOrigin.y");
+ origin[2] = TE_ReadFloat("m_vOrigin.z");
+ start[0] = TE_ReadFloat("m_vStart.x");
+ start[1] = TE_ReadFloat("m_vStart.y");
+ start[2] = TE_ReadFloat("m_vStart.z");
+ int flags = TE_ReadNum("m_fFlags");
+ float scale = TE_ReadFloat("m_flScale");
+ int surfaceProp = TE_ReadNum("m_nSurfaceProp");
+ int damageType = TE_ReadNum("m_nDamageType");
+ int entindex = TE_ReadNum("entindex");
+ int positionsAreRelativeToEntity = TE_ReadNum("m_bPositionsAreRelativeToEntity");
+
+ TE_Start("EffectDispatch");
+ TE_WriteNum("m_iEffectName", effIndex);
+ TE_WriteFloatArray("m_vOrigin.x", origin, 3);
+ TE_WriteFloatArray("m_vStart.x", start, 3);
+ TE_WriteFloat("m_flScale", scale);
+ TE_WriteNum("m_nSurfaceProp", surfaceProp);
+ TE_WriteNum("m_nDamageType", damageType);
+ TE_WriteNum("entindex", entindex);
+ TE_WriteNum("m_bPositionsAreRelativeToEntity", positionsAreRelativeToEntity);
+ TE_WriteNum("m_fFlags", flags);
+
+ // Send the TE and stop the engine from processing its own.
+ TE_Send(newClients, newTotal, delay);
+ return Plugin_Stop;
+}
diff --git a/sourcemod/scripting/gokz-quiet/options.sp b/sourcemod/scripting/gokz-quiet/options.sp
new file mode 100644
index 0000000..a9da3d7
--- /dev/null
+++ b/sourcemod/scripting/gokz-quiet/options.sp
@@ -0,0 +1,206 @@
+// =====[ OPTIONS ]=====
+
+void OnOptionsMenuReady_Options()
+{
+ RegisterOptions();
+}
+
+void RegisterOptions()
+{
+ for (QTOption option; option < QTOPTION_COUNT; option++)
+ {
+ GOKZ_RegisterOption(gC_QTOptionNames[option], gC_QTOptionDescriptions[option],
+ OptionType_Int, gI_QTOptionDefaultValues[option], 0, gI_QTOptionCounts[option] - 1);
+ }
+}
+
+void OnOptionChanged_Options(int client, QTOption option, any newValue)
+{
+ if (option == QTOption_Soundscapes && newValue == Soundscapes_Enabled)
+ {
+ EnableSoundscape(client);
+ }
+ PrintOptionChangeMessage(client, option, newValue);
+}
+
+void PrintOptionChangeMessage(int client, QTOption option, any newValue)
+{
+ switch (option)
+ {
+ case QTOption_ShowPlayers:
+ {
+ switch (newValue)
+ {
+ case ShowPlayers_Disabled:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Show Players - Disable");
+ }
+ case ShowPlayers_Enabled:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Show Players - Enable");
+ }
+ }
+ }
+ case QTOption_Soundscapes:
+ {
+ switch (newValue)
+ {
+ case Soundscapes_Disabled:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Soundscapes - Disable");
+ }
+ case Soundscapes_Enabled:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Soundscapes - Enable");
+ }
+ }
+ }
+ }
+}
+
+// =====[ OPTIONS MENU ]=====
+
+TopMenu gTM_Options;
+TopMenuObject gTMO_CatQuiet;
+TopMenuObject gTMO_ItemsQuiet[QTOPTION_COUNT];
+
+void OnOptionsMenuCreated_OptionsMenu(TopMenu topMenu)
+{
+ if (gTM_Options == topMenu && gTMO_CatQuiet != INVALID_TOPMENUOBJECT)
+ {
+ return;
+ }
+
+ gTMO_CatQuiet = topMenu.AddCategory(QUIET_OPTION_CATEGORY, TopMenuHandler_Categories);
+}
+
+void OnOptionsMenuReady_OptionsMenu(TopMenu topMenu)
+{
+ // Make sure category exists
+ if (gTMO_CatQuiet == INVALID_TOPMENUOBJECT)
+ {
+ GOKZ_OnOptionsMenuCreated(topMenu);
+ }
+
+ if (gTM_Options == topMenu)
+ {
+ return;
+ }
+
+ gTM_Options = topMenu;
+
+ // Add gokz-profile option items
+ for (int option = 0; option < view_as<int>(QTOPTION_COUNT); option++)
+ {
+ gTMO_ItemsQuiet[option] = gTM_Options.AddItem(gC_QTOptionNames[option], TopMenuHandler_QT, gTMO_CatQuiet);
+ }
+}
+
+public void TopMenuHandler_Categories(TopMenu topmenu, TopMenuAction action, TopMenuObject topobj_id, int param, char[] buffer, int maxlength)
+{
+ if (action == TopMenuAction_DisplayOption || action == TopMenuAction_DisplayTitle)
+ {
+ if (topobj_id == gTMO_CatQuiet)
+ {
+ Format(buffer, maxlength, "%T", "Options Menu - Quiet", param);
+ }
+ }
+}
+
+public void TopMenuHandler_QT(TopMenu topmenu, TopMenuAction action, TopMenuObject topobj_id, int param, char[] buffer, int maxlength)
+{
+ QTOption option = QTOPTION_INVALID;
+ for (int i = 0; i < view_as<int>(QTOPTION_COUNT); i++)
+ {
+ if (topobj_id == gTMO_ItemsQuiet[i])
+ {
+ option = view_as<QTOption>(i);
+ break;
+ }
+ }
+
+ if (option == QTOPTION_INVALID)
+ {
+ return;
+ }
+
+ if (action == TopMenuAction_DisplayOption)
+ {
+ switch (option)
+ {
+ case QTOption_ShowPlayers:
+ {
+ FormatToggleableOptionDisplay(param, option, buffer, maxlength);
+ }
+ case QTOption_Soundscapes:
+ {
+ FormatToggleableOptionDisplay(param, option, buffer, maxlength);
+ }
+ case QTOption_FallDamageSound:
+ {
+ FormatVolumeOptionDisplay(param, option, buffer, maxlength);
+ }
+ case QTOption_AmbientSounds:
+ {
+ FormatVolumeOptionDisplay(param, option, buffer, maxlength);
+ }
+ case QTOption_CheckpointVolume:
+ {
+ FormatVolumeOptionDisplay(param, option, buffer, maxlength);
+ }
+ case QTOption_TeleportVolume:
+ {
+ FormatVolumeOptionDisplay(param, option, buffer, maxlength);
+ }
+ case QTOption_TimerVolume:
+ {
+ FormatVolumeOptionDisplay(param, option, buffer, maxlength);
+ }
+ case QTOption_ErrorVolume:
+ {
+ FormatVolumeOptionDisplay(param, option, buffer, maxlength);
+ }
+ case QTOption_ServerRecordVolume:
+ {
+ FormatVolumeOptionDisplay(param, option, buffer, maxlength);
+ }
+ case QTOption_WorldRecordVolume:
+ {
+ FormatVolumeOptionDisplay(param, option, buffer, maxlength);
+ }
+ case QTOption_JumpstatsVolume:
+ {
+ FormatVolumeOptionDisplay(param, option, buffer, maxlength);
+ }
+ }
+ }
+ else if (action == TopMenuAction_SelectOption)
+ {
+ GOKZ_CycleOption(param, gC_QTOptionNames[option]);
+ gTM_Options.Display(param, TopMenuPosition_LastCategory);
+ }
+}
+
+void FormatToggleableOptionDisplay(int client, QTOption option, char[] buffer, int maxlength)
+{
+ if (GOKZ_GetOption(client, gC_QTOptionNames[option]) == 0)
+ {
+ FormatEx(buffer, maxlength, "%T - %T",
+ gC_QTOptionPhrases[option], client,
+ "Options Menu - Disabled", client);
+ }
+ else
+ {
+ FormatEx(buffer, maxlength, "%T - %T",
+ gC_QTOptionPhrases[option], client,
+ "Options Menu - Enabled", client);
+ }
+}
+
+void FormatVolumeOptionDisplay(int client, QTOption option, char[] buffer, int maxlength)
+{
+ // Assume 10% volume steps.
+ FormatEx(buffer, maxlength, "%T - %i%",
+ gC_QTOptionPhrases[option], client,
+ GOKZ_QT_GetOption(client, option) * 10);
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-quiet/soundscape.sp b/sourcemod/scripting/gokz-quiet/soundscape.sp
new file mode 100644
index 0000000..f320ad3
--- /dev/null
+++ b/sourcemod/scripting/gokz-quiet/soundscape.sp
@@ -0,0 +1,30 @@
+/*
+ Toggle soundscapes.
+*/
+
+static int currentSoundscapeIndex[MAXPLAYERS + 1] = {BLANK_SOUNDSCAPEINDEX, ...};
+
+void EnableSoundscape(int client)
+{
+ if (currentSoundscapeIndex[client] != BLANK_SOUNDSCAPEINDEX)
+ {
+ SetEntProp(client, Prop_Data, "soundscapeIndex", currentSoundscapeIndex[client]);
+ }
+}
+
+void OnPlayerRunCmdPost_Soundscape(int client)
+{
+ int soundscapeIndex = GetEntProp(client, Prop_Data, "soundscapeIndex");
+ if (GOKZ_GetOption(client, gC_QTOptionNames[QTOption_Soundscapes]) == Soundscapes_Disabled)
+ {
+ if (soundscapeIndex != BLANK_SOUNDSCAPEINDEX)
+ {
+ currentSoundscapeIndex[client] = soundscapeIndex;
+ }
+ SetEntProp(client, Prop_Data, "soundscapeIndex", BLANK_SOUNDSCAPEINDEX);
+ }
+ else
+ {
+ currentSoundscapeIndex[client] = soundscapeIndex;
+ }
+} \ No newline at end of file