diff --git a/OpenSim/Data/MySQL/MySQLAuthenticationData.cs b/OpenSim/Data/MySQL/MySQLAuthenticationData.cs index 8d82f61bda..664ce84f7c 100644 --- a/OpenSim/Data/MySQL/MySQLAuthenticationData.cs +++ b/OpenSim/Data/MySQL/MySQLAuthenticationData.cs @@ -79,14 +79,7 @@ namespace OpenSim.Data.MySQL { ret.PrincipalID = principalID; - if (m_ColumnNames == null) - { - m_ColumnNames = new List(); - - DataTable schemaTable = result.GetSchemaTable(); - foreach (DataRow row in schemaTable.Rows) - m_ColumnNames.Add(row["ColumnName"].ToString()); - } + CheckColumnNames(result); foreach (string s in m_ColumnNames) { @@ -105,6 +98,20 @@ namespace OpenSim.Data.MySQL } } + private void CheckColumnNames(IDataReader result) + { + if (m_ColumnNames != null) + return; + + List columnNames = new List(); + + DataTable schemaTable = result.GetSchemaTable(); + foreach (DataRow row in schemaTable.Rows) + columnNames.Add(row["ColumnName"].ToString()); + + m_ColumnNames = columnNames; + } + public bool Store(AuthenticationData data) { if (data.Data.ContainsKey("UUID")) diff --git a/OpenSim/Data/MySQL/MySQLGenericTableHandler.cs b/OpenSim/Data/MySQL/MySQLGenericTableHandler.cs index 754cf725f0..da8e958b07 100644 --- a/OpenSim/Data/MySQL/MySQLGenericTableHandler.cs +++ b/OpenSim/Data/MySQL/MySQLGenericTableHandler.cs @@ -91,15 +91,17 @@ namespace OpenSim.Data.MySQL if (m_ColumnNames != null) return; - m_ColumnNames = new List(); + List columnNames = new List(); DataTable schemaTable = reader.GetSchemaTable(); foreach (DataRow row in schemaTable.Rows) { if (row["ColumnName"] != null && (!m_Fields.ContainsKey(row["ColumnName"].ToString()))) - m_ColumnNames.Add(row["ColumnName"].ToString()); + columnNames.Add(row["ColumnName"].ToString()); } + + m_ColumnNames = columnNames; } public virtual T[] Get(string field, string key) diff --git a/OpenSim/Data/MySQL/MySQLRegionData.cs b/OpenSim/Data/MySQL/MySQLRegionData.cs index c20c39263a..d1f1932dd8 100644 --- a/OpenSim/Data/MySQL/MySQLRegionData.cs +++ b/OpenSim/Data/MySQL/MySQLRegionData.cs @@ -162,17 +162,7 @@ namespace OpenSim.Data.MySQL ret.sizeX = Convert.ToInt32(result["sizeX"]); ret.sizeY = Convert.ToInt32(result["sizeY"]); - if (m_ColumnNames == null) - { - m_ColumnNames = new List(); - - DataTable schemaTable = result.GetSchemaTable(); - foreach (DataRow row in schemaTable.Rows) - { - if (row["ColumnName"] != null) - m_ColumnNames.Add(row["ColumnName"].ToString()); - } - } + CheckColumnNames(result); foreach (string s in m_ColumnNames) { @@ -187,7 +177,11 @@ namespace OpenSim.Data.MySQL if (s == "locY") continue; - ret.Data[s] = result[s].ToString(); + object value = result[s]; + if (value is DBNull) + ret.Data[s] = null; + else + ret.Data[s] = result[s].ToString(); } retList.Add(ret); @@ -198,6 +192,23 @@ namespace OpenSim.Data.MySQL return retList; } + private void CheckColumnNames(IDataReader result) + { + if (m_ColumnNames != null) + return; + + List columnNames = new List(); + + DataTable schemaTable = result.GetSchemaTable(); + foreach (DataRow row in schemaTable.Rows) + { + if (row["ColumnName"] != null) + columnNames.Add(row["ColumnName"].ToString()); + } + + m_ColumnNames = columnNames; + } + public bool Store(RegionData data) { if (data.Data.ContainsKey("uuid")) diff --git a/OpenSim/Framework/Console/LocalConsole.cs b/OpenSim/Framework/Console/LocalConsole.cs index 7c8626dac0..f65813b9ee 100644 --- a/OpenSim/Framework/Console/LocalConsole.cs +++ b/OpenSim/Framework/Console/LocalConsole.cs @@ -296,6 +296,10 @@ namespace OpenSim.Framework.Console matches[0].Groups["Category"].Value); System.Console.Write("]:"); } + else + { + outText = outText.Trim(); + } } if (level == "error") diff --git a/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs b/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs index faa413e811..7200c4bc00 100644 --- a/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs @@ -143,7 +143,9 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments } catch (Exception e) { - m_log.ErrorFormat("[ATTACHMENTS MODULE]: Unable to rez attachment: {0}{1}", e.Message, e.StackTrace); + UUID agentId = (sp.ControllingClient == null) ? (UUID)null : sp.ControllingClient.AgentId; + m_log.ErrorFormat("[ATTACHMENTS MODULE]: Unable to rez attachment with itemID {0}, assetID {1}, point {2} for {3}: {4}\n{5}", + attach.ItemID, attach.AssetID, p, agentId, e.Message, e.StackTrace); } } } @@ -389,7 +391,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments lock (sp.AttachmentsSyncLock) { // Save avatar attachment information - m_log.Debug("[ATTACHMENTS MODULE]: Detaching from UserID: " + sp.UUID + ", ItemID: " + itemID); +// m_log.Debug("[ATTACHMENTS MODULE]: Detaching from UserID: " + sp.UUID + ", ItemID: " + itemID); bool changed = sp.Appearance.DetachAttachment(itemID); if (changed && m_scene.AvatarFactory != null) @@ -469,9 +471,9 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments if (grp.HasGroupChanged || (saveAllScripted && grp.ContainsScripts())) { - m_log.DebugFormat( - "[ATTACHMENTS MODULE]: Updating asset for attachment {0}, attachpoint {1}", - grp.UUID, grp.AttachmentPoint); +// m_log.DebugFormat( +// "[ATTACHMENTS MODULE]: Updating asset for attachment {0}, attachpoint {1}", +// grp.UUID, grp.AttachmentPoint); string sceneObjectXml = SceneObjectSerializer.ToOriginalXmlFormat(grp); @@ -502,12 +504,12 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments } grp.HasGroupChanged = false; // Prevent it being saved over and over } - else - { - m_log.DebugFormat( - "[ATTACHMENTS MODULE]: Don't need to update asset for unchanged attachment {0}, attachpoint {1}", - grp.UUID, grp.AttachmentPoint); - } +// else +// { +// m_log.DebugFormat( +// "[ATTACHMENTS MODULE]: Don't need to update asset for unchanged attachment {0}, attachpoint {1}", +// grp.UUID, grp.AttachmentPoint); +// } } /// @@ -889,13 +891,12 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments // Calls attach with a Zero position if (AttachObject(sp, part.ParentGroup, AttachmentPt, false)) { - m_scene.EventManager.TriggerOnAttach(objectLocalID, part.ParentGroup.FromItemID, remoteClient.AgentId); +// m_log.Debug( +// "[ATTACHMENTS MODULE]: Saving avatar attachment. AgentID: " + remoteClient.AgentId +// + ", AttachmentPoint: " + AttachmentPt); // Save avatar attachment information - m_log.Debug( - "[ATTACHMENTS MODULE]: Saving avatar attachment. AgentID: " + remoteClient.AgentId - + ", AttachmentPoint: " + AttachmentPt); - + m_scene.EventManager.TriggerOnAttach(objectLocalID, part.ParentGroup.FromItemID, remoteClient.AgentId); } } catch (Exception e) diff --git a/OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs b/OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs index b0cee03e34..0ed10d2864 100644 --- a/OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs @@ -158,7 +158,7 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory // Process the baked texture array if (textureEntry != null) { - m_log.InfoFormat("[AVFACTORY]: Received texture update for {0} {1}", sp.Name, sp.UUID); +// m_log.DebugFormat("[AVFACTORY]: Received texture update for {0} {1}", sp.Name, sp.UUID); // WriteBakedTexturesReport(sp, m_log.DebugFormat); @@ -208,7 +208,8 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory ScenePresence sp = m_scene.GetScenePresence(agentId); if (sp == null) { - m_log.WarnFormat("[AVFACTORY]: Agent {0} no longer in the scene", agentId); + // This is expected if the user has gone away. +// m_log.DebugFormat("[AVFACTORY]: Agent {0} no longer in the scene", agentId); return false; } @@ -248,10 +249,10 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory if (bakedTextureFace == null) { - m_log.WarnFormat( - "[AV FACTORY]: No texture ID set for {0} for {1} in {2} not found when trying to save permanently", - bakeType, sp.Name, m_scene.RegionInfo.RegionName); - + // This can happen legitimately, since some baked textures might not exist + //m_log.WarnFormat( + // "[AV FACTORY]: No texture ID set for {0} for {1} in {2} not found when trying to save permanently", + // bakeType, sp.Name, m_scene.RegionInfo.RegionName); continue; } @@ -337,7 +338,7 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory return false; } - m_log.DebugFormat("[AVFACTORY]: Completed texture check for {0} {1}", sp.Name, sp.UUID); +// m_log.DebugFormat("[AVFACTORY]: Completed texture check for {0} {1}", sp.Name, sp.UUID); // If we only found default textures, then the appearance is not cached return (defonly ? false : true); @@ -417,7 +418,8 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory // acd.AgentID, i, acd.Appearance.Texture.FaceTextures[i]); int ftIndex = (int)AppearanceManager.BakeTypeToAgentTextureIndex(bakeType); - bakedTextures[bakeType] = faceTextures[ftIndex]; + Primitive.TextureEntryFace texture = faceTextures[ftIndex]; // this will be null if there's no such baked texture + bakedTextures[bakeType] = texture; } return bakedTextures; @@ -482,7 +484,8 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory ScenePresence sp = m_scene.GetScenePresence(agentid); if (sp == null) { - m_log.WarnFormat("[AVFACTORY]: Agent {0} no longer in the scene", agentid); + // This is expected if the user has gone away. +// m_log.DebugFormat("[AVFACTORY]: Agent {0} no longer in the scene", agentid); return; } diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs index fa9cd551ad..a36d0fe7c6 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs @@ -61,8 +61,6 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer set { m_MaxTransferDistance = value; } } - private int m_levelHGTeleport = 0; - protected bool m_Enabled = false; protected Scene m_aScene; protected List m_Scenes = new List(); @@ -106,7 +104,6 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer if (transferConfig != null) { MaxTransferDistance = transferConfig.GetInt("max_distance", 4095); - m_levelHGTeleport = transferConfig.GetInt("LevelHGTeleport", 0); } m_agentsInTransit = new List(); @@ -172,13 +169,17 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // Reset animations; the viewer does that in teleports. sp.Animator.ResetAnimations(); + string destinationRegionName = "(not found)"; + try { if (regionHandle == sp.Scene.RegionInfo.RegionHandle) { + destinationRegionName = sp.Scene.RegionInfo.RegionName; + m_log.DebugFormat( - "[ENTITY TRANSFER MODULE]: RequestTeleportToLocation {0} within {1}", - position, sp.Scene.RegionInfo.RegionName); + "[ENTITY TRANSFER MODULE]: RequestTeleportToLocation for {0} to {1} within existing region {2}", + sp.Name, position, destinationRegionName); // Teleport within the same region if (IsOutsideRegion(sp.Scene, position) || position.Z < 0) @@ -188,6 +189,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer m_log.WarnFormat( "[ENTITY TRANSFER MODULE]: RequestTeleportToLocation() was given an illegal position of {0} for avatar {1}, {2}. Substituting {3}", position, sp.Name, sp.UUID, emergencyPos); + position = emergencyPos; } @@ -210,6 +212,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer sp.ControllingClient.SendTeleportStart(teleportFlags); sp.ControllingClient.SendLocalTeleport(position, lookAt, teleportFlags); + sp.Velocity = Vector3.Zero; sp.Teleport(position); foreach (SceneObjectGroup grp in sp.GetAttachments()) @@ -233,15 +236,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer return; } - // check if HyperGrid teleport is allowed, based on user level - int flags = m_aScene.GridService.GetRegionFlags(sp.Scene.RegionInfo.ScopeID, reg.RegionID); - - if (((flags & (int)OpenSim.Data.RegionFlags.Hyperlink) != 0) && (sp.UserLevel < m_levelHGTeleport)) - { - m_log.WarnFormat("[ENTITY TRANSFER MODULE]: Final destination link is non permitted hypergrid region. Unable to teleport agent."); - sp.ControllingClient.SendTeleportFailed("HyperGrid teleport not permitted"); - return; - } + destinationRegionName = finalDestination.RegionName; uint curX = 0, curY = 0; Utils.LongToUInts(sp.Scene.RegionInfo.RegionHandle, out curX, out curY); @@ -307,7 +302,11 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer } catch (Exception e) { - m_log.WarnFormat("[ENTITY TRANSFER MODULE]: Exception on teleport: {0} {1}", e.Message, e.StackTrace); + m_log.ErrorFormat( + "[ENTITY TRANSFER MODULE]: Exception on teleport of {0} from {1}@{2} to {3}@{4}: {5}{6}", + sp.Name, sp.AbsolutePosition, sp.Scene.RegionInfo.RegionName, position, destinationRegionName, + e.Message, e.StackTrace); + sp.ControllingClient.SendTeleportFailed("Internal error"); } } @@ -402,7 +401,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer bool logout = false; if (!CreateAgent(sp, reg, finalDestination, agentCircuit, teleportFlags, out reason, out logout)) { - sp.ControllingClient.SendTeleportFailed(String.Format("Destination refused: {0}", + sp.ControllingClient.SendTeleportFailed(String.Format("Teleport refused: {0}", reason)); return; } diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/HGEntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/HGEntityTransferModule.cs index 7f9175d2fc..634fb4315e 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/HGEntityTransferModule.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/HGEntityTransferModule.cs @@ -50,6 +50,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private bool m_Initialized = false; + private int m_levelHGTeleport = 0; private GatekeeperServiceConnector m_GatekeeperConnector; @@ -68,6 +69,10 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer string name = moduleConfig.GetString("EntityTransferModule", ""); if (name == Name) { + IConfig transferConfig = source.Configs["EntityTransfer"]; + if (transferConfig != null) + m_levelHGTeleport = transferConfig.GetInt("LevelHGTeleport", 0); + InitialiseCommon(source); m_log.DebugFormat("[HG ENTITY TRANSFER MODULE]: {0} enabled.", Name); } @@ -164,6 +169,14 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer if (flags == -1 /* no region in DB */ || (flags & (int)OpenSim.Data.RegionFlags.Hyperlink) != 0) { // this user is going to another grid + // check if HyperGrid teleport is allowed, based on user level + if (sp.UserLevel < m_levelHGTeleport) + { + m_log.WarnFormat("[HG ENTITY TRANSFER MODULE]: Unable to HG teleport agent due to insufficient UserLevel."); + reason = "HyperGrid teleport not permitted"; + return false; + } + if (agentCircuit.ServiceURLs.ContainsKey("HomeURI")) { string userAgentDriver = agentCircuit.ServiceURLs["HomeURI"].ToString(); diff --git a/OpenSim/Region/CoreModules/World/LegacyMap/MapImageModule.cs b/OpenSim/Region/CoreModules/World/LegacyMap/MapImageModule.cs index f86c7906a4..aa306c7db5 100644 --- a/OpenSim/Region/CoreModules/World/LegacyMap/MapImageModule.cs +++ b/OpenSim/Region/CoreModules/World/LegacyMap/MapImageModule.cs @@ -225,7 +225,7 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap int tc = 0; double[,] hm = whichScene.Heightmap.GetDoubles(); tc = Environment.TickCount; - m_log.Info("[MAPTILE]: Generating Maptile Step 2: Object Volume Profile"); + m_log.Debug("[MAPTILE]: Generating Maptile Step 2: Object Volume Profile"); EntityBase[] objs = whichScene.GetEntities(); Dictionary z_sort = new Dictionary(); //SortedList z_sort = new SortedList(); @@ -541,7 +541,7 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap g.Dispose(); } // lock entities objs - m_log.Info("[MAPTILE]: Generating Maptile Step 2: Done in " + (Environment.TickCount - tc) + " ms"); + m_log.Debug("[MAPTILE]: Generating Maptile Step 2: Done in " + (Environment.TickCount - tc) + " ms"); return mapbmp; } diff --git a/OpenSim/Region/CoreModules/World/LegacyMap/ShadedMapTileRenderer.cs b/OpenSim/Region/CoreModules/World/LegacyMap/ShadedMapTileRenderer.cs index eb1a27f9ec..992bff3290 100644 --- a/OpenSim/Region/CoreModules/World/LegacyMap/ShadedMapTileRenderer.cs +++ b/OpenSim/Region/CoreModules/World/LegacyMap/ShadedMapTileRenderer.cs @@ -54,7 +54,7 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap public void TerrainToBitmap(Bitmap mapbmp) { int tc = Environment.TickCount; - m_log.Info("[MAPTILE]: Generating Maptile Step 1: Terrain"); + m_log.Debug("[MAPTILE]: Generating Maptile Step 1: Terrain"); double[,] hm = m_scene.Heightmap.GetDoubles(); bool ShadowDebugContinue = true; @@ -238,7 +238,7 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap } } } - m_log.Info("[MAPTILE]: Generating Maptile Step 1: Done in " + (Environment.TickCount - tc) + " ms"); + m_log.Debug("[MAPTILE]: Generating Maptile Step 1: Done in " + (Environment.TickCount - tc) + " ms"); } } } diff --git a/OpenSim/Region/CoreModules/World/LegacyMap/TexturedMapTileRenderer.cs b/OpenSim/Region/CoreModules/World/LegacyMap/TexturedMapTileRenderer.cs index 1d2141e5e1..d13c2ef91d 100644 --- a/OpenSim/Region/CoreModules/World/LegacyMap/TexturedMapTileRenderer.cs +++ b/OpenSim/Region/CoreModules/World/LegacyMap/TexturedMapTileRenderer.cs @@ -278,7 +278,7 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap public void TerrainToBitmap(Bitmap mapbmp) { int tc = Environment.TickCount; - m_log.Info("[MAPTILE]: Generating Maptile Step 1: Terrain"); + m_log.Debug("[MAPTILE]: Generating Maptile Step 1: Terrain"); // These textures should be in the AssetCache anyway, as every client conneting to this // region needs them. Except on start, when the map is recreated (before anyone connected), @@ -412,7 +412,7 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap } } } - m_log.Info("[MAPTILE]: Generating Maptile Step 1: Done in " + (Environment.TickCount - tc) + " ms"); + m_log.Debug("[MAPTILE]: Generating Maptile Step 1: Done in " + (Environment.TickCount - tc) + " ms"); } } } diff --git a/OpenSim/Region/CoreModules/World/Warp3DMap/MapImageModule.cs b/OpenSim/Region/CoreModules/World/Warp3DMap/MapImageModule.cs index 6163fd12fe..4f4e296b27 100644 --- a/OpenSim/Region/CoreModules/World/Warp3DMap/MapImageModule.cs +++ b/OpenSim/Region/CoreModules/World/Warp3DMap/MapImageModule.cs @@ -88,11 +88,11 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap if (renderers.Count > 0) { m_primMesher = RenderingLoader.LoadRenderer(renderers[0]); - m_log.Info("[MAPTILE]: Loaded prim mesher " + m_primMesher.ToString()); + m_log.Debug("[MAPTILE]: Loaded prim mesher " + m_primMesher.ToString()); } else { - m_log.Info("[MAPTILE]: No prim mesher loaded, prim rendering will be disabled"); + m_log.Debug("[MAPTILE]: No prim mesher loaded, prim rendering will be disabled"); } m_scene.RegisterModuleInterface(this); diff --git a/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs b/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs index 10b25edac1..816d3b64ce 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs @@ -1988,7 +1988,7 @@ namespace OpenSim.Region.Framework.Scenes } } - if (permissionToTake) + if (permissionToTake && (action != DeRezAction.Delete || this.m_useTrashOnDelete)) { m_asyncSceneObjectDeleter.DeleteToInventory( action, destinationID, deleteGroups, remoteClient, diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs index a87dfb7d0f..7a2b2edd81 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.cs @@ -103,6 +103,7 @@ namespace OpenSim.Region.Framework.Scenes public bool m_trustBinaries; public bool m_allowScriptCrossings; public bool m_useFlySlow; + public bool m_useTrashOnDelete = true; /// /// Temporarily setting to trigger appearance resends at 60 second intervals. @@ -709,6 +710,7 @@ namespace OpenSim.Region.Framework.Scenes m_clampPrimSize = true; } + m_useTrashOnDelete = startupConfig.GetBoolean("UseTrashOnDelete",m_useTrashOnDelete); m_trustBinaries = startupConfig.GetBoolean("TrustBinaries", m_trustBinaries); m_allowScriptCrossings = startupConfig.GetBoolean("AllowScriptCrossing", m_allowScriptCrossings); m_dontPersistBefore = diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs index 49a3485183..268600409f 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs @@ -1969,6 +1969,11 @@ namespace OpenSim.Region.Framework.Scenes /// The group of prims which should be linked to this group public void LinkToGroup(SceneObjectGroup objectGroup) { + LinkToGroup(objectGroup, false); + } + + public void LinkToGroup(SceneObjectGroup objectGroup, bool insert) + { // m_log.DebugFormat( // "[SCENE OBJECT GROUP]: Linking group with root part {0}, {1} to group with root part {2}, {3}", // objectGroup.RootPart.Name, objectGroup.RootPart.UUID, RootPart.Name, RootPart.UUID); @@ -1979,6 +1984,10 @@ namespace OpenSim.Region.Framework.Scenes SceneObjectPart linkPart = objectGroup.m_rootPart; + // physics flags from group to be applied to linked parts + bool grpusephys = UsesPhysics; + bool grptemporary = IsTemporary; + Vector3 oldGroupPosition = linkPart.GroupPosition; Quaternion oldRootRotation = linkPart.RotationOffset; @@ -2002,15 +2011,35 @@ namespace OpenSim.Region.Framework.Scenes lock (m_parts.SyncRoot) { - int linkNum = PrimCount + 1; + int linkNum; + if (insert) + { + linkNum = 2; + foreach (SceneObjectPart part in Parts) + { + if (part.LinkNum > 1) + part.LinkNum++; + } + } + else + { + linkNum = PrimCount + 1; + } m_parts.Add(linkPart.UUID, linkPart); linkPart.SetParent(this); linkPart.CreateSelected = true; + // let physics know preserve part volume dtc messy since UpdatePrimFlags doesn't look to parent changes for now + linkPart.UpdatePrimFlags(grpusephys, grptemporary, (IsPhantom || (linkPart.Flags & PrimFlags.Phantom) != 0), linkPart.VolumeDetectActive); + if (linkPart.PhysActor != null && m_rootPart.PhysActor != null && m_rootPart.PhysActor.IsPhysical) + { + linkPart.PhysActor.link(m_rootPart.PhysActor); + this.Scene.PhysicsScene.AddPhysicsActorTaint(linkPart.PhysActor); + } + linkPart.LinkNum = linkNum++; - linkPart.UpdatePrimFlags(UsesPhysics, IsTemporary, IsPhantom, IsVolumeDetect); SceneObjectPart[] ogParts = objectGroup.Parts; Array.Sort(ogParts, delegate(SceneObjectPart a, SceneObjectPart b) @@ -2022,7 +2051,16 @@ namespace OpenSim.Region.Framework.Scenes { SceneObjectPart part = ogParts[i]; if (part.UUID != objectGroup.m_rootPart.UUID) + { LinkNonRootPart(part, oldGroupPosition, oldRootRotation, linkNum++); + // let physics know + part.UpdatePrimFlags(grpusephys, grptemporary, (IsPhantom || (part.Flags & PrimFlags.Phantom) != 0), part.VolumeDetectActive); + if (part.PhysActor != null && m_rootPart.PhysActor != null && m_rootPart.PhysActor.IsPhysical) + { + part.PhysActor.link(m_rootPart.PhysActor); + this.Scene.PhysicsScene.AddPhysicsActorTaint(part.PhysActor); + } + } part.ClearUndoState(); } } diff --git a/OpenSim/Region/Framework/Scenes/ScenePresence.cs b/OpenSim/Region/Framework/Scenes/ScenePresence.cs index b5f789b52a..e8178ce905 100644 --- a/OpenSim/Region/Framework/Scenes/ScenePresence.cs +++ b/OpenSim/Region/Framework/Scenes/ScenePresence.cs @@ -990,23 +990,24 @@ namespace OpenSim.Region.Framework.Scenes /// public void Teleport(Vector3 pos) { - bool isFlying = Flying; - RemoveFromPhysicalScene(); - Velocity = Vector3.Zero; - CheckLandingPoint(ref pos); - AbsolutePosition = pos; - AddToPhysicalScene(isFlying); - - SendTerseUpdateToAllClients(); + TeleportWithMomentum(pos, null); } - public void TeleportWithMomentum(Vector3 pos) + public void TeleportWithMomentum(Vector3 pos, Vector3? v) { bool isFlying = Flying; + Vector3 vel = Velocity; RemoveFromPhysicalScene(); CheckLandingPoint(ref pos); AbsolutePosition = pos; AddToPhysicalScene(isFlying); + if (PhysicsActor != null) + { + if (v.HasValue) + PhysicsActor.SetMomentum((Vector3)v); + else + PhysicsActor.SetMomentum(vel); + } SendTerseUpdateToAllClients(); } diff --git a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceTeleportTests.cs b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceTeleportTests.cs index bebc10ca25..eb7bfbd802 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceTeleportTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceTeleportTests.cs @@ -33,6 +33,7 @@ using OpenMetaverse; using OpenSim.Framework; using OpenSim.Framework.Communications; using OpenSim.Framework.Servers; +using OpenSim.Region.CoreModules.Framework.EntityTransfer; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; using OpenSim.Tests.Common; @@ -47,6 +48,41 @@ namespace OpenSim.Region.Framework.Scenes.Tests [TestFixture] public class ScenePresenceTeleportTests { + [Test] + public void TestSameRegionTeleport() + { + TestHelpers.InMethod(); +// log4net.Config.XmlConfigurator.Configure(); + + EntityTransferModule etm = new EntityTransferModule(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + // Not strictly necessary since FriendsModule assumes it is the default (!) + config.Configs["Modules"].Set("EntityTransferModule", etm.Name); + + TestScene scene = SceneHelpers.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + SceneHelpers.SetupSceneModules(scene, config, etm); + + Vector3 teleportPosition = new Vector3(10, 11, 12); + Vector3 teleportLookAt = new Vector3(20, 21, 22); + + ScenePresence sp = SceneHelpers.AddScenePresence(scene, TestHelpers.ParseTail(0x1)); + sp.AbsolutePosition = new Vector3(30, 31, 32); + scene.RequestTeleportLocation( + sp.ControllingClient, + scene.RegionInfo.RegionHandle, + teleportPosition, + teleportLookAt, + (uint)TeleportFlags.ViaLocation); + + Assert.That(sp.AbsolutePosition, Is.EqualTo(teleportPosition)); + + // Lookat is sent to the client only - sp.Lookat does not yield the same thing (calculation from camera + // position instead). +// Assert.That(sp.Lookat, Is.EqualTo(teleportLookAt)); + } + /// /// Test a teleport between two regions that are not neighbours and do not share any neighbours in common. /// diff --git a/OpenSim/Region/OptionalModules/Avatar/Friends/FriendsCommandsModule.cs b/OpenSim/Region/OptionalModules/Avatar/Friends/FriendsCommandsModule.cs index e68f9d07a3..2602050de4 100644 --- a/OpenSim/Region/OptionalModules/Avatar/Friends/FriendsCommandsModule.cs +++ b/OpenSim/Region/OptionalModules/Avatar/Friends/FriendsCommandsModule.cs @@ -58,6 +58,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Friends private Scene m_scene; private IFriendsModule m_friendsModule; private IUserManagement m_userManagementModule; + private IPresenceService m_presenceService; // private IAvatarFactoryModule m_avatarFactory; @@ -99,8 +100,9 @@ namespace OpenSim.Region.OptionalModules.Avatar.Friends m_friendsModule = m_scene.RequestModuleInterface(); m_userManagementModule = m_scene.RequestModuleInterface(); + m_presenceService = m_scene.RequestModuleInterface(); - if (m_friendsModule != null && m_userManagementModule != null) + if (m_friendsModule != null && m_userManagementModule != null && m_presenceService != null) { m_scene.AddCommand( "Friends", this, "friends show", @@ -162,7 +164,8 @@ namespace OpenSim.Region.OptionalModules.Avatar.Friends MainConsole.Instance.OutputFormat("Friends for {0} {1} {2}:", firstName, lastName, userId); - MainConsole.Instance.OutputFormat("UUID, Name, MyFlags, TheirFlags"); + MainConsole.Instance.OutputFormat( + "{0,-36} {1,-36} {2,-7} {3,7} {4,10}", "UUID", "Name", "Status", "MyFlags", "TheirFlags"); foreach (FriendInfo friend in friends) { @@ -175,14 +178,22 @@ namespace OpenSim.Region.OptionalModules.Avatar.Friends UUID friendId; string friendName; + string onlineText; if (UUID.TryParse(friend.Friend, out friendId)) friendName = m_userManagementModule.GetUserName(friendId); else friendName = friend.Friend; + OpenSim.Services.Interfaces.PresenceInfo[] pi = m_presenceService.GetAgents(new string[] { friend.Friend }); + if (pi.Length > 0) + onlineText = "online"; + else + onlineText = "offline"; + MainConsole.Instance.OutputFormat( - "{0} {1} {2} {3}", friend.Friend, friendName, friend.MyFlags, friend.TheirFlags); + "{0,-36} {1,-36} {2,-7} {3,-7} {4,-10}", + friend.Friend, friendName, onlineText, friend.MyFlags, friend.TheirFlags); } } } diff --git a/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreScriptModule.cs b/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreScriptModule.cs index eda2aef9ed..4949097632 100644 --- a/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreScriptModule.cs +++ b/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreScriptModule.cs @@ -163,28 +163,37 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore return; } - m_comms.RegisterScriptInvocation(this,"JsonCreateStore"); - m_comms.RegisterScriptInvocation(this,"JsonDestroyStore"); + try + { + m_comms.RegisterScriptInvocation(this,"JsonCreateStore"); + m_comms.RegisterScriptInvocation(this,"JsonDestroyStore"); - m_comms.RegisterScriptInvocation(this,"JsonReadNotecard"); - m_comms.RegisterScriptInvocation(this,"JsonWriteNotecard"); + m_comms.RegisterScriptInvocation(this,"JsonReadNotecard"); + m_comms.RegisterScriptInvocation(this,"JsonWriteNotecard"); - m_comms.RegisterScriptInvocation(this,"JsonTestPath"); - m_comms.RegisterScriptInvocation(this,"JsonTestPathJson"); + m_comms.RegisterScriptInvocation(this,"JsonTestPath"); + m_comms.RegisterScriptInvocation(this,"JsonTestPathJson"); - m_comms.RegisterScriptInvocation(this,"JsonGetValue"); - m_comms.RegisterScriptInvocation(this,"JsonGetValueJson"); + m_comms.RegisterScriptInvocation(this,"JsonGetValue"); + m_comms.RegisterScriptInvocation(this,"JsonGetValueJson"); - m_comms.RegisterScriptInvocation(this,"JsonTakeValue"); - m_comms.RegisterScriptInvocation(this,"JsonTakeValueJson"); + m_comms.RegisterScriptInvocation(this,"JsonTakeValue"); + m_comms.RegisterScriptInvocation(this,"JsonTakeValueJson"); - m_comms.RegisterScriptInvocation(this,"JsonReadValue"); - m_comms.RegisterScriptInvocation(this,"JsonReadValueJson"); + m_comms.RegisterScriptInvocation(this,"JsonReadValue"); + m_comms.RegisterScriptInvocation(this,"JsonReadValueJson"); - m_comms.RegisterScriptInvocation(this,"JsonSetValue"); - m_comms.RegisterScriptInvocation(this,"JsonSetValueJson"); + m_comms.RegisterScriptInvocation(this,"JsonSetValue"); + m_comms.RegisterScriptInvocation(this,"JsonSetValueJson"); - m_comms.RegisterScriptInvocation(this,"JsonRemoveValue"); + m_comms.RegisterScriptInvocation(this,"JsonRemoveValue"); + } + catch (Exception e) + { + // See http://opensimulator.org/mantis/view.php?id=5971 for more information + m_log.WarnFormat("[JsonStroreScripts] script method registration failed; {0}",e.Message); + m_enabled = false; + } } } diff --git a/OpenSim/Region/OptionalModules/Scripting/Minimodule/SPAvatar.cs b/OpenSim/Region/OptionalModules/Scripting/Minimodule/SPAvatar.cs index 922eaafeda..d192309dea 100644 --- a/OpenSim/Region/OptionalModules/Scripting/Minimodule/SPAvatar.cs +++ b/OpenSim/Region/OptionalModules/Scripting/Minimodule/SPAvatar.cs @@ -68,7 +68,7 @@ namespace OpenSim.Region.OptionalModules.Scripting.Minimodule public Vector3 WorldPosition { get { return GetSP().AbsolutePosition; } - set { GetSP().TeleportWithMomentum(value); } + set { GetSP().Teleport(value); } } public bool IsChildAgent diff --git a/OpenSim/Region/OptionalModules/World/NPC/NPCModule.cs b/OpenSim/Region/OptionalModules/World/NPC/NPCModule.cs index adc6f9c765..3ac1eb102d 100644 --- a/OpenSim/Region/OptionalModules/World/NPC/NPCModule.cs +++ b/OpenSim/Region/OptionalModules/World/NPC/NPCModule.cs @@ -157,7 +157,7 @@ namespace OpenSim.Region.OptionalModules.World.NPC sp.CompleteMovement(npcAvatar, false); m_avatars.Add(npcAvatar.AgentId, npcAvatar); - m_log.DebugFormat("[NPC MODULE]: Created NPC with id {0}", npcAvatar.AgentId); + m_log.DebugFormat("[NPC MODULE]: Created NPC {0} {1}", npcAvatar.AgentId, sp.Name); return npcAvatar.AgentId; } @@ -340,7 +340,7 @@ namespace OpenSim.Region.OptionalModules.World.NPC scene.RemoveClient(agentID, false); m_avatars.Remove(agentID); -// m_log.DebugFormat("[NPC MODULE]: Removed {0} {1}", agentID, av.Name); + m_log.DebugFormat("[NPC MODULE]: Removed NPC {0} {1}", agentID, av.Name); return true; } } diff --git a/OpenSim/Region/Physics/OdePlugin/ODEPrim.cs b/OpenSim/Region/Physics/OdePlugin/ODEPrim.cs index 3f88353453..071621495a 100644 --- a/OpenSim/Region/Physics/OdePlugin/ODEPrim.cs +++ b/OpenSim/Region/Physics/OdePlugin/ODEPrim.cs @@ -338,6 +338,7 @@ namespace OpenSim.Region.Physics.OdePlugin d.GeomSetCollideBits(prim_geom, (int)m_collisionFlags); _parent_scene.geom_name_map[prim_geom] = Name; + _parent_scene.actor_name_map[prim_geom] = this; if (childPrim) { diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/ApiManager.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/ApiManager.cs index 47ed6ba42a..684138f79c 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/ApiManager.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/ApiManager.cs @@ -29,42 +29,43 @@ using System; using System.Collections; using System.Collections.Generic; using System.Reflection; +using log4net; using OpenSim.Region.ScriptEngine.Interfaces; namespace OpenSim.Region.ScriptEngine.Shared.Api { public class ApiManager { +// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private Dictionary m_Apis = new Dictionary(); public string[] GetApis() { - if (m_Apis.Count > 0) + if (m_Apis.Count <= 0) { - List l = new List(m_Apis.Keys); - return l.ToArray(); - } + Assembly a = Assembly.GetExecutingAssembly(); - Assembly a = Assembly.GetExecutingAssembly(); + Type[] types = a.GetExportedTypes(); - Type[] types = a.GetExportedTypes(); - - foreach (Type t in types) - { - string name = t.ToString(); - int idx = name.LastIndexOf('.'); - if (idx != -1) - name = name.Substring(idx+1); - - if (name.EndsWith("_Api")) + foreach (Type t in types) { - name = name.Substring(0, name.Length - 4); - m_Apis[name] = t; + string name = t.ToString(); + int idx = name.LastIndexOf('.'); + if (idx != -1) + name = name.Substring(idx+1); + + if (name.EndsWith("_Api")) + { + name = name.Substring(0, name.Length - 4); + m_Apis[name] = t; + } } } - List ret = new List(m_Apis.Keys); - return ret.ToArray(); +// m_log.DebugFormat("[API MANAGER]: Found {0} apis", m_Apis.Keys.Count); + + return new List(m_Apis.Keys).ToArray(); } public IScriptApi CreateApi(string api) @@ -76,4 +77,4 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api return ret; } } -} +} \ No newline at end of file diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs index a2176ba8a6..d641958daf 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs @@ -85,7 +85,12 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api protected IScriptEngine m_ScriptEngine; protected SceneObjectPart m_host; protected uint m_localID; + + /// + /// The UUID of the item that hosts this script + /// protected UUID m_itemID; + protected bool throwErrorOnNotImplemented = true; protected AsyncCommandManager AsyncCommands = null; protected float m_ScriptDelayFactor = 1.0f; @@ -267,23 +272,18 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api } } - protected UUID InventorySelf() + /// + /// Get the inventory item that hosts ourselves. + /// + /// + /// FIXME: It would be far easier to pass in TaskInventoryItem rather than just m_itemID so that we don't need + /// to keep looking ourselves up. + /// + /// + protected TaskInventoryItem GetSelfInventoryItem() { - UUID invItemID = new UUID(); - lock (m_host.TaskInventory) - { - foreach (KeyValuePair inv in m_host.TaskInventory) - { - if (inv.Value.Type == 10 && inv.Value.ItemID == m_itemID) - { - invItemID = inv.Key; - break; - } - } - } - - return invItemID; + return m_host.TaskInventory[m_itemID]; } protected UUID InventoryKey(string name, int type) @@ -832,8 +832,6 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api public void llRegionSayTo(string target, int channel, string msg) { - string error = String.Empty; - if (msg.Length > 1023) msg = msg.Substring(0, 1023); @@ -2701,18 +2699,9 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api public LSL_Integer llGiveMoney(string destination, int amount) { - UUID invItemID=InventorySelf(); - if (invItemID == UUID.Zero) - return 0; - m_host.AddScriptLPS(1); - TaskInventoryItem item = m_host.TaskInventory[invItemID]; - - lock (m_host.TaskInventory) - { - item = m_host.TaskInventory[invItemID]; - } + TaskInventoryItem item = GetSelfInventoryItem(); if (item.PermsGranter == UUID.Zero) return 0; @@ -2955,15 +2944,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api public void llTakeControls(int controls, int accept, int pass_on) { - TaskInventoryItem item; - - lock (m_host.TaskInventory) - { - if (!m_host.TaskInventory.ContainsKey(InventorySelf())) - return; - else - item = m_host.TaskInventory[InventorySelf()]; - } + TaskInventoryItem item = GetSelfInventoryItem(); if (item.PermsGranter != UUID.Zero) { @@ -2983,18 +2964,10 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api public void llReleaseControls() { - TaskInventoryItem item; - - lock (m_host.TaskInventory) - { - if (!m_host.TaskInventory.ContainsKey(InventorySelf())) - return; - else - item = m_host.TaskInventory[InventorySelf()]; - } - m_host.AddScriptLPS(1); + TaskInventoryItem item = GetSelfInventoryItem(); + if (item.PermsGranter != UUID.Zero) { ScenePresence presence = World.GetScenePresence(item.PermsGranter); @@ -3019,64 +2992,33 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api m_UrlModule.ReleaseURL(url); } - public void llAttachToAvatar(int attachment) + /// + /// Attach the object containing this script to the avatar that owns it. + /// + /// The attachment point (e.g. ATTACH_CHEST) + /// true if the attach suceeded, false if it did not + public bool AttachToAvatar(int attachmentPoint) { - m_host.AddScriptLPS(1); + SceneObjectGroup grp = m_host.ParentGroup; + ScenePresence presence = World.GetScenePresence(m_host.OwnerID); -// if (m_host.ParentGroup.RootPart.AttachmentPoint == 0) -// return; + IAttachmentsModule attachmentsModule = m_ScriptEngine.World.AttachmentsModule; - TaskInventoryItem item; - - lock (m_host.TaskInventory) - { - if (!m_host.TaskInventory.ContainsKey(InventorySelf())) - return; - else - item = m_host.TaskInventory[InventorySelf()]; - } - - if (item.PermsGranter != m_host.OwnerID) - return; - - if ((item.PermsMask & ScriptBaseClass.PERMISSION_ATTACH) != 0) - { - SceneObjectGroup grp = m_host.ParentGroup; - - ScenePresence presence = World.GetScenePresence(m_host.OwnerID); - - IAttachmentsModule attachmentsModule = m_ScriptEngine.World.AttachmentsModule; - if (attachmentsModule != null) - attachmentsModule.AttachObject(presence, grp, (uint)attachment, false); - } + if (attachmentsModule != null) + return attachmentsModule.AttachObject(presence, grp, (uint)attachmentPoint, false); + else + return false; } - public void llDetachFromAvatar() + /// + /// Detach the object containing this script from the avatar it is attached to. + /// + /// + /// Nothing happens if the object is not attached. + /// + public void DetachFromAvatar() { - m_host.AddScriptLPS(1); - - if (m_host.ParentGroup.AttachmentPoint == 0) - return; - - TaskInventoryItem item; - - lock (m_host.TaskInventory) - { - if (!m_host.TaskInventory.ContainsKey(InventorySelf())) - return; - else - item = m_host.TaskInventory[InventorySelf()]; - } - - if (item.PermsGranter != m_host.OwnerID) - return; - - if ((item.PermsMask & ScriptBaseClass.PERMISSION_ATTACH) != 0) - { - IAttachmentsModule attachmentsModule = m_ScriptEngine.World.AttachmentsModule; - if (attachmentsModule != null) - Util.FireAndForget(DetachWrapper, m_host); - } + Util.FireAndForget(DetachWrapper, m_host); } private void DetachWrapper(object o) @@ -3092,6 +3034,38 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api attachmentsModule.DetachSingleAttachmentToInv(presence, itemID); } + public void llAttachToAvatar(int attachmentPoint) + { + m_host.AddScriptLPS(1); + +// if (m_host.ParentGroup.RootPart.AttachmentPoint == 0) +// return; + + TaskInventoryItem item = GetSelfInventoryItem(); + + if (item.PermsGranter != m_host.OwnerID) + return; + + if ((item.PermsMask & ScriptBaseClass.PERMISSION_ATTACH) != 0) + AttachToAvatar(attachmentPoint); + } + + public void llDetachFromAvatar() + { + m_host.AddScriptLPS(1); + + if (m_host.ParentGroup.AttachmentPoint == 0) + return; + + TaskInventoryItem item = GetSelfInventoryItem(); + + if (item.PermsGranter != m_host.OwnerID) + return; + + if ((item.PermsMask & ScriptBaseClass.PERMISSION_ATTACH) != 0) + DetachFromAvatar(); + } + public void llTakeCamera(string avatar) { m_host.AddScriptLPS(1); @@ -3317,19 +3291,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api { m_host.AddScriptLPS(1); - UUID invItemID = InventorySelf(); - if (invItemID == UUID.Zero) - return; - - TaskInventoryItem item; - - lock (m_host.TaskInventory) - { - if (!m_host.TaskInventory.ContainsKey(InventorySelf())) - return; - else - item = m_host.TaskInventory[InventorySelf()]; - } + TaskInventoryItem item = GetSelfInventoryItem(); if (item.PermsGranter == UUID.Zero) return; @@ -3354,19 +3316,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api { m_host.AddScriptLPS(1); - UUID invItemID=InventorySelf(); - if (invItemID == UUID.Zero) - return; - - TaskInventoryItem item; - - lock (m_host.TaskInventory) - { - if (!m_host.TaskInventory.ContainsKey(InventorySelf())) - return; - else - item = m_host.TaskInventory[InventorySelf()]; - } + TaskInventoryItem item = GetSelfInventoryItem(); if (item.PermsGranter == UUID.Zero) return; @@ -3421,22 +3371,12 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api public void llRequestPermissions(string agent, int perm) { - UUID agentID = new UUID(); + UUID agentID; if (!UUID.TryParse(agent, out agentID)) return; - UUID invItemID = InventorySelf(); - - if (invItemID == UUID.Zero) - return; // Not in a prim? How?? - - TaskInventoryItem item; - - lock (m_host.TaskInventory) - { - item = m_host.TaskInventory[invItemID]; - } + TaskInventoryItem item = GetSelfInventoryItem(); if (agentID == UUID.Zero || perm == 0) // Releasing permissions { @@ -3470,8 +3410,8 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api { lock (m_host.TaskInventory) { - m_host.TaskInventory[invItemID].PermsGranter = agentID; - m_host.TaskInventory[invItemID].PermsMask = perm; + m_host.TaskInventory[m_itemID].PermsGranter = agentID; + m_host.TaskInventory[m_itemID].PermsMask = perm; } m_ScriptEngine.PostScriptEvent(m_itemID, new EventParams( @@ -3494,8 +3434,8 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api { lock (m_host.TaskInventory) { - m_host.TaskInventory[invItemID].PermsGranter = agentID; - m_host.TaskInventory[invItemID].PermsMask = perm; + m_host.TaskInventory[m_itemID].PermsGranter = agentID; + m_host.TaskInventory[m_itemID].PermsMask = perm; } m_ScriptEngine.PostScriptEvent(m_itemID, new EventParams( @@ -3519,8 +3459,8 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api { lock (m_host.TaskInventory) { - m_host.TaskInventory[invItemID].PermsGranter = agentID; - m_host.TaskInventory[invItemID].PermsMask = 0; + m_host.TaskInventory[m_itemID].PermsGranter = agentID; + m_host.TaskInventory[m_itemID].PermsMask = 0; } presence.ControllingClient.OnScriptAnswer += handleScriptAnswer; @@ -3528,7 +3468,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api } presence.ControllingClient.SendScriptQuestion( - m_host.UUID, m_host.ParentGroup.RootPart.Name, ownerName, invItemID, perm); + m_host.UUID, m_host.ParentGroup.RootPart.Name, ownerName, m_itemID, perm); return; } @@ -3545,20 +3485,15 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api if (taskID != m_host.UUID) return; - UUID invItemID = InventorySelf(); - - if (invItemID == UUID.Zero) - return; - - client.OnScriptAnswer-=handleScriptAnswer; - m_waitingForScriptAnswer=false; + client.OnScriptAnswer -= handleScriptAnswer; + m_waitingForScriptAnswer = false; if ((answer & ScriptBaseClass.PERMISSION_TAKE_CONTROLS) == 0) llReleaseControls(); lock (m_host.TaskInventory) { - m_host.TaskInventory[invItemID].PermsMask = answer; + m_host.TaskInventory[m_itemID].PermsMask = answer; } m_ScriptEngine.PostScriptEvent(m_itemID, new EventParams( @@ -3571,39 +3506,19 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api { m_host.AddScriptLPS(1); - lock (m_host.TaskInventory) - { - foreach (TaskInventoryItem item in m_host.TaskInventory.Values) - { - if (item.Type == 10 && item.ItemID == m_itemID) - { - return item.PermsGranter.ToString(); - } - } - } - - return UUID.Zero.ToString(); + return GetSelfInventoryItem().PermsGranter.ToString(); } public LSL_Integer llGetPermissions() { m_host.AddScriptLPS(1); - lock (m_host.TaskInventory) - { - foreach (TaskInventoryItem item in m_host.TaskInventory.Values) - { - if (item.Type == 10 && item.ItemID == m_itemID) - { - int perms = item.PermsMask; - if (m_automaticLinkPermission) - perms |= ScriptBaseClass.PERMISSION_CHANGE_LINKS; - return perms; - } - } - } + int perms = GetSelfInventoryItem().PermsMask; - return 0; + if (m_automaticLinkPermission) + perms |= ScriptBaseClass.PERMISSION_CHANGE_LINKS; + + return perms; } public LSL_Integer llGetLinkNumber() @@ -3631,17 +3546,12 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api public void llCreateLink(string target, int parent) { m_host.AddScriptLPS(1); - UUID invItemID = InventorySelf(); UUID targetID; if (!UUID.TryParse(target, out targetID)) return; - TaskInventoryItem item; - lock (m_host.TaskInventory) - { - item = m_host.TaskInventory[invItemID]; - } + TaskInventoryItem item = GetSelfInventoryItem(); if ((item.PermsMask & ScriptBaseClass.PERMISSION_CHANGE_LINKS) == 0 && !m_automaticLinkPermission) @@ -3659,11 +3569,16 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api if (targetPart.ParentGroup.AttachmentPoint != 0) return; // Fail silently if attached + + if (targetPart.ParentGroup.RootPart.OwnerID != m_host.ParentGroup.RootPart.OwnerID) + return; + SceneObjectGroup parentPrim = null, childPrim = null; if (targetPart != null) { - if (parent != 0) { + if (parent != 0) + { parentPrim = m_host.ParentGroup; childPrim = targetPart.ParentGroup; } @@ -3675,7 +3590,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api // Required for linking childPrim.RootPart.ClearUpdateSchedule(); - parentPrim.LinkToGroup(childPrim); + parentPrim.LinkToGroup(childPrim, true); } parentPrim.TriggerScriptChangedEvent(Changed.LINK); @@ -3692,16 +3607,12 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api public void llBreakLink(int linknum) { m_host.AddScriptLPS(1); - UUID invItemID = InventorySelf(); - lock (m_host.TaskInventory) + if ((GetSelfInventoryItem().PermsMask & ScriptBaseClass.PERMISSION_CHANGE_LINKS) == 0 + && !m_automaticLinkPermission) { - if ((m_host.TaskInventory[invItemID].PermsMask & ScriptBaseClass.PERMISSION_CHANGE_LINKS) == 0 - && !m_automaticLinkPermission) - { - ShoutError("Script trying to link but PERMISSION_CHANGE_LINKS permission not set!"); - return; - } + ShoutError("Script trying to link but PERMISSION_CHANGE_LINKS permission not set!"); + return; } if (linknum < ScriptBaseClass.LINK_THIS) @@ -4578,23 +4489,11 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api public LSL_String llGetScriptName() { - string result = String.Empty; - m_host.AddScriptLPS(1); - lock (m_host.TaskInventory) - { - foreach (TaskInventoryItem item in m_host.TaskInventory.Values) - { - if (item.Type == 10 && item.ItemID == m_itemID) - { - result = item.Name != null ? item.Name : String.Empty; - break; - } - } - } + TaskInventoryItem item = GetSelfInventoryItem(); - return result; + return item.Name != null ? item.Name : String.Empty; } public LSL_Integer llGetLinkNumberOfSides(int link) @@ -9695,21 +9594,16 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api public LSL_Vector llGetCameraPos() { m_host.AddScriptLPS(1); - UUID invItemID = InventorySelf(); - if (invItemID == UUID.Zero) - return new LSL_Vector(); + TaskInventoryItem item = GetSelfInventoryItem(); - lock (m_host.TaskInventory) + if (item.PermsGranter == UUID.Zero) + return new LSL_Vector(); + + if ((item.PermsMask & ScriptBaseClass.PERMISSION_TRACK_CAMERA) == 0) { - if (m_host.TaskInventory[invItemID].PermsGranter == UUID.Zero) - return new LSL_Vector(); - - if ((m_host.TaskInventory[invItemID].PermsMask & ScriptBaseClass.PERMISSION_TRACK_CAMERA) == 0) - { - ShoutError("No permissions to track the camera"); - return new LSL_Vector(); - } + ShoutError("No permissions to track the camera"); + return new LSL_Vector(); } ScenePresence presence = World.GetScenePresence(m_host.OwnerID); @@ -9724,20 +9618,16 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api public LSL_Rotation llGetCameraRot() { m_host.AddScriptLPS(1); - UUID invItemID = InventorySelf(); - if (invItemID == UUID.Zero) - return new LSL_Rotation(); - lock (m_host.TaskInventory) + TaskInventoryItem item = GetSelfInventoryItem(); + + if (item.PermsGranter == UUID.Zero) + return new LSL_Rotation(); + + if ((item.PermsMask & ScriptBaseClass.PERMISSION_TRACK_CAMERA) == 0) { - if (m_host.TaskInventory[invItemID].PermsGranter == UUID.Zero) - return new LSL_Rotation(); - - if ((m_host.TaskInventory[invItemID].PermsMask & ScriptBaseClass.PERMISSION_TRACK_CAMERA) == 0) - { - ShoutError("No permissions to track the camera"); - return new LSL_Rotation(); - } + ShoutError("No permissions to track the camera"); + return new LSL_Rotation(); } ScenePresence presence = World.GetScenePresence(m_host.OwnerID); @@ -9911,23 +9801,21 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api { m_host.AddScriptLPS(1); - // our key in the object we are in - UUID invItemID = InventorySelf(); - if (invItemID == UUID.Zero) return; - // the object we are in UUID objectID = m_host.ParentUUID; - if (objectID == UUID.Zero) return; + if (objectID == UUID.Zero) + return; - UUID agentID; - lock (m_host.TaskInventory) - { - // we need the permission first, to know which avatar we want to set the camera for - agentID = m_host.TaskInventory[invItemID].PermsGranter; + TaskInventoryItem item = GetSelfInventoryItem(); - if (agentID == UUID.Zero) return; - if ((m_host.TaskInventory[invItemID].PermsMask & ScriptBaseClass.PERMISSION_CONTROL_CAMERA) == 0) return; - } + // we need the permission first, to know which avatar we want to set the camera for + UUID agentID = item.PermsGranter; + + if (agentID == UUID.Zero) + return; + + if ((item.PermsMask & ScriptBaseClass.PERMISSION_CONTROL_CAMERA) == 0) + return; ScenePresence presence = World.GetScenePresence(agentID); @@ -9967,27 +9855,27 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api { m_host.AddScriptLPS(1); - // our key in the object we are in - UUID invItemID=InventorySelf(); - if (invItemID == UUID.Zero) return; - // the object we are in UUID objectID = m_host.ParentUUID; - if (objectID == UUID.Zero) return; + if (objectID == UUID.Zero) + return; + + TaskInventoryItem item = GetSelfInventoryItem(); // we need the permission first, to know which avatar we want to clear the camera for - UUID agentID; - lock (m_host.TaskInventory) - { - agentID = m_host.TaskInventory[invItemID].PermsGranter; - if (agentID == UUID.Zero) return; - if ((m_host.TaskInventory[invItemID].PermsMask & ScriptBaseClass.PERMISSION_CONTROL_CAMERA) == 0) return; - } + UUID agentID = item.PermsGranter; + + if (agentID == UUID.Zero) + return; + + if ((item.PermsMask & ScriptBaseClass.PERMISSION_CONTROL_CAMERA) == 0) + return; ScenePresence presence = World.GetScenePresence(agentID); // we are not interested in child-agents - if (presence.IsChildAgent) return; + if (presence.IsChildAgent) + return; presence.ControllingClient.SendClearFollowCamProperties(objectID); } diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/OSSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/OSSL_Api.cs index 890115df07..893fda1ad9 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/OSSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/OSSL_Api.cs @@ -209,6 +209,14 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api throw new Exception("OSSL Runtime Error: " + msg); } + /// + /// Initialize the LSL interface. + /// + /// + /// FIXME: This is an abomination. We should be able to set this up earlier but currently we have no + /// guarantee the interface is present on Initialize(). There needs to be another post initialize call from + /// ScriptInstance. + /// private void InitLSL() { if (m_LSL_Api != null) @@ -1609,7 +1617,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api public Object osParseJSONNew(string JSON) { - CheckThreatLevel(ThreatLevel.None, "osParseJSON"); + CheckThreatLevel(ThreatLevel.None, "osParseJSONNew"); m_host.AddScriptLPS(1); @@ -3132,5 +3140,25 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api estate.setEstateTerrainTextureHeights(corner, (float)low, (float)high); } } + + public void osForceAttachToAvatar(int attachmentPoint) + { + CheckThreatLevel(ThreatLevel.High, "osForceAttachToAvatar"); + + m_host.AddScriptLPS(1); + + InitLSL(); + ((LSL_Api)m_LSL_Api).AttachToAvatar(attachmentPoint); + } + + public void osForceDetachFromAvatar() + { + CheckThreatLevel(ThreatLevel.High, "osForceDetachFromAvatar"); + + m_host.AddScriptLPS(1); + + InitLSL(); + ((LSL_Api)m_LSL_Api).DetachFromAvatar(); + } } } \ No newline at end of file diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Interface/IOSSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Interface/IOSSL_Api.cs index 2d3e8e8bff..e92518d6a7 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Interface/IOSSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Interface/IOSSL_Api.cs @@ -98,6 +98,20 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api.Interfaces void osAvatarPlayAnimation(string avatar, string animation); void osAvatarStopAnimation(string avatar, string animation); + // Attachment commands + + /// + /// Attach the object containing this script to the avatar that owns it without checking for PERMISSION_ATTACH + /// + /// The attachment point. For example, ATTACH_CHEST + void osForceAttachToAvatar(int attachment); + + /// + /// Detach the object containing this script from the avatar it is attached to without checking for PERMISSION_ATTACH + /// + /// Nothing happens if the object is not attached. + void osForceDetachFromAvatar(); + //texture draw functions string osMovePen(string drawList, int x, int y); string osDrawLine(string drawList, int startX, int startY, int endX, int endY); diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/OSSL_Stub.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/OSSL_Stub.cs index e8369597c2..d230662c54 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/OSSL_Stub.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/OSSL_Stub.cs @@ -289,8 +289,19 @@ namespace OpenSim.Region.ScriptEngine.Shared.ScriptBase m_OSSL_Functions.osAvatarStopAnimation(avatar, animation); } + // Avatar functions - //Texture Draw functions + public void osForceAttachToAvatar(int attachmentPoint) + { + m_OSSL_Functions.osForceAttachToAvatar(attachmentPoint); + } + + public void osForceDetachFromAvatar() + { + m_OSSL_Functions.osForceDetachFromAvatar(); + } + + // Texture Draw functions public string osMovePen(string drawList, int x, int y) { diff --git a/OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs b/OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs index 6e367421c0..2c8af81d5e 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Instance/ScriptInstance.cs @@ -964,7 +964,14 @@ namespace OpenSim.Region.ScriptEngine.Shared.Instance public IScriptApi GetApi(string name) { if (m_Apis.ContainsKey(name)) + { +// m_log.DebugFormat("[SCRIPT INSTANCE]: Found api {0} in {1}@{2}", name, ScriptName, PrimName); + return m_Apis[name]; + } + +// m_log.DebugFormat("[SCRIPT INSTANCE]: Did not find api {0} in {1}@{2}", name, ScriptName, PrimName); + return null; } diff --git a/OpenSim/Region/ScriptEngine/XEngine/XEngine.cs b/OpenSim/Region/ScriptEngine/XEngine/XEngine.cs index 23a4cf92c0..a9b6e6754f 100644 --- a/OpenSim/Region/ScriptEngine/XEngine/XEngine.cs +++ b/OpenSim/Region/ScriptEngine/XEngine/XEngine.cs @@ -1812,9 +1812,11 @@ namespace OpenSim.Region.ScriptEngine.XEngine // if there already exists a file at that location, it may be locked. m_log.ErrorFormat("[XEngine]: File {0} already exists! {1}", path, ex.Message); } + + string textpath = path + ".text"; try { - using (FileStream fs = File.Create(path + ".text")) + using (FileStream fs = File.Create(textpath)) { using (StreamWriter sw = new StreamWriter(fs)) { @@ -1827,7 +1829,7 @@ namespace OpenSim.Region.ScriptEngine.XEngine catch (IOException ex) { // if there already exists a file at that location, it may be locked. - m_log.ErrorFormat("[XEngine]: File {0} already exists! {1}", path, ex.Message); + m_log.ErrorFormat("[XEngine]: File {0} already exists! {1}", textpath, ex.Message); } } } @@ -1876,7 +1878,7 @@ namespace OpenSim.Region.ScriptEngine.XEngine catch (IOException ex) { // if there already exists a file at that location, it may be locked. - m_log.ErrorFormat("[XEngine]: File {0} already exists! {1}", statepath, ex.Message); + m_log.ErrorFormat("[XEngine]: File {0} already exists! {1}", mappath, ex.Message); } } diff --git a/OpenSim/Server/Base/ServerUtils.cs b/OpenSim/Server/Base/ServerUtils.cs index 8effdd2d1b..0cc2a4b57f 100644 --- a/OpenSim/Server/Base/ServerUtils.cs +++ b/OpenSim/Server/Base/ServerUtils.cs @@ -117,7 +117,10 @@ namespace OpenSim.Server.Base catch (Exception e) { if (!(e is System.MissingMethodException)) - m_log.ErrorFormat("Error loading plugin from {0}, exception {1}", dllName, e.InnerException); + { + m_log.ErrorFormat("Error loading plugin {0} from {1}. Exception: {2}", + interfaceName, dllName, e.InnerException == null ? e.Message : e.InnerException.Message); + } return null; } diff --git a/OpenSim/Services/AvatarService/AvatarService.cs b/OpenSim/Services/AvatarService/AvatarService.cs index c59a9e0263..423c7818f0 100644 --- a/OpenSim/Services/AvatarService/AvatarService.cs +++ b/OpenSim/Services/AvatarService/AvatarService.cs @@ -93,7 +93,7 @@ namespace OpenSim.Services.AvatarService if (kvp.Key.StartsWith("_")) count++; - m_log.DebugFormat("[AVATAR SERVICE]: SetAvatar for {0}, attachs={1}", principalID, count); +// m_log.DebugFormat("[AVATAR SERVICE]: SetAvatar for {0}, attachs={1}", principalID, count); m_Database.Delete("PrincipalID", principalID.ToString()); AvatarBaseData av = new AvatarBaseData(); diff --git a/OpenSim/Services/LLLoginService/LLLoginResponse.cs b/OpenSim/Services/LLLoginService/LLLoginResponse.cs index 844c5aeb41..01de15990d 100644 --- a/OpenSim/Services/LLLoginService/LLLoginResponse.cs +++ b/OpenSim/Services/LLLoginService/LLLoginResponse.cs @@ -349,7 +349,31 @@ namespace OpenSim.Services.LLLoginService private void SetDefaultValues() { - DST = TimeZone.CurrentTimeZone.IsDaylightSavingTime(DateTime.Now) ? "Y" : "N"; + TimeZoneInfo gridTimeZone; + + // Disabled for now pending making timezone a config value, which can at some point have a default of + // a ; separated list of possible timezones. + // The problem here is that US/Pacific (or even the Olsen America/Los_Angeles) is not universal across + // windows, mac and various distributions of linux, introducing another element of consistency. + // The server operator needs to be able to control this setting +// try +// { +// // First try to fetch DST from Pacific Standard Time, because this is +// // the one expected by the viewer. "US/Pacific" is the string to search +// // on linux and mac, and should work also on Windows (to confirm) +// gridTimeZone = TimeZoneInfo.FindSystemTimeZoneById("US/Pacific"); +// } +// catch (Exception e) +// { +// m_log.WarnFormat( +// "[TIMEZONE]: {0} Falling back to system time. System time should be set to Pacific Standard Time to provide the expected time", +// e.Message); + + gridTimeZone = TimeZoneInfo.Local; +// } + + DST = gridTimeZone.IsDaylightSavingTime(DateTime.Now) ? "Y" : "N"; + StipendSinceLogin = "N"; Gendered = "Y"; EverLoggedIn = "Y"; diff --git a/bin/OpenSimDefaults.ini b/bin/OpenSimDefaults.ini index 3f2f1318ac..cb9ef224fb 100644 --- a/bin/OpenSimDefaults.ini +++ b/bin/OpenSimDefaults.ini @@ -337,6 +337,13 @@ ; OpenJPEG if false ; UseCSJ2K = true + + ; Use "Trash" folder for items deleted from the scene + ; When set to True (the default) items deleted from the scene will be + ; stored in the user's trash or lost and found folder. When set to + ; False items will be removed from the scene permanently + UseTrashOnDelete = True + ; Persist avatar baked textures ; Persisting baked textures can speed up login and region border ; crossings especially with large numbers of users, though it