From 188bf0ab0b83444c95389c80907db7460441a19b Mon Sep 17 00:00:00 2001 From: Casper Warden <216465704+casperwardensl@users.noreply.github.com> Date: Thu, 9 Nov 2023 18:06:49 +0000 Subject: [PATCH] Typescript build fixes --- lib/classes/Agent.ts | 2 +- lib/classes/Circuit.ts | 4 +- lib/classes/LoginResponse.ts | 2 +- lib/classes/ObjectStoreFull.ts | 1032 +++++++------- lib/classes/ObjectStoreLite.ts | 2226 +++++++++++++++--------------- lib/classes/Packet.ts | 6 +- lib/classes/public/GameObject.ts | 2 +- package-lock.json | 247 +++- package.json | 1 + 9 files changed, 1883 insertions(+), 1639 deletions(-) diff --git a/lib/classes/Agent.ts b/lib/classes/Agent.ts index 75d355b..53cd087 100644 --- a/lib/classes/Agent.ts +++ b/lib/classes/Agent.ts @@ -248,7 +248,7 @@ export class Agent ControlFlags: this.controlFlags, Flags: AgentFlags.None }; - circuit.sendMessage(agentUpdate, 0); + circuit.sendMessage(agentUpdate, 0 as PacketFlags); } shutdown(): void { diff --git a/lib/classes/Circuit.ts b/lib/classes/Circuit.ts index 280f945..6de486b 100644 --- a/lib/classes/Circuit.ts +++ b/lib/classes/Circuit.ts @@ -529,7 +529,7 @@ export class Circuit ID: sequenceNumber } ]; - this.sendMessage(msg, 0); + this.sendMessage(msg, 0 as PacketFlags); } getOldestUnacked(): number @@ -600,7 +600,7 @@ export class Circuit reply.PingID = { PingID: msg.PingID.PingID }; - this.sendMessage(reply, 0); + this.sendMessage(reply, 0 as PacketFlags); } this.onPacketReceived.next(packet); } diff --git a/lib/classes/LoginResponse.ts b/lib/classes/LoginResponse.ts index d1098d6..d362d73 100644 --- a/lib/classes/LoginResponse.ts +++ b/lib/classes/LoginResponse.ts @@ -225,7 +225,7 @@ export class LoginResponse this.searchToken = String(val); break; case 'login-flags': - let flags: LoginFlags = 0; + let flags: LoginFlags = 0 as LoginFlags; for (const obj of val) { if (obj['ever_logged_in'] === 'Y') diff --git a/lib/classes/ObjectStoreFull.ts b/lib/classes/ObjectStoreFull.ts index 8710e4b..8dbc5c9 100644 --- a/lib/classes/ObjectStoreFull.ts +++ b/lib/classes/ObjectStoreFull.ts @@ -1,516 +1,516 @@ -import { Circuit } from './Circuit'; -import { ObjectUpdateMessage } from './messages/ObjectUpdate'; -import { ObjectUpdateCachedMessage } from './messages/ObjectUpdateCached'; -import { ObjectUpdateCompressedMessage } from './messages/ObjectUpdateCompressed'; -import { ImprovedTerseObjectUpdateMessage } from './messages/ImprovedTerseObjectUpdate'; -import { RequestMultipleObjectsMessage } from './messages/RequestMultipleObjects'; -import { Agent } from './Agent'; -import { UUID } from './UUID'; -import { Quaternion } from './Quaternion'; -import { Vector3 } from './Vector3'; -import { Utils } from './Utils'; -import { ClientEvents } from './ClientEvents'; -import { IObjectStore } from './interfaces/IObjectStore'; -import { RBush3D } from 'rbush-3d/dist'; -import { Vector4 } from './Vector4'; -import { TextureEntry } from './TextureEntry'; -import { Color4 } from './Color4'; -import { ParticleSystem } from './ParticleSystem'; -import { GameObject } from './public/GameObject'; -import { ObjectStoreLite } from './ObjectStoreLite'; -import { TextureAnim } from './public/TextureAnim'; -import { ExtraParams } from './public/ExtraParams'; -import { CompressedFlags } from '../enums/CompressedFlags'; -import { PCode } from '../enums/PCode'; -import { BotOptionFlags } from '../enums/BotOptionFlags'; - -export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore -{ - rtree?: RBush3D; - - constructor(circuit: Circuit, agent: Agent, clientEvents: ClientEvents, options: BotOptionFlags) - { - super(circuit, agent, clientEvents, options); - this.rtree = new RBush3D(); - } - - protected objectUpdate(objectUpdate: ObjectUpdateMessage): void - { - for (const objData of objectUpdate.ObjectData) - { - const localID = objData.ID; - const parentID = objData.ParentID; - let addToParentList = true; - let newObject = false; - if (this.objects[localID]) - { - if (this.objects[localID].ParentID !== parentID && this.objectsByParent[parentID]) - { - const ind = this.objectsByParent[parentID].indexOf(localID); - if (ind !== -1) - { - this.objectsByParent[parentID].splice(ind, 1); - } - } - else if (this.objectsByParent[parentID]) - { - addToParentList = false; - } - } - else - { - newObject = true; - this.objects[localID] = new GameObject(); - this.objects[localID].region = this.agent.currentRegion; - } - this.objects[localID].deleted = false; - - const obj = this.objects[localID]; - obj.ID = objData.ID; - obj.State = objData.State; - obj.FullID = objData.FullID; - obj.CRC = objData.CRC; - obj.PCode = objData.PCode; - obj.Material = objData.Material; - obj.ClickAction = objData.ClickAction; - - obj.Scale = objData.Scale; - obj.setObjectData(objData.ObjectData); - - obj.ParentID = objData.ParentID; - - obj.Flags = objData.UpdateFlags; - obj.PathCurve = objData.PathCurve; - obj.ProfileCurve = objData.ProfileCurve; - obj.PathBegin = Utils.unpackBeginCut(objData.PathBegin); - obj.PathEnd = Utils.unpackEndCut(objData.PathEnd); - obj.PathScaleX = Utils.unpackPathScale(objData.PathScaleX); - obj.PathScaleY = Utils.unpackPathScale(objData.PathScaleY); - obj.PathShearX = Utils.unpackPathShear(objData.PathShearX); - obj.PathShearY = Utils.unpackPathShear(objData.PathShearY); - obj.PathTwist = Utils.unpackPathTwist(objData.PathTwist); - obj.PathTwistBegin = Utils.unpackPathTwist(objData.PathTwistBegin); - obj.PathRadiusOffset = Utils.unpackPathTwist(objData.PathRadiusOffset); - obj.PathTaperX = Utils.unpackPathTaper(objData.PathTaperX); - obj.PathTaperY = Utils.unpackPathTaper(objData.PathTaperY); - obj.PathRevolutions = Utils.unpackPathRevolutions(objData.PathRevolutions); - obj.PathSkew = Utils.unpackPathTwist(objData.PathSkew); - obj.ProfileBegin = Utils.unpackBeginCut(objData.ProfileBegin); - obj.ProfileEnd = Utils.unpackEndCut(objData.ProfileEnd); - obj.ProfileHollow = Utils.unpackProfileHollow(objData.ProfileHollow); - obj.TextureEntry = TextureEntry.from(objData.TextureEntry); - obj.textureAnim = TextureAnim.from(objData.TextureAnim); - - const pcodeData = objData.Data; - obj.Text = Utils.BufferToStringSimple(objData.Text); - obj.TextColor = new Color4(objData.TextColor, 0, false, true); - obj.MediaURL = Utils.BufferToStringSimple(objData.MediaURL); - obj.Particles = ParticleSystem.from(objData.PSBlock); - obj.Sound = objData.Sound; - obj.OwnerID = objData.OwnerID; - obj.SoundGain = objData.Gain; - obj.SoundFlags = objData.Flags; - obj.SoundRadius = objData.Radius; - obj.JointType = objData.JointType; - obj.JointPivot = objData.JointPivot; - obj.JointAxisOrAnchor = objData.JointAxisOrAnchor; - - switch (obj.PCode) - { - case PCode.Grass: - case PCode.Tree: - case PCode.NewTree: - if (pcodeData.length === 1) - { - obj.TreeSpecies = pcodeData[0]; - } - break; - case PCode.Prim: - - break; - } - - if (this.objects[localID].PCode === PCode.Avatar && this.objects[localID].FullID.toString() === this.agent.agentID.toString()) - { - this.agent.localID = localID; - - if (this.options & BotOptionFlags.StoreMyAttachmentsOnly) - { - for (const objParentID of Object.keys(this.objectsByParent)) - { - const parent = parseInt(objParentID, 10); - if (parent !== this.agent.localID) - { - let foundAvatars = false; - for (const objID of this.objectsByParent[parent]) - { - if (this.objects[objID]) - { - const o = this.objects[objID]; - if (o.PCode === PCode.Avatar) - { - foundAvatars = true; - } - } - } - if (this.objects[parent]) - { - const o = this.objects[parent]; - if (o.PCode === PCode.Avatar) - { - foundAvatars = true; - } - } - if (!foundAvatars) - { - this.deleteObject(parent); - } - } - } - } - } - this.objects[localID].extraParams = ExtraParams.from(objData.ExtraParams); - this.objects[localID].NameValue = this.parseNameValues(Utils.BufferToStringSimple(objData.NameValue)); - - this.objects[localID].IsAttachment = this.objects[localID].NameValue['AttachItemID'] !== undefined; - if (obj.IsAttachment && obj.State !== undefined) - { - this.objects[localID].attachmentPoint = this.decodeAttachPoint(obj.State); - } - - this.objectsByUUID[objData.FullID.toString()] = localID; - if (!this.objectsByParent[parentID]) - { - this.objectsByParent[parentID] = []; - } - if (addToParentList) - { - this.objectsByParent[parentID].push(localID); - } - - if (objData.PCode !== PCode.Avatar && this.options & BotOptionFlags.StoreMyAttachmentsOnly && (this.agent.localID !== 0 && obj.ParentID !== this.agent.localID)) - { - // Drop object - this.deleteObject(localID); - } - else - { - this.insertIntoRtree(obj); - if (objData.ParentID !== undefined && objData.ParentID !== 0 && !this.objects[objData.ParentID]) - { - this.requestMissingObject(objData.ParentID).then(() => - { - }).catch(() => - { - }); - } - this.notifyObjectUpdate(newObject, obj); - obj.onTextureUpdate.next(); - } - } - } - - protected objectUpdateCached(objectUpdateCached: ObjectUpdateCachedMessage): void - { - if (!this.circuit) - { - return; - } - const rmo = new RequestMultipleObjectsMessage(); - rmo.AgentData = { - AgentID: this.agent.agentID, - SessionID: this.circuit.sessionID - }; - rmo.ObjectData = []; - for (const obj of objectUpdateCached.ObjectData) - { - if (!this.objects[obj.ID]) - { - rmo.ObjectData.push({ - CacheMissType: 0, - ID: obj.ID - }); - } - } - if (rmo.ObjectData.length > 0) - { - if (!this.circuit) - { - return; - } - this.circuit.sendMessage(rmo, 0); - } - } - - protected async objectUpdateCompressed(objectUpdateCompressed: ObjectUpdateCompressedMessage): Promise - { - for (const obj of objectUpdateCompressed.ObjectData) - { - const flags = obj.UpdateFlags; - const buf = obj.Data; - let pos = 0; - - const fullID = new UUID(buf, pos); - pos += 16; - const localID = buf.readUInt32LE(pos); - pos += 4; - const pcode = buf.readUInt8(pos++); - let newObj = false; - if (!this.objects[localID]) - { - newObj = true; - this.objects[localID] = new GameObject(); - this.objects[localID].region = this.agent.currentRegion; - } - const o = this.objects[localID]; - o.ID = localID; - this.objectsByUUID[fullID.toString()] = localID; - o.FullID = fullID; - o.Flags = flags; - o.PCode = pcode; - o.deleted = false; - o.State = buf.readUInt8(pos++); - o.CRC = buf.readUInt32LE(pos); - pos = pos + 4; - o.Material = buf.readUInt8(pos++); - o.ClickAction = buf.readUInt8(pos++); - o.Scale = new Vector3(buf, pos, false); - pos = pos + 12; - o.Position = new Vector3(buf, pos, false); - pos = pos + 12; - o.Rotation = new Quaternion(buf, pos); - pos = pos + 12; - const compressedflags: CompressedFlags = buf.readUInt32LE(pos); - pos = pos + 4; - o.OwnerID = new UUID(buf, pos); - pos += 16; - - if (compressedflags & CompressedFlags.HasAngularVelocity) - { - o.AngularVelocity = new Vector3(buf, pos, false); - pos = pos + 12; - } - let newParentID = 0; - if (compressedflags & CompressedFlags.HasParent) - { - newParentID = buf.readUInt32LE(pos); - pos += 4; - } - o.ParentID = newParentID; - let add = true; - if (!newObj && o.ParentID !== undefined) - { - if (newParentID !== o.ParentID) - { - const index = this.objectsByParent[o.ParentID].indexOf(localID); - if (index !== -1) - { - this.objectsByParent[o.ParentID].splice(index, 1); - } - } - else if (this.objectsByParent[o.ParentID]) - { - add = false; - } - } - if (add) - { - if (!this.objectsByParent[newParentID]) - { - this.objectsByParent[newParentID] = []; - } - this.objectsByParent[newParentID].push(localID); - } - - if (pcode !== PCode.Avatar && newObj && this.options & BotOptionFlags.StoreMyAttachmentsOnly && (this.agent.localID !== 0 && o.ParentID !== this.agent.localID)) - { - // Drop object - this.deleteObject(localID); - return; - } - else - { - if (o.ParentID !== undefined && o.ParentID !== 0 && !this.objects[o.ParentID]) - { - this.requestMissingObject(o.ParentID); - } - if (compressedflags & CompressedFlags.Tree) - { - o.TreeSpecies = buf.readUInt8(pos++); - } - else if (compressedflags & CompressedFlags.ScratchPad) - { - o.TreeSpecies = 0; - const scratchPadSize = buf.readUInt8(pos++); - // Ignore this data - pos = pos + scratchPadSize; - } - if (compressedflags & CompressedFlags.HasText) - { - // Read null terminated string - const result = Utils.BufferToString(buf, pos); - - pos += result.readLength; - o.Text = result.result; - o.TextColor = new Color4(buf, pos, false, true); - pos = pos + 4; - } - else - { - o.Text = ''; - } - if (compressedflags & CompressedFlags.MediaURL) - { - const result = Utils.BufferToString(buf, pos); - - pos += result.readLength; - o.MediaURL = result.result; - } - if (compressedflags & CompressedFlags.HasParticles) - { - o.Particles = ParticleSystem.from(buf.slice(pos, pos + 86)); - pos += 86; - } - - // Extra params - const extraParamsLength = ExtraParams.getLengthOfParams(buf, pos); - o.extraParams = ExtraParams.from(buf.slice(pos, pos + extraParamsLength)); - pos += extraParamsLength; - - if (compressedflags & CompressedFlags.HasSound) - { - o.Sound = new UUID(buf, pos); - pos = pos + 16; - o.SoundGain = buf.readFloatLE(pos); - pos += 4; - o.SoundFlags = buf.readUInt8(pos++); - o.SoundRadius = buf.readFloatLE(pos); - pos = pos + 4; - } - if (compressedflags & CompressedFlags.HasNameValues) - { - const result = Utils.BufferToString(buf, pos); - o.NameValue = this.parseNameValues(result.result); - pos += result.readLength; - } - o.PathCurve = buf.readUInt8(pos++); - o.PathBegin = Utils.unpackBeginCut(buf.readUInt16LE(pos)); - pos = pos + 2; - o.PathEnd = Utils.unpackEndCut(buf.readUInt16LE(pos)); - pos = pos + 2; - o.PathScaleX = Utils.unpackPathScale(buf.readUInt8(pos++)); - o.PathScaleY = Utils.unpackPathScale(buf.readUInt8(pos++)); - o.PathShearX = Utils.unpackPathShear(buf.readUInt8(pos++)); - o.PathShearY = Utils.unpackPathShear(buf.readUInt8(pos++)); - o.PathTwist = Utils.unpackPathTwist(buf.readUInt8(pos++)); - o.PathTwistBegin = Utils.unpackPathTwist(buf.readUInt8(pos++)); - o.PathRadiusOffset = Utils.unpackPathTwist(buf.readUInt8(pos++)); - o.PathTaperX = Utils.unpackPathTaper(buf.readUInt8(pos++)); - o.PathTaperY = Utils.unpackPathTaper(buf.readUInt8(pos++)); - o.PathRevolutions = Utils.unpackPathRevolutions(buf.readUInt8(pos++)); - o.PathSkew = Utils.unpackPathTwist(buf.readUInt8(pos++)); - o.ProfileCurve = buf.readUInt8(pos++); - o.ProfileBegin = Utils.unpackBeginCut(buf.readUInt16LE(pos)); - pos = pos + 2; - o.ProfileEnd = Utils.unpackEndCut(buf.readUInt16LE(pos)); - pos = pos + 2; - o.ProfileHollow = Utils.unpackProfileHollow(buf.readUInt16LE(pos)); - pos = pos + 2; - const textureEntryLength = buf.readUInt32LE(pos); - pos = pos + 4; - o.TextureEntry = TextureEntry.from(buf.slice(pos, pos + textureEntryLength)); - pos = pos + textureEntryLength; - - if (compressedflags & CompressedFlags.TextureAnimation) - { - const textureAnimLength = buf.readUInt32LE(pos); - pos = pos + 4; - o.textureAnim = TextureAnim.from(buf.slice(pos, pos + textureAnimLength)); - } - - o.IsAttachment = (compressedflags & CompressedFlags.HasNameValues) !== 0 && o.ParentID !== 0; - if (o.IsAttachment && o.State !== undefined) - { - this.objects[localID].attachmentPoint = this.decodeAttachPoint(o.State); - } - - this.insertIntoRtree(o); - - this.notifyObjectUpdate(newObj, o); - o.onTextureUpdate.next(); - } - } - } - - protected objectUpdateTerse(objectUpdateTerse: ImprovedTerseObjectUpdateMessage): void - { - const dilation = objectUpdateTerse.RegionData.TimeDilation / 65535.0; - this.clientEvents.onRegionTimeDilation.next(dilation); - - for (let i = 0; i < objectUpdateTerse.ObjectData.length; i++) - { - const objectData = objectUpdateTerse.ObjectData[i]; - if (!(this.options & BotOptionFlags.StoreMyAttachmentsOnly)) - { - let pos = 0; - const localID = objectData.Data.readUInt32LE(pos); - pos = pos + 4; - if (this.objects[localID]) - { - this.objects[localID].State = objectData.Data.readUInt8(pos++); - const avatar: boolean = (objectData.Data.readUInt8(pos++) !== 0); - if (avatar) - { - this.objects[localID].CollisionPlane = new Vector4(objectData.Data, pos); - pos += 16; - } - this.objects[localID].Position = new Vector3(objectData.Data, pos); - pos += 12; - this.objects[localID].Velocity = new Vector3([ - Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos), -128.0, 128.0), - Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 2), -128.0, 128.0), - Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 4), -128.0, 128.0) - ]); - pos += 6; - this.objects[localID].Acceleration = new Vector3([ - Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos), -64.0, 64.0), - Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 2), -64.0, 64.0), - Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 4), -64.0, 64.0) - ]); - pos += 6; - this.objects[localID].Rotation = new Quaternion([ - Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos), -1.0, 1.0), - Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 2), -1.0, 1.0), - Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 4), -1.0, 1.0), - Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 6), -1.0, 1.0) - ]); - pos += 8; - this.objects[localID].AngularVelocity = new Vector3([ - Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos), -64.0, 64.0), - Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 2), -64.0, 64.0), - Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 4), -64.0, 64.0) - ]); - pos += 6; - - if (objectData.TextureEntry.length > 0) - { - // No idea why the first four bytes are skipped here. - this.objects[localID].TextureEntry = TextureEntry.from(objectData.TextureEntry.slice(4)); - this.objects[localID].onTextureUpdate.next(); - } - this.insertIntoRtree(this.objects[localID]); - this.notifyTerseUpdate(this.objects[localID]); - - } - else - { - // We don't know about this object, so request it - this.requestMissingObject(localID).catch(() => - { - - }); - } - } - } - } -} +import { Circuit } from './Circuit'; +import { ObjectUpdateMessage } from './messages/ObjectUpdate'; +import { ObjectUpdateCachedMessage } from './messages/ObjectUpdateCached'; +import { ObjectUpdateCompressedMessage } from './messages/ObjectUpdateCompressed'; +import { ImprovedTerseObjectUpdateMessage } from './messages/ImprovedTerseObjectUpdate'; +import { RequestMultipleObjectsMessage } from './messages/RequestMultipleObjects'; +import { Agent } from './Agent'; +import { UUID } from './UUID'; +import { Quaternion } from './Quaternion'; +import { Vector3 } from './Vector3'; +import { Utils } from './Utils'; +import { ClientEvents } from './ClientEvents'; +import { IObjectStore } from './interfaces/IObjectStore'; +import { RBush3D } from 'rbush-3d/dist'; +import { Vector4 } from './Vector4'; +import { TextureEntry } from './TextureEntry'; +import { Color4 } from './Color4'; +import { ParticleSystem } from './ParticleSystem'; +import { GameObject } from './public/GameObject'; +import { ObjectStoreLite } from './ObjectStoreLite'; +import { TextureAnim } from './public/TextureAnim'; +import { ExtraParams } from './public/ExtraParams'; +import { CompressedFlags } from '../enums/CompressedFlags'; +import { PCode } from '../enums/PCode'; +import { BotOptionFlags } from '../enums/BotOptionFlags'; + +export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore +{ + rtree?: RBush3D; + + constructor(circuit: Circuit, agent: Agent, clientEvents: ClientEvents, options: BotOptionFlags) + { + super(circuit, agent, clientEvents, options); + this.rtree = new RBush3D(); + } + + protected objectUpdate(objectUpdate: ObjectUpdateMessage): void + { + for (const objData of objectUpdate.ObjectData) + { + const localID = objData.ID; + const parentID = objData.ParentID; + let addToParentList = true; + let newObject = false; + if (this.objects[localID]) + { + if (this.objects[localID].ParentID !== parentID && this.objectsByParent[parentID]) + { + const ind = this.objectsByParent[parentID].indexOf(localID); + if (ind !== -1) + { + this.objectsByParent[parentID].splice(ind, 1); + } + } + else if (this.objectsByParent[parentID]) + { + addToParentList = false; + } + } + else + { + newObject = true; + this.objects[localID] = new GameObject(); + this.objects[localID].region = this.agent.currentRegion; + } + this.objects[localID].deleted = false; + + const obj = this.objects[localID]; + obj.ID = objData.ID; + obj.State = objData.State; + obj.FullID = objData.FullID; + obj.CRC = objData.CRC; + obj.PCode = objData.PCode; + obj.Material = objData.Material; + obj.ClickAction = objData.ClickAction; + + obj.Scale = objData.Scale; + obj.setObjectData(objData.ObjectData); + + obj.ParentID = objData.ParentID; + + obj.Flags = objData.UpdateFlags; + obj.PathCurve = objData.PathCurve; + obj.ProfileCurve = objData.ProfileCurve; + obj.PathBegin = Utils.unpackBeginCut(objData.PathBegin); + obj.PathEnd = Utils.unpackEndCut(objData.PathEnd); + obj.PathScaleX = Utils.unpackPathScale(objData.PathScaleX); + obj.PathScaleY = Utils.unpackPathScale(objData.PathScaleY); + obj.PathShearX = Utils.unpackPathShear(objData.PathShearX); + obj.PathShearY = Utils.unpackPathShear(objData.PathShearY); + obj.PathTwist = Utils.unpackPathTwist(objData.PathTwist); + obj.PathTwistBegin = Utils.unpackPathTwist(objData.PathTwistBegin); + obj.PathRadiusOffset = Utils.unpackPathTwist(objData.PathRadiusOffset); + obj.PathTaperX = Utils.unpackPathTaper(objData.PathTaperX); + obj.PathTaperY = Utils.unpackPathTaper(objData.PathTaperY); + obj.PathRevolutions = Utils.unpackPathRevolutions(objData.PathRevolutions); + obj.PathSkew = Utils.unpackPathTwist(objData.PathSkew); + obj.ProfileBegin = Utils.unpackBeginCut(objData.ProfileBegin); + obj.ProfileEnd = Utils.unpackEndCut(objData.ProfileEnd); + obj.ProfileHollow = Utils.unpackProfileHollow(objData.ProfileHollow); + obj.TextureEntry = TextureEntry.from(objData.TextureEntry); + obj.textureAnim = TextureAnim.from(objData.TextureAnim); + + const pcodeData = objData.Data; + obj.Text = Utils.BufferToStringSimple(objData.Text); + obj.TextColor = new Color4(objData.TextColor, 0, false, true); + obj.MediaURL = Utils.BufferToStringSimple(objData.MediaURL); + obj.Particles = ParticleSystem.from(objData.PSBlock); + obj.Sound = objData.Sound; + obj.OwnerID = objData.OwnerID; + obj.SoundGain = objData.Gain; + obj.SoundFlags = objData.Flags; + obj.SoundRadius = objData.Radius; + obj.JointType = objData.JointType; + obj.JointPivot = objData.JointPivot; + obj.JointAxisOrAnchor = objData.JointAxisOrAnchor; + + switch (obj.PCode) + { + case PCode.Grass: + case PCode.Tree: + case PCode.NewTree: + if (pcodeData.length === 1) + { + obj.TreeSpecies = pcodeData[0]; + } + break; + case PCode.Prim: + + break; + } + + if (this.objects[localID].PCode === PCode.Avatar && this.objects[localID].FullID.toString() === this.agent.agentID.toString()) + { + this.agent.localID = localID; + + if (this.options & BotOptionFlags.StoreMyAttachmentsOnly) + { + for (const objParentID of Object.keys(this.objectsByParent)) + { + const parent = parseInt(objParentID, 10); + if (parent !== this.agent.localID) + { + let foundAvatars = false; + for (const objID of this.objectsByParent[parent]) + { + if (this.objects[objID]) + { + const o = this.objects[objID]; + if (o.PCode === PCode.Avatar) + { + foundAvatars = true; + } + } + } + if (this.objects[parent]) + { + const o = this.objects[parent]; + if (o.PCode === PCode.Avatar) + { + foundAvatars = true; + } + } + if (!foundAvatars) + { + this.deleteObject(parent); + } + } + } + } + } + this.objects[localID].extraParams = ExtraParams.from(objData.ExtraParams); + this.objects[localID].NameValue = this.parseNameValues(Utils.BufferToStringSimple(objData.NameValue)); + + this.objects[localID].IsAttachment = this.objects[localID].NameValue['AttachItemID'] !== undefined; + if (obj.IsAttachment && obj.State !== undefined) + { + this.objects[localID].attachmentPoint = this.decodeAttachPoint(obj.State); + } + + this.objectsByUUID[objData.FullID.toString()] = localID; + if (!this.objectsByParent[parentID]) + { + this.objectsByParent[parentID] = []; + } + if (addToParentList) + { + this.objectsByParent[parentID].push(localID); + } + + if (objData.PCode !== PCode.Avatar && this.options & BotOptionFlags.StoreMyAttachmentsOnly && (this.agent.localID !== 0 && obj.ParentID !== this.agent.localID)) + { + // Drop object + this.deleteObject(localID); + } + else + { + this.insertIntoRtree(obj); + if (objData.ParentID !== undefined && objData.ParentID !== 0 && !this.objects[objData.ParentID]) + { + this.requestMissingObject(objData.ParentID).then(() => + { + }).catch(() => + { + }); + } + this.notifyObjectUpdate(newObject, obj); + obj.onTextureUpdate.next(); + } + } + } + + protected objectUpdateCached(objectUpdateCached: ObjectUpdateCachedMessage): void + { + if (!this.circuit) + { + return; + } + const rmo = new RequestMultipleObjectsMessage(); + rmo.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.circuit.sessionID + }; + rmo.ObjectData = []; + for (const obj of objectUpdateCached.ObjectData) + { + if (!this.objects[obj.ID]) + { + rmo.ObjectData.push({ + CacheMissType: 0, + ID: obj.ID + }); + } + } + if (rmo.ObjectData.length > 0) + { + if (!this.circuit) + { + return; + } + this.circuit.sendMessage(rmo, 0 as PacketFlags); + } + } + + protected async objectUpdateCompressed(objectUpdateCompressed: ObjectUpdateCompressedMessage): Promise + { + for (const obj of objectUpdateCompressed.ObjectData) + { + const flags = obj.UpdateFlags; + const buf = obj.Data; + let pos = 0; + + const fullID = new UUID(buf, pos); + pos += 16; + const localID = buf.readUInt32LE(pos); + pos += 4; + const pcode = buf.readUInt8(pos++); + let newObj = false; + if (!this.objects[localID]) + { + newObj = true; + this.objects[localID] = new GameObject(); + this.objects[localID].region = this.agent.currentRegion; + } + const o = this.objects[localID]; + o.ID = localID; + this.objectsByUUID[fullID.toString()] = localID; + o.FullID = fullID; + o.Flags = flags; + o.PCode = pcode; + o.deleted = false; + o.State = buf.readUInt8(pos++); + o.CRC = buf.readUInt32LE(pos); + pos = pos + 4; + o.Material = buf.readUInt8(pos++); + o.ClickAction = buf.readUInt8(pos++); + o.Scale = new Vector3(buf, pos, false); + pos = pos + 12; + o.Position = new Vector3(buf, pos, false); + pos = pos + 12; + o.Rotation = new Quaternion(buf, pos); + pos = pos + 12; + const compressedflags: CompressedFlags = buf.readUInt32LE(pos); + pos = pos + 4; + o.OwnerID = new UUID(buf, pos); + pos += 16; + + if (compressedflags & CompressedFlags.HasAngularVelocity) + { + o.AngularVelocity = new Vector3(buf, pos, false); + pos = pos + 12; + } + let newParentID = 0; + if (compressedflags & CompressedFlags.HasParent) + { + newParentID = buf.readUInt32LE(pos); + pos += 4; + } + o.ParentID = newParentID; + let add = true; + if (!newObj && o.ParentID !== undefined) + { + if (newParentID !== o.ParentID) + { + const index = this.objectsByParent[o.ParentID].indexOf(localID); + if (index !== -1) + { + this.objectsByParent[o.ParentID].splice(index, 1); + } + } + else if (this.objectsByParent[o.ParentID]) + { + add = false; + } + } + if (add) + { + if (!this.objectsByParent[newParentID]) + { + this.objectsByParent[newParentID] = []; + } + this.objectsByParent[newParentID].push(localID); + } + + if (pcode !== PCode.Avatar && newObj && this.options & BotOptionFlags.StoreMyAttachmentsOnly && (this.agent.localID !== 0 && o.ParentID !== this.agent.localID)) + { + // Drop object + this.deleteObject(localID); + return; + } + else + { + if (o.ParentID !== undefined && o.ParentID !== 0 && !this.objects[o.ParentID]) + { + this.requestMissingObject(o.ParentID); + } + if (compressedflags & CompressedFlags.Tree) + { + o.TreeSpecies = buf.readUInt8(pos++); + } + else if (compressedflags & CompressedFlags.ScratchPad) + { + o.TreeSpecies = 0; + const scratchPadSize = buf.readUInt8(pos++); + // Ignore this data + pos = pos + scratchPadSize; + } + if (compressedflags & CompressedFlags.HasText) + { + // Read null terminated string + const result = Utils.BufferToString(buf, pos); + + pos += result.readLength; + o.Text = result.result; + o.TextColor = new Color4(buf, pos, false, true); + pos = pos + 4; + } + else + { + o.Text = ''; + } + if (compressedflags & CompressedFlags.MediaURL) + { + const result = Utils.BufferToString(buf, pos); + + pos += result.readLength; + o.MediaURL = result.result; + } + if (compressedflags & CompressedFlags.HasParticles) + { + o.Particles = ParticleSystem.from(buf.slice(pos, pos + 86)); + pos += 86; + } + + // Extra params + const extraParamsLength = ExtraParams.getLengthOfParams(buf, pos); + o.extraParams = ExtraParams.from(buf.slice(pos, pos + extraParamsLength)); + pos += extraParamsLength; + + if (compressedflags & CompressedFlags.HasSound) + { + o.Sound = new UUID(buf, pos); + pos = pos + 16; + o.SoundGain = buf.readFloatLE(pos); + pos += 4; + o.SoundFlags = buf.readUInt8(pos++); + o.SoundRadius = buf.readFloatLE(pos); + pos = pos + 4; + } + if (compressedflags & CompressedFlags.HasNameValues) + { + const result = Utils.BufferToString(buf, pos); + o.NameValue = this.parseNameValues(result.result); + pos += result.readLength; + } + o.PathCurve = buf.readUInt8(pos++); + o.PathBegin = Utils.unpackBeginCut(buf.readUInt16LE(pos)); + pos = pos + 2; + o.PathEnd = Utils.unpackEndCut(buf.readUInt16LE(pos)); + pos = pos + 2; + o.PathScaleX = Utils.unpackPathScale(buf.readUInt8(pos++)); + o.PathScaleY = Utils.unpackPathScale(buf.readUInt8(pos++)); + o.PathShearX = Utils.unpackPathShear(buf.readUInt8(pos++)); + o.PathShearY = Utils.unpackPathShear(buf.readUInt8(pos++)); + o.PathTwist = Utils.unpackPathTwist(buf.readUInt8(pos++)); + o.PathTwistBegin = Utils.unpackPathTwist(buf.readUInt8(pos++)); + o.PathRadiusOffset = Utils.unpackPathTwist(buf.readUInt8(pos++)); + o.PathTaperX = Utils.unpackPathTaper(buf.readUInt8(pos++)); + o.PathTaperY = Utils.unpackPathTaper(buf.readUInt8(pos++)); + o.PathRevolutions = Utils.unpackPathRevolutions(buf.readUInt8(pos++)); + o.PathSkew = Utils.unpackPathTwist(buf.readUInt8(pos++)); + o.ProfileCurve = buf.readUInt8(pos++); + o.ProfileBegin = Utils.unpackBeginCut(buf.readUInt16LE(pos)); + pos = pos + 2; + o.ProfileEnd = Utils.unpackEndCut(buf.readUInt16LE(pos)); + pos = pos + 2; + o.ProfileHollow = Utils.unpackProfileHollow(buf.readUInt16LE(pos)); + pos = pos + 2; + const textureEntryLength = buf.readUInt32LE(pos); + pos = pos + 4; + o.TextureEntry = TextureEntry.from(buf.slice(pos, pos + textureEntryLength)); + pos = pos + textureEntryLength; + + if (compressedflags & CompressedFlags.TextureAnimation) + { + const textureAnimLength = buf.readUInt32LE(pos); + pos = pos + 4; + o.textureAnim = TextureAnim.from(buf.slice(pos, pos + textureAnimLength)); + } + + o.IsAttachment = (compressedflags & CompressedFlags.HasNameValues) !== 0 && o.ParentID !== 0; + if (o.IsAttachment && o.State !== undefined) + { + this.objects[localID].attachmentPoint = this.decodeAttachPoint(o.State); + } + + this.insertIntoRtree(o); + + this.notifyObjectUpdate(newObj, o); + o.onTextureUpdate.next(); + } + } + } + + protected objectUpdateTerse(objectUpdateTerse: ImprovedTerseObjectUpdateMessage): void + { + const dilation = objectUpdateTerse.RegionData.TimeDilation / 65535.0; + this.clientEvents.onRegionTimeDilation.next(dilation); + + for (let i = 0; i < objectUpdateTerse.ObjectData.length; i++) + { + const objectData = objectUpdateTerse.ObjectData[i]; + if (!(this.options & BotOptionFlags.StoreMyAttachmentsOnly)) + { + let pos = 0; + const localID = objectData.Data.readUInt32LE(pos); + pos = pos + 4; + if (this.objects[localID]) + { + this.objects[localID].State = objectData.Data.readUInt8(pos++); + const avatar: boolean = (objectData.Data.readUInt8(pos++) !== 0); + if (avatar) + { + this.objects[localID].CollisionPlane = new Vector4(objectData.Data, pos); + pos += 16; + } + this.objects[localID].Position = new Vector3(objectData.Data, pos); + pos += 12; + this.objects[localID].Velocity = new Vector3([ + Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos), -128.0, 128.0), + Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 2), -128.0, 128.0), + Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 4), -128.0, 128.0) + ]); + pos += 6; + this.objects[localID].Acceleration = new Vector3([ + Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos), -64.0, 64.0), + Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 2), -64.0, 64.0), + Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 4), -64.0, 64.0) + ]); + pos += 6; + this.objects[localID].Rotation = new Quaternion([ + Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos), -1.0, 1.0), + Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 2), -1.0, 1.0), + Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 4), -1.0, 1.0), + Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 6), -1.0, 1.0) + ]); + pos += 8; + this.objects[localID].AngularVelocity = new Vector3([ + Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos), -64.0, 64.0), + Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 2), -64.0, 64.0), + Utils.UInt16ToFloat(objectData.Data.readUInt16LE(pos + 4), -64.0, 64.0) + ]); + pos += 6; + + if (objectData.TextureEntry.length > 0) + { + // No idea why the first four bytes are skipped here. + this.objects[localID].TextureEntry = TextureEntry.from(objectData.TextureEntry.slice(4)); + this.objects[localID].onTextureUpdate.next(); + } + this.insertIntoRtree(this.objects[localID]); + this.notifyTerseUpdate(this.objects[localID]); + + } + else + { + // We don't know about this object, so request it + this.requestMissingObject(localID).catch(() => + { + + }); + } + } + } + } +} diff --git a/lib/classes/ObjectStoreLite.ts b/lib/classes/ObjectStoreLite.ts index 342abc4..92323a3 100644 --- a/lib/classes/ObjectStoreLite.ts +++ b/lib/classes/ObjectStoreLite.ts @@ -1,1113 +1,1113 @@ -import { Circuit } from './Circuit'; -import { Logger } from './Logger'; -import { Packet } from './Packet'; -import { Message } from '../enums/Message'; -import { ObjectUpdateMessage } from './messages/ObjectUpdate'; -import { ObjectUpdateCachedMessage } from './messages/ObjectUpdateCached'; -import { ObjectUpdateCompressedMessage } from './messages/ObjectUpdateCompressed'; -import { ImprovedTerseObjectUpdateMessage } from './messages/ImprovedTerseObjectUpdate'; -import { RequestMultipleObjectsMessage } from './messages/RequestMultipleObjects'; -import { Agent } from './Agent'; -import { UUID } from './UUID'; -import { Utils } from './Utils'; -import { ClientEvents } from './ClientEvents'; -import { KillObjectMessage } from './messages/KillObject'; -import { IObjectStore } from './interfaces/IObjectStore'; -import { NameValue } from './NameValue'; -import { GameObject } from './public/GameObject'; -import { RBush3D } from 'rbush-3d/dist'; -import { ITreeBoundingBox } from './interfaces/ITreeBoundingBox'; -import { FilterResponse } from '../enums/FilterResponse'; -import { ObjectSelectMessage } from './messages/ObjectSelect'; -import { ObjectDeselectMessage } from './messages/ObjectDeselect'; -import { Quaternion } from './Quaternion'; -import { Subscription } from 'rxjs'; -import { ExtraParams } from './public/ExtraParams'; -import { ObjectPropertiesMessage } from './messages/ObjectProperties'; -import { SelectedObjectEvent } from '../events/SelectedObjectEvent'; -import { PrimFlags } from '../enums/PrimFlags'; -import { PacketFlags } from '../enums/PacketFlags'; -import { PCode } from '../enums/PCode'; -import { BotOptionFlags } from '../enums/BotOptionFlags'; -import { NewObjectEvent } from '../events/NewObjectEvent'; -import { ObjectUpdatedEvent } from '../events/ObjectUpdatedEvent'; -import { CompressedFlags } from '../enums/CompressedFlags'; -import { Vector3 } from './Vector3'; -import { ObjectPhysicsDataEvent } from '../events/ObjectPhysicsDataEvent'; -import { ObjectResolvedEvent } from '../events/ObjectResolvedEvent'; -import { Avatar } from './public/Avatar'; - -import Timer = NodeJS.Timer; - -export class ObjectStoreLite implements IObjectStore -{ - protected circuit?: Circuit; - protected agent: Agent; - protected objects: { [key: number]: GameObject } = {}; - protected objectsByUUID: { [key: string]: number } = {}; - protected objectsByParent: { [key: number]: number[] } = {}; - protected clientEvents: ClientEvents; - protected options: BotOptionFlags; - protected requestedObjects: { [key: number]: boolean } = {}; - protected deadObjects: number[] = []; - protected persist = false; - protected pendingObjectProperties: { [key: string]: any } = {}; - private physicsSubscription: Subscription; - private selectedPrimsWithoutUpdate: { [key: number]: boolean } = {}; - private selectedChecker?: Timer; - - rtree?: RBush3D; - - constructor(circuit: Circuit, agent: Agent, clientEvents: ClientEvents, options: BotOptionFlags) - { - agent.localID = 0; - this.options = options; - this.clientEvents = clientEvents; - this.circuit = circuit; - this.agent = agent; - this.circuit.subscribeToMessages([ - Message.ObjectUpdate, - Message.ObjectUpdateCached, - Message.ObjectUpdateCompressed, - Message.ImprovedTerseObjectUpdate, - Message.ObjectProperties, - Message.KillObject - ], async(packet: Packet) => - { - switch (packet.message.id) - { - case Message.ObjectProperties: - { - const objProp = packet.message as ObjectPropertiesMessage; - for (const obj of objProp.ObjectData) - { - const obje = this.objectsByUUID[obj.ObjectID.toString()]; - if (obje !== undefined && this.objects[obje] !== undefined) - { - const o = this.objects[obje]; - this.applyObjectProperties(o, obj); - } - else - { - this.pendingObjectProperties[obj.ObjectID.toString()] = obj; - } - } - break; - } - case Message.ObjectUpdate: - { - const objectUpdate = packet.message as ObjectUpdateMessage; - this.objectUpdate(objectUpdate); - break; - } - case Message.ObjectUpdateCached: - { - const objectUpdateCached = packet.message as ObjectUpdateCachedMessage; - this.objectUpdateCached(objectUpdateCached); - break; - } - case Message.ObjectUpdateCompressed: - { - const objectUpdateCompressed = packet.message as ObjectUpdateCompressedMessage; - await this.objectUpdateCompressed(objectUpdateCompressed); - break; - } - case Message.ImprovedTerseObjectUpdate: - { - const objectUpdateTerse = packet.message as ImprovedTerseObjectUpdateMessage; - this.objectUpdateTerse(objectUpdateTerse); - break; - } - case Message.KillObject: - { - const killObj = packet.message as KillObjectMessage; - this.killObject(killObj); - break; - } - } - }); - - this.physicsSubscription = this.clientEvents.onPhysicsDataEvent.subscribe((evt: ObjectPhysicsDataEvent) => - { - if (this.objects[evt.localID]) - { - this.objects[evt.localID].physicsShapeType = evt.physicsShapeType; - this.objects[evt.localID].density = evt.density; - this.objects[evt.localID].restitution = evt.restitution; - this.objects[evt.localID].gravityMultiplier = evt.gravityMultiplier; - this.objects[evt.localID].friction = evt.friction; - } - }); - - this.selectedChecker = setInterval(() => - { - if (this.circuit === undefined) - { - return; - } - try - { - let selectObjects = []; - for (const key of Object.keys(this.selectedPrimsWithoutUpdate)) - { - selectObjects.push(key); - } - - function shuffle(a: string[]): string[] - { - let j, x, i; - for (i = a.length - 1; i > 0; i--) - { - j = Math.floor(Math.random() * (i + 1)); - x = a[i]; - a[i] = a[j]; - a[j] = x; - } - return a; - } - - selectObjects = shuffle(selectObjects); - if (selectObjects.length > 10) - { - selectObjects = selectObjects.slice(0, 20); - } - if (selectObjects.length > 0) - { - const selectObject = new ObjectSelectMessage(); - selectObject.AgentData = { - AgentID: this.agent.agentID, - SessionID: this.circuit.sessionID - }; - selectObject.ObjectData = []; - for (const id of selectObjects) - { - selectObject.ObjectData.push({ - ObjectLocalID: parseInt(id, 10) - }); - } - this.circuit.sendMessage(selectObject, PacketFlags.Reliable); - } - } - catch (e: unknown) - { - Logger.Error(e); - } - }, 1000) - } - - private applyObjectProperties(o: GameObject, obj: any): void - { - if (this.selectedPrimsWithoutUpdate[o.ID]) - { - delete this.selectedPrimsWithoutUpdate[o.ID]; - } - // const n = Utils.BufferToStringSimple(obj.Name); // Currently unused - o.creatorID = obj.CreatorID; - o.creationDate = obj.CreationDate; - o.baseMask = obj.BaseMask; - o.ownerMask = obj.OwnerMask; - o.groupMask = obj.GroupMask; - o.everyoneMask = obj.EveryoneMask; - o.nextOwnerMask = obj.NextOwnerMask; - o.ownershipCost = obj.OwnershipCost; - o.saleType = obj.SaleType; - o.salePrice = obj.SalePrice; - o.aggregatePerms = obj.AggregatePerms; - o.aggregatePermTextures = obj.AggregatePermTextures; - o.aggregatePermTexturesOwner = obj.AggregatePermTexturesOwner; - o.category = obj.Category; - o.inventorySerial = obj.InventorySerial; - o.itemID = obj.ItemID; - o.folderID = obj.FolderID; - o.fromTaskID = obj.FromTaskID; - o.groupID = obj.GroupID; - o.lastOwnerID = obj.LastOwnerID; - o.name = Utils.BufferToStringSimple(obj.Name); - o.description = Utils.BufferToStringSimple(obj.Description); - o.touchName = Utils.BufferToStringSimple(obj.TouchName); - o.sitName = Utils.BufferToStringSimple(obj.SitName); - o.textureID = Utils.BufferToStringSimple(obj.TextureID); - 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) - { - // tslint:disable-next-line:no-bitwise - // noinspection JSBitwiseOperatorUsage - if (o.Flags & PrimFlags.CreateSelected) - { - const evt = new SelectedObjectEvent(); - evt.object = o; - this.clientEvents.onSelectedObjectEvent.next(evt); - } - } - } - - protected async requestMissingObject(localID: number, attempt = 0): Promise - { - if (this.requestedObjects[localID]) - { - return; - } - if (this.circuit === undefined) - { - return; - } - this.requestedObjects[localID] = true; - const rmo = new RequestMultipleObjectsMessage(); - rmo.AgentData = { - AgentID: this.agent.agentID, - SessionID: this.circuit.sessionID - }; - rmo.ObjectData = []; - rmo.ObjectData.push({ - CacheMissType: 0, - ID: localID - }); - this.circuit.sendMessage(rmo, PacketFlags.Reliable); - - const selectObject = new ObjectSelectMessage(); - selectObject.AgentData = { - AgentID: this.agent.agentID, - SessionID: this.circuit.sessionID - }; - selectObject.ObjectData = [ - { - 'ObjectLocalID': localID - } - ]; - this.circuit.sendMessage(selectObject, PacketFlags.Reliable); - - try - { - await this.circuit.waitForMessage(Message.ObjectUpdate, 10000, (message: ObjectUpdateMessage): FilterResponse => - { - for (const obj of message.ObjectData) - { - if (obj.ID === localID) - { - return FilterResponse.Finish; - } - } - return FilterResponse.NoMatch; - }); - delete this.requestedObjects[localID]; - } - catch (error) - { - delete this.requestedObjects[localID]; - if (attempt < 5) - { - await this.requestMissingObject(localID, ++attempt); - } - else - { - if (!this.circuit) - { - return; - } - console.error('Error retrieving missing object after 5 attempts: ' + localID); - console.error(error); - } - } - finally - { - if (this.circuit) - { - const deselectObject = new ObjectDeselectMessage(); - deselectObject.AgentData = { - AgentID: this.agent.agentID, - SessionID: this.circuit.sessionID - }; - deselectObject.ObjectData = [ - { - 'ObjectLocalID': localID - } - ]; - this.circuit.sendMessage(deselectObject, PacketFlags.Reliable); - } - } - } - - protected objectUpdate(objectUpdate: ObjectUpdateMessage): void - { - for (const objData of objectUpdate.ObjectData) - { - const localID = objData.ID; - const parentID = objData.ParentID; - let addToParentList = true; - let newObject = false; - - if (this.objects[localID]) - { - if (this.objects[localID].ParentID !== parentID && this.objectsByParent[parentID]) - { - const ind = this.objectsByParent[parentID].indexOf(localID); - if (ind !== -1) - { - this.objectsByParent[parentID].splice(ind, 1); - } - } - else if (this.objectsByParent[parentID]) - { - addToParentList = false; - } - } - else - { - newObject = true; - this.objects[localID] = new GameObject(); - this.objects[localID].region = this.agent.currentRegion; - } - - const obj = this.objects[localID]; - obj.deleted = false; - obj.ID = objData.ID; - obj.FullID = objData.FullID; - obj.ParentID = objData.ParentID; - obj.OwnerID = objData.OwnerID; - obj.PCode = objData.PCode; - - this.objects[localID].NameValue = this.parseNameValues(Utils.BufferToStringSimple(objData.NameValue)); - - this.objects[localID].IsAttachment = this.objects[localID].NameValue['AttachItemID'] !== undefined; - if (obj.IsAttachment && obj.State !== undefined) - { - this.objects[localID].attachmentPoint = this.decodeAttachPoint(obj.State); - } - - if (objData.PCode === PCode.Avatar && this.objects[localID].FullID.toString() === this.agent.agentID.toString()) - { - this.agent.localID = localID; - - if (this.options & BotOptionFlags.StoreMyAttachmentsOnly) - { - for (const objParentID of Object.keys(this.objectsByParent)) - { - const parent = parseInt(objParentID, 10); - if (parent !== this.agent.localID) - { - let foundAvatars = false; - for (const objID of this.objectsByParent[parent]) - { - if (this.objects[objID]) - { - const o = this.objects[objID]; - if (o.PCode === PCode.Avatar) - { - foundAvatars = true; - } - } - } - if (this.objects[parent]) - { - const o = this.objects[parent]; - if (o.PCode === PCode.Avatar) - { - foundAvatars = true; - } - } - if (!foundAvatars) - { - this.deleteObject(parent); - } - } - } - } - } - - this.objectsByUUID[objData.FullID.toString()] = localID; - if (!this.objectsByParent[parentID]) - { - this.objectsByParent[parentID] = []; - } - if (addToParentList) - { - this.objectsByParent[parentID].push(localID); - } - - if (objData.PCode !== PCode.Avatar && this.options & BotOptionFlags.StoreMyAttachmentsOnly) - { - if (this.agent.localID !== 0 && obj.ParentID !== this.agent.localID) - { - // Drop object - this.deleteObject(localID); - return; - } - } - - this.notifyObjectUpdate(newObject, obj); - - if (objData.ParentID !== undefined && objData.ParentID !== 0 && !this.objects[objData.ParentID]) - { - this.requestMissingObject(objData.ParentID); - } - } - } - - protected notifyTerseUpdate(obj: GameObject): void - { - if (this.objects[obj.ID]) - { - if (obj.PCode === PCode.Avatar) - { - if (this.agent.currentRegion.agents[obj.FullID.toString()] !== undefined) - { - this.agent.currentRegion.agents[obj.FullID.toString()].processObjectUpdate(obj); - } - else - { - console.warn('Received update for unknown avatar, but not a new object?!'); - } - } - const updObj = new ObjectUpdatedEvent(); - updObj.localID = obj.ID; - updObj.objectID = obj.FullID; - updObj.object = obj; - this.clientEvents.onObjectUpdatedTerseEvent.next(updObj); - } - } - - protected notifyObjectUpdate(newObject: boolean, obj: GameObject): void - { - if (obj.PCode === PCode.Avatar) - { - const avatarID = obj.FullID.toString(); - if (newObject) - { - if (this.agent.currentRegion.agents[avatarID] === undefined) - { - const av = Avatar.fromGameObject(obj); - this.agent.currentRegion.agents[avatarID] = av; - this.clientEvents.onAvatarEnteredRegion.next(av) - } - else - { - this.agent.currentRegion.agents[avatarID].processObjectUpdate(obj); - } - } - else - { - if (this.agent.currentRegion.agents[avatarID] !== undefined) - { - this.agent.currentRegion.agents[avatarID].processObjectUpdate(obj); - } - else - { - console.warn('Received update for unknown avatar, but not a new object?!'); - } - } - } - if (obj.ParentID === 0 || (obj.ParentID !== undefined && this.objects[obj.ParentID] !== undefined && this.objects[obj.ParentID].PCode === PCode.Avatar)) - { - if (newObject) - { - if (obj.IsAttachment && obj.ParentID !== undefined) - { - if (this.objects[obj.ParentID] !== undefined && this.objects[obj.ParentID].PCode === PCode.Avatar) - { - const avatar = this.agent.currentRegion.agents[this.objects[obj.ParentID].FullID.toString()]; - - let invItemID = UUID.zero(); - if (obj.NameValue['AttachItemID']) - { - invItemID = new UUID(obj.NameValue['AttachItemID'].value); - } - - this.agent.currentRegion.clientCommands.region.resolveObject(obj, true, false).then(() => - { - try - { - if (obj.itemID === undefined) - { - obj.itemID = UUID.zero(); - } - obj.itemID = invItemID; - if (avatar !== undefined) - { - avatar.addAttachment(obj); - } - } - catch (err) - { - console.error(err); - } - }).catch(() => - { - console.error('Failed to resolve new avatar attachment'); - }); - - } - } - - const newObj = new NewObjectEvent(); - newObj.localID = obj.ID; - newObj.objectID = obj.FullID; - newObj.object = obj; - newObj.createSelected = obj.Flags !== undefined && (obj.Flags & PrimFlags.CreateSelected) !== 0; - obj.createdSelected = newObj.createSelected; - // tslint:disable-next-line:no-bitwise - // noinspection JSBitwiseOperatorUsage - if (obj.Flags !== undefined && obj.Flags & PrimFlags.CreateSelected && !this.pendingObjectProperties[obj.FullID.toString()]) - { - this.selectedPrimsWithoutUpdate[obj.ID] = true; - } - this.clientEvents.onNewObjectEvent.next(newObj); - } - else - { - const updObj = new ObjectUpdatedEvent(); - updObj.localID = obj.ID; - updObj.objectID = obj.FullID; - updObj.object = obj; - this.clientEvents.onObjectUpdatedEvent.next(updObj); - } - if (this.pendingObjectProperties[obj.FullID.toString()]) - { - this.applyObjectProperties(obj, this.pendingObjectProperties[obj.FullID.toString()]); - delete this.pendingObjectProperties[obj.FullID.toString()]; - } - } - } - - protected objectUpdateCached(objectUpdateCached: ObjectUpdateCachedMessage): void - { - if (this.circuit === undefined) - { - return; - } - const rmo = new RequestMultipleObjectsMessage(); - rmo.AgentData = { - AgentID: this.agent.agentID, - SessionID: this.circuit.sessionID - }; - rmo.ObjectData = []; - for (const obj of objectUpdateCached.ObjectData) - { - rmo.ObjectData.push({ - CacheMissType: 0, - ID: obj.ID - }); - } - this.circuit.sendMessage(rmo, 0); - } - - protected objectUpdateCompressed(objectUpdateCompressed: ObjectUpdateCompressedMessage): void - { - for (const obj of objectUpdateCompressed.ObjectData) - { - const buf = obj.Data; - let pos = 0; - - const fullID = new UUID(buf, pos); - pos += 16; - const localID = buf.readUInt32LE(pos); - pos += 4; - const pcode = buf.readUInt8(pos++); - let newObj = false; - if (!this.objects[localID]) - { - newObj = true; - this.objects[localID] = new GameObject(); - this.objects[localID].region = this.agent.currentRegion; - } - const o = this.objects[localID]; - o.deleted = false; - o.ID = localID; - o.PCode = pcode; - this.objectsByUUID[fullID.toString()] = localID; - o.FullID = fullID; - - - pos++; - - pos = pos + 42; - const compressedflags: CompressedFlags = buf.readUInt32LE(pos); - pos = pos + 4; - o.OwnerID = new UUID(buf, pos); - pos += 16; - - if (compressedflags & CompressedFlags.HasAngularVelocity) - { - pos = pos + 12; - } - let newParentID = 0; - if (compressedflags & CompressedFlags.HasParent) - { - newParentID = buf.readUInt32LE(pos); - pos += 4; - } - - o.ParentID = newParentID; - let add = true; - if (!newObj && o.ParentID !== undefined) - { - if (newParentID !== o.ParentID) - { - const index = this.objectsByParent[o.ParentID].indexOf(localID); - if (index !== -1) - { - this.objectsByParent[o.ParentID].splice(index, 1); - } - } - else if (this.objectsByParent[o.ParentID]) - { - add = false; - } - } - if (add) - { - if (!this.objectsByParent[newParentID]) - { - this.objectsByParent[newParentID] = []; - } - this.objectsByParent[newParentID].push(localID); - } - if (pcode !== PCode.Avatar && newObj && this.options & BotOptionFlags.StoreMyAttachmentsOnly) - { - if (this.agent.localID !== 0 && o.ParentID !== this.agent.localID) - { - // Drop object - this.deleteObject(localID); - return; - } - } - if (o.ParentID !== undefined && o.ParentID !== 0 && !this.objects[o.ParentID]) - { - this.requestMissingObject(o.ParentID); - } - if (compressedflags & CompressedFlags.Tree) - { - pos++; - } - else if (compressedflags & CompressedFlags.ScratchPad) - { - const scratchPadSize = buf.readUInt8(pos++); - // Ignore this data - pos = pos + scratchPadSize; - } - if (compressedflags & CompressedFlags.HasText) - { - // Read null terminated string - const result = Utils.BufferToString(buf, pos); - - pos += result.readLength; - pos = pos + 4; - } - if (compressedflags & CompressedFlags.MediaURL) - { - const result = Utils.BufferToString(buf, pos); - - pos += result.readLength; - } - if (compressedflags & CompressedFlags.HasParticles) - { - pos += 86; - } - - // Extra params - const extraParamsLength = ExtraParams.getLengthOfParams(buf, pos); - o.extraParams = ExtraParams.from(buf.slice(pos, pos + extraParamsLength)); - pos = pos + extraParamsLength; - - if (compressedflags & CompressedFlags.HasSound) - { - pos = pos + 25 - } - if (compressedflags & CompressedFlags.HasNameValues) - { - const result = Utils.BufferToString(buf, pos); - o.NameValue = this.parseNameValues(result.result); - pos += result.readLength; - } - pos++; - pos = pos + 22; - const textureEntryLength = buf.readUInt32LE(pos); - pos = pos + 4; - pos = pos + textureEntryLength; - if (compressedflags & CompressedFlags.TextureAnimation) - { - pos = pos + 4; - } - - o.IsAttachment = (compressedflags & CompressedFlags.HasNameValues) !== 0 && o.ParentID !== 0; - if (o.IsAttachment && o.State !== undefined) - { - o.attachmentPoint = this.decodeAttachPoint(o.State); - } - - this.notifyObjectUpdate(newObj, o); - } - } - - protected decodeAttachPoint(state: number): number - { - const mask = 0xf << 4 >>> 0; - return (((state & mask) >>> 4) | ((state & ~mask) << 4)) >>> 0; - } - - protected objectUpdateTerse(_objectUpdateTerse: ImprovedTerseObjectUpdateMessage): void - { - - } - - protected killObject(killObj: KillObjectMessage): void - { - for (const obj of killObj.ObjectData) - { - const objectID = obj.ID; - if (this.objects[objectID]) - { - this.deleteObject(objectID); - } - } - } - - setPersist(persist: boolean): void - { - this.persist = persist; - if (!this.persist) - { - for (const d of this.deadObjects) - { - this.deleteObject(d); - } - this.deadObjects = []; - } - } - - deleteObject(objectID: number): void - { - if (this.objects[objectID]) - { - const objectUUID = this.objects[objectID].FullID; - const obj = this.objects[objectID]; - obj.deleted = true; - - if (this.persist) - { - this.deadObjects.push(objectID); - return; - } - - if (obj.IsAttachment && obj.ParentID !== undefined) - { - if (this.objects[obj.ParentID] !== undefined && this.objects[obj.ParentID].PCode === PCode.Avatar) - { - this.agent.currentRegion.agents[this.objects[obj.ParentID].FullID.toString()]?.removeAttachment(obj); - } - } - - if (this.agent.currentRegion.agents[objectUUID.toString()] !== undefined) - { - this.agent.currentRegion.agents[objectUUID.toString()].isVisible = false; - } - - // First, kill all children (not the people kind) - if (this.objectsByParent[objectID]) - { - for (const childObjID of this.objectsByParent[objectID]) - { - this.deleteObject(childObjID); - } - } - delete this.objectsByParent[objectID]; - - // Now delete this object - const uuid = obj.FullID.toString(); - - if (this.objectsByUUID[uuid]) - { - delete this.objectsByUUID[uuid]; - } - if (obj.ParentID !== undefined) - { - const parentID = obj.ParentID; - if (this.objectsByParent[parentID]) - { - const ind = this.objectsByParent[parentID].indexOf(objectID); - if (ind !== -1) - { - this.objectsByParent[parentID].splice(ind, 1); - } - } - } - if (this.rtree && obj.rtreeEntry !== undefined) - { - this.rtree.remove(obj.rtreeEntry); - } - delete this.objects[objectID]; - } - } - getObjectsByParent(parentID: number): GameObject[] - { - const list = this.objectsByParent[parentID]; - if (list === undefined) - { - return []; - } - const result: GameObject[] = []; - for (const localID of list) - { - if (this.objects[localID]) - { - result.push(this.objects[localID]); - } - } - result.sort((a: GameObject, b: GameObject) => - { - return a.ID - b.ID; - }); - return result; - } - - parseNameValues(str: string): { [key: string]: NameValue } - { - const nv: { [key: string]: NameValue } = {}; - const lines = str.split('\n'); - for (const line of lines) - { - if (line.length > 0) - { - let kv = line.split(/[\t ]/); - if (kv.length > 5) - { - for (let x = 5; x < kv.length; x++) - { - kv[4] += ' ' + kv[x]; - } - kv = kv.slice(0, 5); - } - if (kv.length === 5) - { - const namevalue = new NameValue(); - namevalue.type = kv[1]; - namevalue.class = kv[2]; - namevalue.sendTo = kv[3]; - namevalue.value = kv[4]; - nv[kv[0]] = namevalue; - } - } - } - return nv; - } - - shutdown(): void - { - if (this.selectedChecker !== undefined) - { - clearInterval(this.selectedChecker); - delete this.selectedChecker; - } - this.physicsSubscription.unsubscribe(); - this.objects = {}; - if (this.rtree) - { - this.rtree.clear(); - } - this.objectsByUUID = {}; - this.objectsByParent = {}; - delete this.circuit; - } - - protected findParent(go: GameObject): GameObject - { - if (go.ParentID !== undefined && go.ParentID !== 0 && this.objects[go.ParentID]) - { - return this.findParent(this.objects[go.ParentID]); - } - else - { - if (go.ParentID !== undefined && go.ParentID !== 0 && !this.objects[go.ParentID]) - { - this.requestMissingObject(go.ParentID).catch((e: unknown) => - { - Logger.Error(e); - }); - } - return go; - } - } - - populateChildren(obj: GameObject, _resolve = false): void - { - if (obj !== undefined) - { - obj.children = []; - obj.totalChildren = 0; - for (const child of this.getObjectsByParent(obj.ID)) - { - if (child.PCode !== PCode.Avatar) - { - obj.totalChildren++; - this.populateChildren(child); - if (child.totalChildren !== undefined) - { - obj.totalChildren += child.totalChildren; - } - obj.children.push(child); - } - } - } - } - - async getAllObjects(): Promise - { - const results = []; - const found: { [key: string]: GameObject } = {}; - for (const k of Object.keys(this.objects)) - { - const go = this.objects[parseInt(k, 10)]; - if (go.PCode !== PCode.Avatar && (go.IsAttachment === undefined || !go.IsAttachment)) - { - try - { - const parent = this.findParent(go); - if (parent.ParentID === 0) - { - const uuid = parent.FullID.toString(); - - if (found[uuid] === undefined) - { - found[uuid] = parent; - results.push(parent); - } - } - } - catch (error) - { - console.log('Failed to find parent for ' + go.FullID.toString()); - console.error(error); - // Unable to find parent, full object probably not fully loaded yet - } - } - } - - // Now populate children of each found object - for (const obj of results) - { - this.populateChildren(obj); - } - - return results; - } - - - getNumberOfObjects(): number - { - return Object.keys(this.objects).length; - } - - async getObjectsInArea(minX: number, maxX: number, minY: number, maxY: number, minZ: number, maxZ: number): Promise - { - if (!this.rtree) - { - throw new Error('GetObjectsInArea not available with the Lite object store'); - } - const result = this.rtree.search({ - minX: minX, - maxX: maxX, - minY: minY, - maxY: maxY, - minZ: minZ, - maxZ: maxZ - }); - const found: { [key: string]: GameObject } = {}; - const objs: GameObject[] = []; - for (const obj of result) - { - const o = obj as ITreeBoundingBox; - const go = o.gameObject as GameObject; - if (go.PCode !== PCode.Avatar && (go.IsAttachment === undefined || !go.IsAttachment)) - { - try - { - const parent = this.findParent(go); - if (parent.PCode !== PCode.Avatar && (parent.IsAttachment === undefined || !parent.IsAttachment) && parent.ParentID === 0) - { - const uuid = parent.FullID.toString(); - - if (found[uuid] === undefined) - { - found[uuid] = parent; - objs.push(parent); - } - } - } - catch (error) - { - console.log('Failed to find parent for ' + go.FullID.toString()); - console.error(error); - // Unable to find parent, full object probably not fully loaded yet - } - } - } - - // Now populate children of each found object - for (const obj of objs) - { - this.populateChildren(obj); - } - - return objs; - } - - getObjectByUUID(fullID: UUID | string): GameObject - { - if (fullID instanceof UUID) - { - fullID = fullID.toString(); - } - if (!this.objectsByUUID[fullID]) - { - throw new Error('No object found with that UUID'); - } - const localID: number = this.objectsByUUID[fullID]; - return this.objects[localID]; - } - - getObjectByLocalID(localID: number): GameObject - { - if (!this.objects[localID]) - { - throw new Error('No object found with that UUID'); - } - return this.objects[localID]; - } - - insertIntoRtree(obj: GameObject): void - { - if (!this.rtree) - { - return; - } - if (obj.rtreeEntry !== undefined) - { - this.rtree.remove(obj.rtreeEntry); - } - if (!obj.Scale || !obj.Position || !obj.Rotation) - { - return; - } - const normalizedScale = new Vector3(obj.Scale).multiplyByTSMQuat(new Quaternion(obj.Rotation)); - - const bounds: ITreeBoundingBox = { - minX: obj.Position.x - (normalizedScale.x / 2), - maxX: obj.Position.x + (normalizedScale.x / 2), - minY: obj.Position.y - (normalizedScale.y / 2), - maxY: obj.Position.y + (normalizedScale.y / 2), - minZ: obj.Position.z - (normalizedScale.z / 2), - maxZ: obj.Position.z + (normalizedScale.z / 2), - gameObject: obj - }; - - obj.rtreeEntry = bounds; - this.rtree.insert(bounds); - } -} +import { Circuit } from './Circuit'; +import { Logger } from './Logger'; +import { Packet } from './Packet'; +import { Message } from '../enums/Message'; +import { ObjectUpdateMessage } from './messages/ObjectUpdate'; +import { ObjectUpdateCachedMessage } from './messages/ObjectUpdateCached'; +import { ObjectUpdateCompressedMessage } from './messages/ObjectUpdateCompressed'; +import { ImprovedTerseObjectUpdateMessage } from './messages/ImprovedTerseObjectUpdate'; +import { RequestMultipleObjectsMessage } from './messages/RequestMultipleObjects'; +import { Agent } from './Agent'; +import { UUID } from './UUID'; +import { Utils } from './Utils'; +import { ClientEvents } from './ClientEvents'; +import { KillObjectMessage } from './messages/KillObject'; +import { IObjectStore } from './interfaces/IObjectStore'; +import { NameValue } from './NameValue'; +import { GameObject } from './public/GameObject'; +import { RBush3D } from 'rbush-3d/dist'; +import { ITreeBoundingBox } from './interfaces/ITreeBoundingBox'; +import { FilterResponse } from '../enums/FilterResponse'; +import { ObjectSelectMessage } from './messages/ObjectSelect'; +import { ObjectDeselectMessage } from './messages/ObjectDeselect'; +import { Quaternion } from './Quaternion'; +import { Subscription } from 'rxjs'; +import { ExtraParams } from './public/ExtraParams'; +import { ObjectPropertiesMessage } from './messages/ObjectProperties'; +import { SelectedObjectEvent } from '../events/SelectedObjectEvent'; +import { PrimFlags } from '../enums/PrimFlags'; +import { PacketFlags } from '../enums/PacketFlags'; +import { PCode } from '../enums/PCode'; +import { BotOptionFlags } from '../enums/BotOptionFlags'; +import { NewObjectEvent } from '../events/NewObjectEvent'; +import { ObjectUpdatedEvent } from '../events/ObjectUpdatedEvent'; +import { CompressedFlags } from '../enums/CompressedFlags'; +import { Vector3 } from './Vector3'; +import { ObjectPhysicsDataEvent } from '../events/ObjectPhysicsDataEvent'; +import { ObjectResolvedEvent } from '../events/ObjectResolvedEvent'; +import { Avatar } from './public/Avatar'; + +import Timer = NodeJS.Timer; + +export class ObjectStoreLite implements IObjectStore +{ + protected circuit?: Circuit; + protected agent: Agent; + protected objects: { [key: number]: GameObject } = {}; + protected objectsByUUID: { [key: string]: number } = {}; + protected objectsByParent: { [key: number]: number[] } = {}; + protected clientEvents: ClientEvents; + protected options: BotOptionFlags; + protected requestedObjects: { [key: number]: boolean } = {}; + protected deadObjects: number[] = []; + protected persist = false; + protected pendingObjectProperties: { [key: string]: any } = {}; + private physicsSubscription: Subscription; + private selectedPrimsWithoutUpdate: { [key: number]: boolean } = {}; + private selectedChecker?: Timer; + + rtree?: RBush3D; + + constructor(circuit: Circuit, agent: Agent, clientEvents: ClientEvents, options: BotOptionFlags) + { + agent.localID = 0; + this.options = options; + this.clientEvents = clientEvents; + this.circuit = circuit; + this.agent = agent; + this.circuit.subscribeToMessages([ + Message.ObjectUpdate, + Message.ObjectUpdateCached, + Message.ObjectUpdateCompressed, + Message.ImprovedTerseObjectUpdate, + Message.ObjectProperties, + Message.KillObject + ], async(packet: Packet) => + { + switch (packet.message.id) + { + case Message.ObjectProperties: + { + const objProp = packet.message as ObjectPropertiesMessage; + for (const obj of objProp.ObjectData) + { + const obje = this.objectsByUUID[obj.ObjectID.toString()]; + if (obje !== undefined && this.objects[obje] !== undefined) + { + const o = this.objects[obje]; + this.applyObjectProperties(o, obj); + } + else + { + this.pendingObjectProperties[obj.ObjectID.toString()] = obj; + } + } + break; + } + case Message.ObjectUpdate: + { + const objectUpdate = packet.message as ObjectUpdateMessage; + this.objectUpdate(objectUpdate); + break; + } + case Message.ObjectUpdateCached: + { + const objectUpdateCached = packet.message as ObjectUpdateCachedMessage; + this.objectUpdateCached(objectUpdateCached); + break; + } + case Message.ObjectUpdateCompressed: + { + const objectUpdateCompressed = packet.message as ObjectUpdateCompressedMessage; + await this.objectUpdateCompressed(objectUpdateCompressed); + break; + } + case Message.ImprovedTerseObjectUpdate: + { + const objectUpdateTerse = packet.message as ImprovedTerseObjectUpdateMessage; + this.objectUpdateTerse(objectUpdateTerse); + break; + } + case Message.KillObject: + { + const killObj = packet.message as KillObjectMessage; + this.killObject(killObj); + break; + } + } + }); + + this.physicsSubscription = this.clientEvents.onPhysicsDataEvent.subscribe((evt: ObjectPhysicsDataEvent) => + { + if (this.objects[evt.localID]) + { + this.objects[evt.localID].physicsShapeType = evt.physicsShapeType; + this.objects[evt.localID].density = evt.density; + this.objects[evt.localID].restitution = evt.restitution; + this.objects[evt.localID].gravityMultiplier = evt.gravityMultiplier; + this.objects[evt.localID].friction = evt.friction; + } + }); + + this.selectedChecker = setInterval(() => + { + if (this.circuit === undefined) + { + return; + } + try + { + let selectObjects = []; + for (const key of Object.keys(this.selectedPrimsWithoutUpdate)) + { + selectObjects.push(key); + } + + function shuffle(a: string[]): string[] + { + let j, x, i; + for (i = a.length - 1; i > 0; i--) + { + j = Math.floor(Math.random() * (i + 1)); + x = a[i]; + a[i] = a[j]; + a[j] = x; + } + return a; + } + + selectObjects = shuffle(selectObjects); + if (selectObjects.length > 10) + { + selectObjects = selectObjects.slice(0, 20); + } + if (selectObjects.length > 0) + { + const selectObject = new ObjectSelectMessage(); + selectObject.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.circuit.sessionID + }; + selectObject.ObjectData = []; + for (const id of selectObjects) + { + selectObject.ObjectData.push({ + ObjectLocalID: parseInt(id, 10) + }); + } + this.circuit.sendMessage(selectObject, PacketFlags.Reliable); + } + } + catch (e: unknown) + { + Logger.Error(e); + } + }, 1000) + } + + private applyObjectProperties(o: GameObject, obj: any): void + { + if (this.selectedPrimsWithoutUpdate[o.ID]) + { + delete this.selectedPrimsWithoutUpdate[o.ID]; + } + // const n = Utils.BufferToStringSimple(obj.Name); // Currently unused + o.creatorID = obj.CreatorID; + o.creationDate = obj.CreationDate; + o.baseMask = obj.BaseMask; + o.ownerMask = obj.OwnerMask; + o.groupMask = obj.GroupMask; + o.everyoneMask = obj.EveryoneMask; + o.nextOwnerMask = obj.NextOwnerMask; + o.ownershipCost = obj.OwnershipCost; + o.saleType = obj.SaleType; + o.salePrice = obj.SalePrice; + o.aggregatePerms = obj.AggregatePerms; + o.aggregatePermTextures = obj.AggregatePermTextures; + o.aggregatePermTexturesOwner = obj.AggregatePermTexturesOwner; + o.category = obj.Category; + o.inventorySerial = obj.InventorySerial; + o.itemID = obj.ItemID; + o.folderID = obj.FolderID; + o.fromTaskID = obj.FromTaskID; + o.groupID = obj.GroupID; + o.lastOwnerID = obj.LastOwnerID; + o.name = Utils.BufferToStringSimple(obj.Name); + o.description = Utils.BufferToStringSimple(obj.Description); + o.touchName = Utils.BufferToStringSimple(obj.TouchName); + o.sitName = Utils.BufferToStringSimple(obj.SitName); + o.textureID = Utils.BufferToStringSimple(obj.TextureID); + 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) + { + // tslint:disable-next-line:no-bitwise + // noinspection JSBitwiseOperatorUsage + if (o.Flags & PrimFlags.CreateSelected) + { + const evt = new SelectedObjectEvent(); + evt.object = o; + this.clientEvents.onSelectedObjectEvent.next(evt); + } + } + } + + protected async requestMissingObject(localID: number, attempt = 0): Promise + { + if (this.requestedObjects[localID]) + { + return; + } + if (this.circuit === undefined) + { + return; + } + this.requestedObjects[localID] = true; + const rmo = new RequestMultipleObjectsMessage(); + rmo.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.circuit.sessionID + }; + rmo.ObjectData = []; + rmo.ObjectData.push({ + CacheMissType: 0, + ID: localID + }); + this.circuit.sendMessage(rmo, PacketFlags.Reliable); + + const selectObject = new ObjectSelectMessage(); + selectObject.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.circuit.sessionID + }; + selectObject.ObjectData = [ + { + 'ObjectLocalID': localID + } + ]; + this.circuit.sendMessage(selectObject, PacketFlags.Reliable); + + try + { + await this.circuit.waitForMessage(Message.ObjectUpdate, 10000, (message: ObjectUpdateMessage): FilterResponse => + { + for (const obj of message.ObjectData) + { + if (obj.ID === localID) + { + return FilterResponse.Finish; + } + } + return FilterResponse.NoMatch; + }); + delete this.requestedObjects[localID]; + } + catch (error) + { + delete this.requestedObjects[localID]; + if (attempt < 5) + { + await this.requestMissingObject(localID, ++attempt); + } + else + { + if (!this.circuit) + { + return; + } + console.error('Error retrieving missing object after 5 attempts: ' + localID); + console.error(error); + } + } + finally + { + if (this.circuit) + { + const deselectObject = new ObjectDeselectMessage(); + deselectObject.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.circuit.sessionID + }; + deselectObject.ObjectData = [ + { + 'ObjectLocalID': localID + } + ]; + this.circuit.sendMessage(deselectObject, PacketFlags.Reliable); + } + } + } + + protected objectUpdate(objectUpdate: ObjectUpdateMessage): void + { + for (const objData of objectUpdate.ObjectData) + { + const localID = objData.ID; + const parentID = objData.ParentID; + let addToParentList = true; + let newObject = false; + + if (this.objects[localID]) + { + if (this.objects[localID].ParentID !== parentID && this.objectsByParent[parentID]) + { + const ind = this.objectsByParent[parentID].indexOf(localID); + if (ind !== -1) + { + this.objectsByParent[parentID].splice(ind, 1); + } + } + else if (this.objectsByParent[parentID]) + { + addToParentList = false; + } + } + else + { + newObject = true; + this.objects[localID] = new GameObject(); + this.objects[localID].region = this.agent.currentRegion; + } + + const obj = this.objects[localID]; + obj.deleted = false; + obj.ID = objData.ID; + obj.FullID = objData.FullID; + obj.ParentID = objData.ParentID; + obj.OwnerID = objData.OwnerID; + obj.PCode = objData.PCode; + + this.objects[localID].NameValue = this.parseNameValues(Utils.BufferToStringSimple(objData.NameValue)); + + this.objects[localID].IsAttachment = this.objects[localID].NameValue['AttachItemID'] !== undefined; + if (obj.IsAttachment && obj.State !== undefined) + { + this.objects[localID].attachmentPoint = this.decodeAttachPoint(obj.State); + } + + if (objData.PCode === PCode.Avatar && this.objects[localID].FullID.toString() === this.agent.agentID.toString()) + { + this.agent.localID = localID; + + if (this.options & BotOptionFlags.StoreMyAttachmentsOnly) + { + for (const objParentID of Object.keys(this.objectsByParent)) + { + const parent = parseInt(objParentID, 10); + if (parent !== this.agent.localID) + { + let foundAvatars = false; + for (const objID of this.objectsByParent[parent]) + { + if (this.objects[objID]) + { + const o = this.objects[objID]; + if (o.PCode === PCode.Avatar) + { + foundAvatars = true; + } + } + } + if (this.objects[parent]) + { + const o = this.objects[parent]; + if (o.PCode === PCode.Avatar) + { + foundAvatars = true; + } + } + if (!foundAvatars) + { + this.deleteObject(parent); + } + } + } + } + } + + this.objectsByUUID[objData.FullID.toString()] = localID; + if (!this.objectsByParent[parentID]) + { + this.objectsByParent[parentID] = []; + } + if (addToParentList) + { + this.objectsByParent[parentID].push(localID); + } + + if (objData.PCode !== PCode.Avatar && this.options & BotOptionFlags.StoreMyAttachmentsOnly) + { + if (this.agent.localID !== 0 && obj.ParentID !== this.agent.localID) + { + // Drop object + this.deleteObject(localID); + return; + } + } + + this.notifyObjectUpdate(newObject, obj); + + if (objData.ParentID !== undefined && objData.ParentID !== 0 && !this.objects[objData.ParentID]) + { + this.requestMissingObject(objData.ParentID); + } + } + } + + protected notifyTerseUpdate(obj: GameObject): void + { + if (this.objects[obj.ID]) + { + if (obj.PCode === PCode.Avatar) + { + if (this.agent.currentRegion.agents[obj.FullID.toString()] !== undefined) + { + this.agent.currentRegion.agents[obj.FullID.toString()].processObjectUpdate(obj); + } + else + { + console.warn('Received update for unknown avatar, but not a new object?!'); + } + } + const updObj = new ObjectUpdatedEvent(); + updObj.localID = obj.ID; + updObj.objectID = obj.FullID; + updObj.object = obj; + this.clientEvents.onObjectUpdatedTerseEvent.next(updObj); + } + } + + protected notifyObjectUpdate(newObject: boolean, obj: GameObject): void + { + if (obj.PCode === PCode.Avatar) + { + const avatarID = obj.FullID.toString(); + if (newObject) + { + if (this.agent.currentRegion.agents[avatarID] === undefined) + { + const av = Avatar.fromGameObject(obj); + this.agent.currentRegion.agents[avatarID] = av; + this.clientEvents.onAvatarEnteredRegion.next(av) + } + else + { + this.agent.currentRegion.agents[avatarID].processObjectUpdate(obj); + } + } + else + { + if (this.agent.currentRegion.agents[avatarID] !== undefined) + { + this.agent.currentRegion.agents[avatarID].processObjectUpdate(obj); + } + else + { + console.warn('Received update for unknown avatar, but not a new object?!'); + } + } + } + if (obj.ParentID === 0 || (obj.ParentID !== undefined && this.objects[obj.ParentID] !== undefined && this.objects[obj.ParentID].PCode === PCode.Avatar)) + { + if (newObject) + { + if (obj.IsAttachment && obj.ParentID !== undefined) + { + if (this.objects[obj.ParentID] !== undefined && this.objects[obj.ParentID].PCode === PCode.Avatar) + { + const avatar = this.agent.currentRegion.agents[this.objects[obj.ParentID].FullID.toString()]; + + let invItemID = UUID.zero(); + if (obj.NameValue['AttachItemID']) + { + invItemID = new UUID(obj.NameValue['AttachItemID'].value); + } + + this.agent.currentRegion.clientCommands.region.resolveObject(obj, true, false).then(() => + { + try + { + if (obj.itemID === undefined) + { + obj.itemID = UUID.zero(); + } + obj.itemID = invItemID; + if (avatar !== undefined) + { + avatar.addAttachment(obj); + } + } + catch (err) + { + console.error(err); + } + }).catch(() => + { + console.error('Failed to resolve new avatar attachment'); + }); + + } + } + + const newObj = new NewObjectEvent(); + newObj.localID = obj.ID; + newObj.objectID = obj.FullID; + newObj.object = obj; + newObj.createSelected = obj.Flags !== undefined && (obj.Flags & PrimFlags.CreateSelected) !== 0; + obj.createdSelected = newObj.createSelected; + // tslint:disable-next-line:no-bitwise + // noinspection JSBitwiseOperatorUsage + if (obj.Flags !== undefined && obj.Flags & PrimFlags.CreateSelected && !this.pendingObjectProperties[obj.FullID.toString()]) + { + this.selectedPrimsWithoutUpdate[obj.ID] = true; + } + this.clientEvents.onNewObjectEvent.next(newObj); + } + else + { + const updObj = new ObjectUpdatedEvent(); + updObj.localID = obj.ID; + updObj.objectID = obj.FullID; + updObj.object = obj; + this.clientEvents.onObjectUpdatedEvent.next(updObj); + } + if (this.pendingObjectProperties[obj.FullID.toString()]) + { + this.applyObjectProperties(obj, this.pendingObjectProperties[obj.FullID.toString()]); + delete this.pendingObjectProperties[obj.FullID.toString()]; + } + } + } + + protected objectUpdateCached(objectUpdateCached: ObjectUpdateCachedMessage): void + { + if (this.circuit === undefined) + { + return; + } + const rmo = new RequestMultipleObjectsMessage(); + rmo.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.circuit.sessionID + }; + rmo.ObjectData = []; + for (const obj of objectUpdateCached.ObjectData) + { + rmo.ObjectData.push({ + CacheMissType: 0, + ID: obj.ID + }); + } + this.circuit.sendMessage(rmo, 0 as PacketFlags); + } + + protected objectUpdateCompressed(objectUpdateCompressed: ObjectUpdateCompressedMessage): void + { + for (const obj of objectUpdateCompressed.ObjectData) + { + const buf = obj.Data; + let pos = 0; + + const fullID = new UUID(buf, pos); + pos += 16; + const localID = buf.readUInt32LE(pos); + pos += 4; + const pcode = buf.readUInt8(pos++); + let newObj = false; + if (!this.objects[localID]) + { + newObj = true; + this.objects[localID] = new GameObject(); + this.objects[localID].region = this.agent.currentRegion; + } + const o = this.objects[localID]; + o.deleted = false; + o.ID = localID; + o.PCode = pcode; + this.objectsByUUID[fullID.toString()] = localID; + o.FullID = fullID; + + + pos++; + + pos = pos + 42; + const compressedflags: CompressedFlags = buf.readUInt32LE(pos); + pos = pos + 4; + o.OwnerID = new UUID(buf, pos); + pos += 16; + + if (compressedflags & CompressedFlags.HasAngularVelocity) + { + pos = pos + 12; + } + let newParentID = 0; + if (compressedflags & CompressedFlags.HasParent) + { + newParentID = buf.readUInt32LE(pos); + pos += 4; + } + + o.ParentID = newParentID; + let add = true; + if (!newObj && o.ParentID !== undefined) + { + if (newParentID !== o.ParentID) + { + const index = this.objectsByParent[o.ParentID].indexOf(localID); + if (index !== -1) + { + this.objectsByParent[o.ParentID].splice(index, 1); + } + } + else if (this.objectsByParent[o.ParentID]) + { + add = false; + } + } + if (add) + { + if (!this.objectsByParent[newParentID]) + { + this.objectsByParent[newParentID] = []; + } + this.objectsByParent[newParentID].push(localID); + } + if (pcode !== PCode.Avatar && newObj && this.options & BotOptionFlags.StoreMyAttachmentsOnly) + { + if (this.agent.localID !== 0 && o.ParentID !== this.agent.localID) + { + // Drop object + this.deleteObject(localID); + return; + } + } + if (o.ParentID !== undefined && o.ParentID !== 0 && !this.objects[o.ParentID]) + { + this.requestMissingObject(o.ParentID); + } + if (compressedflags & CompressedFlags.Tree) + { + pos++; + } + else if (compressedflags & CompressedFlags.ScratchPad) + { + const scratchPadSize = buf.readUInt8(pos++); + // Ignore this data + pos = pos + scratchPadSize; + } + if (compressedflags & CompressedFlags.HasText) + { + // Read null terminated string + const result = Utils.BufferToString(buf, pos); + + pos += result.readLength; + pos = pos + 4; + } + if (compressedflags & CompressedFlags.MediaURL) + { + const result = Utils.BufferToString(buf, pos); + + pos += result.readLength; + } + if (compressedflags & CompressedFlags.HasParticles) + { + pos += 86; + } + + // Extra params + const extraParamsLength = ExtraParams.getLengthOfParams(buf, pos); + o.extraParams = ExtraParams.from(buf.slice(pos, pos + extraParamsLength)); + pos = pos + extraParamsLength; + + if (compressedflags & CompressedFlags.HasSound) + { + pos = pos + 25 + } + if (compressedflags & CompressedFlags.HasNameValues) + { + const result = Utils.BufferToString(buf, pos); + o.NameValue = this.parseNameValues(result.result); + pos += result.readLength; + } + pos++; + pos = pos + 22; + const textureEntryLength = buf.readUInt32LE(pos); + pos = pos + 4; + pos = pos + textureEntryLength; + if (compressedflags & CompressedFlags.TextureAnimation) + { + pos = pos + 4; + } + + o.IsAttachment = (compressedflags & CompressedFlags.HasNameValues) !== 0 && o.ParentID !== 0; + if (o.IsAttachment && o.State !== undefined) + { + o.attachmentPoint = this.decodeAttachPoint(o.State); + } + + this.notifyObjectUpdate(newObj, o); + } + } + + protected decodeAttachPoint(state: number): number + { + const mask = 0xf << 4 >>> 0; + return (((state & mask) >>> 4) | ((state & ~mask) << 4)) >>> 0; + } + + protected objectUpdateTerse(_objectUpdateTerse: ImprovedTerseObjectUpdateMessage): void + { + + } + + protected killObject(killObj: KillObjectMessage): void + { + for (const obj of killObj.ObjectData) + { + const objectID = obj.ID; + if (this.objects[objectID]) + { + this.deleteObject(objectID); + } + } + } + + setPersist(persist: boolean): void + { + this.persist = persist; + if (!this.persist) + { + for (const d of this.deadObjects) + { + this.deleteObject(d); + } + this.deadObjects = []; + } + } + + deleteObject(objectID: number): void + { + if (this.objects[objectID]) + { + const objectUUID = this.objects[objectID].FullID; + const obj = this.objects[objectID]; + obj.deleted = true; + + if (this.persist) + { + this.deadObjects.push(objectID); + return; + } + + if (obj.IsAttachment && obj.ParentID !== undefined) + { + if (this.objects[obj.ParentID] !== undefined && this.objects[obj.ParentID].PCode === PCode.Avatar) + { + this.agent.currentRegion.agents[this.objects[obj.ParentID].FullID.toString()]?.removeAttachment(obj); + } + } + + if (this.agent.currentRegion.agents[objectUUID.toString()] !== undefined) + { + this.agent.currentRegion.agents[objectUUID.toString()].isVisible = false; + } + + // First, kill all children (not the people kind) + if (this.objectsByParent[objectID]) + { + for (const childObjID of this.objectsByParent[objectID]) + { + this.deleteObject(childObjID); + } + } + delete this.objectsByParent[objectID]; + + // Now delete this object + const uuid = obj.FullID.toString(); + + if (this.objectsByUUID[uuid]) + { + delete this.objectsByUUID[uuid]; + } + if (obj.ParentID !== undefined) + { + const parentID = obj.ParentID; + if (this.objectsByParent[parentID]) + { + const ind = this.objectsByParent[parentID].indexOf(objectID); + if (ind !== -1) + { + this.objectsByParent[parentID].splice(ind, 1); + } + } + } + if (this.rtree && obj.rtreeEntry !== undefined) + { + this.rtree.remove(obj.rtreeEntry); + } + delete this.objects[objectID]; + } + } + getObjectsByParent(parentID: number): GameObject[] + { + const list = this.objectsByParent[parentID]; + if (list === undefined) + { + return []; + } + const result: GameObject[] = []; + for (const localID of list) + { + if (this.objects[localID]) + { + result.push(this.objects[localID]); + } + } + result.sort((a: GameObject, b: GameObject) => + { + return a.ID - b.ID; + }); + return result; + } + + parseNameValues(str: string): { [key: string]: NameValue } + { + const nv: { [key: string]: NameValue } = {}; + const lines = str.split('\n'); + for (const line of lines) + { + if (line.length > 0) + { + let kv = line.split(/[\t ]/); + if (kv.length > 5) + { + for (let x = 5; x < kv.length; x++) + { + kv[4] += ' ' + kv[x]; + } + kv = kv.slice(0, 5); + } + if (kv.length === 5) + { + const namevalue = new NameValue(); + namevalue.type = kv[1]; + namevalue.class = kv[2]; + namevalue.sendTo = kv[3]; + namevalue.value = kv[4]; + nv[kv[0]] = namevalue; + } + } + } + return nv; + } + + shutdown(): void + { + if (this.selectedChecker !== undefined) + { + clearInterval(this.selectedChecker); + delete this.selectedChecker; + } + this.physicsSubscription.unsubscribe(); + this.objects = {}; + if (this.rtree) + { + this.rtree.clear(); + } + this.objectsByUUID = {}; + this.objectsByParent = {}; + delete this.circuit; + } + + protected findParent(go: GameObject): GameObject + { + if (go.ParentID !== undefined && go.ParentID !== 0 && this.objects[go.ParentID]) + { + return this.findParent(this.objects[go.ParentID]); + } + else + { + if (go.ParentID !== undefined && go.ParentID !== 0 && !this.objects[go.ParentID]) + { + this.requestMissingObject(go.ParentID).catch((e: unknown) => + { + Logger.Error(e); + }); + } + return go; + } + } + + populateChildren(obj: GameObject, _resolve = false): void + { + if (obj !== undefined) + { + obj.children = []; + obj.totalChildren = 0; + for (const child of this.getObjectsByParent(obj.ID)) + { + if (child.PCode !== PCode.Avatar) + { + obj.totalChildren++; + this.populateChildren(child); + if (child.totalChildren !== undefined) + { + obj.totalChildren += child.totalChildren; + } + obj.children.push(child); + } + } + } + } + + async getAllObjects(): Promise + { + const results = []; + const found: { [key: string]: GameObject } = {}; + for (const k of Object.keys(this.objects)) + { + const go = this.objects[parseInt(k, 10)]; + if (go.PCode !== PCode.Avatar && (go.IsAttachment === undefined || !go.IsAttachment)) + { + try + { + const parent = this.findParent(go); + if (parent.ParentID === 0) + { + const uuid = parent.FullID.toString(); + + if (found[uuid] === undefined) + { + found[uuid] = parent; + results.push(parent); + } + } + } + catch (error) + { + console.log('Failed to find parent for ' + go.FullID.toString()); + console.error(error); + // Unable to find parent, full object probably not fully loaded yet + } + } + } + + // Now populate children of each found object + for (const obj of results) + { + this.populateChildren(obj); + } + + return results; + } + + + getNumberOfObjects(): number + { + return Object.keys(this.objects).length; + } + + async getObjectsInArea(minX: number, maxX: number, minY: number, maxY: number, minZ: number, maxZ: number): Promise + { + if (!this.rtree) + { + throw new Error('GetObjectsInArea not available with the Lite object store'); + } + const result = this.rtree.search({ + minX: minX, + maxX: maxX, + minY: minY, + maxY: maxY, + minZ: minZ, + maxZ: maxZ + }); + const found: { [key: string]: GameObject } = {}; + const objs: GameObject[] = []; + for (const obj of result) + { + const o = obj as ITreeBoundingBox; + const go = o.gameObject as GameObject; + if (go.PCode !== PCode.Avatar && (go.IsAttachment === undefined || !go.IsAttachment)) + { + try + { + const parent = this.findParent(go); + if (parent.PCode !== PCode.Avatar && (parent.IsAttachment === undefined || !parent.IsAttachment) && parent.ParentID === 0) + { + const uuid = parent.FullID.toString(); + + if (found[uuid] === undefined) + { + found[uuid] = parent; + objs.push(parent); + } + } + } + catch (error) + { + console.log('Failed to find parent for ' + go.FullID.toString()); + console.error(error); + // Unable to find parent, full object probably not fully loaded yet + } + } + } + + // Now populate children of each found object + for (const obj of objs) + { + this.populateChildren(obj); + } + + return objs; + } + + getObjectByUUID(fullID: UUID | string): GameObject + { + if (fullID instanceof UUID) + { + fullID = fullID.toString(); + } + if (!this.objectsByUUID[fullID]) + { + throw new Error('No object found with that UUID'); + } + const localID: number = this.objectsByUUID[fullID]; + return this.objects[localID]; + } + + getObjectByLocalID(localID: number): GameObject + { + if (!this.objects[localID]) + { + throw new Error('No object found with that UUID'); + } + return this.objects[localID]; + } + + insertIntoRtree(obj: GameObject): void + { + if (!this.rtree) + { + return; + } + if (obj.rtreeEntry !== undefined) + { + this.rtree.remove(obj.rtreeEntry); + } + if (!obj.Scale || !obj.Position || !obj.Rotation) + { + return; + } + const normalizedScale = new Vector3(obj.Scale).multiplyByTSMQuat(new Quaternion(obj.Rotation)); + + const bounds: ITreeBoundingBox = { + minX: obj.Position.x - (normalizedScale.x / 2), + maxX: obj.Position.x + (normalizedScale.x / 2), + minY: obj.Position.y - (normalizedScale.y / 2), + maxY: obj.Position.y + (normalizedScale.y / 2), + minZ: obj.Position.z - (normalizedScale.z / 2), + maxZ: obj.Position.z + (normalizedScale.z / 2), + gameObject: obj + }; + + obj.rtreeEntry = bounds; + this.rtree.insert(bounds); + } +} diff --git a/lib/classes/Packet.ts b/lib/classes/Packet.ts index d2e4008..2d3fb67 100644 --- a/lib/classes/Packet.ts +++ b/lib/classes/Packet.ts @@ -8,7 +8,7 @@ import { DecodeFlags } from '../enums/DecodeFlags'; export class Packet { - packetFlags: PacketFlags = 0; + packetFlags: PacketFlags = 0 as PacketFlags; sequenceNumber = 0; extraHeader: Buffer = Buffer.allocUnsafe(0); message: MessageBase; @@ -31,9 +31,9 @@ export class Packet { if (options === undefined) { - options = 0; + options = 0 as DecodeFlags; } - if (this.message.messageFlags & MessageFlags.Zerocoded && !(options & 1)) + if (this.message.messageFlags & MessageFlags.Zerocoded && !((options ?? 0) & 1)) { this.packetFlags = this.packetFlags | PacketFlags.Zerocoded; } diff --git a/lib/classes/public/GameObject.ts b/lib/classes/public/GameObject.ts index f87a06b..af17f49 100644 --- a/lib/classes/public/GameObject.ts +++ b/lib/classes/public/GameObject.ts @@ -838,7 +838,7 @@ export class GameObject implements IGameObjectData return Utils.waitOrTimeOut(this.onTextureUpdate, timeout); } - async rezScript(name: string, description: string, perms: PermissionMask = 532480): Promise + async rezScript(name: string, description: string, perms: PermissionMask = 532480 as PermissionMask): Promise { const rezScriptMsg = new RezScriptMessage(); rezScriptMsg.AgentData = { diff --git a/package-lock.json b/package-lock.json index 1984c4c..87d00b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@caspertech/node-metaverse", - "version": "0.5.55", + "version": "0.5.56", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@caspertech/node-metaverse", - "version": "0.5.55", + "version": "0.5.56", "license": "MIT", "dependencies": { "@caspertech/llsd": "^1.0.5", @@ -51,6 +51,7 @@ "mocha": "^9.2.2", "source-map-support": "^0.5.21", "ts-node": "^10.9.1", + "tslint": "^6.1.3", "typescript": "^4.9.5" }, "engines": { @@ -1149,6 +1150,15 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1295,6 +1305,12 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1876,6 +1892,15 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", @@ -2008,6 +2033,18 @@ "node": ">=8" } }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -2104,6 +2141,18 @@ "node": ">=8" } }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2432,6 +2481,27 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/mocha": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", @@ -2739,6 +2809,12 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -2960,6 +3036,23 @@ "node": ">=0.10.0" } }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3255,6 +3348,18 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/table": { "version": "6.8.1", "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", @@ -3420,6 +3525,144 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/tslint": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.13.0", + "tsutils": "^2.29.0" + }, + "bin": { + "tslint": "bin/tslint" + }, + "engines": { + "node": ">=4.8.0" + }, + "peerDependencies": { + "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" + } + }, + "node_modules/tslint/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslint/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslint/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/tslint/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/tslint/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/tslint/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/tslint/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslint/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/tslint/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslint/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tslint/node_modules/tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "peerDependencies": { + "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" + } + }, "node_modules/tsutils": { "version": "3.21.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", diff --git a/package.json b/package.json index 1241972..0cc1cb3 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "mocha": "^9.2.2", "source-map-support": "^0.5.21", "ts-node": "^10.9.1", + "tslint": "^6.1.3", "typescript": "^4.9.5" }, "dependencies": {