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);