aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorMW2009-05-30 12:51:28 +0000
committerMW2009-05-30 12:51:28 +0000
commit69817b4d6a08d3f9866796c50a0d0fb0d28ded77 (patch)
tree7e35f8ac4f54d9586a8c2d7a051a079487a66e5e
parent* Tweaks to /WHO listings. (diff)
downloadopensim-SC-69817b4d6a08d3f9866796c50a0d0fb0d28ded77.zip
opensim-SC-69817b4d6a08d3f9866796c50a0d0fb0d28ded77.tar.gz
opensim-SC-69817b4d6a08d3f9866796c50a0d0fb0d28ded77.tar.bz2
opensim-SC-69817b4d6a08d3f9866796c50a0d0fb0d28ded77.tar.xz
Added a AvatarCreationModule to the user server. This handles the "cloning" of a existing avatar's inventory to another avatar's inventory (the base method will also create the inventory for a new avatar if the avatar doesn't alreayd have any inventory).
The code also sets the target avatar as wearing whatever the template avatar was wearing (including attachments). The idea is to allow the use of this to give new avatars a more interesting avatar (and inventory set) than the current default. I have include a console command that will clone the inventory from one existing user (template avatar) to another existing avatar (target avatar). The format of the command is : "clone avatar <templateAvatarFirstName> <templateAvatarLastName> <targetAvatarFirstName> <targetAvatarLastName>" (to use this console command on new accounts, the "create user" command will need to be used first) While the code was designed for hooking up to a remote call from a web interface, I have left the hookup code out of this commit, as I believe most use cases will want to handle it differently. Also added a "trusted handler" to the inventory server to allow the user server to create new folders in a user's inventory. All this is in the old UGAIM servers so will need porting to the new servers.
-rw-r--r--OpenSim/Grid/InventoryServer/Main.cs4
-rw-r--r--OpenSim/Grid/UserServer.Modules/AvatarCreationModule.cs499
-rw-r--r--OpenSim/Grid/UserServer/Main.cs9
3 files changed, 512 insertions, 0 deletions
diff --git a/OpenSim/Grid/InventoryServer/Main.cs b/OpenSim/Grid/InventoryServer/Main.cs
index 7560383..537661e 100644
--- a/OpenSim/Grid/InventoryServer/Main.cs
+++ b/OpenSim/Grid/InventoryServer/Main.cs
@@ -136,6 +136,10 @@ namespace OpenSim.Grid.InventoryServer
136 "POST", "/NewFolder/", m_inventoryService.AddFolder, m_inventoryService.CheckAuthSession)); 136 "POST", "/NewFolder/", m_inventoryService.AddFolder, m_inventoryService.CheckAuthSession));
137 137
138 m_httpServer.AddStreamHandler( 138 m_httpServer.AddStreamHandler(
139 new RestDeserialiseTrustedHandler<InventoryFolderBase, bool>(
140 "POST", "/CreateFolder/", m_inventoryService.AddFolder, m_inventoryService.CheckTrustSource));
141
142 m_httpServer.AddStreamHandler(
139 new RestDeserialiseSecureHandler<InventoryItemBase, bool>( 143 new RestDeserialiseSecureHandler<InventoryItemBase, bool>(
140 "POST", "/NewItem/", m_inventoryService.AddItem, m_inventoryService.CheckAuthSession)); 144 "POST", "/NewItem/", m_inventoryService.AddItem, m_inventoryService.CheckAuthSession));
141 145
diff --git a/OpenSim/Grid/UserServer.Modules/AvatarCreationModule.cs b/OpenSim/Grid/UserServer.Modules/AvatarCreationModule.cs
new file mode 100644
index 0000000..5e6bc85
--- /dev/null
+++ b/OpenSim/Grid/UserServer.Modules/AvatarCreationModule.cs
@@ -0,0 +1,499 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSim Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Collections;
30using System.Collections.Generic;
31using System.Reflection;
32using System.Threading;
33using log4net;
34using Nwc.XmlRpc;
35using OpenMetaverse;
36using OpenSim.Framework;
37using OpenSim.Framework.Console;
38using OpenSim.Framework.Communications;
39using OpenSim.Framework.Servers;
40using OpenSim.Framework.Servers.HttpServer;
41using OpenSim.Grid.Framework;
42
43
44namespace OpenSim.Grid.UserServer.Modules
45{
46 public class AvatarCreationModule
47 {
48 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
49
50 private UserDataBaseService m_userDataBaseService;
51 // private BaseHttpServer m_httpServer;
52 private UserConfig m_config;
53
54 private string m_inventoryServerUrl;
55 private IInterServiceInventoryServices m_inventoryService;
56
57 public AvatarCreationModule(UserDataBaseService userDataBaseService, UserConfig config, IInterServiceInventoryServices inventoryService)
58 {
59 m_config = config;
60 m_userDataBaseService = userDataBaseService;
61 m_inventoryService = inventoryService;
62 m_inventoryServerUrl = config.InventoryUrl.OriginalString;
63 }
64
65 public void Initialise(IGridServiceCore core)
66 {
67 CommandConsole console;
68 if (core.TryGet<CommandConsole>(out console))
69 {
70 console.Commands.AddCommand("userserver", false, "clone avatar",
71 "clone avatar <TemplateAvatarFirstName> <TemplateAvatarLastName> <TargetAvatarFirstName> <TargetAvatarLastName>",
72 "Clone the template avatar inventory into a target avatar", RunCommand);
73 }
74 }
75
76 public void PostInitialise()
77 {
78
79 }
80
81 public void RegisterHandlers(BaseHttpServer httpServer)
82 {
83 }
84
85 public void RunCommand(string module, string[] cmd)
86 {
87 if ((cmd.Length == 6) && (cmd[0] == "clone") && (cmd[1] == "avatar"))
88 {
89 try
90 {
91 string tFirst = cmd[2];
92 string tLast = cmd[3];
93
94 string nFirst = cmd[4];
95 string nLast = cmd[5];
96
97 UserProfileData templateAvatar = m_userDataBaseService.GetUserProfile(tFirst, tLast);
98 UserProfileData newAvatar = m_userDataBaseService.GetUserProfile(nFirst, nLast);
99
100 if (templateAvatar == null)
101 {
102 m_log.ErrorFormat("[AvatarAppearance] Clone Avatar: Could not find template avatar {0} , {1}", tFirst, tLast);
103 return;
104 }
105
106 if (newAvatar == null)
107 {
108 m_log.ErrorFormat("[AvatarAppearance] Clone Avatar: Could not find target avatar {0} , {1}", nFirst, nLast);
109 return;
110 }
111 Guid avatar = newAvatar.ID.Guid;
112 Guid template = templateAvatar.ID.Guid;
113 CloneAvatar(avatar, template);
114
115 }
116 catch (Exception e)
117 {
118 m_log.Error("Error: " + e.ToString());
119 }
120 }
121 }
122 #region Avatar Appearance Creation
123
124 public bool CloneAvatar(Guid avatarID, Guid templateID)
125 {
126 m_log.InfoFormat("[AvatarAppearance] Starting to create avatar's appearance for user {0}", avatarID.ToString());
127 Guid bodyFolder = Guid.Empty;
128 Guid clothesFolder = Guid.Empty;
129 bool success = false;
130
131 UUID avID = new UUID(avatarID);
132 List<InventoryFolderBase> avatarInventory = m_inventoryService.GetInventorySkeleton(avID);
133 if ((avatarInventory == null) || (avatarInventory.Count == 0))
134 {
135 m_log.InfoFormat("[AvatarAppearance] No inventory found for user {0} , so creating it", avID.ToString());
136 m_inventoryService.CreateNewUserInventory(avID);
137 Thread.Sleep(5000);
138 avatarInventory = m_inventoryService.GetInventorySkeleton(avID);
139 }
140
141 if ((avatarInventory != null) && (avatarInventory.Count > 0))
142 {
143 UUID tempOwnID = new UUID(templateID);
144 AvatarAppearance appearance = m_userDataBaseService.GetUserAppearance(tempOwnID);
145
146 List<InventoryFolderBase> templateInventory = m_inventoryService.GetInventorySkeleton(tempOwnID);
147 if ((templateInventory != null) && (templateInventory.Count != 0))
148 {
149 for (int i = 0; i < templateInventory.Count; i++)
150 {
151 if (templateInventory[i].ParentID == UUID.Zero)
152 {
153 success = CloneFolder(avatarInventory, avID, UUID.Zero, appearance, templateInventory[i], templateInventory);
154 break;
155 }
156 }
157 }
158 else
159 {
160 m_log.InfoFormat("[AvatarAppearance] Failed to find the template owner's {0} inventory", tempOwnID);
161 }
162 }
163 m_log.InfoFormat("[AvatarAppearance] finished cloning avatar with result: {0}", success);
164 return success;
165 }
166
167 private void UpdateAvatarAppearance(UUID avatarID, int wearableType, UUID itemID, UUID assetID)
168 {
169 AvatarAppearance appearance = m_userDataBaseService.GetUserAppearance(avatarID);
170 if (appearance == null)
171 {
172 appearance = CreateDefaultAppearance(avatarID);
173 }
174
175 appearance.Wearables[wearableType].AssetID = assetID;
176 appearance.Wearables[wearableType].ItemID = itemID;
177
178 m_userDataBaseService.UpdateUserAppearance(avatarID, appearance);
179
180 }
181
182 private void UpdateAvatarAttachment(UUID avatarID, int attachmentPoint, UUID itemID, UUID assetID)
183 {
184 AvatarAppearance appearance = m_userDataBaseService.GetUserAppearance(avatarID);
185 if (appearance == null)
186 {
187 appearance = CreateDefaultAppearance(avatarID);
188 }
189
190 appearance.SetAttachment(attachmentPoint, itemID, assetID);
191
192 m_userDataBaseService.UpdateUserAppearance(avatarID, appearance);
193
194 }
195
196 private UUID FindFolderID(string name, List<InventoryFolderBase> folders)
197 {
198 foreach (InventoryFolderBase folder in folders)
199 {
200 if (folder.Name == name)
201 {
202 return folder.ID;
203 }
204 }
205 return UUID.Zero;
206 }
207
208 private InventoryFolderBase FindFolder(string name, List<InventoryFolderBase> folders)
209 {
210 foreach (InventoryFolderBase folder in folders)
211 {
212 if (folder.Name == name)
213 {
214 return folder;
215 }
216 }
217 return null;
218 }
219
220 private InventoryFolderBase FindFolder(string name, Guid parentFolderID, List<InventoryFolderBase> folders)
221 {
222 foreach (InventoryFolderBase folder in folders)
223 {
224 if ((folder.Name == name) && (folder.ParentID.Guid == parentFolderID))
225 {
226 return folder;
227 }
228 }
229 return null;
230 }
231
232 private InventoryItemBase GetItem(string itemName, List<InventoryItemBase> items)
233 {
234 foreach (InventoryItemBase item in items)
235 {
236 if (item.Name.ToLower() == itemName.ToLower())
237 {
238 return item;
239 }
240 }
241 return null;
242 }
243
244 private List<InventoryFolderBase> FindSubFolders(Guid parentFolderID, List<InventoryFolderBase> folders)
245 {
246 List<InventoryFolderBase> subFolders = new List<InventoryFolderBase>();
247 foreach (InventoryFolderBase folder in folders)
248 {
249 if (folder.ParentID.Guid == parentFolderID)
250 {
251 subFolders.Add(folder);
252 }
253 }
254 return subFolders;
255 }
256
257
258 private UUID CloneInventoryItem(UUID avatarID, UUID avatarFolder, InventoryItemBase item, bool modifyPerms)
259 {
260 if (avatarFolder != UUID.Zero)
261 {
262 InventoryItemBase clonedItem = new InventoryItemBase();
263 clonedItem.Owner = avatarID;
264 clonedItem.AssetID = item.AssetID;
265 clonedItem.AssetType = item.AssetType;
266 clonedItem.BasePermissions = item.BasePermissions;
267 clonedItem.CreationDate = item.CreationDate;
268 clonedItem.CreatorId = item.CreatorId;
269 clonedItem.CreatorIdAsUuid = item.CreatorIdAsUuid;
270 clonedItem.CurrentPermissions = item.CurrentPermissions;
271 clonedItem.Description = item.Description;
272 clonedItem.EveryOnePermissions = item.EveryOnePermissions;
273 clonedItem.Flags = item.Flags;
274 clonedItem.Folder = avatarFolder;
275 clonedItem.GroupID = item.GroupID;
276 clonedItem.GroupOwned = item.GroupOwned;
277 clonedItem.GroupPermissions = item.GroupPermissions;
278 clonedItem.ID = UUID.Random();
279 clonedItem.InvType = item.InvType;
280 clonedItem.Name = item.Name;
281 clonedItem.NextPermissions = item.NextPermissions;
282 clonedItem.SalePrice = item.SalePrice;
283 clonedItem.SaleType = item.SaleType;
284
285 if (modifyPerms)
286 {
287 ModifyPermissions(clonedItem);
288 }
289
290 SynchronousRestObjectRequester.MakeRequest<InventoryItemBase, bool>(
291 "POST", m_inventoryServerUrl + "AddNewItem/", clonedItem);
292
293 return clonedItem.ID;
294 }
295
296 return UUID.Zero;
297 }
298
299 private static void ModifyPermissions(InventoryItemBase clonedItem)
300 {
301 if ((clonedItem.CurrentPermissions & (uint)PermissionMask.Modify) == 0)
302 clonedItem.CurrentPermissions |= (uint)PermissionMask.Modify;
303
304 if ((clonedItem.CurrentPermissions & (uint)PermissionMask.Copy) == 0)
305 clonedItem.CurrentPermissions |= (uint)PermissionMask.Copy;
306
307 if ((clonedItem.CurrentPermissions & (uint)PermissionMask.Transfer) != 0)
308 clonedItem.CurrentPermissions &= ~(uint)PermissionMask.Transfer;
309
310 if ((clonedItem.NextPermissions & (uint)PermissionMask.Modify) == 0)
311 clonedItem.NextPermissions |= (uint)PermissionMask.Modify;
312
313 if ((clonedItem.NextPermissions & (uint)PermissionMask.Copy) == 0)
314 clonedItem.NextPermissions |= (uint)PermissionMask.Copy;
315
316 if ((clonedItem.NextPermissions & (uint)PermissionMask.Transfer) != 0)
317 clonedItem.NextPermissions &= ~(uint)PermissionMask.Transfer;
318 }
319
320 private AvatarAppearance CreateDefaultAppearance(UUID avatarId)
321 {
322 AvatarAppearance appearance = null;
323 AvatarWearable[] wearables;
324 byte[] visualParams;
325 GetDefaultAvatarAppearance(out wearables, out visualParams);
326 appearance = new AvatarAppearance(avatarId, wearables, visualParams);
327
328 return appearance;
329 }
330
331 private static void GetDefaultAvatarAppearance(out AvatarWearable[] wearables, out byte[] visualParams)
332 {
333 visualParams = GetDefaultVisualParams();
334 wearables = AvatarWearable.DefaultWearables;
335 }
336
337 private static byte[] GetDefaultVisualParams()
338 {
339 byte[] visualParams;
340 visualParams = new byte[218];
341 for (int i = 0; i < 218; i++)
342 {
343 visualParams[i] = 100;
344 }
345 return visualParams;
346 }
347 #endregion
348
349
350
351 private bool CloneFolder(List<InventoryFolderBase> avatarInventory, UUID avID, UUID parentFolder, AvatarAppearance appearance, InventoryFolderBase templateFolder, List<InventoryFolderBase> templateFolders)
352 {
353 bool success = false;
354 UUID templateFolderId = templateFolder.ID;
355 if (templateFolderId != UUID.Zero)
356 {
357 InventoryFolderBase toFolder = FindFolder(templateFolder.Name, parentFolder.Guid, avatarInventory);
358 if (toFolder == null)
359 {
360 //create new folder
361 toFolder = new InventoryFolderBase();
362 toFolder.ID = UUID.Random();
363 toFolder.Name = templateFolder.Name;
364 toFolder.Owner = avID;
365 toFolder.Type = templateFolder.Type;
366 toFolder.Version = 1;
367 toFolder.ParentID = parentFolder;
368 if (!SynchronousRestObjectRequester.MakeRequest<InventoryFolderBase, bool>(
369 "POST", m_inventoryServerUrl + "CreateFolder/", toFolder))
370 {
371 m_log.InfoFormat("[AvatarApperance] Couldn't make new folder {0} in users inventory", toFolder.Name);
372 return false;
373 }
374 else
375 {
376 // m_log.InfoFormat("made new folder {0} in users inventory", toFolder.Name);
377 }
378 }
379
380 List<InventoryItemBase> templateItems = SynchronousRestObjectRequester.MakeRequest<Guid, List<InventoryItemBase>>(
381 "POST", m_inventoryServerUrl + "GetItems/", templateFolderId.Guid);
382 if ((templateItems != null) && (templateItems.Count > 0))
383 {
384 foreach (InventoryItemBase item in templateItems)
385 {
386
387 UUID clonedItemId = CloneInventoryItem(avID, toFolder.ID, item, false);
388 if (clonedItemId != UUID.Zero)
389 {
390 int appearanceType = ItemIsPartOfAppearance(item, appearance);
391 if (appearanceType >= 0)
392 {
393 UpdateAvatarAppearance(avID, appearanceType, clonedItemId, item.AssetID);
394 }
395
396 if (appearance != null)
397 {
398 int attachment = appearance.GetAttachpoint(item.ID);
399 if (attachment > 0)
400 {
401 UpdateAvatarAttachment(avID, attachment, clonedItemId, item.AssetID);
402 }
403 }
404 success = true;
405 }
406 }
407 }
408
409 List<InventoryFolderBase> subFolders = FindSubFolders(templateFolder.ID.Guid, templateFolders);
410 foreach (InventoryFolderBase subFolder in subFolders)
411 {
412 if (subFolder.Name.ToLower() != "trash")
413 {
414 success = CloneFolder(avatarInventory, avID, toFolder.ID, appearance, subFolder, templateFolders);
415 }
416 }
417 }
418 else
419 {
420 m_log.Info("[AvatarAppearance] Failed to find the template folder");
421 }
422 return success;
423 }
424
425 private int ItemIsPartOfAppearance(InventoryItemBase item, AvatarAppearance appearance)
426 {
427 if (appearance != null)
428 {
429 if (appearance.BodyItem == item.ID)
430 return (int)WearableType.Shape;
431
432 if (appearance.EyesItem == item.ID)
433 return (int)WearableType.Eyes;
434
435 if (appearance.GlovesItem == item.ID)
436 return (int)WearableType.Gloves;
437
438 if (appearance.HairItem == item.ID)
439 return (int)WearableType.Hair;
440
441 if (appearance.JacketItem == item.ID)
442 return (int)WearableType.Jacket;
443
444 if (appearance.PantsItem == item.ID)
445 return (int)WearableType.Pants;
446
447 if (appearance.ShirtItem == item.ID)
448 return (int)WearableType.Shirt;
449
450 if (appearance.ShoesItem == item.ID)
451 return (int)WearableType.Shoes;
452
453 if (appearance.SkinItem == item.ID)
454 return (int)WearableType.Skin;
455
456 if (appearance.SkirtItem == item.ID)
457 return (int)WearableType.Skirt;
458
459 if (appearance.SocksItem == item.ID)
460 return (int)WearableType.Socks;
461
462 if (appearance.UnderPantsItem == item.ID)
463 return (int)WearableType.Underpants;
464
465 if (appearance.UnderShirtItem == item.ID)
466 return (int)WearableType.Undershirt;
467 }
468 return -1;
469 }
470
471 }
472 public enum PermissionMask
473 {
474 None = 0,
475 Transfer = 8192,
476 Modify = 16384,
477 Copy = 32768,
478 Move = 524288,
479 Damage = 1048576,
480 All = 2147483647,
481 }
482
483 public enum WearableType
484 {
485 Shape = 0,
486 Skin = 1,
487 Hair = 2,
488 Eyes = 3,
489 Shirt = 4,
490 Pants = 5,
491 Shoes = 6,
492 Socks = 7,
493 Jacket = 8,
494 Gloves = 9,
495 Undershirt = 10,
496 Underpants = 11,
497 Skirt = 12,
498 }
499}
diff --git a/OpenSim/Grid/UserServer/Main.cs b/OpenSim/Grid/UserServer/Main.cs
index 7bb8b12..d9b3d3f 100644
--- a/OpenSim/Grid/UserServer/Main.cs
+++ b/OpenSim/Grid/UserServer/Main.cs
@@ -71,6 +71,8 @@ namespace OpenSim.Grid.UserServer
71 protected UserServerCommandModule m_consoleCommandModule; 71 protected UserServerCommandModule m_consoleCommandModule;
72 protected UserServerEventDispatchModule m_eventDispatcher; 72 protected UserServerEventDispatchModule m_eventDispatcher;
73 73
74 protected AvatarCreationModule m_appearanceModule;
75
74 public static void Main(string[] args) 76 public static void Main(string[] args)
75 { 77 {
76 XmlConfigurator.Configure(); 78 XmlConfigurator.Configure();
@@ -180,6 +182,9 @@ namespace OpenSim.Grid.UserServer
180 182
181 protected virtual void StartOtherComponents(IInterServiceInventoryServices inventoryService) 183 protected virtual void StartOtherComponents(IInterServiceInventoryServices inventoryService)
182 { 184 {
185 m_appearanceModule = new AvatarCreationModule(m_userDataBaseService, Cfg, inventoryService);
186 m_appearanceModule.Initialise(this);
187
183 StartupLoginService(inventoryService); 188 StartupLoginService(inventoryService);
184 // 189 //
185 // Get the minimum defaultLevel to access to the grid 190 // Get the minimum defaultLevel to access to the grid
@@ -216,6 +221,8 @@ namespace OpenSim.Grid.UserServer
216 m_userManager.PostInitialise(); 221 m_userManager.PostInitialise();
217 m_avatarAppearanceModule.PostInitialise(); 222 m_avatarAppearanceModule.PostInitialise();
218 m_friendsModule.PostInitialise(); 223 m_friendsModule.PostInitialise();
224
225 m_avatarAppearanceModule.PostInitialise();
219 } 226 }
220 227
221 protected virtual void RegisterHttpHandlers() 228 protected virtual void RegisterHttpHandlers()
@@ -230,6 +237,8 @@ namespace OpenSim.Grid.UserServer
230 m_avatarAppearanceModule.RegisterHandlers(m_httpServer); 237 m_avatarAppearanceModule.RegisterHandlers(m_httpServer);
231 m_messagesService.RegisterHandlers(m_httpServer); 238 m_messagesService.RegisterHandlers(m_httpServer);
232 m_gridInfoService.RegisterHandlers(m_httpServer); 239 m_gridInfoService.RegisterHandlers(m_httpServer);
240
241 m_avatarAppearanceModule.RegisterHandlers(m_httpServer);
233 } 242 }
234 243
235 public override void ShutdownSpecific() 244 public override void ShutdownSpecific()