384 lines
14 KiB
C#
384 lines
14 KiB
C#
/*
|
|
* Copyright (c) Contributors, http://opensimulator.org/
|
|
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of the OpenSimulator Project nor the
|
|
* names of its contributors may be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
using System;
|
|
using System.IO;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
|
|
using Nini.Config;
|
|
using OpenMetaverse;
|
|
using log4net;
|
|
using Mono.Addins;
|
|
|
|
using OpenSim.Framework;
|
|
using OpenSim.Region.Framework.Interfaces;
|
|
using OpenSim.Region.Framework.Scenes;
|
|
|
|
namespace OpenSim.Region.CoreModules.World.Sound
|
|
{
|
|
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "SoundModule")]
|
|
public class SoundModule : INonSharedRegionModule, ISoundModule
|
|
{
|
|
// private static readonly ILog m_log = LogManager.GetLogger(
|
|
// MethodBase.GetCurrentMethod().DeclaringType);
|
|
|
|
private Scene m_scene;
|
|
|
|
public bool Enabled { get; private set; }
|
|
|
|
public float MaxDistance { get; private set; }
|
|
|
|
#region INonSharedRegionModule
|
|
|
|
public void Initialise(IConfigSource configSource)
|
|
{
|
|
IConfig config = configSource.Configs["Sounds"];
|
|
|
|
if (config == null)
|
|
{
|
|
Enabled = true;
|
|
MaxDistance = 100.0f;
|
|
}
|
|
else
|
|
{
|
|
Enabled = config.GetString("Module", "OpenSim.Region.CoreModules.dll:SoundModule") ==
|
|
Path.GetFileName(Assembly.GetExecutingAssembly().Location)
|
|
+ ":" + MethodBase.GetCurrentMethod().DeclaringType.Name;
|
|
MaxDistance = config.GetFloat("MaxDistance", 100.0f);
|
|
}
|
|
}
|
|
|
|
public void AddRegion(Scene scene) { }
|
|
|
|
public void RemoveRegion(Scene scene)
|
|
{
|
|
m_scene.EventManager.OnNewClient -= OnNewClient;
|
|
}
|
|
|
|
public void RegionLoaded(Scene scene)
|
|
{
|
|
if (!Enabled)
|
|
return;
|
|
|
|
m_scene = scene;
|
|
m_scene.EventManager.OnNewClient += OnNewClient;
|
|
|
|
m_scene.RegisterModuleInterface<ISoundModule>(this);
|
|
}
|
|
|
|
public void Close() { }
|
|
|
|
public Type ReplaceableInterface
|
|
{
|
|
get { return typeof(ISoundModule); }
|
|
}
|
|
|
|
public string Name { get { return "Sound Module"; } }
|
|
|
|
#endregion
|
|
|
|
#region Event Handlers
|
|
|
|
private void OnNewClient(IClientAPI client)
|
|
{
|
|
client.OnSoundTrigger += TriggerSound;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ISoundModule
|
|
|
|
public virtual void PlayAttachedSound(
|
|
UUID soundID, UUID ownerID, UUID objectID, double gain, Vector3 position, byte flags, float radius)
|
|
{
|
|
SceneObjectPart part;
|
|
if (!m_scene.TryGetSceneObjectPart(objectID, out part))
|
|
return;
|
|
|
|
SceneObjectGroup grp = part.ParentGroup;
|
|
|
|
if (radius == 0)
|
|
radius = MaxDistance;
|
|
|
|
m_scene.ForEachRootScenePresence(delegate(ScenePresence sp)
|
|
{
|
|
double dis = Util.GetDistanceTo(sp.AbsolutePosition, position);
|
|
if (dis > MaxDistance) // Max audio distance
|
|
return;
|
|
|
|
if (grp.IsAttachment)
|
|
{
|
|
if (grp.HasPrivateAttachmentPoint && sp.ControllingClient.AgentId != grp.OwnerID)
|
|
return;
|
|
|
|
if (sp.ControllingClient.AgentId == grp.OwnerID)
|
|
dis = 0;
|
|
}
|
|
|
|
// Scale by distance
|
|
double thisSpGain = gain * ((radius - dis) / radius);
|
|
|
|
sp.ControllingClient.SendPlayAttachedSound(soundID, objectID,
|
|
ownerID, (float)thisSpGain, flags);
|
|
});
|
|
}
|
|
|
|
public virtual void TriggerSound(
|
|
UUID soundId, UUID ownerID, UUID objectID, UUID parentID, double gain, Vector3 position, UInt64 handle, float radius)
|
|
{
|
|
SceneObjectPart part;
|
|
if (!m_scene.TryGetSceneObjectPart(objectID, out part))
|
|
{
|
|
ScenePresence sp;
|
|
if (!m_scene.TryGetScenePresence(ownerID, out sp))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
SceneObjectGroup grp = part.ParentGroup;
|
|
|
|
if (grp.IsAttachment && grp.AttachmentPoint > 30)
|
|
{
|
|
objectID = ownerID;
|
|
parentID = ownerID;
|
|
}
|
|
}
|
|
|
|
if (radius == 0)
|
|
radius = MaxDistance;
|
|
|
|
m_scene.ForEachRootScenePresence(delegate(ScenePresence sp)
|
|
{
|
|
double dis = Util.GetDistanceTo(sp.AbsolutePosition, position);
|
|
|
|
if (dis > MaxDistance) // Max audio distance
|
|
return;
|
|
|
|
// Scale by distance
|
|
double thisSpGain = gain * ((radius - dis) / radius);
|
|
|
|
sp.ControllingClient.SendTriggeredSound(soundId, ownerID,
|
|
objectID, parentID, handle, position,
|
|
(float)thisSpGain);
|
|
});
|
|
}
|
|
|
|
public virtual void StopSound(UUID objectID)
|
|
{
|
|
SceneObjectPart m_host;
|
|
if (!m_scene.TryGetSceneObjectPart(objectID, out m_host))
|
|
return;
|
|
|
|
StopSound(m_host);
|
|
}
|
|
|
|
private static void StopSound(SceneObjectPart m_host)
|
|
{
|
|
m_host.AdjustSoundGain(0);
|
|
// Xantor 20080528: Clear prim data of sound instead
|
|
if (m_host.ParentGroup.LoopSoundSlavePrims.Contains(m_host))
|
|
{
|
|
if (m_host.ParentGroup.LoopSoundMasterPrim == m_host)
|
|
{
|
|
foreach (SceneObjectPart part in m_host.ParentGroup.LoopSoundSlavePrims)
|
|
{
|
|
part.Sound = UUID.Zero;
|
|
part.SoundFlags = 1 << 5;
|
|
part.SoundRadius = 0;
|
|
part.ScheduleFullUpdate();
|
|
part.SendFullUpdateToAllClients();
|
|
}
|
|
m_host.ParentGroup.LoopSoundMasterPrim = null;
|
|
m_host.ParentGroup.LoopSoundSlavePrims.Clear();
|
|
}
|
|
else
|
|
{
|
|
m_host.Sound = UUID.Zero;
|
|
m_host.SoundFlags = 1 << 5;
|
|
m_host.SoundRadius = 0;
|
|
m_host.ScheduleFullUpdate();
|
|
m_host.SendFullUpdateToAllClients();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_host.Sound = UUID.Zero;
|
|
m_host.SoundFlags = 1 << 5;
|
|
m_host.SoundRadius = 0;
|
|
m_host.ScheduleFullUpdate();
|
|
m_host.SendFullUpdateToAllClients();
|
|
}
|
|
}
|
|
|
|
public virtual void PreloadSound(UUID objectID, UUID soundID, float radius)
|
|
{
|
|
SceneObjectPart part;
|
|
if (soundID == UUID.Zero
|
|
|| !m_scene.TryGetSceneObjectPart(objectID, out part))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (radius == 0)
|
|
radius = MaxDistance;
|
|
|
|
m_scene.ForEachRootScenePresence(delegate(ScenePresence sp)
|
|
{
|
|
if (!(Util.GetDistanceTo(sp.AbsolutePosition, part.AbsolutePosition) >= MaxDistance))
|
|
sp.ControllingClient.SendPreLoadSound(objectID, objectID, soundID);
|
|
});
|
|
}
|
|
|
|
// Xantor 20080528 we should do this differently.
|
|
// 1) apply the sound to the object
|
|
// 2) schedule full update
|
|
// just sending the sound out once doesn't work so well when other avatars come in view later on
|
|
// or when the prim gets moved, changed, sat on, whatever
|
|
// see large number of mantises (mantes?)
|
|
// 20080530 Updated to remove code duplication
|
|
// 20080530 Stop sound if there is one, otherwise volume only changes don't work
|
|
public void LoopSound(UUID objectID, UUID soundID,
|
|
double volume, double radius, bool isMaster)
|
|
{
|
|
SceneObjectPart m_host;
|
|
if (!m_scene.TryGetSceneObjectPart(objectID, out m_host))
|
|
return;
|
|
|
|
if (isMaster)
|
|
m_host.ParentGroup.LoopSoundMasterPrim = m_host;
|
|
|
|
if (m_host.Sound != UUID.Zero)
|
|
StopSound(m_host);
|
|
|
|
m_host.Sound = soundID;
|
|
m_host.SoundGain = volume;
|
|
m_host.SoundFlags = 1; // looping
|
|
m_host.SoundRadius = radius;
|
|
|
|
m_host.ScheduleFullUpdate();
|
|
m_host.SendFullUpdateToAllClients();
|
|
}
|
|
|
|
public void SendSound(UUID objectID, UUID soundID, double volume,
|
|
bool triggered, byte flags, float radius, bool useMaster,
|
|
bool isMaster)
|
|
{
|
|
if (soundID == UUID.Zero)
|
|
return;
|
|
|
|
SceneObjectPart part;
|
|
if (!m_scene.TryGetSceneObjectPart(objectID, out part))
|
|
return;
|
|
|
|
volume = Util.Clip((float)volume, 0, 1);
|
|
|
|
UUID parentID = part.ParentGroup.UUID;
|
|
|
|
Vector3 position = part.AbsolutePosition; // region local
|
|
ulong regionHandle = m_scene.RegionInfo.RegionHandle;
|
|
|
|
if (useMaster)
|
|
{
|
|
if (isMaster)
|
|
{
|
|
if (triggered)
|
|
TriggerSound(soundID, part.OwnerID, part.UUID, parentID, volume, position, regionHandle, radius);
|
|
else
|
|
PlayAttachedSound(soundID, part.OwnerID, part.UUID, volume, position, flags, radius);
|
|
part.ParentGroup.PlaySoundMasterPrim = part;
|
|
if (triggered)
|
|
TriggerSound(soundID, part.OwnerID, part.UUID, parentID, volume, position, regionHandle, radius);
|
|
else
|
|
PlayAttachedSound(soundID, part.OwnerID, part.UUID, volume, position, flags, radius);
|
|
foreach (SceneObjectPart prim in part.ParentGroup.PlaySoundSlavePrims)
|
|
{
|
|
position = prim.AbsolutePosition; // region local
|
|
if (triggered)
|
|
TriggerSound(soundID, part.OwnerID, prim.UUID, parentID, volume, position, regionHandle, radius);
|
|
else
|
|
PlayAttachedSound(soundID, part.OwnerID, prim.UUID, volume, position, flags, radius);
|
|
}
|
|
part.ParentGroup.PlaySoundSlavePrims.Clear();
|
|
part.ParentGroup.PlaySoundMasterPrim = null;
|
|
}
|
|
else
|
|
{
|
|
part.ParentGroup.PlaySoundSlavePrims.Add(part);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (triggered)
|
|
TriggerSound(soundID, part.OwnerID, part.UUID, parentID, volume, position, regionHandle, radius);
|
|
else
|
|
PlayAttachedSound(soundID, part.OwnerID, part.UUID, volume, position, flags, radius);
|
|
}
|
|
}
|
|
|
|
public void TriggerSoundLimited(UUID objectID, UUID sound,
|
|
double volume, Vector3 min, Vector3 max)
|
|
{
|
|
if (sound == UUID.Zero)
|
|
return;
|
|
|
|
SceneObjectPart part;
|
|
if (!m_scene.TryGetSceneObjectPart(objectID, out part))
|
|
return;
|
|
|
|
m_scene.ForEachRootScenePresence(delegate(ScenePresence sp)
|
|
{
|
|
double dis = Util.GetDistanceTo(sp.AbsolutePosition,
|
|
part.AbsolutePosition);
|
|
|
|
if (dis > MaxDistance) // Max audio distance
|
|
return;
|
|
else if (!Util.IsInsideBox(sp.AbsolutePosition, min, max))
|
|
return;
|
|
|
|
// Scale by distance
|
|
double thisSpGain = volume * ((MaxDistance - dis) / MaxDistance);
|
|
|
|
sp.ControllingClient.SendTriggeredSound(sound, part.OwnerID,
|
|
part.UUID, part.ParentGroup.UUID,
|
|
m_scene.RegionInfo.RegionHandle,
|
|
part.AbsolutePosition, (float)thisSpGain);
|
|
});
|
|
}
|
|
|
|
public void SetSoundQueueing(UUID objectID, bool shouldQueue)
|
|
{
|
|
SceneObjectPart part;
|
|
if (!m_scene.TryGetSceneObjectPart(objectID, out part))
|
|
return;
|
|
|
|
part.SoundQueueing = shouldQueue;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|