2 // Radegast Metaverse Client
3 // Copyright (c) 2009-2013, Radegast Development Team
4 // All rights reserved.
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are met:
9 // * Redistributions of source code must retain the above copyright notice,
10 // this list of conditions and the following disclaimer.
11 // * Redistributions in binary form must reproduce the above copyright
12 // notice, this list of conditions and the following disclaimer in the
13 // documentation and/or other materials provided with the distribution.
14 // * Neither the name of the application "Radegast", nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 using System.Collections;
33 using System.Collections.Generic;
35 using System.Threading;
37 using OpenMetaverse.StructuredData;
41 public class CurrentOutfitFolder : IDisposable
45 RadegastInstance Instance;
46 bool InitiCOF = false;
47 bool AppearanceSent = false;
48 bool COFReady = false;
49 bool InitialUpdateDone = false;
50 public Dictionary<UUID, InventoryItem> Content = new Dictionary<UUID, InventoryItem>();
51 public InventoryFolder COF;
55 #region Construction and disposal
56 public CurrentOutfitFolder(RadegastInstance instance)
58 this.Instance = instance;
59 this.Client = instance.Client;
60 Instance.ClientChanged += new EventHandler<ClientChangedEventArgs>(instance_ClientChanged);
61 RegisterClientEvents(Client);
66 UnregisterClientEvents(Client);
67 Instance.ClientChanged -= new EventHandler<ClientChangedEventArgs>(instance_ClientChanged);
69 #endregion Construction and disposal
71 #region Event handling
72 void instance_ClientChanged(object sender, ClientChangedEventArgs e)
74 UnregisterClientEvents(Client);
76 RegisterClientEvents(Client);
79 void RegisterClientEvents(GridClient client)
81 client.Network.EventQueueRunning += new EventHandler<EventQueueRunningEventArgs>(Network_EventQueueRunning);
82 client.Inventory.FolderUpdated += new EventHandler<FolderUpdatedEventArgs>(Inventory_FolderUpdated);
83 client.Inventory.ItemReceived += new EventHandler<ItemReceivedEventArgs>(Inventory_ItemReceived);
84 client.Appearance.AppearanceSet += new EventHandler<AppearanceSetEventArgs>(Appearance_AppearanceSet);
85 client.Objects.KillObject += new EventHandler<KillObjectEventArgs>(Objects_KillObject);
88 void UnregisterClientEvents(GridClient client)
90 client.Network.EventQueueRunning -= new EventHandler<EventQueueRunningEventArgs>(Network_EventQueueRunning);
91 client.Inventory.FolderUpdated -= new EventHandler<FolderUpdatedEventArgs>(Inventory_FolderUpdated);
92 client.Inventory.ItemReceived -= new EventHandler<ItemReceivedEventArgs>(Inventory_ItemReceived);
93 client.Appearance.AppearanceSet -= new EventHandler<AppearanceSetEventArgs>(Appearance_AppearanceSet);
94 client.Objects.KillObject -= new EventHandler<KillObjectEventArgs>(Objects_KillObject);
95 lock (Content) Content.Clear();
97 AppearanceSent = false;
99 InitialUpdateDone = false;
102 void Appearance_AppearanceSet(object sender, AppearanceSetEventArgs e)
104 AppearanceSent = true;
111 void Inventory_ItemReceived(object sender, ItemReceivedEventArgs e)
113 bool partOfCOF = false;
114 var links = ContentLinks();
115 foreach (var cofItem in links)
117 if (cofItem.AssetUUID == e.Item.UUID)
128 Content[e.Item.UUID] = e.Item;
132 if (Content.Count == links.Count)
141 foreach (InventoryItem link in Content.Values)
143 if (link.InventoryType == InventoryType.Wearable)
145 InventoryWearable w = (InventoryWearable)link;
146 InventoryItem lk = links.Find(l => l.AssetUUID == w.UUID);
147 // Logger.DebugLog(string.Format("\nName: {0}\nDescription: {1}\nType: {2} - {3}", w.Name, lk == null ? "" : lk.Description, w.Flags.ToString(), w.WearableType.ToString())); ;
155 object FolderSync = new object();
157 void Inventory_FolderUpdated(object sender, FolderUpdatedEventArgs e)
159 if (COF == null) return;
161 if (e.FolderID == COF.UUID && e.Success)
163 COF = (InventoryFolder)Client.Inventory.Store[COF.UUID];
166 lock (Content) Content.Clear();
169 List<UUID> items = new List<UUID>();
170 List<UUID> owners = new List<UUID>();
172 foreach (var link in ContentLinks())
174 //if (Client.Inventory.Store.Contains(link.AssetUUID))
178 items.Add(link.AssetUUID);
179 owners.Add(Client.Self.AgentID);
184 Client.Inventory.RequestFetchInventory(items, owners);
190 void Objects_KillObject(object sender, KillObjectEventArgs e)
192 if (Client.Network.CurrentSim != e.Simulator) return;
194 Primitive prim = null;
195 if (Client.Network.CurrentSim.ObjectsPrimitives.TryGetValue(e.ObjectLocalID, out prim))
197 UUID invItem = GetAttachmentItem(prim);
198 if (invItem != UUID.Zero)
205 void Network_EventQueueRunning(object sender, EventQueueRunningEventArgs e)
207 if (e.Simulator == Client.Network.CurrentSim && !InitiCOF)
213 #endregion Event handling
215 #region Private methods
216 void RequestDescendants(UUID folderID)
218 Client.Inventory.RequestFolderContents(folderID, Client.Self.AgentID, true, true, InventorySortOrder.ByDate);
223 List<InventoryBase> rootContent = Client.Inventory.Store.GetContents(Client.Inventory.Store.RootFolder.UUID);
224 foreach (InventoryBase baseItem in rootContent)
226 if (baseItem is InventoryFolder && ((InventoryFolder)baseItem).PreferredType == AssetType.CurrentOutfitFolder)
228 COF = (InventoryFolder)baseItem;
239 RequestDescendants(COF.UUID);
245 UUID cofID = Client.Inventory.CreateFolder(Client.Inventory.Store.RootFolder.UUID, "Current Look", AssetType.CurrentOutfitFolder);
246 if (Client.Inventory.Store.Items.ContainsKey(cofID) && Client.Inventory.Store.Items[cofID].Data is InventoryFolder)
248 COF = (InventoryFolder)Client.Inventory.Store.Items[cofID].Data;
259 if (InitialUpdateDone) return;
260 InitialUpdateDone = true;
263 List<Primitive> myAtt = Client.Network.CurrentSim.ObjectsPrimitives.FindAll((Primitive p) => p.ParentID == Client.Self.LocalID);
265 foreach (InventoryItem item in Content.Values)
267 if (item is InventoryObject || item is InventoryAttachment)
269 if (!IsAttached(myAtt, item))
271 Client.Appearance.Attach(item, AttachmentPoint.Default, false);
277 #endregion Private methods
279 #region Public methods
283 /// <returns>List if InventoryItems that can be part of appearance (attachments, wearables)</returns>
284 public List<InventoryItem> ContentLinks()
286 List<InventoryItem> ret = new List<InventoryItem>();
287 if (COF == null) return ret;
289 Client.Inventory.Store.GetContents(COF)
290 .FindAll(b => CanBeWorn(b) && ((InventoryItem)b).AssetType == AssetType.Link)
291 .ForEach(item => ret.Add((InventoryItem)item));
297 /// Get inventory ID of a prim
299 /// <param name="prim">Prim to check</param>
300 /// <returns>Inventory ID of the object. UUID.Zero if not found</returns>
301 public static UUID GetAttachmentItem(Primitive prim)
303 if (prim.NameValues == null) return UUID.Zero;
305 for (int i = 0; i < prim.NameValues.Length; i++)
307 if (prim.NameValues[i].Name == "AttachItemID")
309 return (UUID)prim.NameValues[i].Value.ToString();
316 /// Is an inventory item currently attached
318 /// <param name="attachments">List of root prims that are attached to our avatar</param>
319 /// <param name="item">Inventory item to check</param>
320 /// <returns>True if the inventory item is attached to avatar</returns>
321 public static bool IsAttached(List<Primitive> attachments, InventoryItem item)
323 foreach (Primitive prim in attachments)
325 if (GetAttachmentItem(prim) == item.UUID)
335 /// Checks if inventory item of Wearable type is worn
337 /// <param name="currentlyWorn">Current outfit</param>
338 /// <param name="item">Item to check</param>
339 /// <returns>True if the item is worn</returns>
340 public static bool IsWorn(Dictionary<WearableType, AppearanceManager.WearableData> currentlyWorn, InventoryItem item)
342 foreach (var n in currentlyWorn.Values)
344 if (n.ItemID == item.UUID)
353 /// Can this inventory type be worn
355 /// <param name="item">Item to check</param>
356 /// <returns>True if the inventory item can be worn</returns>
357 public static bool CanBeWorn(InventoryBase item)
359 return item is InventoryWearable || item is InventoryAttachment || item is InventoryObject;
363 /// Attach an inventory item
365 /// <param name="item">Item to be attached</param>
366 /// <param name="point">Attachment point</param>
367 /// <param name="replace">Replace existing attachment at that point first?</param>
368 public void Attach(InventoryItem item, AttachmentPoint point, bool replace)
370 Client.Appearance.Attach(item, point, replace);
375 /// Creates a new COF link
377 /// <param name="item">Original item to be linked from COF</param>
378 public void AddLink(InventoryItem item)
380 if (item.InventoryType == InventoryType.Wearable && !IsBodyPart(item))
382 InventoryWearable w = (InventoryWearable)item;
384 string desc = string.Format("@{0}{1:00}", (int)w.WearableType, layer);
389 AddLink(item, string.Empty);
394 /// Creates a new COF link
396 /// <param name="item">Original item to be linked from COF</param>
397 /// <param name="newDescription">Description for the link</param>
398 public void AddLink(InventoryItem item, string newDescription)
400 if (COF == null) return;
402 bool linkExists = false;
404 linkExists = null != ContentLinks().Find(itemLink => itemLink.AssetUUID == item.UUID);
408 Client.Inventory.CreateLink(COF.UUID, item.UUID, item.Name, newDescription, AssetType.Link, item.InventoryType, UUID.Random(), (success, newItem) =>
412 Client.Inventory.RequestFetchInventory(newItem.UUID, newItem.OwnerID);
419 /// Remove a link to specified inventory item
421 /// <param name="itemID">ID of the target inventory item for which we want link to be removed</param>
422 public void RemoveLink(UUID itemID)
424 RemoveLink(new List<UUID>(1) { itemID });
428 /// Remove a link to specified inventory item
430 /// <param name="itemIDs">List of IDs of the target inventory item for which we want link to be removed</param>
431 public void RemoveLink(List<UUID> itemIDs)
433 if (COF == null) return;
435 List<UUID> toRemove = new List<UUID>();
437 foreach (UUID itemID in itemIDs)
439 var links = ContentLinks().FindAll(itemLink => itemLink.AssetUUID == itemID);
440 links.ForEach(item => toRemove.Add(item.UUID));
443 Client.Inventory.Remove(toRemove, null);
447 /// Remove attachment
449 /// <param name="item">>Inventory item to be detached</param>
450 public void Detach(InventoryItem item)
452 Client.Appearance.Detach(item);
453 RemoveLink(item.UUID);
456 public List<InventoryItem> GetWornAt(WearableType type)
458 var ret = new List<InventoryItem>();
459 ContentLinks().ForEach(link =>
461 var item = RealInventoryItem(link);
462 if (item is InventoryWearable)
464 var w = (InventoryWearable)item;
465 if (w.WearableType == type)
476 /// Resolves inventory links and returns a real inventory item that
477 /// the link is pointing to
479 /// <param name="item"></param>
480 /// <returns></returns>
481 public InventoryItem RealInventoryItem(InventoryItem item)
483 if (item.IsLink() && Client.Inventory.Store.Contains(item.AssetUUID) && Client.Inventory.Store[item.AssetUUID] is InventoryItem)
485 return (InventoryItem)Client.Inventory.Store[item.AssetUUID];
492 /// Replaces the current outfit and updates COF links accordingly
494 /// <param name="outfit">List of new wearables and attachments that comprise the new outfit</param>
495 public void ReplaceOutfit(List<InventoryItem> newOutfit)
497 // Resolve inventory links
498 List<InventoryItem> outfit = new List<InventoryItem>();
499 foreach (var item in newOutfit)
501 outfit.Add(RealInventoryItem(item));
504 // Remove links to all exiting items
505 List<UUID> toRemove = new List<UUID>();
506 ContentLinks().ForEach(item =>
508 if (IsBodyPart(item))
510 WearableType linkType = ((InventoryWearable)RealInventoryItem(item)).WearableType;
511 bool hasBodyPart = false;
513 foreach (var newItemTmp in newOutfit)
515 var newItem = RealInventoryItem(newItemTmp);
516 if (IsBodyPart(newItem))
518 if (((InventoryWearable)newItem).WearableType == linkType)
528 toRemove.Add(item.UUID);
533 toRemove.Add(item.UUID);
537 Client.Inventory.Remove(toRemove, null);
539 // Add links to new items
540 List<InventoryItem> newItems = outfit.FindAll(item => CanBeWorn(item));
541 foreach (var item in newItems)
546 Client.Appearance.ReplaceOutfit(outfit);
547 WorkPool.QueueUserWorkItem(sync =>
550 Client.Appearance.RequestSetAppearance(true);
555 /// Add items to current outfit
557 /// <param name="item">Item to add</param>
558 /// <param name="replace">Should existing wearable of the same type be removed</param>
559 public void AddToOutfit(InventoryItem item, bool replace)
561 AddToOutfit(new List<InventoryItem>(1) { item }, replace);
565 /// Add items to current outfit
567 /// <param name="items">List of items to add</param>
568 /// <param name="replace">Should existing wearable of the same type be removed</param>
569 public void AddToOutfit(List<InventoryItem> items, bool replace)
571 List<InventoryItem> current = ContentLinks();
572 List<UUID> toRemove = new List<UUID>();
574 // Resolve inventory links and remove wearables of the same type from COF
575 List<InventoryItem> outfit = new List<InventoryItem>();
577 foreach (var item in items)
579 InventoryItem realItem = RealInventoryItem(item);
580 if (realItem is InventoryWearable)
582 foreach (var link in current)
584 var currentItem = RealInventoryItem(link);
585 if (link.AssetUUID == item.UUID)
587 toRemove.Add(link.UUID);
589 else if (currentItem is InventoryWearable)
591 var w = (InventoryWearable)currentItem;
592 if (w.WearableType == ((InventoryWearable)realItem).WearableType)
594 toRemove.Add(link.UUID);
600 outfit.Add(realItem);
602 Client.Inventory.Remove(toRemove, null);
604 // Add links to new items
605 List<InventoryItem> newItems = outfit.FindAll(item => CanBeWorn(item));
606 foreach (var item in newItems)
611 Client.Appearance.AddToOutfit(outfit, replace);
612 WorkPool.QueueUserWorkItem(sync =>
615 Client.Appearance.RequestSetAppearance(true);
620 /// Remove an item from the current outfit
622 /// <param name="items">Item to remove</param>
623 public void RemoveFromOutfit(InventoryItem item)
625 RemoveFromOutfit(new List<InventoryItem>(1) { item });
629 /// Remove specified items from the current outfit
631 /// <param name="items">List of items to remove</param>
632 public void RemoveFromOutfit(List<InventoryItem> items)
634 // Resolve inventory links
635 List<InventoryItem> outfit = new List<InventoryItem>();
636 foreach (var item in items)
638 var realItem = RealInventoryItem(item);
639 if (Instance.RLV.AllowDetach(realItem))
641 outfit.Add(realItem);
645 // Remove links to all items that were removed
646 List<UUID> toRemove = new List<UUID>();
647 foreach (InventoryItem item in outfit.FindAll(item => CanBeWorn(item) && !IsBodyPart(item)))
649 toRemove.Add(item.UUID);
651 RemoveLink(toRemove);
653 Client.Appearance.RemoveFromOutfit(outfit);
656 public bool IsBodyPart(InventoryItem item)
658 var realItem = RealInventoryItem(item);
659 if (realItem is InventoryWearable)
661 var w = (InventoryWearable)realItem;
662 var t = w.WearableType;
663 if (t == WearableType.Shape ||
664 t == WearableType.Skin ||
665 t == WearableType.Eyes ||
666 t == WearableType.Hair)
675 /// Force rebaking textures
677 public void RebakeTextures()
679 Client.Appearance.RequestSetAppearance(true);
682 #endregion Public methods