Index: binaries/data/mods/public/gui/session/Ambient.js =================================================================== --- binaries/data/mods/public/gui/session/Ambient.js +++ binaries/data/mods/public/gui/session/Ambient.js @@ -5,7 +5,7 @@ { constructor() { - Engine.PlayAmbientSound(pickRandom(this.Tracks), true); + // Engine.PlayAmbientSound(pickRandom(this.Tracks), true); } } Index: binaries/data/mods/public/simulation/components/Sound.js =================================================================== --- binaries/data/mods/public/simulation/components/Sound.js +++ binaries/data/mods/public/simulation/components/Sound.js @@ -8,6 +8,7 @@ "actor/human/movement/walk.xml" + "attack/weapon/sword.xml" + "actor/human/death/death.xml" + + "ambient/water/river_slow.xml" + "" + "" + "" + @@ -21,6 +22,7 @@ Sound.prototype.Init = function() { + this.hasAmbientEmitter = !!this.template.SoundGroups.ambientEmitter; }; Sound.prototype.Serialize = null; // we have no dynamic state to save @@ -50,4 +52,37 @@ } }; +Sound.prototype.OnOwnershipChanged = function(msg) +{ + if (!this.hasAmbientEmitter || msg.to != INVALID_PLAYER) + return; + + let cmpSoundManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager); + cmpSoundManager.RemoveAmbientEmitter(this.entity); + this.emitsAmbientSound = false; +}; + +// This may be performace-intensive for moving ents with ambient emitters +Sound.prototype.OnPositionChanged = function(msg) +{ + if (!this.hasAmbientEmitter || !msg.inWorld) + return; + + if (this.emitsAmbientSound) { + let cmpSoundManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager); + cmpSoundManager.UpdateAmbientEmitterPosition(this.entity); + } + else + this.AddAmbientEmitter(); + +}; + +Sound.prototype.AddAmbientEmitter = function() +{ + let cmpSoundManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager); + this.emitsAmbientSound = cmpSoundManager.AddAmbientEmitter(this.template.SoundGroups.ambientEmitter, this.entity); +} + + + Engine.RegisterComponentType(IID_Sound, "Sound", Sound); Index: source/simulation2/components/CCmpSoundManager.cpp =================================================================== --- source/simulation2/components/CCmpSoundManager.cpp +++ source/simulation2/components/CCmpSoundManager.cpp @@ -80,14 +80,14 @@ if (cmpOwnership) playerOwned = cmpOwnership->GetOwner() == currentPlayer; - CVector3D sourcePos = CVector3D(cmpPosition->GetPosition()); - g_SoundManager->PlayAsGroup(name, sourcePos, source, playerOwned); + g_SoundManager->PlayAsGroup(name, CVector3D(cmpPosition->GetPosition()), source, playerOwned); } virtual void PlaySoundGroupAtPosition(const std::wstring& name, const CFixedVector3D& sourcePos) { if (!g_SoundManager) return; + g_SoundManager->PlayAsGroup(name, CVector3D(sourcePos), INVALID_ENTITY, false); } @@ -98,6 +98,36 @@ g_SoundManager->Pause(true); } + virtual bool AddAmbientEmitter(const VfsPath& name, entity_id_t source) + { + if (!g_SoundManager) + return false; + + CmpPtr cmpPosition(GetSimContext(), source); + if (!cmpPosition || !cmpPosition->IsInWorld()) + return false; + + return g_SoundManager->AddAmbientEmitter(name, CVector3D(cmpPosition->GetPosition()), source); + } + + virtual void RemoveAmbientEmitter(entity_id_t source) + { + if (!g_SoundManager) + return; + g_SoundManager->RemoveAmbientEmitter(source); + } + + virtual void UpdateAmbientEmitterPosition(entity_id_t source) + { + if (!g_SoundManager) + return; + + CmpPtr cmpPosition(GetSimContext(), source); + if (!cmpPosition || !cmpPosition->IsInWorld()) + return; + + g_SoundManager->UpdateAmbientEmitterPosition(source, CVector3D(cmpPosition->GetPosition())); + } }; REGISTER_COMPONENT_TYPE(SoundManager) Index: source/simulation2/components/ICmpSoundManager.h =================================================================== --- source/simulation2/components/ICmpSoundManager.h +++ source/simulation2/components/ICmpSoundManager.h @@ -42,6 +42,12 @@ */ virtual void PlaySoundGroupAtPosition(const std::wstring& name, const CFixedVector3D& sourcePos) = 0; + virtual bool AddAmbientEmitter(const VfsPath& name, entity_id_t source) = 0; + + virtual void RemoveAmbientEmitter(entity_id_t source) = 0; + + virtual void UpdateAmbientEmitterPosition(entity_id_t source) = 0; + virtual void StopMusic() = 0; DECLARE_INTERFACE_TYPE(SoundManager) Index: source/simulation2/components/ICmpSoundManager.cpp =================================================================== --- source/simulation2/components/ICmpSoundManager.cpp +++ source/simulation2/components/ICmpSoundManager.cpp @@ -25,4 +25,7 @@ DEFINE_INTERFACE_METHOD_2("PlaySoundGroup", void, ICmpSoundManager, PlaySoundGroup, std::wstring, entity_id_t) DEFINE_INTERFACE_METHOD_2("PlaySoundGroupAtPosition", void, ICmpSoundManager, PlaySoundGroupAtPosition, std::wstring, CFixedVector3D) DEFINE_INTERFACE_METHOD_0("StopMusic", void, ICmpSoundManager, StopMusic) +DEFINE_INTERFACE_METHOD_2("AddAmbientEmitter", bool, ICmpSoundManager, AddAmbientEmitter, VfsPath, entity_id_t) +DEFINE_INTERFACE_METHOD_1("RemoveAmbientEmitter", void, ICmpSoundManager, RemoveAmbientEmitter, entity_id_t) +DEFINE_INTERFACE_METHOD_1("UpdateAmbientEmitterPosition", void, ICmpSoundManager, UpdateAmbientEmitterPosition, entity_id_t) END_INTERFACE_WRAPPER(SoundManager) Index: source/soundmanager/ISoundManager.h =================================================================== --- source/soundmanager/ISoundManager.h +++ source/soundmanager/ISoundManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -49,6 +49,10 @@ virtual void PlayAsAmbient(const VfsPath& itemPath, bool looping) = 0; virtual void PlayAsGroup(const VfsPath& groupPath, const CVector3D& sourcePos, entity_id_t source, bool ownedSound) = 0; + virtual bool AddAmbientEmitter(const VfsPath& groupPath, const CVector3D& sourcePos, entity_id_t source) = 0; + virtual void RemoveAmbientEmitter(entity_id_t source) = 0; + virtual void UpdateAmbientEmitterPosition(entity_id_t source, const CVector3D& sourcePos) = 0; + virtual bool InDistress() = 0; }; Index: source/soundmanager/SoundManager.h =================================================================== --- source/soundmanager/SoundManager.h +++ source/soundmanager/SoundManager.h @@ -64,6 +64,12 @@ ALSourceHolder* m_ALSourceBuffer; ISoundItem* m_CurrentTune; ISoundItem* m_CurrentEnvirons; + struct SAmbientEmitterData { + ISoundItem* soundItem; + CVector3D position; + entity_id_t id; + }; + std::vector m_AmbientEmitters; CSoundManagerWorker* m_Worker; std::mutex m_DistressMutex; PlayList* m_PlayListItems; @@ -139,7 +145,7 @@ void PlayAsAmbient(const VfsPath& itemPath, bool looping); void PlayAsUI(const VfsPath& itemPath, bool looping); void PlayAsGroup(const VfsPath& groupPath, const CVector3D& sourcePos, entity_id_t source, bool ownedSound); - + CSoundGroup* GetSoundGroup(const VfsPath& groupPath); void PlayGroupItem(ISoundItem* anItem, ALfloat groupGain); bool InDistress(); @@ -149,6 +155,7 @@ void Pause(bool pauseIt); void PauseMusic(bool pauseIt); void PauseAmbient(bool pauseIt); + void PauseAmbientEmitters(bool pause); void PauseAction(bool pauseIt); void SetAmbientItem(ISoundItem* anItem); @@ -158,6 +165,11 @@ void SetActionGain(float gain); void SetUIGain(float gain); + float GetAmbientEmitterGain(const CVector3D& emitterPos) const; + bool AddAmbientEmitter(const VfsPath& groupPath, const CVector3D& sourcePos, entity_id_t source); + void RemoveAmbientEmitter(entity_id_t source); + void UpdateAmbientEmitterPosition(entity_id_t source, const CVector3D& sourcePos); + protected: void InitListener(); Status AlcInit(); Index: source/soundmanager/SoundManager.cpp =================================================================== --- source/soundmanager/SoundManager.cpp +++ source/soundmanager/SoundManager.cpp @@ -24,11 +24,13 @@ #include "items/CSoundItem.h" #include "items/CStreamItem.h" +#include "graphics/GameView.h" #include "lib/external_libraries/libsdl.h" #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" +#include "ps/Game.h" #include "ps/Profiler2.h" #include "ps/XML/Xeromyces.h" @@ -586,6 +588,13 @@ if (m_CurrentEnvirons) m_CurrentEnvirons->EnsurePlay(); + if (g_Game && g_Game->GetView()) + for (const SAmbientEmitterData& ambientEmitter : m_AmbientEmitters) + { + ambientEmitter.soundItem->EnsurePlay(); + ambientEmitter.soundItem->SetGain(GetAmbientEmitterGain(ambientEmitter.position)); + } + if (m_Worker) m_Worker->CleanupItems(); } @@ -638,7 +647,7 @@ m_MusicEnabled = isEnabled; } -void CSoundManager::PlayAsGroup(const VfsPath& groupPath, const CVector3D& sourcePos, entity_id_t source, bool ownedSound) +CSoundGroup* CSoundManager::GetSoundGroup(const VfsPath& groupPath) { // Make sure the sound group is loaded CSoundGroup* group; @@ -649,7 +658,7 @@ { LOGERROR("Failed to load sound group '%s'", groupPath.string8()); delete group; - group = NULL; + group = nullptr; } // Cache the sound group (or the null, if it failed) m_SoundGroups[groupPath.string()] = group; @@ -659,11 +668,90 @@ group = m_SoundGroups[groupPath.string()]; } + return group; +} + +void CSoundManager::PlayAsGroup(const VfsPath& groupPath, const CVector3D& sourcePos, entity_id_t source, bool ownedSound) +{ + CSoundGroup* group = GetSoundGroup(groupPath); // Failed to load group -> do nothing if (group && (ownedSound || !group->TestFlag(eOwnerOnly))) group->PlayNext(sourcePos, source); } +float CSoundManager::GetAmbientEmitterGain(const CVector3D& emitterPos) const +{ + const CVector3D& cameraPos = g_Game->GetView()->GetCameraPosition(); + float itemDist = (emitterPos - cameraPos).Length(); + return std::max(1.0f - itemDist / 200.f, 0.f); // TODO: Use something better +} + +bool CSoundManager::AddAmbientEmitter(const VfsPath& groupPath, const CVector3D& sourcePos, entity_id_t source) +{ + if (!m_Enabled) + return false; + + CSoundGroup* group = GetSoundGroup(groupPath); + if (!group) + return false; + + CSoundData* sndData = group->GetRandomSound(); + if (!sndData) + return false; + + ISoundItem* ambientItem = static_cast(g_SoundManager)->ItemForEntity(source, sndData); + if (!ambientItem) + return false; + + // Use the ambient gain config option for ambient emitters + if (m_AmbientGain > 0.0f) + { + ambientItem->SetGain(group->GetGain()); + ambientItem->PlayLoop(); + } + + SAmbientEmitterData ambientEmitter; + ambientEmitter.soundItem = ambientItem; + ambientEmitter.position = sourcePos; + ambientEmitter.id = source; + + m_AmbientEmitters.emplace_back(ambientEmitter); + + return true; +} + +void CSoundManager::RemoveAmbientEmitter(entity_id_t source) +{ + for (const SAmbientEmitterData& ambientEmitter : m_AmbientEmitters) + { + if (ambientEmitter.id == source) + { + ambientEmitter.soundItem->FadeAndDelete(3.00); + break; + } + } + + m_AmbientEmitters.erase( + std::remove_if(m_AmbientEmitters.begin(), m_AmbientEmitters.end(), + [&](SAmbientEmitterData& emitterData) + { + return emitterData.id == source; + } + ), m_AmbientEmitters.end()); +} + +void CSoundManager::UpdateAmbientEmitterPosition(entity_id_t source, const CVector3D& sourcePos) +{ + for (SAmbientEmitterData& ambientEmitter : m_AmbientEmitters) + { + if (ambientEmitter.id == source) + { + ambientEmitter.position = sourcePos; + break; + } + } +} + void CSoundManager::PlayAsMusic(const VfsPath& itemPath, bool looping) { if (m_Enabled) @@ -712,6 +800,7 @@ PauseMusic(pauseIt); PauseAmbient(pauseIt); PauseAction(pauseIt); + PauseAmbientEmitters(pauseIt); } void CSoundManager::PauseMusic(bool pauseIt) @@ -739,6 +828,13 @@ m_AmbientPaused = pauseIt; } +void CSoundManager::PauseAmbientEmitters(bool pause) +{ + for (SAmbientEmitterData& emitterData : m_AmbientEmitters) + if (emitterData.soundItem) + pause ? emitterData.soundItem->Pause() : emitterData.soundItem->Resume(); +} + void CSoundManager::PauseAction(bool pauseIt) { m_ActionPaused = pauseIt; Index: source/soundmanager/scripting/SoundGroup.h =================================================================== --- source/soundmanager/scripting/SoundGroup.h +++ source/soundmanager/scripting/SoundGroup.h @@ -60,8 +60,11 @@ // Load a group bool LoadSoundGroup(const VfsPath& pathnameXML); + CSoundData* CSoundGroup::GetRandomSound(); void Reload(); + float GetGain() const; + // Release all remaining loaded handles void ReleaseGroup(); @@ -77,7 +80,7 @@ private: void SetGain(float gain); - void UploadPropertiesAndPlay(size_t theIndex, const CVector3D& position, entity_id_t source); + void UploadPropertiesAndPlay(const CVector3D& position, entity_id_t source); void SetDefaultValues(); #if CONFIG2_AUDIO Index: source/soundmanager/scripting/SoundGroup.cpp =================================================================== --- source/soundmanager/scripting/SoundGroup.cpp +++ source/soundmanager/scripting/SoundGroup.cpp @@ -140,7 +140,21 @@ return answer; } -void CSoundGroup::UploadPropertiesAndPlay(size_t index, const CVector3D& position, entity_id_t source) +CSoundData* CSoundGroup::GetRandomSound() +{ + m_CurrentSoundIndex = rand(0, m_Filenames.size()); + + if (m_SoundGroups.empty()) + Reload(); + + if (m_SoundGroups.size() <= m_CurrentSoundIndex) + return nullptr; + + return m_SoundGroups[m_CurrentSoundIndex]; +} + + +void CSoundGroup::UploadPropertiesAndPlay(const CVector3D& position, entity_id_t source) { #if !CONFIG2_AUDIO UNUSED2(index); @@ -150,6 +164,10 @@ if (!g_SoundManager) return; + CSoundData* sndData = GetRandomSound(); + if (!sndData) + return; + bool isOnscreen = false; ALfloat itemRollOff = 0.1f; float offset = RadiansOffCenter(position, isOnscreen, itemRollOff); @@ -157,16 +175,6 @@ if (!isOnscreen && !TestFlag(eDistanceless) && !TestFlag(eOmnipresent)) return; - if (m_SoundGroups.empty()) - Reload(); - - if (m_SoundGroups.size() <= index) - return; - - CSoundData* sndData = m_SoundGroups[index]; - if (!sndData) - return; - ISoundItem* hSound = static_cast(g_SoundManager)->ItemForEntity(source, sndData); if (!hSound) return; @@ -218,8 +226,7 @@ if (m_Filenames.empty()) return; - m_CurrentSoundIndex = rand(0, m_Filenames.size()); - UploadPropertiesAndPlay(m_CurrentSoundIndex, position, source); + UploadPropertiesAndPlay(position, source); } void CSoundGroup::Reload() @@ -246,6 +253,11 @@ #endif } +float CSoundGroup::GetGain() const +{ + return m_Gain; +} + void CSoundGroup::ReleaseGroup() { #if CONFIG2_AUDIO