From b4680f653dbc1c6f712898af79c4ea22bca3f678 Mon Sep 17 00:00:00 2001
From: Justin Clarke Casey
Date: Wed, 17 Dec 2008 16:11:03 +0000
Subject: * Implement 'Save Object Back to My Inventory'. On the Linden client
this is in the Tools menu available when editing an object * This facility
allows you to save changes to an object that you've rezzed into a region back
into their original inventory item without having to take a copy of the
rezzed object.
---
.../Modules/Avatar/Friends/FriendsModule.cs | 17 ++-
.../Modules/World/Permissions/PermissionsModule.cs | 2 +-
.../Scenes/AsyncSceneObjectGroupDeleter.cs | 10 +-
.../Region/Environment/Scenes/Scene.Inventory.cs | 170 +++++++++++++--------
OpenSim/Region/Environment/Scenes/Scene.cs | 9 --
.../Region/Environment/Scenes/SceneObjectGroup.cs | 13 +-
.../Region/Environment/Scenes/SceneObjectPart.cs | 49 +++++-
7 files changed, 185 insertions(+), 85 deletions(-)
(limited to 'OpenSim/Region/Environment')
diff --git a/OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs b/OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs
index 5bdf32c..e4ed9fa 100644
--- a/OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs
+++ b/OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs
@@ -124,6 +124,7 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends
if (!m_scenes.ContainsKey(scene.RegionInfo.RegionHandle))
m_scenes[scene.RegionInfo.RegionHandle] = scene;
}
+
scene.EventManager.OnNewClient += OnNewClient;
scene.EventManager.OnIncomingInstantMessage += OnGridInstantMessage;
scene.EventManager.OnAvatarEnteringNewParcel += AvatarEnteringParcel;
@@ -157,6 +158,11 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends
#endregion
+ ///
+ /// Receive presence information changes about clients in other regions.
+ ///
+ ///
+ ///
public XmlRpcResponse processPresenceUpdateBulk(XmlRpcRequest req)
{
Hashtable requestData = (Hashtable)req.Params[0];
@@ -171,6 +177,7 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends
lock (m_rootAgents)
{
List friendsHere = new List();
+
try
{
UUID agentID = new UUID((string)requestData["agentID"]);
@@ -728,6 +735,12 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends
if (destAgent != null) destAgent.ControllingClient.SendDeclineCallingCard(transactionID);
}
+ ///
+ /// Send presence information about a client to other clients in both this region and others.
+ ///
+ ///
+ ///
+ ///
private void SendPresenceState(IClientAPI client, List friendList, bool iAmOnline)
{
m_log.DebugFormat("[FRIEND]: {0} logged {1}; sending presence updates", client.Name, iAmOnline ? "in" : "out");
@@ -756,8 +769,6 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends
}
}
-
-
// we now have a list of "interesting" friends (which we have to find out on-/offline state for),
// friends we want to send our online state to (if *they* are online, too), and
// friends we want to receive online state for (currently unknown whether online or not)
@@ -799,8 +810,10 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends
friendIDsToReceiveFromOnline.Add(uuid);
}
}
+
m_log.DebugFormat("[FRIEND]: Sending {0} offline and {1} online friends to {2}",
friendIDsToReceiveFromOffline.Count, friendIDsToReceiveFromOnline.Count, client.Name);
+
if (friendIDsToReceiveFromOffline.Count > 0) client.SendAgentOffline(friendIDsToReceiveFromOffline.ToArray());
if (friendIDsToReceiveFromOnline.Count > 0) client.SendAgentOnline(friendIDsToReceiveFromOnline.ToArray());
diff --git a/OpenSim/Region/Environment/Modules/World/Permissions/PermissionsModule.cs b/OpenSim/Region/Environment/Modules/World/Permissions/PermissionsModule.cs
index e2ca975..36f5965 100644
--- a/OpenSim/Region/Environment/Modules/World/Permissions/PermissionsModule.cs
+++ b/OpenSim/Region/Environment/Modules/World/Permissions/PermissionsModule.cs
@@ -1106,7 +1106,6 @@ namespace OpenSim.Region.Environment.Modules.World.Permissions
// the administrator object permissions to take effect.
// UUID objectOwner = task.OwnerID;
-
if ((task.RootPart.EveryoneMask & PERM_COPY) != 0)
permission = true;
@@ -1120,6 +1119,7 @@ namespace OpenSim.Region.Environment.Modules.World.Permissions
if ((task.GetEffectivePermissions() & PERM_COPY) == 0)
permission = false;
}
+
return permission;
}
diff --git a/OpenSim/Region/Environment/Scenes/AsyncSceneObjectGroupDeleter.cs b/OpenSim/Region/Environment/Scenes/AsyncSceneObjectGroupDeleter.cs
index 03b3977..1429452 100644
--- a/OpenSim/Region/Environment/Scenes/AsyncSceneObjectGroupDeleter.cs
+++ b/OpenSim/Region/Environment/Scenes/AsyncSceneObjectGroupDeleter.cs
@@ -110,7 +110,7 @@ namespace OpenSim.Region.Environment.Scenes
while (InventoryDeQueueAndDelete())
{
- m_log.Debug("[SCENE]: Returned item successfully to inventory, continuing...");
+ m_log.Debug("[SCENE]: Sent item successfully to inventory, continuing...");
}
}
@@ -130,7 +130,7 @@ namespace OpenSim.Region.Environment.Scenes
if (left > 0)
{
m_log.DebugFormat(
- "[SCENE]: Sending deleted object to user's inventory, {0} item(s) remaining.", left);
+ "[SCENE]: Sending object to user's inventory, {0} item(s) remaining.", left);
x = m_inventoryDeletes.Dequeue();
@@ -142,7 +142,7 @@ namespace OpenSim.Region.Environment.Scenes
}
catch (Exception e)
{
- m_log.DebugFormat("Exception background deleting object: " + e);
+ m_log.DebugFormat("Exception background sending object: " + e);
}
return true;
@@ -154,11 +154,11 @@ namespace OpenSim.Region.Environment.Scenes
// We can't put the object group details in here since the root part may have disappeared (which is where these sit).
// FIXME: This needs to be fixed.
m_log.ErrorFormat(
- "[SCENE]: Queued deletion of scene object to agent {0} {1} failed: {2}",
+ "[SCENE]: Queued sending of scene object to agent {0} {1} failed: {2}",
(x != null ? x.remoteClient.Name : "unavailable"), (x != null ? x.remoteClient.AgentId.ToString() : "unavailable"), e.ToString());
}
- m_log.Debug("[SCENE]: No objects left in inventory delete queue.");
+ m_log.Debug("[SCENE]: No objects left in inventory send queue.");
return false;
}
}
diff --git a/OpenSim/Region/Environment/Scenes/Scene.Inventory.cs b/OpenSim/Region/Environment/Scenes/Scene.Inventory.cs
index 7c518c4..c1decaa 100644
--- a/OpenSim/Region/Environment/Scenes/Scene.Inventory.cs
+++ b/OpenSim/Region/Environment/Scenes/Scene.Inventory.cs
@@ -1686,7 +1686,15 @@ namespace OpenSim.Region.Environment.Scenes
bool permissionToTake = false;
bool permissionToDelete = false;
- if (action == DeRezAction.TakeCopy)
+ if (action == DeRezAction.SaveToExistingUserInventoryItem)
+ {
+ if (grp.OwnerID == remoteClient.AgentId && grp.RootPart.FromUserInventoryItemID != UUID.Zero)
+ {
+ permissionToTake = true;
+ permissionToDelete = false;
+ }
+ }
+ else if (action == DeRezAction.TakeCopy)
{
permissionToTake =
Permissions.CanTakeCopyObject(
@@ -1726,6 +1734,7 @@ namespace OpenSim.Region.Environment.Scenes
grp.UUID,
remoteClient.AgentId);
permissionToDelete = permissionToTake;
+
if (permissionToDelete)
{
AddReturn(grp.OwnerID, grp.Name, grp.AbsolutePosition, "parcel owner return");
@@ -1737,6 +1746,12 @@ namespace OpenSim.Region.Environment.Scenes
permissionToDelete = true;
}
}
+ else
+ {
+ m_log.DebugFormat(
+ "[AGENT INVENTORY]: Ignoring unexpected derez action {0} for {1}", action, remoteClient.Name);
+ return;
+ }
if (permissionToTake)
{
@@ -1769,13 +1784,11 @@ namespace OpenSim.Region.Environment.Scenes
if (remoteClient == null)
{
- userInfo = CommsManager.UserProfileCacheService.GetUserDetails(
- objectGroup.RootPart.OwnerID);
+ userInfo = CommsManager.UserProfileCacheService.GetUserDetails(objectGroup.RootPart.OwnerID);
}
else
{
- userInfo = CommsManager.UserProfileCacheService.GetUserDetails(
- remoteClient.AgentId);
+ userInfo = CommsManager.UserProfileCacheService.GetUserDetails(remoteClient.AgentId);
}
if (userInfo != null)
@@ -1785,8 +1798,8 @@ namespace OpenSim.Region.Environment.Scenes
// If we're returning someone's item, it goes back to the
// owner's Lost And Found folder.
- if (folderID == UUID.Zero || (action == DeRezAction.Delete &&
- objectGroup.OwnerID != remoteClient.AgentId))
+ if (folderID == UUID.Zero
+ || (action == DeRezAction.Delete && objectGroup.OwnerID != remoteClient.AgentId))
{
InventoryFolderBase folder =
userInfo.FindFolderForType(
@@ -1811,6 +1824,21 @@ namespace OpenSim.Region.Environment.Scenes
}
}
+ InventoryItemBase item = null;
+
+ if (DeRezAction.SaveToExistingUserInventoryItem == action)
+ {
+ item = userInfo.RootFolder.FindItem(objectGroup.RootPart.FromUserInventoryItemID);
+
+ if (null == item)
+ {
+ m_log.DebugFormat(
+ "[AGENT INVENTORY]: Object {0} {1} scheduled for save to inventory has already been deleted.",
+ objectGroup.Name, objectGroup.UUID);
+ return UUID.Zero;
+ }
+ }
+
AssetBase asset = CreateAsset(
objectGroup.GetPartName(objectGroup.RootPart.LocalId),
objectGroup.GetPartDescription(objectGroup.RootPart.LocalId),
@@ -1818,65 +1846,74 @@ namespace OpenSim.Region.Environment.Scenes
Utils.StringToBytes(sceneObjectXml));
AssetCache.AddAsset(asset);
assetID = asset.FullID;
-
- InventoryItemBase item = new InventoryItemBase();
- item.Creator = objectGroup.RootPart.CreatorID;
-
- if (action == DeRezAction.TakeCopy || action == DeRezAction.Take)
- item.Owner = remoteClient.AgentId;
- else // Delete / Return
- item.Owner = objectGroup.OwnerID;
-
- item.ID = UUID.Random();
- item.AssetID = asset.FullID;
- item.Description = asset.Description;
- item.Name = asset.Name;
- item.AssetType = asset.Type;
- item.InvType = (int)InventoryType.Object;
- item.Folder = folderID;
- if (remoteClient != null && (remoteClient.AgentId != objectGroup.RootPart.OwnerID) && Permissions.PropagatePermissions())
- {
- uint perms=objectGroup.GetEffectivePermissions();
- uint nextPerms=(perms & 7) << 13;
- if ((nextPerms & (uint)PermissionMask.Copy) == 0)
- perms &= ~(uint)PermissionMask.Copy;
- if ((nextPerms & (uint)PermissionMask.Transfer) == 0)
- perms &= ~(uint)PermissionMask.Transfer;
- if ((nextPerms & (uint)PermissionMask.Modify) == 0)
- perms &= ~(uint)PermissionMask.Modify;
-
- item.BasePermissions = perms & objectGroup.RootPart.NextOwnerMask;
- item.CurrentPermissions = item.BasePermissions;
- item.NextPermissions = objectGroup.RootPart.NextOwnerMask;
- item.EveryOnePermissions = objectGroup.RootPart.EveryoneMask & objectGroup.RootPart.NextOwnerMask;
- item.GroupPermissions = objectGroup.RootPart.GroupMask & objectGroup.RootPart.NextOwnerMask;
- item.CurrentPermissions |= 8; // Slam!
+ if (DeRezAction.SaveToExistingUserInventoryItem == action)
+ {
+ item.AssetID = asset.FullID;
+ userInfo.UpdateItem(item);
}
else
{
- item.BasePermissions = objectGroup.GetEffectivePermissions();
- item.CurrentPermissions = objectGroup.GetEffectivePermissions();
- item.NextPermissions = objectGroup.RootPart.NextOwnerMask;
- item.EveryOnePermissions = objectGroup.RootPart.EveryoneMask;
- item.GroupPermissions = objectGroup.RootPart.GroupMask;
- }
+ item = new InventoryItemBase();
+ item.Creator = objectGroup.RootPart.CreatorID;
- // TODO: add the new fields (Flags, Sale info, etc)
- item.CreationDate = Util.UnixTimeSinceEpoch();
+ if (action == DeRezAction.TakeCopy || action == DeRezAction.Take)
+ item.Owner = remoteClient.AgentId;
+ else // Delete / Return
+ item.Owner = objectGroup.OwnerID;
- userInfo.AddItem(item);
- if (remoteClient != null && item.Owner == remoteClient.AgentId)
- {
- remoteClient.SendInventoryItemCreateUpdate(item);
- }
- else
- {
- ScenePresence notifyUser = GetScenePresence(item.Owner);
- if (notifyUser != null)
+ item.ID = UUID.Random();
+ item.AssetID = asset.FullID;
+ item.Description = asset.Description;
+ item.Name = asset.Name;
+ item.AssetType = asset.Type;
+ item.InvType = (int)InventoryType.Object;
+ item.Folder = folderID;
+
+ if (remoteClient != null && (remoteClient.AgentId != objectGroup.RootPart.OwnerID) && Permissions.PropagatePermissions())
+ {
+ uint perms=objectGroup.GetEffectivePermissions();
+ uint nextPerms=(perms & 7) << 13;
+ if ((nextPerms & (uint)PermissionMask.Copy) == 0)
+ perms &= ~(uint)PermissionMask.Copy;
+ if ((nextPerms & (uint)PermissionMask.Transfer) == 0)
+ perms &= ~(uint)PermissionMask.Transfer;
+ if ((nextPerms & (uint)PermissionMask.Modify) == 0)
+ perms &= ~(uint)PermissionMask.Modify;
+
+ item.BasePermissions = perms & objectGroup.RootPart.NextOwnerMask;
+ item.CurrentPermissions = item.BasePermissions;
+ item.NextPermissions = objectGroup.RootPart.NextOwnerMask;
+ item.EveryOnePermissions = objectGroup.RootPart.EveryoneMask & objectGroup.RootPart.NextOwnerMask;
+ item.GroupPermissions = objectGroup.RootPart.GroupMask & objectGroup.RootPart.NextOwnerMask;
+ item.CurrentPermissions |= 8; // Slam!
+ }
+ else
+ {
+ item.BasePermissions = objectGroup.GetEffectivePermissions();
+ item.CurrentPermissions = objectGroup.GetEffectivePermissions();
+ item.NextPermissions = objectGroup.RootPart.NextOwnerMask;
+ item.EveryOnePermissions = objectGroup.RootPart.EveryoneMask;
+ item.GroupPermissions = objectGroup.RootPart.GroupMask;
+ }
+
+ // TODO: add the new fields (Flags, Sale info, etc)
+ item.CreationDate = Util.UnixTimeSinceEpoch();
+
+ userInfo.AddItem(item);
+
+ if (remoteClient != null && item.Owner == remoteClient.AgentId)
{
- notifyUser.ControllingClient.SendInventoryItemCreateUpdate(item);
+ remoteClient.SendInventoryItemCreateUpdate(item);
}
+ else
+ {
+ ScenePresence notifyUser = GetScenePresence(item.Owner);
+ if (notifyUser != null)
+ {
+ notifyUser.ControllingClient.SendInventoryItemCreateUpdate(item);
+ }
+ }
}
}
@@ -2093,7 +2130,7 @@ namespace OpenSim.Region.Environment.Scenes
CachedUserInfo userInfo = CommsManager.UserProfileCacheService.GetUserDetails(remoteClient.AgentId);
if (userInfo != null)
{
- if (userInfo.RootFolder != null)
+ if (userInfo.HasReceivedInventory)
{
InventoryItemBase item = userInfo.RootFolder.FindItem(itemID);
@@ -2103,8 +2140,21 @@ namespace OpenSim.Region.Environment.Scenes
if (rezAsset != null)
{
+ UUID itemId = UUID.Zero;
+
+ // If we have permission to copy then link the rezzed object back to the user inventory
+ // item that it came from. This allows us to enable 'save object to inventory'
+ if (!Permissions.BypassPermissions())
+ {
+ if ((item.CurrentPermissions & (uint)PermissionMask.Copy) == (uint)PermissionMask.Copy)
+ {
+ itemId = item.ID;
+ }
+ }
+
string xmlData = Utils.BytesToString(rezAsset.Data);
- SceneObjectGroup group = new SceneObjectGroup(xmlData, true);
+ SceneObjectGroup group = new SceneObjectGroup(itemId, xmlData, true);
+
if (!Permissions.CanRezObject(
group.Children.Count, remoteClient.AgentId, pos)
&& !attachment)
diff --git a/OpenSim/Region/Environment/Scenes/Scene.cs b/OpenSim/Region/Environment/Scenes/Scene.cs
index 5502f97..6831885 100644
--- a/OpenSim/Region/Environment/Scenes/Scene.cs
+++ b/OpenSim/Region/Environment/Scenes/Scene.cs
@@ -3480,28 +3480,19 @@ namespace OpenSim.Region.Environment.Scenes
public virtual void StoreAddFriendship(UUID ownerID, UUID friendID, uint perms)
{
- // TODO: m_sceneGridService.DoStuff;
m_sceneGridService.AddNewUserFriend(ownerID, friendID, perms);
}
public virtual void StoreUpdateFriendship(UUID ownerID, UUID friendID, uint perms)
{
- // TODO: m_sceneGridService.DoStuff;
m_sceneGridService.UpdateUserFriendPerms(ownerID, friendID, perms);
}
public virtual void StoreRemoveFriendship(UUID ownerID, UUID ExfriendID)
{
- // TODO: m_sceneGridService.DoStuff;
m_sceneGridService.RemoveUserFriend(ownerID, ExfriendID);
}
- public virtual List StoreGetFriendsForUser(UUID ownerID)
- {
- // TODO: m_sceneGridService.DoStuff;
- return m_sceneGridService.GetUserFriendList(ownerID);
- }
-
public void AddPacketStats(int inPackets, int outPackets, int unAckedBytes)
{
m_statsReporter.AddInPackets(inPackets);
diff --git a/OpenSim/Region/Environment/Scenes/SceneObjectGroup.cs b/OpenSim/Region/Environment/Scenes/SceneObjectGroup.cs
index cc99929..5456282 100644
--- a/OpenSim/Region/Environment/Scenes/SceneObjectGroup.cs
+++ b/OpenSim/Region/Environment/Scenes/SceneObjectGroup.cs
@@ -401,16 +401,25 @@ namespace OpenSim.Region.Environment.Scenes
{
SetRootPart(part);
}
+
+ public SceneObjectGroup(string xmlData, bool isOriginalXmlFormat)
+ : this(UUID.Zero, xmlData, isOriginalXmlFormat)
+ {
+ }
///
/// Create an object using serialized data in OpenSim's original xml format.
///
+ ///
+ /// If applicable, the user inventory item id from which this object was rezzed. If not applicable then this
+ /// should be UUID.Zero
+ ///
///
///
/// This parameter only exists to separate the two different xml constructors. In the future, versions should
/// be specified within the xml itself.
///
- public SceneObjectGroup(string xmlData, bool isOriginalXmlFormat)
+ public SceneObjectGroup(UUID fromUserInventoryItemID, string xmlData, bool isOriginalXmlFormat)
{
if (!isOriginalXmlFormat)
throw new Exception("This constructor must specify the xml is in OpenSim's original format");
@@ -430,7 +439,7 @@ namespace OpenSim.Region.Environment.Scenes
reader.Read();
reader.ReadStartElement("SceneObjectGroup");
reader.ReadStartElement("RootPart");
- SetRootPart(SceneObjectPart.FromXml(reader));
+ SetRootPart(SceneObjectPart.FromXml(fromUserInventoryItemID, reader));
reader.ReadEndElement();
diff --git a/OpenSim/Region/Environment/Scenes/SceneObjectPart.cs b/OpenSim/Region/Environment/Scenes/SceneObjectPart.cs
index 10a37ee..3547cd4 100644
--- a/OpenSim/Region/Environment/Scenes/SceneObjectPart.cs
+++ b/OpenSim/Region/Environment/Scenes/SceneObjectPart.cs
@@ -116,30 +116,55 @@ namespace OpenSim.Region.Environment.Scenes
// for SL compatibility it should be persisted though (set sound / displaytext / particlesystem, kill script)
[XmlIgnore]
public UUID Sound;
+
[XmlIgnore]
public byte SoundFlags;
+
[XmlIgnore]
public double SoundGain;
+
[XmlIgnore]
public double SoundRadius;
+
[XmlIgnore]
public uint TimeStampFull = 0;
+
[XmlIgnore]
public uint TimeStampLastActivity = 0; // Will be used for AutoReturn
+
[XmlIgnore]
public uint TimeStampTerse = 0;
+
[XmlIgnore]
public UUID FromAssetID = UUID.Zero;
+
+ ///
+ /// The UUID of the user inventory item from which this object was rezzed if this is a root part.
+ /// If UUID.Zero then either this is not a root part or there is no connection with a user inventory item.
+ ///
+ private UUID m_fromUserInventoryItemID = UUID.Zero;
+
+ [XmlIgnore]
+ public UUID FromUserInventoryItemID
+ {
+ get { return m_fromUserInventoryItemID; }
+ }
+
[XmlIgnore]
public bool IsAttachment = false;
+
[XmlIgnore]
public scriptEvents AggregateScriptEvents = 0;
+
[XmlIgnore]
public UUID AttachedAvatar = UUID.Zero;
+
[XmlIgnore]
public Vector3 AttachedPos = Vector3.Zero;
+
[XmlIgnore]
public uint AttachmentPoint = (byte)0;
+
[XmlIgnore]
public PhysicsVector RotationAxis = new PhysicsVector(1f,1f,1f);
@@ -1428,15 +1453,27 @@ if (m_shape != null) {
///
/// Restore this part from the serialized xml representation.
///
+ ///
///
public static SceneObjectPart FromXml(XmlReader xmlReader)
{
- // It's not necessary to persist this
-
- XmlSerializer serializer = new XmlSerializer(typeof (SceneObjectPart));
- SceneObjectPart newobject = (SceneObjectPart) serializer.Deserialize(xmlReader);
- return newobject;
+ return FromXml(UUID.Zero, xmlReader);
}
+
+ ///
+ /// Restore this part from the serialized xml representation.
+ ///
+ /// The inventory id from which this part came, if applicable
+ ///
+ ///
+ public static SceneObjectPart FromXml(UUID fromUserInventoryItemId, XmlReader xmlReader)
+ {
+ XmlSerializer serializer = new XmlSerializer(typeof (SceneObjectPart));
+ SceneObjectPart part = (SceneObjectPart)serializer.Deserialize(xmlReader);
+ part.m_fromUserInventoryItemID = fromUserInventoryItemId;
+
+ return part;
+ }
public UUID GetAvatarOnSitTarget()
{
@@ -1603,7 +1640,7 @@ if (m_shape != null) {
public void GetProperties(IClientAPI client)
{
client.SendObjectPropertiesReply(
- UUID.Zero, (ulong)_creationDate, _creatorID, UUID.Zero, UUID.Zero,
+ m_fromUserInventoryItemID, (ulong)_creationDate, _creatorID, UUID.Zero, UUID.Zero,
_groupID, (short)InventorySerial, _lastOwnerID, UUID, _ownerID,
ParentGroup.RootPart.TouchName, new byte[0], ParentGroup.RootPart.SitName, Name, Description,
ParentGroup.RootPart._ownerMask, ParentGroup.RootPart._nextOwnerMask, ParentGroup.RootPart._groupMask, ParentGroup.RootPart._everyoneMask,
--
cgit v1.1