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",