diff --git a/OpenMetaverse/Types/DoubleDictionary.cs b/OpenMetaverse/Types/DoubleDictionary.cs index 4e3e286c..907eeddf 100644 --- a/OpenMetaverse/Types/DoubleDictionary.cs +++ b/OpenMetaverse/Types/DoubleDictionary.cs @@ -179,6 +179,51 @@ namespace OpenMetaverse return default(TValue); } + public IList FindAll(Predicate predicate) + { + IList list = new List(); + + lock (syncObject) + { + foreach (TValue value in Dictionary1.Values) + { + if (predicate(value)) + list.Add(value); + } + } + + return list; + } + + public int RemoveAll(Predicate predicate) + { + IList list = new List(); + + lock (syncObject) + { + foreach (KeyValuePair kvp in Dictionary1) + { + if (predicate(kvp.Value)) + list.Add(kvp.Key); + } + + IList list2 = new List(list.Count); + foreach (KeyValuePair kvp in Dictionary2) + { + if (predicate(kvp.Value)) + list2.Add(kvp.Key); + } + + for (int i = 0; i < list.Count; i++) + Dictionary1.Remove(list[i]); + + for (int i = 0; i < list2.Count; i++) + Dictionary2.Remove(list2[i]); + } + + return list.Count; + } + public TValue this[TKey1 key1] { get { return Dictionary1[key1]; } diff --git a/Programs/Simian/Archiving/OarFile.cs b/Programs/Simian/Archiving/OarFile.cs index b5c0732a..e5c6e062 100644 --- a/Programs/Simian/Archiving/OarFile.cs +++ b/Programs/Simian/Archiving/OarFile.cs @@ -11,7 +11,7 @@ namespace Simian public class Linkset { public SimulationObject Parent; - public List Children = new List(); + public List Children; } public class OarFile @@ -53,14 +53,16 @@ namespace Simian archive.Close(); } - public static void SavePrims(Simian server, string path) + public static void SavePrims(Simian server, string primsPath, string assetsPath, string textureCacheFolder) { + Dictionary textureList = new Dictionary(); + // Delete all of the old linkset files - try - { - Directory.Delete(path, true); - Directory.CreateDirectory(path); - } + try { Directory.Delete(primsPath, true); } + catch (Exception) { } + + // Create a new folder for the linkset files + try { Directory.CreateDirectory(primsPath); } catch (Exception ex) { Logger.Log("Failed saving prims: " + ex.Message, Helpers.LogLevel.Error); @@ -81,16 +83,74 @@ namespace Simian { Linkset linkset = new Linkset(); linkset.Parent = p; + linkset.Children = p.GetChildren(); - server.Scene.ForEachObject(delegate(SimulationObject q) - { - if (q.Prim.ParentID == p.Prim.LocalID) - linkset.Children.Add(q); - }); + SaveLinkset(linkset, Path.Combine(primsPath, "Primitive_" + linkset.Parent.Prim.ID.ToString() + ".xml")); + } - SaveLinkset(linkset, path + "/Primitive_" + linkset.Parent.Prim.ID.ToString() + ".xml"); + // Add all of the textures on this prim to the save list + for (int i = 0; i < p.Prim.Textures.FaceTextures.Length; i++) + { + Primitive.TextureEntryFace face = p.Prim.Textures.FaceTextures[i]; + if (face != null && !textureList.ContainsKey(face.TextureID)) + textureList.Add(face.TextureID, face.TextureID); } } + + SaveTextures(new List(textureList.Keys), assetsPath, textureCacheFolder); + } + + public static void SaveTextures(IList textures, string assetsPath, string textureCacheFolder) + { + int count = 0; + + // Delete the assets folder + try { Directory.Delete(assetsPath, true); } + catch (Exception) { } + + // Create a new assets folder + try { Directory.CreateDirectory(assetsPath); } + catch (Exception ex) + { + Logger.Log("Failed saving assets: " + ex.Message, Helpers.LogLevel.Error); + return; + } + + // Create a map of all of the textures in the cache + string[] files = Directory.GetFiles(textureCacheFolder, "*.texture", SearchOption.TopDirectoryOnly); + Dictionary idToFiles = new Dictionary(files.Length); + for (int i = 0; i < files.Length; i++) + { + string file = files[i]; + UUID id; + + if (UUID.TryParse(Path.GetFileNameWithoutExtension(file), out id)) + idToFiles[id] = file; + } + + for (int i = 0; i < textures.Count; i++) + { + UUID texture = textures[i]; + + if (idToFiles.ContainsKey(texture)) + { + try + { + File.Copy(idToFiles[texture], Path.Combine(assetsPath, texture.ToString() + "_texture.jp2")); + ++count; + } + catch (Exception ex) + { + Logger.Log("Failed to save texture " + texture.ToString() + ": " + ex.Message, Helpers.LogLevel.Error); + } + } + else + { + Logger.Log("Skipping missing texture " + texture.ToString(), Helpers.LogLevel.Warning); + } + } + + Logger.Log("Copied " + count + " textures to the asset archive folder", Helpers.LogLevel.Info); } static void SaveLinkset(Linkset linkset, string filename) @@ -148,7 +208,10 @@ namespace Simian groupPosition = parent.Prim.Position; WriteVector(writer, "GroupPosition", groupPosition); - WriteVector(writer, "OffsetPosition", groupPosition - prim.Prim.Position); + if (prim.Prim.ParentID == 0) + WriteVector(writer, "OffsetPosition", Vector3.Zero); + else + WriteVector(writer, "OffsetPosition", prim.Prim.Position); WriteQuaternion(writer, "RotationOffset", prim.Prim.Rotation); WriteVector(writer, "Velocity", Vector3.Zero); WriteVector(writer, "RotationalVelocity", Vector3.Zero); @@ -156,10 +219,10 @@ namespace Simian WriteVector(writer, "Acceleration", Vector3.Zero); writer.WriteElementString("Description", prim.Prim.Properties.Description); writer.WriteStartElement("Color"); - writer.WriteElementString("R", prim.Prim.TextColor.R.ToString()); - writer.WriteElementString("G", prim.Prim.TextColor.G.ToString()); - writer.WriteElementString("B", prim.Prim.TextColor.B.ToString()); - writer.WriteElementString("A", prim.Prim.TextColor.G.ToString()); + writer.WriteElementString("R", prim.Prim.TextColor.R.ToString()); + writer.WriteElementString("G", prim.Prim.TextColor.G.ToString()); + writer.WriteElementString("B", prim.Prim.TextColor.B.ToString()); + writer.WriteElementString("A", prim.Prim.TextColor.G.ToString()); writer.WriteEndElement(); writer.WriteElementString("Text", prim.Prim.Text); writer.WriteElementString("SitName", prim.Prim.Properties.SitName); diff --git a/Programs/Simian/Extensions/AssetManager.cs b/Programs/Simian/Extensions/AssetManager.cs index 32450d0e..40496110 100644 --- a/Programs/Simian/Extensions/AssetManager.cs +++ b/Programs/Simian/Extensions/AssetManager.cs @@ -9,8 +9,6 @@ namespace Simian.Extensions { public class AssetManager : IExtension, IAssetProvider { - public const string UPLOAD_DIR = "uploadedAssets"; - Simian server; Dictionary AssetStore = new Dictionary(); string UploadDir; @@ -23,7 +21,7 @@ namespace Simian.Extensions { this.server = server; - UploadDir = Path.Combine(Simian.DATA_DIR, UPLOAD_DIR); + UploadDir = Simian.ASSET_CACHE_DIR; // Try to create the data directories if they don't already exist if (!Directory.Exists(Simian.DATA_DIR)) diff --git a/Programs/Simian/Extensions/Periscope.cs b/Programs/Simian/Extensions/Periscope.cs index 63dcb378..0a72ea24 100644 --- a/Programs/Simian/Extensions/Periscope.cs +++ b/Programs/Simian/Extensions/Periscope.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading; using ExtensionLoader; using OpenMetaverse; @@ -19,6 +20,7 @@ namespace Simian.Extensions PeriscopeImageDelivery imageDelivery; PeriscopeMovement movement; PeriscopeTransferManager transferManager; + bool ignoreObjectKill = false; object loginLock = new object(); public Periscope() @@ -97,58 +99,69 @@ namespace Simian.Extensions agent.LastName = avatar.LastName; server.Scene.AgentAdd(this, agent, avatar.Flags); + server.Scene.ObjectAddOrUpdate(this, obj, obj.Prim.OwnerID, 0, PrimFlags.None, UpdateFlags.FullUpdate); } void Objects_OnObjectUpdated(Simulator simulator, ObjectUpdate update, ulong regionHandle, ushort timeDilation) { SimulationObject obj; + UpdateFlags flags = UpdateFlags.Acceleration | UpdateFlags.AngularVelocity | UpdateFlags.Position | + UpdateFlags.Rotation | UpdateFlags.Velocity; + + if (update.Avatar) flags |= UpdateFlags.CollisionPlane; + if (update.Textures != null) flags |= UpdateFlags.Textures; + if (server.Scene.TryGetObject(update.LocalID, out obj)) { - server.Scene.ObjectAddOrUpdate(this, obj, obj.Prim.OwnerID, 0, PrimFlags.None, - UpdateFlags.Acceleration | UpdateFlags.AngularVelocity | UpdateFlags.CollisionPlane | - UpdateFlags.Position | UpdateFlags.Rotation | UpdateFlags.Velocity); + obj.Prim.Acceleration = update.Acceleration; + obj.Prim.AngularVelocity = update.AngularVelocity; + obj.Prim.Position = update.Position; + obj.Prim.Rotation = update.Rotation; + obj.Prim.Velocity = update.Velocity; + if (update.Avatar) obj.Prim.CollisionPlane = update.CollisionPlane; + if (update.Textures != null) obj.Prim.Textures = update.Textures; + + server.Scene.ObjectAddOrUpdate(this, obj, obj.Prim.OwnerID, 0, PrimFlags.None, flags); } if (update.LocalID == client.Self.LocalID) { MasterAgent.Avatar.Prim.Acceleration = update.Acceleration; MasterAgent.Avatar.Prim.AngularVelocity = update.AngularVelocity; - MasterAgent.Avatar.Prim.CollisionPlane = update.CollisionPlane; MasterAgent.Avatar.Prim.Position = update.Position; MasterAgent.Avatar.Prim.Rotation = update.Rotation; MasterAgent.Avatar.Prim.Velocity = update.Velocity; + if (update.Avatar) MasterAgent.Avatar.Prim.CollisionPlane = update.CollisionPlane; + if (update.Textures != null) MasterAgent.Avatar.Prim.Textures = update.Textures; - if (update.Textures != null) - MasterAgent.Avatar.Prim.Textures = update.Textures; + server.Scene.ObjectAddOrUpdate(this, MasterAgent.Avatar, MasterAgent.ID, 0, PrimFlags.None, flags); } } void Objects_OnObjectKilled(Simulator simulator, uint objectID) { - server.Scene.ObjectRemove(this, objectID); + if (!ignoreObjectKill) + server.Scene.ObjectRemove(this, objectID); } void Avatars_OnAvatarAppearance(UUID avatarID, bool isTrial, Primitive.TextureEntryFace defaultTexture, Primitive.TextureEntryFace[] faceTextures, List visualParams) { + Primitive.TextureEntry te = new Primitive.TextureEntry(defaultTexture); + te.FaceTextures = faceTextures; + byte[] vp = (visualParams != null && visualParams.Count > 1 ? visualParams.ToArray() : null); + Agent agent; if (server.Scene.TryGetAgent(avatarID, out agent)) { - Primitive.TextureEntry te = new Primitive.TextureEntry(defaultTexture); - te.FaceTextures = faceTextures; - - byte[] vp = (visualParams != null && visualParams.Count > 1 ? visualParams.ToArray() : null); - Logger.Log("[Periscope] Updating foreign avatar appearance for " + agent.FirstName + " " + agent.LastName, Helpers.LogLevel.Info); - server.Scene.AgentAppearance(this, agent, te, vp); - - if (agent.ID == client.Self.AgentID) - server.Scene.AgentAppearance(this, MasterAgent, te, vp); } - else + + if (avatarID == client.Self.AgentID) { - Logger.Log("[Periscope] Received a foreign avatar appearance for an unknown avatar", Helpers.LogLevel.Warning); + Logger.Log("[Periscope] Updating foreign avatar appearance for the MasterAgent", Helpers.LogLevel.Info); + server.Scene.AgentAppearance(this, MasterAgent, te, vp); } } @@ -169,44 +182,22 @@ namespace Simian.Extensions void Self_OnChat(string message, ChatAudibleLevel audible, ChatType type, ChatSourceType sourceType, string fromName, UUID id, UUID ownerid, Vector3 position) { - // TODO: Inject chat into the Scene instead of relaying it - ChatFromSimulatorPacket chat = new ChatFromSimulatorPacket(); - chat.ChatData.Audible = (byte)ChatAudibleLevel.Fully; - chat.ChatData.ChatType = (byte)type; - chat.ChatData.OwnerID = ownerid; - chat.ChatData.SourceID = id; - chat.ChatData.SourceType = (byte)sourceType; - chat.ChatData.Position = position; - chat.ChatData.FromName = Utils.StringToBytes(fromName); - chat.ChatData.Message = Utils.StringToBytes(message); - - server.UDP.BroadcastPacket(chat, PacketCategory.Transaction); + server.Scene.ObjectChat(this, ownerid, id, audible, type, sourceType, fromName, position, 0, message); } void Self_OnTeleport(string message, TeleportStatus status, TeleportFlags flags) { if (status == TeleportStatus.Finished) { - server.Scene.ForEachObject( - delegate(SimulationObject obj) - { - if (obj.Prim.RegionHandle != client.Network.CurrentSim.Handle) - server.Scene.ObjectRemove(this, obj.Prim.ID); - } - ); + ulong localRegionHandle = server.Scene.RegionHandle; - ulong localRegionHandle = Utils.UIntsToLong(256 * server.Scene.RegionX, 256 * server.Scene.RegionY); - - server.Scene.ForEachAgent( + server.Scene.RemoveAllAgents( delegate(Agent agent) - { - if (agent.Avatar.Prim.RegionHandle != localRegionHandle && - agent.Avatar.Prim.RegionHandle != client.Network.CurrentSim.Handle) - { - server.Scene.ObjectRemove(this, agent.ID); - } - } - ); + { return agent.Avatar.Prim.RegionHandle != client.Network.CurrentSim.Handle && agent.Avatar.Prim.RegionHandle != localRegionHandle; }); + + server.Scene.RemoveAllObjects( + delegate(SimulationObject obj) + { return obj.Prim.RegionHandle != client.Network.CurrentSim.Handle && obj.Prim.RegionHandle != localRegionHandle; }); } } @@ -219,7 +210,7 @@ namespace Simian.Extensions void AvatarAnimationHandler(Packet packet, Simulator simulator) { AvatarAnimationPacket animations = (AvatarAnimationPacket)packet; - + Agent agent; if (server.Scene.TryGetAgent(animations.Sender.ID, out agent)) { @@ -278,6 +269,7 @@ namespace Simian.Extensions switch (messageParts[0]) { case "/teleport": + { float x, y, z; if (messageParts.Length == 5 && @@ -294,24 +286,79 @@ namespace Simian.Extensions server.Avatars.SendAlert(agent, "Usage: /teleport \"sim name\" x y z"); } return; + } case "/stats": server.Avatars.SendAlert(agent, String.Format("Downloading textures: {0}, Queued textures: {1}", imageDelivery.Pipeline.CurrentCount, imageDelivery.Pipeline.QueuedCount)); return; + case "/objectkill": + if (messageParts.Length == 2) + { + if (messageParts[1] == "off" || messageParts[1] == "0") + { + ignoreObjectKill = true; + server.Avatars.SendAlert(agent, "Ignoring upstream ObjectKill packets"); + } + else + { + ignoreObjectKill = false; + server.Avatars.SendAlert(agent, "Enabling upstream ObjectKill packets"); + } + } + return; case "/save": + { if (messageParts.Length == 2) { string filename = messageParts[1]; - string directoryname = System.IO.Path.GetFileNameWithoutExtension(filename); + string directoryname = Path.GetFileNameWithoutExtension(filename); - Logger.Log(String.Format("Preparing to serialize {0} objects", server.Scene.ObjectCount()), Helpers.LogLevel.Info); - OarFile.SavePrims(server, directoryname + "/objects"); - Logger.Log("Saving " + directoryname, Helpers.LogLevel.Info); - OarFile.PackageArchive(directoryname, filename); - System.IO.Directory.Delete(directoryname, true); - Logger.Log("Done", Helpers.LogLevel.Info); + Thread saveThread = new Thread(new ThreadStart( + delegate() + { + Logger.Log(String.Format("Preparing to serialize {0} objects", server.Scene.ObjectCount()), Helpers.LogLevel.Info); + OarFile.SavePrims(server, directoryname + "/objects", directoryname + "/assets", Simian.ASSET_CACHE_DIR); + + try { Directory.Delete(directoryname + "/terrains", true); } + catch (Exception) { } + + try + { + Directory.CreateDirectory(directoryname + "/terrains"); + + using (FileStream stream = new FileStream(directoryname + "/terrains/heightmap.r32", FileMode.Create, FileAccess.Write)) + { + for (int y = 0; y < 256; y++) + { + for (int x = 0; x < 256; x++) + { + float t = server.Scene.GetTerrainHeightAt(x, y); + stream.Write(BitConverter.GetBytes(t), 0, 4); + } + } + } + } + catch (Exception ex) + { + Logger.Log("Failed saving terrain: " + ex.Message, Helpers.LogLevel.Error); + } + + Logger.Log("Saving " + directoryname, Helpers.LogLevel.Info); + OarFile.PackageArchive(directoryname, filename); + + try + { System.IO.Directory.Delete(directoryname, true); } + catch (Exception ex) + { Logger.Log("Failed to delete temporary directory " + directoryname + ": " + ex.Message, Helpers.LogLevel.Error); } + + server.Avatars.SendAlert(agent, "Finished OAR export to " + filename); + })); + + saveThread.Start(); + server.Avatars.SendAlert(agent, "Starting OAR export to " + filename); } return; + } case "/nudemod": //int count = 0; // FIXME: AvatarAppearance locks the agents dictionary. Need to be able to copy the agents dictionary? @@ -351,18 +398,14 @@ namespace Simian.Extensions client.Self.Chat(finalMessage, chat.ChatData.Channel, (ChatType)chat.ChatData.Type); } - static void EraseTexture(Avatar avatar, AppearanceManager.TextureIndex texture) - { - Primitive.TextureEntryFace face = avatar.Textures.FaceTextures[(int)texture]; - if (face != null) face.TextureID = UUID.Zero; - } - void AgentUpdateHandler(Packet packet, Agent agent) { AgentUpdatePacket update = (AgentUpdatePacket)packet; - if (MasterAgent == null) + if (MasterAgent == null || (!client.Network.Connected && client.Network.LoginStatusCode == LoginStatus.Failed)) { + MasterAgent = null; + lock (loginLock) { // Double-checked locking to avoid hitting the loginLock each time @@ -465,6 +508,12 @@ namespace Simian.Extensions } #endregion Simian client packet handlers + + static void EraseTexture(Avatar avatar, AppearanceManager.TextureIndex texture) + { + Primitive.TextureEntryFace face = avatar.Textures.FaceTextures[(int)texture]; + if (face != null) face.TextureID = UUID.Zero; + } } public static class CommandLineParser diff --git a/Programs/Simian/Extensions/SceneManager.cs b/Programs/Simian/Extensions/SceneManager.cs index 22007cfc..95ef1f56 100644 --- a/Programs/Simian/Extensions/SceneManager.cs +++ b/Programs/Simian/Extensions/SceneManager.cs @@ -90,12 +90,14 @@ namespace Simian.Extensions public void Stop() { - while (sceneAgents.Count > 0) + lock (sceneAgents) { - Dictionary.ValueCollection.Enumerator e = sceneAgents.Values.GetEnumerator(); - e.MoveNext(); - AgentRemove(this, e.Current); + List agents = new List(sceneAgents.Values); + for (int i = 0; i < agents.Count; i++) + ObjectRemove(this, agents[i].ID); } + + Logger.DebugLog("SceneManager is stopped"); } #region Object Interfaces @@ -246,11 +248,11 @@ namespace Simian.Extensions SimulationObject obj; Agent agent; - if (sceneAgents.TryGetValue(id, out agent)) - AgentRemove(sender, agent); - if (sceneObjects.TryGetValue(id, out obj)) { + if (sceneAgents.TryGetValue(id, out agent)) + AgentRemove(sender, agent); + if (OnObjectRemove != null) OnObjectRemove(sender, obj); @@ -443,6 +445,11 @@ namespace Simian.Extensions return sceneObjects.FindValue(predicate); } + public int RemoveAllObjects(Predicate predicate) + { + return sceneObjects.RemoveAll(predicate); + } + public void TriggerSound(object sender, UUID objectID, UUID parentID, UUID ownerID, UUID soundID, Vector3 position, float gain) { if (OnTriggerSound != null) @@ -546,9 +553,7 @@ namespace Simian.Extensions // Add the agent to the scene dictionary lock (sceneAgents) sceneAgents[agent.ID] = agent; - // Send out an update to everyone - //ObjectAdd(this, agent.Avatar, agent.Avatar.Prim.OwnerID, 0, PrimFlags.None); - + Logger.Log("Added agent " + agent.FullName + " to the scene", Helpers.LogLevel.Info); return true; } @@ -561,13 +566,6 @@ namespace Simian.Extensions lock (sceneAgents) sceneAgents.Remove(agent.ID); - KillObjectPacket kill = new KillObjectPacket(); - kill.ObjectData = new KillObjectPacket.ObjectDataBlock[1]; - kill.ObjectData[0] = new KillObjectPacket.ObjectDataBlock(); - kill.ObjectData[0].ID = agent.Avatar.Prim.LocalID; - - server.UDP.BroadcastPacket(kill, PacketCategory.State); - // Kill the EventQueue RemoveEventQueue(agent.ID); @@ -647,6 +645,25 @@ namespace Simian.Extensions return null; } + public int RemoveAllAgents(Predicate predicate) + { + List list = new List(); + + lock (sceneAgents) + { + foreach (KeyValuePair kvp in sceneAgents) + { + if (predicate(kvp.Value)) + list.Add(kvp.Key); + } + + for (int i = 0; i < list.Count; i++) + sceneAgents.Remove(list[i]); + } + + return list.Count; + } + #endregion Agent Interfaces #region Terrain and Wind diff --git a/Programs/Simian/Interfaces/ISceneProvider.cs b/Programs/Simian/Interfaces/ISceneProvider.cs index f506677c..cb99e52b 100644 --- a/Programs/Simian/Interfaces/ISceneProvider.cs +++ b/Programs/Simian/Interfaces/ISceneProvider.cs @@ -168,11 +168,13 @@ namespace Simian bool TryGetObject(UUID id, out SimulationObject obj); void ForEachObject(Action obj); SimulationObject FindObject(Predicate predicate); + int RemoveAllObjects(Predicate predicate); int AgentCount(); bool TryGetAgent(UUID id, out Agent agent); void ForEachAgent(Action action); Agent FindAgent(Predicate predicate); + int RemoveAllAgents(Predicate predicate); void SendEvent(Agent agent, string name, OSDMap body); bool HasRunningEventQueue(Agent agent); diff --git a/Programs/Simian/Simian.cs b/Programs/Simian/Simian.cs index 95a5ffa8..8f1460d8 100644 --- a/Programs/Simian/Simian.cs +++ b/Programs/Simian/Simian.cs @@ -20,6 +20,7 @@ namespace Simian { public const string CONFIG_FILE = "Simian.ini"; public const string DATA_DIR = "SimianData/"; + public const string ASSET_CACHE_DIR = "SimianData/AssetCache/"; public int UDPPort { get { return 9000; } } public int HttpPort { get { return 8002; } } diff --git a/Programs/Simian/SimulationObject.cs b/Programs/Simian/SimulationObject.cs index 801359a3..1155557c 100644 --- a/Programs/Simian/SimulationObject.cs +++ b/Programs/Simian/SimulationObject.cs @@ -310,8 +310,15 @@ namespace Simian update.Data[0] = (byte)prim.TreeSpecies; break; default: - update.Data = new byte[prim.ScratchPad.Length]; - Buffer.BlockCopy(prim.ScratchPad, 0, update.Data, 0, update.Data.Length); + if (prim.ScratchPad != null) + { + update.Data = new byte[prim.ScratchPad.Length]; + Buffer.BlockCopy(prim.ScratchPad, 0, update.Data, 0, update.Data.Length); + } + else + { + update.Data = new byte[0]; + } break; }