diff --git a/OpenMetaverse/InventoryManager.cs b/OpenMetaverse/InventoryManager.cs index f4f99146..eed0d48c 100644 --- a/OpenMetaverse/InventoryManager.cs +++ b/OpenMetaverse/InventoryManager.cs @@ -154,6 +154,25 @@ namespace OpenMetaverse ReturnToLastOwner = 10 } + /// + /// Upper half of the Flags field for inventory items + /// + [Flags] + public enum InventoryItemFlags + { + None = 0, + LandmarkVisited = 1, + ObjectSlamPerm = 0x100, + ObjectSlamSale = 0x1000, + ObjectPermOverwriteBase = 0x010000, + ObjectPermOverwriteOwner = 0x020000, + ObjectPermOverwriteGroup = 0x040000, + ObjectPermOverwriteEveryone = 0x080000, + ObjectPermOverwriteNextOwner = 0x100000, + ObjectHasMultipleItems = 0x200000, + SharedSingleReference = 0x40000000, + } + #endregion Enums #region Inventory Object Classes diff --git a/OpenMetaverse/Permissions.cs b/OpenMetaverse/Permissions.cs index a71436ac..90d8273c 100644 --- a/OpenMetaverse/Permissions.cs +++ b/OpenMetaverse/Permissions.cs @@ -91,6 +91,19 @@ namespace OpenMetaverse OwnerMask = (PermissionMask)ownerMask; } + public Permissions GetNextPermissions() + { + uint nextMask = (uint)NextOwnerMask; + + return new Permissions( + (uint)BaseMask & nextMask, + (uint)EveryoneMask & nextMask, + (uint)GroupMask & nextMask, + (uint)NextOwnerMask, + (uint)OwnerMask & nextMask + ); + } + public OSD GetOSD() { OSDMap permissions = new OSDMap(5); diff --git a/OpenMetaverse/Primitives/Primitive.cs b/OpenMetaverse/Primitives/Primitive.cs index e8e26f30..ecfa985f 100644 --- a/OpenMetaverse/Primitives/Primitive.cs +++ b/OpenMetaverse/Primitives/Primitive.cs @@ -1008,6 +1008,18 @@ namespace OpenMetaverse Name = props.Name; Description = props.Description; } + + public byte[] GetTextureIDBytes() + { + if (TextureIDs == null || TextureIDs.Length == 0) + return Utils.EmptyBytes; + + byte[] bytes = new byte[16 * TextureIDs.Length]; + for (int i = 0; i < TextureIDs.Length; i++) + TextureIDs[i].ToBytes(bytes, 16 * i); + + return bytes; + } } #endregion Subclasses diff --git a/Programs/Simian/Extensions/SceneManager.cs b/Programs/Simian/Extensions/SceneManager.cs index 0f8e15f7..210fa90f 100644 --- a/Programs/Simian/Extensions/SceneManager.cs +++ b/Programs/Simian/Extensions/SceneManager.cs @@ -364,24 +364,10 @@ namespace Simian public bool ObjectRemove(object sender, uint localID) { SimulationObject obj; - Agent agent; if (sceneObjects.TryGetValue(localID, out obj)) { - if (sceneAgents.TryGetValue(obj.Prim.ID, out agent)) - AgentRemove(sender, agent); - - if (OnObjectRemove != null) - OnObjectRemove(sender, obj); - - sceneObjects.Remove(obj.Prim.LocalID, obj.Prim.ID); - - KillObjectPacket kill = new KillObjectPacket(); - kill.ObjectData = new KillObjectPacket.ObjectDataBlock[1]; - kill.ObjectData[0] = new KillObjectPacket.ObjectDataBlock(); - kill.ObjectData[0].ID = obj.Prim.LocalID; - - udp.BroadcastPacket(kill, PacketCategory.State); + RemoveObject(sender, obj); return true; } @@ -391,24 +377,10 @@ namespace Simian public bool ObjectRemove(object sender, UUID id) { SimulationObject obj; - Agent agent; if (sceneObjects.TryGetValue(id, out obj)) { - if (sceneAgents.TryGetValue(id, out agent)) - AgentRemove(sender, agent); - - if (OnObjectRemove != null) - OnObjectRemove(sender, obj); - - sceneObjects.Remove(obj.Prim.LocalID, obj.Prim.ID); - - KillObjectPacket kill = new KillObjectPacket(); - kill.ObjectData = new KillObjectPacket.ObjectDataBlock[1]; - kill.ObjectData[0] = new KillObjectPacket.ObjectDataBlock(); - kill.ObjectData[0].ID = obj.Prim.LocalID; - - udp.BroadcastPacket(kill, PacketCategory.State); + RemoveObject(sender, obj); return true; } @@ -1377,6 +1349,33 @@ namespace Simian } } + void RemoveObject(object sender, SimulationObject obj) + { + // If this object has an agent associated with it, remove the agent from the scene as well + Agent agent; + if (sceneAgents.TryGetValue(obj.Prim.ID, out agent)) + AgentRemove(sender, agent); + + // Remove the agent from the scene + sceneObjects.Remove(obj.Prim.LocalID, obj.Prim.ID); + + // Fire the callback + if (OnObjectRemove != null) + OnObjectRemove(sender, obj); + + // If this object has a cached task inventory asset, delete it now + if (obj.Inventory.InventorySerial > 0) + taskInventory.RemoveTaskFile(obj.Inventory.GetInventoryFilename()); + + // Broadcast the kill message + KillObjectPacket kill = new KillObjectPacket(); + kill.ObjectData = new KillObjectPacket.ObjectDataBlock[1]; + kill.ObjectData[0] = new KillObjectPacket.ObjectDataBlock(); + kill.ObjectData[0].ID = obj.Prim.LocalID; + + udp.BroadcastPacket(kill, PacketCategory.State); + } + void SendObjectPacket(SimulationObject obj, bool canUseCompressed, bool canUseImproved, PrimFlags creatorFlags, UpdateFlags updateFlags) { if (!canUseImproved && !canUseCompressed) diff --git a/Programs/Simian/Interfaces/ITaskInventoryProvider.cs b/Programs/Simian/Interfaces/ITaskInventoryProvider.cs index f64abc0a..059d9ca5 100644 --- a/Programs/Simian/Interfaces/ITaskInventoryProvider.cs +++ b/Programs/Simian/Interfaces/ITaskInventoryProvider.cs @@ -6,15 +6,8 @@ namespace Simian { public interface ITaskInventoryProvider { - UUID CreateItem(UUID agentID, UUID containerObjectID, string name, string description, InventoryType invType, - AssetType type, UUID assetID, UUID parentID, PermissionMask ownerMask, PermissionMask nextOwnerMask, - UUID ownerID, UUID creatorID, UUID transactionID, uint callbackID, bool sendPacket); - bool RemoveItem(UUID agentID, UUID containerObjectID, UUID itemID); - - bool TryGetAsset(UUID containerObjectID, UUID assetID, out Asset asset); - - void ForEachItem(UUID containerObjectID, Action action); - InventoryTaskItem FindItem(UUID containerObjectID, Predicate match); - List FindAllItems(UUID containerObjectID, Predicate match); + void AddTaskFile(string filename, byte[] assetData); + bool RemoveTaskFile(string filename); + bool TryGetTaskFile(string filename, out byte[] assetData); } } diff --git a/Programs/Simian/LLUDP/LLAssets.cs b/Programs/Simian/LLUDP/LLAssets.cs index 1c6704dc..361a3eb3 100644 --- a/Programs/Simian/LLUDP/LLAssets.cs +++ b/Programs/Simian/LLUDP/LLAssets.cs @@ -9,7 +9,8 @@ namespace Simian public class LLAssets : IExtension { ISceneProvider scene; - Dictionary CurrentUploads = new Dictionary(); + Dictionary currentDownloads = new Dictionary(); + Dictionary currentUploads = new Dictionary(); public LLAssets() { @@ -19,10 +20,12 @@ namespace Simian { this.scene = scene; - scene.UDP.RegisterPacketCallback(PacketType.AssetUploadRequest, new PacketCallback(AssetUploadRequestHandler)); - scene.UDP.RegisterPacketCallback(PacketType.SendXferPacket, new PacketCallback(SendXferPacketHandler)); - scene.UDP.RegisterPacketCallback(PacketType.AbortXfer, new PacketCallback(AbortXferHandler)); - scene.UDP.RegisterPacketCallback(PacketType.TransferRequest, new PacketCallback(TransferRequestHandler)); + scene.UDP.RegisterPacketCallback(PacketType.RequestXfer, RequestXferHandler); + scene.UDP.RegisterPacketCallback(PacketType.ConfirmXferPacket, ConfirmXferPacketHandler); + scene.UDP.RegisterPacketCallback(PacketType.AssetUploadRequest, AssetUploadRequestHandler); + scene.UDP.RegisterPacketCallback(PacketType.SendXferPacket, SendXferPacketHandler); + scene.UDP.RegisterPacketCallback(PacketType.AbortXfer, AbortXferHandler); + scene.UDP.RegisterPacketCallback(PacketType.TransferRequest, TransferRequestHandler); return true; } @@ -32,6 +35,89 @@ namespace Simian #region Xfer System + void RequestXferHandler(Packet packet, Agent agent) + { + RequestXferPacket request = (RequestXferPacket)packet; + + string filename = Utils.BytesToString(request.XferID.Filename); + + byte[] assetData; + if (scene.TaskInventory.TryGetTaskFile(filename, out assetData)) + { + SendXferPacketPacket xfer = new SendXferPacketPacket(); + xfer.XferID.ID = request.XferID.ID; + + if (assetData.Length < 1000) + { + xfer.XferID.Packet = 0x80000000; + xfer.DataPacket.Data = new byte[assetData.Length + 4]; + Utils.IntToBytes(assetData.Length, xfer.DataPacket.Data, 0); + Buffer.BlockCopy(assetData, 0, xfer.DataPacket.Data, 4, assetData.Length); + + scene.UDP.SendPacket(agent.ID, xfer, PacketCategory.Asset); + Logger.DebugLog("Completed single packet xfer download of " + filename); + } + else + { + xfer.XferID.Packet = 0; + xfer.DataPacket.Data = new byte[1000 + 4]; + Utils.IntToBytes(assetData.Length, xfer.DataPacket.Data, 0); + Buffer.BlockCopy(assetData, 0, xfer.DataPacket.Data, 4, 1000); + + // We don't need the entire XferDownload class, just the asset data and the current packet number + XferDownload download = new XferDownload(); + download.AssetData = assetData; + download.PacketNum = 1; + download.Filename = filename; + lock (currentDownloads) + currentDownloads[request.XferID.ID] = download; + + scene.UDP.SendPacket(agent.ID, xfer, PacketCategory.Asset); + } + } + else + { + Logger.Log("Got a RequestXfer for an unknown file: " + filename, Helpers.LogLevel.Warning); + } + + // lock (currentDownloads) + } + + void ConfirmXferPacketHandler(Packet packet, Agent agent) + { + ConfirmXferPacketPacket confirm = (ConfirmXferPacketPacket)packet; + + XferDownload download; + if (currentDownloads.TryGetValue(confirm.XferID.ID, out download)) + { + // Send the next packet + SendXferPacketPacket xfer = new SendXferPacketPacket(); + xfer.XferID.ID = confirm.XferID.ID; + + int bytesRemaining = (int)(download.AssetData.Length - (download.PacketNum * 1000)); + + if (bytesRemaining > 1000) + { + xfer.DataPacket.Data = new byte[1000]; + Buffer.BlockCopy(download.AssetData, (int)download.PacketNum * 1000, xfer.DataPacket.Data, 0, 1000); + xfer.XferID.Packet = download.PacketNum++; + } + else + { + // Last packet + xfer.DataPacket.Data = new byte[bytesRemaining]; + Buffer.BlockCopy(download.AssetData, (int)download.PacketNum * 1000, xfer.DataPacket.Data, 0, bytesRemaining); + xfer.XferID.Packet = download.PacketNum++ | 0x80000000; + + lock (currentDownloads) + currentDownloads.Remove(confirm.XferID.ID); + Logger.DebugLog("Completing xfer download for: " + download.Filename); + } + + scene.UDP.SendPacket(agent.ID, xfer, PacketCategory.Asset); + } + } + void AssetUploadRequestHandler(Packet packet, Agent agent) { AssetUploadRequestPacket request = (AssetUploadRequestPacket)packet; @@ -86,8 +172,8 @@ namespace Simian xfer.XferID.VFileType = request.AssetBlock.Type; // Add this asset to the current upload list - lock (CurrentUploads) - CurrentUploads[xfer.XferID.ID] = asset; + lock (currentUploads) + currentUploads[xfer.XferID.ID] = asset; scene.UDP.SendPacket(agent.ID, xfer, PacketCategory.Inventory); } @@ -98,7 +184,7 @@ namespace Simian SendXferPacketPacket xfer = (SendXferPacketPacket)packet; Asset asset; - if (CurrentUploads.TryGetValue(xfer.XferID.ID, out asset)) + if (currentUploads.TryGetValue(xfer.XferID.ID, out asset)) { if (asset.AssetData == null) { @@ -136,8 +222,8 @@ namespace Simian // Asset upload finished Logger.DebugLog(String.Format("Completed Xfer upload of asset {0} ({1}", asset.AssetID, asset.AssetType)); - lock (CurrentUploads) - CurrentUploads.Remove(xfer.XferID.ID); + lock (currentUploads) + currentUploads.Remove(xfer.XferID.ID); scene.Server.Assets.StoreAsset(asset); @@ -159,14 +245,14 @@ namespace Simian { AbortXferPacket abort = (AbortXferPacket)packet; - lock (CurrentUploads) + lock (currentUploads) { - if (CurrentUploads.ContainsKey(abort.XferID.ID)) + if (currentUploads.ContainsKey(abort.XferID.ID)) { Logger.DebugLog(String.Format("Aborting Xfer {0}, result: {1}", abort.XferID.ID, (TransferError)abort.XferID.Result)); - CurrentUploads.Remove(abort.XferID.ID); + currentUploads.Remove(abort.XferID.ID); } else { diff --git a/Programs/Simian/LLUDP/LLTaskInventory.cs b/Programs/Simian/LLUDP/LLTaskInventory.cs new file mode 100644 index 00000000..38726fd3 --- /dev/null +++ b/Programs/Simian/LLUDP/LLTaskInventory.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using ExtensionLoader; +using OpenMetaverse; +using OpenMetaverse.Packets; + +namespace Simian +{ + public class LLTaskInventory : IExtension + { + ISceneProvider scene; + + public LLTaskInventory() + { + } + + public bool Start(ISceneProvider scene) + { + this.scene = scene; + + scene.UDP.RegisterPacketCallback(PacketType.RequestTaskInventory, RequestTaskInventoryHandler); + scene.UDP.RegisterPacketCallback(PacketType.UpdateTaskInventory, UpdateTaskInventoryHandler); + scene.UDP.RegisterPacketCallback(PacketType.RemoveTaskInventory, RemoveTaskInventoryHandler); + scene.UDP.RegisterPacketCallback(PacketType.MoveTaskInventory, MoveTaskInventoryHandler); + return true; + } + + public void Stop() + { + } + + void RequestTaskInventoryHandler(Packet packet, Agent agent) + { + RequestTaskInventoryPacket request = (RequestTaskInventoryPacket)packet; + + // Try to find this object in the scene + SimulationObject obj; + if (scene.TryGetObject(request.InventoryData.LocalID, out obj)) + { + ReplyTaskInventoryPacket reply = new ReplyTaskInventoryPacket(); + reply.InventoryData.Filename = Utils.StringToBytes(obj.Inventory.GetInventoryFilename()); + reply.InventoryData.Serial = obj.Inventory.InventorySerial; + reply.InventoryData.TaskID = obj.Prim.ID; + + scene.UDP.SendPacket(agent.ID, reply, PacketCategory.Transaction); + } + } + + void UpdateTaskInventoryHandler(Packet packet, Agent agent) + { + UpdateTaskInventoryPacket update = (UpdateTaskInventoryPacket)packet; + + InventoryTaskItem item; + SimulationObject targetObj; + InventoryObject invObj; + + if (update.UpdateData.Key == 0) + { + if (scene.TryGetObject(update.UpdateData.LocalID, out targetObj)) + { + if (targetObj.Inventory.TryGetItem(update.InventoryData.ItemID, out item)) + { + // Updating an existing item in the task inventory + } + else if (scene.Server.Inventory.TryGetInventory(agent.ID, update.InventoryData.ItemID, out invObj)) + { + // Create a new item in the task inventory + if (invObj is InventoryItem) + { + InventoryItem fromItem = (InventoryItem)invObj; + + item = new InventoryTaskItem(); + //item.ID will be assigned in AddOrUpdateItem + item.AssetID = fromItem.AssetID; + item.AssetType = fromItem.AssetType; + item.CreationDate = fromItem.CreationDate; + item.CreatorID = fromItem.CreatorID; + item.Description = fromItem.Description; + item.Flags = fromItem.Flags; + item.GrantedPermissions = 0; + item.GroupID = fromItem.GroupID; + item.GroupOwned = fromItem.GroupOwned; + item.InventoryType = fromItem.InventoryType; + item.Name = fromItem.Name; + item.OwnerID = agent.ID; + item.ParentID = update.InventoryData.FolderID; + item.Parent = null; // TODO: Try to find a parent folder in task inventory? + item.ParentObjectID = targetObj.Prim.ID; + item.PermissionGranter = UUID.Zero; + item.Permissions = fromItem.Permissions.GetNextPermissions(); + item.SalePrice = fromItem.SalePrice; + item.SaleType = fromItem.SaleType; + + bool allowDrop = (targetObj.Prim.Flags & PrimFlags.AllowInventoryDrop) != 0; + + targetObj.Inventory.AddOrUpdateItem(item, false, allowDrop); + Logger.Log("Created new task inventory item: " + item.Name, Helpers.LogLevel.Info); + + // Send an ObjectPropertiesReply to inform the client that inventory has changed + ObjectPropertiesPacket props = new ObjectPropertiesPacket(); + props.ObjectData = new ObjectPropertiesPacket.ObjectDataBlock[1]; + props.ObjectData[0] = new ObjectPropertiesPacket.ObjectDataBlock(); + props.ObjectData[0].AggregatePerms = targetObj.Prim.Properties.AggregatePerms; + props.ObjectData[0].AggregatePermTextures = targetObj.Prim.Properties.AggregatePermTextures; + props.ObjectData[0].AggregatePermTexturesOwner = targetObj.Prim.Properties.AggregatePermTexturesOwner; + props.ObjectData[0].BaseMask = (uint)targetObj.Prim.Properties.Permissions.BaseMask; + props.ObjectData[0].Category = (uint)targetObj.Prim.Properties.Category; + props.ObjectData[0].CreationDate = Utils.DateTimeToUnixTime(targetObj.Prim.Properties.CreationDate); + props.ObjectData[0].CreatorID = targetObj.Prim.Properties.CreatorID; + props.ObjectData[0].Description = Utils.StringToBytes(targetObj.Prim.Properties.Description); + props.ObjectData[0].EveryoneMask = (uint)targetObj.Prim.Properties.Permissions.EveryoneMask; + props.ObjectData[0].FolderID = targetObj.Prim.Properties.FolderID; + props.ObjectData[0].FromTaskID = targetObj.Prim.Properties.FromTaskID; + props.ObjectData[0].GroupID = targetObj.Prim.Properties.GroupID; + props.ObjectData[0].GroupMask = (uint)targetObj.Prim.Properties.Permissions.GroupMask; + props.ObjectData[0].InventorySerial = targetObj.Prim.Properties.InventorySerial; + props.ObjectData[0].ItemID = targetObj.Prim.Properties.ItemID; + props.ObjectData[0].LastOwnerID = targetObj.Prim.Properties.LastOwnerID; + props.ObjectData[0].Name = Utils.StringToBytes(targetObj.Prim.Properties.Name); + props.ObjectData[0].NextOwnerMask = (uint)targetObj.Prim.Properties.Permissions.NextOwnerMask; + props.ObjectData[0].ObjectID = targetObj.Prim.ID; + props.ObjectData[0].OwnerID = targetObj.Prim.Properties.OwnerID; + props.ObjectData[0].OwnerMask = (uint)targetObj.Prim.Properties.Permissions.OwnerMask; + props.ObjectData[0].OwnershipCost = targetObj.Prim.Properties.OwnershipCost; + props.ObjectData[0].SalePrice = targetObj.Prim.Properties.SalePrice; + props.ObjectData[0].SaleType = (byte)targetObj.Prim.Properties.SaleType; + props.ObjectData[0].SitName = Utils.StringToBytes(targetObj.Prim.Properties.SitName); + props.ObjectData[0].TextureID = targetObj.Prim.Properties.GetTextureIDBytes(); + props.ObjectData[0].TouchName = Utils.StringToBytes(targetObj.Prim.Properties.TouchName); + + scene.UDP.SendPacket(agent.ID, props, PacketCategory.Transaction); + } + else + { + Logger.Log("[TODO] Handle dropping folders in task inventory", Helpers.LogLevel.Warning); + } + } + else + { + Logger.Log("Got an UpdateTaskInventory packet referencing an unknown inventory item", Helpers.LogLevel.Warning); + } + } + else + { + Logger.Log("Got an UpdateTaskInventory packet referencing an unknown object", Helpers.LogLevel.Warning); + } + } + else + { + Logger.Log("Got an UpdateTaskInventory packet with a Key of " + update.UpdateData.Key, Helpers.LogLevel.Warning); + } + } + + void RemoveTaskInventoryHandler(Packet packet, Agent agent) + { + } + + void MoveTaskInventoryHandler(Packet packet, Agent agent) + { + } + } +} diff --git a/Programs/Simian/SceneExtensions/TaskInventoryManager.cs b/Programs/Simian/SceneExtensions/TaskInventoryManager.cs index c34cf818..26d6457d 100644 --- a/Programs/Simian/SceneExtensions/TaskInventoryManager.cs +++ b/Programs/Simian/SceneExtensions/TaskInventoryManager.cs @@ -10,6 +10,7 @@ namespace Simian class TaskInventoryManager : IExtension, ITaskInventoryProvider { ISceneProvider scene; + Dictionary assets = new Dictionary(); public TaskInventoryManager() { @@ -25,36 +26,21 @@ namespace Simian { } - public UUID CreateItem(UUID agentID, UUID containerObjectID, string name, string description, InventoryType invType, - AssetType type, UUID assetID, UUID parentID, PermissionMask ownerMask, PermissionMask nextOwnerMask, - UUID ownerID, UUID creatorID, UUID transactionID, uint callbackID, bool sendPacket) + public void AddTaskFile(string filename, byte[] assetData) { - return UUID.Zero; + lock (assets) + assets[filename] = assetData; } - public bool RemoveItem(UUID agentID, UUID containerObjectID, UUID itemID) + public bool RemoveTaskFile(string filename) { - return false; + lock (assets) + return assets.Remove(filename); } - public bool TryGetAsset(UUID containerObjectID, UUID assetID, out Asset asset) + public bool TryGetTaskFile(string filename, out byte[] assetData) { - asset = null; - return false; - } - - public void ForEachItem(UUID containerObjectID, Action action) - { - } - - public InventoryTaskItem FindItem(UUID containerObjectID, Predicate match) - { - return null; - } - - public List FindAllItems(UUID containerObjectID, Predicate match) - { - return new List(); + return assets.TryGetValue(filename, out assetData); } } } diff --git a/Programs/Simian/ScriptApi.cs b/Programs/Simian/ScriptApi.cs index 61ec817d..e56ddd72 100644 --- a/Programs/Simian/ScriptApi.cs +++ b/Programs/Simian/ScriptApi.cs @@ -1857,7 +1857,7 @@ namespace Simian } if ((item != null && - scene.TaskInventory.TryGetAsset(hostObject.Prim.ID, item.AssetID, out asset) && + scene.Server.Assets.TryGetAsset(item.AssetID, out asset) && asset is AssetPrim && scene.Server.Assets.TryDecodePrimAsset(((AssetPrim)asset).AssetData, out newLinkset)) || newLinkset != null) @@ -2362,10 +2362,8 @@ namespace Simian { hostObject.AddScriptLPS(1); - InventoryTaskItem scriptItem = scene.TaskInventory.FindItem(hostObject.Prim.ID, - delegate(InventoryTaskItem item) { return item.ID == scriptID; }); - - if (scriptItem != null) + InventoryTaskItem scriptItem; + if (hostObject.Inventory.TryGetItem(scriptID, out scriptItem)) return scriptItem.PermissionGranter.ToString(); else return LSL_Key.Zero; @@ -2375,12 +2373,10 @@ namespace Simian { hostObject.AddScriptLPS(1); - InventoryTaskItem scriptItem = scene.TaskInventory.FindItem(hostObject.Prim.ID, - delegate(InventoryTaskItem item) { return item.ID == scriptID; }); - uint perms = 0; - if (scriptItem != null) + InventoryTaskItem scriptItem; + if (hostObject.Inventory.TryGetItem(scriptID, out scriptItem)) { perms = scriptItem.GrantedPermissions; if (automaticLinkPermission) @@ -2499,7 +2495,7 @@ namespace Simian hostObject.AddScriptLPS(1); int count = 0; - scene.TaskInventory.ForEachItem(hostObject.Prim.ID, + hostObject.Inventory.ForEachItem( delegate(InventoryTaskItem item) { if (type == -1 || (int)item.AssetType == type) @@ -4520,10 +4516,8 @@ namespace Simian { hostObject.AddScriptLPS(1); - InventoryTaskItem findItem = scene.TaskInventory.FindItem(hostObject.Prim.ID, - delegate(InventoryTaskItem item) { return item.Name == itemName; }); - - if (findItem != null) + InventoryTaskItem findItem; + if (hostObject.Inventory.TryGetItem(itemName, out findItem)) { switch (mask) { @@ -5103,15 +5097,17 @@ namespace Simian private InventoryTaskItem InventorySelf() { hostObject.AddScriptLPS(1); - return scene.TaskInventory.FindItem(hostObject.Prim.ID, - delegate(InventoryTaskItem item) { return item.AssetID == scriptID; }); + + InventoryTaskItem self; + hostObject.Inventory.TryGetItem(scriptID, out self); + return self; } private InventoryTaskItem InventoryKey(string name, AssetType type) { hostObject.AddScriptLPS(1); - return scene.TaskInventory.FindItem(hostObject.Prim.ID, + return hostObject.Inventory.FindItem( delegate(InventoryTaskItem item) { return item.AssetType == type && item.Name == name; }); } @@ -5119,8 +5115,9 @@ namespace Simian { hostObject.AddScriptLPS(1); - return scene.TaskInventory.FindItem(hostObject.Prim.ID, - delegate(InventoryTaskItem item) { return item.Name == name; }); + InventoryTaskItem item; + hostObject.Inventory.TryGetItem(name, out item); + return item; } /// @@ -5151,10 +5148,11 @@ namespace Simian UUID ScriptByName(string name) { - InventoryTaskItem scriptItem = scene.TaskInventory.FindItem(hostObject.Prim.ID, - delegate(InventoryTaskItem item) { return item.AssetType == AssetType.LSLText && item.Name == name; }); - - return (scriptItem != null) ? scriptItem.ID : UUID.Zero; + InventoryTaskItem scriptItem; + if (hostObject.Inventory.TryGetItem(name, out scriptItem) && scriptItem.AssetType == AssetType.LSLText) + return scriptItem.ID; + else + return UUID.Zero; } void ShoutError(string msg) diff --git a/Programs/Simian/ScriptTypes.cs b/Programs/Simian/ScriptTypes.cs index 83d358df..565ee08e 100644 --- a/Programs/Simian/ScriptTypes.cs +++ b/Programs/Simian/ScriptTypes.cs @@ -4,6 +4,22 @@ using System.Text.RegularExpressions; namespace Simian { + [Flags] + public enum Changed : uint + { + INVENTORY = 1, + COLOR = 2, + SHAPE = 4, + SCALE = 8, + TEXTURE = 16, + LINK = 32, + ALLOWED_DROP = 64, + OWNER = 128, + REGION_RESTART = 256, + REGION = 512, + TELEPORT = 1024 + } + public static class ScriptTypes { #region LSL Types diff --git a/Programs/Simian/SimulationObject.cs b/Programs/Simian/SimulationObject.cs index 8a02272d..4e25cc30 100644 --- a/Programs/Simian/SimulationObject.cs +++ b/Programs/Simian/SimulationObject.cs @@ -8,21 +8,16 @@ namespace Simian { public class SimulationObject { - // TODO: Frozen and RotationAxis might want to become properties that access the parent values - + /// Reference to the scene this object exists in + public ISceneProvider Scene; /// Reference to the primitive object this class wraps public Primitive Prim; /// Link number, if this object is part of a linkset public int LinkNumber; - /// True when an avatar grabs this object. Stops movement and - /// rotation - public bool Frozen; /// Holds the state of the object after each edit to enable undo public CircularQueue UndoSteps = new CircularQueue(10); /// Holds the state of the object after each undo to enable redo public CircularQueue RedoSteps = new CircularQueue(10); - /// Axis of rotation for the object in the physics engine - public Vector3 RotationAxis = Vector3.UnitY; /// A continual rotational impulse public Vector3 Torque; /// Last point the object was attached to (right hand by default) @@ -41,13 +36,61 @@ namespace Simian /// Will be applied to the object when it is dropped. This is always the world /// rotation, since it is only applicable to parent objects public Quaternion BeforeAttachmentRotation = Quaternion.Identity; + /// Task inventory + public SimulationObjectInventory Inventory; - protected ISceneProvider Scene; protected SimpleMesh[] Meshes; protected SimpleMesh[] WorldTransformedMeshes; + bool frozen; + Vector3 rotationAxis = Vector3.UnitY; uint? crc; + #region Properties + + /// True when an avatar grabs this object. Stops movement and + /// rotation + public bool Frozen + { + get + { + SimulationObject parent; + if (Prim.ParentID != 0 && Scene.TryGetObject(Prim.ID, out parent)) + return parent.Frozen; + else + return frozen; + } + set + { + SimulationObject parent; + if (Prim.ParentID != 0 && Scene.TryGetObject(Prim.ID, out parent)) + parent.Frozen = value; + else + frozen = value; + } + } + + /// Axis of rotation for the object in the physics engine + public Vector3 RotationAxis + { + get + { + SimulationObject parent; + if (Prim.ParentID != 0 && Scene.TryGetObject(Prim.ID, out parent)) + return parent.RotationAxis; + else + return rotationAxis; + } + set + { + SimulationObject parent; + if (Prim.ParentID != 0 && Scene.TryGetObject(Prim.ID, out parent)) + parent.RotationAxis = value; + else + rotationAxis = value; + } + } + public uint CRC { get @@ -76,12 +119,16 @@ namespace Simian } } + #endregion Properties + public SimulationObject(SimulationObject obj) { - Prim = new Primitive(obj.Prim); Scene = obj.Scene; + Prim = new Primitive(obj.Prim); + Inventory = new SimulationObjectInventory(this); LinkNumber = obj.LinkNumber; - Frozen = obj.Frozen; + frozen = obj.frozen; + rotationAxis = obj.rotationAxis; // Skip everything else because it can be lazily reconstructed } @@ -89,6 +136,7 @@ namespace Simian { Prim = prim; Scene = scene; + Inventory = new SimulationObjectInventory(this); } public SimulationObject GetLinksetParent() diff --git a/Programs/Simian/SimulationObjectInventory.cs b/Programs/Simian/SimulationObjectInventory.cs new file mode 100644 index 00000000..bce81305 --- /dev/null +++ b/Programs/Simian/SimulationObjectInventory.cs @@ -0,0 +1,309 @@ +using System; +using System.Text; +using System.Collections.Generic; +using OpenMetaverse; + +namespace Simian +{ + public class TaskInventoryStringBuilder + { + StringBuilder builder = new StringBuilder(); + + public TaskInventoryStringBuilder(UUID folderID, UUID parentID) + { + builder.Append("\tinv_object\t0\n\t{\n"); + AddNameValueLine("obj_id", folderID.ToString()); + AddNameValueLine("parent_id", parentID.ToString()); + AddNameValueLine("type", "category"); + AddNameValueLine("name", "Contents|"); + AddSectionEnd(); + } + + public void AddItemStart() + { + builder.Append("\tinv_item\t0\n"); + AddSectionStart(); + } + + public void AddPermissionsStart() + { + builder.Append("\tpermissions 0\n"); + AddSectionStart(); + } + + public void AddSaleStart() + { + builder.Append("\tsale_info\t0\n"); + AddSectionStart(); + } + + protected void AddSectionStart() + { + builder.Append("\t{\n"); + } + + public void AddSectionEnd() + { + builder.Append("\t}\n"); + } + + public void AddLine(string addLine) + { + builder.Append(addLine); + } + + public void AddNameValueLine(string name, string value) + { + builder.Append("\t\t"); + builder.Append(name); + builder.Append("\t"); + builder.Append(value); + builder.Append("\n"); + } + + public override string ToString() + { + return builder.ToString(); + } + } + + public class SimulationObjectInventory + { + SimulationObject hostObject; + string inventoryFilename; + short inventoryFilenameSerial; + DoubleDictionary items; + + public short InventorySerial + { + get { return hostObject.Prim.Properties.InventorySerial; } + set { hostObject.Prim.Properties.InventorySerial = value; } + } + + public SimulationObjectInventory(SimulationObject hostObject) + { + this.hostObject = hostObject; + } + + public void ChangeInventoryOwner(UUID newOwnerID) + { + LazyInitialize(); + throw new NotImplementedException(); + } + + public void ChangeInventoryGroup(UUID newGroupID) + { + LazyInitialize(); + throw new NotImplementedException(); + } + + public void StartScripts(int startParam, bool triggerOnRezEvent) + { + LazyInitialize(); + throw new NotImplementedException(); + } + + public void StopScripts() + { + LazyInitialize(); + throw new NotImplementedException(); + } + + public void StartScript(InventoryTaskItem script, int startParam, bool triggerOnRezEvent) + { + LazyInitialize(); + throw new NotImplementedException(); + } + + public void StopScript(UUID itemID) + { + LazyInitialize(); + throw new NotImplementedException(); + } + + public void AddOrUpdateItem(InventoryTaskItem item, bool replace, bool allowedDrop) + { + LazyInitialize(); + + item.ParentObjectID = hostObject.Prim.ID; + + if (replace) + { + InventoryTaskItem oldItem; + if (items.TryGetValue(item.Name, out oldItem)) + { + item.ID = oldItem.ID; + items.Remove(item.ID, item.Name); + } + } + else + { + item.Name = NextAvailableFilename(item.Name); + } + + if (item.ID == UUID.Zero) + item.ID = UUID.Random(); + + items.Add(item.ID, item.Name, item); + + UpdateTaskInventoryAsset(); + + // Post a script event + Changed change = allowedDrop ? Changed.ALLOWED_DROP : Changed.INVENTORY; + hostObject.Scene.ScriptEngine.PostObjectEvent(hostObject.Prim.ID, new EventParams( + "changed", new object[] { new ScriptTypes.LSL_Integer((uint)change) }, new DetectParams[0])); + } + + public InventoryType RemoveItem(UUID itemID) + { + LazyInitialize(); + InventoryTaskItem item; + if (items.TryGetValue(itemID, out item)) + { + items.Remove(itemID, item.Name); + + UpdateTaskInventoryAsset(); + + // Post a script event + hostObject.Scene.ScriptEngine.PostObjectEvent(hostObject.Prim.ID, new EventParams( + "changed", new object[] { new ScriptTypes.LSL_Integer((uint)Changed.INVENTORY) }, new DetectParams[0])); + + // FIXME: Check if this prim still classifies as "scripted" + + return item.InventoryType; + } + else + { + return InventoryType.Unknown; + } + } + + public bool TryGetItem(UUID itemID, out InventoryTaskItem item) + { + LazyInitialize(); + return items.TryGetValue(itemID, out item); + } + + public bool TryGetItem(string name, out InventoryTaskItem item) + { + LazyInitialize(); + return items.TryGetValue(name, out item); + } + + public string GetInventoryFilename() + { + if (InventorySerial > 0) + { + if (String.IsNullOrEmpty(inventoryFilename) || inventoryFilenameSerial < InventorySerial) + inventoryFilename = "inventory_" + UUID.Random() + ".tmp"; + + inventoryFilenameSerial = InventorySerial; + + return inventoryFilename; + } + else + { + return String.Empty; + } + } + + public void ForEachItem(Action action) + { + LazyInitialize(); + items.ForEach(action); + } + + public InventoryTaskItem FindItem(Predicate match) + { + LazyInitialize(); + return items.FindValue(match); + } + + public IList FindAllItems(Predicate match) + { + LazyInitialize(); + return items.FindAll(match); + } + + void UpdateTaskInventoryAsset() + { + // Remove the previous task inventory asset + string filename = GetInventoryFilename(); + if (!String.IsNullOrEmpty(filename)) + hostObject.Scene.TaskInventory.RemoveTaskFile(filename); + + // Update the inventory serial number + ++InventorySerial; + + // Create the new asset + filename = GetInventoryFilename(); + byte[] assetData = GetTaskInventoryAsset(); + hostObject.Scene.TaskInventory.AddTaskFile(filename, assetData); + } + + byte[] GetTaskInventoryAsset() + { + TaskInventoryStringBuilder invString = new TaskInventoryStringBuilder(hostObject.Prim.ID, UUID.Zero); + + items.ForEach( + delegate(InventoryTaskItem item) + { + invString.AddItemStart(); + invString.AddNameValueLine("item_id", item.ID.ToString()); + invString.AddNameValueLine("parent_id", hostObject.Prim.ID.ToString()); + + invString.AddPermissionsStart(); + + invString.AddNameValueLine("base_mask", Utils.UIntToHexString((uint)item.Permissions.BaseMask)); + invString.AddNameValueLine("owner_mask", Utils.UIntToHexString((uint)item.Permissions.OwnerMask)); + invString.AddNameValueLine("group_mask", Utils.UIntToHexString((uint)item.Permissions.GroupMask)); + invString.AddNameValueLine("everyone_mask", Utils.UIntToHexString((uint)item.Permissions.EveryoneMask)); + invString.AddNameValueLine("next_owner_mask", Utils.UIntToHexString((uint)item.Permissions.NextOwnerMask)); + + invString.AddNameValueLine("creator_id", item.CreatorID.ToString()); + invString.AddNameValueLine("owner_id", item.OwnerID.ToString()); + + invString.AddNameValueLine("last_owner_id", item.CreatorID.ToString()); // FIXME: Do we need InventoryItem.LastOwnerID? + + invString.AddNameValueLine("group_id", item.GroupID.ToString()); + invString.AddSectionEnd(); + + invString.AddNameValueLine("asset_id", item.AssetID.ToString()); + invString.AddNameValueLine("type", OpenMetaverse.InventoryManager.AssetTypeToString(item.AssetType)); + invString.AddNameValueLine("inv_type", OpenMetaverse.InventoryManager.InventoryTypeToString(item.InventoryType)); + invString.AddNameValueLine("flags", Utils.UIntToHexString(item.Flags)); + + invString.AddSaleStart(); + invString.AddNameValueLine("sale_type", OpenMetaverse.InventoryManager.SaleTypeToString(item.SaleType)); + invString.AddNameValueLine("sale_price", item.SalePrice.ToString()); + invString.AddSectionEnd(); + + invString.AddNameValueLine("name", item.Name + "|"); + invString.AddNameValueLine("desc", item.Description + "|"); + + invString.AddNameValueLine("creation_date", Utils.DateTimeToUnixTime(item.CreationDate).ToString()); + invString.AddSectionEnd(); + } + ); + + return Utils.StringToBytes(invString.ToString()); + } + + string NextAvailableFilename(string name) + { + string tryName = name; + int suffix = 1; + + while (items.ContainsKey(tryName) && suffix < 256) + tryName = String.Format("{0} {1}", name, suffix++); + + return tryName; + } + + void LazyInitialize() + { + if (items == null) + items = new DoubleDictionary(); + } + } +} diff --git a/bin/SimianData/Simian.ini b/bin/SimianData/Simian.ini index cee3de43..010ca345 100644 --- a/bin/SimianData/Simian.ini +++ b/bin/SimianData/Simian.ini @@ -145,6 +145,9 @@ LLObjects ; Packet handling for parcel information LLParcels +; Packet handling for object inventory +LLTaskInventory + ; Packet handling for texture downloads LLTextures