From cfca28cab0c1f1c503ca9fda3b2bd62a7ec32724 Mon Sep 17 00:00:00 2001 From: Ykkrosh Date: Sun, 4 Apr 2010 21:24:39 +0000 Subject: [PATCH] # Initial audio integration with new simulation system. Support sound group playback from simulation scripts. See #486. Update some sound group files to sound better for rough testing. Clean up CSoundGroup a tiny bit. This was SVN commit r7437. --- .../public/audio/actor/human/death/death.xml | 4 +- .../audio/actor/human/movement/walk.xml | 6 +- .../public/audio/attack/weapon/arrowfly.xml | 2 +- .../public/simulation/components/Health.js | 2 + .../public/simulation/components/Sound.js | 17 +++ .../public/simulation/components/UnitAI.js | 22 +++- .../simulation/components/interfaces/Sound.js | 1 + .../mods/public/simulation/helpers/Sound.js | 13 ++ source/simulation2/Simulation2.cpp | 1 + source/simulation2/TypeList.h | 3 + .../components/CCmpSoundManager.cpp | 123 ++++++++++++++++++ .../components/ICmpSoundManager.cpp | 26 ++++ .../simulation2/components/ICmpSoundManager.h | 39 ++++++ source/sound/SoundGroup.cpp | 71 ++++------ source/sound/SoundGroup.h | 7 +- 15 files changed, 276 insertions(+), 61 deletions(-) create mode 100644 binaries/data/mods/public/simulation/components/Sound.js create mode 100644 binaries/data/mods/public/simulation/components/interfaces/Sound.js create mode 100644 binaries/data/mods/public/simulation/helpers/Sound.js create mode 100644 source/simulation2/components/CCmpSoundManager.cpp create mode 100644 source/simulation2/components/ICmpSoundManager.cpp create mode 100644 source/simulation2/components/ICmpSoundManager.h diff --git a/binaries/data/mods/public/audio/actor/human/death/death.xml b/binaries/data/mods/public/audio/actor/human/death/death.xml index 20b03cccd1..f3f8819c6f 100644 --- a/binaries/data/mods/public/audio/actor/human/death/death.xml +++ b/binaries/data/mods/public/audio/actor/human/death/death.xml @@ -12,8 +12,8 @@ 1 0 1 - 0.5 - 0 + 1.0 + 0.8 10 3 death_11.ogg diff --git a/binaries/data/mods/public/audio/actor/human/movement/walk.xml b/binaries/data/mods/public/audio/actor/human/movement/walk.xml index 89a4323bac..b7cd5b8492 100644 --- a/binaries/data/mods/public/audio/actor/human/movement/walk.xml +++ b/binaries/data/mods/public/audio/actor/human/movement/walk.xml @@ -12,11 +12,11 @@ 1 0 1 - 0.5 - 0 + 1.0 + 0.8 10 3 hstep_dirt_MN_11.ogg audio/actor/human/movement/ - hstep_dirt11.ogg + hstep_dirt_MN_11.ogg diff --git a/binaries/data/mods/public/audio/attack/weapon/arrowfly.xml b/binaries/data/mods/public/audio/attack/weapon/arrowfly.xml index 1506bb1904..1f78400893 100644 --- a/binaries/data/mods/public/audio/attack/weapon/arrowfly.xml +++ b/binaries/data/mods/public/audio/attack/weapon/arrowfly.xml @@ -14,7 +14,7 @@ 1 1.1 0.9 - 1 + 100 3 arrowfly_28.ogg audio/attack/weapon/ diff --git a/binaries/data/mods/public/simulation/components/Health.js b/binaries/data/mods/public/simulation/components/Health.js index e010ebb075..9b739521e7 100644 --- a/binaries/data/mods/public/simulation/components/Health.js +++ b/binaries/data/mods/public/simulation/components/Health.js @@ -37,6 +37,8 @@ Health.prototype.Reduce = function(amount) // might get called multiple times) if (this.hitpoints) { + PlaySound("death", this.entity); + if (this.template.DeathType == "corpse") this.CreateCorpse(); diff --git a/binaries/data/mods/public/simulation/components/Sound.js b/binaries/data/mods/public/simulation/components/Sound.js new file mode 100644 index 0000000000..e52f1b575e --- /dev/null +++ b/binaries/data/mods/public/simulation/components/Sound.js @@ -0,0 +1,17 @@ +function Sound() {} + +Sound.prototype.Init = function() +{ +}; + +Sound.prototype.PlaySoundGroup = function(name) +{ + if (name in this.template.SoundGroups) + { + var cmpSoundManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager); + if (cmpSoundManager) + cmpSoundManager.PlaySoundGroup(this.template.SoundGroups[name], this.entity); + } +}; + +Engine.RegisterComponentType(IID_Sound, "Sound", Sound); diff --git a/binaries/data/mods/public/simulation/components/UnitAI.js b/binaries/data/mods/public/simulation/components/UnitAI.js index cd28a8f80f..ca3280f08b 100644 --- a/binaries/data/mods/public/simulation/components/UnitAI.js +++ b/binaries/data/mods/public/simulation/components/UnitAI.js @@ -66,6 +66,7 @@ UnitAI.prototype.Walk = function(x, z) return; this.SelectAnimation("walk", false, cmpMotion.GetSpeed()); + PlaySound("walk", this.entity); cmpMotion.MoveToPoint(x, z, 0, 0); @@ -200,12 +201,14 @@ UnitAI.prototype.OnMotionChanged = function(msg) var cmpResourceSupply = Engine.QueryInterface(this.gatherTarget, IID_ResourceSupply); var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); - this.gatherTimer = cmpTimer.SetTimeout(this.entity, IID_UnitAI, "GatherTimeout", 1000, {}); + // Get the animation/sound type name + var type = cmpResourceSupply.GetType(); + var typename = "gather_" + (type.specific || type.generic); + + this.gatherTimer = cmpTimer.SetTimeout(this.entity, IID_UnitAI, "GatherTimeout", 1000, {"typename": typename}); // Start the gather animation - var type = cmpResourceSupply.GetType(); - var anim = "gather_" + (type.specific || type.generic); - this.SelectAnimation(anim); + this.SelectAnimation(typename); } } }; @@ -385,6 +388,9 @@ UnitAI.prototype.AttackTimeout = function(data) // Play the attack animation this.SelectAnimation("melee", false, 1); + // Play the sound + // TODO: these sounds should be triggered by the animation instead + PlaySound("attack", this.entity); // Hit the target cmpAttack.PerformAttack(this.attackTarget); @@ -410,7 +416,11 @@ UnitAI.prototype.RepairTimeout = function(data) var cmpBuilder = Engine.QueryInterface(this.entity, IID_Builder); + // Play the sound + PlaySound("build", this.entity); + // Repair/build the target + // TODO: these sounds should be triggered by the animation instead var status = cmpBuilder.PerformBuilding(this.repairTarget); // If the target is fully built and repaired, then stop and go back to idle @@ -439,6 +449,10 @@ UnitAI.prototype.GatherTimeout = function(data) var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); + // Play the gather_* sound + // TODO: these sounds should be triggered by the animation instead + PlaySound(data.typename, this.entity); + // Gather from the target var status = cmpResourceGatherer.PerformGather(this.gatherTarget); diff --git a/binaries/data/mods/public/simulation/components/interfaces/Sound.js b/binaries/data/mods/public/simulation/components/interfaces/Sound.js new file mode 100644 index 0000000000..b598a6f57d --- /dev/null +++ b/binaries/data/mods/public/simulation/components/interfaces/Sound.js @@ -0,0 +1 @@ +Engine.RegisterInterface("Sound"); diff --git a/binaries/data/mods/public/simulation/helpers/Sound.js b/binaries/data/mods/public/simulation/helpers/Sound.js new file mode 100644 index 0000000000..92609c8114 --- /dev/null +++ b/binaries/data/mods/public/simulation/helpers/Sound.js @@ -0,0 +1,13 @@ +/** + * Simple wrapper function for playing sounds that are associated with entities + * @param name Typically one of 'walk', 'run', 'attack', 'death', 'build', + * 'gather_fruit', 'gather_grain', 'gather_wood', 'gather_stone', 'gather_metal' + */ +function PlaySound(name, ent) +{ + var cmpSound = Engine.QueryInterface(ent, IID_Sound); + if (cmpSound) + cmpSound.PlaySoundGroup(name); +} + +Engine.RegisterGlobal("PlaySound", PlaySound); diff --git a/source/simulation2/Simulation2.cpp b/source/simulation2/Simulation2.cpp index 5af1503af0..c352c973fc 100644 --- a/source/simulation2/Simulation2.cpp +++ b/source/simulation2/Simulation2.cpp @@ -63,6 +63,7 @@ public: m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_ObstructionManager, noParam); m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_Pathfinder, noParam); m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_ProjectileManager, noParam); + m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_SoundManager, noParam); m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_Terrain, noParam); // Add scripted system components: diff --git a/source/simulation2/TypeList.h b/source/simulation2/TypeList.h index 59c41d19c6..ad2a338573 100644 --- a/source/simulation2/TypeList.h +++ b/source/simulation2/TypeList.h @@ -90,6 +90,9 @@ COMPONENT(ProjectileManager) INTERFACE(Selectable) COMPONENT(Selectable) +INTERFACE(SoundManager) +COMPONENT(SoundManager) + INTERFACE(Terrain) COMPONENT(Terrain) diff --git a/source/simulation2/components/CCmpSoundManager.cpp b/source/simulation2/components/CCmpSoundManager.cpp new file mode 100644 index 0000000000..2a62376400 --- /dev/null +++ b/source/simulation2/components/CCmpSoundManager.cpp @@ -0,0 +1,123 @@ +/* Copyright (C) 2010 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "simulation2/system/Component.h" +#include "ICmpSoundManager.h" + +#include "ps/CLogger.h" +#include "simulation2/MessageTypes.h" +#include "simulation2/components/ICmpPosition.h" +#include "sound/SoundGroup.h" + +class CCmpSoundManager : public ICmpSoundManager +{ +public: + static void ClassInit(CComponentManager& componentManager) + { + componentManager.SubscribeToMessageType(MT_Update); + } + + DEFAULT_COMPONENT_ALLOCATOR(SoundManager) + + const CSimContext* m_Context; + + std::map m_SoundGroups; + + virtual void Init(const CSimContext& context, const CParamNode& UNUSED(paramNode)) + { + m_Context = &context; + } + + virtual void Deinit(const CSimContext& UNUSED(context)) + { + for (std::map::iterator it = m_SoundGroups.begin(); it != m_SoundGroups.end(); ++it) + delete it->second; + m_SoundGroups.clear(); + } + + virtual void Serialize(ISerializer& UNUSED(serialize)) + { + // Do nothing here - sounds are purely local, and don't need to be preserved across saved games etc + // (If we add music support in here then we might want to save the music state, though) + } + + virtual void Deserialize(const CSimContext& context, const CParamNode& paramNode, IDeserializer& UNUSED(deserialize)) + { + Init(context, paramNode); + } + + virtual void HandleMessage(const CSimContext& UNUSED(context), const CMessage& msg, bool UNUSED(global)) + { + switch (msg.GetType()) + { + case MT_Update: + { + // Update all the sound groups + // TODO: is it sensible to do this once per simulation turn, not once per renderer frame + // or on some other timer? + const CMessageUpdate& msgData = static_cast (msg); + float t = msgData.turnLength.ToFloat(); + for (std::map::iterator it = m_SoundGroups.begin(); it != m_SoundGroups.end(); ++it) + if (it->second) + it->second->Update(t); + break; + } + } + } + + virtual void PlaySoundGroup(std::wstring name, entity_id_t source) + { + // Make sure the sound group is loaded + CSoundGroup* group; + if (m_SoundGroups.find(name) == m_SoundGroups.end()) + { + group = new CSoundGroup(); + if (!group->LoadSoundGroup(L"audio/" + name)) + { + LOGERROR(L"Failed to load sound group '%ls'", name.c_str()); + delete group; + group = NULL; + } + // Cache the sound group (or the null, if it failed) + m_SoundGroups[name] = group; + } + else + { + group = m_SoundGroups[name]; + } + + // Failed to load group -> do nothing + if (!group) + return; + + // Find the source's position, if possible + // (TODO: we should do something more sensible if there's no position available) + CVector3D sourcePos(0, 0, 0); + if (source != INVALID_ENTITY) + { + CmpPtr cmpPosition(*m_Context, source); + if (!cmpPosition.null() && cmpPosition->IsInWorld()) + sourcePos = CVector3D(cmpPosition->GetPosition()); + } + + group->PlayNext(sourcePos); + } +}; + +REGISTER_COMPONENT_TYPE(SoundManager) diff --git a/source/simulation2/components/ICmpSoundManager.cpp b/source/simulation2/components/ICmpSoundManager.cpp new file mode 100644 index 0000000000..12a85ad20f --- /dev/null +++ b/source/simulation2/components/ICmpSoundManager.cpp @@ -0,0 +1,26 @@ +/* Copyright (C) 2010 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "ICmpSoundManager.h" + +#include "simulation2/system/InterfaceScripted.h" + +BEGIN_INTERFACE_WRAPPER(SoundManager) +DEFINE_INTERFACE_METHOD_2("PlaySoundGroup", void, ICmpSoundManager, PlaySoundGroup, std::wstring, entity_id_t) +END_INTERFACE_WRAPPER(SoundManager) diff --git a/source/simulation2/components/ICmpSoundManager.h b/source/simulation2/components/ICmpSoundManager.h new file mode 100644 index 0000000000..ec1877e749 --- /dev/null +++ b/source/simulation2/components/ICmpSoundManager.h @@ -0,0 +1,39 @@ +/* Copyright (C) 2010 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_ICMPSOUNDMANAGER +#define INCLUDED_ICMPSOUNDMANAGER + +#include "simulation2/system/Interface.h" + +/** + * Interface to the engine's sound system. + */ +class ICmpSoundManager : public IComponent +{ +public: + /** + * Start playing audio defined by a sound group file. + * @param name VFS path of sound group .xml, relative to audio/ + * @param source entity emitting the sound (used for positioning) + */ + virtual void PlaySoundGroup(std::wstring name, entity_id_t source) = 0; + + DECLARE_INTERFACE_TYPE(SoundManager) +}; + +#endif // INCLUDED_ICMPSOUNDMANAGER diff --git a/source/sound/SoundGroup.cpp b/source/sound/SoundGroup.cpp index 6909073100..f930e7dffb 100644 --- a/source/sound/SoundGroup.cpp +++ b/source/sound/SoundGroup.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -146,7 +146,7 @@ void CSoundGroup::PlayNext(const CVector3D& position) UploadPropertiesAndPlay(hs, position); } - playtimes[m_index] = 0.0f; + playtimes.at(m_index) = 0.0f; m_index++; m_Intensity++; if(m_Intensity > m_IntensityThreshold) @@ -216,10 +216,7 @@ bool CSoundGroup::LoadSoundGroup(const VfsPath& pathnameXML) if (XeroFile.Load(pathnameXML) != PSRETURN_OK) return false; - // adjust the path name for resources if necessary - //m_Name = XMLfile + directorypath; - - //Define elements used in XML file + // Define elements used in XML file #define EL(x) int el_##x = XeroFile.GetElementID(#x) #define AT(x) int at_##x = XeroFile.GetAttributeID(#x) EL(soundgroup); @@ -263,106 +260,85 @@ bool CSoundGroup::LoadSoundGroup(const VfsPath& pathnameXML) { SetGain(CStr(child.GetText()).ToFloat()); } - - if(child_name == el_looping) + else if(child_name == el_looping) { if(CStr(child.GetText()).ToInt() == 1) SetFlag(eLoop); } - - if(child_name == el_omnipresent) + else if(child_name == el_omnipresent) { if(CStr(child.GetText()).ToInt() == 1) SetFlag(eOmnipresent); } - - if(child_name == el_pitch) + else if(child_name == el_pitch) { this->m_Pitch = CStr(child.GetText()).ToFloat(); } - - if(child_name == el_priority) + else if(child_name == el_priority) { this->m_Priority = CStr(child.GetText()).ToFloat(); } - - if(child_name == el_randorder) + else if(child_name == el_randorder) { if(CStr(child.GetText()).ToInt() == 1) SetFlag(eRandOrder); } - - if(child_name == el_randgain) + else if(child_name == el_randgain) { if(CStr(child.GetText()).ToInt() == 1) SetFlag(eRandGain); } - - if(child_name == el_gainupper) + else if(child_name == el_gainupper) { this->m_GainUpper = CStr(child.GetText()).ToFloat(); } - - if(child_name == el_gainlower) + else if(child_name == el_gainlower) { this->m_GainLower = CStr(child.GetText()).ToFloat(); } - - if(child_name == el_randpitch) + else if(child_name == el_randpitch) { if(CStr(child.GetText()).ToInt() == 1) SetFlag(eRandPitch); } - - if(child_name == el_pitchupper) + else if(child_name == el_pitchupper) { this->m_PitchUpper = CStr(child.GetText()).ToFloat(); } - - if(child_name == el_pitchlower) + else if(child_name == el_pitchlower) { this->m_PitchLower = CStr(child.GetText()).ToFloat(); } - - if(child_name == el_conegain) + else if(child_name == el_conegain) { this->m_ConeOuterGain = CStr(child.GetText()).ToFloat(); } - - if(child_name == el_coneinner) + else if(child_name == el_coneinner) { this->m_ConeInnerAngle = CStr(child.GetText()).ToFloat(); } - - if(child_name == el_coneouter) + else if(child_name == el_coneouter) { this->m_ConeOuterAngle = CStr(child.GetText()).ToFloat(); } - - if(child_name == el_sound) + else if(child_name == el_sound) { CStrW szTemp(child.GetText()); - this->filenames.push_back(szTemp); - + this->filenames.push_back(szTemp); } - if(child_name == el_path) + else if(child_name == el_path) { m_filepath = CStrW(child.GetText()); - } - if(child_name == el_threshold) + else if(child_name == el_threshold) { - //m_intensity_threshold = CStr(child.GetText()).ToFloat(); m_IntensityThreshold = CStr(child.GetText()).ToFloat(); } - - if(child_name == el_decay) + else if(child_name == el_decay) { - //m_intensity_threshold = CStr(child.GetText()).ToFloat(); m_Decay = CStr(child.GetText()).ToFloat(); } - - if(child_name == el_replacement) + else if(child_name == el_replacement) { m_intensity_file = CStrW(child.GetText()); } @@ -370,5 +346,4 @@ bool CSoundGroup::LoadSoundGroup(const VfsPath& pathnameXML) Reload(); return true; - } diff --git a/source/sound/SoundGroup.h b/source/sound/SoundGroup.h index 07be3e49ac..8fad9ad915 100644 --- a/source/sound/SoundGroup.h +++ b/source/sound/SoundGroup.h @@ -66,12 +66,13 @@ enum eSndGrpFlags eRandGain = 0x02, eRandPitch = 0x04, eLoop = 0x08, - eOmnipresent = 0x10 + eOmnipresent = 0x10 }; class CSoundGroup { + NONCOPYABLE(CSoundGroup); public: CSoundGroup(const VfsPath& pathnameXML); CSoundGroup(void); @@ -94,10 +95,10 @@ public: void Update(float TimeSinceLastFrame); // Set a flag using a value from eSndGrpFlags - inline void SetFlag(int flag){ m_Flags |= flag; } + inline void SetFlag(int flag) { m_Flags |= flag; } // Test flag, returns true if flag is set. - inline bool TestFlag(int flag) { return (m_Flags & flag) != 0;} + inline bool TestFlag(int flag) { return (m_Flags & flag) != 0; } private: void SetGain(float gain);