import {CommandsBase} from './CommandsBase'; import {UUID} from '../UUID'; import * as Long from 'long'; import {RegionHandleRequestMessage} from '../messages/RegionHandleRequest'; import {Message} from '../../enums/Message'; import {FilterResponse} from '../../enums/FilterResponse'; import {RegionIDAndHandleReplyMessage} from '../messages/RegionIDAndHandleReply'; import {PacketFlags, Vector3} from '../..'; import {ObjectGrabMessage} from '../messages/ObjectGrab'; import {ObjectDeGrabMessage} from '../messages/ObjectDeGrab'; import {ObjectGrabUpdateMessage} from '../messages/ObjectGrabUpdate'; import {GameObject} from '../GameObject'; import {ObjectSelectMessage} from '../messages/ObjectSelect'; import {ObjectPropertiesMessage} from '../messages/ObjectProperties'; import {Utils} from '../Utils'; import {ObjectDeselectMessage} from '../messages/ObjectDeselect'; import {PCode} from '../../enums/PCode'; export class RegionCommands extends CommandsBase { async getRegionHandle(regionID: UUID): Promise { const circuit = this.currentRegion.circuit; const msg: RegionHandleRequestMessage = new RegionHandleRequestMessage(); msg.RequestBlock = { RegionID: regionID, }; circuit.sendMessage(msg, PacketFlags.Reliable); const responseMsg: RegionIDAndHandleReplyMessage = await circuit.waitForMessage(Message.RegionIDAndHandleReply, 10000, (filterMsg: RegionIDAndHandleReplyMessage): FilterResponse => { if (filterMsg.ReplyBlock.RegionID.toString() === regionID.toString()) { return FilterResponse.Finish; } else { return FilterResponse.NoMatch; } }); return responseMsg.ReplyBlock.RegionHandle; } async deselectObjects(objects: GameObject[]) { // Limit to 255 objects at once const selectLimit = 255; if (objects.length > selectLimit) { for (let x = 0; x < objects.length; x += selectLimit) { const selectList: GameObject[] = []; for (let y = 0; y < selectLimit; y++) { if (y < objects.length) { selectList.push(objects[x + y]); } } await this.deselectObjects(selectList); } return; } else { const deselectObject = new ObjectDeselectMessage(); deselectObject.AgentData = { AgentID: this.agent.agentID, SessionID: this.circuit.sessionID }; deselectObject.ObjectData = []; const uuidMap: {[key: string]: GameObject} = {}; for (const obj of objects) { const uuidStr = obj.FullID.toString(); if (!uuidMap[uuidStr]) { uuidMap[uuidStr] = obj; deselectObject.ObjectData.push({ ObjectLocalID: obj.ID }); } } // Create a map of our expected UUIDs const sequenceID = this.circuit.sendMessage(deselectObject, PacketFlags.Reliable); return await this.circuit.waitForAck(sequenceID, 10000); } } countObjects(): number { return this.currentRegion.objects.getNumberOfObjects(); } async selectObjects(objects: GameObject[]) { // Limit to 255 objects at once const selectLimit = 255; if (objects.length > selectLimit) { for (let x = 0; x < objects.length; x += selectLimit) { const selectList: GameObject[] = []; for (let y = 0; y < selectLimit; y++) { if (y < objects.length) { selectList.push(objects[x + y]); } } await this.selectObjects(selectList); } return; } else { const selectObject = new ObjectSelectMessage(); selectObject.AgentData = { AgentID: this.agent.agentID, SessionID: this.circuit.sessionID }; selectObject.ObjectData = []; const uuidMap: {[key: string]: GameObject} = {}; for (const obj of objects) { const uuidStr = obj.FullID.toString(); if (!uuidMap[uuidStr]) { uuidMap[uuidStr] = obj; selectObject.ObjectData.push({ ObjectLocalID: obj.ID }); } } // Create a map of our expected UUIDs let resolved = 0; this.circuit.sendMessage(selectObject, PacketFlags.Reliable); return await this.circuit.waitForMessage(Message.ObjectProperties, 10000, (propertiesMessage: ObjectPropertiesMessage): FilterResponse => { let found = false; for (const objData of propertiesMessage.ObjectData) { const objDataUUID = objData.ObjectID.toString(); if (uuidMap[objDataUUID] !== undefined) { resolved++; const obj = uuidMap[objDataUUID]; obj.creatorID = objData.CreatorID; obj.creationDate = objData.CreationDate; obj.baseMask = objData.BaseMask; obj.ownerMask = objData.OwnerMask; obj.groupMask = objData.GroupMask; obj.everyoneMask = objData.EveryoneMask; obj.nextOwnerMask = objData.NextOwnerMask; obj.ownershipCost = objData.OwnershipCost; obj.saleType = objData.SaleType; obj.salePrice = objData.SalePrice; obj.aggregatePerms = objData.AggregatePerms; obj.aggregatePermTextures = objData.AggregatePermTextures; obj.aggregatePermTexturesOwner = objData.AggregatePermTexturesOwner; obj.category = objData.Category; obj.inventorySerial = objData.InventorySerial; obj.itemID = objData.ItemID; obj.folderID = objData.FolderID; obj.fromTaskID = objData.FromTaskID; obj.lastOwnerID = objData.LastOwnerID; obj.name = Utils.BufferToStringSimple(objData.Name); obj.description = Utils.BufferToStringSimple(objData.Description); obj.touchName = Utils.BufferToStringSimple(objData.TouchName); obj.sitName = Utils.BufferToStringSimple(objData.SitName); obj.textureID = Utils.BufferToStringSimple(objData.TextureID); obj.resolvedAt = new Date().getTime() / 1000; delete uuidMap[objDataUUID]; found = true; // console.log(obj.name + ' (' + resolved + ' of ' + objects.length + ')'); } } if (Object.keys(uuidMap).length === 0) { return FilterResponse.Finish; } if (!found) { return FilterResponse.NoMatch; } else { return FilterResponse.Match; } }); } } private async resolveObjects(objects: GameObject[]) { // First, create a map of all object IDs const objs: {[key: number]: GameObject} = {}; const scanObject = function(obj: GameObject) { const localID = obj.ID; if (!objs[localID]) { objs[localID] = obj; if (obj.children) { for (const child of obj.children) { scanObject(child); } } } }; for (const obj of objects) { scanObject(obj); } const resolveTime = new Date().getTime() / 1000; let objectList = []; let totalRemaining = 0; try { for (const k of Object.keys(objs)) { const ky = parseInt(k, 10); if (objs[ky] !== undefined) { const o = objs[ky]; if (o.resolvedAt === undefined) { o.resolvedAt = 0; } if (o.resolvedAt !== undefined && o.resolvedAt < resolveTime && o.PCode !== PCode.Avatar) { objs[ky].name = undefined; totalRemaining++; objectList.push(objs[ky]); if (objectList.length > 254) { try { await this.selectObjects(objectList); await this.deselectObjects(objectList); for (const chk of objectList) { if (chk.resolvedAt !== undefined && chk.resolvedAt >= resolveTime) { totalRemaining--; } } } catch (ignore) { } finally { objectList = []; } } } } } if (objectList.length > 0) { await this.selectObjects(objectList); await this.deselectObjects(objectList); for (const chk of objectList) { if (chk.resolvedAt !== undefined && chk.resolvedAt >= resolveTime) { totalRemaining --; } } } } catch (ignore) { } finally { if (totalRemaining < 1) { totalRemaining = 0; for (const obj of objectList) { if (obj.resolvedAt === undefined || obj.resolvedAt < resolveTime) { totalRemaining++; } } if (totalRemaining > 0) { console.error(totalRemaining + ' objects could not be resolved'); } } } } async getAllObjects(resolve: boolean = false): Promise { const objs = this.currentRegion.objects.getAllObjects(); if (resolve) { await this.resolveObjects(objs); } return objs; } async getObjectsInArea(minX: number, maxX: number, minY: number, maxY: number, minZ: number, maxZ: number, resolve: boolean = false): Promise { const objs = this.currentRegion.objects.getObjectsInArea(minX, maxX, minY, maxY, minZ, maxZ); if (resolve) { await this.resolveObjects(objs); } return objs; } async grabObject(localID: number | UUID, grabOffset: Vector3 = Vector3.getZero(), uvCoordinate: Vector3 = Vector3.getZero(), stCoordinate: Vector3 = Vector3.getZero(), faceIndex: number = 0, position: Vector3 = Vector3.getZero(), normal: Vector3 = Vector3.getZero(), binormal: Vector3 = Vector3.getZero()) { if (localID instanceof UUID) { const obj: GameObject = this.currentRegion.objects.getObjectByUUID(localID); localID = obj.ID; } const msg = new ObjectGrabMessage(); msg.AgentData = { AgentID: this.agent.agentID, SessionID: this.circuit.sessionID }; msg.ObjectData = { LocalID: localID, GrabOffset: grabOffset }; msg.SurfaceInfo = [ { UVCoord: uvCoordinate, STCoord: stCoordinate, FaceIndex: faceIndex, Position: position, Normal: normal, Binormal: binormal } ]; const seqID = this.circuit.sendMessage(msg, PacketFlags.Reliable); await this.circuit.waitForAck(seqID, 10000); } async deGrabObject(localID: number | UUID, grabOffset: Vector3 = Vector3.getZero(), uvCoordinate: Vector3 = Vector3.getZero(), stCoordinate: Vector3 = Vector3.getZero(), faceIndex: number = 0, position: Vector3 = Vector3.getZero(), normal: Vector3 = Vector3.getZero(), binormal: Vector3 = Vector3.getZero()) { if (localID instanceof UUID) { const obj: GameObject = this.currentRegion.objects.getObjectByUUID(localID); localID = obj.ID; } const msg = new ObjectDeGrabMessage(); msg.AgentData = { AgentID: this.agent.agentID, SessionID: this.circuit.sessionID }; msg.ObjectData = { LocalID: localID }; msg.SurfaceInfo = [ { UVCoord: uvCoordinate, STCoord: stCoordinate, FaceIndex: faceIndex, Position: position, Normal: normal, Binormal: binormal } ]; const seqID = this.circuit.sendMessage(msg, PacketFlags.Reliable); await this.circuit.waitForAck(seqID, 10000); } async dragGrabbedObject(localID: number | UUID, grabPosition: Vector3, grabOffset: Vector3 = Vector3.getZero(), uvCoordinate: Vector3 = Vector3.getZero(), stCoordinate: Vector3 = Vector3.getZero(), faceIndex: number = 0, position: Vector3 = Vector3.getZero(), normal: Vector3 = Vector3.getZero(), binormal: Vector3 = Vector3.getZero()) { // For some reason this message takes a UUID when the others take a LocalID - wtf? if (!(localID instanceof UUID)) { const obj: GameObject = this.currentRegion.objects.getObjectByLocalID(localID); localID = obj.FullID; } const msg = new ObjectGrabUpdateMessage(); msg.AgentData = { AgentID: this.agent.agentID, SessionID: this.circuit.sessionID }; msg.ObjectData = { ObjectID: localID, GrabOffsetInitial: grabOffset, GrabPosition: grabPosition, TimeSinceLast: 0 }; msg.SurfaceInfo = [ { UVCoord: uvCoordinate, STCoord: stCoordinate, FaceIndex: faceIndex, Position: position, Normal: normal, Binormal: binormal } ]; const seqID = this.circuit.sendMessage(msg, PacketFlags.Reliable); await this.circuit.waitForAck(seqID, 10000); } async touchObject(localID: number | UUID, grabOffset: Vector3 = Vector3.getZero(), uvCoordinate: Vector3 = Vector3.getZero(), stCoordinate: Vector3 = Vector3.getZero(), faceIndex: number = 0, position: Vector3 = Vector3.getZero(), normal: Vector3 = Vector3.getZero(), binormal: Vector3 = Vector3.getZero()) { if (localID instanceof UUID) { const obj: GameObject = this.currentRegion.objects.getObjectByUUID(localID); localID = obj.ID; } await this.grabObject(localID, grabOffset, uvCoordinate, stCoordinate, faceIndex, position, normal, binormal); await this.deGrabObject(localID, grabOffset, uvCoordinate, stCoordinate, faceIndex, position, normal, binormal); } }