corrade-vassal – Blame information for rev 1
?pathlinks?
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 | } |