From f793774ee92eeeed0f2b5939758497f13765b1f4 Mon Sep 17 00:00:00 2001 From: Casper Warden <216465704+casperwardensl@users.noreply.github.com> Date: Tue, 31 Dec 2019 02:23:30 +0000 Subject: [PATCH] [Closes #13] Add ParcelPropertiesRequest with parcel bitmap decoding to ensure the correct reply is given. Heavily optimise getParcels() so that it uses the parcelOverlay to intelligently request parcels at known locations rather than iterating over every block. --- example/testBot.js | 12 + lib/classes/Region.ts | 584 +++++++++++++++++-------- lib/classes/commands/RegionCommands.ts | 67 +-- lib/classes/interfaces/ILandBlock.ts | 9 + lib/enums/LandFlags.ts | 7 + lib/enums/LandType.ts | 9 + package-lock.json | 2 +- package.json | 2 +- 8 files changed, 465 insertions(+), 227 deletions(-) create mode 100644 lib/classes/interfaces/ILandBlock.ts create mode 100644 lib/enums/LandFlags.ts create mode 100644 lib/enums/LandType.ts diff --git a/example/testBot.js b/example/testBot.js index 982fc53..948ddb7 100644 --- a/example/testBot.js +++ b/example/testBot.js @@ -312,6 +312,18 @@ async function connect() //await bot.clientCommands.friends.grantFriendRights('d1cd5b71-6209-4595-9bf0-771bf689ce00', nmv.RightsFlags.CanModifyObjects | nmv.RightsFlags.CanSeeOnline | nmv.RightsFlags.CanSeeOnMap ); + + const parcelInMiddle = await bot.clientCommands.region.getParcelAt(128, 128); + console.log('Parcel at 128x128 is ' + parcelInMiddle.Name); + + const parcels = await bot.clientCommands.region.getParcels(); + console.log('Parcels on region:'); + console.log('========================'); + for (const p of parcels) + { + console.log(p.Name); + } + console.log('========================'); } catch (error) { diff --git a/lib/classes/Region.ts b/lib/classes/Region.ts index b7cb9fe..d93b561 100644 --- a/lib/classes/Region.ts +++ b/lib/classes/Region.ts @@ -1,41 +1,46 @@ -import {Circuit} from './Circuit'; -import {Agent} from './Agent'; -import {Caps} from './Caps'; -import {Comms} from './Comms'; -import {ClientEvents} from './ClientEvents'; -import {IObjectStore} from './interfaces/IObjectStore'; -import {ObjectStoreFull} from './ObjectStoreFull'; -import {ObjectStoreLite} from './ObjectStoreLite'; -import {BotOptionFlags, PacketFlags, ParcelPropertiesEvent, RegionFlags, UUID, Vector2, Vector3} from '..'; -import {RequestRegionInfoMessage} from './messages/RequestRegionInfo'; -import {RegionInfoMessage} from './messages/RegionInfo'; -import {Message} from '../enums/Message'; -import {Utils} from './Utils'; -import {RegionHandshakeMessage} from './messages/RegionHandshake'; -import {MapNameRequestMessage} from './messages/MapNameRequest'; -import {GridLayerType} from '../enums/GridLayerType'; -import {MapBlockReplyMessage} from './messages/MapBlockReply'; -import {FilterResponse} from '../enums/FilterResponse'; +import { Circuit } from './Circuit'; +import { Agent } from './Agent'; +import { Caps } from './Caps'; +import { Comms } from './Comms'; +import { ClientEvents } from './ClientEvents'; +import { IObjectStore } from './interfaces/IObjectStore'; +import { ObjectStoreFull } from './ObjectStoreFull'; +import { ObjectStoreLite } from './ObjectStoreLite'; +import { BotOptionFlags, PacketFlags, ParcelPropertiesEvent, RegionFlags, UUID, Vector2, Vector3 } from '..'; +import { RequestRegionInfoMessage } from './messages/RequestRegionInfo'; +import { RegionInfoMessage } from './messages/RegionInfo'; +import { Message } from '../enums/Message'; +import { Utils } from './Utils'; +import { RegionHandshakeMessage } from './messages/RegionHandshake'; +import { MapNameRequestMessage } from './messages/MapNameRequest'; +import { GridLayerType } from '../enums/GridLayerType'; +import { MapBlockReplyMessage } from './messages/MapBlockReply'; +import { FilterResponse } from '../enums/FilterResponse'; import * as Long from 'long'; -import {Packet} from './Packet'; -import {LayerDataMessage} from './messages/LayerData'; -import {LayerType} from '../enums/LayerType'; -import {Subscription} from 'rxjs/internal/Subscription'; -import {BitPack} from './BitPack'; +import { Packet } from './Packet'; +import { LayerDataMessage } from './messages/LayerData'; +import { LayerType } from '../enums/LayerType'; +import { Subscription } from 'rxjs/internal/Subscription'; +import { BitPack } from './BitPack'; import * as builder from 'xmlbuilder'; -import {SimAccessFlags} from '../enums/SimAccessFlags'; -import {Subject} from 'rxjs/internal/Subject'; -import {ParcelDwellRequestMessage} from './messages/ParcelDwellRequest'; -import {ParcelDwellReplyMessage} from './messages/ParcelDwellReply'; +import { SimAccessFlags } from '../enums/SimAccessFlags'; +import { Subject } from 'rxjs/internal/Subject'; +import { ParcelDwellRequestMessage } from './messages/ParcelDwellRequest'; +import { ParcelDwellReplyMessage } from './messages/ParcelDwellReply'; +import { Parcel } from './public/Parcel'; +import { RegionEnvironment } from './public/RegionEnvironment'; +import { Color4 } from './Color4'; +import { SkyPreset } from './public/interfaces/SkyPreset'; +import { Vector4 } from './Vector4'; +import { WaterPreset } from './public/interfaces/WaterPreset'; +import { ClientCommands } from './ClientCommands'; +import { SimulatorViewerTimeMessageMessage } from './messages/SimulatorViewerTimeMessage'; +import { ParcelOverlayMessage } from './messages/ParcelOverlay'; +import { ILandBlock } from './interfaces/ILandBlock'; +import { LandFlags } from '../enums/LandFlags'; +import { ParcelPropertiesRequestMessage } from './messages/ParcelPropertiesRequest'; +import { ParcelPropertiesMessage } from './messages/ParcelProperties'; import Timer = NodeJS.Timer; -import {Parcel} from './public/Parcel'; -import {RegionEnvironment} from './public/RegionEnvironment'; -import {Color4} from './Color4'; -import {SkyPreset} from './public/interfaces/SkyPreset'; -import {Vector4} from './Vector4'; -import {WaterPreset} from './public/interfaces/WaterPreset'; -import {ClientCommands} from './ClientCommands'; -import {SimulatorViewerTimeMessageMessage} from './messages/SimulatorViewerTimeMessage'; export class Region { @@ -48,6 +53,8 @@ export class Region regionName: string; regionOwner: UUID; regionID: UUID; + regionSizeX = 256; + regionSizeY = 256; regionHandle: Long; xCoordinate: number; yCoordinate: number; @@ -114,14 +121,23 @@ export class Region parcelsComplete = false; parcelsCompleteEvent: Subject = new Subject(); + parcelOverlayComplete = false; + parcelOverlayCompleteEvent: Subject = new Subject(); + + parcelOverlay: ILandBlock[] = []; parcels: {[key: number]: Parcel} = {}; parcelsByUUID: {[key: string]: Parcel} = {}; parcelMap: number[][] = []; + + parcelCoordinates: {x: number, y: number}[] = []; + environment: RegionEnvironment; timeOffset = 0; + private parcelOverlayReceived: {[key: number]: Buffer} = {}; + static IDCTColumn16(linein: number[], lineout: number[], column: number) { let total: number; @@ -257,6 +273,17 @@ export class Region this.setup = true; } + private static doesBitmapContainCoordinate(bitmap: Buffer, x: number, y: number): boolean + { + const mapBlockX = Math.floor(x / 4); + const mapBlockY = Math.floor(y / 4); + + let index = (mapBlockY * 64) + mapBlockX; + const bit = index % 8; + index >>= 3; + return ((bitmap[index] & (1 << bit)) !== 0); + } + constructor(agent: Agent, clientEvents: ClientEvents, options: BotOptionFlags) { if (!Region.setup) @@ -295,141 +322,92 @@ export class Region this.parcelPropertiesSubscription = this.clientEvents.onParcelPropertiesEvent.subscribe(async (parcelProperties: ParcelPropertiesEvent) => { - // Get the parcel UUID - const msg = new ParcelDwellRequestMessage(); - msg.AgentData = { - AgentID: this.agent.agentID, - SessionID: this.circuit.sessionID - }; - msg.Data = { - LocalID: parcelProperties.LocalID, - ParcelID: UUID.zero() - }; - - this.circuit.sendMessage(msg, PacketFlags.Reliable); - const dwellReply = await this.circuit.waitForMessage(Message.ParcelDwellReply, 1000, (message: ParcelDwellReplyMessage): FilterResponse => - { - if (message.Data.LocalID === parcelProperties.LocalID) - { - return FilterResponse.Finish; - } - else - { - return FilterResponse.NoMatch; - } - }); - const parcelID: string = dwellReply.Data.ParcelID.toString(); - let parcel = new Parcel(); - if (this.parcelsByUUID[parcelID]) - { - parcel = this.parcelsByUUID[parcelID]; - } - parcel.LocalID = parcelProperties.LocalID; - parcel.ParcelID = dwellReply.Data.ParcelID; - parcel.RegionDenyAgeUnverified = parcelProperties.RegionDenyTransacted; - parcel.MediaDesc = parcelProperties.MediaDesc; - parcel.MediaHeight = parcelProperties.MediaHeight; - parcel.MediaLoop = parcelProperties.MediaLoop; - parcel.MediaType = parcelProperties.MediaType; - parcel.MediaWidth = parcelProperties.MediaWidth; - parcel.ObscureMedia = parcelProperties.ObscureMedia; - parcel.ObscureMusic = parcelProperties.ObscureMusic; - parcel.AABBMax = parcelProperties.AABBMax; - parcel.AABBMin = parcelProperties.AABBMin; - parcel.AnyAVSounds = parcelProperties.AnyAVSounds; - parcel.Area = parcelProperties.Area; - parcel.AuctionID = parcelProperties.AuctionID; - parcel.AuthBuyerID = parcelProperties.AuthBuyerID; - parcel.Bitmap = parcelProperties.Bitmap; - parcel.Category = parcelProperties.Category; - parcel.ClaimDate = parcelProperties.ClaimDate; - parcel.ClaimPrice = parcelProperties.ClaimPrice; - parcel.Desc = parcelProperties.Desc; - parcel.GroupAVSounds = parcelProperties.GroupAVSounds; - parcel.GroupID = parcelProperties.GroupID; - parcel.GroupPrims = parcelProperties.GroupPrims; - parcel.IsGroupOwned = parcelProperties.IsGroupOwned; - parcel.LandingType = parcelProperties.LandingType; - parcel.MaxPrims = parcelProperties.MaxPrims; - parcel.MediaAutoScale = parcelProperties.MediaAutoScale; - parcel.MediaID = parcelProperties.MediaID; - parcel.MediaURL = parcelProperties.MediaURL; - parcel.MusicURL = parcelProperties.MusicURL; - parcel.Name = parcelProperties.Name; - parcel.OtherCleanTime = parcelProperties.OtherCleanTime; - parcel.OtherCount = parcelProperties.OtherCount; - parcel.OtherPrims = parcelProperties.OtherPrims; - parcel.OwnerID = parcelProperties.OwnerID; - parcel.OwnerPrims = parcelProperties.OwnerPrims; - parcel.ParcelFlags = parcelProperties.ParcelFlags; - parcel.ParcelPrimBonus = parcelProperties.ParcelPrimBonus; - parcel.PassHours = parcelProperties.PassHours; - parcel.PassPrice = parcelProperties.PassPrice; - parcel.PublicCount = parcelProperties.PublicCount; - parcel.RegionDenyAnonymous = parcelProperties.RegionDenyAnonymous; - parcel.RegionDenyIdentified = parcelProperties.RegionDenyIdentified; - parcel.RegionPushOverride = parcelProperties.RegionPushOverride; - parcel.RegionDenyTransacted = parcelProperties.RegionDenyTransacted; - parcel.RentPrice = parcelProperties.RentPrice; - parcel.RequestResult = parcelProperties.RequestResult; - parcel.SalePrice = parcelProperties.SalePrice; - parcel.SeeAvs = parcelProperties.SeeAvs; - parcel.SelectedPrims = parcelProperties.SelectedPrims; - parcel.SelfCount = parcelProperties.SelfCount; - parcel.SequenceID = parcelProperties.SequenceID; - parcel.SimWideMaxPrims = parcelProperties.SimWideMaxPrims; - parcel.SimWideTotalPrims = parcelProperties.SimWideTotalPrims; - parcel.SnapSelection = parcelProperties.SnapSelection; - parcel.SnapshotID = parcelProperties.SnapshotID; - parcel.Status = parcelProperties.Status; - parcel.TotalPrims = parcelProperties.TotalPrims; - parcel.UserLocation = parcelProperties.UserLocation; - parcel.UserLookAt = parcelProperties.UserLookAt; - parcel.RegionAllowAccessOverride = parcelProperties.RegionAllowAccessOverride; - this.parcels[parcelProperties.LocalID] = parcel; - - let foundEmpty = false; - for (let y = 0; y < 64; y++) - { - for (let x = 0; x < 64; x++) - { - let index = (y * 64) + x; - const bit = index % 8; - index >>= 3; - if ((parcel.Bitmap[index] & (1 << bit)) !== 0) - { - this.parcelMap[y][x] = parcel.LocalID; - } - else - { - if (this.parcelMap[y][x] === 0) - { - foundEmpty = true; - } - } - } - } - if (foundEmpty === false) - { - if (this.parcelsComplete === false) - { - this.parcelsComplete = true; - this.parcelsCompleteEvent.next(); - } - } - else if (this.parcelsComplete === true) - { - this.parcelsComplete = false; - } + await this.resolveParcel(parcelProperties); }); this.messageSubscription = this.circuit.subscribeToMessages([ + Message.ParcelOverlay, Message.LayerData, Message.SimulatorViewerTimeMessage ], (packet: Packet) => { switch (packet.message.id) { + case Message.ParcelOverlay: + { + const parcelData: ParcelOverlayMessage = packet.message as ParcelOverlayMessage; + const sequence = parcelData.ParcelData.SequenceID; + + if (this.parcelOverlayReceived[sequence] !== undefined) + { + this.parcelOverlayReceived = {}; + } + + this.parcelOverlayReceived[sequence] = parcelData.ParcelData.Data; + let totalLength = 0; + let highestSeq = 0; + for (const seq of Object.keys(this.parcelOverlayReceived)) + { + const sequenceID: number = parseInt(seq, 10); + totalLength += this.parcelOverlayReceived[sequenceID].length; + if (sequenceID > highestSeq) + { + highestSeq = sequenceID; + } + } + if (totalLength !== (this.regionSizeX / 4) * (this.regionSizeY / 4)) + { + // Overlay is incomplete + return; + } + for (let x = 0; x <= highestSeq; x++) + { + if (this.parcelOverlayReceived[x] === undefined) + { + // Overlay is incomplete + return; + } + } + + this.parcelOverlay = []; + for (let seq = 0; seq <= highestSeq; seq++) + { + const data = this.parcelOverlayReceived[seq]; + for (let x = 0; x < data.length; x++) + { + const block = data.readUInt8(x); + this.parcelOverlay.push({ + landType: block & 0xF, + landFlags: block & ~ 0xF, + parcelID: -1 + }); + } + } + + this.parcelCoordinates = []; + let currentParcelID = 0; + for (let y = 63; y > -1; y--) + { + for (let x = 63; x > -1; x--) + { + if (this.parcelOverlay[(y * 64) + x].parcelID === -1) + { + this.parcelCoordinates.push({x, y}); + currentParcelID++; + this.fillParcel(currentParcelID, x, y); + } + } + } + + if (!this.parcelOverlayComplete) + { + this.parcelOverlayComplete = true; + this.parcelOverlayCompleteEvent.next(); + } + + this.parcelOverlayReceived = {}; + break; + } case Message.LayerData: { const layerData: LayerDataMessage = packet.message as LayerDataMessage; @@ -592,29 +570,264 @@ export class Region } }) } - getParcels(): Parcel[] + + private async resolveParcel(parcelProperties: ParcelPropertiesEvent): Promise { - const found: {[key: number]: Parcel} = {}; + // Get the parcel UUID + const msg = new ParcelDwellRequestMessage(); + msg.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.circuit.sessionID + }; + msg.Data = { + LocalID: parcelProperties.LocalID, + ParcelID: UUID.zero() + }; + this.circuit.sendMessage(msg, PacketFlags.Reliable); + const dwellReply = await this.circuit.waitForMessage(Message.ParcelDwellReply, 10000, (message: ParcelDwellReplyMessage): FilterResponse => + { + if (message.Data.LocalID === parcelProperties.LocalID) + { + return FilterResponse.Finish; + } + else + { + return FilterResponse.NoMatch; + } + }); + const parcelID: string = dwellReply.Data.ParcelID.toString(); + let parcel = new Parcel(); + if (this.parcelsByUUID[parcelID]) + { + parcel = this.parcelsByUUID[parcelID]; + } + parcel.LocalID = parcelProperties.LocalID; + parcel.ParcelID = dwellReply.Data.ParcelID; + parcel.RegionDenyAgeUnverified = parcelProperties.RegionDenyTransacted; + parcel.MediaDesc = parcelProperties.MediaDesc; + parcel.MediaHeight = parcelProperties.MediaHeight; + parcel.MediaLoop = parcelProperties.MediaLoop; + parcel.MediaType = parcelProperties.MediaType; + parcel.MediaWidth = parcelProperties.MediaWidth; + parcel.ObscureMedia = parcelProperties.ObscureMedia; + parcel.ObscureMusic = parcelProperties.ObscureMusic; + parcel.AABBMax = parcelProperties.AABBMax; + parcel.AABBMin = parcelProperties.AABBMin; + parcel.AnyAVSounds = parcelProperties.AnyAVSounds; + parcel.Area = parcelProperties.Area; + parcel.AuctionID = parcelProperties.AuctionID; + parcel.AuthBuyerID = parcelProperties.AuthBuyerID; + parcel.Bitmap = parcelProperties.Bitmap; + parcel.Category = parcelProperties.Category; + parcel.ClaimDate = parcelProperties.ClaimDate; + parcel.ClaimPrice = parcelProperties.ClaimPrice; + parcel.Desc = parcelProperties.Desc; + parcel.GroupAVSounds = parcelProperties.GroupAVSounds; + parcel.GroupID = parcelProperties.GroupID; + parcel.GroupPrims = parcelProperties.GroupPrims; + parcel.IsGroupOwned = parcelProperties.IsGroupOwned; + parcel.LandingType = parcelProperties.LandingType; + parcel.MaxPrims = parcelProperties.MaxPrims; + parcel.MediaAutoScale = parcelProperties.MediaAutoScale; + parcel.MediaID = parcelProperties.MediaID; + parcel.MediaURL = parcelProperties.MediaURL; + parcel.MusicURL = parcelProperties.MusicURL; + parcel.Name = parcelProperties.Name; + parcel.OtherCleanTime = parcelProperties.OtherCleanTime; + parcel.OtherCount = parcelProperties.OtherCount; + parcel.OtherPrims = parcelProperties.OtherPrims; + parcel.OwnerID = parcelProperties.OwnerID; + parcel.OwnerPrims = parcelProperties.OwnerPrims; + parcel.ParcelFlags = parcelProperties.ParcelFlags; + parcel.ParcelPrimBonus = parcelProperties.ParcelPrimBonus; + parcel.PassHours = parcelProperties.PassHours; + parcel.PassPrice = parcelProperties.PassPrice; + parcel.PublicCount = parcelProperties.PublicCount; + parcel.RegionDenyAnonymous = parcelProperties.RegionDenyAnonymous; + parcel.RegionDenyIdentified = parcelProperties.RegionDenyIdentified; + parcel.RegionPushOverride = parcelProperties.RegionPushOverride; + parcel.RegionDenyTransacted = parcelProperties.RegionDenyTransacted; + parcel.RentPrice = parcelProperties.RentPrice; + parcel.RequestResult = parcelProperties.RequestResult; + parcel.SalePrice = parcelProperties.SalePrice; + parcel.SeeAvs = parcelProperties.SeeAvs; + parcel.SelectedPrims = parcelProperties.SelectedPrims; + parcel.SelfCount = parcelProperties.SelfCount; + parcel.SequenceID = parcelProperties.SequenceID; + parcel.SimWideMaxPrims = parcelProperties.SimWideMaxPrims; + parcel.SimWideTotalPrims = parcelProperties.SimWideTotalPrims; + parcel.SnapSelection = parcelProperties.SnapSelection; + parcel.SnapshotID = parcelProperties.SnapshotID; + parcel.Status = parcelProperties.Status; + parcel.TotalPrims = parcelProperties.TotalPrims; + parcel.UserLocation = parcelProperties.UserLocation; + parcel.UserLookAt = parcelProperties.UserLookAt; + parcel.RegionAllowAccessOverride = parcelProperties.RegionAllowAccessOverride; + this.parcels[parcelProperties.LocalID] = parcel; + + let foundEmpty = false; for (let y = 0; y < 64; y++) { for (let x = 0; x < 64; x++) { - if (this.parcelMap[y][x] !== 0) + if (Region.doesBitmapContainCoordinate(parcel.Bitmap, x * 4, y * 4)) { - const localID = this.parcelMap[y][x]; - if (!found[localID]) + this.parcelMap[y][x] = parcel.LocalID; + } + else + { + if (this.parcelMap[y][x] === 0) { - found[localID] = this.parcels[localID]; + foundEmpty = true; } } } } - const result: Parcel[] = []; - for (const key of Object.keys(found)) + if (!foundEmpty) { - result.push(found[parseInt(key, 10)]); + if (!this.parcelsComplete) + { + this.parcelsComplete = true; + this.parcelsCompleteEvent.next(); + } } - return result; + else if (this.parcelsComplete) + { + this.parcelsComplete = false; + } + return parcel; + } + + /* // This was useful for debugging, so leaving it here for the future + private drawParcelMap() + { + console.log('===================================================='); + for (let y2 = 63; y2 > -1; y2--) + { + let row = ''; + for (let x2 = 0; x2 < 64; x2++) + { + const parcelID = this.parcelOverlay[(y2 * 64) + x2].parcelID; + if (parcelID === -1) + { + row += 'X'; + } + else if (parcelID < 10) + { + row += String(parcelID); + } + else + { + row += '#'; + } + } + console.log(row); + } + } + */ + + private fillParcel(parcelID: number, x: number, y: number) + { + if ( x < 0 || y < 0 || x > 63 || y > 63) + { + return; + } + if (this.parcelOverlay[(y * 64) + x].parcelID !== -1) + { + return; + } + this.parcelOverlay[(y * 64) + x].parcelID = parcelID; + const flags = this.parcelOverlay[(y * 64) + x].landFlags; + if (!(flags & LandFlags.BorderSouth)) + { + this.fillParcel(parcelID, x, y - 1); + } + if (!(flags & LandFlags.BorderWest)) + { + this.fillParcel(parcelID, x - 1, y); + } + if (x < 63 && !(this.parcelOverlay[(y * 64) + (x + 1)].landFlags & LandFlags.BorderWest)) + { + this.fillParcel(parcelID, x + 1, y); + } + if (y < 63 && !(this.parcelOverlay[((y + 1) * 64) + x].landFlags & LandFlags.BorderSouth)) + { + this.fillParcel(parcelID, x, y + 1); + } + } + + public getParcelProperties(x: number, y: number): Promise + { + return new Promise((resolve, reject) => + { + const request = new ParcelPropertiesRequestMessage(); + request.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.circuit.sessionID + }; + request.ParcelData = { + North: y + 1, + East: x + 1, + South: y, + West: x, + SequenceID: -10000, + SnapSelection: false + }; + this.circuit.sendMessage(request, PacketFlags.Reliable); + let messageAwait: Subscription | undefined = undefined; + let messageWaitTimer: number | undefined = undefined; + + messageAwait = this.clientEvents.onParcelPropertiesEvent.subscribe(async (parcelProperties: ParcelPropertiesEvent) => + { + if (Region.doesBitmapContainCoordinate(parcelProperties.Bitmap, x, y)) + { + if (messageAwait !== undefined) + { + messageAwait.unsubscribe(); + messageAwait = undefined; + } + if (messageWaitTimer !== undefined) + { + clearTimeout(messageWaitTimer); + messageWaitTimer = undefined; + } + resolve(await this.resolveParcel(parcelProperties)); + } + }); + + messageWaitTimer = setTimeout(() => + { + if (messageAwait !== undefined) + { + messageAwait.unsubscribe(); + messageAwait = undefined; + } + if (messageWaitTimer !== undefined) + { + clearTimeout(messageWaitTimer); + messageWaitTimer = undefined; + } + reject(new Error('Timed out')); + }, 10000) as any as number; + }); + } + + async getParcels(): Promise + { + await this.waitForParcelOverlay(); + const parcels: Parcel[] = []; + for (const parcel of this.parcelCoordinates) + { + try + { + parcels.push(await this.getParcelProperties(parcel.x * 4.0, parcel.y * 4.0)); + } + catch (error) + { + console.error(error); + } + } + return parcels; } resetParcels(): void @@ -633,6 +846,35 @@ export class Region this.parcelsComplete = false; } + waitForParcelOverlay(): Promise + { + return new Promise((resolve, reject) => + { + if (this.parcelOverlayComplete) + { + resolve(); + } + else + { + let timeout: Timer | null = null; + const subscription = this.parcelOverlayCompleteEvent.subscribe(() => + { + if (timeout !== null) + { + clearTimeout(timeout); + } + subscription.unsubscribe(); + resolve(); + }); + timeout = setTimeout(() => + { + subscription.unsubscribe(); + reject(new Error('Timeout waiting for parcel overlay')); + }, 10000); + } + }); + } + waitForParcels(): Promise { return new Promise((resolve, reject) => diff --git a/lib/classes/commands/RegionCommands.ts b/lib/classes/commands/RegionCommands.ts index 4175b71..a3c96e1 100644 --- a/lib/classes/commands/RegionCommands.ts +++ b/lib/classes/commands/RegionCommands.ts @@ -25,7 +25,6 @@ import {Utils} from '../Utils'; import {ObjectDeselectMessage} from '../messages/ObjectDeselect'; import * as micromatch from 'micromatch'; import * as LLSD from '@caspertech/llsd'; -import {ParcelPropertiesRequestMessage} from '../messages/ParcelPropertiesRequest'; import {RequestTaskInventoryMessage} from '../messages/RequestTaskInventory'; import {ReplyTaskInventoryMessage} from '../messages/ReplyTaskInventory'; import {InventoryItem} from '../InventoryItem'; @@ -37,9 +36,7 @@ import {Quaternion} from '../Quaternion'; import Timer = NodeJS.Timer; import {RezObjectMessage} from '../messages/RezObject'; import {PermissionMask} from '../../enums/PermissionMask'; -import {from} from 'rxjs'; import {SelectedObjectEvent} from '../../events/SelectedObjectEvent'; -import uuid = require('uuid'); export class RegionCommands extends CommandsBase { @@ -807,14 +804,14 @@ export class RegionCommands extends CommandsBase }); } - private waitForObjectByUUID(uuid: UUID, timeout: number): Promise + private waitForObjectByUUID(objectID: UUID, timeout: number): Promise { return new Promise((resolve, reject) => { let tmr: Timer | null = null; const subscription = this.currentRegion.clientEvents.onNewObjectEvent.subscribe(async (event: NewObjectEvent) => { - if (event.objectID.equals(uuid)) + if (event.objectID.equals(objectID)) { if (tmr !== null) { @@ -904,12 +901,6 @@ export class RegionCommands extends CommandsBase }); } - private echo(st: string): boolean - { - //console.log(st); - return true; - } - createPrim(obj: GameObject, posOffset: Vector3, inventoryID?: UUID): Promise { console.log('Create prim'); @@ -1172,11 +1163,11 @@ export class RegionCommands extends CommandsBase if (match) { - const uuid = go.FullID.toString(); - if (!idCheck[uuid]) + const fullID = go.FullID.toString(); + if (!idCheck[fullID]) { matches.push(go); - idCheck[uuid] = true; + idCheck[fullID] = true; } } } @@ -1195,45 +1186,13 @@ export class RegionCommands extends CommandsBase return matches; } - async getParcels(): Promise + getParcelAt(x: number, y: number): Promise + { + return this.currentRegion.getParcelProperties(x, y); + } + + getParcels(): Promise { - this.currentRegion.resetParcels(); - for (let y = 0; y < 64; y++) - { - for (let x = 0; x < 64; x++) - { - if (this.currentRegion.parcelMap[y][x] === 0) - { - const request = new ParcelPropertiesRequestMessage(); - request.AgentData = { - AgentID: this.agent.agentID, - SessionID: this.circuit.sessionID - }; - request.ParcelData = { - North: (y + 1) * 4.0, - East: (x + 1) * 4.0, - South: y * 4.0, - West: x * 4.0, - SequenceID: 2147483647, - SnapSelection: false - }; - const seqNo = this.circuit.sendMessage(request, PacketFlags.Reliable); - await this.circuit.waitForAck(seqNo, 10000); - // Wait a second until we request the next one - await function() - { - return new Promise((resolve, reject) => - { - setTimeout(() => - { - resolve(); - }, 1000); - }) - }(); - } - } - } - await this.currentRegion.waitForParcels(); return this.currentRegion.getParcels(); } @@ -1304,8 +1263,8 @@ export class RegionCommands extends CommandsBase let found = false; if (o.FullID) { - const uuid = o.FullID.toString(); - if (stillAlive[uuid]) + const fullID = o.FullID.toString(); + if (stillAlive[fullID]) { found = true; } diff --git a/lib/classes/interfaces/ILandBlock.ts b/lib/classes/interfaces/ILandBlock.ts new file mode 100644 index 0000000..d89127a --- /dev/null +++ b/lib/classes/interfaces/ILandBlock.ts @@ -0,0 +1,9 @@ +import { LandFlags } from '../../enums/LandFlags'; +import { LandType } from '../../enums/LandType'; + +export interface ILandBlock +{ + landType: LandType; + landFlags: LandFlags; + parcelID: number; +} diff --git a/lib/enums/LandFlags.ts b/lib/enums/LandFlags.ts new file mode 100644 index 0000000..d9d5bf2 --- /dev/null +++ b/lib/enums/LandFlags.ts @@ -0,0 +1,7 @@ +export enum LandFlags +{ + HideAvatars = 0x10, + LocalSound = 0x20, + BorderWest = 0x40, + BorderSouth = 0x80 +} diff --git a/lib/enums/LandType.ts b/lib/enums/LandType.ts new file mode 100644 index 0000000..b53f42c --- /dev/null +++ b/lib/enums/LandType.ts @@ -0,0 +1,9 @@ +export enum LandType +{ + Public = 0, + OwnedByOther = 1, + OwnedByGroup = 2, + OwnedByRequester = 3, + ForSale = 4, + BeingAuctioned = 5 +} diff --git a/package-lock.json b/package-lock.json index 37e229d..60ba810 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@caspertech/node-metaverse", - "version": "0.5.7", + "version": "0.5.8", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index efdf018..2696ed7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@caspertech/node-metaverse", - "version": "0.5.8", + "version": "0.5.9", "description": "A node.js interface for Second Life.", "main": "dist/index.js", "types": "dist/index.d.ts",