aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/CoreModules
diff options
context:
space:
mode:
authorMelanie2010-10-07 01:13:17 +0200
committerMelanie2010-10-07 01:13:17 +0200
commit2db0ac74c72f18d1bceb15b8a368777e42b366c0 (patch)
tree874f9c1eaa17855e0dd6f07ea50efa0326647a16 /OpenSim/Region/CoreModules
parentPlumb the path for multiple object deletes (diff)
downloadopensim-SC_OLD-2db0ac74c72f18d1bceb15b8a368777e42b366c0.zip
opensim-SC_OLD-2db0ac74c72f18d1bceb15b8a368777e42b366c0.tar.gz
opensim-SC_OLD-2db0ac74c72f18d1bceb15b8a368777e42b366c0.tar.bz2
opensim-SC_OLD-2db0ac74c72f18d1bceb15b8a368777e42b366c0.tar.xz
Implement taking of coalesced objects.
WARNING!!!!! You can TAKE them, but you can't REZ them again. Only the first of the contained objects will rez, the rest is inaccessible until rezzing them is implemented. Also, rotations are not explicitly stored. This MAY work. Or not.
Diffstat (limited to 'OpenSim/Region/CoreModules')
-rw-r--r--OpenSim/Region/CoreModules/Framework/InventoryAccess/InventoryAccessModule.cs463
1 files changed, 263 insertions, 200 deletions
diff --git a/OpenSim/Region/CoreModules/Framework/InventoryAccess/InventoryAccessModule.cs b/OpenSim/Region/CoreModules/Framework/InventoryAccess/InventoryAccessModule.cs
index 9ccf5a0..753aada 100644
--- a/OpenSim/Region/CoreModules/Framework/InventoryAccess/InventoryAccessModule.cs
+++ b/OpenSim/Region/CoreModules/Framework/InventoryAccess/InventoryAccessModule.cs
@@ -28,6 +28,7 @@
28using System; 28using System;
29using System.Collections.Generic; 29using System.Collections.Generic;
30using System.Net; 30using System.Net;
31using System.Xml;
31using System.Reflection; 32using System.Reflection;
32using System.Threading; 33using System.Threading;
33 34
@@ -191,11 +192,11 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess
191 public virtual UUID DeleteToInventory(DeRezAction action, UUID folderID, 192 public virtual UUID DeleteToInventory(DeRezAction action, UUID folderID,
192 List<SceneObjectGroup> objectGroups, IClientAPI remoteClient) 193 List<SceneObjectGroup> objectGroups, IClientAPI remoteClient)
193 { 194 {
194 // HACK: This is only working for lists containing a single item!
195 // It's just a hack to make this WIP compile and run. Nothing
196 // currently calls this with multiple items.
197 UUID ret = UUID.Zero; 195 UUID ret = UUID.Zero;
198 196
197 // The following code groups the SOG's by owner. No objects
198 // belonging to different people can be coalesced, for obvious
199 // reasons.
199 Dictionary<UUID, List<SceneObjectGroup>> deletes = 200 Dictionary<UUID, List<SceneObjectGroup>> deletes =
200 new Dictionary<UUID, List<SceneObjectGroup>>(); 201 new Dictionary<UUID, List<SceneObjectGroup>>();
201 202
@@ -207,266 +208,328 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess
207 deletes[g.OwnerID].Add(g); 208 deletes[g.OwnerID].Add(g);
208 } 209 }
209 210
210 foreach (List<SceneObjectGroup> objlist in deletes.Values) 211 // This is pethod scoped and will be returned. It will be the
211 ret = DeleteToInventory(action, folderID, objlist, remoteClient); 212 // last created asset id
213 UUID assetID = UUID.Zero;
212 214
213 return ret; 215 // Each iteration is really a separate asset being created,
214 } 216 // with distinct destinations as well.
217 foreach (List<SceneObjectGroup> objlist in deletes.Values)
218 {
219 Dictionary<UUID, string> xmlStrings =
220 new Dictionary<UUID, string>();
215 221
216 private UUID DeleteToInventory(DeRezAction action, UUID folderID, 222 foreach (SceneObjectGroup objectGroup in objlist)
217 SceneObjectGroup objectGroup, IClientAPI remoteClient) 223 {
218 { 224 Vector3 inventoryStoredPosition = new Vector3
219 UUID assetID = UUID.Zero; 225 (((objectGroup.AbsolutePosition.X > (int)Constants.RegionSize)
226 ? 250
227 : objectGroup.AbsolutePosition.X)
228 ,
229 (objectGroup.AbsolutePosition.X > (int)Constants.RegionSize)
230 ? 250
231 : objectGroup.AbsolutePosition.X,
232 objectGroup.AbsolutePosition.Z);
220 233
221 Vector3 inventoryStoredPosition = new Vector3 234 Vector3 originalPosition = objectGroup.AbsolutePosition;
222 (((objectGroup.AbsolutePosition.X > (int)Constants.RegionSize)
223 ? 250
224 : objectGroup.AbsolutePosition.X)
225 ,
226 (objectGroup.AbsolutePosition.X > (int)Constants.RegionSize)
227 ? 250
228 : objectGroup.AbsolutePosition.X,
229 objectGroup.AbsolutePosition.Z);
230 235
231 Vector3 originalPosition = objectGroup.AbsolutePosition; 236 // Restore attachment data after trip through the sim
237 if (objectGroup.RootPart.AttachPoint > 0)
238 inventoryStoredPosition = objectGroup.RootPart.AttachOffset;
239 objectGroup.RootPart.Shape.State = objectGroup.RootPart.AttachPoint;
232 240
233 // Restore attachment data after trip through the sim 241 objectGroup.AbsolutePosition = inventoryStoredPosition;
234 if (objectGroup.RootPart.AttachPoint > 0)
235 inventoryStoredPosition = objectGroup.RootPart.AttachOffset;
236 objectGroup.RootPart.Shape.State = objectGroup.RootPart.AttachPoint;
237 242
238 objectGroup.AbsolutePosition = inventoryStoredPosition; 243 // Make sure all bits but the ones we want are clear
244 // on take.
245 // This will be applied to the current perms, so
246 // it will do what we want.
247 objectGroup.RootPart.NextOwnerMask &=
248 ((uint)PermissionMask.Copy |
249 (uint)PermissionMask.Transfer |
250 (uint)PermissionMask.Modify);
251 objectGroup.RootPart.NextOwnerMask |=
252 (uint)PermissionMask.Move;
239 253
240 string sceneObjectXml = SceneObjectSerializer.ToOriginalXmlFormat(objectGroup); 254 string sceneObjectXml = SceneObjectSerializer.ToOriginalXmlFormat(objectGroup);
241 255
242 objectGroup.AbsolutePosition = originalPosition; 256 objectGroup.AbsolutePosition = originalPosition;
243 257
244 // Get the user info of the item destination 258 xmlStrings[objectGroup.UUID] = sceneObjectXml;
245 // 259 }
246 UUID userID = UUID.Zero;
247 260
248 if (action == DeRezAction.Take || action == DeRezAction.TakeCopy || 261 string itemXml;
249 action == DeRezAction.SaveToExistingUserInventoryItem)
250 {
251 // Take or take copy require a taker
252 // Saving changes requires a local user
253 //
254 if (remoteClient == null)
255 return UUID.Zero;
256 262
257 userID = remoteClient.AgentId; 263 if (objlist.Count > 1)
258 } 264 {
259 else 265 float minX, minY, minZ;
260 { 266 float maxX, maxY, maxZ;
261 // All returns / deletes go to the object owner
262 //
263 267
264 userID = objectGroup.RootPart.OwnerID; 268 Vector3[] offsets = m_Scene.GetCombinedBoundingBox(objlist,
265 } 269 out minX, out maxX, out minY, out maxY,
270 out minZ, out maxZ);
266 271
267 if (userID == UUID.Zero) // Can't proceed 272 // CreateWrapper
268 { 273 XmlDocument itemDoc = new XmlDocument();
269 return UUID.Zero; 274 XmlElement root = itemDoc.CreateElement("", "CoalescedObject", "");
270 } 275 itemDoc.AppendChild(root);
271 276
272 // If we're returning someone's item, it goes back to the 277 // Embed the offsets into the group XML
273 // owner's Lost And Found folder. 278 for ( int i = 0 ; i < objlist.Count ; i++ )
274 // Delete is treated like return in this case 279 {
275 // Deleting your own items makes them go to trash 280 XmlDocument doc = new XmlDocument();
276 // 281 SceneObjectGroup g = objlist[i];
282 doc.LoadXml(xmlStrings[g.UUID]);
283 XmlElement e = (XmlElement)doc.SelectSingleNode("/SceneObjectGroup");
284 e.SetAttribute("offsetx", offsets[i].X.ToString());
285 e.SetAttribute("offsety", offsets[i].Y.ToString());
286 e.SetAttribute("offsetz", offsets[i].Z.ToString());
277 287
278 InventoryFolderBase folder = null; 288 XmlNode objectNode = itemDoc.ImportNode(e, true);
279 InventoryItemBase item = null; 289 root.AppendChild(objectNode);
290 }
280 291
281 if (DeRezAction.SaveToExistingUserInventoryItem == action) 292 float sizeX = maxX - minX;
282 { 293 float sizeY = maxY - minY;
283 item = new InventoryItemBase(objectGroup.RootPart.FromUserInventoryItemID, userID); 294 float sizeZ = maxZ - minZ;
284 item = m_Scene.InventoryService.GetItem(item);
285 295
286 //item = userInfo.RootFolder.FindItem( 296 root.SetAttribute("x", sizeX.ToString());
287 // objectGroup.RootPart.FromUserInventoryItemID); 297 root.SetAttribute("y", sizeY.ToString());
298 root.SetAttribute("z", sizeZ.ToString());
288 299
289 if (null == item) 300 itemXml = itemDoc.InnerXml;
301 }
302 else
290 { 303 {
291 m_log.DebugFormat( 304 itemXml = xmlStrings[objlist[0].UUID];
292 "[AGENT INVENTORY]: Object {0} {1} scheduled for save to inventory has already been deleted.",
293 objectGroup.Name, objectGroup.UUID);
294 return UUID.Zero;
295 } 305 }
296 } 306
297 else 307 // Get the user info of the item destination
298 {
299 // Folder magic
300 // 308 //
301 if (action == DeRezAction.Delete) 309 UUID userID = UUID.Zero;
310
311 if (action == DeRezAction.Take || action == DeRezAction.TakeCopy ||
312 action == DeRezAction.SaveToExistingUserInventoryItem)
302 { 313 {
303 // Deleting someone else's item 314 // Take or take copy require a taker
315 // Saving changes requires a local user
304 // 316 //
305 if (remoteClient == null || 317 if (remoteClient == null)
306 objectGroup.OwnerID != remoteClient.AgentId) 318 return UUID.Zero;
307 {
308 319
309 folder = m_Scene.InventoryService.GetFolderForType(userID, AssetType.LostAndFoundFolder); 320 userID = remoteClient.AgentId;
310 }
311 else
312 {
313 folder = m_Scene.InventoryService.GetFolderForType(userID, AssetType.TrashFolder);
314 }
315 } 321 }
316 else if (action == DeRezAction.Return) 322 else
317 { 323 {
318 324 // All returns / deletes go to the object owner
319 // Dump to lost + found unconditionally
320 // 325 //
321 folder = m_Scene.InventoryService.GetFolderForType(userID, AssetType.LostAndFoundFolder); 326
327 userID = objlist[0].RootPart.OwnerID;
322 } 328 }
323 329
324 if (folderID == UUID.Zero && folder == null) 330 if (userID == UUID.Zero) // Can't proceed
325 { 331 {
326 if (action == DeRezAction.Delete) 332 return UUID.Zero;
333 }
334
335 // If we're returning someone's item, it goes back to the
336 // owner's Lost And Found folder.
337 // Delete is treated like return in this case
338 // Deleting your own items makes them go to trash
339 //
340
341 InventoryFolderBase folder = null;
342 InventoryItemBase item = null;
343
344 if (DeRezAction.SaveToExistingUserInventoryItem == action)
345 {
346 item = new InventoryItemBase(objlist[0].RootPart.FromUserInventoryItemID, userID);
347 item = m_Scene.InventoryService.GetItem(item);
348
349 //item = userInfo.RootFolder.FindItem(
350 // objectGroup.RootPart.FromUserInventoryItemID);
351
352 if (null == item)
327 { 353 {
328 // Deletes go to trash by default 354 m_log.DebugFormat(
329 // 355 "[AGENT INVENTORY]: Object {0} {1} scheduled for save to inventory has already been deleted.",
330 folder = m_Scene.InventoryService.GetFolderForType(userID, AssetType.TrashFolder); 356 objlist[0].Name, objlist[0].UUID);
357 return UUID.Zero;
331 } 358 }
332 else 359 }
360 else
361 {
362 // Folder magic
363 //
364 if (action == DeRezAction.Delete)
333 { 365 {
366 // Deleting someone else's item
367 //
334 if (remoteClient == null || 368 if (remoteClient == null ||
335 objectGroup.OwnerID != remoteClient.AgentId) 369 objlist[0].OwnerID != remoteClient.AgentId)
336 { 370 {
337 // Taking copy of another person's item. Take to 371
338 // Objects folder. 372 folder = m_Scene.InventoryService.GetFolderForType(userID, AssetType.LostAndFoundFolder);
339 folder = m_Scene.InventoryService.GetFolderForType(userID, AssetType.Object);
340 } 373 }
341 else 374 else
342 { 375 {
343 // Catch all. Use lost & found 376 folder = m_Scene.InventoryService.GetFolderForType(userID, AssetType.TrashFolder);
377 }
378 }
379 else if (action == DeRezAction.Return)
380 {
381
382 // Dump to lost + found unconditionally
383 //
384 folder = m_Scene.InventoryService.GetFolderForType(userID, AssetType.LostAndFoundFolder);
385 }
386
387 if (folderID == UUID.Zero && folder == null)
388 {
389 if (action == DeRezAction.Delete)
390 {
391 // Deletes go to trash by default
344 // 392 //
393 folder = m_Scene.InventoryService.GetFolderForType(userID, AssetType.TrashFolder);
394 }
395 else
396 {
397 if (remoteClient == null ||
398 objlist[0].OwnerID != remoteClient.AgentId)
399 {
400 // Taking copy of another person's item. Take to
401 // Objects folder.
402 folder = m_Scene.InventoryService.GetFolderForType(userID, AssetType.Object);
403 }
404 else
405 {
406 // Catch all. Use lost & found
407 //
345 408
346 folder = m_Scene.InventoryService.GetFolderForType(userID, AssetType.LostAndFoundFolder); 409 folder = m_Scene.InventoryService.GetFolderForType(userID, AssetType.LostAndFoundFolder);
410 }
347 } 411 }
348 } 412 }
349 }
350 413
351 // Override and put into where it came from, if it came 414 // Override and put into where it came from, if it came
352 // from anywhere in inventory 415 // from anywhere in inventory
353 // 416 //
354 if (action == DeRezAction.Take || action == DeRezAction.TakeCopy) 417 if (action == DeRezAction.Take || action == DeRezAction.TakeCopy)
355 {
356 if (objectGroup.RootPart.FromFolderID != UUID.Zero)
357 { 418 {
358 InventoryFolderBase f = new InventoryFolderBase(objectGroup.RootPart.FromFolderID, userID); 419 if (objlist[0].RootPart.FromFolderID != UUID.Zero)
359 folder = m_Scene.InventoryService.GetFolder(f); 420 {
421 InventoryFolderBase f = new InventoryFolderBase(objlist[0].RootPart.FromFolderID, userID);
422 folder = m_Scene.InventoryService.GetFolder(f);
423 }
360 } 424 }
361 }
362
363 if (folder == null) // None of the above
364 {
365 folder = new InventoryFolderBase(folderID);
366 425
367 if (folder == null) // Nowhere to put it 426 if (folder == null) // None of the above
368 { 427 {
369 return UUID.Zero; 428 folder = new InventoryFolderBase(folderID);
370 }
371 }
372 429
373 item = new InventoryItemBase(); 430 if (folder == null) // Nowhere to put it
374 item.CreatorId = objectGroup.RootPart.CreatorID.ToString(); 431 {
375 item.ID = UUID.Random(); 432 return UUID.Zero;
376 item.InvType = (int)InventoryType.Object; 433 }
377 item.Folder = folder.ID; 434 }
378 item.Owner = userID;
379 }
380 435
381 AssetBase asset = CreateAsset( 436 item = new InventoryItemBase();
382 objectGroup.GetPartName(objectGroup.RootPart.LocalId), 437 // Can't know creator is the same, so null it in inventory
383 objectGroup.GetPartDescription(objectGroup.RootPart.LocalId), 438 if (objlist.Count > 1)
384 (sbyte)AssetType.Object, 439 item.CreatorId = UUID.Zero.ToString();
385 Utils.StringToBytes(sceneObjectXml), 440 else
386 objectGroup.OwnerID.ToString()); 441 item.CreatorId = objlist[0].RootPart.CreatorID.ToString();
387 m_Scene.AssetService.Store(asset); 442 item.ID = UUID.Random();
388 assetID = asset.FullID; 443 item.InvType = (int)InventoryType.Object;
444 item.Folder = folder.ID;
445 item.Owner = userID;
446 if (objlist.Count > 1)
447 item.Flags = (uint)InventoryItemFlags.ObjectHasMultipleItems;
448 }
389 449
390 if (DeRezAction.SaveToExistingUserInventoryItem == action) 450 AssetBase asset = CreateAsset(
391 { 451 objlist[0].GetPartName(objlist[0].RootPart.LocalId),
392 item.AssetID = asset.FullID; 452 objlist[0].GetPartDescription(objlist[0].RootPart.LocalId),
393 m_Scene.InventoryService.UpdateItem(item); 453 (sbyte)AssetType.Object,
394 } 454 Utils.StringToBytes(itemXml),
395 else 455 objlist[0].OwnerID.ToString());
396 { 456 m_Scene.AssetService.Store(asset);
397 item.AssetID = asset.FullID; 457 assetID = asset.FullID;
398 458
399 if (remoteClient != null && (remoteClient.AgentId != objectGroup.RootPart.OwnerID) && m_Scene.Permissions.PropagatePermissions()) 459 if (DeRezAction.SaveToExistingUserInventoryItem == action)
400 { 460 {
401 uint perms = objectGroup.GetEffectivePermissions(); 461 item.AssetID = asset.FullID;
402 uint nextPerms = (perms & 7) << 13; 462 m_Scene.InventoryService.UpdateItem(item);
403 if ((nextPerms & (uint)PermissionMask.Copy) == 0)
404 perms &= ~(uint)PermissionMask.Copy;
405 if ((nextPerms & (uint)PermissionMask.Transfer) == 0)
406 perms &= ~(uint)PermissionMask.Transfer;
407 if ((nextPerms & (uint)PermissionMask.Modify) == 0)
408 perms &= ~(uint)PermissionMask.Modify;
409
410 // Make sure all bits but the ones we want are clear
411 // on take.
412 // This will be applied to the current perms, so
413 // it will do what we want.
414 objectGroup.RootPart.NextOwnerMask &=
415 ((uint)PermissionMask.Copy |
416 (uint)PermissionMask.Transfer |
417 (uint)PermissionMask.Modify);
418 objectGroup.RootPart.NextOwnerMask |=
419 (uint)PermissionMask.Move;
420
421 item.BasePermissions = perms & objectGroup.RootPart.NextOwnerMask;
422 item.CurrentPermissions = item.BasePermissions;
423 item.NextPermissions = objectGroup.RootPart.NextOwnerMask;
424 item.EveryOnePermissions = objectGroup.RootPart.EveryoneMask & objectGroup.RootPart.NextOwnerMask;
425 item.GroupPermissions = objectGroup.RootPart.GroupMask & objectGroup.RootPart.NextOwnerMask;
426
427 // Magic number badness. Maybe this deserves an enum.
428 // bit 4 (16) is the "Slam" bit, it means treat as passed
429 // and apply next owner perms on rez
430 item.CurrentPermissions |= 16; // Slam!
431 } 463 }
432 else 464 else
433 { 465 {
434 item.BasePermissions = objectGroup.GetEffectivePermissions(); 466 item.AssetID = asset.FullID;
435 item.CurrentPermissions = objectGroup.GetEffectivePermissions(); 467
436 item.NextPermissions = objectGroup.RootPart.NextOwnerMask; 468 uint effectivePerms = (uint)(PermissionMask.Copy | PermissionMask.Transfer | PermissionMask.Modify | PermissionMask.Move) | 7;
437 item.EveryOnePermissions = objectGroup.RootPart.EveryoneMask; 469 foreach (SceneObjectGroup grp in objlist)
438 item.GroupPermissions = objectGroup.RootPart.GroupMask; 470 effectivePerms &= grp.GetEffectivePermissions();
471 effectivePerms |= (uint)PermissionMask.Move;
472
473 if (remoteClient != null && (remoteClient.AgentId != objlist[0].RootPart.OwnerID) && m_Scene.Permissions.PropagatePermissions())
474 {
475 uint perms = effectivePerms;
476 uint nextPerms = (perms & 7) << 13;
477 if ((nextPerms & (uint)PermissionMask.Copy) == 0)
478 perms &= ~(uint)PermissionMask.Copy;
479 if ((nextPerms & (uint)PermissionMask.Transfer) == 0)
480 perms &= ~(uint)PermissionMask.Transfer;
481 if ((nextPerms & (uint)PermissionMask.Modify) == 0)
482 perms &= ~(uint)PermissionMask.Modify;
483
484 item.BasePermissions = perms & objlist[0].RootPart.NextOwnerMask;
485 item.CurrentPermissions = item.BasePermissions;
486 item.NextPermissions = perms & objlist[0].RootPart.NextOwnerMask;
487 item.EveryOnePermissions = objlist[0].RootPart.EveryoneMask & objlist[0].RootPart.NextOwnerMask;
488 item.GroupPermissions = objlist[0].RootPart.GroupMask & objlist[0].RootPart.NextOwnerMask;
489
490 // Magic number badness. Maybe this deserves an enum.
491 // bit 4 (16) is the "Slam" bit, it means treat as passed
492 // and apply next owner perms on rez
493 item.CurrentPermissions |= 16; // Slam!
494 }
495 else
496 {
497 item.BasePermissions = effectivePerms;
498 item.CurrentPermissions = effectivePerms;
499 item.NextPermissions = objlist[0].RootPart.NextOwnerMask & effectivePerms;
500 item.EveryOnePermissions = objlist[0].RootPart.EveryoneMask & effectivePerms;
501 item.GroupPermissions = objlist[0].RootPart.GroupMask & effectivePerms;
439 502
440 item.CurrentPermissions &= 503 item.CurrentPermissions &=
441 ((uint)PermissionMask.Copy | 504 ((uint)PermissionMask.Copy |
442 (uint)PermissionMask.Transfer | 505 (uint)PermissionMask.Transfer |
443 (uint)PermissionMask.Modify | 506 (uint)PermissionMask.Modify |
444 (uint)PermissionMask.Move | 507 (uint)PermissionMask.Move |
445 7); // Preserve folded permissions 508 7); // Preserve folded permissions
446 } 509 }
447 510
448 // TODO: add the new fields (Flags, Sale info, etc) 511 // TODO: add the new fields (Flags, Sale info, etc)
449 item.CreationDate = Util.UnixTimeSinceEpoch(); 512 item.CreationDate = Util.UnixTimeSinceEpoch();
450 item.Description = asset.Description; 513 item.Description = asset.Description;
451 item.Name = asset.Name; 514 item.Name = asset.Name;
452 item.AssetType = asset.Type; 515 item.AssetType = asset.Type;
453 516
454 m_Scene.AddInventoryItem(item); 517 m_Scene.AddInventoryItem(item);
455 518
456 if (remoteClient != null && item.Owner == remoteClient.AgentId) 519 if (remoteClient != null && item.Owner == remoteClient.AgentId)
457 {
458 remoteClient.SendInventoryItemCreateUpdate(item, 0);
459 }
460 else
461 {
462 ScenePresence notifyUser = m_Scene.GetScenePresence(item.Owner);
463 if (notifyUser != null)
464 { 520 {
465 notifyUser.ControllingClient.SendInventoryItemCreateUpdate(item, 0); 521 remoteClient.SendInventoryItemCreateUpdate(item, 0);
522 }
523 else
524 {
525 ScenePresence notifyUser = m_Scene.GetScenePresence(item.Owner);
526 if (notifyUser != null)
527 {
528 notifyUser.ControllingClient.SendInventoryItemCreateUpdate(item, 0);
529 }
466 } 530 }
467 } 531 }
468 } 532 }
469
470 return assetID; 533 return assetID;
471 } 534 }
472 535