From 2145de775b3abc86c3832d7d490a6016f141c5d2 Mon Sep 17 00:00:00 2001 From: Casper Warden <216465704+casperwardensl@users.noreply.github.com> Date: Thu, 9 Jan 2020 17:53:22 +0000 Subject: [PATCH] - Bump to 0.5.13 - Add building support for TaskInventory and nested objects (from XML) - Add support for taking objects into inventory - Add waitForAppearanceSet utility - Add new event for when object is fully resolved (ObjectProperties received) - Fixed InventoryItem CRC method - Fixed quaternion bug - Support for uploading Script, Notecard and Gesture assets - Significantly improved build process --- .npmignore | 1 + lib/classes/Agent.ts | 7 + lib/classes/AssetMap.ts | 21 + lib/classes/BuildMap.ts | 15 + lib/classes/Circuit.ts | 2 +- lib/classes/ClientEvents.ts | 2 + lib/classes/InventoryFolder.ts | 1 - lib/classes/InventoryItem.ts | 41 +- lib/classes/ObjectStoreLite.ts | 11 +- lib/classes/Quaternion.ts | 2 +- lib/classes/UUID.ts | 13 +- lib/classes/Utils.ts | 80 ++- lib/classes/commands/AgentCommands.ts | 59 ++ lib/classes/commands/AssetCommands.ts | 231 ++++++- lib/classes/commands/RegionCommands.ts | 878 +++++++++++++++++++------ lib/classes/messages/MapBlockReply.ts | 40 +- lib/classes/public/GameObject.ts | 319 +++++++-- lib/enums/DeRezDestination.ts | 14 + lib/events/ObjectResolvedEvent.ts | 6 + lib/index.ts | 2 + package-lock.json | 2 +- package.json | 2 +- tools/msg_template.json | 17 - tools/msg_template.msg | 5 - 24 files changed, 1414 insertions(+), 357 deletions(-) create mode 100644 lib/classes/AssetMap.ts create mode 100644 lib/classes/BuildMap.ts create mode 100644 lib/enums/DeRezDestination.ts create mode 100644 lib/events/ObjectResolvedEvent.ts diff --git a/.npmignore b/.npmignore index f92b79f..c016217 100644 --- a/.npmignore +++ b/.npmignore @@ -5,6 +5,7 @@ lib/ .idea/ tools/ example/ +exampleMine/ docs/ dist/tests/ testing/ diff --git a/lib/classes/Agent.ts b/lib/classes/Agent.ts index 9a35437..c2f1940 100644 --- a/lib/classes/Agent.ts +++ b/lib/classes/Agent.ts @@ -26,6 +26,7 @@ import { AgentFlags } from '../enums/AgentFlags'; import { ControlFlags } from '../enums/ControlFlags'; import { PacketFlags } from '../enums/PacketFlags'; import { FolderType } from '../enums/FolderType'; +import { Subject, Subscription } from 'rxjs'; export class Agent { @@ -84,6 +85,9 @@ export class Agent }; agentUpdateTimer: Timer | null = null; estateManager = false; + appearanceSet = false; + appearanceSetEvent: Subject = new Subject(); + private clientEvents: ClientEvents; constructor(clientEvents: ClientEvents) @@ -311,6 +315,9 @@ export class Agent } }); + + this.appearanceSet = true; + this.appearanceSetEvent.next(); }); } } diff --git a/lib/classes/AssetMap.ts b/lib/classes/AssetMap.ts new file mode 100644 index 0000000..af6beb7 --- /dev/null +++ b/lib/classes/AssetMap.ts @@ -0,0 +1,21 @@ +export class AssetMap +{ + mesh: { + [key: string]: { + objectName: string, + objectDescription: string, + assetID: string + } + } = {}; + textures: { [key: string]: string } = {}; + animations: { [key: string]: string } = {}; + sounds: { [key: string]: string } = {}; + gestures: { [key: string]: string } = {}; + landmarks: { [key: string]: string } = {}; + callingcards: { [key: string]: string } = {}; + scripts: { [key: string]: string } = {}; + clothing: { [key: string]: string } = {}; + notecards: { [key: string]: string } = {}; + bodyparts: { [key: string]: string } = {}; + objects: { [key: string]: Buffer | null } = {}; +} diff --git a/lib/classes/BuildMap.ts b/lib/classes/BuildMap.ts new file mode 100644 index 0000000..11f6118 --- /dev/null +++ b/lib/classes/BuildMap.ts @@ -0,0 +1,15 @@ +import { AssetMap } from './AssetMap'; +import { GameObject } from './public/GameObject'; +import { Vector3 } from './Vector3'; + +export class BuildMap +{ + public primsNeeded = 0; + public primReservoir: GameObject[] = []; + public rezLocation: Vector3 = Vector3.getZero(); + + constructor(public assetMap: AssetMap, public callback: (map: AssetMap) => void, public costOnly = false) + { + + } +} diff --git a/lib/classes/Circuit.ts b/lib/classes/Circuit.ts index b5fbdef..9a8b8dc 100644 --- a/lib/classes/Circuit.ts +++ b/lib/classes/Circuit.ts @@ -484,7 +484,7 @@ export class Circuit { clearTimeout(this.receivedPackets[packet.sequenceNumber]); this.receivedPackets[packet.sequenceNumber] = setTimeout(this.expireReceivedPacket.bind(this, packet.sequenceNumber), 10000); - console.log('Ignoring duplicate packet: ' + packet.message.name + ' sequenceID: ' + packet.sequenceNumber); + this.sendAck(packet.sequenceNumber); return; } this.receivedPackets[packet.sequenceNumber] = setTimeout(this.expireReceivedPacket.bind(this, packet.sequenceNumber), 10000); diff --git a/lib/classes/ClientEvents.ts b/lib/classes/ClientEvents.ts index 70e67ea..e1eaa64 100644 --- a/lib/classes/ClientEvents.ts +++ b/lib/classes/ClientEvents.ts @@ -23,6 +23,7 @@ import { FriendRightsEvent } from '../events/FriendRightsEvent'; import { FriendRemovedEvent } from '../events/FriendRemovedEvent'; import { ObjectPhysicsDataEvent } from '../events/ObjectPhysicsDataEvent'; import { ParcelPropertiesEvent } from '../events/ParcelPropertiesEvent'; +import { ObjectResolvedEvent } from '../events/ObjectResolvedEvent'; export class ClientEvents @@ -52,4 +53,5 @@ export class ClientEvents onObjectUpdatedEvent: Subject = new Subject(); onObjectKilledEvent: Subject = new Subject(); onSelectedObjectEvent: Subject = new Subject(); + onObjectResolvedEvent: Subject = new Subject(); } diff --git a/lib/classes/InventoryFolder.ts b/lib/classes/InventoryFolder.ts index 5030508..d59a189 100644 --- a/lib/classes/InventoryFolder.ts +++ b/lib/classes/InventoryFolder.ts @@ -141,7 +141,6 @@ export class InventoryFolder delete this.agent.inventory.itemsByID[itemID.toString()]; this.items = this.items.filter((item) => { - console.log(item.itemID + ' vs ' + JSON.stringify(itemID)); return !item.itemID.equals(itemID); }) } diff --git a/lib/classes/InventoryItem.ts b/lib/classes/InventoryItem.ts index 5f173c7..1a286c9 100644 --- a/lib/classes/InventoryItem.ts +++ b/lib/classes/InventoryItem.ts @@ -15,6 +15,9 @@ export class InventoryItem parentID: UUID; flags: InventoryItemFlags; itemID: UUID; + oldItemID?: UUID; + parentPartID?: UUID; + permsGranter?: UUID; description: string; type: AssetType; permissions: { @@ -44,26 +47,24 @@ export class InventoryItem getCRC(): number { let crc = 0; - crc += this.assetID.CRC(); - crc += this.parentID.CRC(); - crc += this.itemID.CRC(); - crc += this.permissions.creator.CRC(); - crc += this.permissions.owner.CRC(); - crc += this.permissions.group.CRC(); - crc += this.permissions.ownerMask; - crc += this.permissions.nextOwnerMask; - crc += this.permissions.everyoneMask; - crc += this.permissions.groupMask; - crc += this.flags; - crc += this.inventoryType; - crc += this.type; - crc += Math.round(this.created.getTime() / 1000); - crc += this.salePrice; - crc += this.saleType * 0x07073096; - while (crc > 4294967295) - { - crc -= 4294967295; - } + crc = crc + this.itemID.CRC() >>> 0; + crc = crc + this.parentID.CRC() >>> 0; + crc = crc + this.permissions.creator.CRC() >>> 0; + crc = crc + this.permissions.owner.CRC() >>> 0; + crc = crc + this.permissions.group.CRC() >>> 0; + crc = crc + this.permissions.baseMask >>> 0; + crc = crc + this.permissions.ownerMask >>> 0; + crc = crc + this.permissions.everyoneMask >>> 0; + crc = crc + this.permissions.groupMask >>> 0; + + crc = crc + this.assetID.CRC() >>> 0; + crc = crc + this.type >>> 0; + crc = crc + this.inventoryType >>> 0; + + crc = crc + this.flags >>> 0; + crc = crc + this.salePrice >>> 0; + crc = crc + (this.saleType * 0x07073096 >>> 0) >>> 0; + crc = crc + Math.round(this.created.getTime() / 1000) >>> 0; return crc; } } diff --git a/lib/classes/ObjectStoreLite.ts b/lib/classes/ObjectStoreLite.ts index c36118d..30af79f 100644 --- a/lib/classes/ObjectStoreLite.ts +++ b/lib/classes/ObjectStoreLite.ts @@ -33,6 +33,7 @@ import { ObjectUpdatedEvent } from '../events/ObjectUpdatedEvent'; import { CompressedFlags } from '../enums/CompressedFlags'; import { Vector3 } from './Vector3'; import { ObjectPhysicsDataEvent } from '../events/ObjectPhysicsDataEvent'; +import { ObjectResolvedEvent } from '../events/ObjectResolvedEvent'; export class ObjectStoreLite implements IObjectStore { @@ -207,8 +208,13 @@ export class ObjectStoreLite implements IObjectStore o.touchName = Utils.BufferToStringSimple(obj.TouchName); o.sitName = Utils.BufferToStringSimple(obj.SitName); o.textureID = Utils.BufferToStringSimple(obj.TextureID); - o.resolvedAt = new Date().getTime() / 1000; - + if (!o.resolvedAt) + { + o.resolvedAt = new Date().getTime() / 1000; + const evt = new ObjectResolvedEvent(); + evt.object = o; + this.clientEvents.onObjectResolvedEvent.next(evt); + } if (o.Flags !== undefined) { if (o.Flags & PrimFlags.CreateSelected) @@ -419,6 +425,7 @@ export class ObjectStoreLite implements IObjectStore newObj.objectID = obj.FullID; newObj.object = obj; newObj.createSelected = obj.Flags !== undefined && (obj.Flags & PrimFlags.CreateSelected) !== 0; + obj.createdSelected = newObj.createSelected; if (obj.Flags !== undefined && obj.Flags & PrimFlags.CreateSelected && !this.pendingObjectProperties[obj.FullID.toString()]) { this.selectedPrimsWithoutUpdate[obj.ID] = true; diff --git a/lib/classes/Quaternion.ts b/lib/classes/Quaternion.ts index 215b201..eac8320 100644 --- a/lib/classes/Quaternion.ts +++ b/lib/classes/Quaternion.ts @@ -72,7 +72,7 @@ export class Quaternion extends quat this.x = buf.x; this.y = buf.y; this.z = buf.z; - this.w = buf.z; + this.w = buf.w; } else if (buf instanceof Quaternion) { diff --git a/lib/classes/UUID.ts b/lib/classes/UUID.ts index fd16fba..81c4040 100644 --- a/lib/classes/UUID.ts +++ b/lib/classes/UUID.ts @@ -164,14 +164,11 @@ export class UUID public CRC(): number { - let retval = 0; const bytes: Buffer = this.getBuffer(); - - retval += ((bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0]); - retval += ((bytes[7] << 24) + (bytes[6] << 16) + (bytes[5] << 8) + bytes[4]); - retval += ((bytes[11] << 24) + (bytes[10] << 16) + (bytes[9] << 8) + bytes[8]); - retval += ((bytes[15] << 24) + (bytes[14] << 16) + (bytes[13] << 8) + bytes[12]); - - return retval; + const crcOne = ((bytes[3] << 24 >>> 0) + (bytes[2] << 16 >>> 0) + (bytes[1] << 8 >>> 0) + bytes[0]); + const crcTwo = ((bytes[7] << 24 >>> 0) + (bytes[6] << 16 >>> 0) + (bytes[5] << 8 >>> 0) + bytes[4]); + const crcThree = ((bytes[11] << 24 >>> 0) + (bytes[10] << 16 >>> 0) + (bytes[9] << 8 >>> 0) + bytes[8]); + const crcFour = ((bytes[15] << 24 >>> 0) + (bytes[14] << 16 >>> 0) + (bytes[13] << 8 >>> 0) + bytes[12]); + return crcOne + crcTwo + crcThree + crcFour >>> 0; } } diff --git a/lib/classes/Utils.ts b/lib/classes/Utils.ts index 3f626d2..f40f3b0 100644 --- a/lib/classes/Utils.ts +++ b/lib/classes/Utils.ts @@ -3,7 +3,9 @@ import { Quaternion } from './Quaternion'; import { GlobalPosition } from './public/interfaces/GlobalPosition'; import { HTTPAssets } from '../enums/HTTPAssets'; import { Vector3 } from './Vector3'; -import { Subject, Subscription } from 'rxjs'; +import { Subject } from 'rxjs'; +import { AssetType } from '../enums/AssetType'; +import { InventoryTypeLL } from '../enums/InventoryTypeLL'; import Timeout = NodeJS.Timeout; export class Utils @@ -132,7 +134,81 @@ export class Utils }; } - static HTTPAssetTypeToInventoryType(HTTPAssetType: string) + static HTTPAssetTypeToAssetType(HTTPAssetType: string): AssetType + { + switch (HTTPAssetType) + { + case HTTPAssets.ASSET_TEXTURE: + return AssetType.Texture; + case HTTPAssets.ASSET_SOUND: + return AssetType.Sound; + case HTTPAssets.ASSET_ANIMATION: + return AssetType.Animation; + case HTTPAssets.ASSET_GESTURE: + return AssetType.Gesture; + case HTTPAssets.ASSET_LANDMARK: + return AssetType.Landmark; + case HTTPAssets.ASSET_CALLINGCARD: + return AssetType.CallingCard; + case HTTPAssets.ASSET_SCRIPT: + return AssetType.Script; + case HTTPAssets.ASSET_CLOTHING: + return AssetType.Clothing; + case HTTPAssets.ASSET_OBJECT: + return AssetType.Object; + case HTTPAssets.ASSET_NOTECARD: + return AssetType.Notecard; + case HTTPAssets.ASSET_LSL_TEXT: + return AssetType.LSLText; + case HTTPAssets.ASSET_LSL_BYTECODE: + return AssetType.LSLBytecode; + case HTTPAssets.ASSET_BODYPART: + return AssetType.Bodypart; + case HTTPAssets.ASSET_MESH: + return AssetType.Mesh; + default: + return 0; + } + } + + static HTTPAssetTypeToInventoryType(HTTPAssetType: string): InventoryTypeLL + { + switch (HTTPAssetType) + { + case HTTPAssets.ASSET_TEXTURE: + return InventoryTypeLL.texture; + case HTTPAssets.ASSET_SOUND: + return InventoryTypeLL.sound; + case HTTPAssets.ASSET_ANIMATION: + return InventoryTypeLL.animation; + case HTTPAssets.ASSET_GESTURE: + return InventoryTypeLL.gesture; + case HTTPAssets.ASSET_LANDMARK: + return InventoryTypeLL.landmark; + case HTTPAssets.ASSET_CALLINGCARD: + return InventoryTypeLL.callcard; + case HTTPAssets.ASSET_SCRIPT: + return InventoryTypeLL.script; + case HTTPAssets.ASSET_CLOTHING: + return InventoryTypeLL.wearable; + case HTTPAssets.ASSET_OBJECT: + return InventoryTypeLL.object; + case HTTPAssets.ASSET_NOTECARD: + return InventoryTypeLL.notecard; + case HTTPAssets.ASSET_LSL_TEXT: + return InventoryTypeLL.script; + case HTTPAssets.ASSET_LSL_BYTECODE: + return InventoryTypeLL.script; + case HTTPAssets.ASSET_BODYPART: + return InventoryTypeLL.wearable; + case HTTPAssets.ASSET_MESH: + return InventoryTypeLL.mesh; + default: + return 0; + } + } + + static HTTPAssetTypeToCapInventoryType(HTTPAssetType: string): String { switch (HTTPAssetType) { diff --git a/lib/classes/commands/AgentCommands.ts b/lib/classes/commands/AgentCommands.ts index 611a649..7f58d67 100644 --- a/lib/classes/commands/AgentCommands.ts +++ b/lib/classes/commands/AgentCommands.ts @@ -9,6 +9,7 @@ import { FilterResponse } from '../../enums/FilterResponse'; import { AvatarPropertiesReplyMessage } from '../messages/AvatarPropertiesReply'; import { AvatarPropertiesRequestMessage } from '../messages/AvatarPropertiesRequest'; import { AvatarPropertiesReplyEvent } from '../../events/AvatarPropertiesReplyEvent'; +import { Subscription } from 'rxjs'; export class AgentCommands extends CommandsBase { @@ -69,6 +70,64 @@ export class AgentCommands extends CommandsBase this.agent.sendAgentUpdate(); } + waitForAppearanceSet(timeout: number = 10000): Promise + { + return new Promise((resolve, reject) => + { + if (this.agent.appearanceSet) + { + resolve(); + } + else + { + let appearanceSubscription: Subscription | undefined; + let timeoutTimer: number | undefined; + appearanceSubscription = this.agent.appearanceSetEvent.subscribe(() => + { + if (timeoutTimer !== undefined) + { + clearTimeout(timeoutTimer); + timeoutTimer = undefined; + } + if (appearanceSubscription !== undefined) + { + appearanceSubscription.unsubscribe(); + appearanceSubscription = undefined; + resolve(); + } + }); + timeoutTimer = setTimeout(() => + { + if (appearanceSubscription !== undefined) + { + appearanceSubscription.unsubscribe(); + appearanceSubscription = undefined; + } + if (timeoutTimer !== undefined) + { + clearTimeout(timeoutTimer); + timeoutTimer = undefined; + reject(new Error('Timeout')); + } + }, timeout) as any as number; + if (this.agent.appearanceSet) + { + if (appearanceSubscription !== undefined) + { + appearanceSubscription.unsubscribe(); + appearanceSubscription = undefined; + } + if (timeoutTimer !== undefined) + { + clearTimeout(timeoutTimer); + timeoutTimer = undefined; + } + resolve(); + } + } + }); + } + async getAvatarProperties(avatarID: UUID | string): Promise { if (typeof avatarID === 'string') diff --git a/lib/classes/commands/AssetCommands.ts b/lib/classes/commands/AssetCommands.ts index 9a88cc9..deed003 100644 --- a/lib/classes/commands/AssetCommands.ts +++ b/lib/classes/commands/AssetCommands.ts @@ -21,9 +21,15 @@ import { Material } from '../public/Material'; import { LLMesh } from '../public/LLMesh'; import { FolderType } from '../../enums/FolderType'; import { HTTPAssets } from '../../enums/HTTPAssets'; +import { InventoryItem } from '../InventoryItem'; +import { CreateInventoryItemMessage } from '../messages/CreateInventoryItem'; +import { WearableType } from '../../enums/WearableType'; +import { UpdateCreateInventoryItemMessage } from '../messages/UpdateCreateInventoryItem'; +import { FilterResponse } from '../../enums/FilterResponse'; export class AssetCommands extends CommandsBase { + private callbackID: number = 1; async downloadAsset(type: HTTPAssets, uuid: UUID | string): Promise { if (typeof uuid === 'string') @@ -400,8 +406,8 @@ export class AssetCommands extends CommandsBase 'metric': 'MUT_Unspecified' }; const uploadMap = { - 'name': name, - 'description': description, + 'name': String(name), + 'description': String(description), 'asset_resources': assetResources, 'asset_type': 'mesh', 'inventory_type': 'object', @@ -411,7 +417,15 @@ export class AssetCommands extends CommandsBase 'group_mask': PermissionMask.All, 'next_owner_mask': PermissionMask.All }; - const result = await this.currentRegion.caps.capsPostXML('NewFileAgentInventory', uploadMap); + let result; + try + { + result = await this.currentRegion.caps.capsPostXML('NewFileAgentInventory', uploadMap); + } + catch (error) + { + console.error(error); + } if (result['state'] === 'upload' && result['upload_price'] !== undefined) { const cost = result['upload_price']; @@ -445,16 +459,204 @@ export class AssetCommands extends CommandsBase } } - uploadAsset(type: HTTPAssets, data: Buffer, name: string, description: string): Promise + uploadInventoryItem(type: HTTPAssets, data: Buffer, name: string, description: string): Promise { return new Promise((resolve, reject) => { + if (type === HTTPAssets.ASSET_SCRIPT) + { + type = HTTPAssets.ASSET_LSL_TEXT; + } + const transactionID = UUID.random(); + const callbackID = ++this.callbackID; + const msg = new CreateInventoryItemMessage(); + msg.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.circuit.sessionID + }; + msg.InventoryBlock = { + CallbackID: callbackID, + FolderID: this.agent.inventory.main.root || UUID.zero(), + TransactionID: transactionID, + NextOwnerMask: (1 << 13) | (1 << 14) | (1 << 15) | (1 << 19), + Type: Utils.HTTPAssetTypeToAssetType(type), + InvType: Utils.HTTPAssetTypeToInventoryType(type), + WearableType: WearableType.Shape, + Name: Utils.StringToBuffer(name), + Description: Utils.StringToBuffer(description) + }; + this.currentRegion.circuit.waitForMessage(Message.UpdateCreateInventoryItem, 10000, (message: UpdateCreateInventoryItemMessage) => + { + if (message.InventoryData[0].CallbackID === callbackID) + { + return FilterResponse.Finish; + } + else + { + return FilterResponse.NoMatch; + } + }).then((createInventoryMsg: UpdateCreateInventoryItemMessage) => + { + switch (type) + { + case HTTPAssets.ASSET_NOTECARD: + { + this.currentRegion.caps.capsPostXML('UpdateNotecardAgentInventory', { + 'item_id': new LLSD.UUID(createInventoryMsg.InventoryData[0].ItemID.toString()), + }).then((result: any) => + { + if (result['uploader']) + { + const uploader = result['uploader']; + this.currentRegion.caps.capsRequestUpload(uploader, data).then((uploadResult: any) => + { + if (uploadResult['state'] && uploadResult['state'] === 'complete') + { + const itemID: UUID = createInventoryMsg.InventoryData[0].ItemID; + resolve(itemID); + } + else + { + reject(new Error('Asset upload failed')) + } + }).catch((err) => + { + reject(err); + }); + } + else + { + reject(new Error('Invalid response when attempting to request upload URL for notecard')); + } + }).catch((err) => + { + reject(err); + }); + break; + } + case HTTPAssets.ASSET_GESTURE: + { + this.currentRegion.caps.capsPostXML('UpdateGestureAgentInventory', { + 'item_id': new LLSD.UUID(createInventoryMsg.InventoryData[0].ItemID.toString()), + }).then((result: any) => + { + if (result['uploader']) + { + const uploader = result['uploader']; + this.currentRegion.caps.capsRequestUpload(uploader, data).then((uploadResult: any) => + { + if (uploadResult['state'] && uploadResult['state'] === 'complete') + { + const itemID: UUID = createInventoryMsg.InventoryData[0].ItemID; + resolve(itemID); + } + else + { + reject(new Error('Asset upload failed')) + } + }).catch((err) => + { + reject(err); + }); + } + else + { + reject(new Error('Invalid response when attempting to request upload URL for notecard')); + } + }).catch((err) => + { + reject(err); + }); + break; + } + case HTTPAssets.ASSET_LSL_TEXT: + { + this.currentRegion.caps.capsPostXML('UpdateScriptAgent', { + 'item_id': new LLSD.UUID(createInventoryMsg.InventoryData[0].ItemID.toString()), + 'target': 'mono' + }).then((result: any) => + { + if (result['uploader']) + { + const uploader = result['uploader']; + this.currentRegion.caps.capsRequestUpload(uploader, data).then((uploadResult: any) => + { + if (uploadResult['state'] && uploadResult['state'] === 'complete') + { + const itemID: UUID = createInventoryMsg.InventoryData[0].ItemID; + resolve(itemID); + } + else + { + reject(new Error('Asset upload failed')) + } + }).catch((err) => + { + reject(err); + }); + } + else + { + reject(new Error('Invalid response when attempting to request upload URL for notecard')); + } + }).catch((err) => + { + reject(err); + }); + break; + } + default: + { + reject(new Error('Currently unsupported CreateInventoryType: ' + type)); + } + } + }).catch(() => + { + reject(new Error('Timed out waiting for UpdateCreateInventoryItem')); + }); + this.circuit.sendMessage(msg, PacketFlags.Reliable); + }); + } + + uploadAsset(type: HTTPAssets, data: Buffer, name: string, description: string): Promise + { + return new Promise((resolve, reject) => + { + switch (type) + { + case HTTPAssets.ASSET_LANDMARK: + case HTTPAssets.ASSET_NOTECARD: + case HTTPAssets.ASSET_GESTURE: + case HTTPAssets.ASSET_SCRIPT: + // These types of assets use an different process + const inventoryItem = this.uploadInventoryItem(type, data, name, description).then((invItemID: UUID) => + { + this.agent.inventory.fetchInventoryItem(invItemID).then((item: InventoryItem | null) => + { + if (item === null) + { + reject(new Error('Unable to get inventory item')); + } + else + { + resolve(item); + } + }).catch((err) => + { + reject(err); + }); + }).catch((err) => + { + reject(err); + }); + return ; + } if (this.agent && this.agent.inventory && this.agent.inventory.main && this.agent.inventory.main.root) { this.currentRegion.caps.capsPostXML('NewFileAgentInventory', { 'folder_id': new LLSD.UUID(this.agent.inventory.main.root.toString()), 'asset_type': type, - 'inventory_type': Utils.HTTPAssetTypeToInventoryType(type), + 'inventory_type': Utils.HTTPAssetTypeToCapInventoryType(type), 'name': name, 'description': description, 'everyone_mask': PermissionMask.All, @@ -468,7 +670,24 @@ export class AssetCommands extends CommandsBase const uploadURL = response['uploader']; this.currentRegion.caps.capsRequestUpload(uploadURL, data).then((responseUpload: any) => { - resolve(new UUID(responseUpload['new_asset'].toString())); + if (responseUpload['new_inventory_item'] !== undefined) + { + const invItemID = new UUID(responseUpload['new_inventory_item'].toString()); + this.agent.inventory.fetchInventoryItem(invItemID).then((item: InventoryItem | null) => + { + if (item === null) + { + reject(new Error('Unable to get inventory item')); + } + else + { + resolve(item); + } + }).catch((err) => + { + reject(err); + }); + } }).catch((err) => { reject(err); diff --git a/lib/classes/commands/RegionCommands.ts b/lib/classes/commands/RegionCommands.ts index 5800293..0443c5c 100644 --- a/lib/classes/commands/RegionCommands.ts +++ b/lib/classes/commands/RegionCommands.ts @@ -21,8 +21,6 @@ import { ObjectAddMessage } from '../messages/ObjectAdd'; import { Quaternion } from '../Quaternion'; import { RezObjectMessage } from '../messages/RezObject'; import { PermissionMask } from '../../enums/PermissionMask'; -import { SelectedObjectEvent } from '../../events/SelectedObjectEvent'; -import Timer = NodeJS.Timer; import { PacketFlags } from '../../enums/PacketFlags'; import { GameObject } from '../public/GameObject'; import { PCode } from '../../enums/PCode'; @@ -35,12 +33,19 @@ import { Parcel } from '../public/Parcel'; import * as Long from 'long'; import * as micromatch from 'micromatch'; import * as LLSD from '@caspertech/llsd'; -import { Subscription } from 'rxjs'; +import { Subject, Subscription } from 'rxjs'; +import { SculptType } from '../..'; +import { ObjectResolvedEvent } from '../../events/ObjectResolvedEvent'; +import { AssetMap } from '../AssetMap'; +import { InventoryType } from '../../enums/InventoryType'; +import { BuildMap } from '../BuildMap'; +import Timer = NodeJS.Timer; import Timeout = NodeJS.Timeout; -import { ObjectUpdatedEvent } from '../..'; export class RegionCommands extends CommandsBase { + private resolveQueue: {[key: number]: GameObject} = {}; + async getRegionHandle(regionID: UUID): Promise { const circuit = this.currentRegion.circuit; @@ -372,6 +377,102 @@ export class RegionCommands extends CommandsBase return this.currentRegion.regionName; } + private waitForObjectResolve(localID: number) + { + return new Promise((resolve, reject) => + { + let timeout: Timeout | undefined = undefined; + let subs: Subscription | undefined = undefined; + try + { + const ourObject = this.currentRegion.objects.getObjectByLocalID(localID); + if (ourObject.resolvedAt) + { + resolve(); + return; + } + } + catch (ignore) + { + + } + subs = this.currentRegion.clientEvents.onObjectResolvedEvent.subscribe((evt: ObjectResolvedEvent) => + { + if (evt.object.ID === localID) + { + if (timeout !== undefined) + { + clearTimeout(timeout); + timeout = undefined; + } + if (subs !== undefined) + { + subs.unsubscribe(); + subs = undefined; + } + resolve(); + } + }); + timeout = setTimeout(() => + { + if (timeout !== undefined) + { + clearTimeout(timeout); + timeout = undefined; + } + if (subs !== undefined) + { + subs.unsubscribe(); + subs = undefined; + } + const object = this.currentRegion.objects.getObjectByLocalID(localID); + if (object.resolvedAt) + { + try + { + const ourObject = this.currentRegion.objects.getObjectByLocalID(localID); + if (ourObject.resolvedAt) + { + console.warn('Resolve timed out but object ' + localID + ' HAS been resolved!'); + resolve(); + return; + } + } + catch (ignore) + { + + } + } + reject(new Error('Timeout')); + }, 10000); + }); + } + + private async queueResolveObject(object: GameObject, skipInventory = false) + { + if (object.resolvedAt) + { + return; + } + if (this.resolveQueue[object.ID] === undefined) + { + this.resolveQueue[object.ID] = object; + try + { + await this.resolveObjects([object], true, true); + } + catch (error) + { + console.error('Failed to resolve ' + object.ID); + } + delete this.resolveQueue[object.ID]; + } + else + { + return this.waitForObjectResolve(object.ID); + } + } + private async resolveObjects(objects: GameObject[], onlyUnresolved: boolean = false, skipInventory = false) { // First, create a map of all object IDs @@ -468,7 +569,6 @@ export class RegionCommands extends CommandsBase const o = objs[ky]; if ((o.resolveAttempts === undefined || o.resolveAttempts < 3) && o.FullID !== undefined && o.name !== undefined && o.Flags !== undefined && !(o.Flags & PrimFlags.InventoryEmpty) && (!o.inventory || o.inventory.length === 0)) { - console.log(' ... Downloading task inventory for object ' + o.FullID.toString() + ' (' + o.name + '), done ' + count + ' of ' + objectSet.length); const req = new RequestTaskInventoryMessage(); req.AgentData = { AgentID: this.agent.agentID, @@ -886,237 +986,608 @@ export class RegionCommands extends CommandsBase }); } - private async createPrimWithRetry(retries: number, obj: GameObject, posOffset: Vector3, rotOffset: Quaternion, inventoryID?: UUID) + private async buildPart(obj: GameObject, posOffset: Vector3, rotOffset: Quaternion, buildMap: BuildMap) { - for (retries--; retries > -1; retries--) - { - try - { - const newObject = await this.createPrim(obj, new Vector3(posOffset), new Quaternion(rotOffset), inventoryID); - return newObject; - } - catch (ignore) - { - process.exit(1); - } - } - throw new Error('Failed to create prim with ' + retries + ' tries'); - } + // Calculate geometry + const objectPosition = new Vector3(obj.Position); + const objectRotation = new Quaternion(obj.Rotation); + const objectScale = new Vector3(obj.Scale); - private async buildPart(obj: GameObject, posOffset: Vector3, rotOffset: Quaternion, meshCallback: (object: GameObject, meshData: UUID) => UUID | null) - { - // Rez a prim - let newObject: GameObject; - if (obj.extraParams !== undefined && obj.extraParams.meshData !== null) + let finalPos = Vector3.getZero(); + let finalRot = Quaternion.getIdentity(); + if (posOffset.x === 0.0 && posOffset.y === 0.0 && posOffset.z === 0.0 && objectPosition !== undefined) { - const inventoryID: UUID | null = await meshCallback(obj, obj.extraParams.meshData.meshData); - if (inventoryID !== null) - { - newObject = await this.createPrimWithRetry(3, obj, posOffset, rotOffset, inventoryID); - } - else - { - newObject = await this.createPrimWithRetry(3, obj, posOffset, rotOffset); - } + finalPos = new Vector3(objectPosition); + finalRot = new Quaternion(objectRotation); } else { - newObject = await this.createPrimWithRetry(3, obj, posOffset, rotOffset); + const adjustedPos = new Vector3(objectPosition).multiplyByQuat(new Quaternion(rotOffset)); + finalPos = new Vector3(new Vector3(posOffset).add(adjustedPos)); + + const baseRot = new Quaternion(rotOffset); + finalRot = new Quaternion(baseRot.multiply(new Quaternion(objectRotation))); } - await newObject.setExtraParams(obj.extraParams); + + // Is this a mesh part? + let object: GameObject | null = null; + if (obj.extraParams !== undefined && obj.extraParams.meshData !== null) + { + if (buildMap.assetMap.mesh[obj.extraParams.meshData.meshData.toString()] !== undefined) + { + const meshEntry = buildMap.assetMap.mesh[obj.extraParams.meshData.meshData.toString()]; + const rezLocation = new Vector3(buildMap.rezLocation); + rezLocation.z += (objectScale.z / 2); + + object = await this.rezFromInventory(obj, rezLocation, new UUID(meshEntry.assetID)); + } + } + else if (buildMap.primReservoir.length > 0) + { + const newPrim = buildMap.primReservoir.shift(); + if (newPrim !== undefined) + { + object = newPrim; + await object.setShape( + obj.PathCurve, + obj.ProfileCurve, + obj.PathBegin, + obj.PathEnd, + obj.PathScaleX, + obj.PathScaleY, + obj.PathShearX, + obj.PathShearY, + obj.PathTwist, + obj.PathTwistBegin, + obj.PathRadiusOffset, + obj.PathTaperX, + obj.PathTaperY, + obj.PathRevolutions, + obj.PathSkew, + obj.ProfileBegin, + obj.ProfileEnd, + obj.ProfileHollow + ); + } + } + + if (object === null) + { + throw new Error('Failed to acquire prim for build'); + } + + await object.setGeometry(finalPos, finalRot, objectScale); + + if (obj.extraParams.sculptData !== null) + { + if (obj.extraParams.sculptData.type !== SculptType.Mesh) + { + const oldTextureID = obj.extraParams.sculptData.texture.toString(); + if (buildMap.assetMap.textures[oldTextureID] !== undefined) + { + obj.extraParams.sculptData.texture = new UUID(buildMap.assetMap.textures[oldTextureID]); + } + } + } + await object.setExtraParams(obj.extraParams); + if (obj.TextureEntry !== undefined) { - await newObject.setTextureEntry(obj.TextureEntry); + if (obj.TextureEntry.defaultTexture !== null) + { + const oldTextureID = obj.TextureEntry.defaultTexture.textureID.toString(); + if (buildMap.assetMap.textures[oldTextureID] !== undefined) + { + obj.TextureEntry.defaultTexture.textureID = new UUID(buildMap.assetMap.textures[oldTextureID]); + } + } + for (const j of obj.TextureEntry.faces) + { + const oldTextureID = j.textureID.toString(); + if (buildMap.assetMap.textures[oldTextureID] !== undefined) + { + j.textureID = new UUID(buildMap.assetMap.textures[oldTextureID]); + } + } + + try + { + await object.setTextureEntry(obj.TextureEntry); + } + catch (error) + { + console.error(error); + } } + if (obj.name !== undefined) { - await newObject.setName(obj.name); + await object.setName(obj.name); } + if (obj.description !== undefined) { - await newObject.setDescription(obj.description); + await object.setDescription(obj.description); } - return newObject; - } - buildObject(obj: GameObject, meshCallback: (object: GameObject, meshData: UUID) => UUID | null): Promise - { - return new Promise(async (resolve, reject) => + for (const invItem of obj.inventory) { - const parts: (Promise)[] = []; - console.log('Rezzing prims'); - parts.push(this.buildPart(obj, Vector3.getZero(), Quaternion.getIdentity(), meshCallback)); - - if (obj.children) + try { - if (obj.Position === undefined) + switch (invItem.inventoryType) { - obj.Position = Vector3.getZero(); - } - if (obj.Rotation === undefined) - { - obj.Rotation = Quaternion.getIdentity(); - } - for (const child of obj.children) - { - if (child.Position !== undefined && child.Rotation !== undefined) + case InventoryType.Clothing: { - const objPos = new Vector3(obj.Position); - const objRot = new Quaternion(obj.Rotation); - parts.push(this.buildPart(child, objPos, objRot, meshCallback)); + if (buildMap.assetMap.clothing[invItem.assetID.toString()] !== undefined) + { + const invItemID = buildMap.assetMap.clothing[invItem.assetID.toString()]; + await object.dropInventoryIntoContents(new UUID(invItemID)); + } + break; + } + case InventoryType.Bodypart: + { + if (buildMap.assetMap.bodyparts[invItem.assetID.toString()] !== undefined) + { + const invItemID = buildMap.assetMap.bodyparts[invItem.assetID.toString()]; + await object.dropInventoryIntoContents(new UUID(invItemID)); + } + break; + } + case InventoryType.Notecard: + { + if (buildMap.assetMap.notecards[invItem.assetID.toString()] !== undefined) + { + const invItemID = buildMap.assetMap.notecards[invItem.assetID.toString()]; + await object.dropInventoryIntoContents(new UUID(invItemID)); + } + break; + } + case InventoryType.Sound: + { + if (buildMap.assetMap.sounds[invItem.assetID.toString()] !== undefined) + { + const invItemID = buildMap.assetMap.sounds[invItem.assetID.toString()]; + await object.dropInventoryIntoContents(new UUID(invItemID)); + } + break; + } + case InventoryType.Gesture: + { + if (buildMap.assetMap.gestures[invItem.assetID.toString()] !== undefined) + { + const invItemID = buildMap.assetMap.gestures[invItem.assetID.toString()]; + await object.dropInventoryIntoContents(new UUID(invItemID)); + } + break; + } + case InventoryType.Landmark: + { + if (buildMap.assetMap.landmarks[invItem.assetID.toString()] !== undefined) + { + const invItemID = buildMap.assetMap.landmarks[invItem.assetID.toString()]; + await object.dropInventoryIntoContents(new UUID(invItemID)); + } + break; + } + case InventoryType.LSL: + { + if (buildMap.assetMap.scripts[invItem.assetID.toString()] !== undefined) + { + const invItemID = buildMap.assetMap.scripts[invItem.assetID.toString()]; + await object.dropInventoryIntoContents(new UUID(invItemID)); + } + break; + } + case InventoryType.Animation: + { + if (buildMap.assetMap.animations[invItem.assetID.toString()] !== undefined) + { + const invItemID = buildMap.assetMap.animations[invItem.assetID.toString()]; + await object.dropInventoryIntoContents(new UUID(invItemID)); + } + break; } } } - Promise.all(parts).then(async (results) => + catch (error) { - console.log('Linking prims'); - const rootObj = results[0]; - const childPrims: GameObject[] = []; - for (const childObject of results) + console.error(error); + } + } + + // Do nested objects last + for (const invItem of obj.inventory) + { + try + { + switch (invItem.inventoryType) { - if (childObject !== rootObj) + case InventoryType.Object: { - childPrims.push(childObject); + if (buildMap.assetMap.objects[invItem.assetID.toString()] !== undefined) + { + const objectXML = buildMap.assetMap.objects[invItem.assetID.toString()]; + if (objectXML !== null) + { + const taskObjectXML = await GameObject.fromXML(objectXML.toString('utf-8')); + const taskObject = await this.buildObjectNew(taskObjectXML, buildMap.callback, buildMap.costOnly); + if (taskObject !== null) + { + const invItemUUID = await taskObject.takeToInventory(); + await object.dropInventoryIntoContents(invItemUUID); + } + } + } + break; } } - await rootObj.linkFrom(childPrims); - console.log('All done'); - resolve(rootObj); - }).catch((err) => + } + catch (error) { - reject(err); - }); - /* - Utils.promiseConcurrent(parts, 1000, 10000).then(async (results) => - { - console.log('Linking prims'); - const rootObj = results.results[0]; - await rootObj.linkFrom(results.results); - console.log('All done'); - resolve(rootObj); - }).catch((err) => - { - reject(err); - }); - */ - }); + console.error(error); + } + } + return object; } - createPrim(obj: GameObject, posOffset: Vector3, rotOffset: Quaternion, inventoryID?: UUID): Promise + private gatherAssets(obj: GameObject, buildMap: BuildMap) { - return new Promise(async (resolve, reject) => + if (obj.extraParams !== undefined) { - const timeRequested = (new Date().getTime() / 1000) - this.currentRegion.timeOffset; - - const objectPosition = new Vector3(obj.Position); - const objectRotation = new Quaternion(obj.Rotation); - const objectScale = new Vector3(obj.Scale); - - let finalPos = Vector3.getZero(); - let finalRot = Quaternion.getIdentity(); - if (posOffset.x === 0.0 && posOffset.y === 0.0 && posOffset.z === 0.0 && objectPosition !== undefined) + if (obj.extraParams.meshData !== null) { - finalPos = new Vector3(objectPosition); - finalRot = new Quaternion(objectRotation); + buildMap.assetMap.mesh[obj.extraParams.meshData.meshData.toString()] = { + objectName: obj.name || 'Object', + objectDescription: obj.description || '(no description)', + assetID: obj.extraParams.meshData.meshData.toString() + }; } else { - const adjustedPos = new Vector3(new Vector3(objectPosition).multiplyByQuat(new Quaternion(rotOffset).inverse())); - finalPos = new Vector3(new Vector3(adjustedPos).add(new Vector3(posOffset))); - finalRot = new Quaternion(new Quaternion(objectRotation).add(new Quaternion(rotOffset))); + buildMap.primsNeeded++; } - let msg: ObjectAddMessage | RezObjectMessage | null = null; - let fromInventory = false; - if (inventoryID === undefined || this.agent.inventory.itemsByID[inventoryID.toString()] === undefined) + if (obj.extraParams.sculptData !== null) { - // First, rez object in scene - msg = new ObjectAddMessage(); + if (obj.extraParams.sculptData.type !== SculptType.Mesh) + { + buildMap.assetMap.textures[obj.extraParams.sculptData.texture.toString()] = obj.extraParams.sculptData.texture.toString(); + } + } + if (obj.TextureEntry !== undefined) + { + for (const j of obj.TextureEntry.faces) + { + const textureID = j.textureID; + buildMap.assetMap.textures[textureID.toString()] = textureID.toString(); + } + if (obj.TextureEntry.defaultTexture !== null) + { + const textureID = obj.TextureEntry.defaultTexture.textureID; + buildMap.assetMap.textures[textureID.toString()] = textureID.toString(); + } + } + if (obj.inventory !== undefined) + { + for (const j of obj.inventory) + { + switch (j.inventoryType) + { + case InventoryType.Animation: + { + buildMap.assetMap.animations[j.assetID.toString()] = j.assetID.toString(); + break; + } + case InventoryType.Bodypart: + { + buildMap.assetMap.bodyparts[j.assetID.toString()] = j.assetID.toString(); + break; + } + case InventoryType.CallingCard: + { + buildMap.assetMap.callingcards[j.assetID.toString()] = j.assetID.toString(); + break; + } + case InventoryType.Clothing: + { + buildMap.assetMap.clothing[j.assetID.toString()] = j.assetID.toString(); + break; + } + case InventoryType.Gesture: + { + buildMap.assetMap.gestures[j.assetID.toString()] = j.assetID.toString(); + break; + } + case InventoryType.Landmark: + { + buildMap.assetMap.landmarks[j.assetID.toString()] = j.assetID.toString(); + break; + } + case InventoryType.LSL: + { + buildMap.assetMap.scripts[j.assetID.toString()] = j.assetID.toString(); + break; + } + case InventoryType.Snapshot: + { + buildMap.assetMap.textures[j.assetID.toString()] = j.assetID.toString(); + break; + } + case InventoryType.Notecard: + { + buildMap.assetMap.notecards[j.assetID.toString()] = j.assetID.toString(); + break; + } + case InventoryType.Sound: + { + buildMap.assetMap.sounds[j.assetID.toString()] = j.assetID.toString(); + break; + } + case InventoryType.Object: + { + buildMap.assetMap.objects[j.assetID.toString()] = null; + } + } + } + } + } + if (obj.children) + { + for (const child of obj.children) + { + this.gatherAssets(child, buildMap); + } + } + } + + async buildObjectNew(obj: GameObject, callback: (map: AssetMap) => void, costOnly: boolean = false): Promise + { + const map: AssetMap = new AssetMap(); + const buildMap = new BuildMap(map, callback, costOnly); + this.gatherAssets(obj, buildMap); + await callback(map); + + if (costOnly) + { + return null; + } + + let agentPos = new Vector3([128, 128, 2048]); + try + { + const agentLocalID = this.currentRegion.agent.localID; + const agentObject = this.currentRegion.objects.getObjectByLocalID(agentLocalID); + if (agentObject.Position !== undefined) + { + agentPos = new Vector3(agentObject.Position); + } + else + { + throw new Error('Agent position is undefined'); + } + } + catch (error) + { + console.warn('Unable to find avatar location, rezzing at ' + agentPos.toString()); + } + agentPos.z += 2.0; + buildMap.rezLocation = agentPos; + // Set camera above target location for fast acquisition + const campos = new Vector3(agentPos); + campos.z += 2.0; + await this.currentRegion.clientCommands.agent.setCamera(campos, agentPos, 10, new Vector3([-1.0, 0, 0]), new Vector3([0.0, 1.0, 0])); + + if (buildMap.primsNeeded > 0) + { + buildMap.primReservoir = await this.createPrims(buildMap.primsNeeded, agentPos); + } + + const parts = []; + parts.push(this.buildPart(obj, Vector3.getZero(), Quaternion.getIdentity(), buildMap)); + + if (obj.children) + { + if (obj.Position === undefined) + { + obj.Position = Vector3.getZero(); + } + if (obj.Rotation === undefined) + { + obj.Rotation = Quaternion.getIdentity(); + } + for (const child of obj.children) + { + if (child.Position !== undefined && child.Rotation !== undefined) + { + const objPos = new Vector3(obj.Position); + const objRot = new Quaternion(obj.Rotation); + parts.push(this.buildPart(child, objPos, objRot, buildMap)); + } + } + } + const results: GameObject[] = await Promise.all(parts); + + const rootObj = results[0]; + const childPrims: GameObject[] = []; + for (const childObject of results) + { + if (childObject !== rootObj) + { + childPrims.push(childObject); + } + } + await rootObj.linkFrom(childPrims); + return rootObj; + } + + private createPrims(count: number, position: Vector3) + { + return new Promise((resolve, reject) => + { + const gatheredPrims: GameObject[] = []; + let objSub: Subscription | undefined = undefined; + let timeout: Timeout | undefined = setTimeout(() => + { + if (objSub !== undefined) + { + objSub.unsubscribe(); + objSub = undefined; + } + if (timeout !== undefined) + { + clearTimeout(timeout); + timeout = undefined; + } + reject(new Error('Failed to gather ' + count + ' prims - only gathered ' + gatheredPrims.length)); + }, 30000); + objSub = this.currentRegion.clientEvents.onNewObjectEvent.subscribe(async (evt: NewObjectEvent) => + { + if (!evt.object.resolvedAt) + { + // We need to get the full ObjectProperties so we can be sure this is or isn't a rez from inventory + await this.queueResolveObject(evt.object, true); + } + if (evt.createSelected && !evt.object.claimedForBuild) + { + if (evt.object.itemID === undefined || evt.object.itemID.equals(UUID.zero())) + { + if ( + evt.object.PCode === PCode.Prim && + evt.object.Material === 3 && + evt.object.PathCurve === 16 && + evt.object.ProfileCurve === 1 && + evt.object.PathBegin === 0 && + evt.object.PathEnd === 1 && + evt.object.PathScaleX === 1 && + evt.object.PathScaleY === 1 && + evt.object.PathShearX === 0 && + evt.object.PathShearY === 0 && + evt.object.PathTwist === 0 && + evt.object.PathTwistBegin === 0 && + evt.object.PathRadiusOffset === 0 && + evt.object.PathTaperX === 0 && + evt.object.PathTaperY === 0 && + evt.object.PathRevolutions === 1 && + evt.object.PathSkew === 0 && + evt.object.ProfileBegin === 0 && + evt.object.ProfileHollow === 0 + ) + { + evt.object.claimedForBuild = true; + gatheredPrims.push(evt.object); + if (gatheredPrims.length === count) + { + if (objSub !== undefined) + { + objSub.unsubscribe(); + objSub = undefined; + } + if (timeout !== undefined) + { + clearTimeout(timeout); + timeout = undefined; + } + resolve(gatheredPrims); + } + } + } + } + }); + + for (let x = 0; x < count; x++) + { + const msg = new ObjectAddMessage(); msg.AgentData = { AgentID: this.agent.agentID, SessionID: this.circuit.sessionID, GroupID: UUID.zero() }; msg.ObjectData = { - PCode: Utils.numberOrZero(obj.PCode), - Material: Utils.numberOrZero(obj.Material), + PCode: PCode.Prim, + Material: 3, AddFlags: PrimFlags.CreateSelected, - PathCurve: Utils.numberOrZero(obj.PathCurve), - ProfileCurve: Utils.numberOrZero(obj.ProfileCurve), - PathBegin: Utils.packBeginCut(Utils.numberOrZero(obj.PathBegin)), - PathEnd: Utils.packEndCut(Utils.numberOrZero(obj.PathEnd)), - PathScaleX: Utils.packPathScale(Utils.numberOrZero(obj.PathScaleX)), - PathScaleY: Utils.packPathScale(Utils.numberOrZero(obj.PathScaleY)), - PathShearX: Utils.packPathShear(Utils.numberOrZero(obj.PathShearX)), - PathShearY: Utils.packPathShear(Utils.numberOrZero(obj.PathShearY)), - PathTwist: Utils.packPathTwist(Utils.numberOrZero(obj.PathTwist)), - PathTwistBegin: Utils.packPathTwist(Utils.numberOrZero(obj.PathTwistBegin)), - PathRadiusOffset: Utils.packPathTwist(Utils.numberOrZero(obj.PathRadiusOffset)), - PathTaperX: Utils.packPathTaper(Utils.numberOrZero(obj.PathTaperX)), - PathTaperY: Utils.packPathTaper(Utils.numberOrZero(obj.PathTaperY)), - PathRevolutions: Utils.packPathRevolutions(Utils.numberOrZero(obj.PathRevolutions)), - PathSkew: Utils.packPathTwist(Utils.numberOrZero(obj.PathSkew)), - ProfileBegin: Utils.packBeginCut(Utils.numberOrZero(obj.ProfileBegin)), - ProfileEnd: Utils.packEndCut(Utils.numberOrZero(obj.ProfileEnd)), - ProfileHollow: Utils.packProfileHollow(Utils.numberOrZero(obj.ProfileHollow)), + PathCurve: 16, + ProfileCurve: 1, + PathBegin: 0, + PathEnd: 0, + PathScaleX: 100, + PathScaleY: 100, + PathShearX: 0, + PathShearY: 0, + PathTwist: 0, + PathTwistBegin: 0, + PathRadiusOffset: 0, + PathTaperX: 0, + PathTaperY: 0, + PathRevolutions: 0, + PathSkew: 0, + ProfileBegin: 0, + ProfileEnd: 0, + ProfileHollow: 0, BypassRaycast: 1, - RayStart: finalPos, - RayEnd: finalPos, + RayStart: position, + RayEnd: position, RayTargetID: UUID.zero(), RayEndIsIntersection: 0, - Scale: Utils.vector3OrZero(obj.Scale), - Rotation: finalRot, - State: Utils.numberOrZero(obj.State) - }; - } - else - { - fromInventory = true; - const invItem = this.agent.inventory.itemsByID[inventoryID.toString()]; - const queryID = UUID.random(); - msg = new RezObjectMessage(); - msg.AgentData = { - AgentID: this.agent.agentID, - SessionID: this.circuit.sessionID, - GroupID: UUID.zero() - }; - msg.RezData = { - FromTaskID: UUID.zero(), - BypassRaycast: 1, - RayStart: finalPos, - RayEnd: finalPos, - RayTargetID: UUID.zero(), - RayEndIsIntersection: false, - RezSelected: true, - RemoveItem: false, - ItemFlags: invItem.flags, - GroupMask: PermissionMask.All, - EveryoneMask: PermissionMask.All, - NextOwnerMask: PermissionMask.All, - }; - msg.InventoryData = { - ItemID: invItem.itemID, - FolderID: invItem.parentID, - CreatorID: invItem.permissions.creator, - OwnerID: invItem.permissions.owner, - GroupID: invItem.permissions.group, - BaseMask: invItem.permissions.baseMask, - OwnerMask: invItem.permissions.ownerMask, - GroupMask: invItem.permissions.groupMask, - EveryoneMask: invItem.permissions.everyoneMask, - NextOwnerMask: invItem.permissions.nextOwnerMask, - GroupOwned: false, - TransactionID: queryID, - Type: invItem.type, - InvType: invItem.inventoryType, - Flags: invItem.flags, - SaleType: invItem.saleType, - SalePrice: invItem.salePrice, - Name: Utils.StringToBuffer(invItem.name), - Description: Utils.StringToBuffer(invItem.description), - CreationDate: Math.round(invItem.created.getTime() / 1000), - CRC: 0, + Scale: new Vector3([0.5, 0.5, 0.5]), + Rotation: Quaternion.getIdentity(), + State: 0 }; + this.circuit.sendMessage(msg, PacketFlags.Reliable); } + }); + } + + rezFromInventory(obj: GameObject, position: Vector3, inventoryID: UUID): Promise + { + return new Promise(async (resolve, reject) => + { + const invItem = this.agent.inventory.itemsByID[inventoryID.toString()]; + const queryID = UUID.random(); + const msg = new RezObjectMessage(); + msg.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.circuit.sessionID, + GroupID: UUID.zero() + }; + msg.RezData = { + FromTaskID: UUID.zero(), + BypassRaycast: 1, + RayStart: position, + RayEnd: position, + RayTargetID: UUID.zero(), + RayEndIsIntersection: false, + RezSelected: true, + RemoveItem: false, + ItemFlags: invItem.flags, + GroupMask: PermissionMask.All, + EveryoneMask: PermissionMask.All, + NextOwnerMask: PermissionMask.All, + }; + msg.InventoryData = { + ItemID: invItem.itemID, + FolderID: invItem.parentID, + CreatorID: invItem.permissions.creator, + OwnerID: invItem.permissions.owner, + GroupID: invItem.permissions.group, + BaseMask: invItem.permissions.baseMask, + OwnerMask: invItem.permissions.ownerMask, + GroupMask: invItem.permissions.groupMask, + EveryoneMask: invItem.permissions.everyoneMask, + NextOwnerMask: invItem.permissions.nextOwnerMask, + GroupOwned: false, + TransactionID: queryID, + Type: invItem.type, + InvType: invItem.inventoryType, + Flags: invItem.flags, + SaleType: invItem.saleType, + SalePrice: invItem.salePrice, + Name: Utils.StringToBuffer(invItem.name), + Description: Utils.StringToBuffer(invItem.description), + CreationDate: Math.round(invItem.created.getTime() / 1000), + CRC: 0, + }; + let objSub: Subscription | undefined = undefined; let timeout: Timeout | undefined = setTimeout(() => { @@ -1132,11 +1603,17 @@ export class RegionCommands extends CommandsBase } reject(new Error('Prim never arrived')); }, 10000); + let claimedPrim = false; objSub = this.currentRegion.clientEvents.onNewObjectEvent.subscribe(async (evt: NewObjectEvent) => { - if (evt.createSelected && !evt.object.claimedForBuild) + if (evt.createSelected && !evt.object.resolvedAt) { - if (!fromInventory || (inventoryID !== undefined && evt.object.itemID.equals(inventoryID))) + // We need to get the full ObjectProperties so we can be sure this is or isn't a rez from inventory + await this.queueResolveObject(evt.object, true); + } + if (evt.createSelected && !evt.object.claimedForBuild && !claimedPrim) + { + if (inventoryID !== undefined && evt.object.itemID !== undefined && evt.object.itemID.equals(inventoryID)) { if (objSub !== undefined) { @@ -1149,36 +1626,20 @@ export class RegionCommands extends CommandsBase timeout = undefined; } evt.object.claimedForBuild = true; - - if (!fromInventory) - { - await evt.object.setShape(obj.PathCurve, obj.ProfileCurve, obj.PathBegin, obj.PathEnd, obj.PathScaleX, obj.PathScaleY, obj.PathShearX, obj.PathShearY, obj.PathTwist, obj.PathTwistBegin, obj.PathRadiusOffset, obj.PathTaperX, obj.PathTaperY, obj.PathRevolutions, obj.PathSkew, obj.ProfileBegin, obj.ProfileEnd, obj.ProfileHollow); - } - - - // Set the object's position properly - try - { - await evt.object.setGeometry(finalPos, finalRot, obj.Scale); - } - catch (error) - { - console.log('Failed to set object position :/'); - } + claimedPrim = true; resolve(evt.object); } } }); // Move the camera to look directly at prim for faster capture - const campos = new Vector3(finalPos); - campos.z += 128.0 + objectScale.z; - await this.currentRegion.clientCommands.agent.setCamera(campos, finalPos, 4096, new Vector3([-1.0, 0, 0]), new Vector3([0.0, 1.0, 0])); - - if (msg !== null) + if (obj.Scale !== undefined) { - this.circuit.sendMessage(msg, PacketFlags.Reliable); + const camLocation = new Vector3(position); + camLocation.z += (obj.Scale.z / 2) + 1; + await this.currentRegion.clientCommands.agent.setCamera(camLocation, position, obj.Scale.z, new Vector3([-1.0, 0, 0]), new Vector3([0.0, 1.0, 0])); } + this.circuit.sendMessage(msg, PacketFlags.Reliable); }); } @@ -1357,7 +1818,6 @@ export class RegionCommands extends CommandsBase { await checkObjects(uuids, objects); } - console.log('Found ' + Object.keys(stillAlive).length + ' objects still present out of ' + checkList.length + ' objects'); const deadObjects: GameObject[] = []; for (const o of checkList) { diff --git a/lib/classes/messages/MapBlockReply.ts b/lib/classes/messages/MapBlockReply.ts index 08a38c6..bdd6c04 100644 --- a/lib/classes/messages/MapBlockReply.ts +++ b/lib/classes/messages/MapBlockReply.ts @@ -25,14 +25,10 @@ export class MapBlockReplyMessage implements MessageBase Agents: number; MapImageID: UUID; }[]; - Size: { - SizeX: number; - SizeY: number; - }[]; getSize(): number { - return this.calculateVarVarSize(this.Data, 'Name', 1) + ((27) * this.Data.length) + ((4) * this.Size.length) + 22; + return this.calculateVarVarSize(this.Data, 'Name', 1) + ((27) * this.Data.length) + 21; } calculateVarVarSize(block: object[], paramName: string, extraPerVar: number): number @@ -52,7 +48,7 @@ export class MapBlockReplyMessage implements MessageBase pos += 16; buf.writeUInt32LE(this.AgentData['Flags'], pos); pos += 4; - let count = this.Data.length; + const count = this.Data.length; buf.writeUInt8(this.Data.length, pos++); for (let i = 0; i < count; i++) { @@ -71,15 +67,6 @@ export class MapBlockReplyMessage implements MessageBase this.Data[i]['MapImageID'].writeToBuffer(buf, pos); pos += 16; } - count = this.Size.length; - buf.writeUInt8(this.Size.length, pos++); - for (let i = 0; i < count; i++) - { - buf.writeUInt16LE(this.Size[i]['SizeX'], pos); - pos += 2; - buf.writeUInt16LE(this.Size[i]['SizeY'], pos); - pos += 2; - } return pos - startPos; } @@ -103,7 +90,7 @@ export class MapBlockReplyMessage implements MessageBase { return pos - startPos; } - let count = buf.readUInt8(pos++); + const count = buf.readUInt8(pos++); this.Data = []; for (let i = 0; i < count; i++) { @@ -142,27 +129,6 @@ export class MapBlockReplyMessage implements MessageBase pos += 16; this.Data.push(newObjData); } - if (pos >= buf.length) - { - return pos - startPos; - } - count = buf.readUInt8(pos++); - this.Size = []; - for (let i = 0; i < count; i++) - { - const newObjSize: { - SizeX: number, - SizeY: number - } = { - SizeX: 0, - SizeY: 0 - }; - newObjSize['SizeX'] = buf.readUInt16LE(pos); - pos += 2; - newObjSize['SizeY'] = buf.readUInt16LE(pos); - pos += 2; - this.Size.push(newObjSize); - } return pos - startPos; } } diff --git a/lib/classes/public/GameObject.ts b/lib/classes/public/GameObject.ts index 0ea2683..327dc42 100644 --- a/lib/classes/public/GameObject.ts +++ b/lib/classes/public/GameObject.ts @@ -38,6 +38,15 @@ import { HTTPAssets } from '../../enums/HTTPAssets'; import { PhysicsShapeType } from '../../enums/PhysicsShapeType'; import { PCode } from '../../enums/PCode'; import { SoundFlags } from '../../enums/SoundFlags'; +import { DeRezObjectMessage } from '../messages/DeRezObject'; +import { DeRezDestination } from '../../enums/DeRezDestination'; +import { Message } from '../../enums/Message'; +import { UpdateCreateInventoryItemMessage } from '../messages/UpdateCreateInventoryItem'; +import { FilterResponse } from '../../enums/FilterResponse'; +import { UpdateTaskInventoryMessage } from '../messages/UpdateTaskInventory'; +import { ObjectPropertiesMessage } from '../messages/ObjectProperties'; +import { ObjectSelectMessage } from '../messages/ObjectSelect'; +import { ObjectDeselectMessage } from '../messages/ObjectDeselect'; export class GameObject implements IGameObjectData { @@ -149,6 +158,7 @@ export class GameObject implements IGameObjectData resolveAttempts = 0; claimedForBuild = false; + createdSelected = false; private static getFromXMLJS(obj: any, param: string): any { @@ -569,7 +579,98 @@ export class GameObject implements IGameObjectData go.extraParams = ExtraParams.from(buf); } } - // TODO: TaskInventory + if ((prop = this.getFromXMLJS(obj, 'TaskInventory')) !== undefined) + { + if (prop.TaskInventoryItem) + { + for (const invItemXML of prop.TaskInventoryItem) + { + const invItem = new InventoryItem(); + let subProp: any; + if ((subProp = UUID.fromXMLJS(invItemXML, 'AssetID')) !== undefined) + { + invItem.assetID = subProp; + } + if ((subProp = this.getFromXMLJS(invItemXML, 'BasePermissions')) !== undefined) + { + invItem.permissions.baseMask = subProp; + } + if ((subProp = this.getFromXMLJS(invItemXML, 'EveryonePermissions')) !== undefined) + { + invItem.permissions.everyoneMask = subProp; + } + if ((subProp = this.getFromXMLJS(invItemXML, 'GroupPermissions')) !== undefined) + { + invItem.permissions.groupMask = subProp; + } + if ((subProp = this.getFromXMLJS(invItemXML, 'NextPermissions')) !== undefined) + { + invItem.permissions.nextOwnerMask = subProp; + } + if ((subProp = this.getFromXMLJS(invItemXML, 'CurrentPermissions')) !== undefined) + { + invItem.permissions.ownerMask = subProp; + } + if ((subProp = this.getFromXMLJS(invItemXML, 'CreationDate')) !== undefined) + { + invItem.created = new Date(parseInt(subProp, 10) * 1000); + } + if ((subProp = UUID.fromXMLJS(invItemXML, 'CreatorID')) !== undefined) + { + invItem.permissions.creator = subProp; + } + if ((subProp = this.getFromXMLJS(invItemXML, 'Description')) !== undefined) + { + invItem.description = subProp; + } + if ((subProp = this.getFromXMLJS(invItemXML, 'Flags')) !== undefined) + { + invItem.flags = subProp; + } + if ((subProp = UUID.fromXMLJS(invItemXML, 'GroupID')) !== undefined) + { + invItem.permissions.group = subProp; + } + if ((subProp = this.getFromXMLJS(invItemXML, 'InvType')) !== undefined) + { + invItem.inventoryType = subProp; + } + if ((subProp = UUID.fromXMLJS(invItemXML, 'ItemID')) !== undefined) + { + invItem.itemID = subProp; + } + if ((subProp = UUID.fromXMLJS(invItemXML, 'OldItemID')) !== undefined) + { + invItem.oldItemID = subProp; + } + if ((subProp = UUID.fromXMLJS(invItemXML, 'LastOwnerID')) !== undefined) + { + invItem.permissions.lastOwner = subProp; + } + if ((subProp = this.getFromXMLJS(invItemXML, 'Name')) !== undefined) + { + invItem.name = subProp; + } + if ((subProp = UUID.fromXMLJS(invItemXML, 'OwnerID')) !== undefined) + { + invItem.permissions.owner = subProp; + } + if ((subProp = UUID.fromXMLJS(invItemXML, 'ParentID')) !== undefined) + { + invItem.parentID = subProp; + } + if ((subProp = UUID.fromXMLJS(invItemXML, 'ParentPartID')) !== undefined) + { + invItem.parentPartID = subProp; + } + if ((subProp = UUID.fromXMLJS(invItemXML, 'PermsGranter')) !== undefined) + { + invItem.permsGranter = subProp; + } + go.inventory.push(invItem); + } + } + } return go; } @@ -590,11 +691,22 @@ export class GameObject implements IGameObjectData throw new Error('SceneObjectGroup not found'); } result = result['SceneObjectGroup']; - if (!result['SceneObjectPart']) + + let rootPartXML; + if (result['SceneObjectPart']) + { + rootPartXML = result['SceneObjectPart']; + } + else if (result['RootPart'] && result['RootPart'][0] && result['RootPart'][0]['SceneObjectPart']) + { + rootPartXML = result['RootPart'][0]['SceneObjectPart']; + } + else { throw new Error('Root part not found'); } - const rootPart = GameObject.partFromXMLJS(result['SceneObjectPart'][0], true); + + const rootPart = GameObject.partFromXMLJS(rootPartXML[0], true); rootPart.children = []; rootPart.totalChildren = 0; if (result['OtherParts'] && Array.isArray(result['OtherParts']) && result['OtherParts'].length > 0) @@ -749,49 +861,6 @@ export class GameObject implements IGameObjectData await this.region.circuit.waitForAck(this.region.circuit.sendMessage(msg, PacketFlags.Reliable), 10000); } - private compareParam(name: string, param1: number | undefined, param2: number | undefined): boolean - { - if (param1 === undefined) - { - param1 = 0; - } - if (param2 === undefined) - { - param2 = 0; - } - if (Math.abs(param1 - param2) < 0.0001) - { - return true; - } - else - { - console.log('Failed ' + name + ' - ' + param1 + ' vs ' + param2); - return false; - } - } - - compareShape(obj: GameObject): boolean - { - return this.compareParam('PathCurve', this.PathCurve, obj.PathCurve) && - this.compareParam('ProfileCurve', this.ProfileCurve, obj.ProfileCurve) && - this.compareParam('PathBegin', this.PathBegin, obj.PathBegin) && - this.compareParam('PathEnd', this.PathEnd, obj.PathEnd) && - this.compareParam('PathScaleX', this.PathScaleX, obj.PathScaleX) && - this.compareParam('PathScaleY', this.PathScaleY, obj.PathScaleY) && - this.compareParam('PathShearX', this.PathShearX, obj.PathShearX) && - this.compareParam('PathShearY', this.PathShearY, obj.PathShearY) && - this.compareParam('PathTwist', this.PathTwist, obj.PathTwist) && - this.compareParam('PathTwistBegin', this.PathTwistBegin, obj.PathTwistBegin) && - this.compareParam('PathRadiusOffset', this.PathRadiusOffset, obj.PathRadiusOffset) && - this.compareParam('PathTaperX', this.PathTaperX, obj.PathTaperX) && - this.compareParam('PathTaperY', this.PathTaperY, obj.PathTaperY) && - this.compareParam('PathRevolutions', this.PathRevolutions, obj.PathRevolutions) && - this.compareParam('PathSkew', this.PathSkew, obj.PathSkew) && - this.compareParam('ProfileBegin', this.ProfileBegin, obj.ProfileBegin) && - this.compareParam('ProfileEnd', this.ProfileEnd, obj.ProfileEnd) && - this.compareParam('PRofileHollow', this.ProfileHollow, obj.ProfileHollow); - } - async setGeometry(pos?: Vector3, rot?: Quaternion, scale?: Vector3) { const data = []; @@ -1455,4 +1524,162 @@ export class GameObject implements IGameObjectData break; } } + + private async deRezObject(destination: DeRezDestination, transactionID: UUID, destFolder: UUID): Promise + { + const msg = new DeRezObjectMessage(); + + msg.AgentData = { + AgentID: this.region.agent.agentID, + SessionID: this.region.circuit.sessionID + }; + msg.AgentBlock = { + GroupID: UUID.zero(), + Destination: destination, + DestinationID: destFolder, + TransactionID: transactionID, + PacketCount: 1, + PacketNumber: 1 + }; + msg.ObjectData = [{ + ObjectLocalID: this.ID + }]; + const ack = this.region.circuit.sendMessage(msg, PacketFlags.Reliable); + return this.region.circuit.waitForAck(ack, 10000); + } + + takeToInventory(): Promise + { + const transactionID = UUID.random(); + const rootFolder = this.region.agent.inventory.getRootFolderMain(); + return new Promise((resolve, reject) => + { + + this.region.circuit.waitForMessage(Message.UpdateCreateInventoryItem, 10000, (message: UpdateCreateInventoryItemMessage) => + { + if (Utils.BufferToStringSimple(message.InventoryData[0].Name, 0) === this.name) + { + return FilterResponse.Finish; + } + else + { + return FilterResponse.NoMatch; + } + }).then((createInventoryMsg: UpdateCreateInventoryItemMessage) => + { + resolve(createInventoryMsg.InventoryData[0].ItemID); + }).catch(() => + { + reject(new Error('Timed out waiting for UpdateCreateInventoryItem')); + }); + this.deRezObject(DeRezDestination.AgentInventoryTake, transactionID, rootFolder.folderID).then(() => {}).catch((err) => + { + console.error(err); + }); + }); + } + + dropInventoryIntoContents(inventoryID: UUID): Promise + { + return new Promise(async (resolve, reject) => + { + const transactionID = UUID.random(); + const item: InventoryItem | null = await this.region.agent.inventory.fetchInventoryItem(inventoryID); + if (item === null) + { + reject(new Error('Failed to drop inventory into object contents - Inventory item ' + inventoryID.toString() + ' not found')); + return; + } + + const msg = new UpdateTaskInventoryMessage(); + msg.AgentData = { + AgentID: this.region.agent.agentID, + SessionID: this.region.circuit.sessionID + }; + msg.UpdateData = { + Key: 0, + LocalID: this.ID + }; + msg.InventoryData = { + ItemID: item.itemID, + FolderID: item.parentID, + CreatorID: item.permissions.creator, + OwnerID: item.permissions.owner, + GroupID: item.permissions.group, + BaseMask: item.permissions.baseMask, + OwnerMask: item.permissions.ownerMask, + GroupMask: item.permissions.groupMask, + EveryoneMask: item.permissions.everyoneMask, + NextOwnerMask: item.permissions.nextOwnerMask, + GroupOwned: item.permissions.groupOwned || false, + TransactionID: transactionID, + Type: item.type, + InvType: item.inventoryType, + Flags: item.flags, + SaleType: item.saleType, + SalePrice: item.salePrice, + Name: Utils.StringToBuffer(item.name), + Description: Utils.StringToBuffer(item.description), + CreationDate: item.created.getTime() / 1000, + CRC: item.getCRC() + }; + + const inventorySerial = this.inventorySerial; + this.region.circuit.waitForMessage(Message.ObjectProperties, 10000, (message: ObjectPropertiesMessage) => + { + const n = 5; + for (const obj of message.ObjectData) + { + if (obj.ObjectID.equals(this.FullID)) + { + if (obj.InventorySerial > inventorySerial) + { + return FilterResponse.Finish; + } + } + } + return FilterResponse.NoMatch; + }).then((message: ObjectPropertiesMessage) => + { + this.deselect().then(() => {}).catch(() => {}); + resolve(); + }).catch(() => + { + reject(new Error('Timed out waiting for task inventory drop')); + }); + + // We need to select the object or we won't get the objectProperties message + await this.select(); + + this.region.circuit.sendMessage(msg, PacketFlags.Reliable) + }); + } + + async select() + { + const selectObject = new ObjectSelectMessage(); + selectObject.AgentData = { + AgentID: this.region.agent.agentID, + SessionID: this.region.circuit.sessionID + }; + selectObject.ObjectData = [{ + ObjectLocalID: this.ID + }]; + const ack = this.region.circuit.sendMessage(selectObject, PacketFlags.Reliable); + await this.region.circuit.waitForAck(ack, 10000); + } + + async deselect() + { + const deselectObject = new ObjectDeselectMessage(); + deselectObject.AgentData = { + AgentID: this.region.agent.agentID, + SessionID: this.region.circuit.sessionID + }; + deselectObject.ObjectData = [{ + ObjectLocalID: this.ID + }]; + const ack = this.region.circuit.sendMessage(deselectObject, PacketFlags.Reliable); + await this.region.circuit.waitForAck(ack, 10000); + } } diff --git a/lib/enums/DeRezDestination.ts b/lib/enums/DeRezDestination.ts new file mode 100644 index 0000000..c9c6ffc --- /dev/null +++ b/lib/enums/DeRezDestination.ts @@ -0,0 +1,14 @@ +export enum DeRezDestination +{ + AgentInventorySave = 0, + AgentInventoryCopy = 1, + TaskInventory = 2, + Attachment = 3, + AgentInventoryTake = 4, + ForceToGodInventory = 5, + TrashFolder = 6, + AttachmentToInventory = 7, + AttachmentExists = 8, + ReturnToOwner = 9, + ReturnToLastOwner = 10 +} diff --git a/lib/events/ObjectResolvedEvent.ts b/lib/events/ObjectResolvedEvent.ts new file mode 100644 index 0000000..6b363ae --- /dev/null +++ b/lib/events/ObjectResolvedEvent.ts @@ -0,0 +1,6 @@ +import { GameObject } from '../classes/public/GameObject'; + +export class ObjectResolvedEvent +{ + object: GameObject +} diff --git a/lib/index.ts b/lib/index.ts index 7fd5a4e..30bc12d 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -94,6 +94,7 @@ import { ParticleSystem } from './classes/ParticleSystem'; import { ExtraParams } from './classes/public/ExtraParams'; import { LLMesh } from './classes/public/LLMesh'; import { FolderType } from './enums/FolderType'; +import { InventoryItem } from './classes/InventoryItem'; export { Bot, @@ -193,6 +194,7 @@ export { SculptData, MeshData, LLMesh, + InventoryItem, // Public Interfaces GlobalPosition, diff --git a/package-lock.json b/package-lock.json index 74a3b51..44a333a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@caspertech/node-metaverse", - "version": "0.5.12", + "version": "0.5.13", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 5d3fc27..9f21e4c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@caspertech/node-metaverse", - "version": "0.5.12", + "version": "0.5.13", "description": "A node.js interface for Second Life.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/tools/msg_template.json b/tools/msg_template.json index 6549e29..67060c2 100644 --- a/tools/msg_template.json +++ b/tools/msg_template.json @@ -25039,23 +25039,6 @@ "size": 1 } ] - }, - { - "name": "Size", - "type": "Variable", - "count": 1, - "params": [ - { - "name": "SizeX", - "type": "U16", - "size": 1 - }, - { - "name": "SizeY", - "type": "U16", - "size": 1 - } - ] } ] }, diff --git a/tools/msg_template.msg b/tools/msg_template.msg index 16b416b..e071f26 100644 --- a/tools/msg_template.msg +++ b/tools/msg_template.msg @@ -8766,11 +8766,6 @@ version 2.0 { Agents U8 } { MapImageID LLUUID } } - { - Size Variable - { SizeX U16 } - { SizeY U16 } - } } // viewer -> sim