Skip to content

Commit

Permalink
Implement bomb plant sound visualization
Browse files Browse the repository at this point in the history
  • Loading branch information
danielkrupinski committed Oct 31, 2023
1 parent b12119a commit 900e416
Show file tree
Hide file tree
Showing 15 changed files with 276 additions and 44 deletions.
2 changes: 2 additions & 0 deletions Source/CS2/Constants/SoundNames.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ namespace cs2

#if IS_WIN64()
constexpr std::string_view kPlayerFootstepSoundsPath = "sounds\\player\\footsteps\\";
constexpr std::string_view kBombPlantSoundPath = "sounds\\weapons\\c4\\c4_initiate.vsnd";
#elif IS_LINUX()
constexpr std::string_view kPlayerFootstepSoundsPath = "sounds/player/footsteps/";
constexpr std::string_view kBombPlantSoundPath = "sounds/weapons/c4/c4_initiate.vsnd";
#endif

constexpr std::string_view kPlayerSuitSoundPrefix = "suit";
Expand Down
2 changes: 2 additions & 0 deletions Source/Endpoints.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,7 @@ inline void* LoopModeGameHook::getWorldSession(cs2::CLoopModeGame* thisptr) noex
inline void ViewRenderHook::onRenderStart(cs2::CViewRender* thisptr) noexcept
{
GlobalContext::instance().hooks->viewRenderHook.originalOnRenderStart(thisptr);
GlobalContext::instance().soundWatcher->update();
features().soundFeatures.footstepVisualizer.run();
features().soundFeatures.bombPlantVisualizer.run();
}
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,20 @@
#include <MemoryPatterns/FileSystemPatterns.h>
#include <MemoryPatterns/SoundSystemPatterns.h>
#include "PlayedSound.h"
#include "Sound/WatchedSounds.h"
#include "Sound/WatchedSoundType.h"
#include "WatchedSounds.h"
#include "WatchedSoundType.h"
#include <Utils/BitFlags.h>
#include <Utils/DynamicArray.h>

class PlayedSounds {
class SoundWatcher {
public:
static constexpr auto kFootstepLifespan = 2.0f;
static constexpr auto kBombPlantLifespan = 2.5f;

explicit SoundWatcher(GlobalVarsProvider globalVarsProvider) noexcept
: globalVarsProvider{ globalVarsProvider }
{
}

template <WatchedSoundType soundType>
void startWatching() noexcept
Expand All @@ -34,36 +40,56 @@ class PlayedSounds {
watchedSounds.unset<soundType>();
}

void update(float curtime) noexcept
void update() noexcept
{
if (!globalVarsProvider || !globalVarsProvider.getGlobalVars())
return;

if (watchedSounds.has<WatchedSoundType::Footsteps>()) {
getSounds<WatchedSoundType::Footsteps>().removeExpiredSounds(curtime, kFootstepLifespan);
collectSounds(curtime);
getSounds<WatchedSoundType::Footsteps>().removeExpiredSounds(globalVarsProvider.getGlobalVars()->curtime, kFootstepLifespan);
}

if (watchedSounds.has<WatchedSoundType::BombPlant>()) {
getSounds<WatchedSoundType::BombPlant>().removeExpiredSounds(globalVarsProvider.getGlobalVars()->curtime, kBombPlantLifespan);
}

if (watchedSounds)
collectSounds(globalVarsProvider.getGlobalVars()->curtime);
}

template <typename F>
template <WatchedSoundType soundType, typename F>
void forEach(F&& f) const noexcept
{
footsteps.forEach(std::forward<F>(f));
getSounds<soundType>().forEach(std::forward<F>(f));
}

private:
template <WatchedSoundType soundType>
[[nodiscard]] const WatchedSounds& getSounds() const noexcept
{
if constexpr (soundType == WatchedSoundType::Footsteps) {
return footsteps;
} else if constexpr (soundType == WatchedSoundType::BombPlant) {
return bombPlants;
} else {
static_assert(soundType != soundType, "Unknown sound type!");
}
}

template <WatchedSoundType soundType>
[[nodiscard]] WatchedSounds& getSounds() noexcept
{
if constexpr (soundType == WatchedSoundType::Footsteps) {
return footsteps;
} else if constexpr (soundType == WatchedSoundType::BombPlant) {
return bombPlants;
} else {
static_assert(soundType != soundType, "Unknown sound type!");
}
}

void collectSounds(float curtime) noexcept
{
if (!watchedSounds)
return;

if (!soundChannels || !*soundChannels)
return;

Expand All @@ -86,16 +112,24 @@ class PlayedSounds {
fileNames.getString(channel.sfx->fileNameHandle, buffer);
buffer.back() = '\0';

if (watchedSounds.has<WatchedSoundType::Footsteps>()) {
if (!isFootstepSound(std::string_view{buffer.data()}))
continue;
if (const auto sounds = getSoundsToAddTo(std::string_view{buffer.data()}, channel.guid))
sounds->addSound(PlayedSound{ .guid = channel.guid, .spawnTime = curtime, .origin = channelInfo2.memory[i].origin });
}
}

if (footsteps.hasSound(channel.guid))
continue;
[[nodiscard]] WatchedSounds* getSoundsToAddTo(std::string_view soundName, int guid) noexcept
{
if (watchedSounds.has<WatchedSoundType::Footsteps>()) {
if (isFootstepSound(soundName) && !footsteps.hasSound(guid))
return &footsteps;
}

footsteps.addSound(PlayedSound{ .guid = channel.guid, .spawnTime = curtime, .origin = channelInfo2.memory[i].origin });
}
if (watchedSounds.has<WatchedSoundType::BombPlant>()) {
if (isBombPlantSound(soundName) && !bombPlants.hasSound(guid))
return &bombPlants;
}

return nullptr;
}

[[nodiscard]] static bool isFootstepSound(std::string_view soundName) noexcept
Expand All @@ -106,9 +140,16 @@ class PlayedSounds {
return false;
}

[[nodiscard]] static bool isBombPlantSound(std::string_view soundName) noexcept
{
return soundName == cs2::kBombPlantSoundPath;
}

cs2::SoundChannels** soundChannels{ SoundSystemPatterns::soundChannels() };
cs2::CBaseFileSystem** fileSystem{ FileSystemPatterns::fileSystem() };
GlobalVarsProvider globalVarsProvider;

BitFlags<WatchedSoundType, std::uint8_t> watchedSounds;
WatchedSounds footsteps;
WatchedSounds bombPlants;
};
3 changes: 2 additions & 1 deletion Source/FeatureHelpers/Sound/WatchedSoundType.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

enum class WatchedSoundType {
Footsteps
Footsteps,
BombPlant
};
2 changes: 1 addition & 1 deletion Source/FeatureHelpers/Sound/WatchedSounds.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#include <algorithm>

#include <FeatureHelpers/PlayedSound.h>
#include "PlayedSound.h"
#include <Utils/DynamicArray.h>

class WatchedSounds {
Expand Down
4 changes: 2 additions & 2 deletions Source/Features/Features.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
#include "Visuals/VisualFeatures.h"

struct Features {
Features(HudProvider hudProvider, GlobalVarsProvider globalVarsProvider, LoopModeGameHook& loopModeGameHook, ViewRenderHook& viewRenderHook) noexcept
Features(HudProvider hudProvider, GlobalVarsProvider globalVarsProvider, LoopModeGameHook& loopModeGameHook, ViewRenderHook& viewRenderHook, SoundWatcher& soundWatcher) noexcept
: hudFeatures{ PlantedC4Provider{}, hudProvider, globalVarsProvider }
, visuals{ hudProvider, loopModeGameHook }
, soundFeatures{ hudProvider, globalVarsProvider, viewRenderHook }
, soundFeatures{ hudProvider, globalVarsProvider, viewRenderHook, soundWatcher }
{
}

Expand Down
172 changes: 172 additions & 0 deletions Source/Features/Sound/BombPlantVisualizer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#pragma once

#include <algorithm>

#include <CS2/Classes/Panorama.h>
#include <CS2/Constants/SoundNames.h>
#include <FeatureHelpers/Sound/SoundWatcher.h>
#include <FeatureHelpers/TogglableFeature.h>
#include <FeatureHelpers/WorldToClipSpaceConverter.h>
#include <GameClasses/PanoramaUiEngine.h>
#include <Helpers/HudProvider.h>
#include <Helpers/PanoramaTransformFactory.h>
#include <Hooks/ViewRenderHook.h>

class BombPlantVisualizer : public TogglableFeature<BombPlantVisualizer> {
public:
explicit BombPlantVisualizer(HudProvider hudProvider, GlobalVarsProvider globalVarsProvider, ViewRenderHook& viewRenderHook, SoundWatcher& soundWatcher) noexcept
: hudProvider{ hudProvider }
, globalVarsProvider{ globalVarsProvider }
, viewRenderHook{ viewRenderHook }
, soundWatcher{ soundWatcher }
{
}

~BombPlantVisualizer() noexcept
{
if (bombPlantContainerPanelPointer.getHandle().isValid())
PanoramaUiEngine::onDeletePanel(bombPlantContainerPanelPointer.getHandle());
}

void run() noexcept
{
if (!isEnabled())
return;

if (!globalVarsProvider || !globalVarsProvider.getGlobalVars())
return;

if (!worldtoClipSpaceConverter)
return;

createBombPlantPanels();

std::size_t currentIndex = 0;
soundWatcher.forEach<WatchedSoundType::BombPlant>([this, &currentIndex] (const PlayedSound& sound) {
const auto soundInClipSpace = worldtoClipSpaceConverter.toClipSpace(sound.origin);
if (!soundInClipSpace.onScreen())
return;

const auto deviceCoordinates = soundInClipSpace.toNormalizedDeviceCoordinates();
const auto panel = getBombPlantPanel(currentIndex);
if (!panel)
return;

const auto style = panel.getStyle();
if (!style)
return;

const auto timeAlive = sound.getTimeAlive(globalVarsProvider.getGlobalVars()->curtime);
if (timeAlive >= SoundWatcher::kBombPlantLifespan - kBombPlantFadeAwayDuration) {
style.setOpacity((SoundWatcher::kBombPlantLifespan - timeAlive) / kBombPlantFadeAwayDuration);
} else {
style.setOpacity(1.0f);
}

cs2::CTransform3D* transformations[]{ transformFactory.create<cs2::CTransformScale3D>(
(std::max)(1.0f - soundInClipSpace.z / 1000.0f, 0.4f), (std::max)(1.0f - soundInClipSpace.z / 1000.0f, 0.4f), 1.0f
), transformFactory.create<cs2::CTransformTranslate3D>(
deviceCoordinates.getX(),
deviceCoordinates.getY(),
cs2::CUILength{ 0.0f, cs2::CUILength::k_EUILengthLength }
) };

cs2::CUtlVector<cs2::CTransform3D*> dummyVector;
dummyVector.allocationCount = 2;
dummyVector.memory = transformations;
dummyVector.growSize = 0;
dummyVector.size = 2;

style.setTransform3D(dummyVector);
++currentIndex;
});

hidePanels(currentIndex);
}

private:
friend TogglableFeature;

void onEnable() noexcept
{
viewRenderHook.incrementReferenceCount();
soundWatcher.startWatching<WatchedSoundType::BombPlant>();
}

void onDisable() noexcept
{
viewRenderHook.decrementReferenceCount();
soundWatcher.stopWatching<WatchedSoundType::BombPlant>();
hidePanels(0);
}

void createBombPlantPanels() noexcept
{
if (bombPlantContainerPanelPointer.get())
return;

const auto hudReticle = hudProvider.findChildInLayoutFile(cs2::HudReticle);
if (!hudReticle)
return;

PanoramaUiEngine::runScript(hudReticle,
R"(
$.CreatePanel('Panel', $.GetContextPanel(), 'BombPlantContainer', {
style: 'width: 100%; height: 100%;'
});)", "", 0);

bombPlantContainerPanelPointer = hudReticle.findChildInLayoutFile("BombPlantContainer");

for (std::size_t i = 0; i < kMaxNumberOfBombPlantsToDraw; ++i) {
PanoramaUiEngine::runScript(hudReticle,
R"(
(function() {
var bombPlantPanel = $.CreatePanel('Panel', $.GetContextPanel().FindChildInLayoutFile("BombPlantContainer"), '', {
style: 'width: 100px; height: 100px; x: -50px; y: -50px;'
});
$.CreatePanel('Image', bombPlantPanel, '', {
src: "s2r://panorama/images/icons/ui/chatwheel_bombat.svg",
style: "horizontal-align: center; vertical-align: center; img-shadow: 0px 0px 1px 3 #000000;",
textureheight: "64"
});
})();)", "", 0);
}
}

[[nodiscard]] PanoramaUiPanel getBombPlantPanel(std::size_t index) const noexcept
{
const auto bombPlantContainerPanel = bombPlantContainerPanelPointer.get();
if (!bombPlantContainerPanel)
return PanoramaUiPanel{nullptr};

if (const auto children = bombPlantContainerPanel.children()) {
if (children->size > 0 && static_cast<std::size_t>(children->size) > index)
return PanoramaUiPanel{ children->memory[index] };
}
return PanoramaUiPanel{nullptr};
}

void hidePanels(std::size_t fromPanelIndex) const noexcept
{
for (std::size_t i = fromPanelIndex; i < kMaxNumberOfBombPlantsToDraw; ++i) {
const auto panel = getBombPlantPanel(i);
if (!panel)
break;

if (const auto style = panel.getStyle())
style.setOpacity(0.0f);
}
}

HudProvider hudProvider;
PanoramaPanelPointer bombPlantContainerPanelPointer;
GlobalVarsProvider globalVarsProvider;
PanoramaTransformFactory transformFactory;
ViewRenderHook& viewRenderHook;
SoundWatcher& soundWatcher;
WorldToClipSpaceConverter worldtoClipSpaceConverter;

static constexpr auto kMaxNumberOfBombPlantsToDraw = 5;
static constexpr auto kBombPlantFadeAwayDuration = 0.4f;
};
Loading

0 comments on commit 900e416

Please sign in to comment.