corrade-vassal – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 1 /*
2 * ClientAO.cs: GridProxy application that acts as a client side animation overrider.
3 * The application will start and stop animations corresponding to the movements
4 * of the avatar on screen.
5 *
6 * Copyright (c) 2007 Gilbert Roulot
7 * All rights reserved.
8 *
9 * - Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions are met:
11 *
12 * - Redistributions of source code must retain the above copyright notice, this
13 * list of conditions and the following disclaimer.
14 * - Neither the name of the Second Life Reverse Engineering Team nor the names
15 * of its 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
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
29 */
30  
31 using System;
32 using System.Collections;
33 using System.Collections.Generic;
34 using System.Net;
35 using System.IO;
36 using System.Reflection;
37 using OpenMetaverse;
38 using OpenMetaverse.Packets;
39 using OpenMetaverse.StructuredData;
40 using Nwc.XmlRpc;
41 using GridProxy;
42  
43  
44 public class ClientAO : ProxyPlugin
45 {
46 private ProxyFrame frame;
47 private Proxy proxy;
48 private UUID[] wetikonanims = {
49 Animations.WALK,
50 Animations.RUN,
51 Animations.CROUCHWALK,
52 Animations.FLY,
53 Animations.TURNLEFT,
54 Animations.TURNRIGHT,
55 Animations.JUMP,
56 Animations.HOVER_UP,
57 Animations.CROUCH,
58 Animations.HOVER_DOWN,
59 Animations.STAND,
60 Animations.STAND_1,
61 Animations.STAND_2,
62 Animations.STAND_3,
63 Animations.STAND_4,
64 Animations.HOVER,
65 Animations.SIT,
66 Animations.PRE_JUMP,
67 Animations.FALLDOWN,
68 Animations.LAND,
69 Animations.STANDUP,
70 Animations.FLYSLOW,
71 Animations.SIT_GROUND_staticRAINED,
72 UUID.Zero, //swimming doesnt exist
73 UUID.Zero,
74 UUID.Zero,
75 UUID.Zero
76 };
77  
78 private string[] wetikonanimnames = {
79 "walk",
80 "run",
81 "crouch walk",
82 "fly",
83 "turn left",
84 "turn right",
85 "jump",
86 "hover up",
87 "crouch",
88 "hover down",
89 "stand",
90 "stand 2",
91 "stand 3",
92 "stand 4",
93 "stand 5",
94 "hover",
95 "sit",
96 "pre jump",
97 "fall down",
98 "land",
99 "stand up",
100 "fly slow",
101 "sit on ground",
102 "swim (ignored)", //swimming doesnt exist
103 "swim (ignored)",
104 "swim (ignored)",
105 "swim (ignored)"
106 };
107  
108 private Dictionary<UUID, string> animuid2name;
109  
110 //private Assembly libslAssembly;
111  
112 #region Packet delegates members
113 private PacketDelegate _packetDelegate;
114 private PacketDelegate packetDelegate
115 {
116 get
117 {
118 if (_packetDelegate == null)
119 {
120 _packetDelegate = new PacketDelegate(AnimationPacketHandler);
121 }
122 return _packetDelegate;
123 }
124 }
125  
126 private PacketDelegate _inventoryPacketDelegate;
127 private PacketDelegate inventoryPacketDelegate
128 {
129 get
130 {
131 if (_inventoryPacketDelegate == null)
132 {
133 _inventoryPacketDelegate = new PacketDelegate(InventoryDescendentsHandler);
134 }
135 return _inventoryPacketDelegate;
136 }
137 }
138  
139 private PacketDelegate _transferPacketDelegate;
140 private PacketDelegate transferPacketDelegate
141 {
142 get
143 {
144 if (_transferPacketDelegate == null)
145 {
146 _transferPacketDelegate = new PacketDelegate(TransferPacketHandler);
147 }
148 return _transferPacketDelegate;
149 }
150 }
151  
152 // private PacketDelegate _transferInfoDelegate;
153 // private PacketDelegate transferInfoDelegate
154 // {
155 // get
156 // {
157 // if (_transferInfoDelegate == null)
158 // {
159 // _transferInfoDelegate = new PacketDelegate(TransferInfoHandler);
160 // }
161 // return _transferInfoDelegate;
162 // }
163 // }
164 #endregion
165  
166  
167 //map of built in SL animations and their overrides
168 private Dictionary<UUID,UUID> overrides = new Dictionary<UUID,UUID>();
169  
170 //list of animations currently running
171 private Dictionary<UUID, int> SignaledAnimations = new Dictionary<UUID, int>();
172  
173 //playing status of animations'override animation
174 private Dictionary<UUID, bool> overrideanimationisplaying;
175  
176 //Current inventory path search
177 string[] searchPath;
178 //Search level
179 int searchLevel;
180 //Current folder
181 UUID currentFolder;
182 // Number of directory descendents received
183 int nbdescendantsreceived;
184 //List of items in the current folder
185 Dictionary<string,InventoryItem> currentFolderItems;
186 //Asset download request ID
187 UUID assetdownloadID;
188 //Downloaded bytes so far
189 int downloadedbytes;
190 //size of download
191 int downloadsize;
192 //data buffer
193 byte[] buffer;
194  
195  
196 public ClientAO(ProxyFrame frame)
197 {
198 this.frame = frame;
199 this.proxy = frame.proxy;
200 }
201  
202 //Initialise the plugin
203 public override void Init()
204 {
205 //libslAssembly = Assembly.Load("libsecondlife");
206 //if (libslAssembly == null) throw new Exception("Assembly load exception");
207  
208 // build the table of /command delegates
209 InitializeCommandDelegates();
210  
211 SayToUser("ClientAO loaded");
212 }
213  
214 // InitializeCommandDelegates: configure ClientAO's commands
215 private void InitializeCommandDelegates()
216 {
217 //The ClientAO responds to command beginning with /ao
218 frame.AddCommand("/ao", new ProxyFrame.CommandDelegate(CmdAO));
219 }
220  
221 //Process commands from the user
222 private void CmdAO(string[] words) {
223 if (words.Length < 2)
224 {
225 SayToUser("Usage: /ao on/off/notecard path");
226 }
227 else if (words[1] == "on")
228 {
229 //Turn AO on
230 AOOn();
231 SayToUser("AO started");
232 }
233 else if (words[1] == "off")
234 {
235 //Turn AO off
236 AOOff();
237 SayToUser("AO stopped");
238 }
239 else
240 {
241 //Load notecard from path
242 //exemple: /ao Objects/My AOs/wetikon/config.txt
243 string[] tmp = new string[words.Length - 1];
244 //join the arguments together with spaces, to
245 //take care of folder and item names with spaces in them
246 for (int i = 1; i < words.Length; i++)
247 {
248 tmp[i - 1] = words[i];
249 }
250 // add a delegate to monitor inventory infos
251 proxy.AddDelegate(PacketType.InventoryDescendents, Direction.Incoming, this.inventoryPacketDelegate);
252 RequestFindObjectByPath(frame.InventoryRoot, String.Join(" ", tmp));
253 }
254 }
255  
256 private void AOOn()
257 {
258 // add a delegate to track agent movements
259 proxy.AddDelegate(PacketType.AvatarAnimation, Direction.Incoming, this.packetDelegate);
260 }
261  
262 private void AOOff()
263 {
264 // remove the delegate to track agent movements
265 proxy.RemoveDelegate(PacketType.AvatarAnimation, Direction.Incoming, this.packetDelegate);
266 //Stop all override animations
267 foreach (UUID tmp in overrides.Values)
268 {
269 Animate(tmp, false);
270 }
271 }
272  
273 // Inventory functions
274  
275 //start requesting an item by its path
276 public void RequestFindObjectByPath(UUID baseFolder, string path)
277 {
278 if (path == null || path.Length == 0)
279 throw new ArgumentException("Empty path is not supported");
280 currentFolder = baseFolder;
281 //split path by '/'
282 searchPath = path.Split('/');
283 //search for first element in the path
284 searchLevel = 0;
285  
286 // Start the search
287 RequestFolderContents(baseFolder,
288 true,
289 (searchPath.Length == 1) ? true : false,
290 InventorySortOrder.ByName);
291 }
292  
293 //request a folder content
294 public void RequestFolderContents(UUID folder, bool folders, bool items,
295 InventorySortOrder order)
296 {
297 //empty the dictionnary containing current folder items by name
298 currentFolderItems = new Dictionary<string, InventoryItem>();
299 //reset the number of descendants received
300 nbdescendantsreceived = 0;
301 //build a packet to request the content
302 FetchInventoryDescendentsPacket fetch = new FetchInventoryDescendentsPacket();
303 fetch.AgentData.AgentID = frame.AgentID;
304 fetch.AgentData.SessionID = frame.SessionID;
305  
306 fetch.InventoryData.FetchFolders = folders;
307 fetch.InventoryData.FetchItems = items;
308 fetch.InventoryData.FolderID = folder;
309 fetch.InventoryData.OwnerID = frame.AgentID; //is it correct?
310 fetch.InventoryData.SortOrder = (int)order;
311  
312 //send packet to SL
313 proxy.InjectPacket(fetch, Direction.Outgoing);
314 }
315  
316 //process the reply from SL
317 private Packet InventoryDescendentsHandler(Packet packet, IPEndPoint sim)
318 {
319 bool intercept = false;
320 InventoryDescendentsPacket reply = (InventoryDescendentsPacket)packet;
321  
322 if (reply.AgentData.Descendents > 0
323 && reply.AgentData.FolderID == currentFolder)
324 {
325 //SayToUser("nb descendents: " + reply.AgentData.Descendents);
326 //this packet concerns the folder we asked for
327 if (reply.FolderData[0].FolderID != UUID.Zero
328 && searchLevel < searchPath.Length - 1)
329 {
330 nbdescendantsreceived += reply.FolderData.Length;
331 //SayToUser("nb received: " + nbdescendantsreceived);
332 //folders are present, and we are not at end of path.
333 //look at them
334 for (int i = 0; i < reply.FolderData.Length; i++)
335 {
336 //SayToUser("Folder: " + Utils.BytesToString(reply.FolderData[i].Name));
337 if (searchPath[searchLevel] == Utils.BytesToString(reply.FolderData[i].Name)) {
338 //We found the next folder in the path
339 currentFolder = reply.FolderData[i].FolderID;
340 if (searchLevel < searchPath.Length - 1)
341 {
342 // ask for next item in path
343 searchLevel++;
344 RequestFolderContents(currentFolder,
345 true,
346 (searchLevel < searchPath.Length - 1) ? false : true,
347 InventorySortOrder.ByName);
348 //Jump to end
349 goto End;
350 }
351 }
352 }
353 if (nbdescendantsreceived >= reply.AgentData.Descendents)
354 {
355 //We have not found the folder. The user probably mistyped it
356 SayToUser("Didn't find folder " + searchPath[searchLevel]);
357 //Stop looking at packets
358 proxy.RemoveDelegate(PacketType.InventoryDescendents, Direction.Incoming, this.inventoryPacketDelegate);
359 }
360 }
361 else if (searchLevel < searchPath.Length - 1)
362 {
363 //There are no folders in the packet ; but we are looking for one!
364 //We have not found the folder. The user probably mistyped it
365 SayToUser("Didn't find folder " + searchPath[searchLevel]);
366 //Stop looking at packets
367 proxy.RemoveDelegate(PacketType.InventoryDescendents, Direction.Incoming, this.inventoryPacketDelegate);
368 }
369 else
370 {
371 //There are folders in the packet. And we are at the end of
372 //the path, count their number in nbdescendantsreceived
373 nbdescendantsreceived += reply.FolderData.Length;
374 //SayToUser("nb received: " + nbdescendantsreceived);
375 }
376 if (reply.ItemData[0].ItemID != UUID.Zero
377 && searchLevel == searchPath.Length - 1)
378 {
379 //there are items returned and we are looking for one
380 //(end of search path)
381 //count them
382 nbdescendantsreceived += reply.ItemData.Length;
383 //SayToUser("nb received: " + nbdescendantsreceived);
384 for (int i = 0; i < reply.ItemData.Length; i++)
385 {
386 //we are going to store info on all items. we'll need
387 //it to get the asset ID of animations refered to by the
388 //configuration notecard
389 if (reply.ItemData[i].ItemID != UUID.Zero)
390 {
391 InventoryItem item = CreateInventoryItem((InventoryType)reply.ItemData[i].InvType, reply.ItemData[i].ItemID);
392 item.ParentUUID = reply.ItemData[i].FolderID;
393 item.CreatorID = reply.ItemData[i].CreatorID;
394 item.AssetType = (AssetType)reply.ItemData[i].Type;
395 item.AssetUUID = reply.ItemData[i].AssetID;
396 item.CreationDate = Utils.UnixTimeToDateTime((uint)reply.ItemData[i].CreationDate);
397 item.Description = Utils.BytesToString(reply.ItemData[i].Description);
398 item.Flags = (uint)reply.ItemData[i].Flags;
399 item.Name = Utils.BytesToString(reply.ItemData[i].Name);
400 item.GroupID = reply.ItemData[i].GroupID;
401 item.GroupOwned = reply.ItemData[i].GroupOwned;
402 item.Permissions = new Permissions(
403 reply.ItemData[i].BaseMask,
404 reply.ItemData[i].EveryoneMask,
405 reply.ItemData[i].GroupMask,
406 reply.ItemData[i].NextOwnerMask,
407 reply.ItemData[i].OwnerMask);
408 item.SalePrice = reply.ItemData[i].SalePrice;
409 item.SaleType = (SaleType)reply.ItemData[i].SaleType;
410 item.OwnerID = reply.AgentData.OwnerID;
411  
412 //SayToUser("item in folder: " + item.Name);
413  
414 //Add the item to the name -> item hash
415 currentFolderItems.Add(item.Name, item);
416 }
417 }
418 if (nbdescendantsreceived >= reply.AgentData.Descendents)
419 {
420 //We have received all the items in the last folder
421 //Let's look for the item we are looking for
422 if (currentFolderItems.ContainsKey(searchPath[searchLevel]))
423 {
424 //We found what we where looking for
425 //Stop looking at packets
426 proxy.RemoveDelegate(PacketType.InventoryDescendents, Direction.Incoming, this.inventoryPacketDelegate);
427 //Download the notecard
428 assetdownloadID = RequestInventoryAsset(currentFolderItems[searchPath[searchLevel]]);
429 }
430 else
431 {
432 //We didnt find the item, the user probably mistyped its name
433 SayToUser("Didn't find notecard " + searchPath[searchLevel]);
434 //TODO: keep looking for a moment, or else reply packets may still
435 //come in case of a very large inventory folder
436 //Stop looking at packets
437 proxy.RemoveDelegate(PacketType.InventoryDescendents, Direction.Incoming, this.inventoryPacketDelegate);
438 }
439 }
440 }
441 else if (searchLevel == searchPath.Length - 1 && nbdescendantsreceived >= reply.AgentData.Descendents)
442 {
443 //There are no items in the packet, but we are looking for one!
444 //We didnt find the item, the user probably mistyped its name
445 SayToUser("Didn't find notecard " + searchPath[searchLevel]);
446 //TODO: keep looking for a moment, or else reply packets may still
447 //come in case of a very large inventory folder
448 //Stop looking at packets
449 proxy.RemoveDelegate(PacketType.InventoryDescendents, Direction.Incoming, this.inventoryPacketDelegate);
450 }
451 //Intercept the packet, it was a reply to our request. No need
452 //to confuse the actual SL client
453 intercept = true;
454 }
455 End:
456 if (intercept)
457 {
458 //stop packet
459 return null;
460 }
461 else
462 {
463 //let packet go to client
464 return packet;
465 }
466 }
467  
468 public static InventoryItem CreateInventoryItem(InventoryType type, UUID id)
469 {
470 switch (type)
471 {
472 case InventoryType.Texture: return new InventoryTexture(id);
473 case InventoryType.Sound: return new InventorySound(id);
474 case InventoryType.CallingCard: return new InventoryCallingCard(id);
475 case InventoryType.Landmark: return new InventoryLandmark(id);
476 case InventoryType.Object: return new InventoryObject(id);
477 case InventoryType.Notecard: return new InventoryNotecard(id);
478 case InventoryType.Category: return new InventoryCategory(id);
479 case InventoryType.LSL: return new InventoryLSL(id);
480 case InventoryType.Snapshot: return new InventorySnapshot(id);
481 case InventoryType.Attachment: return new InventoryAttachment(id);
482 case InventoryType.Wearable: return new InventoryWearable(id);
483 case InventoryType.Animation: return new InventoryAnimation(id);
484 case InventoryType.Gesture: return new InventoryGesture(id);
485 default: return new InventoryItem(type, id);
486 }
487 }
488  
489 //Ask for download of an item
490 public UUID RequestInventoryAsset(InventoryItem item)
491 {
492 // Build the request packet and send it
493 TransferRequestPacket request = new TransferRequestPacket();
494 request.TransferInfo.ChannelType = (int)ChannelType.Asset;
495 request.TransferInfo.Priority = 101.0f;
496 request.TransferInfo.SourceType = (int)SourceType.SimInventoryItem;
497 UUID transferID = UUID.Random();
498 request.TransferInfo.TransferID = transferID;
499  
500 byte[] paramField = new byte[100];
501 Buffer.BlockCopy(frame.AgentID.GetBytes(), 0, paramField, 0, 16);
502 Buffer.BlockCopy(frame.SessionID.GetBytes(), 0, paramField, 16, 16);
503 Buffer.BlockCopy(item.OwnerID.GetBytes(), 0, paramField, 32, 16);
504 Buffer.BlockCopy(UUID.Zero.GetBytes(), 0, paramField, 48, 16);
505 Buffer.BlockCopy(item.UUID.GetBytes(), 0, paramField, 64, 16);
506 Buffer.BlockCopy(item.AssetUUID.GetBytes(), 0, paramField, 80, 16);
507 Buffer.BlockCopy(Utils.IntToBytes((int)item.AssetType), 0, paramField, 96, 4);
508 request.TransferInfo.Params = paramField;
509  
510 // add a delegate to monitor configuration notecards download
511 proxy.AddDelegate(PacketType.TransferPacket, Direction.Incoming, this.transferPacketDelegate);
512  
513 //send packet to SL
514 proxy.InjectPacket(request, Direction.Outgoing);
515  
516 //so far we downloaded 0 bytes
517 downloadedbytes = 0;
518 //the total size of the download is yet unknown
519 downloadsize = 0;
520 //A 100K buffer should be enough for everyone
521 buffer = new byte[1024 * 100];
522 //Return the transfer ID
523 return transferID;
524 }
525  
526 // SayToUser: send a message to the user as in-world chat
527 private void SayToUser(string message)
528 {
529 ChatFromSimulatorPacket packet = new ChatFromSimulatorPacket();
530 packet.ChatData.FromName = Utils.StringToBytes("ClientAO");
531 packet.ChatData.SourceID = UUID.Random();
532 packet.ChatData.OwnerID = frame.AgentID;
533 packet.ChatData.SourceType = (byte)2;
534 packet.ChatData.ChatType = (byte)1;
535 packet.ChatData.Audible = (byte)1;
536 packet.ChatData.Position = new Vector3(0, 0, 0);
537 packet.ChatData.Message = Utils.StringToBytes(message);
538 proxy.InjectPacket(packet, Direction.Incoming);
539 }
540  
541 //start or stop an animation
542 public void Animate(UUID animationuuid, bool run)
543 {
544 AgentAnimationPacket animate = new AgentAnimationPacket();
545 animate.Header.Reliable = true;
546 animate.AgentData.AgentID = frame.AgentID;
547 animate.AgentData.SessionID = frame.SessionID;
548 //We send one animation
549 animate.AnimationList = new AgentAnimationPacket.AnimationListBlock[1];
550 animate.AnimationList[0] = new AgentAnimationPacket.AnimationListBlock();
551 animate.AnimationList[0].AnimID = animationuuid;
552 animate.AnimationList[0].StartAnim = run;
553  
554 animate.PhysicalAvatarEventList = new AgentAnimationPacket.PhysicalAvatarEventListBlock[0];
555  
556 //SayToUser("anim " + animname(animationuuid) + " " + run);
557 proxy.InjectPacket(animate, Direction.Outgoing);
558 }
559  
560 //return the name of an animation by its UUID
561 // private string animname(UUID arg)
562 // {
563 // return animuid2name[arg];
564 // }
565  
566 //handle animation packets from simulator
567 private Packet AnimationPacketHandler(Packet packet, IPEndPoint sim) {
568 AvatarAnimationPacket animation = (AvatarAnimationPacket)packet;
569  
570 if (animation.Sender.ID == frame.AgentID)
571 {
572 //the received animation packet is about our Agent, handle it
573 lock (SignaledAnimations)
574 {
575 // Reset the signaled animation list
576 SignaledAnimations.Clear();
577 //fill it with the fresh list from simulator
578 for (int i = 0; i < animation.AnimationList.Length; i++)
579 {
580 UUID animID = animation.AnimationList[i].AnimID;
581 int sequenceID = animation.AnimationList[i].AnimSequenceID;
582  
583 // Add this animation to the list of currently signaled animations
584 SignaledAnimations[animID] = sequenceID;
585 //SayToUser("Animation: " + animname(animID));
586 }
587 }
588  
589 //we now have a list of currently running animations
590 //Start override animations if necessary
591 foreach (UUID key in overrides.Keys)
592 {
593 //For each overriden animation key, test if its override is running
594 if (SignaledAnimations.ContainsKey(key) && (!overrideanimationisplaying[key] ))
595 {
596 //An overriden animation is present and its override animation
597 //isnt currently playing
598 //Start the override animation
599 //SayToUser("animation " + animname(key) + " started, will override with " + animname(overrides[key]));
600 overrideanimationisplaying[key] = true;
601 Animate(overrides[key], true);
602 }
603 else if ((!SignaledAnimations.ContainsKey(key)) && overrideanimationisplaying[key])
604 {
605 //an override animation is currently playing, but it's overriden
606 //animation is not.
607 //stop the override animation
608 //SayToUser("animation " + animname(key) + " stopped, will override with " + animname(overrides[key]));
609 overrideanimationisplaying[key] = false;
610 Animate(overrides[key], false);
611 }
612 }
613 }
614 //Let the packet go to the client
615 return packet;
616 }
617  
618 //handle packets that contain info about the notecard data transfer
619 // private Packet TransferInfoHandler(Packet packet, IPEndPoint simulator)
620 // {
621 // TransferInfoPacket info = (TransferInfoPacket)packet;
622 //
623 // if (info.TransferInfo.TransferID == assetdownloadID)
624 // {
625 // //this is our requested tranfer, handle it
626 // downloadsize = info.TransferInfo.Size;
627 //
628 // if ((StatusCode)info.TransferInfo.Status != StatusCode.OK)
629 // {
630 // SayToUser("Failed to read notecard");
631 // }
632 // if (downloadedbytes >= downloadsize)
633 // {
634 // //Download already completed!
635 // downloadCompleted();
636 // }
637 // //intercept packet
638 // return null;
639 // }
640 // return packet;
641 // }
642  
643 //handle packets which contain the notecard data
644 private Packet TransferPacketHandler(Packet packet, IPEndPoint simulator)
645 {
646 TransferPacketPacket asset = (TransferPacketPacket)packet;
647  
648 if (asset.TransferData.TransferID == assetdownloadID) {
649 Buffer.BlockCopy(asset.TransferData.Data, 0, buffer, 1000 * asset.TransferData.Packet,
650 asset.TransferData.Data.Length);
651 downloadedbytes += asset.TransferData.Data.Length;
652  
653 // Check if we downloaded the full asset
654 if (downloadedbytes >= downloadsize)
655 {
656 downloadCompleted();
657 }
658 //Intercept packet
659 return null;
660 }
661 return packet;
662 }
663  
664 private void downloadCompleted()
665 {
666 //We have the notecard.
667 //Stop looking at transfer packets
668 proxy.RemoveDelegate(PacketType.TransferPacket, Direction.Incoming, this.transferPacketDelegate);
669 //crop the buffer size
670 byte[] tmp = new byte[downloadedbytes];
671 Buffer.BlockCopy(buffer, 0, tmp, 0, downloadedbytes);
672 buffer = tmp;
673 String notecardtext = getNotecardText(Utils.BytesToString(buffer));
674  
675 //Load config, wetikon format
676 loadWetIkon(notecardtext);
677 }
678  
679 private void loadWetIkon(string config)
680 {
681 //Reinitialise override table
682 overrides = new Dictionary<UUID,UUID>();
683 overrideanimationisplaying = new Dictionary<UUID, bool>();
684  
685 animuid2name = new Dictionary<UUID,string>();
686 foreach (UUID key in wetikonanims )
687 {
688 animuid2name[key] = wetikonanimnames[Array.IndexOf(wetikonanims, key)];
689 }
690  
691 //list of animations in wetikon
692  
693 //read every second line in the config
694 char[] sep = { '\n' };
695 string[] lines = config.Split(sep);
696 int length = lines.Length;
697 int i = 1;
698 while (i < length) {
699 //Read animation name and look it up
700 string animname = lines[i].Trim();
701 //SayToUser("anim: " + animname);
702 if (animname != "")
703 {
704 if (currentFolderItems.ContainsKey(animname))
705 {
706 UUID over = currentFolderItems[animname].AssetUUID;
707 UUID orig = wetikonanims[((i + 1) / 2) - 1];
708 //put it in overrides
709 animuid2name[over] = animname;
710 overrides[orig] = over;
711 overrideanimationisplaying[orig] = false;
712 //SayToUser(wetikonanimnames[((i + 1) / 2) - 1] + " overriden by " + animname + " ( " + over + ")");
713 }
714 else
715 {
716 //Not found
717 SayToUser(animname + " not found.");
718 }
719 }
720 i += 2;
721 }
722 SayToUser("Notecard read, " + overrides.Count + " animations found");
723 }
724  
725 private string getNotecardText(string data)
726 {
727 // Version 1 format:
728 // Linden text version 1
729 // {
730 // <EmbeddedItemList chunk>
731 // Text length
732 // <ASCII text; 0x80 | index = embedded item>
733 // }
734  
735 // Version 2 format: (NOTE: Imports identically to version 1)
736 // Linden text version 2
737 // {
738 // <EmbeddedItemList chunk>
739 // Text length
740 // <UTF8 text; FIRST_EMBEDDED_CHAR + index = embedded item>
741 // }
742 int i = 0;
743 char[] sep = { '\n' };
744 string[] lines = data.Split(sep);
745 int length = lines.Length;
746 string result = "";
747  
748 //check format
749 if (!lines[i].StartsWith("Linden text version "))
750 {
751 SayToUser("error");
752 return "";
753 }
754  
755 //{
756 i++;
757 if (lines[i] != "{")
758 {
759 SayToUser("error");
760 return "";
761 }
762  
763 i++;
764 if (lines[i] != "LLEmbeddedItems version 1")
765 {
766 SayToUser("error");
767 return "";
768 }
769  
770 //{
771 i++;
772 if (lines[i] != "{")
773 {
774 SayToUser("error");
775 return "";
776 }
777  
778 //count ...
779 i++;
780 if (!lines[i].StartsWith("count "))
781 {
782 SayToUser("error");
783 return "";
784 }
785  
786 //}
787 i++;
788 if (lines[i] != "}")
789 {
790 SayToUser("error");
791 return "";
792 }
793  
794 //Text length ...
795 i++;
796 if (!lines[i].StartsWith("Text length "))
797 {
798 SayToUser("error");
799 return "";
800 }
801  
802 i++;
803 while (i < length)
804 {
805 result += lines[i] + "\n";
806 i++;
807 }
808 result = result.Substring(0, result.Length - 3);
809 return result;
810 }
811 }