OSDN Git Service

58e78912317ce5caf46ebc42b330dcdd0448139e
[radegast/radegast.git] / Radegast / GUI / Consoles / Inventory / CurrentOutfitFolder.cs
1 // 
2 // Radegast Metaverse Client
3 // Copyright (c) 2009-2013, Radegast Development Team
4 // All rights reserved.
5 // 
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are met:
8 // 
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.
17 // 
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.
28 //
29 // $Id$
30 //
31 using System;
32 using System.Collections;
33 using System.Collections.Generic;
34 using System.Linq;
35 using System.Threading;
36 using OpenMetaverse;
37 using OpenMetaverse.StructuredData;
38
39 namespace Radegast
40 {
41     public class CurrentOutfitFolder : IDisposable
42     {
43         #region Fields
44         GridClient Client;
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;
52
53         #endregion Fields
54
55         #region Construction and disposal
56         public CurrentOutfitFolder(RadegastInstance instance)
57         {
58             this.Instance = instance;
59             this.Client = instance.Client;
60             Instance.ClientChanged += new EventHandler<ClientChangedEventArgs>(instance_ClientChanged);
61             RegisterClientEvents(Client);
62         }
63
64         public void Dispose()
65         {
66             UnregisterClientEvents(Client);
67             Instance.ClientChanged -= new EventHandler<ClientChangedEventArgs>(instance_ClientChanged);
68         }
69         #endregion Construction and disposal
70
71         #region Event handling
72         void instance_ClientChanged(object sender, ClientChangedEventArgs e)
73         {
74             UnregisterClientEvents(Client);
75             Client = e.Client;
76             RegisterClientEvents(Client);
77         }
78
79         void RegisterClientEvents(GridClient client)
80         {
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);
86         }
87
88         void UnregisterClientEvents(GridClient client)
89         {
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();
96             InitiCOF = false;
97             AppearanceSent = false;
98             COFReady = false;
99             InitialUpdateDone = false;
100         }
101
102         void Appearance_AppearanceSet(object sender, AppearanceSetEventArgs e)
103         {
104             AppearanceSent = true;
105             if (COFReady)
106             {
107                 InitialUpdate();
108             }
109         }
110
111         void Inventory_ItemReceived(object sender, ItemReceivedEventArgs e)
112         {
113             bool partOfCOF = false;
114             var links = ContentLinks();
115             foreach (var cofItem in links)
116             {
117                 if (cofItem.AssetUUID == e.Item.UUID)
118                 {
119                     partOfCOF = true;
120                     break;
121                 }
122             }
123
124             if (partOfCOF)
125             {
126                 lock (Content)
127                 {
128                     Content[e.Item.UUID] = e.Item;
129                 }
130             }
131
132             if (Content.Count == links.Count)
133             {
134                 COFReady = true;
135                 if (AppearanceSent)
136                 {
137                     InitialUpdate();
138                 }
139                 lock (Content)
140                 {
141                     foreach (InventoryItem link in Content.Values)
142                     {
143                         if (link.InventoryType == InventoryType.Wearable)
144                         {
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())); ;
148                         }
149                     }
150
151                 }
152             }
153         }
154
155         object FolderSync = new object();
156
157         void Inventory_FolderUpdated(object sender, FolderUpdatedEventArgs e)
158         {
159             if (COF == null) return;
160
161             if (e.FolderID == COF.UUID && e.Success)
162             {
163                 COF = (InventoryFolder)Client.Inventory.Store[COF.UUID];
164                 lock (FolderSync)
165                 {
166                     lock (Content) Content.Clear();
167
168
169                     List<UUID> items = new List<UUID>();
170                     List<UUID> owners = new List<UUID>();
171
172                     foreach (var link in ContentLinks())
173                     {
174                         //if (Client.Inventory.Store.Contains(link.AssetUUID))
175                         //{
176                         //    continue;
177                         //}
178                         items.Add(link.AssetUUID);
179                         owners.Add(Client.Self.AgentID);
180                     }
181
182                     if (items.Count > 0)
183                     {
184                         Client.Inventory.RequestFetchInventory(items, owners);
185                     }
186                 }
187             }
188         }
189
190         void Objects_KillObject(object sender, KillObjectEventArgs e)
191         {
192             if (Client.Network.CurrentSim != e.Simulator) return;
193
194             Primitive prim = null;
195             if (Client.Network.CurrentSim.ObjectsPrimitives.TryGetValue(e.ObjectLocalID, out prim))
196             {
197                 UUID invItem = GetAttachmentItem(prim);
198                 if (invItem != UUID.Zero)
199                 {
200                     RemoveLink(invItem);
201                 }
202             }
203         }
204
205         void Network_EventQueueRunning(object sender, EventQueueRunningEventArgs e)
206         {
207             if (e.Simulator == Client.Network.CurrentSim && !InitiCOF)
208             {
209                 InitiCOF = true;
210                 InitCOF();
211             }
212         }
213         #endregion Event handling
214
215         #region Private methods
216         void RequestDescendants(UUID folderID)
217         {
218             Client.Inventory.RequestFolderContents(folderID, Client.Self.AgentID, true, true, InventorySortOrder.ByDate);
219         }
220
221         void InitCOF()
222         {
223             List<InventoryBase> rootContent = Client.Inventory.Store.GetContents(Client.Inventory.Store.RootFolder.UUID);
224             foreach (InventoryBase baseItem in rootContent)
225             {
226                 if (baseItem is InventoryFolder && ((InventoryFolder)baseItem).PreferredType == AssetType.CurrentOutfitFolder)
227                 {
228                     COF = (InventoryFolder)baseItem;
229                     break;
230                 }
231             }
232
233             if (COF == null)
234             {
235                 CreateCOF();
236             }
237             else
238             {
239                 RequestDescendants(COF.UUID);
240             }
241         }
242
243         void CreateCOF()
244         {
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)
247             {
248                 COF = (InventoryFolder)Client.Inventory.Store.Items[cofID].Data;
249                 COFReady = true;
250                 if (AppearanceSent)
251                 {
252                     InitialUpdate();
253                 }
254             }
255         }
256
257         void InitialUpdate()
258         {
259             if (InitialUpdateDone) return;
260             InitialUpdateDone = true;
261             lock (Content)
262             {
263                 List<Primitive> myAtt = Client.Network.CurrentSim.ObjectsPrimitives.FindAll((Primitive p) => p.ParentID == Client.Self.LocalID);
264
265                 foreach (InventoryItem item in Content.Values)
266                 {
267                     if (item is InventoryObject || item is InventoryAttachment)
268                     {
269                         if (!IsAttached(myAtt, item))
270                         {
271                             Client.Appearance.Attach(item, AttachmentPoint.Default, false);
272                         }
273                     }
274                 }
275             }
276         }
277         #endregion Private methods
278
279         #region Public methods
280         /// <summary>
281         /// Get COF contents
282         /// </summary>
283         /// <returns>List if InventoryItems that can be part of appearance (attachments, wearables)</returns>
284         public List<InventoryItem> ContentLinks()
285         {
286             List<InventoryItem> ret = new List<InventoryItem>();
287             if (COF == null) return ret;
288
289             Client.Inventory.Store.GetContents(COF)
290                 .FindAll(b => CanBeWorn(b) && ((InventoryItem)b).AssetType == AssetType.Link)
291                 .ForEach(item => ret.Add((InventoryItem)item));
292
293             return ret;
294         }
295
296         /// <summary>
297         /// Get inventory ID of a prim
298         /// </summary>
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)
302         {
303             if (prim.NameValues == null) return UUID.Zero;
304
305             for (int i = 0; i < prim.NameValues.Length; i++)
306             {
307                 if (prim.NameValues[i].Name == "AttachItemID")
308                 {
309                     return (UUID)prim.NameValues[i].Value.ToString();
310                 }
311             }
312             return UUID.Zero;
313         }
314
315         /// <summary>
316         /// Is an inventory item currently attached
317         /// </summary>
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)
322         {
323             foreach (Primitive prim in attachments)
324             {
325                 if (GetAttachmentItem(prim) == item.UUID)
326                 {
327                     return true;
328                 }
329             }
330
331             return false;
332         }
333
334         /// <summary>
335         /// Checks if inventory item of Wearable type is worn
336         /// </summary>
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)
341         {
342             foreach (var n in currentlyWorn.Values)
343             {
344                 if (n.ItemID == item.UUID)
345                 {
346                     return true;
347                 }
348             }
349             return false;
350         }
351
352         /// <summary>
353         /// Can this inventory type be worn
354         /// </summary>
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)
358         {
359             return item is InventoryWearable || item is InventoryAttachment || item is InventoryObject;
360         }
361
362         /// <summary>
363         /// Attach an inventory item
364         /// </summary>
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)
369         {
370             Client.Appearance.Attach(item, point, replace);
371             AddLink(item);
372         }
373
374         /// <summary>
375         /// Creates a new COF link
376         /// </summary>
377         /// <param name="item">Original item to be linked from COF</param>
378         public void AddLink(InventoryItem item)
379         {
380             if (item.InventoryType == InventoryType.Wearable && !IsBodyPart(item))
381             {
382                 InventoryWearable w = (InventoryWearable)item;
383                 int layer = 0;
384                 string desc = string.Format("@{0}{1:00}", (int)w.WearableType, layer);
385                 AddLink(item, desc);
386             }
387             else
388             {
389                 AddLink(item, string.Empty);
390             }
391         }
392
393         /// <summary>
394         /// Creates a new COF link
395         /// </summary>
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)
399         {
400             if (COF == null) return;
401
402             bool linkExists = false;
403
404             linkExists = null != ContentLinks().Find(itemLink => itemLink.AssetUUID == item.UUID);
405
406             if (!linkExists)
407             {
408                 Client.Inventory.CreateLink(COF.UUID, item.UUID, item.Name, newDescription, AssetType.Link, item.InventoryType, UUID.Random(), (success, newItem) =>
409                 {
410                     if (success)
411                     {
412                         Client.Inventory.RequestFetchInventory(newItem.UUID, newItem.OwnerID);
413                     }
414                 });
415             }
416         }
417
418         /// <summary>
419         /// Remove a link to specified inventory item
420         /// </summary>
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)
423         {
424             RemoveLink(new List<UUID>(1) { itemID });
425         }
426
427         /// <summary>
428         /// Remove a link to specified inventory item
429         /// </summary>
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)
432         {
433             if (COF == null) return;
434
435             List<UUID> toRemove = new List<UUID>();
436
437             foreach (UUID itemID in itemIDs)
438             {
439                 var links = ContentLinks().FindAll(itemLink => itemLink.AssetUUID == itemID);
440                 links.ForEach(item => toRemove.Add(item.UUID));
441             }
442
443             Client.Inventory.Remove(toRemove, null);
444         }
445
446         /// <summary>
447         /// Remove attachment
448         /// </summary>
449         /// <param name="item">>Inventory item to be detached</param>
450         public void Detach(InventoryItem item)
451         {
452             Client.Appearance.Detach(item);
453             RemoveLink(item.UUID);
454         }
455
456         public List<InventoryItem> GetWornAt(WearableType type)
457         {
458             var ret = new List<InventoryItem>();
459             ContentLinks().ForEach(link =>
460             {
461                 var item = RealInventoryItem(link);
462                 if (item is InventoryWearable)
463                 {
464                     var w = (InventoryWearable)item;
465                     if (w.WearableType == type)
466                     {
467                         ret.Add(item);
468                     }
469                 }
470             });
471
472             return ret;
473         }
474
475         /// <summary>
476         /// Resolves inventory links and returns a real inventory item that
477         /// the link is pointing to
478         /// </summary>
479         /// <param name="item"></param>
480         /// <returns></returns>
481         public InventoryItem RealInventoryItem(InventoryItem item)
482         {
483             if (item.IsLink() && Client.Inventory.Store.Contains(item.AssetUUID) && Client.Inventory.Store[item.AssetUUID] is InventoryItem)
484             {
485                 return (InventoryItem)Client.Inventory.Store[item.AssetUUID];
486             }
487
488             return item;
489         }
490
491         /// <summary>
492         /// Replaces the current outfit and updates COF links accordingly
493         /// </summary>
494         /// <param name="outfit">List of new wearables and attachments that comprise the new outfit</param>
495         public void ReplaceOutfit(List<InventoryItem> newOutfit)
496         {
497             // Resolve inventory links
498             List<InventoryItem> outfit = new List<InventoryItem>();
499             foreach (var item in newOutfit)
500             {
501                 outfit.Add(RealInventoryItem(item));
502             }
503
504             // Remove links to all exiting items
505             List<UUID> toRemove = new List<UUID>();
506             ContentLinks().ForEach(item =>
507             {
508                 if (IsBodyPart(item))
509                 {
510                     WearableType linkType = ((InventoryWearable)RealInventoryItem(item)).WearableType;
511                     bool hasBodyPart = false;
512
513                     foreach (var newItemTmp in newOutfit)
514                     {
515                         var newItem = RealInventoryItem(newItemTmp);
516                         if (IsBodyPart(newItem))
517                         {
518                             if (((InventoryWearable)newItem).WearableType == linkType)
519                             {
520                                 hasBodyPart = true;
521                                 break;
522                             }
523                         }
524                     }
525
526                     if (hasBodyPart)
527                     {
528                         toRemove.Add(item.UUID);
529                     }
530                 }
531                 else
532                 {
533                     toRemove.Add(item.UUID);
534                 }
535             });
536
537             Client.Inventory.Remove(toRemove, null);
538
539             // Add links to new items
540             List<InventoryItem> newItems = outfit.FindAll(item => CanBeWorn(item));
541             foreach (var item in newItems)
542             {
543                 AddLink(item);
544             }
545
546             Client.Appearance.ReplaceOutfit(outfit);
547             WorkPool.QueueUserWorkItem(sync =>
548             {
549                 Thread.Sleep(2000);
550                 Client.Appearance.RequestSetAppearance(true);
551             });
552         }
553
554         /// <summary>
555         /// Add items to current outfit
556         /// </summary>
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)
560         {
561             AddToOutfit(new List<InventoryItem>(1) { item }, replace);
562         }
563
564         /// <summary>
565         /// Add items to current outfit
566         /// </summary>
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)
570         {
571             List<InventoryItem> current = ContentLinks();
572             List<UUID> toRemove = new List<UUID>();
573
574             // Resolve inventory links and remove wearables of the same type from COF
575             List<InventoryItem> outfit = new List<InventoryItem>();
576
577             foreach (var item in items)
578             {
579                 InventoryItem realItem = RealInventoryItem(item);
580                 if (realItem is InventoryWearable)
581                 {
582                     foreach (var link in current)
583                     {
584                         var currentItem = RealInventoryItem(link);
585                         if (link.AssetUUID == item.UUID)
586                         {
587                             toRemove.Add(link.UUID);
588                         }
589                         else if (currentItem is InventoryWearable)
590                         {
591                             var w = (InventoryWearable)currentItem;
592                             if (w.WearableType == ((InventoryWearable)realItem).WearableType)
593                             {
594                                 toRemove.Add(link.UUID);
595                             }
596                         }
597                     }
598                 }
599
600                 outfit.Add(realItem);
601             }
602             Client.Inventory.Remove(toRemove, null);
603
604             // Add links to new items
605             List<InventoryItem> newItems = outfit.FindAll(item => CanBeWorn(item));
606             foreach (var item in newItems)
607             {
608                 AddLink(item);
609             }
610
611             Client.Appearance.AddToOutfit(outfit, replace);
612             WorkPool.QueueUserWorkItem(sync =>
613             {
614                 Thread.Sleep(2000);
615                 Client.Appearance.RequestSetAppearance(true);
616             });
617         }
618
619         /// <summary>
620         /// Remove an item from the current outfit
621         /// </summary>
622         /// <param name="items">Item to remove</param>
623         public void RemoveFromOutfit(InventoryItem item)
624         {
625             RemoveFromOutfit(new List<InventoryItem>(1) { item });
626         }
627
628         /// <summary>
629         /// Remove specified items from the current outfit
630         /// </summary>
631         /// <param name="items">List of items to remove</param>
632         public void RemoveFromOutfit(List<InventoryItem> items)
633         {
634             // Resolve inventory links
635             List<InventoryItem> outfit = new List<InventoryItem>();
636             foreach (var item in items)
637             {
638                 var realItem = RealInventoryItem(item);
639                 if (Instance.RLV.AllowDetach(realItem))
640                 {
641                     outfit.Add(realItem);
642                 }
643             }
644
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)))
648             {
649                 toRemove.Add(item.UUID);
650             }
651             RemoveLink(toRemove);
652
653             Client.Appearance.RemoveFromOutfit(outfit);
654         }
655
656         public bool IsBodyPart(InventoryItem item)
657         {
658             var realItem = RealInventoryItem(item);
659             if (realItem is InventoryWearable)
660             {
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)
667                 {
668                     return true;
669                 }
670             }
671             return false;
672         }
673
674         /// <summary>
675         /// Force rebaking textures
676         /// </summary>
677         public void RebakeTextures()
678         {
679             Client.Appearance.RequestSetAppearance(true);
680         }
681
682         #endregion Public methods
683     }
684 }