From cd648f450018ce72af66c0803f0daa769b13dab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Krupi=C5=84ski?= Date: Thu, 23 Nov 2023 23:01:35 +0100 Subject: [PATCH] Implement bomb defuse sound visualization (#4191) --- Source/CS2/Constants/SoundNames.h | 1 + Source/FeatureHelpers/FeatureHelpers.h | 6 + Source/FeatureHelpers/HudInWorldPanelZOrder.h | 3 +- Source/FeatureHelpers/Sound/BombDefuseSound.h | 31 +++++ .../Sound/BombDefuseVisualizerHelpers.h | 13 ++ Source/FeatureHelpers/Sound/SoundWatcher.h | 3 +- Source/Features/Sound/BombDefuseVisualizer.h | 124 ++++++++++++++++++ Source/Features/Sound/SoundFeatures.h | 3 + Source/GlobalContext.h | 1 + Source/Osiris.vcxproj | 3 + Source/Osiris.vcxproj.filters | 9 ++ Source/UI/Panorama/CreateGUI.js | 2 + Source/UI/Panorama/SetCommandHandler.h | 2 + 13 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 Source/FeatureHelpers/Sound/BombDefuseSound.h create mode 100644 Source/FeatureHelpers/Sound/BombDefuseVisualizerHelpers.h create mode 100644 Source/Features/Sound/BombDefuseVisualizer.h diff --git a/Source/CS2/Constants/SoundNames.h b/Source/CS2/Constants/SoundNames.h index c34c9bc2abd..12634964e4b 100644 --- a/Source/CS2/Constants/SoundNames.h +++ b/Source/CS2/Constants/SoundNames.h @@ -19,5 +19,6 @@ constexpr std::string_view kBombSoundsPath = "sounds/weapons/c4/"; constexpr std::string_view kPlayerSuitSoundPrefix = "suit"; constexpr std::string_view kBombBeepSoundsPrefix = "c4_beep"; +constexpr std::string_view kBombDefuseStartSoundName = "c4_disarmstart.vsnd"; } diff --git a/Source/FeatureHelpers/FeatureHelpers.h b/Source/FeatureHelpers/FeatureHelpers.h index 866fb08e1b6..ffec736a06f 100644 --- a/Source/FeatureHelpers/FeatureHelpers.h +++ b/Source/FeatureHelpers/FeatureHelpers.h @@ -8,11 +8,17 @@ #include "Hud/DefusingAlertHelpers.h" #include "Hud/KillfeedPreserverHelpers.h" #include "Sound/BombBeepVisualizerHelpers.h" +#include "Sound/BombDefuseVisualizerHelpers.h" #include "Sound/BombPlantVisualizerHelpers.h" #include "Sound/FootstepVisualizerHelpers.h" #include "WorldToClipSpaceConverter.h" struct FeatureHelpers { + [[nodiscard]] BombDefuseVisualizerHelpers getBombDefuseVisualizerHelpers() noexcept + { + return BombDefuseVisualizerHelpers{ HudInWorldPanelFactory{hudInWorldPanelContainer, hudProvider}, globalVarsProvider, transformFactory, worldtoClipSpaceConverter }; + } + [[nodiscard]] BombBeepVisualizerHelpers getBombBeepVisualizerHelpers() noexcept { return BombBeepVisualizerHelpers{ HudInWorldPanelFactory{hudInWorldPanelContainer, hudProvider}, globalVarsProvider, transformFactory, worldtoClipSpaceConverter }; diff --git a/Source/FeatureHelpers/HudInWorldPanelZOrder.h b/Source/FeatureHelpers/HudInWorldPanelZOrder.h index 1205edd0d8a..32eda0a9c24 100644 --- a/Source/FeatureHelpers/HudInWorldPanelZOrder.h +++ b/Source/FeatureHelpers/HudInWorldPanelZOrder.h @@ -3,5 +3,6 @@ enum class HudInWorldPanelZOrder { Footstep, BombBeep, - BombPlant + BombPlant, + BombDefuse }; diff --git a/Source/FeatureHelpers/Sound/BombDefuseSound.h b/Source/FeatureHelpers/Sound/BombDefuseSound.h new file mode 100644 index 00000000000..fdde0eca408 --- /dev/null +++ b/Source/FeatureHelpers/Sound/BombDefuseSound.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +#include + +struct BombDefuseSound { + static constexpr auto kFadeAwayStart = 2.0f; + static constexpr auto kFadeAwayDuration = 1.0f; + + [[nodiscard]] static constexpr float getScale(float clipSpaceZ) noexcept + { + return (std::max)(1.0f - clipSpaceZ / 1000.0f, 0.4f); + } + + [[nodiscard]] static constexpr float getOpacity(float timeAlive) noexcept + { + if (timeAlive >= kFadeAwayStart) { + return 1.0f - (std::min)((timeAlive - kFadeAwayStart) / kFadeAwayDuration, 1.0f); + } else { + return 1.0f; + } + } + + [[nodiscard]] static constexpr bool isSound(std::string_view soundName) noexcept + { + return soundName.starts_with(cs2::kBombSoundsPath) && std::string_view{ soundName.data() + cs2::kBombSoundsPath.length(), soundName.length() - cs2::kBombSoundsPath.length() }.starts_with(cs2::kBombDefuseStartSoundName); + } +}; diff --git a/Source/FeatureHelpers/Sound/BombDefuseVisualizerHelpers.h b/Source/FeatureHelpers/Sound/BombDefuseVisualizerHelpers.h new file mode 100644 index 00000000000..b4fe783e22e --- /dev/null +++ b/Source/FeatureHelpers/Sound/BombDefuseVisualizerHelpers.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include +#include +#include + +struct BombDefuseVisualizerHelpers { + HudInWorldPanelFactory hudInWorldPanelFactory; + GlobalVarsProvider globalVarsProvider; + PanoramaTransformFactory transformFactory; + WorldToClipSpaceConverter worldtoClipSpaceConverter; +}; diff --git a/Source/FeatureHelpers/Sound/SoundWatcher.h b/Source/FeatureHelpers/Sound/SoundWatcher.h index f3432f10c99..5ee27c4c4b0 100644 --- a/Source/FeatureHelpers/Sound/SoundWatcher.h +++ b/Source/FeatureHelpers/Sound/SoundWatcher.h @@ -1,8 +1,9 @@ #pragma once #include "BombBeepSound.h" +#include "BombDefuseSound.h" #include "BombPlantSound.h" #include "FootstepSound.h" #include "SoundWatcherImpl.h" -using SoundWatcher = SoundWatcherImpl; +using SoundWatcher = SoundWatcherImpl; diff --git a/Source/Features/Sound/BombDefuseVisualizer.h b/Source/Features/Sound/BombDefuseVisualizer.h new file mode 100644 index 00000000000..1d9ac34a249 --- /dev/null +++ b/Source/Features/Sound/BombDefuseVisualizer.h @@ -0,0 +1,124 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct BombDefusePanels { + [[nodiscard]] static cs2::CPanel2D* createContainerPanel(const HudInWorldPanelFactory& inWorldFactory) noexcept + { + return inWorldFactory.createPanel("BombDefuseContainer", HudInWorldPanelZOrder::BombDefuse); + } + + static void createContentPanels(cs2::CUIPanel& containerPanel) noexcept + { + for (std::size_t i = 0; i < kMaxNumberOfPanels; ++i) { + PanoramaUiEngine::runScript(&containerPanel, + R"( +(function() { +var bombDefusePanel = $.CreatePanel('Panel', $.GetContextPanel().FindChildInLayoutFile("BombDefuseContainer"), '', { + style: 'width: 100px; height: 100px; x: -50px; y: -50px;' +}); + +$.CreatePanel('Image', bombDefusePanel, '', { + src: "s2r://panorama/images/icons/equipment/defuser.vsvg", + style: "horizontal-align: center; vertical-align: center; img-shadow: 0px 0px 1px 3 #000000;", + textureheight: "40" +}); +})();)", "", 0); + } + } + + static constexpr auto kMaxNumberOfPanels = 5; +}; + +class BombDefuseVisualizer : public TogglableFeature { +public: + explicit BombDefuseVisualizer(ViewRenderHook& viewRenderHook, SoundWatcher& soundWatcher) noexcept + : viewRenderHook{ viewRenderHook } + , soundWatcher{ soundWatcher } + { + } + + void run(const BombDefuseVisualizerHelpers& params) noexcept + { + if (!isEnabled()) + return; + + if (!params.globalVarsProvider || !params.globalVarsProvider.getGlobalVars()) + return; + + if (!params.worldtoClipSpaceConverter) + return; + + panels.createPanels(params.hudInWorldPanelFactory); + + std::size_t currentIndex = 0; + std::as_const(soundWatcher).getSoundsOfType().forEach([this, ¤tIndex, params](const PlayedSound& sound) { + const auto soundInClipSpace = params.worldtoClipSpaceConverter.toClipSpace(sound.origin); + if (!soundInClipSpace.onScreen()) + return; + + const auto opacity = BombDefuseSound::getOpacity(sound.getTimeAlive(params.globalVarsProvider.getGlobalVars()->curtime)); + if (opacity <= 0.0f) + return; + + const auto panel = panels.getPanel(currentIndex); + if (!panel) + return; + + const auto style = panel.getStyle(); + if (!style) + return; + + style.setOpacity(opacity); + style.setZIndex(-soundInClipSpace.z); + + const auto deviceCoordinates = soundInClipSpace.toNormalizedDeviceCoordinates(); + cs2::CTransform3D* transformations[]{ params.transformFactory.create( + BombDefuseSound::getScale(soundInClipSpace.z), BombDefuseSound::getScale(soundInClipSpace.z), 1.0f + ), params.transformFactory.create( + deviceCoordinates.getX(), + deviceCoordinates.getY(), + cs2::CUILength{ 0.0f, cs2::CUILength::k_EUILengthLength } + ) }; + + cs2::CUtlVector dummyVector; + dummyVector.allocationCount = 2; + dummyVector.memory = transformations; + dummyVector.growSize = 0; + dummyVector.size = 2; + + style.setTransform3D(dummyVector); + ++currentIndex; + }); + + panels.hidePanels(currentIndex); + } + +private: + friend TogglableFeature; + + void onEnable() noexcept + { + viewRenderHook.incrementReferenceCount(); + soundWatcher.startWatching(); + } + + void onDisable() noexcept + { + viewRenderHook.decrementReferenceCount(); + soundWatcher.stopWatching(); + panels.hidePanels(0); + } + + HudInWorldPanels panels; + ViewRenderHook& viewRenderHook; + SoundWatcher& soundWatcher; +}; diff --git a/Source/Features/Sound/SoundFeatures.h b/Source/Features/Sound/SoundFeatures.h index 79b3fdbe48c..5af1871ddb0 100644 --- a/Source/Features/Sound/SoundFeatures.h +++ b/Source/Features/Sound/SoundFeatures.h @@ -1,6 +1,7 @@ #pragma once #include "BombBeepVisualizer.h" +#include "BombDefuseVisualizer.h" #include "BombPlantVisualizer.h" #include "FootstepVisualizer.h" #include @@ -11,10 +12,12 @@ struct SoundFeatures { : footstepVisualizer{ viewRenderHook, soundWatcher } , bombPlantVisualizer{ viewRenderHook, soundWatcher } , bombBeepVisualizer{ viewRenderHook, soundWatcher } + , bombDefuseVisualizer{ viewRenderHook, soundWatcher } { } FootstepVisualizer footstepVisualizer; BombPlantVisualizer bombPlantVisualizer; BombBeepVisualizer bombBeepVisualizer; + BombDefuseVisualizer bombDefuseVisualizer; }; diff --git a/Source/GlobalContext.h b/Source/GlobalContext.h index a3f60b50e1e..93b8a30d343 100644 --- a/Source/GlobalContext.h +++ b/Source/GlobalContext.h @@ -83,6 +83,7 @@ struct GlobalContext { features->soundFeatures.footstepVisualizer.run(featureHelpers->getFootstepVisualizerHelpers()); features->soundFeatures.bombPlantVisualizer.run(featureHelpers->getBombPlantVisualizerHelpers()); features->soundFeatures.bombBeepVisualizer.run(featureHelpers->getBombBeepVisualizerHelpers()); + features->soundFeatures.bombDefuseVisualizer.run(featureHelpers->getBombDefuseVisualizerHelpers()); } private: diff --git a/Source/Osiris.vcxproj b/Source/Osiris.vcxproj index 4ca071aa390..ba0990631a0 100644 --- a/Source/Osiris.vcxproj +++ b/Source/Osiris.vcxproj @@ -46,6 +46,8 @@ + + @@ -63,6 +65,7 @@ + diff --git a/Source/Osiris.vcxproj.filters b/Source/Osiris.vcxproj.filters index bcb43f18bfe..18441f749fa 100644 --- a/Source/Osiris.vcxproj.filters +++ b/Source/Osiris.vcxproj.filters @@ -711,6 +711,15 @@ FeatureHelpers + + FeatureHelpers\Sound + + + FeatureHelpers\Sound + + + Features\Sound + diff --git a/Source/UI/Panorama/CreateGUI.js b/Source/UI/Panorama/CreateGUI.js index 0b0f52f5692..b66261343e6 100644 --- a/Source/UI/Panorama/CreateGUI.js +++ b/Source/UI/Panorama/CreateGUI.js @@ -206,6 +206,8 @@ $.Osiris = (function () { createYesNoDropDown(visualization, "Visualize Bomb Plant", 'sound', 'visualize_bomb_plant'); $.CreatePanel('Panel', visualization, '', { class: "horizontal-separator" }); createYesNoDropDown(visualization, "Visualize Bomb Beep", 'sound', 'visualize_bomb_beep'); + $.CreatePanel('Panel', visualization, '', { class: "horizontal-separator" }); + createYesNoDropDown(visualization, "Visualize Bomb Defuse", 'sound', 'visualize_bomb_defuse'); $.Osiris.navigateToTab('hud'); })(); diff --git a/Source/UI/Panorama/SetCommandHandler.h b/Source/UI/Panorama/SetCommandHandler.h index 1f3dbd9a8ad..eb24fd9c22d 100644 --- a/Source/UI/Panorama/SetCommandHandler.h +++ b/Source/UI/Panorama/SetCommandHandler.h @@ -41,6 +41,8 @@ struct SetCommandHandler { handleTogglableFeature(features.soundFeatures.bombPlantVisualizer); } else if (feature == "visualize_bomb_beep") { handleTogglableFeature(features.soundFeatures.bombBeepVisualizer); + } else if (feature == "visualize_bomb_defuse") { + handleTogglableFeature(features.soundFeatures.bombDefuseVisualizer); } }