diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index ad1ddaf..0000000 --- a/.eslintrc +++ /dev/null @@ -1,224 +0,0 @@ -{ - "env": { - "browser": true, - "es6": true, - "node": true - }, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": "tsconfig.json", - "sourceType": "module" - }, - "plugins": [ - "@angular-eslint/eslint-plugin", - "@typescript-eslint", - "@typescript-eslint/tslint" - ], - "rules": { - "brace-style": "off", - "@typescript-eslint/brace-style": [ - "error", - "allman" - ], - "@typescript-eslint/object-curly-spacing": [ - "error", - "always" - ], - "@typescript-eslint/space-before-function-paren": [ - "error", - "never" - ], - "@typescript-eslint/naming-convention": [ - "error", - { - "selector": "enumMember", - "format": null - } - ], - "@typescript-eslint/keyword-spacing": [ - "error", - { - "before": true, - "after": true - } - ], - "@angular-eslint/component-class-suffix": "error", - "@angular-eslint/component-selector": [ - "error", - { - "type": "element", - "prefix": "app", - "style": "kebab-case" - } - ], - "@angular-eslint/directive-class-suffix": "error", - "@angular-eslint/directive-selector": [ - "error", - { - "type": "attribute", - "prefix": "app", - "style": "camelCase" - } - ], - "@angular-eslint/no-input-rename": "error", - "@angular-eslint/no-output-rename": "error", - "@angular-eslint/use-pipe-transform-interface": "error", - "@typescript-eslint/consistent-type-definitions": "error", - "@typescript-eslint/dot-notation": "off", - "@typescript-eslint/explicit-member-accessibility": [ - "off", - { - "accessibility": "explicit" - } - ], - "@typescript-eslint/member-delimiter-style": [ - "off", - { - "multiline": { - "delimiter": "none", - "requireLast": true - }, - "singleline": { - "delimiter": "semi", - "requireLast": false - } - } - ], - "@typescript-eslint/member-ordering": "off", - "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/no-empty-interface": "error", - "@typescript-eslint/no-inferrable-types": [ - "error", - { - "ignoreParameters": true - } - ], - "@typescript-eslint/no-shadow": [ - "error", - { - "hoist": "all" - } - ], - "@typescript-eslint/no-unused-expressions": "error", - "@typescript-eslint/no-use-before-define": "error", - "@typescript-eslint/prefer-function-type": "error", - "@typescript-eslint/quotes": [ - "error", - "single" - ], - "@typescript-eslint/semi": [ - "off", - null - ], - "@typescript-eslint/type-annotation-spacing": "error", - "@typescript-eslint/unified-signatures": "error", - "curly": "error", - "dot-notation": "off", - "eol-last": "error", - "eqeqeq": [ - "error", - "smart" - ], - "guard-for-in": "error", - "id-denylist": "off", - "id-match": "off", - "indent": "off", - "max-len": [ - "off", - { - "code": 250 - } - ], - "no-bitwise": "off", - "no-caller": "error", - "no-console": [ - "error", - { - "allow": [ - "log", - "warn", - "dir", - "timeLog", - "assert", - "clear", - "count", - "countReset", - "group", - "groupEnd", - "table", - "dirxml", - "error", - "groupCollapsed", - "Console", - "profile", - "profileEnd", - "timeStamp", - "context" - ] - } - ], - "no-debugger": "error", - "no-empty": "off", - "no-empty-function": "off", - "no-eval": "error", - "no-fallthrough": "error", - "no-new-wrappers": "error", - "no-redeclare": "error", - "no-shadow": "off", - "no-throw-literal": "error", - "no-trailing-spaces": "error", - "no-underscore-dangle": "off", - "no-unused-expressions": "error", - "no-unused-labels": "error", - "no-use-before-define": "error", - "no-var": "error", - "prefer-const": "error", - "quotes": ["off"], - "radix": "error", - "semi": "off", - "spaced-comment": [ - "error", - "always", - { - "markers": [ - "/" - ] - } - ], - "valid-typeof": "error", - "@typescript-eslint/tslint/config": [ - "error", - { - "rules": { - "brace-style": [ - true, - "allman", - { - "allowSingleLine": true - } - ], - "import-spacing": true, - "invoke-injectable": true, - "no-access-missing-member": true, - "templates-use-public": true, - "typedef": [ - true, - "call-signature" - ], - "use-host-property-decorator": true, - "use-input-property-decorator": true, - "use-life-cycle-interface": true, - "use-output-property-decorator": true, - "whitespace": [ - true, - "check-branch", - "check-decl", - "check-operator", - "check-separator", - "check-type" - ] - } - } - ] - } -} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..c49728a --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,104 @@ +// noinspection JSUnusedGlobalSymbols + +import typescriptEslint from '@typescript-eslint/eslint-plugin'; +import tsParser from '@typescript-eslint/parser'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import js from '@eslint/js'; +import { FlatCompat } from '@eslint/eslintrc'; + + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all +}); + +export default [ + { + ignores: ["**/*.spec.ts", "**/*.d.ts", "dist/**", "vitest.config.ts", "lib/classes/messages/*.ts", "examples/**/*.ts"], + }, + ...compat.extends("eslint:recommended", "plugin:@typescript-eslint/all").map(config => ({ + ...config, + files: ["**/*.ts"], + })), + { + files: ["**/*.ts"], + + plugins: { + "@typescript-eslint": typescriptEslint, + }, + + languageOptions: { + parser: tsParser, + ecmaVersion: 5, + sourceType: "script", + + parserOptions: { + project: "./tsconfig.json", + }, + }, + + rules: { + "@typescript-eslint/prefer-destructuring": "off", + "@typescript-eslint/strict-boolean-expressions": "off", + "@typescript-eslint/no-unnecessary-condition": "off", + "@typescript-eslint/no-magic-numbers": "off", + "@typescript-eslint/unified-signatures": "off", + "@typescript-eslint/no-unsafe-call": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-argument": "off", + "@typescript-eslint/no-unsafe-type-assertion": "off", + "@typescript-eslint/no-extraneous-class": "off", + "@typescript-eslint/class-methods-use-this": "off", + "@typescript-eslint/prefer-readonly-parameter-types": "off", + "@typescript-eslint/max-params": "off", + "@typescript-eslint/prefer-literal-enum-member": "off", + "@typescript-eslint/prefer-enum-initializers": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/switch-exhaustiveness-check": [ + 'error', + { + "allowDefaultCaseForExhaustiveSwitch": true, + "considerDefaultExhaustiveForUnions": true + } + ], + "@typescript-eslint/parameter-properties": [ + 'error', + { + 'allow': ['public readonly', 'private readonly'] + }, + ], + "@typescript-eslint/member-ordering": [ + "error", + { + "default": [ + "public-static-field", + "public-instance-field", + "private-static-field", + "private-instance-field", + "constructor", + "public-static-method", + "public-instance-method", + "private-static-method", + "private-instance-method" + ] + } + ], + "@typescript-eslint/no-unused-vars": ["error", { + caughtErrorsIgnorePattern: "^_", + argsIgnorePattern: "^_", + varsIgnorePattern: "^_" + }], + + "@typescript-eslint/naming-convention": ["error", { + selector: "enumMember", + format: null, + }], + }, + }, +]; diff --git a/examples/Inventory/Inventory.ts b/examples/Inventory/Inventory.ts index 0578a96..7de2026 100644 --- a/examples/Inventory/Inventory.ts +++ b/examples/Inventory/Inventory.ts @@ -33,7 +33,8 @@ class Inventory extends ExampleBot if (childFolder.name === exampleFolderName) { exampleFolder = childFolder; - await exampleFolder.populate(false); + // Pass "false" to skip inventory cache + await exampleFolder.populate(); break; } } @@ -128,7 +129,8 @@ class Inventory extends ExampleBot async iterateFolder(folder: InventoryFolder, prefix: string): Promise { console.log(prefix + ' [' + folder.name + ']'); - await folder.populate(false); + // Set to false to skip cache + await folder.populate(); for (const subFolder of folder.folders) { diff --git a/examples/Money/Money.ts b/examples/Money/Money.ts index 0f61ba0..6b77185 100644 --- a/examples/Money/Money.ts +++ b/examples/Money/Money.ts @@ -1,13 +1,13 @@ import { ExampleBot } from '../ExampleBot'; -import { BalanceUpdatedEvent } from '../../lib/events/BalanceUpdatedEvent'; -import { AvatarQueryResult } from '../../lib/classes/public/AvatarQueryResult'; +import type { BalanceUpdatedEvent } from '../../lib/events/BalanceUpdatedEvent'; +import type { AvatarQueryResult } from '../../lib/classes/public/AvatarQueryResult'; import { MoneyTransactionType } from '../../lib/enums/MoneyTransactionType'; class Money extends ExampleBot { private balance = 0; - async onConnected(): Promise + public async onConnected(): Promise { this.bot.clientEvents.onBalanceUpdated.subscribe(this.onBalanceUpdated.bind(this)); try @@ -17,13 +17,13 @@ class Money extends ExampleBot await this.bot.clientCommands.grid.payAvatar('d1cd5b71-6209-4595-9bf0-771bf689ce00', 1, 'This is a gift for being so awesome!'); console.log('Payment success'); } - catch (error) + catch (_error: unknown) { console.log('Payment failed'); } } - async onBalanceUpdated(evt: BalanceUpdatedEvent): Promise + public async onBalanceUpdated(evt: BalanceUpdatedEvent): Promise { this.balance = evt.balance; if (evt.transaction.from.equals(this.bot.agentID())) @@ -57,7 +57,7 @@ class Money extends ExampleBot new Money().run().then(() => { -}).catch((err) => +}).catch((err: unknown) => { console.error(err); }); diff --git a/examples/Objects/TaskInventory.ts b/examples/Objects/TaskInventory.ts index 9149adf..c1ddc09 100644 --- a/examples/Objects/TaskInventory.ts +++ b/examples/Objects/TaskInventory.ts @@ -1,9 +1,10 @@ import { ExampleBot } from '../ExampleBot'; -import { GameObject, Utils } from '../../lib'; +import type { GameObject} from '../../lib'; +import { Utils } from '../../lib'; class TaskInventory extends ExampleBot { - async onConnected(): Promise + public async onConnected(): Promise { let attachments: GameObject[] = []; while(!attachments.length) @@ -29,7 +30,7 @@ class TaskInventory extends ExampleBot new TaskInventory().run().then(() => { -}).catch((err) => +}).catch((err: unknown) => { console.error(err); }); diff --git a/examples/Region/Settings.ts b/examples/Region/Settings.ts new file mode 100644 index 0000000..8db95ef --- /dev/null +++ b/examples/Region/Settings.ts @@ -0,0 +1,19 @@ +import { ExampleBot } from '../ExampleBot'; + +class Region extends ExampleBot +{ + async onConnected(): Promise + { + console.log('Exporting:'); + console.log(this.bot.currentRegion.exportXML()); + console.log('done'); + } +} + +new Region().run().then(() => +{ + +}).catch((err) => +{ + console.error(err); +}); diff --git a/lib/Bot.ts b/lib/Bot.ts index b61e198..01e310a 100644 --- a/lib/Bot.ts +++ b/lib/Bot.ts @@ -1,58 +1,61 @@ import { LoginHandler } from './LoginHandler'; -import { LoginResponse } from './classes/LoginResponse'; -import { LoginParameters } from './classes/LoginParameters'; -import { Agent } from './classes/Agent'; +import type { LoginResponse } from './classes/LoginResponse'; +import type { LoginParameters } from './classes/LoginParameters'; +import type { Agent } from './classes/Agent'; import { PacketFlags } from './enums/PacketFlags'; import { UseCircuitCodeMessage } from './classes/messages/UseCircuitCode'; import { CompleteAgentMovementMessage } from './classes/messages/CompleteAgentMovement'; import { Message } from './enums/Message'; -import { Packet } from './classes/Packet'; -import { Region } from './classes/Region'; +import type { Packet } from './classes/Packet'; +import type { Region } from './classes/Region'; import { LogoutRequestMessage } from './classes/messages/LogoutRequest'; import { Utils } from './classes/Utils'; import { RegionHandshakeReplyMessage } from './classes/messages/RegionHandshakeReply'; import { RegionProtocolFlags } from './enums/RegionProtocolFlags'; import { AgentDataUpdateRequestMessage } from './classes/messages/AgentDataUpdateRequest'; -import { TeleportProgressMessage } from './classes/messages/TeleportProgress'; +import type { TeleportProgressMessage } from './classes/messages/TeleportProgress'; import { TeleportEvent } from './events/TeleportEvent'; import { ClientEvents } from './classes/ClientEvents'; import { TeleportEventType } from './enums/TeleportEventType'; import { ClientCommands } from './classes/ClientCommands'; import { DisconnectEvent } from './events/DisconnectEvent'; -import { KickUserMessage } from './classes/messages/KickUser'; +import type { KickUserMessage } from './classes/messages/KickUser'; import { StartPingCheckMessage } from './classes/messages/StartPingCheck'; -import { CompletePingCheckMessage } from './classes/messages/CompletePingCheck'; -import { BotOptionFlags } from './enums/BotOptionFlags'; +import type { CompletePingCheckMessage } from './classes/messages/CompletePingCheck'; +import type { BotOptionFlags } from './enums/BotOptionFlags'; import { FilterResponse } from './enums/FilterResponse'; -import { LogoutReplyMessage } from './classes/messages/LogoutReply'; -import { EventQueueStateChangeEvent } from './events/EventQueueStateChangeEvent'; +import type { LogoutReplyMessage } from './classes/messages/LogoutReply'; +import type { EventQueueStateChangeEvent } from './events/EventQueueStateChangeEvent'; import { UUID } from './classes/UUID'; import { Vector3 } from './classes/Vector3'; -import { RegionHandshakeMessage } from './classes/messages/RegionHandshake'; -import { AgentMovementCompleteMessage } from './classes/messages/AgentMovementComplete'; -import { Subscription } from 'rxjs'; -import Timer = NodeJS.Timer; +import type { RegionHandshakeMessage } from './classes/messages/RegionHandshake'; +import type { AgentMovementCompleteMessage } from './classes/messages/AgentMovementComplete'; +import type { Subscription } from 'rxjs'; export class Bot { - private loginParams: LoginParameters; + public clientEvents: ClientEvents; + private stayRegion = ''; + private stayPosition = new Vector3(); + + private readonly loginParams: LoginParameters; private ping: NodeJS.Timeout | null = null; private pingNumber = 0; private lastSuccessfulPing = 0; private circuitSubscription: Subscription | null = null; - private options: BotOptionFlags; + private readonly options: BotOptionFlags; private eventQueueRunning = false; - private eventQueueWaits: any = {}; + private readonly eventQueueWaits = new Map)) => void + }>(); private stay = false; - public clientEvents: ClientEvents; - private stayRegion = ''; - private stayPosition = new Vector3(); private _agent?: Agent; private _currentRegion?: Region; private _clientCommands?: ClientCommands; - get currentRegion(): Region + public get currentRegion(): Region { if (this._currentRegion === undefined) { @@ -61,7 +64,7 @@ export class Bot return this._currentRegion; } - get agent(): Agent + public get agent(): Agent { if (this._agent === undefined) { @@ -70,7 +73,7 @@ export class Bot return this._agent; } - get clientCommands(): ClientCommands + public get clientCommands(): ClientCommands { if (this._clientCommands === undefined) { @@ -79,12 +82,12 @@ export class Bot return this._clientCommands; } - get loginParameters(): LoginParameters + public get loginParameters(): LoginParameters { return this.loginParams; } - constructor(login: LoginParameters, options: BotOptionFlags) + public constructor(login: LoginParameters, options: BotOptionFlags) { this.clientEvents = new ClientEvents(); this.loginParams = login; @@ -93,23 +96,27 @@ export class Bot this.clientEvents.onEventQueueStateChange.subscribe((evt: EventQueueStateChangeEvent) => { this.eventQueueRunning = evt.active; - for (const waitID of Object.keys(this.eventQueueWaits)) + for (const waitID of this.eventQueueWaits.keys()) { try { - clearTimeout(this.eventQueueWaits[waitID].timer); - this.eventQueueWaits[waitID].resolve(); - delete this.eventQueueWaits[waitID]; + const wait = this.eventQueueWaits.get(waitID); + if (wait !== undefined) + { + clearTimeout(wait.timer); + wait.resolve(); + this.eventQueueWaits.delete(waitID); + } } - catch (ignore) + catch (_ignore: unknown) { - + //Nothing } } }); } - stayPut(stay: boolean, regionName?: string, position?: Vector3): void + public stayPut(stay: boolean, regionName?: string, position?: Vector3): void { this.stay = stay; if (regionName !== undefined) @@ -122,12 +129,12 @@ export class Bot } } - getCurrentRegion(): Region + public getCurrentRegion(): Region { return this.currentRegion; } - async login(): Promise + public async login(): Promise { const loginHandler = new LoginHandler(this.clientEvents, this.options); const response: LoginResponse = await loginHandler.Login(this.loginParams); @@ -138,7 +145,7 @@ export class Bot return response; } - async changeRegion(region: Region, requested: boolean): Promise + public async changeRegion(region: Region, requested: boolean): Promise { this.closeCircuit(); this._currentRegion = region; @@ -153,7 +160,7 @@ export class Bot await this.connectToSim(requested); } - waitForEventQueue(timeout: number = 1000): Promise + public async waitForEventQueue(timeout = 1000): Promise { return new Promise((resolve, reject) => { @@ -165,19 +172,19 @@ export class Bot { const waitID = UUID.random().toString(); const newWait: { - 'resolve': any, - 'timer'?: Timer + resolve: (value: (void | PromiseLike)) => void, + timer?: NodeJS.Timeout } = { 'resolve': resolve }; newWait.timer = setTimeout(() => { - delete this.eventQueueWaits[waitID]; + this.eventQueueWaits.delete(waitID); reject(new Error('Timeout')); }, timeout); - this.eventQueueWaits[waitID] = newWait; + this.eventQueueWaits.set(waitID, newWait); } }); } @@ -206,46 +213,7 @@ export class Bot } } - private closeCircuit(): void - { - this.currentRegion.shutdown(); - if (this.circuitSubscription !== null) - { - this.circuitSubscription.unsubscribe(); - this.circuitSubscription = null; - } - delete this._currentRegion; - - this.clientCommands.shutdown(); - delete this._clientCommands; - if (this.ping !== null) - { - clearInterval(this.ping); - this.ping = null; - } - - } - - private kicked(message: string): void - { - this.closeCircuit(); - this.agent.shutdown(); - delete this._agent; - this.disconnected(false, message); - } - - private disconnected(requested: boolean, message: string): void - { - const disconnectEvent = new DisconnectEvent(); - disconnectEvent.requested = requested; - disconnectEvent.message = message; - if (this.clientEvents) - { - this.clientEvents.onDisconnected.next(disconnectEvent); - } - } - - async close(): Promise + public async close(): Promise { const circuit = this.currentRegion.circuit; const msg: LogoutRequestMessage = new LogoutRequestMessage(); @@ -263,12 +231,12 @@ export class Bot this.disconnected(true, 'Logout completed'); } - agentID(): UUID + public agentID(): UUID { return this.agent.agentID; } - async connectToSim(requested: boolean = false): Promise + public async connectToSim(requested = false): Promise { if (!requested) { @@ -340,7 +308,7 @@ export class Bot this.stayPut(this.stay, regionName, agentPosition); } } - }).catch((error) => + }).catch((error: unknown) => { console.error('Timed out getting handshake'); console.error(error); @@ -348,7 +316,7 @@ export class Bot if (this._clientCommands) { - this._clientCommands.network.setBandwidth(1536000); + await this._clientCommands.network.setBandwidth(1536000); } const agentRequest = new AgentDataUpdateRequestMessage(); @@ -357,88 +325,84 @@ export class Bot SessionID: circuit.sessionID }; circuit.sendMessage(agentRequest, PacketFlags.Reliable); - this.agent.setInitialAppearance(); + await this.agent.setInitialAppearance(); this.agent.circuitActive(); this.lastSuccessfulPing = new Date().getTime(); - this.ping = setInterval(async() => - { - const now = new Date().getTime(); - if (now - this.lastSuccessfulPing > 120 * 1000) + this.ping = setInterval(() => { + (async(): Promise => { - if (this.ping !== null) + const now = new Date().getTime(); + if (now - this.lastSuccessfulPing > 120 * 1000) { - clearInterval(this.ping); - this.ping = null; - this.disconnected(false, 'Disconnected from the simulator'); - } - return; - } - - this.pingNumber++; - if (this.pingNumber % 12 === 0 && this.stay) - { - if (this.currentRegion.regionName.toLowerCase() !== this.stayRegion.toLowerCase()) - { - console.log('Stay Put: Attempting to teleport to ' + this.stayRegion); - if (this.stayPosition === undefined) + if (this.ping !== null) { - this.stayPosition = new Vector3([128, 128, 20]); + clearInterval(this.ping); + this.ping = null; + this.disconnected(false, 'Disconnected from the simulator'); } - this.clientCommands.teleport.teleportTo(this.stayRegion, this.stayPosition, this.stayPosition).then(() => - { - console.log('I found my way home.'); - }).catch(() => - { - console.log('Cannot teleport home right now.'); - }); + return; } - } - if (this.pingNumber > 255) - { - this.pingNumber = 0; - } - const ping = new StartPingCheckMessage(); - ping.PingID = { - PingID: this.pingNumber, - OldestUnacked: this.currentRegion.circuit.getOldestUnacked() - }; - circuit.sendMessage(ping, PacketFlags.Reliable); - circuit.waitForMessage(Message.CompletePingCheck, 10000, ((pingData: { - pingID: number, - timeSent: number - }, cpc: CompletePingCheckMessage): FilterResponse => - { - if (cpc.PingID.PingID === pingData.pingID) + this.pingNumber++; + if (this.pingNumber % 12 === 0 && this.stay) { - this.lastSuccessfulPing = new Date().getTime(); - const pingTime = this.lastSuccessfulPing - pingData.timeSent; - if (this.clientEvents !== null) + if (this.currentRegion.regionName.toLowerCase() !== this.stayRegion.toLowerCase()) { - this.clientEvents.onCircuitLatency.next(pingTime); + console.log('Stay Put: Attempting to teleport to ' + this.stayRegion); + if (this.stayPosition === undefined) + { + this.stayPosition = new Vector3([128, 128, 20]); + } + this.clientCommands.teleport.teleportTo(this.stayRegion, this.stayPosition, this.stayPosition).then(() => + { + console.log('I found my way home.'); + }).catch(() => + { + console.log('Cannot teleport home right now.'); + }); } - return FilterResponse.Finish; } - return FilterResponse.NoMatch; - }).bind(this, { - pingID: this.pingNumber, - timeSent: new Date().getTime() - })).then(() => - { - // No action needed - }).catch(() => - { - }); + if (this.pingNumber > 255) + { + this.pingNumber = 0; + } + const ping = new StartPingCheckMessage(); + ping.PingID = { + PingID: this.pingNumber, + OldestUnacked: this.currentRegion.circuit.getOldestUnacked() + }; + circuit.sendMessage(ping, PacketFlags.Reliable); + await circuit.waitForMessage(Message.CompletePingCheck, 10000, ((pingData: { + pingID: number, + timeSent: number + }, cpc: CompletePingCheckMessage): FilterResponse => + { + if (cpc.PingID.PingID === pingData.pingID) + { + this.lastSuccessfulPing = new Date().getTime(); + const pingTime = this.lastSuccessfulPing - pingData.timeSent; + if (this.clientEvents !== null) + { + this.clientEvents.onCircuitLatency.next(pingTime); + } + return FilterResponse.Finish; + } + return FilterResponse.NoMatch; + }).bind(this, { + pingID: this.pingNumber, + timeSent: new Date().getTime() + })); - if ((new Date().getTime() - this.lastSuccessfulPing) > 60000) - { - // We're dead, jim - this.kicked('Circuit Timeout'); - } + if ((new Date().getTime() - this.lastSuccessfulPing) > 60000) + { + // We're dead, jim + this.kicked('Circuit Timeout'); + } + })().catch((_e: unknown) => { /*ignore*/ }) }, 5000); this.circuitSubscription = circuit.subscribeToMessages( @@ -515,7 +479,48 @@ export class Bot break; } + default: + break; } }); } + + private closeCircuit(): void + { + this.currentRegion.shutdown(); + if (this.circuitSubscription !== null) + { + this.circuitSubscription.unsubscribe(); + this.circuitSubscription = null; + } + delete this._currentRegion; + + this.clientCommands.shutdown(); + delete this._clientCommands; + if (this.ping !== null) + { + clearInterval(this.ping); + this.ping = null; + } + + } + + private kicked(message: string): void + { + this.closeCircuit(); + this.agent.shutdown(); + delete this._agent; + this.disconnected(false, message); + } + + private disconnected(requested: boolean, message: string): void + { + const disconnectEvent = new DisconnectEvent(); + disconnectEvent.requested = requested; + disconnectEvent.message = message; + if (this.clientEvents) + { + this.clientEvents.onDisconnected.next(disconnectEvent); + } + } } diff --git a/lib/LoginHandler.ts b/lib/LoginHandler.ts index 4e57c28..32e3d07 100644 --- a/lib/LoginHandler.ts +++ b/lib/LoginHandler.ts @@ -3,16 +3,17 @@ import * as xmlrpc from 'xmlrpc'; import * as fs from 'fs'; import * as path from 'path'; import { LoginError } from './classes/LoginError'; -import { LoginParameters } from './classes/LoginParameters'; +import type { LoginParameters } from './classes/LoginParameters'; import { LoginResponse } from './classes/LoginResponse'; -import { ClientEvents } from './classes/ClientEvents'; +import type { ClientEvents } from './classes/ClientEvents'; import { Utils } from './classes/Utils'; import { UUID } from './classes/UUID'; -import { BotOptionFlags } from './enums/BotOptionFlags'; +import type { BotOptionFlags } from './enums/BotOptionFlags'; import { URL } from 'url'; import * as os from 'os'; const packageJsonPath = path.join(__dirname, '..', '..', 'package.json'); +// eslint-disable-next-line @typescript-eslint/no-require-imports const packageJson = require(packageJsonPath); const version = packageJson.version; @@ -22,7 +23,7 @@ export class LoginHandler private readonly clientEvents: ClientEvents; private readonly options: BotOptionFlags; - constructor(ce: ClientEvents, options: BotOptionFlags) + public constructor(ce: ClientEvents, options: BotOptionFlags) { this.clientEvents = ce; this.options = options; @@ -71,7 +72,7 @@ export class LoginHandler const data = JSON.parse(hwID.toString('utf-8')); hardwareID = data.id0; } - catch (e: unknown) + catch (_e: unknown) { // Ignore any error } @@ -105,6 +106,8 @@ export class LoginHandler case 'win32': platform = 'win'; break; + default: + throw new Error('Unsupported platform'); } const versions = version.split('.'); @@ -151,15 +154,16 @@ export class LoginHandler 'global-textures' ] } - ], (error: Object, value: any) => + ], (error: object, value: any) => { if (error) { + // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors reject(error); } else { - if (!value['login'] || value['login'] === 'false') + if (!value.login || value.login === 'false') { reject(new LoginError(value)); } diff --git a/lib/classes/Agent.ts b/lib/classes/Agent.ts index 7e43974..5dc6239 100644 --- a/lib/classes/Agent.ts +++ b/lib/classes/Agent.ts @@ -1,108 +1,113 @@ import { UUID } from './UUID'; import { Vector3 } from './Vector3'; import { Inventory } from './Inventory'; -import { Wearable } from './Wearable'; -import { Region } from './Region'; +import type { Wearable } from './Wearable'; +import type { Region } from './Region'; import { Message } from '../enums/Message'; -import { Packet } from './Packet'; -import { AvatarAnimationMessage } from './messages/AvatarAnimation'; +import type { Packet } from './Packet'; +import type { AvatarAnimationMessage } from './messages/AvatarAnimation'; import { AgentUpdateMessage } from './messages/AgentUpdate'; import { Quaternion } from './Quaternion'; import { AgentState } from '../enums/AgentState'; import { BuiltInAnimations } from '../enums/BuiltInAnimations'; import { AgentWearablesRequestMessage } from './messages/AgentWearablesRequest'; -import { AgentWearablesUpdateMessage } from './messages/AgentWearablesUpdate'; +import type { AgentWearablesUpdateMessage } from './messages/AgentWearablesUpdate'; import { RezSingleAttachmentFromInvMessage } from './messages/RezSingleAttachmentFromInv'; import { AttachmentPoint } from '../enums/AttachmentPoint'; import { Utils } from './Utils'; -import { ClientEvents } from './ClientEvents'; -import * as Long from 'long'; -import { GroupChatSessionAgentListEvent } from '../events/GroupChatSessionAgentListEvent'; +import type { ClientEvents } from './ClientEvents'; +import type * as Long from 'long'; +import type { GroupChatSessionAgentListEvent } from '../events/GroupChatSessionAgentListEvent'; import { AgentFlags } from '../enums/AgentFlags'; import { ControlFlags } from '../enums/ControlFlags'; import { PacketFlags } from '../enums/PacketFlags'; import { FolderType } from '../enums/FolderType'; -import { Subject, Subscription } from 'rxjs'; +import type { Subscription } from 'rxjs'; +import { Subject } from 'rxjs'; import { InventoryFolder } from './InventoryFolder'; import { BulkUpdateInventoryEvent } from '../events/BulkUpdateInventoryEvent'; -import { BulkUpdateInventoryMessage } from './messages/BulkUpdateInventory'; +import type { BulkUpdateInventoryMessage } from './messages/BulkUpdateInventory'; import { InventoryItem } from './InventoryItem'; -import { AgentDataUpdateMessage } from './messages/AgentDataUpdate'; +import type { AgentDataUpdateMessage } from './messages/AgentDataUpdate'; import { InventoryLibrary } from '../enums/InventoryLibrary'; +import { AssetType } from '../enums/AssetType'; export class Agent { - firstName: string; - lastName: string; - localID = 0; - agentID: UUID; - activeGroupID: UUID = UUID.zero(); - accessMax: string; - regionAccess: string; - agentAccess: string; - currentRegion: Region; - chatSessions = new Map(); + public cameraLookAt: Vector3 = new Vector3([0.979546, 0.105575, -0.171303]); + public cameraCenter: Vector3 = new Vector3([199.58, 203.95, 24.304]); + public cameraLeftAxis: Vector3 = new Vector3([-1.0, 0.0, 0]); + public cameraUpAxis: Vector3 = new Vector3([0.0, 0.0, 1.0]); + public cameraFar = 1; + public readonly appearanceCompleteEvent: Subject = new Subject(); + + private readonly headRotation = Quaternion.getIdentity(); + private readonly bodyRotation = Quaternion.getIdentity(); + + private wearables?: { + attachments: Wearable[]; + serialNumber: number + }; + private agentUpdateTimer: NodeJS.Timeout | null = null; + + private controlFlags: ControlFlags = 0; + + private readonly clientEvents: ClientEvents; + private animSubscription?: Subscription; + private readonly chatSessions = new Map, timeout?: NodeJS.Timeout }>(); - controlFlags: ControlFlags = 0; - openID: { - 'token'?: string, - 'url'?: string - } = {}; - AOTransition: boolean; - buddyList: { - 'buddyRightsGiven': boolean, - 'buddyID': UUID, - 'buddyRightsHas': boolean - }[] = []; - uiFlags: { - 'allowFirstLife'?: boolean - } = {}; - headRotation = Quaternion.getIdentity(); - bodyRotation = Quaternion.getIdentity(); - cameraLookAt: Vector3 = new Vector3([0.979546, 0.105575, -0.171303]); - cameraCenter: Vector3 = new Vector3([199.58, 203.95, 24.304]); - cameraLeftAxis: Vector3 = new Vector3([-1.0, 0.0, 0]); - cameraUpAxis: Vector3 = new Vector3([0.0, 0.0, 1.0]); - cameraFar = 1; - maxGroups: number; - agentFlags: number; - startLocation: string; - cofVersion: number; - home: { - 'regionHandle'?: Long, - 'position'?: Vector3, - 'lookAt'?: Vector3 - } = {}; - snapshotConfigURL: string; - inventory: Inventory; - gestures: { - assetID: UUID, - itemID: UUID - }[] = []; - agentAppearanceService: string; - wearables?: { - attachments: Wearable[]; - serialNumber: number - }; - agentUpdateTimer: NodeJS.Timeout | null = null; - estateManager = false; - appearanceComplete = false; - appearanceCompleteEvent: Subject = new Subject(); - private clientEvents: ClientEvents; - private animSubscription?: Subscription; - - public onGroupChatExpired = new Subject(); - - constructor(clientEvents: ClientEvents) + public constructor(clientEvents: ClientEvents) { - this.inventory = new Inventory(clientEvents, this); + this.inventory = new Inventory(this); this.clientEvents = clientEvents; this.clientEvents.onGroupChatAgentListUpdate.subscribe((event: GroupChatSessionAgentListEvent) => { @@ -152,12 +157,12 @@ export class Agent } } - setIsEstateManager(is: boolean): void + public setIsEstateManager(is: boolean): void { this.estateManager = is; } - getSessionAgentCount(uuid: UUID): number + public getSessionAgentCount(uuid: UUID): number { const str = uuid.toString(); const session = this.chatSessions.get(str); @@ -167,11 +172,11 @@ export class Agent } else { - return Object.keys(session.agents).length; + return session.agents.size; } } - addChatSession(uuid: UUID, timeout: boolean): boolean + public addChatSession(uuid: UUID, timeout: boolean): boolean { const str = uuid.toString(); if (this.chatSessions.has(str)) @@ -188,18 +193,18 @@ export class Agent return true; } - private groupChatExpired(groupID: UUID): void + public groupChatExpired(groupID: UUID): void { this.onGroupChatExpired.next(groupID); } - hasChatSession(uuid: UUID): boolean + public hasChatSession(uuid: UUID): boolean { const str = uuid.toString(); return this.chatSessions.has(str); } - deleteChatSession(uuid: UUID): boolean + public deleteChatSession(uuid: UUID): boolean { const str = uuid.toString(); if (!this.chatSessions.has(str)) @@ -210,7 +215,7 @@ export class Agent return true; } - setCurrentRegion(region: Region): void + public setCurrentRegion(region: Region): void { if (this.animSubscription !== undefined) { @@ -223,11 +228,122 @@ export class Agent Message.BulkUpdateInventory ], this.onMessage.bind(this)); } - circuitActive(): void + + public circuitActive(): void { this.agentUpdateTimer = setInterval(this.sendAgentUpdate.bind(this), 1000); } - sendAgentUpdate(): void + + public shutdown(): void + { + if (this.agentUpdateTimer !== null) + { + clearInterval(this.agentUpdateTimer); + this.agentUpdateTimer = null; + } + } + + public async setInitialAppearance(): Promise + { + const circuit = this.currentRegion.circuit; + const wearablesRequest: AgentWearablesRequestMessage = new AgentWearablesRequestMessage(); + wearablesRequest.AgentData = { + AgentID: this.agentID, + SessionID: circuit.sessionID + }; + circuit.sendMessage(wearablesRequest, PacketFlags.Reliable); + + const wearables: AgentWearablesUpdateMessage = await circuit.waitForMessage(Message.AgentWearablesUpdate, 30000); + + if (!this.wearables || wearables.AgentData.SerialNum > this.wearables.serialNumber) + { + this.wearables = { + serialNumber: wearables.AgentData.SerialNum, + attachments: [] + }; + for (const wearable of wearables.WearableData) + { + if (this.wearables.attachments) + { + this.wearables.attachments.push({ + itemID: wearable.ItemID, + assetID: wearable.AssetID, + wearableType: wearable.WearableType + }); + } + } + } + + + const currentOutfitFolder = await this.getWearables(); + const wornObjects = this.currentRegion.objects.getObjectsByParent(this.localID); + for (const item of currentOutfitFolder.items) + { + if (item.type === AssetType.Notecard) + { + let found = false; + for (const obj of wornObjects) + { + if (obj.hasNameValueEntry('AttachItemID')) + { + if (item.itemID.toString() === obj.getNameValueEntry('AttachItemID')) + { + found = true; + } + } + } + + if (!found) + { + const rsafi = new RezSingleAttachmentFromInvMessage(); + rsafi.AgentData = { + AgentID: this.agentID, + SessionID: circuit.sessionID + }; + rsafi.ObjectData = { + ItemID: new UUID(item.itemID.toString()), + OwnerID: this.agentID, + AttachmentPt: 0x80 | AttachmentPoint.Default, + ItemFlags: item.flags, + GroupMask: item.permissions.groupMask, + EveryoneMask: item.permissions.everyoneMask, + NextOwnerMask: item.permissions.nextOwnerMask, + Name: Utils.StringToBuffer(item.name), + Description: Utils.StringToBuffer(item.description) + }; + circuit.sendMessage(rsafi, PacketFlags.Reliable); + } + } + } + this.appearanceComplete = true; + this.appearanceCompleteEvent.next(); + } + + public setControlFlag(flag: ControlFlags): void + { + this.controlFlags = this.controlFlags | flag; + } + + public clearControlFlag(flag: ControlFlags): void + { + this.controlFlags = this.controlFlags & ~flag; + } + + public async getWearables(): Promise + { + for (const uuid of this.inventory.main.skeleton.keys()) + { + const folder = this.inventory.main.skeleton.get(uuid); + if (folder && folder.typeDefault === FolderType.CurrentOutfit) + { + await folder.populate(false); + return folder; + } + } + throw new Error('Unable to get wearables from inventory') + } + + public sendAgentUpdate(): void { if (!this.currentRegion) { @@ -251,15 +367,8 @@ export class Agent }; circuit.sendMessage(agentUpdate, 0 as PacketFlags); } - shutdown(): void - { - if (this.agentUpdateTimer !== null) - { - clearInterval(this.agentUpdateTimer); - this.agentUpdateTimer = null; - } - } - onMessage(packet: Packet): void + + private onMessage(packet: Packet): void { if (packet.message.id === Message.AgentDataUpdate) { @@ -274,7 +383,7 @@ export class Agent for (const newItem of msg.ItemData) { const folder = this.inventory.findFolder(newItem.FolderID); - const item = new InventoryItem(folder || undefined, this); + const item = new InventoryItem(folder ?? undefined, this); item.assetID = newItem.AssetID; item.inventoryType = newItem.InvType; item.name = Utils.BufferToStringSimple(newItem.Name); @@ -316,7 +425,7 @@ export class Agent { for (const anim of animMsg.AnimationList) { - const a = anim.AnimID.toString(); + const a = anim.AnimID.toString() as BuiltInAnimations; if (a === BuiltInAnimations.STANDUP || a === BuiltInAnimations.PRE_JUMP || a === BuiltInAnimations.LAND || @@ -333,104 +442,4 @@ export class Agent } } } - - async getWearables(): Promise - { - for (const uuid of Object.keys(this.inventory.main.skeleton)) - { - const folder = this.inventory.main.skeleton[uuid]; - if (folder.typeDefault === FolderType.CurrentOutfit) - { - await folder.populate(false); - return folder; - } - } - throw new Error('Unable to get wearables from inventory') - } - - async setInitialAppearance(): Promise - { - const circuit = this.currentRegion.circuit; - const wearablesRequest: AgentWearablesRequestMessage = new AgentWearablesRequestMessage(); - wearablesRequest.AgentData = { - AgentID: this.agentID, - SessionID: circuit.sessionID - }; - circuit.sendMessage(wearablesRequest, PacketFlags.Reliable); - - const wearables: AgentWearablesUpdateMessage = await circuit.waitForMessage(Message.AgentWearablesUpdate, 10000); - - if (!this.wearables || wearables.AgentData.SerialNum > this.wearables.serialNumber) - { - this.wearables = { - serialNumber: wearables.AgentData.SerialNum, - attachments: [] - }; - for (const wearable of wearables.WearableData) - { - if (this.wearables && this.wearables.attachments) - { - this.wearables.attachments.push({ - itemID: wearable.ItemID, - assetID: wearable.AssetID, - wearableType: wearable.WearableType - }); - } - } - } - - - const currentOutfitFolder = await this.getWearables(); - const wornObjects = this.currentRegion.objects.getObjectsByParent(this.localID); - for (const item of currentOutfitFolder.items) - { - if (item.type === 6) - { - let found = false; - for (const obj of wornObjects) - { - if (obj.hasNameValueEntry('AttachItemID')) - { - if (item.itemID.toString() === obj.getNameValueEntry('AttachItemID')) - { - found = true; - } - } - } - - if (!found) - { - const rsafi = new RezSingleAttachmentFromInvMessage(); - rsafi.AgentData = { - AgentID: this.agentID, - SessionID: circuit.sessionID - }; - rsafi.ObjectData = { - ItemID: new UUID(item.itemID.toString()), - OwnerID: this.agentID, - AttachmentPt: 0x80 | AttachmentPoint.Default, - ItemFlags: item.flags, - GroupMask: item.permissions.groupMask, - EveryoneMask: item.permissions.everyoneMask, - NextOwnerMask: item.permissions.nextOwnerMask, - Name: Utils.StringToBuffer(item.name), - Description: Utils.StringToBuffer(item.description) - }; - circuit.sendMessage(rsafi, PacketFlags.Reliable); - } - } - } - this.appearanceComplete = true; - this.appearanceCompleteEvent.next(); - } - - setControlFlag(flag: ControlFlags): void - { - this.controlFlags = this.controlFlags | flag; - } - - clearControlFlag(flag: ControlFlags): void - { - this.controlFlags = this.controlFlags & ~flag; - } } diff --git a/lib/classes/AssetMap.ts b/lib/classes/AssetMap.ts index 4d47d70..96f6400 100644 --- a/lib/classes/AssetMap.ts +++ b/lib/classes/AssetMap.ts @@ -1,96 +1,132 @@ -import { InventoryItem } from './InventoryItem'; -import { Material } from './public/Material'; +import { UUID } from './UUID'; +import type { InventoryItem } from './InventoryItem'; +import type { AssetType } from '../enums/AssetType'; -export class AssetMap +export interface AssetData { - mesh: { - [key: string]: { - name: string, - description: string, - item: InventoryItem | null - } - } = {}; - textures: { - [key: string]: { - name?: string, - description?: string, - item: InventoryItem | null - } - } = {}; - materials: { - [key: string]: Material | null - } = {}; - animations: { - [key: string]: { - name?: string, - description?: string, - item: InventoryItem | null - } - } = {}; - sounds: { - [key: string]: { - name?: string, - description?: string, - item: InventoryItem | null - } - } = {}; - gestures: { - [key: string]: { - name?: string, - description?: string, - item: InventoryItem | null - } - } = {}; - callingcards: { - [key: string]: { - name?: string, - description?: string, - item: InventoryItem | null - } - } = {}; - scripts: { - [key: string]: { - name?: string, - description?: string, - item: InventoryItem | null - } - } = {}; - clothing: { - [key: string]: { - name?: string, - description?: string, - item: InventoryItem | null - } - } = {}; - settings: { - [key: string]: { - name?: string, - description?: string, - item: InventoryItem | null - } - } = {}; - notecards: { - [key: string]: { - name?: string, - description?: string, - item: InventoryItem | null - } - } = {}; - bodyparts: { - [key: string]: { - name?: string, - description?: string, - item: InventoryItem | null - } - } = {}; - objects: { - [key: string]: InventoryItem | null - } = {}; - temporaryInventory: { - [key: string]: InventoryItem - } = {}; - byUUID: { - [key: string]: InventoryItem - } = {}; - pending: { [key: string]: boolean } = {}; + name?: string; + description?: string; + assetType?: AssetType; + item?: T +} + +export class AssetMap +{ + private readonly map = new Map>(); + private readonly pending = new Map(); + + public get(uuid: UUID | string): AssetData | undefined + { + if (uuid instanceof UUID) + { + uuid = uuid.toString(); + } + return this.map.get(uuid); + } + + public request(uuid: UUID | string, metadata?: AssetData): void + { + if (uuid instanceof UUID) + { + uuid = uuid.toString(); + } + const mapItem = this.map.get(uuid); + if (mapItem === undefined) + { + if (metadata === undefined) + { + metadata = {}; + } + this.map.set(uuid, metadata); + } + let pending = this.pending.get(uuid); + if (pending === undefined) + { + pending = 0; + } + this.pending.set(uuid, ++pending); + } + + public delete(uuid: UUID | string): void + { + if (uuid instanceof UUID) + { + uuid = uuid.toString(); + } + this.map.delete(uuid); + this.pending.delete(uuid); + } + + public getFetchList(): string[] + { + const list: string[] = []; + for(const k of this.map.keys()) + { + let p = this.pending.get(k); + if (p === undefined) + { + continue; + } + if (p > 0) + { + const data = this.map.get(k); + if (data === undefined) + { + continue; + } + if (data.item === undefined) + { + this.pending.set(k, --p); + list.push(k); + } + } + } + return list; + } + + public setItem(uuid: UUID | string, item: T): void + { + if (uuid instanceof UUID) + { + uuid = uuid.toString(); + } + let entry = this.map.get(uuid); + if (entry === undefined) + { + entry = { + item: item + }; + } + else + { + entry.item = item; + } + + this.map.set(uuid, entry); + } + + public doneFetch(list: string[]): void + { + for(const item of list) + { + const i = this.map.get(item); + if (i === undefined) + { + continue; + } + if (i.item === undefined) + { + let pending = this.pending.get(item) + if (pending === undefined) + { + pending = 0; + } + this.pending.set(item, ++pending); + } + else + { + this.pending.delete(item); + } + } + } } diff --git a/lib/classes/AssetRegistry.ts b/lib/classes/AssetRegistry.ts new file mode 100644 index 0000000..9ddf041 --- /dev/null +++ b/lib/classes/AssetRegistry.ts @@ -0,0 +1,34 @@ +import type { InventoryItem } from './InventoryItem'; +import type { Material } from './public/Material'; +import type { GameObject } from './public/GameObject'; +import type { UUID } from './UUID'; +import { AssetMap } from './AssetMap'; + +export class AssetRegistry +{ + public readonly mesh = new AssetMap(); + public readonly textures = new AssetMap(); + public readonly materials = new AssetMap(); + public readonly gltfMaterials = new AssetMap(); + public readonly animations = new AssetMap(); + public readonly sounds = new AssetMap(); + public readonly gestures = new AssetMap(); + public readonly callingcards = new AssetMap(); + public readonly scripts = new AssetMap(); + public readonly settings = new AssetMap(); + public readonly notecards = new AssetMap(); + public readonly wearables = new AssetMap(); + public readonly objects = new AssetMap(); + + public scriptsToCompile = new Map; + public temporaryInventory = new Map(); + public byUUID = new Map(); +} diff --git a/lib/classes/AssetTypeRegistry.ts b/lib/classes/AssetTypeRegistry.ts new file mode 100644 index 0000000..86abb3c --- /dev/null +++ b/lib/classes/AssetTypeRegistry.ts @@ -0,0 +1,102 @@ +import { AssetType } from '../enums/AssetType'; + +export class RegisteredAssetType +{ + public type: AssetType; + public description: string; + public typeName: string; + public humanName: string; + public canLink: boolean; + public canFetch: boolean; + public canKnow: boolean; +} + +export class AssetTypeRegistry +{ + private static readonly assetTypeByType = new Map(); + private static readonly assetTypeByName = new Map(); + private static readonly assetTypeByHumanName = new Map(); + + public static registerAssetType(type: AssetType, description: string, typeName: string, humanName: string, canLink: boolean, canFetch: boolean, canKnow: boolean): void + { + const t = new RegisteredAssetType(); + t.type = type; + t.description = description; + t.typeName = typeName; + t.humanName = humanName; + t.canLink = canLink; + t.canFetch = canFetch; + t.canKnow = canKnow; + this.assetTypeByType.set(type, t); + this.assetTypeByName.set(typeName, t); + this.assetTypeByHumanName.set(humanName, t); + } + + public static getType(type: AssetType): RegisteredAssetType | undefined + { + return this.assetTypeByType.get(type); + } + + public static getTypeName(type: AssetType): string + { + const t = this.getType(type); + if (t === undefined) + { + return 'invalid'; + } + return t.typeName; + } + + public static getHumanName(type: AssetType): string + { + const t = this.getType(type); + if (t === undefined) + { + return 'Unknown'; + } + return t.humanName; + } + + public static getTypeFromTypeName(type: string): RegisteredAssetType | undefined + { + return this.assetTypeByName.get(type); + } + + public static getTypeFromHumanName(type: string): RegisteredAssetType | undefined + { + return this.assetTypeByHumanName.get(type); + } +} + +AssetTypeRegistry.registerAssetType(AssetType.Texture, 'TEXTURE', 'texture', 'texture', true, false, true); +AssetTypeRegistry.registerAssetType(AssetType.Sound, 'SOUND', 'sound', 'sound', true, true, true); +AssetTypeRegistry.registerAssetType(AssetType.CallingCard, 'CALLINGCARD', 'callcard', 'calling card', true, false, false); +AssetTypeRegistry.registerAssetType(AssetType.Landmark, 'LANDMARK', 'landmark', 'landmark', true, true, true); +AssetTypeRegistry.registerAssetType(AssetType.Script, 'SCRIPT', 'script', 'legacy script', true, false, false); +AssetTypeRegistry.registerAssetType(AssetType.Clothing, 'CLOTHING', 'clothing', 'clothing', true, true, true); +AssetTypeRegistry.registerAssetType(AssetType.Object, 'OBJECT', 'object', 'object', true, false, false); +AssetTypeRegistry.registerAssetType(AssetType.Notecard, 'NOTECARD', 'notecard', 'note card', true, false, true); +AssetTypeRegistry.registerAssetType(AssetType.Category, 'CATEGORY', 'category', 'folder', true, false, false); +AssetTypeRegistry.registerAssetType(AssetType.LSLText, 'LSL_TEXT', 'lsltext', 'lsl2 script', true, false, false); +AssetTypeRegistry.registerAssetType(AssetType.LSLBytecode, 'LSL_BYTECODE', 'lslbyte', 'lsl bytecode', true, false, false); +AssetTypeRegistry.registerAssetType(AssetType.TextureTGA, 'TEXTURE_TGA', 'txtr_tga', 'tga texture', true, false, false); +AssetTypeRegistry.registerAssetType(AssetType.Bodypart, 'BODYPART', 'bodypart', 'body part', true, true, true); +AssetTypeRegistry.registerAssetType(AssetType.SoundWAV, 'SOUND_WAV', 'snd_wav', 'sound', true, false, false); +AssetTypeRegistry.registerAssetType(AssetType.ImageTGA, 'IMAGE_TGA', 'img_tga', 'targa image', true, false, false); +AssetTypeRegistry.registerAssetType(AssetType.ImageJPEG, 'IMAGE_JPEG', 'jpeg', 'jpeg image', true, false, false); +AssetTypeRegistry.registerAssetType(AssetType.Animation, 'ANIMATION', 'animatn', 'animation', true, true, true); +AssetTypeRegistry.registerAssetType(AssetType.Gesture, 'GESTURE', 'gesture', 'gesture', true, true, true); +AssetTypeRegistry.registerAssetType(AssetType.Simstate, 'SIMSTATE', 'simstate', 'simstate', false, false, false); +AssetTypeRegistry.registerAssetType(AssetType.Link, 'LINK', 'link', 'sym link', false, false, true); +AssetTypeRegistry.registerAssetType(AssetType.LinkFolder, 'FOLDER_LINK', 'link_f', 'sym folder link', false, false, true); +AssetTypeRegistry.registerAssetType(AssetType.Mesh, 'MESH', 'mesh', 'mesh', false, false, false); +AssetTypeRegistry.registerAssetType(AssetType.Widget, 'WIDGET', 'widget', 'widget', false, false, false); +AssetTypeRegistry.registerAssetType(AssetType.Person, 'PERSON', 'person', 'person', false, false, false); +AssetTypeRegistry.registerAssetType(AssetType.Settings, 'SETTINGS', 'settings', 'settings blob', true, true, true); +AssetTypeRegistry.registerAssetType(AssetType.Material, 'MATERIAL', 'material', 'render material', true, true, true); +AssetTypeRegistry.registerAssetType(AssetType.GLTF, 'GLTF', 'gltf', 'GLTF', true, true, true); +AssetTypeRegistry.registerAssetType(AssetType.GLTFBin, 'GLTF_BIN', 'glbin', 'GLTF binary', true, true, true); +AssetTypeRegistry.registerAssetType(AssetType.Unknown, 'UNKNOWN', 'invalid', 'Unknown', false, false, false); +AssetTypeRegistry.registerAssetType(AssetType.None, 'NONE', '-1', 'None', false, false, false); +AssetTypeRegistry.registerAssetType(AssetType.LegacyMaterial, 'LEGACYMAT', 'legacymat', 'legacy material', false, false, false); + diff --git a/lib/classes/BVH.ts b/lib/classes/BVH.ts index abb2d4a..2e3ab33 100644 --- a/lib/classes/BVH.ts +++ b/lib/classes/BVH.ts @@ -3,20 +3,20 @@ import { BVHJoint } from './BVHJoint'; export class BVH { - priority: number; - length: number; - expressionName: string; - inPoint: number; - outPoint: number; - loop: number; - easeInTime: number; - easeOutTime: number; - handPose: number; - jointCount: number; - joints: BVHJoint[] = []; + public priority: number; + public length: number; + public expressionName: string; + public inPoint: number; + public outPoint: number; + public loop: number; + public easeInTime: number; + public easeOutTime: number; + public handPose: number; + public jointCount: number; + public joints: BVHJoint[] = []; // Decodes the binary LL animation format into human-readable BVH. - readFromBuffer(buf: Buffer, pos: number): number + public readFromBuffer(buf: Buffer, pos: number): number { const header1 = buf.readUInt16LE(pos); pos = pos + 2; diff --git a/lib/classes/BVHJoint.ts b/lib/classes/BVHJoint.ts index 82cba0e..131eb52 100644 --- a/lib/classes/BVHJoint.ts +++ b/lib/classes/BVHJoint.ts @@ -4,17 +4,17 @@ import { BVHJointKeyframe } from './BVHJointKeyframe'; export class BVHJoint { - name: string; - priority: number; + public name: string; + public priority: number; - rotationKeyframeCount: number; - rotationKeyframes: BVHJointKeyframe[] = []; + public rotationKeyframeCount: number; + public rotationKeyframes: BVHJointKeyframe[] = []; - positionKeyframeCount: number; - positionKeyframes: BVHJointKeyframe[] = []; + public positionKeyframeCount: number; + public positionKeyframes: BVHJointKeyframe[] = []; - readFromBuffer(buf: Buffer, pos: number, inPoint: number, outPoint: number): number + public readFromBuffer(buf: Buffer, pos: number, inPoint: number, outPoint: number): number { const result = Utils.BufferToString(buf, pos); pos += result.readLength; diff --git a/lib/classes/BVHJointKeyframe.ts b/lib/classes/BVHJointKeyframe.ts index c4c564a..34411ff 100644 --- a/lib/classes/BVHJointKeyframe.ts +++ b/lib/classes/BVHJointKeyframe.ts @@ -1,7 +1,7 @@ -import { Vector3 } from './Vector3'; +import type { Vector3 } from './Vector3'; export class BVHJointKeyframe { - time: number; - transform: Vector3; + public time: number; + public transform: Vector3; } diff --git a/lib/classes/BatchQueue.ts b/lib/classes/BatchQueue.ts index c8fb226..24537d0 100644 --- a/lib/classes/BatchQueue.ts +++ b/lib/classes/BatchQueue.ts @@ -3,8 +3,8 @@ import { Subject } from 'rxjs'; export class BatchQueue { private running = false; - private pending: Set = new Set(); - private onResult = new Subject<{ + private readonly pending: Set = new Set(); + private readonly onResult = new Subject<{ batch: Set, failed?: Set, exception?: unknown @@ -26,7 +26,7 @@ export class BatchQueue if (!this.running) { - this.processBatch().catch((_e) => + this.processBatch().catch((_e: unknown) => { // ignore }); @@ -61,7 +61,7 @@ export class BatchQueue if (results.exception !== undefined) { subs.unsubscribe(); - reject(results.exception); + reject(new Error(String(results.exception))); return; } if (waiting.size === 0) @@ -116,7 +116,7 @@ export class BatchQueue this.running = false; if (this.pending.size > 0) { - this.processBatch().catch((_e) => + this.processBatch().catch((_e: unknown) => { // ignore }); diff --git a/lib/classes/BinaryReader.spec.ts b/lib/classes/BinaryReader.spec.ts new file mode 100644 index 0000000..63f151d --- /dev/null +++ b/lib/classes/BinaryReader.spec.ts @@ -0,0 +1,327 @@ +// BinaryReader.spec.ts +import { describe, it, expect, beforeEach } from 'vitest'; +import { BinaryReader } from './BinaryReader'; +import { UUID } from './UUID'; +import { BinaryWriter } from "./BinaryWriter"; + +describe('BinaryReader', () => +{ + let buffer: Buffer; + let reader: BinaryReader; + + beforeEach(() => + { + buffer = Buffer.alloc(64); + let pos = 0; + + buffer.writeUInt8(0xFF, pos++); + buffer.writeInt8(-1, pos++); + buffer.writeUInt16LE(0xABCD, pos); + pos += 2; + buffer.writeInt16LE(-12345, pos); + pos += 2; + buffer.writeUInt32LE(0x12345678, pos); + pos += 4; + buffer.writeBigUInt64LE(BigInt('0x1234567890ABCDEF'), pos); + pos += 8; + buffer.writeFloatLE(1.23, pos); + pos += 4; + buffer.writeDoubleLE(3.14159, pos); + pos += 8; + buffer.write('test\x00', pos); + pos += 5; + buffer.writeUInt8(10, pos++); + buffer.write('hello', pos); + reader = new BinaryReader(buffer); + }); + + const testRead = ( + writeFunc: (bw: BinaryWriter, value: T) => void, + readFunc: (br: BinaryReader) => T, + value: T + ) => + { + const bw = new BinaryWriter(); + writeFunc(bw, value); + const buf = bw.get(); + const br = new BinaryReader(buf); + const readValue = readFunc(br); + expect(readValue).toBe(value); + }; + + describe('Seek and Positioning', () => + { + it('should seek to a valid position', () => + { + reader.seek(10); + expect(reader.getPos()).toBe(10); + }); + + it('should throw on invalid seek', () => + { + expect(() => reader.seek(-1)).toThrow(RangeError); + expect(() => reader.seek(100)).toThrow(RangeError); + }); + + it('should validate bounds correctly', () => + { + expect(() => reader.seek(100)).toThrow(RangeError); + }); + }); + + describe('Unsigned Integers', () => + { + it('should read UInt8 correctly', () => + { + reader.seek(0); + expect(reader.readUInt8()).toBe(0xFF); + }); + + it('should read UInt16LE correctly', () => + { + reader.seek(2); + expect(reader.readUInt16LE()).toBe(0xABCD); + }); + + it('should read UInt32LE correctly', () => + { + reader.seek(6); + expect(reader.readUInt32LE()).toBe(0x12345678); + }); + + it('should read UInt64LE correctly', () => + { + reader.seek(10); + expect(reader.readUInt64LE()).toBe(BigInt('0x1234567890ABCDEF')); + }); + }); + + describe('Signed Integers', () => + { + it('should read Int8 correctly', () => + { + reader.seek(1); + expect(reader.readInt8()).toBe(-1); + }); + + it('should read Int16LE correctly', () => + { + reader.seek(4); + expect(reader.readInt16LE()).toBe(-12345); + }); + }); + + describe('Floating Point Numbers', () => + { + it('should read FloatLE correctly', () => + { + reader.seek(18); + expect(reader.readFloatLE()).toBeCloseTo(1.23, 2); + }); + + it('should read DoubleLE correctly', () => + { + reader.seek(22); + expect(reader.readDoubleLE()).toBeCloseTo(3.14159, 5); + }); + }); + + describe('Complex Types', () => + { + describe('UUID', () => + { + it('should read UUID correctly', () => + { + const uuidBuf = Buffer.alloc(16, 0x01); // 16-byte buffer for UUID + buffer.set(uuidBuf, 0); + reader.seek(0); + const uuid = reader.readUUID(); + expect(uuid.toString()).toBe(new UUID(uuidBuf, 0).toString()); + }); + }); + + describe('Date', () => + { + it('should read Date correctly', () => + { + const timestamp = 1638460800000; // Arbitrary timestamp + buffer.writeBigUInt64LE(BigInt(timestamp), 0); + reader.seek(0); + const date = reader.readDate(); + expect(date.getTime()).toBe(timestamp); + }); + }); + + describe('CString', () => + { + it('should read CString correctly', () => + { + reader.seek(30); + expect(reader.readCString()).toBe('test'); + }); + + it('should throw when CString is not null-terminated during read', () => + { + const nonTerminatedBuf = Buffer.from('Test without null terminator', 'utf-8'); + const bw = new BinaryWriter(); + bw.writeBuffer(nonTerminatedBuf); + const br = new BinaryReader(bw.get()); + expect(() => br.readCString()).toThrow(RangeError); + }); + + it('should read empty CString correctly', () => + { + const bw = new BinaryWriter(); + bw.writeCString(''); + const br = new BinaryReader(bw.get()); + expect(br.readCString()).toBe(''); + }); + }); + + describe('String', () => + { + it('should read String correctly', () => + { + reader.seek(35); + expect(reader.readString()).toBe('hello'); + }); + + it('should read empty String correctly', () => + { + const bw = new BinaryWriter(); + bw.writeString(''); + const br = new BinaryReader(bw.get()); + expect(br.readString()).toBe(''); + }); + + it('should read large String correctly', () => + { + const largeStr = 'a'.repeat(1000); + testRead( + (bw, val) => bw.writeString(val), + (br) => br.readString(), + largeStr + ); + }); + }); + }); + + describe('Buffer Operations', () => + { + it('should read Buffer correctly', () => + { + const testBuffer = Buffer.from([0xDE, 0xAD, 0xBE, 0xEF]); + const bw = new BinaryWriter(); + bw.writeBuffer(testBuffer); + const br = new BinaryReader(bw.get()); + expect(br.readBuffer(testBuffer.length)).toEqual(testBuffer); + }); + + it('should read partial Buffer correctly', () => + { + const testBuffer = Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05]); + const bw = new BinaryWriter(); + bw.writeBuffer(testBuffer, 1, 4); // [0x02, 0x03, 0x04] + const br = new BinaryReader(bw.get()); + expect(br.readBuffer(3)).toEqual(Buffer.from([0x02, 0x03, 0x04])); + }); + + it('should read empty Buffer correctly', () => + { + const bw = new BinaryWriter(); + bw.writeBuffer(Buffer.alloc(0)); + const br = new BinaryReader(bw.get()); + expect(br.readBuffer(0).length).toBe(0); + }); + }); + + describe('Peek and Read Operations', () => + { + it('should peek without altering position', () => + { + reader.seek(2); + const val = reader.peekUInt16LE(); + expect(val).toBe(0xABCD); + expect(reader.getPos()).toBe(2); + }); + + it('should handle peek and read separately', () => + { + reader.seek(2); + const peeked = reader.peekUInt16LE(); + expect(peeked).toBe(0xABCD); + expect(reader.getPos()).toBe(2); + const read = reader.readUInt16LE(); + expect(read).toBe(0xABCD); + expect(reader.getPos()).toBe(4); + }); + }); + + describe('VarInt', () => + { + it('should read various size VarInts correctly', () => + { + const nums: [number, number | bigint, string][] = [ + [1, 0, 'AA=='], + [1, 31, 'Pg=='], + [2, 7166, '/G8='], + [3, 71665, '4t8I'], + [4, 7166512, '4OjqBg=='], + [5, 716651292, 'uOy5qwU='], + [6, 19928182913, 'gsL/vJQB'], + [7, 19928182913289, 'kuSbxPyHCQ=='], + [8, Number.MAX_SAFE_INTEGER, '/v///////x8='], + [14, 79228162514264337593543950000n, '4Pr//////////////z8='], + [19, 340282366920938463463374607431768211455n, '/v//////////////////////Bw=='] + ]; + + for (const num of nums) + { + const br = new BinaryReader(Buffer.from(num[2], 'base64')); + expect(br.length()).toBe(num[0]); + const result = br.readVarInt(); + expect(result).toBe(num[1]); + } + }); + + it('should read various negative size VarInts correctly', () => + { + const nums: [number, number | bigint, string][] = [ + [1, -24, 'Lw=='], + [2, -7166, '+28='], + [3, -71665, '4d8I'], + [4, -7166512, '3+jqBg=='], + [5, -716651292, 't+y5qwU='], + [6, -19928182913, 'gcL/vJQB'], + [7, -19928182913289, 'keSbxPyHCQ=='], + [8, Number.MIN_SAFE_INTEGER, '/f///////x8='], + [14, -39614081257132168796771975168n, '/////////////////x8='], + [19, -170141183460469231731687303715884105728n, '////////////////////////Aw=='] + ]; + + for (const num of nums) + { + const br = new BinaryReader(Buffer.from(num[2], 'base64')); + expect(br.length()).toBe(num[0]); + const result = br.readVarInt(); + expect(result).toBe(num[1]); + } + }); + }); + + describe('Error Handling', () => + { + it('should throw when reading beyond buffer length', () => + { + reader.seek(64); + expect(() => reader.readUInt8()).toThrow(RangeError); + }); + + it('should throw when reading invalid UUID buffer', () => + { + const invalidUUIDBuf = Buffer.alloc(15, 0x00); // Should be 16 bytes + const rdr = new BinaryReader(invalidUUIDBuf); + expect(() => rdr.readUUID()).toThrow(Error); + }); + }); +}); \ No newline at end of file diff --git a/lib/classes/BinaryReader.ts b/lib/classes/BinaryReader.ts new file mode 100644 index 0000000..84f2dba --- /dev/null +++ b/lib/classes/BinaryReader.ts @@ -0,0 +1,443 @@ +import { UUID } from "./UUID"; + +export class BinaryReader +{ + public pos = 0; + + public constructor(private readonly buf: Buffer) + { + + } + + public seek(pos: number): void + { + if (pos < 0 || pos > this.buf.length) + { + throw new RangeError(`Invalid seek position: ${pos}`); + } + this.pos = pos; + } + + public getPos(): number + { + return this.pos; + } + + public peekUInt8(): number + { + this.checkBounds(1); + return this.buf.readUInt8(this.pos); + } + + public peekInt8(): number + { + this.checkBounds(1); + return this.buf.readInt8(this.pos); + } + + public peekUInt16LE(): number + { + this.checkBounds(2); + return this.buf.readUInt16LE(this.pos); + } + + public peekInt16LE(): number + { + this.checkBounds(2); + return this.buf.readInt16LE(this.pos); + } + + public peekUInt16BE(): number + { + this.checkBounds(2); + return this.buf.readUInt16BE(this.pos); + } + + public peekInt16BE(): number + { + this.checkBounds(2); + return this.buf.readInt16BE(this.pos); + } + + public peekUInt32LE(): number + { + this.checkBounds(4); + return this.buf.readUInt32LE(this.pos); + } + + public peekInt32LE(): number + { + this.checkBounds(4); + return this.buf.readInt32LE(this.pos); + } + + public peekUInt32BE(): number + { + this.checkBounds(4); + return this.buf.readUInt32BE(this.pos); + } + + public peekInt32BE(): number + { + this.checkBounds(4); + return this.buf.readInt32BE(this.pos); + } + + public peekUInt64LE(): bigint + { + this.checkBounds(8); + return this.buf.readBigUInt64LE(this.pos); + } + + public peekInt64LE(): bigint + { + this.checkBounds(8); + return this.buf.readBigInt64LE(this.pos); + } + + public peekUInt64BE(): bigint + { + this.checkBounds(8); + return this.buf.readBigUInt64BE(this.pos); + } + + public peekInt64BE(): bigint + { + this.checkBounds(8); + return this.buf.readBigInt64BE(this.pos); + } + + public peekFloatLE(): number + { + this.checkBounds(4); + return this.buf.readFloatLE(this.pos); + } + + public peekFloatBE(): number + { + this.checkBounds(4); + return this.buf.readFloatBE(this.pos); + } + + public peekDoubleLE(): number + { + this.checkBounds(8); + return this.buf.readDoubleLE(this.pos); + } + + public peekDoubleBE(): number + { + this.checkBounds(8); + return this.buf.readDoubleBE(this.pos); + } + + public peekUUID(): UUID + { + this.checkBounds(16); + return new UUID(this.buf, this.pos); + } + + public peekDate(): Date + { + this.checkBounds(8); + return new Date(Number(this.peekUInt64LE())); + } + + public peekBuffer(length: number): Buffer + { + if (this.pos + length > this.buf.length) + { + throw new RangeError("Attempt to read beyond buffer length"); + } + return this.buf.subarray(this.pos, this.pos + length); + } + + public peekVarInt(): { value: number | bigint; bytesRead: number } + { + let value = 0n; + let bytesRead = 0; + let shift = 0n; + + while (this.pos + bytesRead < this.buf.length) + { + const byte = this.buf[this.pos + bytesRead]; + bytesRead++; + + const byteValue = BigInt(byte & 0x7F); + value |= (byteValue << shift); + + if ((byte & 0x80) === 0) + { + break; + } + + shift += 7n; + + if (bytesRead > 100) + { + throw new Error('VarInt is too long'); + } + } + + if (bytesRead === 0 || (this.pos + bytesRead) > this.buf.length) + { + throw new Error('Incomplete VarInt'); + } + + const decoded = (value >> 1n) ^ -(value & 1n); + + if ( + decoded >= BigInt(Number.MIN_SAFE_INTEGER) && + decoded <= BigInt(Number.MAX_SAFE_INTEGER) + ) + { + return {value: Number(decoded), bytesRead}; + } + else + { + return {value: decoded, bytesRead}; + } + } + + public peekString(metadata?: { length: number | bigint, bytesRead: number }): string + { + const {value: length, bytesRead} = this.peekVarInt(); + if (length < 0) + { + throw new Error('Error reading string: Length is negative'); + } + this.checkBounds(bytesRead + Number(length)); + const start = this.pos + bytesRead; + const end = start + Number(length); + const stringData = this.buf.subarray(start, end); + if (metadata) + { + metadata.length = length; + metadata.bytesRead = bytesRead; + } + return new TextDecoder("utf-8").decode(stringData); + } + + public peekCString(): string + { + let tempPos = this.pos; + const start = tempPos; + + while (tempPos < this.buf.length && this.buf[tempPos] !== 0) + { + tempPos++; + } + + if (tempPos >= this.buf.length) + { + throw new RangeError("Null-terminated string not found"); + } + + const stringData = this.buf.subarray(start, tempPos); + return new TextDecoder("utf-8").decode(stringData); + } + + public peekFixedString(length: number): string + { + this.checkBounds(length); + return this.buf.subarray(this.pos, this.pos + length).toString('utf-8'); + } + + // read + + public readUInt8(): number + { + const num = this.peekUInt8(); + this.pos++; + return num; + } + + public readInt8(): number + { + const num = this.peekInt8(); + this.pos++; + return num; + } + + public readUInt16LE(): number + { + const num = this.peekUInt16LE(); + this.pos += 2; + return num; + } + + public readInt16LE(): number + { + const num = this.peekInt16LE(); + this.pos += 2; + return num; + } + + public readUInt16BE(): number + { + const num = this.peekUInt16BE(); + this.pos += 2; + return num; + } + + public readInt16BE(): number + { + const num = this.peekInt16BE(); + this.pos += 2; + return num; + } + + public readUInt32LE(): number + { + const num = this.peekUInt32LE(); + this.pos += 4; + return num; + } + + public readInt32LE(): number + { + const num = this.peekInt32LE(); + this.pos += 4; + return num; + } + + public readUInt32BE(): number + { + const num = this.peekUInt32BE(); + this.pos += 4; + return num; + } + + public readInt32BE(): number + { + const num = this.peekInt32BE(); + this.pos += 4; + return num; + } + + + public readUInt64LE(): bigint + { + const num = this.peekUInt64LE(); + this.pos += 8; + return num; + } + + public readInt64LE(): bigint + { + const num = this.peekInt64LE(); + this.pos += 8; + return num; + } + + public readUInt64BE(): bigint + { + const num = this.peekUInt64BE(); + this.pos += 8; + return num; + } + + public readInt64BE(): bigint + { + const num = this.peekInt64BE(); + this.pos += 8; + return num; + } + + public readFloatLE(): number + { + const num = this.peekFloatLE(); + this.pos += 4; + return num; + } + + public readFloatBE(): number + { + const num = this.peekFloatBE(); + this.pos += 4; + return num; + } + + public readDoubleLE(): number + { + const num = this.peekDoubleLE(); + this.pos += 8; + return num; + } + + public readDoubleBE(): number + { + const num = this.peekDoubleBE(); + this.pos += 8; + return num; + } + + public readUUID(): UUID + { + const uuid = this.peekUUID(); + this.pos += 16; + return uuid; + } + + public readDate(): Date + { + const d = this.peekDate(); + this.pos += 8; + return d; + } + + public readBuffer(length: number): Buffer + { + const buffer = this.peekBuffer(length); + this.pos += length; + return buffer; + } + + public readCString(): string + { + const str = this.peekCString(); + this.pos += Buffer.byteLength(str, "utf-8") + 1; // Include null terminator + return str; + } + + public readString(): string + { + const md: { + length: number | bigint, + bytesRead: number + } = { + length: 0, + bytesRead: 0 + } + const str = this.peekString(md); + this.pos += md.bytesRead + Number(md.length); + return str; + } + + public readFixedString(length: number): string + { + const str = this.peekFixedString(length); + this.pos += length; + return str; + } + + public length(): number + { + return this.buf.length; + } + + public readVarInt(): number | bigint + { + const {value, bytesRead} = this.peekVarInt(); + this.pos += bytesRead; + return value; + } + + private checkBounds(length: number): void + { + if (this.pos + length > this.buf.length) + { + throw new RangeError(`Attempt to read beyond buffer length: position=${this.pos}, length=${length}, bufferSize=${this.buf.length}`); + } + } +} \ No newline at end of file diff --git a/lib/classes/BinaryWriter.spec.ts b/lib/classes/BinaryWriter.spec.ts new file mode 100644 index 0000000..c691fef --- /dev/null +++ b/lib/classes/BinaryWriter.spec.ts @@ -0,0 +1,509 @@ +import { describe, it, expect } from 'vitest'; +import { BinaryReader } from './BinaryReader'; +import { BinaryWriter } from './BinaryWriter'; +import { UUID } from './UUID'; + +describe('BinaryWriter', () => +{ + const testRoundTrip = ( + writeFunc: (bw: BinaryWriter, value: T) => void, + readFunc: (br: BinaryReader) => T, + value: T + ) => + { + const bw = new BinaryWriter(); + writeFunc(bw, value); + const buf = bw.get(); + const br = new BinaryReader(buf); + const readValue = readFunc(br); + if (typeof value === 'number') + { + expect(readValue).toBeCloseTo(value); + } + else + { + expect(readValue).toBe(value); + } + }; + + describe('Unsigned Integers', () => + { + it('should write and read UInt8 correctly', () => + { + const value = 255; + testRoundTrip( + (bw, val) => bw.writeUInt8(val), + (br) => br.readUInt8(), + value + ); + }); + + it('should write and read UInt16LE correctly', () => + { + const value = 65535; + testRoundTrip( + (bw, val) => bw.writeUInt16LE(val), + (br) => br.readUInt16LE(), + value + ); + }); + + it('should write and read UInt16BE correctly', () => + { + const value = 65535; + testRoundTrip( + (bw, val) => bw.writeUInt16BE(val), + (br) => br.readUInt16BE(), + value + ); + }); + + it('should write and read UInt32LE correctly', () => + { + const value = 4294967295; + testRoundTrip( + (bw, val) => bw.writeUInt32LE(val), + (br) => br.readUInt32LE(), + value + ); + }); + + it('should write and read UInt32BE correctly', () => + { + const value = 4294967295; + testRoundTrip( + (bw, val) => bw.writeUInt32BE(val), + (br) => br.readUInt32BE(), + value + ); + }); + + it('should write and read UInt64LE correctly', () => + { + const value = 18446744073709551615n; // Max UInt64 + testRoundTrip( + (bw, val) => bw.writeUInt64LE(val), + (br) => br.readUInt64LE(), + value + ); + }); + + it('should write and read UInt64BE correctly', () => + { + const value = 18446744073709551615n; // Max UInt64 + testRoundTrip( + (bw, val) => bw.writeUInt64BE(val), + (br) => br.readUInt64BE(), + value + ); + }); + }); + + describe('Signed Integers', () => + { + it('should write and read Int8 correctly', () => + { + const value = -128; + testRoundTrip( + (bw, val) => bw.writeInt8(val), + (br) => br.readInt8(), + value + ); + }); + + it('should write and read Int16LE correctly', () => + { + const value = -32768; + testRoundTrip( + (bw, val) => bw.writeInt16LE(val), + (br) => br.readInt16LE(), + value + ); + }); + + it('should write and read Int16BE correctly', () => + { + const value = -32768; + testRoundTrip( + (bw, val) => bw.writeInt16BE(val), + (br) => br.readInt16BE(), + value + ); + }); + + it('should write and read Int32LE correctly', () => + { + const value = -2147483648; + testRoundTrip( + (bw, val) => bw.writeInt32LE(val), + (br) => br.readInt32LE(), + value + ); + }); + + it('should write and read Int32BE correctly', () => + { + const value = -2147483648; + testRoundTrip( + (bw, val) => bw.writeInt32BE(val), + (br) => br.readInt32BE(), + value + ); + }); + + it('should write and read Int64LE correctly', () => + { + const value = -9223372036854775808n; // Min Int64 + testRoundTrip( + (bw, val) => bw.writeInt64LE(val), + (br) => br.readInt64LE(), + value + ); + }); + + it('should write and read Int64BE correctly', () => + { + const value = -9223372036854775808n; // Min Int64 + testRoundTrip( + (bw, val) => bw.writeInt64BE(val), + (br) => br.readInt64BE(), + value + ); + }); + }); + + describe('Floating Point Numbers', () => + { + it('should write and read FloatLE correctly', () => + { + const value = 12345.6789; + testRoundTrip( + (bw, val) => bw.writeFloatLE(val), + (br) => br.readFloatLE(), + value + ); + }); + + it('should write and read FloatBE correctly', () => + { + const value = 12345.6789; + testRoundTrip( + (bw, val) => bw.writeFloatBE(val), + (br) => br.readFloatBE(), + value + ); + }); + + it('should write and read DoubleLE correctly', () => + { + const value = 123456789.123456789; + testRoundTrip( + (bw, val) => bw.writeDoubleLE(val), + (br) => br.readDoubleLE(), + value + ); + }); + + it('should write and read DoubleBE correctly', () => + { + const value = 123456789.123456789; + testRoundTrip( + (bw, val) => bw.writeDoubleBE(val), + (br) => br.readDoubleBE(), + value + ); + }); + }); + + describe('UUID', () => + { + it('should write and read UUID correctly', () => + { + const uuid = UUID.random(); + const bw = new BinaryWriter(); + bw.writeUUID(uuid); + const buf = bw.get(); + expect(buf.length).toBe(16); + + const br = new BinaryReader(buf); + const readUUID = br.readUUID(); + expect(readUUID.toString()).toBe(uuid.toString()); + }); + + it('should write and read zero UUID correctly', () => + { + const uuid = UUID.zero(); + const bw = new BinaryWriter(); + bw.writeUUID(uuid); + const buf = bw.get(); + expect(buf.length).toBe(16); + + const br = new BinaryReader(buf); + const readUUID = br.readUUID(); + expect(readUUID.toString()).toBe(uuid.toString()); + }); + }); + + describe('Date', () => + { + it('should write and read Date correctly', () => + { + const date = new Date(); + const bw = new BinaryWriter(); + bw.writeDate(date); + const buf = bw.get(); + expect(buf.length).toBe(8); + + const br = new BinaryReader(buf); + const readDate = br.readDate(); + expect(readDate.getTime()).toBe(date.getTime()); + }); + + it('should write and read epoch Date correctly', () => + { + const date = new Date(0); + const bw = new BinaryWriter(); + bw.writeDate(date); + const buf = bw.get(); + expect(buf.length).toBe(8); + + const br = new BinaryReader(buf); + const readDate = br.readDate(); + expect(readDate.getTime()).toBe(date.getTime()); + }); + }); + + describe('CString', () => + { + it('should write and read CString correctly', () => + { + const str = 'Hello, World!'; + const bw = new BinaryWriter(); + bw.writeCString(str); + const buf = bw.get(); + expect(buf.length).toBe(Buffer.byteLength(str, 'utf-8') + 1); + + const br = new BinaryReader(buf); + const readStr = br.readCString(); + expect(readStr).toBe(str); + }); + + it('should write and read empty CString correctly', () => + { + const str = ''; + const bw = new BinaryWriter(); + bw.writeCString(str); + const buf = bw.get(); + expect(buf.length).toBe(1); // Only null terminator + + const br = new BinaryReader(buf); + const readStr = br.readCString(); + expect(readStr).toBe(str); + }); + + it('should throw error when CString is not null-terminated during read', () => + { + const buf = Buffer.from('Test without null terminator', 'utf-8'); + const bw = new BinaryWriter(); + bw.writeBuffer(buf); + const result = () => + { + const br = new BinaryReader(bw.get()); + br.readCString(); + }; + expect(result).toThrow(RangeError); + }); + }); + + describe('String', () => + { + it('should write and read String correctly', () => + { + const str = 'Hello, BinaryWriter!'; + const bw = new BinaryWriter(); + bw.writeString(str); + const buf = bw.get(); + + const br = new BinaryReader(buf); + const readStr = br.readString(); + expect(readStr).toBe(str); + }); + + it('should write and read empty String correctly', () => + { + const str = ''; + const bw = new BinaryWriter(); + bw.writeString(str); + const buf = bw.get(); + + const br = new BinaryReader(buf); + const readStr = br.readString(); + expect(readStr).toBe(str); + }); + + it('should handle large strings correctly', () => + { + const str = 'a'.repeat(1000); + const bw = new BinaryWriter(); + bw.writeString(str); + const buf = bw.get(); + + const br = new BinaryReader(buf); + const readStr = br.readString(); + expect(readStr).toBe(str); + }); + }); + + describe('Buffer', () => + { + it('should write and read Buffer correctly', () => + { + const buffer = Buffer.from([0x00, 0xFF, 0xAA, 0x55]); + const bw = new BinaryWriter(); + bw.writeBuffer(buffer); + const buf = bw.get(); + expect(buf.length).toBe(buffer.length); + + const br = new BinaryReader(buf); + const readBuffer = br.readBuffer(buffer.length); + expect(readBuffer).toEqual(buffer); + }); + + it('should write partial Buffer correctly', () => + { + const buffer = Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05]); + const bw = new BinaryWriter(); + bw.writeBuffer(buffer, 1, 4); // [0x02, 0x03, 0x04] + const buf = bw.get(); + expect(buf.length).toBe(3); + expect(buf).toEqual(Buffer.from([0x02, 0x03, 0x04])); + + const br = new BinaryReader(buf); + const readBuffer = br.readBuffer(3); + expect(readBuffer).toEqual(Buffer.from([0x02, 0x03, 0x04])); + }); + + it('should handle empty Buffer correctly', () => + { + const buffer = Buffer.alloc(0); + const bw = new BinaryWriter(); + bw.writeBuffer(buffer); + const buf = bw.get(); + expect(buf.length).toBe(0); + + const br = new BinaryReader(buf); + const readBuffer = br.readBuffer(0); + expect(readBuffer.length).toBe(0); + }); + }); + + describe('Combined Writes', () => + { + it('should handle multiple writes correctly', () => + { + const uint8 = 255; + const int16 = -32768; + const float = 3.14; + const uuid = UUID.random(); + const date = new Date(); + const str = 'Test String'; + const buffer = Buffer.from([0xDE, 0xAD, 0xBE, 0xEF]); + + const bw = new BinaryWriter(); + bw.writeUInt8(uint8); + bw.writeInt16LE(int16); + bw.writeFloatBE(float); + bw.writeUUID(uuid); + bw.writeDate(date); + bw.writeString(str); + bw.writeBuffer(buffer); + + const buf = bw.get(); + + const br = new BinaryReader(buf); + expect(br.readUInt8()).toBe(uint8); + expect(br.readInt16LE()).toBe(int16); + expect(br.readFloatBE()).toBeCloseTo(float, 5); + const readUUID = br.readUUID(); + expect(readUUID.toString()).toBe(uuid.toString()); + const readDate = br.readDate(); + expect(readDate.getTime()).toBe(date.getTime()); + const readStr = br.readString(); + expect(readStr).toBe(str); + const readBuffer = br.readBuffer(buffer.length); + expect(readBuffer).toEqual(buffer); + }); + }); + + describe('Error Handling', () => + { + it('should throw error when writing negative UInt values', () => + { + const bw = new BinaryWriter(); + const writeNegativeUInt = () => bw.writeUInt8(-1); + expect(writeNegativeUInt).toThrow(RangeError); + }); + + it('should throw error when writing string with non-UTF8 characters', () => + { + const bw = new BinaryWriter(); + const invalidStr = '\u{D800}'; + const writeInvalidStr = () => bw.writeString(invalidStr); + expect(writeInvalidStr).not.toThrow(); + }); + }); + + describe('VarInt', () => + { + it('should write various size VarInts correctly', () => + { + const nums: [number, number | bigint, string][] = [ + [1, 0, 'AA=='], + [1, 31, 'Pg=='], + [2, 7166, '/G8='], + [3, 71665, '4t8I'], + [4, 7166512, '4OjqBg=='], + [5, 716651292, 'uOy5qwU='], + [6, 19928182913, 'gsL/vJQB'], + [7, 19928182913289, 'kuSbxPyHCQ=='], + [8, Number.MAX_SAFE_INTEGER, '/v///////x8='], + [14, 79228162514264337593543950000n, '4Pr//////////////z8='], + [19, 340282366920938463463374607431768211455n, '/v//////////////////////Bw=='] + ]; + + for (const num of nums) + { + const bw = new BinaryWriter(); + bw.writeVarInt(num[1]); + const buf = bw.get(); + expect(buf.length).toBe(num[0]); + expect(buf.toString('base64')).toBe(num[2]); + } + }); + + it('should write various negative size VarInts correctly', () => + { + const nums: [number, number | bigint, string][] = [ + [1, -24, 'Lw=='], + [2, -7166, '+28='], + [3, -71665, '4d8I'], + [4, -7166512, '3+jqBg=='], + [5, -716651292, 't+y5qwU='], + [6, -19928182913, 'gcL/vJQB'], + [7, -19928182913289, 'keSbxPyHCQ=='], + [8, Number.MIN_SAFE_INTEGER, '/f///////x8='], + [14, -39614081257132168796771975168n, '/////////////////x8='], + [19, -170141183460469231731687303715884105728n, '////////////////////////Aw=='] + ]; + + for (const num of nums) + { + const bw = new BinaryWriter(); + bw.writeVarInt(num[1]); + const buf = bw.get(); + expect(buf.length).toBe(num[0]); + expect(buf.toString('base64')).toBe(num[2]); + } + }); + }); +}); diff --git a/lib/classes/BinaryWriter.ts b/lib/classes/BinaryWriter.ts new file mode 100644 index 0000000..19aeacb --- /dev/null +++ b/lib/classes/BinaryWriter.ts @@ -0,0 +1,268 @@ +import type { UUID } from "./UUID"; +import type { Vector3 } from './Vector3'; + +export class BinaryWriter +{ + public segments: Buffer[] = []; + + public length(): number + { + let size = 0; + for (const seg of this.segments) + { + size += seg.length; + } + return size; + } + + public writeBuffer(buf: Buffer, start = 0, end = buf.length): void + { + this.segments.push(buf.subarray(start, end)); + } + + public writeVarInt(value: number | bigint): void + { + // noinspection JSUnusedAssignment + let n = 0n; + if (typeof value === 'number') + { + n = BigInt(value); + } + else + { + n = value; + } + const encoded = (n << 1n) ^ (n < 0n ? -1n : 0n); + const bytes: number[] = []; + let remaining = encoded; + + while (remaining >= 0x80n) + { + bytes.push(Number((remaining & 0x7Fn) | 0x80n)); + remaining >>= 7n; + } + bytes.push(Number(remaining)); + const encodedBuffer = Buffer.from(bytes); + this.writeBuffer(encodedBuffer); + } + + public get(): Buffer + { + return Buffer.concat(this.segments); + } + + // Write Methods + + public writeUInt8(value: number): void + { + const buf = Buffer.allocUnsafe(1); + buf.writeUInt8(value, 0); + this.segments.push(buf); + } + + public writeInt8(value: number): void + { + const buf = Buffer.allocUnsafe(1); + buf.writeInt8(value, 0); + this.segments.push(buf); + } + + public writeUInt16LE(value: number): void + { + const buf = Buffer.allocUnsafe(2); + buf.writeUInt16LE(value, 0); + this.segments.push(buf); + } + + public writeInt16LE(value: number): void + { + const buf = Buffer.allocUnsafe(2); + buf.writeInt16LE(value, 0); + this.segments.push(buf); + } + + public writeUInt16BE(value: number): void + { + const buf = Buffer.allocUnsafe(2); + buf.writeUInt16BE(value, 0); + this.segments.push(buf); + } + + public writeInt16BE(value: number): void + { + const buf = Buffer.allocUnsafe(2); + buf.writeInt16BE(value, 0); + this.segments.push(buf); + } + + public writeUInt32LE(value: number): void + { + const buf = Buffer.allocUnsafe(4); + buf.writeUInt32LE(value, 0); + this.segments.push(buf); + } + + public writeInt32LE(value: number): void + { + const buf = Buffer.allocUnsafe(4); + buf.writeInt32LE(value, 0); + this.segments.push(buf); + } + + public writeUInt32BE(value: number): void + { + const buf = Buffer.allocUnsafe(4); + buf.writeUInt32BE(value, 0); + this.segments.push(buf); + } + + public writeInt32BE(value: number): void + { + const buf = Buffer.allocUnsafe(4); + buf.writeInt32BE(value, 0); + this.segments.push(buf); + } + + public writeUInt64LE(value: bigint): void + { + const buf = Buffer.allocUnsafe(8); + buf.writeBigUInt64LE(value, 0); + this.segments.push(buf); + } + + public writeInt64LE(value: bigint): void + { + const buf = Buffer.allocUnsafe(8); + buf.writeBigInt64LE(value, 0); + this.segments.push(buf); + } + + public writeUInt64BE(value: bigint): void + { + const buf = Buffer.allocUnsafe(8); + buf.writeBigUInt64BE(value, 0); + this.segments.push(buf); + } + + public writeInt64BE(value: bigint): void + { + const buf = Buffer.allocUnsafe(8); + buf.writeBigInt64BE(value, 0); + this.segments.push(buf); + } + + public writeFloatLE(value: number): void + { + const buf = Buffer.allocUnsafe(4); + buf.writeFloatLE(value, 0); + this.segments.push(buf); + } + + public writeVector3F(vec: Vector3): void + { + const buf = Buffer.allocUnsafe(12); + buf.writeFloatLE(vec.x, 0); + buf.writeFloatLE(vec.y, 4); + buf.writeFloatLE(vec.z, 8); + this.segments.push(buf); + } + + public writeFloatBE(value: number): void + { + const buf = Buffer.allocUnsafe(4); + buf.writeFloatBE(value, 0); + this.segments.push(buf); + } + + public writeDoubleLE(value: number): void + { + const buf = Buffer.allocUnsafe(8); + buf.writeDoubleLE(value, 0); + this.segments.push(buf); + } + + public writeDoubleBE(value: number): void + { + const buf = Buffer.allocUnsafe(8); + buf.writeDoubleBE(value, 0); + this.segments.push(buf); + } + + public writeUUID(uuid: UUID): void + { + const buf = uuid.getBuffer(); + if (buf.length !== 16) + { + throw new Error('UUID must be 16 bytes long'); + } + this.segments.push(buf); + } + + public writeDate(date: Date): void + { + const timestamp = BigInt(date.getTime()); + this.writeUInt64LE(timestamp); + } + + public writeCString(str: string): void + { + const strBuf = Buffer.from(str, 'utf-8'); + this.writeBuffer(strBuf); + this.writeUInt8(0); // Null terminator + } + + public writeString(str: string): void + { + const strBuf = Buffer.from(str, 'utf-8'); + this.writeVarInt(BigInt(strBuf.length)); + this.writeBuffer(strBuf); + } + + public writeFixedString(str: string, len?: number): void + { + const buf = Buffer.from(str, 'utf-8'); + if (len !== undefined) + { + if (buf.length > len) + { + this.writeBuffer(buf.subarray(0, len)); + } + else if (buf.length < len) + { + const paddedBuffer = Buffer.alloc(len, 0); + buf.copy(paddedBuffer); + this.writeBuffer(paddedBuffer); + } + else + { + this.writeBuffer(buf); + } + } + else + { + this.writeBuffer(buf); + } + } + + public writeUUIDFromParts( + timeLow: number, + timeMid: number, + timeHiAndVersion: number, + clockSeq: number, + node: Buffer + ): void + { + if (node.length !== 6) + { + throw new Error('Node must be 6 bytes long'); + } + const buf = Buffer.allocUnsafe(16); + buf.writeUInt32LE(timeLow, 0); + buf.writeUInt16LE(timeMid, 4); + buf.writeUInt16LE(timeHiAndVersion, 6); + buf.writeUInt8((clockSeq >> 8) & 0xff, 8); + buf.writeUInt8(clockSeq & 0xff, 9); + node.copy(buf, 10); + this.segments.push(buf); + } +} \ No newline at end of file diff --git a/lib/classes/BitPack.ts b/lib/classes/BitPack.ts index b6dab60..6c89dbf 100644 --- a/lib/classes/BitPack.ts +++ b/lib/classes/BitPack.ts @@ -1,17 +1,18 @@ export class BitPack { - static MAX_BITS = 8; - static ON = [1]; - static OFF = [0]; + public static MAX_BITS = 8; + public static ON = [1]; + public static OFF = [0]; private bitPos = 0; + private bytePos: number; - constructor(private Data: Buffer, private bytePos: number) + public constructor(private readonly Data: Buffer, bytePos: number) { - + this.bytePos = bytePos; } - get BytePos(): number + public get BytePos(): number { if (this.bytePos !== 0 && this.bitPos === 0) { @@ -23,56 +24,56 @@ export class BitPack } } - get BitPos(): number + public get BitPos(): number { return this.bitPos; } - UnpackFloat(): number + public UnpackFloat(): number { const output = this.UnpackBitsBuffer(32); return output.readFloatLE(0); } - UnpackBits(count: number): number + public UnpackBits(count: number): number { const output = this.UnpackBitsBuffer(count); return output.readInt32LE(0); } - UnpackUBits(count: number): number + public UnpackUBits(count: number): number { const output = this.UnpackBitsBuffer(count); return output.readUInt32LE(0); } - UnpsckShort(): number + public UnpsckShort(): number { return this.UnpackBits(16); } - UnpackUShort(): number + public UnpackUShort(): number { return this.UnpackUBits(16); } - UnpackInt(): number + public UnpackInt(): number { return this.UnpackBits(32); } - UnpackUInt(): number + public UnpackUInt(): number { return this.UnpackUBits(32); } - UnpackByte(): number + public UnpackByte(): number { const output = this.UnpackBitsBuffer(8); return output[0]; } - UnpackFixed(signed: boolean, intBits: number, fracBits: number): number + public UnpackFixed(signed: boolean, intBits: number, fracBits: number): number { let totalBits = intBits + fracBits; @@ -107,7 +108,7 @@ export class BitPack return fixedVal; } - UnpackBitsBuffer(totalCount: number): Buffer + public UnpackBitsBuffer(totalCount: number): Buffer { const newBuf = Buffer.alloc(4, 0); let count = 0; diff --git a/lib/classes/BuildMap.ts b/lib/classes/BuildMap.ts index 11f6118..b7de37f 100644 --- a/lib/classes/BuildMap.ts +++ b/lib/classes/BuildMap.ts @@ -1,6 +1,6 @@ -import { AssetMap } from './AssetMap'; -import { GameObject } from './public/GameObject'; +import type { GameObject } from './public/GameObject'; import { Vector3 } from './Vector3'; +import type { AssetRegistry } from './AssetRegistry'; export class BuildMap { @@ -8,7 +8,7 @@ export class BuildMap public primReservoir: GameObject[] = []; public rezLocation: Vector3 = Vector3.getZero(); - constructor(public assetMap: AssetMap, public callback: (map: AssetMap) => void, public costOnly = false) + public constructor(public readonly assetMap: AssetRegistry, public readonly callback: (registry: AssetRegistry) => Promise, public readonly costOnly = false) { } diff --git a/lib/classes/Caps.ts b/lib/classes/Caps.ts index 04c97c7..0ae09c8 100644 --- a/lib/classes/Caps.ts +++ b/lib/classes/Caps.ts @@ -1,33 +1,35 @@ -import { Subscription } from 'rxjs'; +import type { Subscription } from 'rxjs'; import { EventQueueClient } from './EventQueueClient'; -import { UUID } from './UUID'; -import { ClientEvents } from './ClientEvents'; -import { Agent } from './Agent'; +import type { UUID } from './UUID'; +import type { ClientEvents } from './ClientEvents'; +import type { Agent } from './Agent'; import { Subject } from 'rxjs'; -import { ICapResponse } from './interfaces/ICapResponse'; -import { HTTPAssets } from '../enums/HTTPAssets'; +import type { ICapResponse } from './interfaces/ICapResponse'; import * as LLSD from '@caspertech/llsd'; import * as url from 'url'; import got from 'got'; +import { AssetType } from '../enums/AssetType'; +import { AssetTypeRegistry } from './AssetTypeRegistry'; export class Caps { - static CAP_INVOCATION_DELAY_MS: { [key: string]: number } = { + public eventQueueClient: EventQueueClient | null = null; + + private static readonly CAP_INVOCATION_DELAY_MS: Record = { 'NewFileAgentInventory': 2000, 'FetchInventory2': 200 }; - private onGotSeedCap: Subject = new Subject(); + private readonly onGotSeedCap: Subject = new Subject(); private gotSeedCap = false; - private capabilities: { [key: string]: string } = {}; - private clientEvents: ClientEvents; - private agent: Agent; + private capabilities: Record = {}; + private readonly clientEvents: ClientEvents; + private readonly agent: Agent; private active = false; - private timeLastCapExecuted: { [key: string]: number } = {}; - eventQueueClient: EventQueueClient | null = null; + private timeLastCapExecuted: Record = {}; - constructor(agent: Agent, seedURL: string, clientEvents: ClientEvents) + public constructor(agent: Agent, seedURL: string, clientEvents: ClientEvents) { this.agent = agent; this.clientEvents = clientEvents; @@ -150,29 +152,30 @@ export class Caps this.capabilities = LLSD.LLSD.parseXML(resp.body); this.gotSeedCap = true; this.onGotSeedCap.next(); - if (this.capabilities['EventQueueGet']) + if (this.capabilities.EventQueueGet) { if (this.eventQueueClient !== null) { - this.eventQueueClient.shutdown(); + void this.eventQueueClient.shutdown(); } this.eventQueueClient = new EventQueueClient(this.agent, this, this.clientEvents); } - }).catch((err) => + }).catch((err: unknown) => { console.error('Error getting seed capability'); console.error(err); }); } - public async downloadAsset(uuid: UUID, type: HTTPAssets): Promise + public async downloadAsset(uuid: UUID, type: AssetType): Promise { - if (type === HTTPAssets.ASSET_LSL_TEXT || type === HTTPAssets.ASSET_NOTECARD) + if (type === AssetType.LSLText || type === AssetType.Notecard) { throw new Error('Invalid Syntax'); } + const capURL = await this.getCapability('ViewerAsset'); - const assetURL = capURL + '/?' + type + '_id=' + uuid.toString(); + const assetURL = capURL + '/?' + AssetTypeRegistry.getTypeName(type) + '_id=' + uuid.toString(); const response = await got.get(assetURL, { https: { @@ -190,7 +193,7 @@ export class Caps return response.body; } - public async requestPost(capURL: string, data: string | Buffer, contentType: string) + public async requestPost(capURL: string, data: string | Buffer, contentType: string): Promise<{ status: number, body: string }> { const response = await got.post(capURL, { headers: { @@ -244,236 +247,154 @@ export class Caps return { status: response.statusCode, body: response.body }; } - waitForSeedCapability(): Promise - { - return new Promise((resolve) => - { - if (this.gotSeedCap) - { - resolve(); - } - else - { - const sub: Subscription = this.onGotSeedCap.subscribe(() => - { - sub.unsubscribe(); - resolve(); - }); - } - }); - } - - async isCapAvailable(capability: string): Promise + public async isCapAvailable(capability: string): Promise { await this.waitForSeedCapability(); return (this.capabilities[capability] !== undefined); } - getCapability(capability: string): Promise + public async getCapability(capability: string): Promise { - return new Promise((resolve, reject) => + if (!this.active) { - if (!this.active) + throw new Error('Requesting getCapability to an inactive Caps instance'); + } + await this.waitForSeedCapability(); + if (this.capabilities[capability] !== undefined) + { + return this.capabilities[capability]; + } + throw new Error('Capability ' + capability + ' not available') + } + + public async capsRequestUpload(capURL: string, data: Buffer): Promise + { + const resp: ICapResponse = await this.requestPost(capURL, data, 'application/octet-stream'); + try + { + return LLSD.LLSD.parseXML(resp.body); + } + catch (err) + { + if (resp.status === 201) { - reject(new Error('Requesting getCapability to an inactive Caps instance')); - return; + return resp.body; } - this.waitForSeedCapability().then(() => + else if (resp.status === 403) { - if (this.capabilities[capability]) - { - resolve(this.capabilities[capability]); - } - else - { - reject(new Error('Capability ' + capability + ' not available')); - } - }); - }); + throw new Error('Access Denied'); + } + throw(err); + } } - public capsRequestUpload(capURL: string, data: Buffer): Promise + public async capsPerformXMLPost(capURL: string, data: any): Promise { - return new Promise((resolve, reject) => + const xml = LLSD.LLSD.formatXML(data); + const resp: ICapResponse = await this.requestPost(capURL, xml, 'application/llsd+xml'); + try { - this.requestPost(capURL, data, 'application/octet-stream').then((resp: ICapResponse) => - { - try - { - resolve(LLSD.LLSD.parseXML(resp.body)); - } - catch (err) - { - if (resp.status === 201) - { - resolve({}); - } - else if (resp.status === 403) - { - reject(new Error('Access Denied')); - } - else - { - reject(err); - } - } - }).catch((err) => - { - console.error(err); - reject(err); - }); - }); - } - - private waitForCapTimeout(capName: string): Promise - { - return new Promise((resolve) => + return LLSD.LLSD.parseXML(resp.body); + } + catch (_err: unknown) { - if (!Caps.CAP_INVOCATION_DELAY_MS[capName]) + if (resp.status === 201) { - resolve(); + return {}; + } + else if (resp.status === 403) + { + throw new Error('Access Denied'); + } + else if (resp.status === 404) + { + throw new Error('Not found'); } else { - if (!this.timeLastCapExecuted[capName] || this.timeLastCapExecuted[capName] < (new Date().getTime() - Caps.CAP_INVOCATION_DELAY_MS[capName])) - { - this.timeLastCapExecuted[capName] = new Date().getTime(); - } - else - { - this.timeLastCapExecuted[capName] += Caps.CAP_INVOCATION_DELAY_MS[capName]; - } - const timeToWait = this.timeLastCapExecuted[capName] - new Date().getTime(); - if (timeToWait > 0) - { - setTimeout(() => - { - resolve(); - }, timeToWait); - } - else - { - resolve(); - } + // eslint-disable-next-line @typescript-eslint/only-throw-error + throw resp.body; } - }); + } } - public capsPerformXMLPost(capURL: string, data: any): Promise + public async capsPerformXMLPut(capURL: string, data: any): Promise { - return new Promise(async(resolve, reject) => + const xml = LLSD.LLSD.formatXML(data); + const resp: ICapResponse = await this.requestPut(capURL, xml, 'application/llsd+xml'); + try { - const xml = LLSD.LLSD.formatXML(data); - this.requestPost(capURL, xml, 'application/llsd+xml').then(async(resp: ICapResponse) => + return LLSD.LLSD.parseXML(resp.body); + } + catch (err) + { + if (resp.status === 201) { - let result: any = null; - try - { - result = LLSD.LLSD.parseXML(resp.body); - resolve(result); - } - catch (err) - { - if (resp.status === 201) - { - resolve({}); - } - else if (resp.status === 403) - { - reject(new Error('Access Denied')); - } - else if (resp.status === 404) - { - reject(new Error('Not found')); - } - else - { - reject(resp.body); - } - } - }).catch((err) => + return {}; + } + else if (resp.status === 403) { - console.error(err); - reject(err); - }); - }); + throw new Error('Access Denied'); + } + else + { + throw err; + } + } } - capsPerformXMLPut(capURL: string, data: any): Promise + public async capsPerformXMLGet(capURL: string): Promise { - return new Promise(async(resolve, reject) => + const resp = await this.requestGet(capURL); + try { - const xml = LLSD.LLSD.formatXML(data); - this.requestPut(capURL, xml, 'application/llsd+xml').then((resp: ICapResponse) => + return LLSD.LLSD.parseXML(resp.body); + } + catch (err) + { + if (resp.status === 201) { - let result: any = null; - try - { - result = LLSD.LLSD.parseXML(resp.body); - resolve(result); - } - catch (err) - { - if (resp.status === 201) - { - resolve({}); - } - else if (resp.status === 403) - { - reject(new Error('Access Denied')); - } - else - { - reject(err); - } - } - }).catch((err) => + return {}; + } + else if (resp.status === 403) { - console.error(err); - reject(err); - }); - }); + throw new Error('Access Denied'); + } + else + { + throw err; + } + } } - capsPerformXMLGet(capURL: string): Promise + public async capsPerformGet(capURL: string): Promise { - return new Promise(async(resolve, reject) => + const resp = await this.requestGet(capURL); + try { - this.requestGet(capURL).then((resp: ICapResponse) => + return resp.body; + } + catch (err) + { + if (resp.status === 201) { - let result: any = null; - try - { - result = LLSD.LLSD.parseXML(resp.body); - resolve(result); - } - catch (err) - { - if (resp.status === 201) - { - resolve({}); - } - else if (resp.status === 403) - { - reject(new Error('Access Denied')); - } - else - { - reject(err); - } - } - }).catch((err) => + return ''; + } + else if (resp.status === 403) { - console.error(err); - reject(err); - }); - }); + throw new Error('Access Denied'); + } + else + { + throw err; + } + } } - async capsGetXML(capability: string | [string, { [key: string]: string }]): Promise + public async capsGetXML(capability: string | [string, Record]): Promise { let capName = ''; - let queryParams: { [key: string]: string } = {}; + let queryParams: Record = {}; if (typeof capability === 'string') { capName = capability; @@ -508,10 +429,48 @@ export class Caps } } - async capsPostXML(capability: string | [string, { [key: string]: string }], data: any): Promise + public async capsGetString(capability: string | [string, Record]): Promise { let capName = ''; - let queryParams: { [key: string]: string } = {}; + let queryParams: Record = {}; + if (typeof capability === 'string') + { + capName = capability; + } + else + { + capName = capability[0]; + queryParams = capability[1]; + } + + await this.waitForCapTimeout(capName); + + let capURL = await this.getCapability(capName); + if (Object.keys(queryParams).length > 0) + { + const parsedURL = url.parse(capURL, true); + for (const key of Object.keys(queryParams)) + { + parsedURL.query[key] = queryParams[key]; + } + capURL = url.format(parsedURL); + } + try + { + return await this.capsPerformGet(capURL); + } + catch (error) + { + console.log('Error with cap ' + capName); + console.log(error); + throw error; + } + } + + public async capsPostXML(capability: string | [string, Record], data: any): Promise + { + let capName = ''; + let queryParams: Record = {}; if (typeof capability === 'string') { capName = capability; @@ -546,10 +505,10 @@ export class Caps } } - async capsPutXML(capability: string | [string, { [key: string]: string }], data: any): Promise + public async capsPutXML(capability: string | [string, Record], data: any): Promise { let capName = ''; - let queryParams: { [key: string]: string } = {}; + let queryParams: Record = {}; if (typeof capability === 'string') { capName = capability; @@ -584,13 +543,66 @@ export class Caps } } - shutdown(): void + public shutdown(): void { this.onGotSeedCap.complete(); if (this.eventQueueClient) { - this.eventQueueClient.shutdown(); + void this.eventQueueClient.shutdown(); } this.active = false; } + + public async waitForSeedCapability(): Promise + { + return new Promise((resolve) => + { + if (this.gotSeedCap) + { + resolve(); + } + else + { + const sub: Subscription = this.onGotSeedCap.subscribe(() => + { + sub.unsubscribe(); + resolve(); + }); + } + }); + } + + private async waitForCapTimeout(capName: string): Promise + { + return new Promise((resolve) => + { + if (!Caps.CAP_INVOCATION_DELAY_MS[capName]) + { + resolve(); + } + else + { + if (!this.timeLastCapExecuted[capName] || this.timeLastCapExecuted[capName] < (new Date().getTime() - Caps.CAP_INVOCATION_DELAY_MS[capName])) + { + this.timeLastCapExecuted[capName] = new Date().getTime(); + } + else + { + this.timeLastCapExecuted[capName] += Caps.CAP_INVOCATION_DELAY_MS[capName]; + } + const timeToWait = this.timeLastCapExecuted[capName] - new Date().getTime(); + if (timeToWait > 0) + { + setTimeout(() => + { + resolve(); + }, timeToWait); + } + else + { + resolve(); + } + } + }); + } } diff --git a/lib/classes/Circuit.ts b/lib/classes/Circuit.ts index 6dc4b8b..0650a48 100644 --- a/lib/classes/Circuit.ts +++ b/lib/classes/Circuit.ts @@ -1,13 +1,13 @@ import { UUID } from './UUID'; import * as dgram from 'dgram'; -import { Socket } from 'dgram'; +import type { Socket } from 'dgram'; import { Packet } from './Packet'; -import { MessageBase } from './MessageBase'; +import type { MessageBase } from './MessageBase'; import { PacketAckMessage } from './messages/PacketAck'; import { Message } from '../enums/Message'; -import { StartPingCheckMessage } from './messages/StartPingCheck'; +import type { StartPingCheckMessage } from './messages/StartPingCheck'; import { CompletePingCheckMessage } from './messages/CompletePingCheck'; -import { Subscription } from 'rxjs'; +import type { Subscription } from 'rxjs'; import { filter } from 'rxjs/operators' import { FilterResponse } from '../enums/FilterResponse'; import { Subject } from 'rxjs'; @@ -15,49 +15,46 @@ import { TimeoutError } from './TimeoutError'; import { RequestXferMessage } from './messages/RequestXfer'; import { SendXferPacketMessage } from './messages/SendXferPacket'; import { ConfirmXferPacketMessage } from './messages/ConfirmXferPacket'; -import { AbortXferMessage } from './messages/AbortXfer'; +import type { AbortXferMessage } from './messages/AbortXfer'; import { PacketFlags } from '../enums/PacketFlags'; -import { AssetType } from '../enums/AssetType'; +import type { AssetType } from '../enums/AssetType'; import { Utils } from './Utils'; -import * as Long from 'long'; +import type * as Long from 'long'; export class Circuit { - secureSessionID: UUID; - sessionID: UUID; - circuitCode: number; - udpBlacklist: string[]; - timestamp: number; - client: Socket | null = null; - port: number; - ipAddress: string; - sequenceNumber = 0; + public secureSessionID: UUID; + public sessionID: UUID; + public circuitCode: number; + public udpBlacklist: string[]; + public timestamp: number; + public port: number; + public ipAddress: string; + private client: Socket | null = null; + private sequenceNumber = 0; - awaitingAck: { - [key: number]: { - packet: Packet, - timeout: NodeJS.Timeout, - sent: number - } - } = {}; - receivedPackets: { - [key: number]: NodeJS.Timeout - } = {}; - active = false; + private readonly awaitingAck = new Map(); - private onPacketReceived: Subject; - private onAckReceived: Subject; + private readonly receivedPackets = new Map(); + private active = false; - constructor() + private readonly onPacketReceived: Subject; + private readonly onAckReceived: Subject; + + public constructor() { this.onPacketReceived = new Subject(); this.onAckReceived = new Subject(); } - subscribeToMessages(ids: number[], callback: (packet: Packet) => void): Subscription + public subscribeToMessages(ids: number[], callback: (packet: Packet) => Promise | void): Subscription { - const lookupObject: { [key: number]: boolean } = {}; + const lookupObject: Record = {}; for (const id of ids) { lookupObject[id] = true; @@ -69,7 +66,7 @@ export class Circuit }) as any).subscribe(callback as any); } - sendMessage(message: MessageBase, flags: PacketFlags): number + public sendMessage(message: MessageBase, flags: PacketFlags): number { if (!this.active) { @@ -83,46 +80,7 @@ export class Circuit return packet.sequenceNumber; } - private sendXferPacket(xferID: Long, packetID: number, data: Buffer, pos: { position: number }): void - { - const sendXfer = new SendXferPacketMessage(); - let final = false; - sendXfer.XferID = { - ID: xferID, - Packet: packetID - }; - const packetLength = Math.min(data.length - pos.position, 1000); - if (packetLength < 1000) - { - sendXfer.XferID.Packet = (sendXfer.XferID.Packet | 0x80000000) >>> 0; - final = true; - } - if (packetID === 0) - { - const packet = Buffer.allocUnsafe(packetLength + 4); - packet.writeUInt32LE(data.length, 0); - data.copy(packet, 4, 0, packetLength); - sendXfer.DataPacket = { - Data: packet - }; - pos.position += packetLength; - } - else - { - const packet = data.slice(pos.position, pos.position + packetLength); - sendXfer.DataPacket = { - Data: packet - }; - pos.position += packetLength; - } - this.sendMessage(sendXfer, PacketFlags.Reliable); - if (final) - { - pos.position = -1; - } - } - - XferFileUp(xferID: Long, data: Buffer): Promise + public async XferFileUp(xferID: Long, data: Buffer): Promise { return new Promise((resolve, reject) => { @@ -159,7 +117,10 @@ export class Circuit subs.unsubscribe(); reject(new Error('Transfer aborted')); } + break; } + default: + break; } }); @@ -172,13 +133,13 @@ export class Circuit }); } - XferFileDown(fileName: string, deleteOnCompletion: boolean, useBigPackets: boolean, vFileID: UUID, vFileType: AssetType, fromCache: boolean): Promise + public async XferFileDown(fileName: string, deleteOnCompletion: boolean, useBigPackets: boolean, vFileID: UUID, vFileType: AssetType, fromCache: boolean): Promise { return new Promise((resolve, reject) => { let subscription: null | Subscription = null; let timeout: NodeJS.Timeout | null = null; - const receivedChunks: { [key: number]: Buffer } = {}; + const receivedChunks: Record = {}; const resetTimeout = function(): void { if (timeout !== null) @@ -246,7 +207,7 @@ export class Circuit if (firstPacket) { dataSize = message.DataPacket.Data.readUInt32LE(0); - receivedChunks[packetNum] = message.DataPacket.Data.slice(4); + receivedChunks[packetNum] = message.DataPacket.Data.subarray(4); firstPacket = false; } else @@ -299,27 +260,14 @@ export class Circuit } break; } + default: + break; } }); }); } - resend(sequenceNumber: number): void - { - if (!this.active) - { - console.log('Resend triggered, but circuit is not active!'); - return; - } - if (this.awaitingAck[sequenceNumber]) - { - const toResend: Packet = this.awaitingAck[sequenceNumber].packet; - toResend.packetFlags = toResend.packetFlags | PacketFlags.Resent; - this.sendPacket(toResend); - } - } - - waitForAck(ack: number, timeout: number): Promise + public async waitForAck(ack: number, timeout: number): Promise { return new Promise((resolve, reject) => { @@ -359,17 +307,13 @@ export class Circuit }); } - init(): void + public init(): void { if (this.client !== null) { this.client.close(); } this.client = dgram.createSocket('udp4'); - this.client.on('listening', () => - { - - }); this.client.on('message', (message, remote) => { @@ -379,25 +323,28 @@ export class Circuit } }); - this.client.on('error', () => - { - - }); this.active = true; } - shutdown(): void + public shutdown(): void { - for (const sequenceNumber of Object.keys(this.awaitingAck)) + for (const seqKey of this.awaitingAck.keys()) { - clearTimeout(this.awaitingAck[parseInt(sequenceNumber, 10)].timeout); - delete this.awaitingAck[parseInt(sequenceNumber, 10)]; + const ack = this.awaitingAck.get(seqKey); + if (ack !== undefined) + { + clearTimeout(ack.timeout); + } + this.awaitingAck.delete(seqKey); } - for (const sequenceNumber of Object.keys(this.receivedPackets)) + for (const seqKey of this.receivedPackets.keys()) { - const seq: number = parseInt(sequenceNumber, 10); - clearTimeout(this.receivedPackets[seq]); - delete this.receivedPackets[seq]; + const s = this.receivedPackets.get(seqKey); + if (s !== undefined) + { + clearTimeout(s); + } + this.receivedPackets.delete(seqKey); } if (this.client !== null) { @@ -409,7 +356,14 @@ export class Circuit this.active = false; } - waitForMessage(id: Message, timeout: number, messageFilter?: (message: T) => FilterResponse): Promise + public async sendAndWaitForMessage(message: MessageBase, flags: PacketFlags, id: Message, timeout: number, messageFilter?: (message: T) => FilterResponse): Promise + { + const awaiter = this.waitForMessage(id, timeout, messageFilter); + this.sendMessage(message, flags); + return awaiter; + } + + public async waitForMessage(id: Message, timeout: number, messageFilter?: (message: T) => FilterResponse): Promise { return new Promise((resolve, reject) => { @@ -421,7 +375,7 @@ export class Circuit subscription: null }; - const timeoutFunc = () => + const timeoutFunc = (): void => { if (handleObj.subscription !== null) { @@ -480,16 +434,94 @@ export class Circuit }); } - sendPacket(packet: Packet): void + public getOldestUnacked(): number + { + let result = 0; + let oldest = -1; + + const keys: number[] = Array.from(this.awaitingAck.keys()); + + for (const nSeq of keys) + { + const awaiting = this.awaitingAck.get(nSeq); + if (awaiting !== undefined) + { + if (oldest === -1 || awaiting.sent < oldest) + { + result = nSeq; + oldest = awaiting.sent; + } + } + } + + return result; + } + + private sendXferPacket(xferID: Long, packetID: number, data: Buffer, pos: { position: number }): void + { + const sendXfer = new SendXferPacketMessage(); + let final = false; + sendXfer.XferID = { + ID: xferID, + Packet: packetID + }; + const packetLength = Math.min(data.length - pos.position, 1000); + if (packetLength < 1000) + { + sendXfer.XferID.Packet = (sendXfer.XferID.Packet | 0x80000000) >>> 0; + final = true; + } + if (packetID === 0) + { + const packet = Buffer.allocUnsafe(packetLength + 4); + packet.writeUInt32LE(data.length, 0); + data.copy(packet, 4, 0, packetLength); + sendXfer.DataPacket = { + Data: packet + }; + pos.position += packetLength; + } + else + { + const packet = data.subarray(pos.position, pos.position + packetLength); + sendXfer.DataPacket = { + Data: packet + }; + pos.position += packetLength; + } + this.sendMessage(sendXfer, PacketFlags.Reliable); + if (final) + { + pos.position = -1; + } + } + + private resend(sequenceNumber: number): void + { + if (!this.active) + { + console.log('Resend triggered, but circuit is not active!'); + return; + } + const waiting = this.awaitingAck.get(sequenceNumber); + if (waiting) + { + const toResend: Packet = waiting.packet; + toResend.packetFlags = toResend.packetFlags | PacketFlags.Resent; + this.sendPacket(toResend); + } + } + + private sendPacket(packet: Packet): void { if (packet.packetFlags & PacketFlags.Reliable) { - this.awaitingAck[packet.sequenceNumber] = - { - packet: packet, - timeout: setTimeout(this.resend.bind(this, packet.sequenceNumber), 1000), - sent: new Date().getTime() - }; + this.awaitingAck.set(packet.sequenceNumber, + { + packet: packet, + timeout: setTimeout(this.resend.bind(this, packet.sequenceNumber), 1000), + sent: new Date().getTime() + }); } let dataToSend: Buffer = Buffer.allocUnsafe(packet.getSize()); dataToSend = packet.writeToBuffer(dataToSend, 0); @@ -497,8 +529,8 @@ export class Circuit { this.client.send(dataToSend, 0, dataToSend.length, this.port, this.ipAddress, (_err, _bytes) => { - - }) + // nothing + }); } else { @@ -506,17 +538,18 @@ export class Circuit } } - ackReceived(sequenceNumber: number): void + private ackReceived(sequenceNumber: number): void { - if (this.awaitingAck[sequenceNumber]) + const awaiting = this.awaitingAck.get(sequenceNumber); + if (awaiting !== undefined) { - clearTimeout(this.awaitingAck[sequenceNumber].timeout); - delete this.awaitingAck[sequenceNumber]; + clearTimeout(awaiting.timeout); + this.awaitingAck.delete(sequenceNumber); } this.onAckReceived.next(sequenceNumber); } - sendAck(sequenceNumber: number): void + private sendAck(sequenceNumber: number): void { const msg: PacketAckMessage = new PacketAckMessage(); msg.Packets = [ @@ -527,36 +560,13 @@ export class Circuit this.sendMessage(msg, 0 as PacketFlags); } - getOldestUnacked(): number - { - let result = 0; - let oldest = -1; - - const keys: string[] = Object.keys(this.awaitingAck); - - for (const seqID of keys) - { - const nSeq = parseInt(seqID, 10); - if (oldest === -1 || this.awaitingAck[nSeq].sent < oldest) - { - result = nSeq; - oldest = this.awaitingAck[nSeq].sent; - } - } - - return result; - } - - expireReceivedPacket(sequenceNumber: number): void + private expireReceivedPacket(sequenceNumber: number): void { // Enough time has elapsed that we can forget about this packet - if (this.receivedPackets[sequenceNumber]) - { - delete this.receivedPackets[sequenceNumber]; - } + this.receivedPackets.delete(sequenceNumber); } - receivedPacket(bytes: Buffer): void + private receivedPacket(bytes: Buffer): void { const packet = new Packet(); try @@ -569,14 +579,15 @@ export class Circuit return; } - if (this.receivedPackets[packet.sequenceNumber]) + const received = this.receivedPackets.get(packet.sequenceNumber); + if (received) { - clearTimeout(this.receivedPackets[packet.sequenceNumber]); - this.receivedPackets[packet.sequenceNumber] = setTimeout(this.expireReceivedPacket.bind(this, packet.sequenceNumber), 10000); + clearTimeout(received); + this.receivedPackets.set(packet.sequenceNumber, setTimeout(this.expireReceivedPacket.bind(this, packet.sequenceNumber), 10000)); this.sendAck(packet.sequenceNumber); return; } - this.receivedPackets[packet.sequenceNumber] = setTimeout(this.expireReceivedPacket.bind(this, packet.sequenceNumber), 10000); + this.receivedPackets.set(packet.sequenceNumber, setTimeout(this.expireReceivedPacket.bind(this, packet.sequenceNumber), 10000)); // console.log('<--- ' + packet.message.name); diff --git a/lib/classes/ClientCommands.ts b/lib/classes/ClientCommands.ts index f71a7cb..2c7da3f 100644 --- a/lib/classes/ClientCommands.ts +++ b/lib/classes/ClientCommands.ts @@ -1,6 +1,6 @@ -import { Region } from './Region'; -import { Agent } from './Agent'; -import { Bot } from '../Bot'; +import type { Region } from './Region'; +import type { Agent } from './Agent'; +import type { Bot } from '../Bot'; import { NetworkCommands } from './commands/NetworkCommands'; import { AssetCommands } from './commands/AssetCommands'; import { TeleportCommands } from './commands/TeleportCommands'; @@ -29,7 +29,7 @@ export class ClientCommands public inventory: InventoryCommands; public movement: MovementCommands; - constructor(region: Region, agent: Agent, bot: Bot) + public constructor(region: Region, agent: Agent, bot: Bot) { this.network = new NetworkCommands(region, agent, bot); this.asset = new AssetCommands(region, agent, bot); @@ -45,7 +45,7 @@ export class ClientCommands this.movement = new MovementCommands(region, agent, bot); } - shutdown(): void + public shutdown(): void { this.network.shutdown(); this.asset.shutdown(); diff --git a/lib/classes/ClientEvents.ts b/lib/classes/ClientEvents.ts index 06d0563..abf5723 100644 --- a/lib/classes/ClientEvents.ts +++ b/lib/classes/ClientEvents.ts @@ -1,72 +1,170 @@ -import { Subject } from 'rxjs'; -import { GroupChatClosedEvent } from '../events/GroupChatClosedEvent'; -import { NewObjectEvent } from '../events/NewObjectEvent'; -import { ObjectUpdatedEvent } from '../events/ObjectUpdatedEvent'; -import { ObjectKilledEvent } from '../events/ObjectKilledEvent'; -import { SelectedObjectEvent } from '../events/SelectedObjectEvent'; -import { ChatEvent } from '../events/ChatEvent'; -import { InstantMessageEvent } from '../events/InstantMessageEvent'; -import { GroupInviteEvent } from '../events/GroupInviteEvent'; -import { FriendRequestEvent } from '../events/FriendRequestEvent'; -import { InventoryOfferedEvent } from '../events/InventoryOfferedEvent'; -import { LureEvent } from '../events/LureEvent'; -import { TeleportEvent } from '../events/TeleportEvent'; -import { DisconnectEvent } from '../events/DisconnectEvent'; -import { GroupChatEvent } from '../events/GroupChatEvent'; -import { GroupNoticeEvent } from '../events/GroupNoticeEvent'; -import { GroupChatSessionJoinEvent } from '../events/GroupChatSessionJoinEvent'; -import { GroupChatSessionAgentListEvent } from '../events/GroupChatSessionAgentListEvent'; -import { FriendResponseEvent } from '../events/FriendResponseEvent'; -import { ScriptDialogEvent } from '../events/ScriptDialogEvent'; -import { EventQueueStateChangeEvent } from '../events/EventQueueStateChangeEvent'; -import { FriendOnlineEvent } from '../events/FriendOnlineEvent'; -import { FriendRightsEvent } from '../events/FriendRightsEvent'; -import { FriendRemovedEvent } from '../events/FriendRemovedEvent'; -import { ObjectPhysicsDataEvent } from '../events/ObjectPhysicsDataEvent'; -import { ParcelPropertiesEvent } from '../events/ParcelPropertiesEvent'; -import { ObjectResolvedEvent } from '../events/ObjectResolvedEvent'; -import { Avatar } from './public/Avatar'; -import { BulkUpdateInventoryEvent } from '../events/BulkUpdateInventoryEvent'; -import { InventoryResponseEvent } from '../events/InventoryResponseEvent'; -import { LandStatsEvent } from '../events/LandStatsEvent'; -import { SimStatsEvent } from '../events/SimStatsEvent'; -import { BalanceUpdatedEvent } from '../events/BalanceUpdatedEvent'; +import { Subject, type Subscription } from 'rxjs'; +import type { GroupChatClosedEvent } from '../events/GroupChatClosedEvent'; +import type { NewObjectEvent } from '../events/NewObjectEvent'; +import type { ObjectUpdatedEvent } from '../events/ObjectUpdatedEvent'; +import type { ObjectKilledEvent } from '../events/ObjectKilledEvent'; +import type { SelectedObjectEvent } from '../events/SelectedObjectEvent'; +import type { ChatEvent } from '../events/ChatEvent'; +import type { InstantMessageEvent } from '../events/InstantMessageEvent'; +import type { GroupInviteEvent } from '../events/GroupInviteEvent'; +import type { FriendRequestEvent } from '../events/FriendRequestEvent'; +import type { InventoryOfferedEvent } from '../events/InventoryOfferedEvent'; +import type { LureEvent } from '../events/LureEvent'; +import type { TeleportEvent } from '../events/TeleportEvent'; +import type { DisconnectEvent } from '../events/DisconnectEvent'; +import type { GroupChatEvent } from '../events/GroupChatEvent'; +import type { GroupNoticeEvent } from '../events/GroupNoticeEvent'; +import type { GroupChatSessionJoinEvent } from '../events/GroupChatSessionJoinEvent'; +import type { GroupChatSessionAgentListEvent } from '../events/GroupChatSessionAgentListEvent'; +import type { FriendResponseEvent } from '../events/FriendResponseEvent'; +import type { ScriptDialogEvent } from '../events/ScriptDialogEvent'; +import type { EventQueueStateChangeEvent } from '../events/EventQueueStateChangeEvent'; +import type { FriendOnlineEvent } from '../events/FriendOnlineEvent'; +import type { FriendRightsEvent } from '../events/FriendRightsEvent'; +import type { FriendRemovedEvent } from '../events/FriendRemovedEvent'; +import type { ObjectPhysicsDataEvent } from '../events/ObjectPhysicsDataEvent'; +import type { ParcelPropertiesEvent } from '../events/ParcelPropertiesEvent'; +import type { ObjectResolvedEvent } from '../events/ObjectResolvedEvent'; +import type { Avatar } from './public/Avatar'; +import type { BulkUpdateInventoryEvent } from '../events/BulkUpdateInventoryEvent'; +import type { InventoryResponseEvent } from '../events/InventoryResponseEvent'; +import type { LandStatsEvent } from '../events/LandStatsEvent'; +import type { SimStatsEvent } from '../events/SimStatsEvent'; +import type { BalanceUpdatedEvent } from '../events/BalanceUpdatedEvent'; +import { TimeoutError } from './TimeoutError'; +import { FilterResponse } from '../enums/FilterResponse'; +import type { UUID } from './UUID'; export class ClientEvents { - onNearbyChat: Subject = new Subject(); - onInstantMessage: Subject = new Subject(); - onGroupInvite: Subject = new Subject(); - onFriendRequest: Subject = new Subject(); - onInventoryOffered: Subject = new Subject(); - onLure: Subject = new Subject(); - onTeleportEvent: Subject = new Subject(); - onDisconnected: Subject = new Subject(); - onCircuitLatency: Subject = new Subject(); - onGroupChat: Subject = new Subject(); - onGroupChatClosed: Subject = new Subject(); - onGroupNotice: Subject = new Subject(); - onGroupChatSessionJoin: Subject = new Subject(); - onGroupChatAgentListUpdate: Subject = new Subject(); - onFriendResponse: Subject = new Subject(); - onInventoryResponse: Subject = new Subject(); - onScriptDialog: Subject = new Subject(); - onEventQueueStateChange: Subject = new Subject(); - onFriendOnline: Subject = new Subject(); - onFriendRights: Subject = new Subject(); - onFriendRemoved: Subject = new Subject(); - onPhysicsDataEvent: Subject = new Subject(); - onParcelPropertiesEvent: Subject = new Subject(); - onNewObjectEvent: Subject = new Subject(); - onObjectUpdatedEvent: Subject = new Subject(); - onObjectUpdatedTerseEvent: Subject = new Subject(); - onObjectKilledEvent: Subject = new Subject(); - onSelectedObjectEvent: Subject = new Subject(); - onObjectResolvedEvent: Subject = new Subject(); - onAvatarEnteredRegion: Subject = new Subject(); - onRegionTimeDilation: Subject = new Subject(); - onBulkUpdateInventoryEvent: Subject = new Subject(); - onLandStatReplyEvent: Subject = new Subject(); - onSimStats: Subject = new Subject(); - onBalanceUpdated: Subject = new Subject(); + public onNearbyChat: Subject = new Subject(); + public onInstantMessage: Subject = new Subject(); + public onGroupInvite: Subject = new Subject(); + public onFriendRequest: Subject = new Subject(); + public onInventoryOffered: Subject = new Subject(); + public onLure: Subject = new Subject(); + public onTeleportEvent: Subject = new Subject(); + public onDisconnected: Subject = new Subject(); + public onCircuitLatency: Subject = new Subject(); + public onGroupChat: Subject = new Subject(); + public onGroupChatClosed: Subject = new Subject(); + public onGroupNotice: Subject = new Subject(); + public onGroupChatSessionJoin: Subject = new Subject(); + public onGroupChatAgentListUpdate: Subject = new Subject(); + public onFriendResponse: Subject = new Subject(); + public onInventoryResponse: Subject = new Subject(); + public onScriptDialog: Subject = new Subject(); + public onEventQueueStateChange: Subject = new Subject(); + public onFriendOnline: Subject = new Subject(); + public onFriendRights: Subject = new Subject(); + public onFriendRemoved: Subject = new Subject(); + public onPhysicsDataEvent: Subject = new Subject(); + public onParcelPropertiesEvent: Subject = new Subject(); + public onNewObjectEvent: Subject = new Subject(); + public onObjectUpdatedEvent: Subject = new Subject(); + public onObjectUpdatedTerseEvent: Subject = new Subject(); + public onObjectKilledEvent: Subject = new Subject(); + public onSelectedObjectEvent: Subject = new Subject(); + public onObjectResolvedEvent: Subject = new Subject(); + public onAvatarEnteredRegion: Subject = new Subject(); + public onRegionTimeDilation: Subject = new Subject(); + public onBulkUpdateInventoryEvent: Subject = new Subject(); + public onLandStatReplyEvent: Subject = new Subject(); + public onSimStats: Subject = new Subject(); + public onBalanceUpdated: Subject = new Subject(); + public onScriptRunningReply = new Subject<{ + ItemID: UUID, + Mono: boolean, + ObjectID: UUID, + Running: boolean + }>(); + + public async waitForEvent(subj: Subject, messageFilter?: (message: T) => FilterResponse, timeout = 10000): Promise + { + return new Promise((resolve, reject) => + { + const handleObj: { + timeout: NodeJS.Timeout | null, + subscription: Subscription | null + } = { + timeout: null, + subscription: null + }; + + const timeoutFunc = (): void => + { + if (handleObj.subscription !== null) + { + handleObj.subscription.unsubscribe(); + reject(new TimeoutError('Timeout waiting for event')); + } + }; + + handleObj.timeout = setTimeout(timeoutFunc, timeout); + + handleObj.subscription = subj.subscribe((item: T) => + { + let finish = false; + if (messageFilter === undefined) + { + finish = true; + } + else + { + try + { + const filterResult = messageFilter(item); + if (filterResult === FilterResponse.Finish) + { + finish = true; + } + else if (filterResult === FilterResponse.Match) + { + // Extend + if (handleObj.timeout !== null) + { + clearTimeout(handleObj.timeout); + } + handleObj.timeout = setTimeout(timeoutFunc, timeout); + } + } + catch(e: unknown) + { + if (handleObj.timeout !== null) + { + clearTimeout(handleObj.timeout); + handleObj.timeout = null; + } + if (handleObj.subscription !== null) + { + handleObj.subscription.unsubscribe(); + handleObj.subscription = null; + } + if (e instanceof Error) + { + reject(e); + } + else + { + reject(new Error('Failed running event filter')); + } + } + } + if (finish) + { + if (handleObj.timeout !== null) + { + clearTimeout(handleObj.timeout); + handleObj.timeout = null; + } + if (handleObj.subscription !== null) + { + handleObj.subscription.unsubscribe(); + handleObj.subscription = null; + } + resolve(item); + } + }); + }); + } } diff --git a/lib/classes/CoalescedGameObject.ts b/lib/classes/CoalescedGameObject.ts index bc28650..d7d429d 100644 --- a/lib/classes/CoalescedGameObject.ts +++ b/lib/classes/CoalescedGameObject.ts @@ -1,37 +1,41 @@ import { Vector3 } from './Vector3'; import { GameObject } from './public/GameObject'; -import { UUID } from './UUID'; +import type { UUID } from './UUID'; import * as builder from 'xmlbuilder'; -import { XMLElement } from 'xmlbuilder'; +import type { XMLElement } from 'xmlbuilder'; import { Utils } from './Utils'; export class CoalescedGameObject { - itemID: UUID; - assetID: UUID; - size: Vector3; - objects: { + public itemID: UUID; + public assetID: UUID; + public size: Vector3; + public objects: { offset: Vector3, object: GameObject }[]; - static async fromXML(xml: string): Promise + public static async fromXML(xml: string): Promise { const obj = new CoalescedGameObject(); const parsed = await Utils.parseXML(xml); - if (!parsed['CoalescedObject']) + if (!parsed.CoalescedObject) { throw new Error('CoalescedObject not found'); } - const result = parsed['CoalescedObject']; + const result = parsed.CoalescedObject; obj.size = new Vector3([parseFloat(result.$.x), parseFloat(result.$.y), parseFloat(result.$.z)]); - const sog = result['SceneObjectGroup']; + const sog = result.SceneObjectGroup; obj.objects = []; for (const object of sog) { - const toProcess = object['SceneObjectGroup'][0]; + if (object.SceneObjectGroup === undefined || !Array.isArray(object.SceneObjectGroup) || object.SceneObjectGroup.length === 0) + { + continue; + } + const toProcess = object.SceneObjectGroup[0]; const go = await GameObject.fromXML(toProcess); obj.objects.push({ offset: new Vector3([parseFloat(object.$.offsetx), parseFloat(object.$.offsety), parseFloat(object.$.offsetz)]), @@ -41,7 +45,7 @@ export class CoalescedGameObject return obj; } - async exportXMLElement(rootNode?: string, skipResolve?: Set): Promise + public async exportXMLElement(rootNode?: string, skipResolve?: Set): Promise { const document = builder.create('CoalescedObject'); document.att('x', this.size.x); @@ -60,7 +64,7 @@ export class CoalescedGameObject return document; } - async exportXML(rootNode?: string, skipResolve?: Set): Promise + public async exportXML(rootNode?: string, skipResolve?: Set): Promise { return (await this.exportXMLElement(rootNode, skipResolve)).end({ pretty: true, allowEmpty: true }); } diff --git a/lib/classes/Color4.ts b/lib/classes/Color4.ts index bbe4de5..302ee7f 100644 --- a/lib/classes/Color4.ts +++ b/lib/classes/Color4.ts @@ -1,66 +1,13 @@ import { Utils } from './Utils'; -import { XMLNode } from 'xmlbuilder'; +import type { XMLNode } from 'xmlbuilder'; export class Color4 { - static black: Color4 = new Color4(0.0, 0.0, 0.0, 1.0); - static white: Color4 = new Color4(1.0, 1.0, 1.0, 1.0); + public static black: Color4 = new Color4(0.0, 0.0, 0.0, 1.0); + public static white: Color4 = new Color4(1.0, 1.0, 1.0, 1.0); - static getXML(doc: XMLNode, c?: Color4): void - { - if (c === undefined) - { - c = Color4.white; - } - doc.ele('R', c.red); - doc.ele('G', c.green); - doc.ele('B', c.blue); - doc.ele('A', c.alpha); - } - - static fromXMLJS(obj: any, param: string): Color4 | false - { - if (!obj[param]) - { - return false; - } - let value = obj[param]; - if (Array.isArray(value) && value.length > 0) - { - value = value[0]; - } - if (typeof value === 'object') - { - if (value['R'] !== undefined && value['G'] !== undefined && value['B'] !== undefined && value['A'] !== undefined) - { - let red = value['R']; - let green = value['G']; - let blue = value['B']; - let alpha = value['A']; - if (Array.isArray(red) && red.length > 0) - { - red = red[0]; - } - if (Array.isArray(green) && green.length > 0) - { - green = green[0]; - } - if (Array.isArray(blue) && blue.length > 0) - { - blue = blue[0]; - } - if (Array.isArray(alpha) && alpha.length > 0) - { - alpha = alpha[0]; - } - return new Color4(red, green, blue, alpha); - } - return false; - } - return false; - } - - constructor(public red: number | Buffer | number[], public green: number = 0, public blue: number | boolean = 0, public alpha: number | boolean = 0) + // eslint-disable-next-line @typescript-eslint/parameter-properties + public constructor(public red: number | Buffer | number[], public green = 0, public blue: number | boolean = 0, public alpha: number | boolean = 0) { if (red instanceof Buffer && typeof blue === 'boolean') { @@ -68,7 +15,7 @@ export class Color4 const pos = green; const inverted = blue; let alphaInverted = false; - if (typeof alpha === 'boolean' && alpha === true) + if (typeof alpha === 'boolean' && alpha) { alphaInverted = true; } @@ -106,7 +53,62 @@ export class Color4 this.red = red[0]; } } - getRed(): number + + public static getXML(doc: XMLNode, c?: Color4): void + { + if (c === undefined) + { + c = Color4.white; + } + doc.ele('R', c.red); + doc.ele('G', c.green); + doc.ele('B', c.blue); + doc.ele('A', c.alpha); + } + + public static fromXMLJS(obj: any, param: string): Color4 | false + { + if (!obj[param]) + { + return false; + } + let value = obj[param]; + if (Array.isArray(value) && value.length > 0) + { + value = value[0]; + } + if (typeof value === 'object') + { + if (value.R !== undefined && value.G !== undefined && value.B !== undefined && value.A !== undefined) + { + let red = value.R; + let green = value.G; + let blue = value.B; + let alpha = value.A; + if (Array.isArray(red) && red.length > 0) + { + red = red[0]; + } + if (Array.isArray(green) && green.length > 0) + { + green = green[0]; + } + if (Array.isArray(blue) && blue.length > 0) + { + blue = blue[0]; + } + if (Array.isArray(alpha) && alpha.length > 0) + { + alpha = alpha[0]; + } + return new Color4(Number(red), Number(green), Number(blue), Number(alpha)); + } + return false; + } + return false; + } + + public getRed(): number { if (typeof this.red === 'number') { @@ -114,7 +116,7 @@ export class Color4 } return 0; } - getGreen(): number + public getGreen(): number { if (typeof this.green === 'number') { @@ -122,7 +124,7 @@ export class Color4 } return 0; } - getBlue(): number + public getBlue(): number { if (typeof this.blue === 'number') { @@ -130,7 +132,7 @@ export class Color4 } return 0; } - getAlpha(): number + public getAlpha(): number { if (typeof this.alpha === 'number') { @@ -139,7 +141,7 @@ export class Color4 return 0; } - writeToBuffer(buf: Buffer, pos: number, inverted: boolean = false): void + public writeToBuffer(buf: Buffer, pos: number, inverted = false): void { buf.writeUInt8(Utils.FloatToByte(this.getRed(), 0, 1.0), pos); buf.writeUInt8(Utils.FloatToByte(this.getGreen(), 0, 1.0), pos + 1); @@ -155,14 +157,14 @@ export class Color4 } } - getBuffer(inverted: boolean = false): Buffer + public getBuffer(inverted = false): Buffer { const buf = Buffer.allocUnsafe(4); this.writeToBuffer(buf, 0, inverted); return buf; } - equals(other: Color4): boolean + public equals(other: Color4): boolean { return (this.red === other.red && this.green === other.green && this.blue === other.blue && this.alpha === other.alpha); } diff --git a/lib/classes/Comms.ts b/lib/classes/Comms.ts index ef8c3c8..d05f116 100644 --- a/lib/classes/Comms.ts +++ b/lib/classes/Comms.ts @@ -1,16 +1,16 @@ -import { Subscription } from 'rxjs'; +import type { Subscription } from 'rxjs'; import { GroupChatClosedEvent } from '../events/GroupChatClosedEvent'; -import { Agent } from './Agent'; -import { Circuit } from './Circuit'; -import { Packet } from './Packet'; +import type { Agent } from './Agent'; +import type { Circuit } from './Circuit'; +import type { Packet } from './Packet'; import { Message } from '../enums/Message'; -import { ChatFromSimulatorMessage } from './messages/ChatFromSimulator'; -import { ImprovedInstantMessageMessage } from './messages/ImprovedInstantMessage'; +import type { ChatFromSimulatorMessage } from './messages/ChatFromSimulator'; +import type { ImprovedInstantMessageMessage } from './messages/ImprovedInstantMessage'; import { Utils } from './Utils'; import { InstantMessageDialog } from '../enums/InstantMessageDialog'; -import { AlertMessageMessage } from './messages/AlertMessage'; -import { ClientEvents } from './ClientEvents'; -import { ScriptDialogMessage } from './messages/ScriptDialog'; +import type { AlertMessageMessage } from './messages/AlertMessage'; +import type { ClientEvents } from './ClientEvents'; +import type { ScriptDialogMessage } from './messages/ScriptDialog'; import { InstantMessageEvent } from '../events/InstantMessageEvent'; import { ChatSourceType } from '../enums/ChatSourceType'; import { InstantMessageEventFlags } from '../enums/InstantMessageEventFlags'; @@ -30,9 +30,15 @@ export class Comms { private groupChatExpiredSub?: Subscription; - constructor(public readonly circuit: Circuit, public readonly agent: Agent, public readonly clientEvents: ClientEvents) + public constructor(public readonly circuit: Circuit, public readonly agent: Agent, public readonly clientEvents: ClientEvents) { - this.groupChatExpiredSub = this.agent.onGroupChatExpired.subscribe(this.groupChatExpired.bind(this)); + this.groupChatExpiredSub = this.agent.onGroupChatExpired.subscribe((groupID: UUID) => + { + this.groupChatExpired(groupID).catch((_e: unknown) => + { + // ignore + }) + }); this.circuit.subscribeToMessages([ Message.ImprovedInstantMessage, @@ -44,8 +50,9 @@ export class Comms switch (packet.message.id) { case Message.ImprovedInstantMessage: + { const im = packet.message as ImprovedInstantMessageMessage; - switch (im.MessageBlock.Dialog) + switch (im.MessageBlock.Dialog as InstantMessageDialog) { case InstantMessageDialog.MessageFromAgent: { @@ -62,6 +69,7 @@ export class Comms case InstantMessageDialog.MessageBox: break; case InstantMessageDialog.GroupInvitation: + { const giEvent = new GroupInviteEvent(); giEvent.from = im.AgentData.AgentID; giEvent.fromName = Utils.BufferToStringSimple(im.MessageBlock.FromAgentName); @@ -69,6 +77,7 @@ export class Comms giEvent.inviteID = im.MessageBlock.ID; this.clientEvents.onGroupInvite.next(giEvent); break; + } case InstantMessageDialog.InventoryOffered: { const fromName = Utils.BufferToStringSimple(im.MessageBlock.FromAgentName); @@ -287,10 +296,11 @@ export class Comms this.clientEvents.onGroupChat.next(groupChatEvent); break; } - + default: + break; } break; - + } case Message.ChatFromSimulator: { const chat = packet.message as ChatFromSimulatorMessage; @@ -343,11 +353,13 @@ export class Comms this.clientEvents.onScriptDialog.next(event); break; } + default: + break; } }); } - shutdown(): void + public shutdown(): void { if (this.groupChatExpiredSub !== undefined) { diff --git a/lib/classes/ConcurrentQueue.ts b/lib/classes/ConcurrentQueue.ts index 5dcd953..74f2b7e 100644 --- a/lib/classes/ConcurrentQueue.ts +++ b/lib/classes/ConcurrentQueue.ts @@ -1,16 +1,31 @@ export class ConcurrentQueue { - private concurrency: number; + private readonly concurrency: number; private runningCount; - private jobQueue: { job: () => Promise, resolve: (value: void | PromiseLike) => void, reject: (reason?: unknown) => void }[]; + private readonly jobQueue: { job: () => Promise, resolve: (value: void | PromiseLike) => void, reject: (reason?: unknown) => void }[] = []; - constructor(concurrency: number) + public constructor(concurrency: number) { this.concurrency = concurrency; this.runningCount = 0; this.jobQueue = []; } + public async addJob(job: () => Promise): Promise + { + return new Promise((resolve, reject) => + { + if (this.runningCount < this.concurrency) + { + this.executeJob(job, resolve, reject); + } + else + { + this.jobQueue.push({ job, resolve, reject }); + } + }); + } + private executeJob(job: () => Promise, resolve: (value: void | PromiseLike) => void, reject: (reason?: unknown) => void): void { this.runningCount++; @@ -25,23 +40,13 @@ export class ConcurrentQueue { if (this.runningCount < this.concurrency && this.jobQueue.length > 0) { - const { job, resolve, reject } = this.jobQueue.shift()!; + const data = this.jobQueue.shift(); + if (!data) + { + return; + } + const { job, resolve, reject } = data; this.executeJob(job, resolve, reject); } } - - public addJob(job: () => Promise): Promise - { - return new Promise((resolve, reject) => - { - if (this.runningCount < this.concurrency) - { - this.executeJob(job, resolve, reject); - } - else - { - this.jobQueue.push({ job, resolve, reject }); - } - }); - } } diff --git a/lib/classes/EventQueueClient.ts b/lib/classes/EventQueueClient.ts index 787be8f..a26bc56 100644 --- a/lib/classes/EventQueueClient.ts +++ b/lib/classes/EventQueueClient.ts @@ -1,6 +1,6 @@ -import { ClientEvents } from './ClientEvents'; -import { Agent } from './Agent'; -import { Caps } from './Caps'; +import type { ClientEvents } from './ClientEvents'; +import type { Agent } from './Agent'; +import type { Caps } from './Caps'; import { EventQueueStateChangeEvent } from '../events/EventQueueStateChangeEvent'; import { ParcelPropertiesEvent } from '../events/ParcelPropertiesEvent'; import { Vector3 } from './Vector3'; @@ -20,19 +20,21 @@ import { InventoryLibrary } from '../enums/InventoryLibrary'; import { LandStatsEvent } from '../events/LandStatsEvent'; import * as LLSD from '@caspertech/llsd'; -import got, { CancelableRequest, Response } from 'got'; +import type { CancelableRequest, Response } from 'got'; +import got from 'got'; import * as Long from 'long'; export class EventQueueClient { - caps: Caps; - ack?: number; - done = false; - private currentRequest?: CancelableRequest> = undefined; - private clientEvents: ClientEvents; - private agent: Agent; + public caps: Caps; + public ack?: number; + public done = false; - constructor(agent: Agent, caps: Caps, clientEvents: ClientEvents) + private currentRequest?: CancelableRequest> = undefined; + private readonly clientEvents: ClientEvents; + private readonly agent: Agent; + + public constructor(agent: Agent, caps: Caps, clientEvents: ClientEvents) { this.agent = agent; this.clientEvents = clientEvents; @@ -43,7 +45,7 @@ export class EventQueueClient this.clientEvents.onEventQueueStateChange.next(state); } - shutdown(): void + public async shutdown(): Promise { // We must ACK any outstanding events this.done = true; @@ -56,15 +58,13 @@ export class EventQueueClient 'ack': this.ack, 'done': true }; - this.capsPostXML('EventQueueGet', req).then(() => - { - const state = new EventQueueStateChangeEvent(); - state.active = false; - this.clientEvents.onEventQueueStateChange.next(state); - }); + await this.capsPostXML('EventQueueGet', req); + const state = new EventQueueStateChangeEvent(); + state.active = false; + this.clientEvents.onEventQueueStateChange.next(state); } - Get(): void + public Get(): void { const req = { 'ack': this.ack, @@ -73,9 +73,9 @@ export class EventQueueClient const startTime = new Date().getTime(); this.capsPostXML('EventQueueGet', req).then((data) => { - if (data['id']) + if (data.id) { - this.ack = data['id']; + this.ack = data.id; } else { @@ -83,16 +83,16 @@ export class EventQueueClient } try { - if (data['events']) + if (data.events) { - for (const event of data['events']) + for (const event of data.events) { try { - if (event['message']) + if (event.message) { // noinspection TsLint - switch (event['message']) + switch (event.message) { case 'EnableSimulator': @@ -114,55 +114,55 @@ export class EventQueueClient break; case 'BulkUpdateInventory': { - const body = event['body']; + const body = event.body; const buie = new BulkUpdateInventoryEvent(); - if (body['FolderData']) + if (body.FolderData) { - for (const f of body['FolderData']) + for (const f of body.FolderData) { - const folderID = new UUID(f['FolderID']); + const folderID = new UUID(f.FolderID); if (!folderID.isZero()) { const folder = new InventoryFolder(InventoryLibrary.Main, this.agent.inventory.main, this.agent); folder.folderID = folderID; - folder.name = f['Name']; - folder.parentID = new UUID(f['ParentID']); - folder.typeDefault = parseInt(f['Type'], 10); + folder.name = f.Name; + folder.parentID = new UUID(f.ParentID); + folder.typeDefault = parseInt(f.Type, 10); buie.folderData.push(folder); } } } - if (body['ItemData']) + if (body.ItemData) { - for (const i of body['ItemData']) + for (const i of body.ItemData) { - const itemID = new UUID(i['ItemID']); + const itemID = new UUID(i.ItemID); if (!itemID.isZero()) { - const folder = this.agent.inventory.findFolder(new UUID(i['FolderID'])); - const item = new InventoryItem(folder || undefined, this.agent); + const folder = this.agent.inventory.findFolder(new UUID(i.FolderID)); + const item = new InventoryItem(folder ?? undefined, this.agent); - item.assetID = new UUID(i['AssetID']); - item.permissions.baseMask = Utils.OctetsToUInt32BE(i['BaseMask'].octets); - item.permissions.everyoneMask = Utils.OctetsToUInt32BE(i['EveryoneMask'].octets); - item.permissions.groupMask = Utils.OctetsToUInt32BE(i['GroupMask'].octets); - item.permissions.nextOwnerMask = Utils.OctetsToUInt32BE(i['NextOwnerMask'].octets); - item.permissions.ownerMask = Utils.OctetsToUInt32BE(i['OwnerMask'].octets); - item.permissions.groupOwned = i['GroupOwned']; - item.permissions.creator = new UUID(i['CreatorID']); - item.permissions.group = new UUID(i['GroupID']); - item.permissions.owner = new UUID(i['OwnerID']); - item.flags = Utils.OctetsToUInt32BE(i['Flags'].octets); - item.callbackID = Utils.OctetsToUInt32BE(i['CallbackID'].octets); - item.created = new Date(parseInt(i['CreationDate'], 10) * 1000); - item.description = i['Description']; - item.parentID = new UUID(i['FolderID']); - item.inventoryType = parseInt(i['InvType'], 10); - item.salePrice = parseInt(i['SalePrice'], 10); - item.saleType = parseInt(i['SaleType'], 10); - item.type = parseInt(i['Type'], 10); + item.assetID = new UUID(i.AssetID); + item.permissions.baseMask = Utils.OctetsToUInt32BE(i.BaseMask.octets); + item.permissions.everyoneMask = Utils.OctetsToUInt32BE(i.EveryoneMask.octets); + item.permissions.groupMask = Utils.OctetsToUInt32BE(i.GroupMask.octets); + item.permissions.nextOwnerMask = Utils.OctetsToUInt32BE(i.NextOwnerMask.octets); + item.permissions.ownerMask = Utils.OctetsToUInt32BE(i.OwnerMask.octets); + item.permissions.groupOwned = i.GroupOwned; + item.permissions.creator = new UUID(i.CreatorID); + item.permissions.group = new UUID(i.GroupID); + item.permissions.owner = new UUID(i.OwnerID); + item.flags = Utils.OctetsToUInt32BE(i.Flags.octets); + item.callbackID = Utils.OctetsToUInt32BE(i.CallbackID.octets); + item.created = new Date(parseInt(i.CreationDate, 10) * 1000); + item.description = i.Description; + item.parentID = new UUID(i.FolderID); + item.inventoryType = parseInt(i.InvType, 10); + item.salePrice = parseInt(i.SalePrice, 10); + item.saleType = parseInt(i.SaleType, 10); + item.type = parseInt(i.Type, 10); item.itemID = itemID; - item.name = i['Name']; + item.name = i.Name; buie.itemData.push(item); } } @@ -172,80 +172,80 @@ export class EventQueueClient } case 'ParcelProperties': { - const body = event['body']; + const body = event.body; const pprop = new ParcelPropertiesEvent(); - pprop.RegionDenyAgeUnverified = body['AgeVerificationBlock'][0]['RegionDenyAgeUnverified']; - pprop.MediaDesc = body['MediaData'][0]['MediaDesc']; - pprop.MediaHeight = body['MediaData'][0]['MediaHeight']; - pprop.MediaLoop = body['MediaData'][0]['MediaLoop']; - pprop.MediaType = body['MediaData'][0]['MediaType']; - pprop.MediaWidth = body['MediaData'][0]['MediaWidth']; - pprop.ObscureMedia = body['MediaData'][0]['ObscureMedia']; - pprop.ObscureMusic = body['MediaData'][0]['ObscureMusic']; - pprop.AABBMax = new Vector3([parseInt(body['ParcelData'][0]['AABBMax'][0], 10), parseInt( body['ParcelData'][0]['AABBMax'][1], 10), parseInt(body['ParcelData'][0]['AABBMax'][2], 10)]); - pprop.AABBMin = new Vector3([parseInt(body['ParcelData'][0]['AABBMin'][0], 10), parseInt(body['ParcelData'][0]['AABBMin'][1], 10), parseInt( body['ParcelData'][0]['AABBMin'][2], 10)]); - pprop.AnyAVSounds = body['ParcelData'][0]['AnyAVSounds']; - pprop.Area = body['ParcelData'][0]['Area']; + pprop.RegionDenyAgeUnverified = body.AgeVerificationBlock[0].RegionDenyAgeUnverified; + pprop.MediaDesc = body.MediaData[0].MediaDesc; + pprop.MediaHeight = body.MediaData[0].MediaHeight; + pprop.MediaLoop = body.MediaData[0].MediaLoop; + pprop.MediaType = body.MediaData[0].MediaType; + pprop.MediaWidth = body.MediaData[0].MediaWidth; + pprop.ObscureMedia = body.MediaData[0].ObscureMedia; + pprop.ObscureMusic = body.MediaData[0].ObscureMusic; + pprop.AABBMax = new Vector3([parseInt(body.ParcelData[0].AABBMax[0], 10), parseInt( body.ParcelData[0].AABBMax[1], 10), parseInt(body.ParcelData[0].AABBMax[2], 10)]); + pprop.AABBMin = new Vector3([parseInt(body.ParcelData[0].AABBMin[0], 10), parseInt(body.ParcelData[0].AABBMin[1], 10), parseInt( body.ParcelData[0].AABBMin[2], 10)]); + pprop.AnyAVSounds = body.ParcelData[0].AnyAVSounds; + pprop.Area = body.ParcelData[0].Area; try { - pprop.AuctionID = Buffer.from(body['ParcelData'][0]['AuctionID'].toArray()).readUInt32BE(0); + pprop.AuctionID = Buffer.from(body.ParcelData[0].AuctionID.toArray()).readUInt32BE(0); } - catch (ignore) + catch (_ignore: unknown) { // TODO: Opensim glitch } - pprop.AuthBuyerID = new UUID(String(body['ParcelData'][0]['AuthBuyerID'])); + pprop.AuthBuyerID = new UUID(String(body.ParcelData[0].AuthBuyerID)); - pprop.Bitmap = Buffer.from(body['ParcelData'][0]['Bitmap'].toArray()); - pprop.Category = body['ParcelData'][0]['Category']; - pprop.ClaimDate = body['ParcelData'][0]['ClaimDate']; - pprop.ClaimPrice = body['ParcelData'][0]['ClaimPrice']; - pprop.Desc = body['ParcelData'][0]['Desc']; - pprop.GroupAVSounds = body['ParcelData'][0]['GroupAVSounds']; - pprop.GroupID = new UUID(String(body['ParcelData'][0]['GroupID'])); - pprop.GroupPrims = body['ParcelData'][0]['GroupPrims']; - pprop.IsGroupOwned = body['ParcelData'][0]['IsGroupOwned']; - pprop.LandingType = body['ParcelData'][0]['LandingType']; - pprop.LocalID = body['ParcelData'][0]['LocalID']; - pprop.MaxPrims = body['ParcelData'][0]['MaxPrims']; - pprop.MediaAutoScale = body['ParcelData'][0]['MediaAutoScale']; - pprop.MediaID = new UUID(String(body['ParcelData'][0]['MediaID'])); - pprop.MediaURL = body['ParcelData'][0]['MediaURL']; - pprop.MusicURL = body['ParcelData'][0]['MusicURL']; - pprop.Name = body['ParcelData'][0]['Name']; - pprop.OtherCleanTime = body['ParcelData'][0]['OtherCleanTime']; - pprop.OtherCount = body['ParcelData'][0]['OtherCount']; - pprop.OtherPrims = body['ParcelData'][0]['OtherPrims']; - pprop.OwnerID = body['ParcelData'][0]['OwnerID']; - pprop.OwnerPrims = body['ParcelData'][0]['OwnerPrims']; - pprop.ParcelFlags = Buffer.from(body['ParcelData'][0]['ParcelFlags'].toArray()).readUInt32BE(0); - pprop.ParcelPrimBonus = body['ParcelData'][0]['ParcelPrimBonus']; - pprop.PassHours = body['ParcelData'][0]['PassHours']; - pprop.PassPrice = body['ParcelData'][0]['PassPrice']; - pprop.PublicCount = body['ParcelData'][0]['PublicCount']; - pprop.RegionDenyAnonymous = body['ParcelData'][0]['RegionDenyAnonymous']; - pprop.RegionDenyIdentified = body['ParcelData'][0]['RegionDenyIdentified']; - pprop.RegionPushOverride = body['ParcelData'][0]['RegionPushOverride']; - pprop.RegionDenyTransacted = body['ParcelData'][0]['RegionDenyTransacted']; - pprop.RentPrice = body['ParcelData'][0]['RentPrice']; - pprop.RequestResult = body['ParcelData'][0]['RequestResult']; - pprop.SalePrice = body['ParcelData'][0]['SalePrice']; - pprop.SeeAvs = body['ParcelData'][0]['SeeAVs']; - pprop.SelectedPrims = body['ParcelData'][0]['SelectedPrims']; - pprop.SelfCount = body['ParcelData'][0]['SelfCount']; - pprop.SequenceID = body['ParcelData'][0]['SequenceID']; - pprop.SimWideMaxPrims = body['ParcelData'][0]['SimWideMaxPrims']; - pprop.SimWideTotalPrims = body['ParcelData'][0]['SimWideTotalPrims']; - pprop.SnapSelection = body['ParcelData'][0]['SnapSelection']; - pprop.SnapshotID = new UUID(body['ParcelData'][0]['SnapshotID'].toString()); - pprop.Status = body['ParcelData'][0]['Status']; - pprop.TotalPrims = body['ParcelData'][0]['TotalPrims']; - pprop.UserLocation = new Vector3([parseInt(body['ParcelData'][0]['UserLocation'][0], 10), parseInt(body['ParcelData'][0]['UserLocation'][1], 10), parseInt(body['ParcelData'][0]['UserLocation'][2], 10)]); - pprop.UserLookAt = new Vector3([parseInt(body['ParcelData'][0]['UserLookAt'][0], 10), parseInt(body['ParcelData'][0]['UserLookAt'][1], 10), parseInt(body['ParcelData'][0]['UserLookAt'][2], 10)]); - if (body['RegionAllowAccessBlock'] !== undefined && body['RegionAllowAccessBlock'].length > 0) + pprop.Bitmap = Buffer.from(body.ParcelData[0].Bitmap.toArray()); + pprop.Category = body.ParcelData[0].Category; + pprop.ClaimDate = body.ParcelData[0].ClaimDate; + pprop.ClaimPrice = body.ParcelData[0].ClaimPrice; + pprop.Desc = body.ParcelData[0].Desc; + pprop.GroupAVSounds = body.ParcelData[0].GroupAVSounds; + pprop.GroupID = new UUID(String(body.ParcelData[0].GroupID)); + pprop.GroupPrims = body.ParcelData[0].GroupPrims; + pprop.IsGroupOwned = body.ParcelData[0].IsGroupOwned; + pprop.LandingType = body.ParcelData[0].LandingType; + pprop.LocalID = body.ParcelData[0].LocalID; + pprop.MaxPrims = body.ParcelData[0].MaxPrims; + pprop.MediaAutoScale = body.ParcelData[0].MediaAutoScale; + pprop.MediaID = new UUID(String(body.ParcelData[0].MediaID)); + pprop.MediaURL = body.ParcelData[0].MediaURL; + pprop.MusicURL = body.ParcelData[0].MusicURL; + pprop.Name = body.ParcelData[0].Name; + pprop.OtherCleanTime = body.ParcelData[0].OtherCleanTime; + pprop.OtherCount = body.ParcelData[0].OtherCount; + pprop.OtherPrims = body.ParcelData[0].OtherPrims; + pprop.OwnerID = body.ParcelData[0].OwnerID; + pprop.OwnerPrims = body.ParcelData[0].OwnerPrims; + pprop.ParcelFlags = Buffer.from(body.ParcelData[0].ParcelFlags.toArray()).readUInt32BE(0); + pprop.ParcelPrimBonus = body.ParcelData[0].ParcelPrimBonus; + pprop.PassHours = body.ParcelData[0].PassHours; + pprop.PassPrice = body.ParcelData[0].PassPrice; + pprop.PublicCount = body.ParcelData[0].PublicCount; + pprop.RegionDenyAnonymous = body.ParcelData[0].RegionDenyAnonymous; + pprop.RegionDenyIdentified = body.ParcelData[0].RegionDenyIdentified; + pprop.RegionPushOverride = body.ParcelData[0].RegionPushOverride; + pprop.RegionDenyTransacted = body.ParcelData[0].RegionDenyTransacted; + pprop.RentPrice = body.ParcelData[0].RentPrice; + pprop.RequestResult = body.ParcelData[0].RequestResult; + pprop.SalePrice = body.ParcelData[0].SalePrice; + pprop.SeeAvs = body.ParcelData[0].SeeAVs; + pprop.SelectedPrims = body.ParcelData[0].SelectedPrims; + pprop.SelfCount = body.ParcelData[0].SelfCount; + pprop.SequenceID = body.ParcelData[0].SequenceID; + pprop.SimWideMaxPrims = body.ParcelData[0].SimWideMaxPrims; + pprop.SimWideTotalPrims = body.ParcelData[0].SimWideTotalPrims; + pprop.SnapSelection = body.ParcelData[0].SnapSelection; + pprop.SnapshotID = new UUID(body.ParcelData[0].SnapshotID.toString()); + pprop.Status = body.ParcelData[0].Status; + pprop.TotalPrims = body.ParcelData[0].TotalPrims; + pprop.UserLocation = new Vector3([parseInt(body.ParcelData[0].UserLocation[0], 10), parseInt(body.ParcelData[0].UserLocation[1], 10), parseInt(body.ParcelData[0].UserLocation[2], 10)]); + pprop.UserLookAt = new Vector3([parseInt(body.ParcelData[0].UserLookAt[0], 10), parseInt(body.ParcelData[0].UserLookAt[1], 10), parseInt(body.ParcelData[0].UserLookAt[2], 10)]); + if (body.RegionAllowAccessBlock !== undefined && body.RegionAllowAccessBlock.length > 0) { // TODO: OpenSim glitch - pprop.RegionAllowAccessOverride = body['RegionAllowAccessBlock'][0]['RegionAllowAccessOverride']; + pprop.RegionAllowAccessOverride = body.RegionAllowAccessBlock[0].RegionAllowAccessOverride; } this.clientEvents.onParcelPropertiesEvent.next(pprop); break; @@ -320,7 +320,7 @@ export class EventQueueClient case 'TeleportFailed': { const tpEvent = new TeleportEvent(); - tpEvent.message = event['body']['Info'][0]['Reason']; + tpEvent.message = event.body.Info[0].Reason; tpEvent.eventType = TeleportEventType.TeleportFailed; tpEvent.simIP = ''; tpEvent.simPort = 0; @@ -331,13 +331,13 @@ export class EventQueueClient } case 'ChatterBoxSessionStartReply': { - if (event['body']) + if (event.body) { const gcsje = new GroupChatSessionJoinEvent(); - gcsje.success = event['body']['success']; + gcsje.success = event.body.success; if (gcsje.success) { - gcsje.sessionID = new UUID(event['body']['session_id'].toString()); + gcsje.sessionID = new UUID(event.body.session_id.toString()); const added = this.agent.addChatSession(gcsje.sessionID, true); if (!added) { @@ -350,17 +350,17 @@ export class EventQueueClient } case 'ChatterBoxInvitation': { - if (event['body'] && event['body']['instantmessage'] && event['body']['instantmessage']['message_params'] && event['body']['instantmessage']['message_params']['id']) + if (event.body?.instantmessage?.message_params?.id) { - const messageParams = event['body']['instantmessage']['message_params']; - const imSessionID = messageParams['id']; + const messageParams = event.body.instantmessage.message_params; + const imSessionID = messageParams.id; const groupChatEvent = new GroupChatEvent(); - groupChatEvent.from = new UUID(messageParams['from_id'].toString()); - groupChatEvent.fromName = messageParams['from_name']; - groupChatEvent.groupID = new UUID(messageParams['id'].toString()); - groupChatEvent.message = messageParams['message']; + groupChatEvent.from = new UUID(messageParams.from_id.toString()); + groupChatEvent.fromName = messageParams.from_name; + groupChatEvent.groupID = new UUID(messageParams.id.toString()); + groupChatEvent.message = messageParams.message; const requested = { 'method': 'accept invitation', @@ -375,7 +375,7 @@ export class EventQueueClient this.clientEvents.onGroupChatSessionJoin.next(gcsje); this.clientEvents.onGroupChat.next(groupChatEvent); this.agent.updateLastMessage(groupChatEvent.groupID); - }).catch((err) => + }).catch((err: unknown) => { console.error(err); }); @@ -384,27 +384,27 @@ export class EventQueueClient } case 'ChatterBoxSessionAgentListUpdates': { - if (event['body']) + if (event.body) { - if (event['body']['agent_updates']) + if (event.body.agent_updates) { - for (const agentUpdate of Object.keys(event['body']['agent_updates'])) + for (const agentUpdate of Object.keys(event.body.agent_updates)) { - const updObj = event['body']['agent_updates'][agentUpdate]; + const updObj = event.body.agent_updates[agentUpdate]; const gcsale = new GroupChatSessionAgentListEvent(); gcsale.agentID = new UUID(agentUpdate); - gcsale.groupID = new UUID(event['body']['session_id'].toString()); + gcsale.groupID = new UUID(event.body.session_id.toString()); gcsale.canVoiceChat = false; gcsale.isModerator = false; - gcsale.entered = (updObj['transition'] === 'ENTER'); + gcsale.entered = (updObj.transition === 'ENTER'); - if (gcsale.entered && updObj['info']) + if (gcsale.entered && updObj.info) { - if (updObj['info']['can_voice_chat'] === true) + if (updObj.info.can_voice_chat === true) { gcsale.canVoiceChat = true; } - if (updObj['info']['is_moderator'] === true) + if (updObj.info.is_moderator === true) { gcsale.isModerator = true; } @@ -417,7 +417,7 @@ export class EventQueueClient } case 'ObjectPhysicsProperties': { - const objData = event['body']['ObjectData']; + const objData = event.body.ObjectData; for (const obj of objData) { const objPhysEvent = new ObjectPhysicsDataEvent(); @@ -435,31 +435,50 @@ export class EventQueueClient } case 'TeleportFinish': { - const info = event['body']['Info'][0]; - if (info['LocationID']) + const info = event.body.Info[0]; + if (info.LocationID) { - info['LocationID'] = Buffer.from(info['LocationID'].toArray()).readUInt32BE(0); + info.LocationID = Buffer.from(info.LocationID.toArray()).readUInt32BE(0); - const regionHandleBuf = Buffer.from(info['RegionHandle'].toArray()); - info['RegionHandle'] = new Long(regionHandleBuf.readUInt32LE(0), regionHandleBuf.readUInt32LE(4), true); + const regionHandleBuf = Buffer.from(info.RegionHandle.toArray()); + info.RegionHandle = new Long(regionHandleBuf.readUInt32LE(0), regionHandleBuf.readUInt32LE(4), true); - info['SimIP'] = new IPAddress(Buffer.from(info['SimIP'].toArray()), 0).toString(); + info.SimIP = new IPAddress(Buffer.from(info.SimIP.toArray()), 0).toString(); - info['TeleportFlags'] = Buffer.from(info['TeleportFlags'].toArray()).readUInt32BE(0); + info.TeleportFlags = Buffer.from(info.TeleportFlags.toArray()).readUInt32BE(0); const tpEvent = new TeleportEvent(); tpEvent.message = ''; tpEvent.eventType = TeleportEventType.TeleportCompleted; - tpEvent.simIP = info['SimIP']; - tpEvent.simPort = info['SimPort']; - tpEvent.seedCapability = info['SeedCapability']; + tpEvent.simIP = info.SimIP; + tpEvent.simPort = info.SimPort; + tpEvent.seedCapability = info.SeedCapability; this.clientEvents.onTeleportEvent.next(tpEvent); } break; } + case 'ScriptRunningReply': + { + const body = event.body.Script as { + ItemID: any, + Mono: boolean, + ObjectID: any, + Running: boolean + }[]; + for(const it of body) + { + this.clientEvents.onScriptRunningReply.next({ + ItemID: new UUID(it.ItemID.toString()), + Mono: it.Mono, + ObjectID: new UUID(it.ObjectID.toString()), + Running: it.Running + }); + } + break; + } case 'LandStatReply': { let requestData = event.body.RequestData; @@ -588,48 +607,30 @@ export class EventQueueClient } } - capsPostXML(capability: string, data: any, attempt: number = 0): Promise + public async capsPostXML(capability: string, data: any, attempt = 0): Promise { - return new Promise((resolve, reject) => + const url = await this.caps.getCapability(capability); + const serializedData = LLSD.LLSD.formatXML(data); + const body = await this.request(url, serializedData, 'application/llsd+xml'); + if (body.includes('')) { - this.caps.getCapability(capability).then((url) => + return LLSD.LLSD.parseXML(body); + } + else + { + // Retry caps request three times before giving up + if (attempt < 3 && capability !== 'EventQueueGet') { - const serializedData = LLSD.LLSD.formatXML(data); - this.request(url, serializedData, 'application/llsd+xml').then((body: string) => + this.capsPostXML(capability, data, ++attempt).catch((_e: unknown) => { - try - { - if (body.indexOf('') !== -1) - { - const parsed = LLSD.LLSD.parseXML(body); - resolve(parsed); - } - else - { - // Retry caps request three times before giving up - if (attempt < 3 && capability !== 'EventQueueGet') - { - this.capsPostXML(capability, data, ++attempt); - return; - } - else - { - reject(new Error('Not an LLSD response, capability: ' + capability)); - } - } - } - catch (error) - { - reject(error); - } - }).catch((err) => - { - reject(err); + // ignore }); - }).catch((err) => + return ''; + } + else { - reject(err); - }); - }); + throw new Error('Not an LLSD response, capability: ' + capability); + } + } } } diff --git a/lib/classes/GroupBan.ts b/lib/classes/GroupBan.ts index c017954..88e79f2 100644 --- a/lib/classes/GroupBan.ts +++ b/lib/classes/GroupBan.ts @@ -1,8 +1,8 @@ -import { UUID } from './UUID'; +import type { UUID } from './UUID'; export class GroupBan { - constructor(public AgentID: UUID, public BanDate: Date) + public constructor(public readonly AgentID: UUID, public readonly BanDate: Date) { } diff --git a/lib/classes/GroupMember.ts b/lib/classes/GroupMember.ts index e3ce88e..9ec84c3 100644 --- a/lib/classes/GroupMember.ts +++ b/lib/classes/GroupMember.ts @@ -1,11 +1,11 @@ -import { UUID } from './UUID'; -import * as Long from 'long'; +import type { UUID } from './UUID'; +import type * as Long from 'long'; export class GroupMember { - AgentID: UUID; - OnlineStatus: string; - AgentPowers: Long; - Title: string; - IsOwner: boolean; + public AgentID: UUID; + public OnlineStatus: string; + public AgentPowers: Long; + public Title: string; + public IsOwner: boolean; } diff --git a/lib/classes/GroupRole.ts b/lib/classes/GroupRole.ts index 5e8cf98..293fb08 100644 --- a/lib/classes/GroupRole.ts +++ b/lib/classes/GroupRole.ts @@ -1,12 +1,12 @@ -import { UUID } from './UUID'; -import * as Long from 'long'; +import type { UUID } from './UUID'; +import type * as Long from 'long'; export class GroupRole { - RoleID: UUID; - Name: string; - Title: string; - Description: string; - Powers: Long; - Members: number; + public RoleID: UUID; + public Name: string; + public Title: string; + public Description: string; + public Powers: Long; + public Members: number; } diff --git a/lib/classes/IPAddress.ts b/lib/classes/IPAddress.ts index 4521073..0aee8fe 100644 --- a/lib/classes/IPAddress.ts +++ b/lib/classes/IPAddress.ts @@ -1,25 +1,11 @@ -const ipaddr = require('ipaddr.js'); +import * as ipaddr from 'ipaddr.js' +import type { IPv4, IPv6 } from 'ipaddr.js'; export class IPAddress { - ip: any = null; + public ip: IPv4 | IPv6 | null = null; - static zero(): IPAddress - { - return new IPAddress('0.0.0.0'); - } - public toString = (): string => - { - try - { - return this.ip.toString(); - } - catch (ignore) - { - return ''; - } - }; - constructor(buf?: Buffer | string, pos?: number) + public constructor(buf?: Buffer | string, pos?: number) { try { @@ -27,10 +13,10 @@ export class IPAddress { if (pos !== undefined) { - const bytes = buf.slice(pos, 4); - this.ip = ipaddr.fromByteArray(bytes); + const bytes = buf.subarray(pos, 4); + this.ip = ipaddr.fromByteArray(Array.from(bytes)); } - else + else if (typeof buf === 'string') { if (ipaddr.isValid(buf)) { @@ -42,7 +28,7 @@ export class IPAddress } } } - else + else if (typeof buf === 'string') { if (ipaddr.isValid(buf)) { @@ -54,14 +40,34 @@ export class IPAddress } } } - catch (ignore) + catch (_ignore: unknown) { this.ip = ipaddr.parse('0.0.0.0'); } } - writeToBuffer(buf: Buffer, pos: number): void + + public static zero(): IPAddress { - const bytes: Uint8Array = this.ip.toByteArray(); + return new IPAddress('0.0.0.0'); + } + + public toString = (): string => + { + if (!this.ip) + { + return ''; + } + return this.ip.toString(); + }; + + + public writeToBuffer(buf: Buffer, pos: number): void + { + if (!this.ip) + { + throw new Error('Invalid IP'); + } + const bytes: number[] = this.ip.toByteArray(); buf.writeUInt8(bytes[0], pos++); buf.writeUInt8(bytes[1], pos++); buf.writeUInt8(bytes[2], pos++); diff --git a/lib/classes/Inventory.ts b/lib/classes/Inventory.ts index fceb54a..cb42259 100644 --- a/lib/classes/Inventory.ts +++ b/lib/classes/Inventory.ts @@ -1,78 +1,78 @@ import { UUID } from './UUID'; -import { ClientEvents } from './ClientEvents'; import { InventoryFolder } from './InventoryFolder'; -import { Agent } from './Agent'; +import type { Agent } from './Agent'; import * as LLSD from '@caspertech/llsd'; import { InventoryItem } from './InventoryItem'; -import { FolderType } from '../enums/FolderType'; +import type { FolderType } from '../enums/FolderType'; import { InventoryLibrary } from '../enums/InventoryLibrary'; export class Inventory { - main: { - skeleton: { [key: string]: InventoryFolder }, + public main: { + skeleton: Map, root?: UUID } = { - skeleton: {} + skeleton: new Map() }; - library: { + + public library: { owner?: UUID, - skeleton: { [key: string]: InventoryFolder }, + skeleton: Map, root?: UUID } = { - skeleton: {} + skeleton: new Map() }; - itemsByID: { [key: string]: InventoryItem } = {}; + public itemsByID = new Map(); - // @ts-ignore - private clientEvents: ClientEvents; - private agent: Agent; + private readonly agent: Agent; - constructor(clientEvents: ClientEvents, agent: Agent) + public constructor(agent: Agent) { this.agent = agent; - this.clientEvents = clientEvents; } - getRootFolderLibrary(): InventoryFolder + + public getRootFolderLibrary(): InventoryFolder { if (this.library.root === undefined) { return new InventoryFolder(InventoryLibrary.Library, this.library, this.agent); } const uuidStr = this.library.root.toString(); - if (this.library.skeleton[uuidStr]) + const skel = this.library.skeleton.get(uuidStr); + if (skel) { - return this.library.skeleton[uuidStr]; + return skel; } else { return new InventoryFolder(InventoryLibrary.Library, this.library, this.agent); } } - getRootFolderMain(): InventoryFolder + public getRootFolderMain(): InventoryFolder { if (this.main.root === undefined) { return new InventoryFolder(InventoryLibrary.Main, this.main, this.agent); } const uuidStr = this.main.root.toString(); - if (this.main.skeleton[uuidStr]) + const skel = this.main.skeleton.get(uuidStr); + if (skel) { - return this.main.skeleton[uuidStr]; + return skel; } else { return new InventoryFolder(InventoryLibrary.Main, this.main, this.agent); } } - findFolderForType(type: FolderType): UUID + + public findFolderForType(type: FolderType): UUID { const root = this.main.skeleton; - for (const key of Object.keys(root)) + for (const f of root.values()) { - const f = root[key]; - if (f.typeDefault === type) + if (f !== undefined && f.typeDefault === type) { return f.folderID; } @@ -80,27 +80,25 @@ export class Inventory return this.getRootFolderMain().folderID; } - findFolder(folderID: UUID): InventoryFolder | null + public findFolder(folderID: UUID): InventoryFolder | null { - for (const id of Object.keys(this.main.skeleton)) + const fol = this.main.skeleton.get(folderID.toString()); + if (fol !== undefined) { - if (folderID.equals(id)) + return fol; + } + for (const folder of this.main.skeleton.values()) + { + const result = folder.findFolder(folderID); + if (result !== null) { - return this.main.skeleton[id]; - } - else - { - const result = this.main.skeleton[id].findFolder(folderID); - if (result !== null) - { - return result; - } + return result; } } return null; } - async fetchInventoryItem(item: UUID): Promise + public async fetchInventoryItem(item: UUID): Promise { const params = { 'agent_id': new LLSD.UUID(this.agent.agentID), @@ -112,47 +110,48 @@ export class Inventory ] }; const response = await this.agent.currentRegion.caps.capsPostXML('FetchInventory2', params); - if (response['items'].length > 0) + if (response.items.length > 0) { - const receivedItem = response['items'][0]; - let folder = await this.findFolder(new UUID(receivedItem['parent_id'].toString())); + const receivedItem = response.items[0]; + let folder = this.findFolder(new UUID(receivedItem.parent_id.toString())); if (folder === null) { folder = this.getRootFolderMain(); } const invItem = new InventoryItem(folder, this.agent); - invItem.assetID = new UUID(receivedItem['asset_id'].toString()); - invItem.inventoryType = parseInt(receivedItem['inv_type'], 10); - invItem.type = parseInt(receivedItem['type'], 10); + invItem.assetID = new UUID(receivedItem.asset_id.toString()); + invItem.inventoryType = parseInt(receivedItem.inv_type, 10); + invItem.type = parseInt(receivedItem.type, 10); invItem.itemID = item; - if (receivedItem['permissions']['last_owner_id'] === undefined) + if (receivedItem.permissions.last_owner_id === undefined) { // TODO: OpenSim glitch - receivedItem['permissions']['last_owner_id'] = receivedItem['permissions']['owner_id']; + receivedItem.permissions.last_owner_id = receivedItem.permissions.owner_id; } invItem.permissions = { - baseMask: parseInt(receivedItem['permissions']['base_mask'], 10), - nextOwnerMask: parseInt(receivedItem['permissions']['next_owner_mask'], 10), - groupMask: parseInt(receivedItem['permissions']['group_mask'], 10), - lastOwner: new UUID(receivedItem['permissions']['last_owner_id'].toString()), - owner: new UUID(receivedItem['permissions']['owner_id'].toString()), - creator: new UUID(receivedItem['permissions']['creator_id'].toString()), - group: new UUID(receivedItem['permissions']['group_id'].toString()), - ownerMask: parseInt(receivedItem['permissions']['owner_mask'], 10), - everyoneMask: parseInt(receivedItem['permissions']['everyone_mask'], 10), + baseMask: parseInt(receivedItem.permissions.base_mask, 10), + nextOwnerMask: parseInt(receivedItem.permissions.next_owner_mask, 10), + groupMask: parseInt(receivedItem.permissions.group_mask, 10), + lastOwner: new UUID(receivedItem.permissions.last_owner_id.toString()), + owner: new UUID(receivedItem.permissions.owner_id.toString()), + creator: new UUID(receivedItem.permissions.creator_id.toString()), + group: new UUID(receivedItem.permissions.group_id.toString()), + ownerMask: parseInt(receivedItem.permissions.owner_mask, 10), + everyoneMask: parseInt(receivedItem.permissions.everyone_mask, 10), }; - invItem.flags = parseInt(receivedItem['flags'], 10); - invItem.description = receivedItem['desc']; - invItem.name = receivedItem['name']; - invItem.created = new Date(receivedItem['created_at'] * 1000); - invItem.parentID = new UUID(receivedItem['parent_id'].toString()); - invItem.saleType = parseInt(receivedItem['sale_info']['sale_type'], 10); - invItem.salePrice = parseInt(receivedItem['sale_info']['sale_price'], 10); - if (this.main.skeleton[invItem.parentID.toString()]) + invItem.flags = parseInt(receivedItem.flags, 10); + invItem.description = receivedItem.desc; + invItem.name = receivedItem.name; + invItem.created = new Date(receivedItem.created_at * 1000); + invItem.parentID = new UUID(receivedItem.parent_id.toString()); + invItem.saleType = parseInt(receivedItem.sale_info.sale_type, 10); + invItem.salePrice = parseInt(receivedItem.sale_info.sale_price, 10); + const skel = this.main.skeleton.get(invItem.parentID.toString()); + if (skel !== undefined) { - await this.main.skeleton[invItem.parentID.toString()].addItem(invItem); + await skel.addItem(invItem); } return invItem; } diff --git a/lib/classes/InventoryFolder.ts b/lib/classes/InventoryFolder.ts index 977e9af..8e39187 100644 --- a/lib/classes/InventoryFolder.ts +++ b/lib/classes/InventoryFolder.ts @@ -2,7 +2,7 @@ import * as LLSD from '@caspertech/llsd'; import * as fsSync from 'fs'; import * as fs from 'fs/promises'; import * as path from 'path'; -import { AssetType } from '../enums/AssetType'; +import type { AssetType } from '../enums/AssetType'; import { FilterResponse } from '../enums/FilterResponse'; import { FolderType } from '../enums/FolderType'; import { InventoryItemFlags } from '../enums/InventoryItemFlags'; @@ -13,44 +13,48 @@ import { Message } from '../enums/Message'; import { PacketFlags } from '../enums/PacketFlags'; import { PermissionMask } from '../enums/PermissionMask'; import { WearableType } from '../enums/WearableType'; -import { Agent } from './Agent'; +import type { Agent } from './Agent'; import { InventoryItem } from './InventoryItem'; import { LLWearable } from './LLWearable'; import { Logger } from './Logger'; import { AssetUploadRequestMessage } from './messages/AssetUploadRequest'; import { CreateInventoryFolderMessage } from './messages/CreateInventoryFolder'; import { CreateInventoryItemMessage } from './messages/CreateInventoryItem'; -import { RequestXferMessage } from './messages/RequestXfer'; -import { UpdateCreateInventoryItemMessage } from './messages/UpdateCreateInventoryItem'; +import type { RequestXferMessage } from './messages/RequestXfer'; +import type { UpdateCreateInventoryItemMessage } from './messages/UpdateCreateInventoryItem'; import { LLMesh } from './public/LLMesh'; import { Utils } from './Utils'; import { UUID } from './UUID'; +import { AssetTypeRegistry } from './AssetTypeRegistry'; +import { InventoryTypeRegistry } from './InventoryTypeRegistry'; export class InventoryFolder { - typeDefault: FolderType; - version: number; - name: string; - folderID: UUID; - parentID: UUID; - items: InventoryItem[] = []; - folders: InventoryFolder[] = []; - cacheDir: string; - agent: Agent; - library: InventoryLibrary; + public typeDefault: FolderType; + public version: number; + public name: string; + public folderID: UUID; + public parentID: UUID; + public items: InventoryItem[] = []; + public folders: InventoryFolder[] = []; + public cacheDir: string; + public agent: Agent; + public library: InventoryLibrary; private callbackID = 1; - private inventoryBase: { - skeleton: { [key: string]: InventoryFolder }, + private readonly inventoryBase: { + owner?: UUID, + skeleton: Map, root?: UUID }; - constructor(lib: InventoryLibrary, - invBase: { - skeleton: { [key: string]: InventoryFolder }, - root?: UUID - }, agent: Agent) + public constructor(lib: InventoryLibrary, + invBase: { + owner?: UUID, + skeleton: Map, + root?: UUID + }, agent: Agent) { this.agent = agent; this.library = lib; @@ -67,14 +71,13 @@ export class InventoryFolder } } - getChildFolders(): InventoryFolder[] + public getChildFolders(): InventoryFolder[] { const children: InventoryFolder[] = []; const ofi = this.folderID.toString(); - for (const uuid of Object.keys(this.inventoryBase.skeleton)) + for (const folder of this.inventoryBase.skeleton.values()) { - const folder = this.inventoryBase.skeleton[uuid]; - if (folder.parentID.toString() === ofi) + if (folder !== undefined && folder.parentID.toString() === ofi) { children.push(folder); } @@ -82,7 +85,7 @@ export class InventoryFolder return children; } - getChildFoldersRecursive(): InventoryFolder[] + public getChildFoldersRecursive(): InventoryFolder[] { const children: InventoryFolder[] = []; const toBrowse: UUID[] = [this.folderID]; @@ -93,10 +96,9 @@ export class InventoryFolder { break; } - const folder = this.inventoryBase.skeleton[uuid.toString()] + const folder = this.inventoryBase.skeleton.get(uuid.toString()); if (folder) { - for (const child of folder.getChildFolders()) { children.push(child); @@ -107,7 +109,7 @@ export class InventoryFolder return children; } - async createFolder(name: string, type: FolderType): Promise + public async createFolder(name: string, type: FolderType): Promise { const msg = new CreateInventoryFolderMessage(); msg.AgentData = { @@ -143,14 +145,14 @@ export class InventoryFolder } const folderContents: any = await this.agent.currentRegion.caps.capsPostXML(cmd, requestedFolders); - if (folderContents['folders'] && folderContents['folders'][0] && folderContents['folders'][0]['categories'] && folderContents['folders'][0]['categories'].length > 0) + if (folderContents.folders?.[0]?.categories && folderContents.folders[0].categories.length > 0) { - for (const folder of folderContents['folders'][0]['categories']) + for (const folder of folderContents.folders[0].categories) { - let folderID = folder['category_id']; + let folderID = folder.category_id; if (folderID === undefined) { - folderID = folder['folder_id']; + folderID = folder.folder_id; } if (folderID === undefined) { @@ -160,11 +162,11 @@ export class InventoryFolder if (foundFolderID.equals(msg.FolderData.FolderID)) { const newFolder = new InventoryFolder(this.library, this.agent.inventory.main, this.agent); - newFolder.typeDefault = parseInt(folder['type_default'], 10); - newFolder.version = parseInt(folder['version'], 10); - newFolder.name = String(folder['name']); + newFolder.typeDefault = parseInt(folder.type_default, 10); + newFolder.version = parseInt(folder.version, 10); + newFolder.name = String(folder.name); newFolder.folderID = new UUID(folderID); - newFolder.parentID = new UUID(folder['parent_id']); + newFolder.parentID = new UUID(folder.parent_id); this.folders.push(newFolder); return newFolder; } @@ -173,17 +175,17 @@ export class InventoryFolder throw new Error('Failed to create inventory folder'); } - async delete(saveCache: boolean = false): Promise + public async delete(saveCache = false): Promise { const { caps } = this.agent.currentRegion; const invCap = await caps.getCapability('InventoryAPIv3'); - await this.agent.currentRegion.caps.requestDelete(`${invCap}/category/${this.folderID}`) + await this.agent.currentRegion.caps.requestDelete(`${invCap}/category/${this.folderID.toString()}`) const folders = this.getChildFoldersRecursive(); for (const folder of folders) { - delete this.inventoryBase.skeleton[folder.folderID.toString()] + this.inventoryBase.skeleton.delete(folder.folderID.toString()); } if (saveCache) { @@ -199,521 +201,60 @@ export class InventoryFolder await fs.unlink(fileName); } } - catch (error: unknown) + catch (_error: unknown) { - + // ignore } } } } - private async saveCache(): Promise + public async removeItem(itemID: UUID, save = false): Promise { - const json = { - version: this.version, - items: this.items - }; - const fileName = path.join(this.cacheDir + '/' + this.folderID.toString()); - await fs.writeFile(fileName, JSON.stringify(json)); - } - - private async loadCache(): Promise - { - const fileName = path.join(this.cacheDir + '/' + this.folderID.toString()); - - try + const item = this.agent.inventory.itemsByID.get(itemID.toString()); + if (item) { - const data = await fs.readFile(fileName); - - const json: any = JSON.parse(data.toString('utf8')); - if (json['version'] >= this.version) + this.agent.inventory.itemsByID.delete(itemID.toString()); + this.items = this.items.filter((filterItem) => { - this.items = []; - for (const item of json['items']) - { - item.created = new Date(item.created.mUUID); - item.assetID = new UUID(item.assetID.mUUID); - item.parentID = new UUID(item.parentID.mUUID); - item.itemID = new UUID(item.itemID.mUUID); - item.permissions.lastOwner = new UUID(item.permissions.lastOwner.mUUID); - item.permissions.owner = new UUID(item.permissions.owner.mUUID); - item.permissions.creator = new UUID(item.permissions.creator.mUUID); - item.permissions.group = new UUID(item.permissions.group.mUUID); - await this.addItem(item, false); - } - } - else - { - throw new Error('Old version'); - } - } - catch (error: unknown) - { - throw new Error('Cache miss'); - } - } - - async removeItem(itemID: UUID, save: boolean = false): Promise - { - if (this.agent.inventory.itemsByID[itemID.toString()]) - { - delete this.agent.inventory.itemsByID[itemID.toString()]; - this.items = this.items.filter((item) => - { - return !item.itemID.equals(itemID); + return !filterItem.itemID.equals(itemID); }) } if (save) { - return this.saveCache(); + await this.saveCache(); } } - async addItem(item: InventoryItem, save: boolean = false): Promise + public async addItem(item: InventoryItem, save = false): Promise { - if (this.agent.inventory.itemsByID[item.itemID.toString()]) + if (this.agent.inventory.itemsByID.has(item.itemID.toString())) { await this.removeItem(item.itemID, false); } this.items.push(item); - this.agent.inventory.itemsByID[item.itemID.toString()] = item; + this.agent.inventory.itemsByID.set(item.itemID.toString(), item); if (save) { - return this.saveCache(); + await this.saveCache(); } } - private populateInternal(): Promise - { - return new Promise((resolve) => - { - const requestFolder = { - folder_id: new LLSD.UUID(this.folderID), - owner_id: new LLSD.UUID(this.agent.agentID), - fetch_folders: true, - fetch_items: true, - sort_order: InventorySortOrder.ByName - }; - const requestedFolders = { - 'folders': [ - requestFolder - ] - }; - - let cmd = 'FetchInventoryDescendents2'; - if (this.library === InventoryLibrary.Library) - { - cmd = 'FetchLibDescendents2'; - } - - this.agent.currentRegion.caps.capsPostXML(cmd, requestedFolders).then((folderContents: any) => - { - for (const folder of folderContents['folders'][0]['categories']) - { - let folderIDStr = folder['category_id']; - if (folderIDStr === undefined) - { - folderIDStr = folder['folder_id']; - } - const folderID = new UUID(folderIDStr); - let found = false; - for (const fld of this.folders) - { - if (fld.folderID.equals(folderID)) - { - found = true; - break; - } - } - if (found) - { - continue; - } - - const newFolder = new InventoryFolder(this.library, this.agent.inventory.main, this.agent); - newFolder.typeDefault = parseInt(folder['type_default'], 10); - newFolder.version = parseInt(folder['version'], 10); - newFolder.name = String(folder['name']); - newFolder.folderID = folderID; - newFolder.parentID = new UUID(folder['parent_id']); - this.folders.push(newFolder); - } - if (folderContents['folders'] && folderContents['folders'][0] && folderContents['folders'][0]['items']) - { - this.version = folderContents['folders'][0]['version']; - this.items = []; - for (const item of folderContents['folders'][0]['items']) - { - const invItem = new InventoryItem(this, this.agent); - invItem.assetID = new UUID(item['asset_id'].toString()); - invItem.inventoryType = item['inv_type']; - invItem.name = item['name']; - invItem.salePrice = item['sale_info']['sale_price']; - invItem.saleType = item['sale_info']['sale_type']; - invItem.created = new Date(item['created_at'] * 1000); - invItem.parentID = new UUID(item['parent_id'].toString()); - invItem.flags = item['flags']; - invItem.itemID = new UUID(item['item_id'].toString()); - invItem.description = item['desc']; - invItem.type = item['type']; - if (item['permissions']['last_owner_id'] === undefined) - { - // TODO: OpenSim Glitch; - item['permissions']['last_owner_id'] = item['permissions']['owner_id']; - } - invItem.permissions = { - baseMask: item['permissions']['base_mask'], - groupMask: item['permissions']['group_mask'], - nextOwnerMask: item['permissions']['next_owner_mask'], - ownerMask: item['permissions']['owner_mask'], - everyoneMask: item['permissions']['everyone_mask'], - lastOwner: new UUID(item['permissions']['last_owner_id'].toString()), - owner: new UUID(item['permissions']['owner_id'].toString()), - creator: new UUID(item['permissions']['creator_id'].toString()), - group: new UUID(item['permissions']['group_id'].toString()) - }; - this.addItem(invItem, false); - } - this.saveCache().then(() => - { - resolve(); - }).catch(() => - { - // Resolve anyway - resolve(); - }); - } - else - { - resolve(); - } - }); - }); - } - - populate(useCached = true): Promise + public async populate(useCached = true): Promise { if (!useCached) { - return this.populateInternal(); + await this.populateInternal(); + return; } - return new Promise((resolve, reject) => + try { - this.loadCache().then(() => - { - resolve(); - }).catch(() => - { - this.populateInternal().then(() => - { - resolve(); - }).catch((erro: Error) => - { - reject(erro); - }); - }); - }); - } - - private uploadInventoryAssetLegacy(assetType: AssetType, inventoryType: InventoryType, data: Buffer, name: string, description: string, flags: InventoryItemFlags): Promise - { - return new Promise(async(resolve, reject) => + await this.loadCache(); + } + catch(_e: unknown) { - // Send an AssetUploadRequest and a CreateInventoryRequest simultaneously - const msg = new AssetUploadRequestMessage(); - const transactionID = UUID.random(); - msg.AssetBlock = { - StoreLocal: false, - Type: assetType, - Tempfile: false, - TransactionID: transactionID, - AssetData: Buffer.allocUnsafe(0) - }; - - const callbackID = ++this.callbackID; - - - const createMsg = new CreateInventoryItemMessage(); - - let wearableType = WearableType.Shape; - if (inventoryType === InventoryType.Wearable) - { - const wearable = new LLWearable(data.toString('utf-8')); - wearableType = wearable.type; - } - else - { - const wearableInFlags = flags & InventoryItemFlags.FlagsSubtypeMask; - if (wearableInFlags > 0) - { - wearableType = wearableInFlags; - } - } - - createMsg.AgentData = { - AgentID: this.agent.agentID, - SessionID: this.agent.currentRegion.circuit.sessionID - }; - createMsg.InventoryBlock = { - CallbackID: callbackID, - FolderID: this.folderID, - TransactionID: transactionID, - NextOwnerMask: (1 << 13) | (1 << 14) | (1 << 15) | (1 << 19), - Type: assetType, - InvType: inventoryType, - WearableType: wearableType, - Name: Utils.StringToBuffer(name), - Description: Utils.StringToBuffer(description) - }; - - this.agent.currentRegion.circuit.waitForMessage(Message.UpdateCreateInventoryItem, 10000, (message: UpdateCreateInventoryItemMessage) => - { - if (message.InventoryData[0].CallbackID === callbackID) - { - return FilterResponse.Finish; - } - else - { - return FilterResponse.NoMatch; - } - }).then((result: UpdateCreateInventoryItemMessage) => - { - if (!result.InventoryData || result.InventoryData.length < 1) - { - reject('Failed to create inventory item for wearable'); - } - resolve(result.InventoryData[0].ItemID); - }); - - - if (data.length + 100 < 1200) - { - msg.AssetBlock.AssetData = data; - this.agent.currentRegion.circuit.sendMessage(msg, PacketFlags.Reliable); - this.agent.currentRegion.circuit.sendMessage(createMsg, PacketFlags.Reliable); - } - else - { - this.agent.currentRegion.circuit.sendMessage(msg, PacketFlags.Reliable); - this.agent.currentRegion.circuit.sendMessage(createMsg, PacketFlags.Reliable); - const result: RequestXferMessage = await this.agent.currentRegion.circuit.waitForMessage(Message.RequestXfer, 10000); - await this.agent.currentRegion.circuit.XferFileUp(result.XferID.ID, data); - } - }); - } - - private uploadInventoryItem(assetType: AssetType, inventoryType: InventoryType, data: Buffer, name: string, description: string, flags: InventoryItemFlags): Promise - { - return new Promise((resolve, reject) => - { - let wearableType = WearableType.Shape; - const wearableInFlags = flags & InventoryItemFlags.FlagsSubtypeMask; - if (wearableInFlags > 0) - { - wearableType = wearableInFlags; - } - - const transactionID = UUID.zero(); - const callbackID = ++this.callbackID; - const msg = new CreateInventoryItemMessage(); - msg.AgentData = { - AgentID: this.agent.agentID, - SessionID: this.agent.currentRegion.circuit.sessionID - }; - msg.InventoryBlock = { - CallbackID: callbackID, - FolderID: this.folderID, - TransactionID: transactionID, - NextOwnerMask: (1 << 13) | (1 << 14) | (1 << 15) | (1 << 19), - Type: assetType, - InvType: inventoryType, - WearableType: wearableType, - Name: Utils.StringToBuffer(name), - Description: Utils.StringToBuffer(description) - }; - this.agent.currentRegion.circuit.waitForMessage(Message.UpdateCreateInventoryItem, 10000, (message: UpdateCreateInventoryItemMessage) => - { - if (message.InventoryData[0].CallbackID === callbackID) - { - return FilterResponse.Finish; - } - else - { - return FilterResponse.NoMatch; - } - }).then((createInventoryMsg: UpdateCreateInventoryItemMessage) => - { - switch (inventoryType) - { - case InventoryType.Notecard: - { - this.agent.currentRegion.caps.capsPostXML('UpdateNotecardAgentInventory', { - 'item_id': new LLSD.UUID(createInventoryMsg.InventoryData[0].ItemID.toString()), - }).then((result: any) => - { - if (result['uploader']) - { - const uploader = result['uploader']; - this.agent.currentRegion.caps.capsRequestUpload(uploader, data).then((uploadResult: any) => - { - if (uploadResult['state'] && uploadResult['state'] === 'complete') - { - const itemID: UUID = createInventoryMsg.InventoryData[0].ItemID; - resolve(itemID); - } - else - { - reject(new Error('Asset upload failed')) - } - }).catch((err) => - { - reject(err); - }); - } - else - { - reject(new Error('Invalid response when attempting to request upload URL for notecard')); - } - }).catch((err) => - { - reject(err); - }); - break; - } - case InventoryType.Settings: - { - this.agent.currentRegion.caps.capsPostXML('UpdateSettingsAgentInventory', { - 'item_id': new LLSD.UUID(createInventoryMsg.InventoryData[0].ItemID.toString()), - }).then((result: any) => - { - if (result['uploader']) - { - const uploader = result['uploader']; - this.agent.currentRegion.caps.capsRequestUpload(uploader, data).then((uploadResult: any) => - { - if (uploadResult['state'] && uploadResult['state'] === 'complete') - { - const itemID: UUID = createInventoryMsg.InventoryData[0].ItemID; - resolve(itemID); - } - else - { - reject(new Error('Asset upload failed')) - } - }).catch((err) => - { - reject(err); - }); - } - else - { - reject(new Error('Invalid response when attempting to request upload URL for notecard')); - } - }).catch((err) => - { - reject(err); - }); - break; - } - case InventoryType.Gesture: - { - this.agent.currentRegion.caps.isCapAvailable('UpdateGestureAgentInventory').then((available) => - { - if (available) - { - this.agent.currentRegion.caps.capsPostXML('UpdateGestureAgentInventory', { - 'item_id': new LLSD.UUID(createInventoryMsg.InventoryData[0].ItemID.toString()), - }).then((result: any) => - { - if (result['uploader']) - { - const uploader = result['uploader']; - this.agent.currentRegion.caps.capsRequestUpload(uploader, data).then((uploadResult: any) => - { - if (uploadResult['state'] && uploadResult['state'] === 'complete') - { - const itemID: UUID = createInventoryMsg.InventoryData[0].ItemID; - resolve(itemID); - } - else - { - reject(new Error('Asset upload failed')) - } - }).catch((err) => - { - reject(err); - }); - } - else - { - reject(new Error('Invalid response when attempting to request upload URL for notecard')); - } - }).catch((err) => - { - reject(err); - }); - } - else - { - this.uploadInventoryAssetLegacy(assetType, inventoryType, data, name, description, flags).then((invItemID: UUID) => - { - resolve(invItemID); - }).catch((err: Error) => - { - reject(err); - }); - } - }); - break; - } - case InventoryType.Script: - case InventoryType.LSL: - { - this.agent.currentRegion.caps.capsPostXML('UpdateScriptAgent', { - 'item_id': new LLSD.UUID(createInventoryMsg.InventoryData[0].ItemID.toString()), - 'target': 'mono' - }).then((result: any) => - { - if (result['uploader']) - { - const uploader = result['uploader']; - this.agent.currentRegion.caps.capsRequestUpload(uploader, data).then((uploadResult: any) => - { - if (uploadResult['state'] && uploadResult['state'] === 'complete') - { - const itemID: UUID = createInventoryMsg.InventoryData[0].ItemID; - resolve(itemID); - } - else - { - reject(new Error('Asset upload failed')) - } - }).catch((err) => - { - reject(err); - }); - } - else - { - reject(new Error('Invalid response when attempting to request upload URL for notecard')); - } - }).catch((err) => - { - reject(err); - }); - break; - } - default: - { - reject(new Error('Currently unsupported CreateInventoryType: ' + inventoryType)); - } - } - }).catch(() => - { - reject(new Error('Timed out waiting for UpdateCreateInventoryItem')); - }); - this.agent.currentRegion.circuit.sendMessage(msg, PacketFlags.Reliable); - }); + await this.populateInternal(); + } } public async uploadAsset(type: AssetType, inventoryType: InventoryType, data: Buffer, name: string, description: string, flags: InventoryItemFlags = InventoryItemFlags.None): Promise @@ -721,7 +262,6 @@ export class InventoryFolder switch (inventoryType) { case InventoryType.Wearable: - case InventoryType.Bodypart: { // Wearables have to be uploaded using the legacy method and then created const invItemID = await this.uploadInventoryAssetLegacy(type, inventoryType, data, name, description, flags); @@ -739,9 +279,9 @@ export class InventoryFolder case InventoryType.Landmark: case InventoryType.Notecard: case InventoryType.Gesture: - case InventoryType.Script: case InventoryType.LSL: case InventoryType.Settings: + case InventoryType.Material: { // These types must be created first and then modified const invItemID: UUID = await this.uploadInventoryItem(type, inventoryType, data, name, description, flags); @@ -756,16 +296,17 @@ export class InventoryFolder } return item; } + default: + break; } const uploadCost = await this.agent.currentRegion.getUploadCost(); Logger.Info('[' + name + ']'); - const httpType = Utils.AssetTypeToHTTPAssetType(type); const response = await this.agent.currentRegion.caps.capsPostXML('NewFileAgentInventory', { 'folder_id': new LLSD.UUID(this.folderID.toString()), - 'asset_type': httpType, - 'inventory_type': Utils.HTTPAssetTypeToCapInventoryType(httpType), + 'asset_type': AssetTypeRegistry.getTypeName(type), + 'inventory_type': InventoryTypeRegistry.getTypeName(inventoryType), 'name': name, 'description': description, 'everyone_mask': PermissionMask.All, @@ -774,13 +315,13 @@ export class InventoryFolder 'expected_upload_cost': uploadCost }); - if (response['state'] === 'upload') + if (response.state === 'upload') { - const uploadURL = response['uploader']; + const uploadURL = response.uploader; const responseUpload = await this.agent.currentRegion.caps.capsRequestUpload(uploadURL, data); - if (responseUpload['new_inventory_item'] !== undefined) + if (responseUpload.new_inventory_item !== undefined) { - const invItemID = new UUID(responseUpload['new_inventory_item'].toString()); + const invItemID = new UUID(responseUpload.new_inventory_item.toString()); const item: InventoryItem | null = await this.agent.inventory.fetchInventoryItem(invItemID); if (item === null) { @@ -797,9 +338,9 @@ export class InventoryFolder throw new Error('Unable to upload asset'); } } - else if (response['error']) + else if (response.error) { - throw new Error(response['error']['message']); + throw new Error(response.error.message); } else { @@ -807,7 +348,7 @@ export class InventoryFolder } } - checkCopyright(creatorID: UUID): void + public checkCopyright(creatorID: UUID): void { if (!creatorID.equals(this.agent.agentID) && !creatorID.isZero()) { @@ -815,7 +356,7 @@ export class InventoryFolder } } - findFolder(id: UUID): InventoryFolder | null + public findFolder(id: UUID): InventoryFolder | null { for (const folder of this.folders) { @@ -832,14 +373,17 @@ export class InventoryFolder return null; } - async uploadMesh(name: string, description: string, mesh: Buffer, confirmCostCallback: (cost: number) => Promise): Promise + public async uploadMesh(name: string, description: string, mesh: Buffer, confirmCostCallback: (cost: number) => Promise): Promise { const decodedMesh = await LLMesh.from(mesh); - this.checkCopyright(decodedMesh.creatorID); + if (decodedMesh.creatorID !== undefined) + { + this.checkCopyright(decodedMesh.creatorID); + } const faces = []; - const faceCount = decodedMesh.lodLevels['high_lod'].length; + const faceCount = decodedMesh.lodLevels.high_lod.length; for (let x = 0; x < faceCount; x++) { faces.push({ @@ -869,12 +413,12 @@ export class InventoryFolder 'asset_type': 'mesh', 'inventory_type': 'object', 'folder_id': new LLSD.UUID(this.folderID.toString()), - 'texture_folder_id': new LLSD.UUID(await this.agent.inventory.findFolderForType(FolderType.Texture)), + 'texture_folder_id': new LLSD.UUID(this.agent.inventory.findFolderForType(FolderType.Texture)), 'everyone_mask': PermissionMask.All, 'group_mask': PermissionMask.All, 'next_owner_mask': PermissionMask.All }; - let result; + let result: any = null; try { result = await this.agent.currentRegion.caps.capsPostXML('NewFileAgentInventory', uploadMap); @@ -883,20 +427,20 @@ export class InventoryFolder { console.error(error); } - if (result['state'] === 'upload' && result['upload_price'] !== undefined) + if (result.state === 'upload' && result.upload_price !== undefined) { - const cost = result['upload_price']; + const cost = result.upload_price; if (await confirmCostCallback(cost)) { - const uploader = result['uploader']; + const uploader = result.uploader; const uploadResult = await this.agent.currentRegion.caps.capsPerformXMLPost(uploader, assetResources); - if (uploadResult['new_inventory_item'] && uploadResult['new_asset']) + if (uploadResult.new_inventory_item && uploadResult.new_asset) { - const inventoryItem = new UUID(uploadResult['new_inventory_item'].toString()); + const inventoryItem = new UUID(uploadResult.new_inventory_item.toString()); const item = await this.agent.inventory.fetchInventoryItem(inventoryItem); if (item !== null) { - item.assetID = new UUID(uploadResult['new_asset'].toString()); + item.assetID = new UUID(uploadResult.new_asset.toString()); await this.addItem(item, false); return item; } @@ -920,4 +464,546 @@ export class InventoryFolder throw new Error('Upload failed'); } } + + private async saveCache(): Promise + { + const json = { + version: this.version, + childItems: this.items, + childFolders: this.folders + }; + + const fileName = path.join(this.cacheDir + '/' + this.folderID.toString() + '.json'); + + const replacer = (key: string, value: unknown): unknown => + { + if (key === 'container' || key === 'agent' || key === 'folders' || key === 'items' || key === 'cacheDir' || key === 'inventoryBase') + { + return undefined; + } + return value; + }; + + await fs.writeFile(fileName, JSON.stringify(json, replacer)); + } + + private async loadCache(): Promise + { + const fileName = path.join(this.cacheDir + '/' + this.folderID.toString() + ".json"); + + try + { + const data = await fs.readFile(fileName); + + const json = JSON.parse(data.toString('utf8')) as { + version: number, + childFolders: { + typeDefault: FolderType; + version: number; + name: string; + folderID: { + mUUID: string + }; + parentID: { + mUUID: string + }; + }[], + childItems: { + assetID: { + mUUID: string + }, + inventoryType: InventoryType; + name: string; + metadata: string; + salePrice: number; + saleType: number; + created: Date; + parentID: { + mUUID: string + }; + flags: InventoryItemFlags; + itemID: { + mUUID: string + }; + oldItemID?: { + mUUID: string + }; + parentPartID?: { + mUUID: string + }; + permsGranter?: string; + description: string; + type: AssetType; + callbackID: number; + permissions: { + baseMask: PermissionMask; + groupMask: PermissionMask; + nextOwnerMask: PermissionMask; + ownerMask: PermissionMask; + everyoneMask: PermissionMask; + lastOwner: { + mUUID: string + }; + owner: { + mUUID: string + }; + creator: { + mUUID: string + }; + group: { + mUUID: string + }; + groupOwned?: boolean + } + }[] + }; + if (json.version >= this.version) + { + this.items = []; + for (const folder of json.childFolders) + { + let f = this.findFolder(new UUID(folder.folderID.mUUID)); + if (f !== null) + { + continue; + } + f = new InventoryFolder(this.library, this.inventoryBase, this.agent); + f.parentID = this.folderID; + f.typeDefault = folder.typeDefault; + f.version = folder.version; + f.name = folder.name; + f.folderID = new UUID(folder.folderID.mUUID); + this.folders.push(f); + } + for (const item of json.childItems) + { + const i = new InventoryItem(this, this.agent); + i.created = new Date(item.created); + i.assetID = new UUID(item.assetID.mUUID); + i.parentID = this.folderID; + i.itemID = new UUID(item.itemID.mUUID); + i.permissions = { + lastOwner: new UUID(item.permissions.lastOwner.mUUID), + owner: new UUID(item.permissions.owner.mUUID), + creator: new UUID(item.permissions.creator.mUUID), + group: new UUID(item.permissions.group.mUUID), + baseMask: item.permissions.baseMask, + groupMask: item.permissions.groupMask, + nextOwnerMask: item.permissions.nextOwnerMask, + ownerMask: item.permissions.ownerMask, + everyoneMask: item.permissions.everyoneMask + }; + i.inventoryType = item.inventoryType; + i.name = item.name; + i.metadata = item.metadata; + i.salePrice = item.salePrice; + i.saleType = item.saleType; + i.flags = item.flags; + i.description= item.description; + i.type = item.type; + await this.addItem(i, false); + } + } + else + { + throw new Error('Old version'); + } + } + catch (_error: unknown) + { + throw new Error('Cache miss'); + } + } + + private async populateInternal(): Promise + { + const requestFolder = { + folder_id: new LLSD.UUID(this.folderID), + owner_id: new LLSD.UUID(this.agent.agentID), + fetch_folders: true, + fetch_items: true, + sort_order: InventorySortOrder.ByName + }; + const requestedFolders = { + 'folders': [ + requestFolder + ] + }; + + let cmd = 'FetchInventoryDescendents2'; + if (this.library === InventoryLibrary.Library) + { + cmd = 'FetchLibDescendents2'; + } + + const folderContents = await this.agent.currentRegion.caps.capsPostXML(cmd, requestedFolders) as unknown as { + folders: { + categories: { + category_id: string, + folder_id: string, + type_default: string, + version: string, + name: string, + parent_id: string + }[] + items: { + asset_id: string, + inv_type: InventoryType, + name: string, + sale_info: { + sale_price: number, + sale_type: number + }, + created_at: number, + parent_id: string, + flags: number, + item_id: string, + desc: string, + type: number, + permissions: { + last_owner_id: string, + owner_id: string, + base_mask: number, + group_mask: number, + next_owner_mask: number, + owner_mask: number, + everyone_mask: number, + creator_id: string, + group_id: string + } + }[], + version: number + }[] + }; + for (const folder of folderContents.folders[0].categories) + { + let folderIDStr = folder.category_id; + if (folderIDStr === undefined) + { + folderIDStr = folder.folder_id; + } + const folderID = new UUID(folderIDStr); + let found = false; + for (const fld of this.folders) + { + if (fld.folderID.equals(folderID)) + { + found = true; + break; + } + } + if (found) + { + continue; + } + + const newFolder = new InventoryFolder(this.library, this.agent.inventory.main, this.agent); + newFolder.typeDefault = parseInt(folder.type_default, 10); + newFolder.version = parseInt(folder.version, 10); + newFolder.name = String(folder.name); + newFolder.folderID = folderID; + newFolder.parentID = new UUID(folder.parent_id); + this.folders.push(newFolder); + } + if (folderContents.folders?.[0]?.items) + { + this.version = folderContents.folders[0].version; + this.items = []; + for (const item of folderContents.folders[0].items) + { + const invItem = new InventoryItem(this, this.agent); + invItem.assetID = new UUID(item.asset_id.toString()); + invItem.inventoryType = item.inv_type; + invItem.name = item.name; + invItem.salePrice = item.sale_info.sale_price; + invItem.saleType = item.sale_info.sale_type; + invItem.created = new Date(item.created_at * 1000); + invItem.parentID = new UUID(item.parent_id.toString()); + invItem.flags = item.flags; + invItem.itemID = new UUID(item.item_id.toString()); + invItem.description = item.desc; + invItem.type = item.type; + if (item.permissions.last_owner_id === undefined) + { + // TODO: OpenSim Glitch; + item.permissions.last_owner_id = item.permissions.owner_id; + } + invItem.permissions = { + baseMask: item.permissions.base_mask, + groupMask: item.permissions.group_mask, + nextOwnerMask: item.permissions.next_owner_mask, + ownerMask: item.permissions.owner_mask, + everyoneMask: item.permissions.everyone_mask, + lastOwner: new UUID(item.permissions.last_owner_id.toString()), + owner: new UUID(item.permissions.owner_id.toString()), + creator: new UUID(item.permissions.creator_id.toString()), + group: new UUID(item.permissions.group_id.toString()) + }; + await this.addItem(invItem, false); + } + await this.saveCache(); + } + } + + private async uploadInventoryAssetLegacy( + assetType: AssetType, + inventoryType: InventoryType, + data: Buffer, + name: string, + description: string, + flags: InventoryItemFlags + ): Promise + { + const transactionID = UUID.random(); + const assetUploadMsg = new AssetUploadRequestMessage(); + assetUploadMsg.AssetBlock = { + StoreLocal: false, + Type: assetType, + Tempfile: false, + TransactionID: transactionID, + AssetData: Buffer.allocUnsafe(0) // Initially empty; will be set later if data is small + }; + + const callbackID = ++this.callbackID; + const createInventoryMsg = new CreateInventoryItemMessage(); + let wearableType = WearableType.Shape; + if (inventoryType === InventoryType.Wearable) + { + const wearable = new LLWearable(data.toString('utf-8')); + wearableType = wearable.type; + } + else + { + const wearableInFlags = flags & InventoryItemFlags.FlagsSubtypeMask; + if (wearableInFlags > 0) + { + wearableType = wearableInFlags; + } + } + + createInventoryMsg.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.agent.currentRegion.circuit.sessionID + }; + + createInventoryMsg.InventoryBlock = { + CallbackID: callbackID, + FolderID: this.folderID, + TransactionID: transactionID, + NextOwnerMask: (1 << 13) | (1 << 14) | (1 << 15) | (1 << 19), + Type: assetType, + InvType: inventoryType, + WearableType: wearableType, + Name: Utils.StringToBuffer(name), + Description: Utils.StringToBuffer(description) + }; + + try + { + const waitForResponse = this.agent.currentRegion.circuit.waitForMessage( + Message.UpdateCreateInventoryItem, + 10000, + (message: UpdateCreateInventoryItemMessage) => + { + return message.InventoryData[0].CallbackID === callbackID + ? FilterResponse.Finish + : FilterResponse.NoMatch; + } + ); + + if (data.length + 100 < 1200) + { + assetUploadMsg.AssetBlock.AssetData = data; + this.agent.currentRegion.circuit.sendMessage(assetUploadMsg, PacketFlags.Reliable); + this.agent.currentRegion.circuit.sendMessage(createInventoryMsg, PacketFlags.Reliable); + } + else + { + this.agent.currentRegion.circuit.sendMessage(assetUploadMsg, PacketFlags.Reliable); + this.agent.currentRegion.circuit.sendMessage(createInventoryMsg, PacketFlags.Reliable); + + const xferRequest = await this.agent.currentRegion.circuit.waitForMessage( + Message.RequestXfer, + 10000 + ); + + await this.agent.currentRegion.circuit.XferFileUp(xferRequest.XferID.ID, data); + } + + const response = await waitForResponse; + if (!response.InventoryData || response.InventoryData.length < 1) + { + throw new Error('Failed to create inventory item for wearable'); + } + + return response.InventoryData[0].ItemID; + } + catch (error) + { + throw new Error(`uploadInventoryAssetLegacy failed: ${String(error instanceof Error ? error.message : error)}`); + } + } + + private async uploadInventoryItem( + assetType: AssetType, + inventoryType: InventoryType, + data: Buffer, + name: string, + description: string, + flags: InventoryItemFlags + ): Promise + { + // Determine the wearable type based on flags + let wearableType = WearableType.Shape; + const wearableInFlags = flags & InventoryItemFlags.FlagsSubtypeMask; + if (wearableInFlags > 0) + { + wearableType = wearableInFlags; + } + + // Generate transaction ID and callback ID + const transactionID = UUID.zero(); + const callbackID = ++this.callbackID; + + // Create the CreateInventoryItemMessage + const createInventoryMsg = new CreateInventoryItemMessage(); + createInventoryMsg.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.agent.currentRegion.circuit.sessionID + }; + createInventoryMsg.InventoryBlock = { + CallbackID: callbackID, + FolderID: this.folderID, + TransactionID: transactionID, + NextOwnerMask: (1 << 13) | (1 << 14) | (1 << 15) | (1 << 19), + Type: assetType, + InvType: inventoryType, + WearableType: wearableType, + Name: Utils.StringToBuffer(name), + Description: Utils.StringToBuffer(description) + }; + + try + { + const createInventoryResponse = await this.agent.currentRegion.circuit.sendAndWaitForMessage( + createInventoryMsg, + PacketFlags.Reliable, + Message.UpdateCreateInventoryItem, + 10000, + (message: UpdateCreateInventoryItemMessage) => + { + return message.InventoryData[0].CallbackID === callbackID + ? FilterResponse.Finish + : FilterResponse.NoMatch; + } + ); + + if (!createInventoryResponse.InventoryData || createInventoryResponse.InventoryData.length < 1) + { + throw new Error('Failed to create inventory item'); + } + + const itemID: UUID = createInventoryResponse.InventoryData[0].ItemID; + if (inventoryType === InventoryType.Notecard && data.length === 0) + { + // Empty notecard we can just leave as-is + return itemID; + } + switch (inventoryType) + { + case InventoryType.Material: + case InventoryType.Notecard: + case InventoryType.Settings: + case InventoryType.LSL: + { + await this.handleStandardInventoryUpload(inventoryType, itemID, data); + return itemID; + } + case InventoryType.Gesture: + { + const isGestureCapAvailable = await this.agent.currentRegion.caps.isCapAvailable('UpdateGestureAgentInventory'); + if (isGestureCapAvailable) + { + await this.handleStandardInventoryUpload(inventoryType, itemID, data); + return itemID; + } + else + { + // Fallback to legacy upload method if Gesture caps are not available + const invItemID = await this.uploadInventoryAssetLegacy(assetType, inventoryType, data, name, description, flags); + return invItemID; + } + } + default: + throw new Error(`Currently unsupported CreateInventoryType: ${inventoryType}`); + } + } + catch (error) + { + throw new Error(`uploadInventoryItem failed: ${String(error instanceof Error ? error.message : error)}`); + } + } + + /** + * Handles the upload process for standard inventory types such as Notecard, Settings, Script, and LSL. + * @param inventoryType The type of inventory item. + * @param itemID The UUID of the created inventory item. + * @param data The data buffer to upload. + */ + private async handleStandardInventoryUpload( + inventoryType: InventoryType, + itemID: UUID, + data: Buffer + ): Promise + { + let xmlEndpoint = ''; + switch (inventoryType) + { + case InventoryType.Notecard: + xmlEndpoint = 'UpdateNotecardAgentInventory'; + break; + case InventoryType.Material: + xmlEndpoint = 'UpdateMaterialAgentInventory'; + break; + case InventoryType.Settings: + xmlEndpoint = 'UpdateSettingsAgentInventory'; + break; + case InventoryType.LSL: + xmlEndpoint = 'UpdateScriptAgent'; + break; + default: + throw new Error(`Unsupported inventory type for standard upload: ${inventoryType}`); + } + + try + { + const xmlPayload: Record = { + 'item_id': new LLSD.UUID(itemID.toString()), + }; + + if (inventoryType === InventoryType.LSL) + { + xmlPayload.target = 'mono'; + } + + const result: any = await this.agent.currentRegion.caps.capsPostXML(xmlEndpoint, xmlPayload); + + if (!result.uploader) + { + throw new Error(`Invalid response when attempting to request upload URL for ${inventoryType}`); + } + + const uploader = result.uploader; + const uploadResult: any = await this.agent.currentRegion.caps.capsRequestUpload(uploader, data); + + if (uploadResult.state !== 'complete') + { + throw new Error('Asset upload failed'); + } + } + catch (error) + { + throw new Error(`Failed to upload inventory item (${inventoryType}): ${String(error instanceof Error ? error.message : error)}`); + } + } } diff --git a/lib/classes/InventoryItem.ts b/lib/classes/InventoryItem.ts index 52c57a9..86b888f 100644 --- a/lib/classes/InventoryItem.ts +++ b/lib/classes/InventoryItem.ts @@ -1,20 +1,18 @@ import * as LLSD from '@caspertech/llsd'; -import { Subscription } from 'rxjs'; +import type { Subscription } from 'rxjs'; import * as builder from 'xmlbuilder'; import * as crypto from 'crypto'; -import { GameObject } from '..'; import { AssetType } from '../enums/AssetType'; -import { AssetTypeLL } from '../enums/AssetTypeLL'; -import { AttachmentPoint } from '../enums/AttachmentPoint'; +import type { AttachmentPoint } from '../enums/AttachmentPoint'; import { FilterResponse } from '../enums/FilterResponse'; -import { InventoryItemFlags } from '../enums/InventoryItemFlags'; +import type { InventoryItemFlags } from '../enums/InventoryItemFlags'; import { InventoryType } from '../enums/InventoryType'; import { Message } from '../enums/Message'; import { PacketFlags } from '../enums/PacketFlags'; import { PermissionMask } from '../enums/PermissionMask'; import { SaleTypeLL } from '../enums/SaleTypeLL'; -import { NewObjectEvent } from '../events/NewObjectEvent'; -import { Agent } from './Agent'; +import type { NewObjectEvent } from '../events/NewObjectEvent'; +import type { Agent } from './Agent'; import { InventoryFolder } from './InventoryFolder'; import { DetachAttachmentIntoInvMessage } from './messages/DetachAttachmentIntoInv'; import { MoveInventoryItemMessage } from './messages/MoveInventoryItem'; @@ -22,35 +20,43 @@ import { MoveTaskInventoryMessage } from './messages/MoveTaskInventory'; import { RemoveInventoryItemMessage } from './messages/RemoveInventoryItem'; import { RezObjectMessage } from './messages/RezObject'; import { RezSingleAttachmentFromInvMessage } from './messages/RezSingleAttachmentFromInv'; -import { UpdateCreateInventoryItemMessage } from './messages/UpdateCreateInventoryItem'; +import type { UpdateCreateInventoryItemMessage } from './messages/UpdateCreateInventoryItem'; import { UpdateInventoryItemMessage } from './messages/UpdateInventoryItem'; import { UpdateTaskInventoryMessage } from './messages/UpdateTaskInventory'; import { Utils } from './Utils'; import { UUID } from './UUID'; import { Vector3 } from './Vector3'; -import Timeout = NodeJS.Timeout; import { CopyInventoryItemMessage } from './messages/CopyInventoryItem'; -import { BulkUpdateInventoryEvent } from '../events/BulkUpdateInventoryEvent'; +import type { BulkUpdateInventoryEvent } from '../events/BulkUpdateInventoryEvent'; +import { AssetTypeRegistry } from './AssetTypeRegistry'; +import { InventoryTypeRegistry } from './InventoryTypeRegistry'; +import { GameObject } from './public/GameObject'; +import { GetScriptRunningMessage } from './messages/GetScriptRunning'; +import { ScriptRunningReplyMessage } from './messages/ScriptRunningReply'; +import Timeout = NodeJS.Timeout; +import { SetScriptRunningMessage } from './messages/SetScriptRunning'; export class InventoryItem { - assetID: UUID = UUID.zero(); - inventoryType: InventoryType; - name: string; - metadata: string; - salePrice: number; - saleType: number; - created: Date; - parentID: UUID; - flags: InventoryItemFlags; - itemID: UUID; - oldItemID?: UUID; - parentPartID?: UUID; - permsGranter?: UUID; - description: string; - type: AssetType; - callbackID: number; - permissions: { + public assetID: UUID = UUID.zero(); + public inventoryType: InventoryType; + public name: string; + public metadata: string; + public salePrice: number; + public saleType: number; + public created: Date; + public parentID: UUID; + public flags: InventoryItemFlags; + public itemID: UUID; + public oldItemID?: UUID; + public parentPartID?: UUID; + public permsGranter?: UUID; + public description: string; + public type: AssetType; + public callbackID: number; + public scriptRunning?: boolean; + public scriptMono?: boolean; + public permissions: { baseMask: PermissionMask; groupMask: PermissionMask; nextOwnerMask: PermissionMask; @@ -74,7 +80,12 @@ export class InventoryItem groupOwned: false }; - static fromEmbeddedAsset(lineObj: { lines: string[], lineNum: number, pos: number }, container?: GameObject | InventoryFolder, agent?: Agent): InventoryItem + public constructor(private readonly container?: GameObject | InventoryFolder, private readonly agent?: Agent) + { + + } + + public static fromEmbeddedAsset(lineObj: { lines: string[], lineNum: number, pos: number }, container?: GameObject | InventoryFolder, agent?: Agent): InventoryItem { const item: InventoryItem = new InventoryItem(container, agent); let contMetadata = false; @@ -269,74 +280,17 @@ export class InventoryItem } else if (result.key === 'type') { - const typeString = result.value as any; - item.type = parseInt(AssetTypeLL[typeString], 10); + const typeString = result.value; + const type = AssetTypeRegistry.getTypeFromTypeName(typeString); + if (type !== undefined) + { + item.type = type.type; + } } else if (result.key === 'inv_type') { - const typeString = String(result.value); - switch (typeString) - { - case 'texture': - item.inventoryType = InventoryType.Texture; - break; - case 'sound': - item.inventoryType = InventoryType.Sound; - break; - case 'callcard': - item.inventoryType = InventoryType.CallingCard; - break; - case 'landmark': - item.inventoryType = InventoryType.Landmark; - break; - case 'object': - item.inventoryType = InventoryType.Object; - break; - case 'notecard': - item.inventoryType = InventoryType.Notecard; - break; - case 'category': - item.inventoryType = InventoryType.Category; - break; - case 'root': - item.inventoryType = InventoryType.RootCategory; - break; - case 'snapshot': - item.inventoryType = InventoryType.Snapshot; - break; - case 'script': - item.inventoryType = InventoryType.LSL; - break; - case 'attach': - item.inventoryType = InventoryType.Attachment; - break; - case 'wearable': - item.inventoryType = InventoryType.Wearable; - break; - case 'animation': - item.inventoryType = InventoryType.Animation; - break; - case 'gesture': - item.inventoryType = InventoryType.Gesture; - break; - case 'mesh': - item.inventoryType = InventoryType.Mesh; - break; - case 'settings': - item.inventoryType = InventoryType.Settings; - break; - case 'widget': - item.inventoryType = InventoryType.Widget; - break; - case 'person': - item.inventoryType = InventoryType.Person; - break; - case 'material': - item.inventoryType = InventoryType.Material; - break; - default: - console.error('Unknown inventory type: ' + typeString); - } + const t = InventoryTypeRegistry.getTypeFromTypeName(String(result.value)); + item.inventoryType = t?.type ?? InventoryType.Unknown; } else if (result.key === 'flags') { @@ -344,7 +298,7 @@ export class InventoryItem } else if (result.key === 'name') { - if (result.value.indexOf('|') !== -1) + if (result.value.includes('|')) { item.name = result.value.substring(0, result.value.indexOf('|')); } @@ -356,7 +310,7 @@ export class InventoryItem } else if (result.key === 'desc') { - if (result.value.indexOf('|') !== -1) + if (result.value.includes('|')) { item.description = result.value.substring(0, result.value.indexOf('|')); } @@ -372,7 +326,7 @@ export class InventoryItem } else if (result.key === 'metadata') { - if (result.value.indexOf('|') !== -1) + if (result.value.includes('|')) { item.metadata = result.value.substring(0, result.value.indexOf('|')); } @@ -391,17 +345,17 @@ export class InventoryItem return item; } - static async fromXML(xml: string): Promise + public static async fromXML(xml: string): Promise { const parsed = await Utils.parseXML(xml); - if (!parsed['InventoryItem']) + if (!parsed.InventoryItem) { throw new Error('InventoryItem not found'); } const inventoryItem = new InventoryItem(); - const result = parsed['InventoryItem']; - let prop: any; + const result = parsed.InventoryItem; + let prop: any = null; if ((prop = Utils.getFromXMLJS(result, 'Name')) !== undefined) { inventoryItem.name = prop.toString(); @@ -425,6 +379,14 @@ export class InventoryItem { inventoryItem.type = parseInt(prop, 10); } + if ((prop = Utils.getFromXMLJS(result, 'ScriptRunning')) !== undefined) + { + inventoryItem.scriptRunning = prop === true; + } + if ((prop = Utils.getFromXMLJS(result, 'ScriptMono')) !== undefined) + { + inventoryItem.scriptMono = prop === true; + } if ((prop = Utils.getFromXMLJS(result, 'CreatorUUID')) !== undefined) { try @@ -534,12 +496,7 @@ export class InventoryItem return inventoryItem; } - constructor(private container?: GameObject | InventoryFolder, private agent?: Agent) - { - - } - - toAsset(indent: string = ''): string + public toAsset(indent = ''): string { const lines: string[] = []; lines.push('{'); @@ -558,8 +515,8 @@ export class InventoryItem lines.push('\tgroup_id\t' + this.permissions.group.toString()); lines.push('}'); lines.push('\tasset_id\t' + this.assetID.toString()); - lines.push('\ttype\t' + Utils.AssetTypeToHTTPAssetType(this.type)); - lines.push('\tinv_type\t' + Utils.InventoryTypeToLLInventoryType(this.inventoryType)); + lines.push('\ttype\t' + AssetTypeRegistry.getTypeName(this.type)); + lines.push('\tinv_type\t' + InventoryTypeRegistry.getTypeName(this.inventoryType)); lines.push('\tflags\t' + Utils.numberToFixedHex(this.flags)); lines.push('sale_info\t0'); lines.push('{'); @@ -588,7 +545,7 @@ export class InventoryItem return indent + lines.join('\n' + indent); } - getCRC(): number + public getCRC(): number { let crc = 0; crc = crc + this.itemID.CRC() >>> 0; @@ -612,7 +569,7 @@ export class InventoryItem return crc; } - async update(): Promise + public async update(): Promise { if (this.agent === undefined) { @@ -635,7 +592,7 @@ export class InventoryItem GroupMask: this.permissions.groupMask, EveryoneMask: this.permissions.everyoneMask, NextOwnerMask: this.permissions.nextOwnerMask, - GroupOwned: this.permissions.groupOwned || false, + GroupOwned: this.permissions.groupOwned ?? false, TransactionID: UUID.zero(), CallbackID: 0, Type: this.type, @@ -652,7 +609,7 @@ export class InventoryItem return this.agent.currentRegion.circuit.waitForAck(ack, 10000); } - async moveToFolder(targetFolder: InventoryFolder): Promise + public async moveToFolder(targetFolder: InventoryFolder): Promise { if (this.agent !== undefined) { @@ -729,7 +686,7 @@ export class InventoryItem } } - async delete(): Promise + public async delete(): Promise { if (this.agent !== undefined) { @@ -752,7 +709,8 @@ export class InventoryItem } } - async exportXML(): Promise + // noinspection JSUnusedGlobalSymbols + public exportXML(): string { const document = builder.create('InventoryItem'); document.ele('Name', this.name); @@ -774,10 +732,16 @@ export class InventoryItem document.ele('Flags', this.flags); document.ele('GroupID', this.permissions.group.toString()); document.ele('GroupOwned', this.permissions.groupOwned); + if (this.type === AssetType.LSLText) + { + document.ele('ScriptRunning', this.scriptRunning); + document.ele('ScriptMono', this.scriptMono); + } return document.end({ pretty: true, allowEmpty: true }); } - async detachFromAvatar(): Promise + // noinspection JSUnusedGlobalSymbols + public async detachFromAvatar(): Promise { if (this.agent === undefined) { @@ -792,7 +756,8 @@ export class InventoryItem return this.agent.currentRegion.circuit.waitForAck(ack, 10000); } - async attachToAvatar(attachPoint: AttachmentPoint, timeout: number = 10000): Promise + // noinspection JSUnusedGlobalSymbols + public async attachToAvatar(attachPoint: AttachmentPoint, timeout = 10000): Promise { return new Promise((resolve, reject) => { @@ -859,9 +824,10 @@ export class InventoryItem }); } - rezGroupInWorld(position: Vector3): Promise + // noinspection JSUnusedGlobalSymbols + public async rezGroupInWorld(position: Vector3): Promise { - return new Promise(async(resolve, reject) => + return new Promise((resolve, reject) => { if (this.agent === undefined) { @@ -919,21 +885,26 @@ export class InventoryItem const gotObjects: GameObject[] = []; - objSub = this.agent.currentRegion.clientEvents.onNewObjectEvent.subscribe(async(evt: NewObjectEvent) => + objSub = this.agent.currentRegion.clientEvents.onNewObjectEvent.subscribe((evt: NewObjectEvent) => { - if (evt.createSelected && !evt.object.resolvedAt) + (async(): Promise => { - // We need to get the full ObjectProperties so we can be sure this is or isn't a rez from inventory - await agent.currentRegion.clientCommands.region.resolveObject(evt.object, {}); - } - if (evt.createSelected && !evt.object.claimedForBuild) - { - if (evt.object.itemID !== undefined && evt.object.itemID.equals(this.itemID)) + if (evt.createSelected && !evt.object.resolvedAt) { - evt.object.claimedForBuild = true; - gotObjects.push(evt.object); + // We need to get the full ObjectProperties so we can be sure this is or isn't a rez from inventory + await agent.currentRegion.clientCommands.region.resolveObject(evt.object, {}); } - } + if (evt.createSelected && !evt.object.claimedForBuild) + { + if (evt.object.itemID?.equals(this.itemID)) + { + evt.object.claimedForBuild = true; + gotObjects.push(evt.object); + } + } + })().catch((_e: unknown) => { + // ignore + }); }); // We have no way of knowing when the cluster is finished rezzing, so we just wait for 30 seconds @@ -957,14 +928,14 @@ export class InventoryItem // Move the camera to look directly at prim for faster capture const camLocation = new Vector3(position); camLocation.z += (5) + 1; - await this.agent.currentRegion.clientCommands.agent.setCamera(camLocation, position, 256, new Vector3([-1.0, 0, 0]), new Vector3([0.0, 1.0, 0])); + this.agent.currentRegion.clientCommands.agent.setCamera(camLocation, position, 256, new Vector3([-1.0, 0, 0]), new Vector3([0.0, 1.0, 0])); this.agent.currentRegion.circuit.sendMessage(msg, PacketFlags.Reliable); }); } - rezInWorld(position: Vector3, objectScale?: Vector3): Promise + public async rezInWorld(position: Vector3, objectScale?: Vector3): Promise { - return new Promise(async(resolve, reject) => + return new Promise((resolve, reject) => { if (this.agent === undefined) { @@ -1033,32 +1004,38 @@ export class InventoryItem }, 10000); let claimedPrim = false; const agent = this.agent; - objSub = this.agent.currentRegion.clientEvents.onNewObjectEvent.subscribe(async(evt: NewObjectEvent) => + objSub = this.agent.currentRegion.clientEvents.onNewObjectEvent.subscribe((evt: NewObjectEvent) => { - if (evt.createSelected && !evt.object.resolvedAt) + (async(): Promise => { - // We need to get the full ObjectProperties so we can be sure this is or isn't a rez from inventory - await agent.currentRegion.clientCommands.region.resolveObject(evt.object, {}); - } - if (evt.createSelected && !evt.object.claimedForBuild && !claimedPrim) - { - if (evt.object.itemID !== undefined && evt.object.itemID.equals(this.itemID)) + if (evt.createSelected && !evt.object.resolvedAt) { - if (objSub !== undefined) - { - objSub.unsubscribe(); - objSub = undefined; - } - if (timeout !== undefined) - { - clearTimeout(timeout); - timeout = undefined; - } - evt.object.claimedForBuild = true; - claimedPrim = true; - resolve(evt.object); + // We need to get the full ObjectProperties so we can be sure this is or isn't a rez from inventory + await agent.currentRegion.clientCommands.region.resolveObject(evt.object, {}); } - } + if (evt.createSelected && !evt.object.claimedForBuild && !claimedPrim) + { + if (evt.object.itemID?.equals(this.itemID)) + { + if (objSub !== undefined) + { + objSub.unsubscribe(); + objSub = undefined; + } + if (timeout !== undefined) + { + clearTimeout(timeout); + timeout = undefined; + } + evt.object.claimedForBuild = true; + claimedPrim = true; + resolve(evt.object); + } + } + })().catch((_e: unknown) => + { + // ignore + }); }); // Move the camera to look directly at prim for faster capture @@ -1069,70 +1046,119 @@ export class InventoryItem } const camLocation = new Vector3(position); camLocation.z += (height / 2) + 1; - await this.agent.currentRegion.clientCommands.agent.setCamera(camLocation, position, height, new Vector3([-1.0, 0, 0]), new Vector3([0.0, 1.0, 0])); + this.agent.currentRegion.clientCommands.agent.setCamera(camLocation, position, height, new Vector3([-1.0, 0, 0]), new Vector3([0.0, 1.0, 0])); this.agent.currentRegion.circuit.sendMessage(msg, PacketFlags.Reliable); }); } - async renameInTask(task: GameObject, newName: string): Promise + // noinspection JSUnusedGlobalSymbols + public async rename(newName: string): Promise { this.name = newName; if (this.agent === undefined) { return; } - const msg = new UpdateTaskInventoryMessage(); - msg.AgentData = { - AgentID: this.agent.agentID, - SessionID: this.agent.currentRegion.circuit.sessionID - }; - msg.UpdateData = { - Key: 0, - LocalID: task.ID - }; - msg.InventoryData = { - ItemID: this.itemID, - FolderID: this.parentID, - CreatorID: this.permissions.creator, - OwnerID: this.permissions.owner, - GroupID: this.permissions.group, - BaseMask: this.permissions.baseMask, - OwnerMask: this.permissions.ownerMask, - GroupMask: this.permissions.groupMask, - EveryoneMask: this.permissions.everyoneMask, - NextOwnerMask: this.permissions.nextOwnerMask, - GroupOwned: this.permissions.groupOwned || false, - TransactionID: UUID.zero(), - Type: this.type, - InvType: this.inventoryType, - Flags: this.flags, - SaleType: this.saleType, - SalePrice: this.salePrice, - Name: Utils.StringToBuffer(this.name), - Description: Utils.StringToBuffer(this.description), - CreationDate: this.created.getTime() / 1000, - CRC: this.getCRC() - }; - return this.agent.currentRegion.circuit.waitForAck(this.agent.currentRegion.circuit.sendMessage(msg, PacketFlags.Reliable), 10000); + if (this.container instanceof GameObject) + { + const msg = new UpdateTaskInventoryMessage(); + if (this.description == '') + { + this.description = '(No Description)'; + } + msg.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.agent.currentRegion.circuit.sessionID + }; + msg.UpdateData = { + Key: 0, + LocalID: this.container.ID + }; + msg.InventoryData = { + ItemID: this.itemID, + FolderID: this.parentID, + CreatorID: this.permissions.creator, + OwnerID: this.agent.agentID, + GroupID: this.permissions.group, + BaseMask: this.permissions.baseMask, + OwnerMask: this.permissions.ownerMask, + GroupMask: this.permissions.groupMask, + EveryoneMask: this.permissions.everyoneMask, + NextOwnerMask: this.permissions.nextOwnerMask, + GroupOwned: this.permissions.groupOwned ?? false, + TransactionID: UUID.zero(), + Type: this.type, + InvType: this.inventoryType, + Flags: this.flags, + SaleType: this.saleType, + SalePrice: this.salePrice, + Name: Utils.StringToBuffer(this.name), + Description: Utils.StringToBuffer(this.description), + CreationDate: this.created.getTime() / 1000, + CRC: this.getCRC() + }; + return this.agent.currentRegion.circuit.waitForAck(this.agent.currentRegion.circuit.sendMessage(msg, PacketFlags.Reliable), 10000); + } + else if (this.container instanceof InventoryFolder) + { + this.name = newName; + return this.update(); + } + else + { + throw new Error('Item has no container, cannot be renamed'); + } } - private async waitForCallbackID(callbackID: number): Promise + // noinspection JSUnusedGlobalSymbols + public async isScriptRunning(): Promise { - if (!this.agent) + if (this.type !== AssetType.LSLText) { - throw new Error('No active agent'); + throw new Error('Item is not a script'); } - return Utils.waitOrTimeOut(this.agent.currentRegion.clientEvents.onBulkUpdateInventoryEvent, 10000, (event: BulkUpdateInventoryEvent) => + if (!(this.container instanceof GameObject)) { - for (const item of event.itemData) + throw new Error('Script can only be running inside a GameObject container') + } + + const isr = new GetScriptRunningMessage(); + isr.Script = { + ObjectID: this.container.FullID, + ItemID: this.itemID + }; + const objID = this.container.FullID; + + + const event = this.container.region.clientEvents.waitForEvent(this.container.region.clientEvents.onScriptRunningReply, (evt): FilterResponse => + { + if (evt.ItemID.equals(this.itemID) && + evt.ObjectID.equals(objID)) { - if (item.callbackID === callbackID) - { - return FilterResponse.Finish; - } + return FilterResponse.Finish; } return FilterResponse.NoMatch; }); + const legacy = this.container.region.circuit.sendAndWaitForMessage(isr, PacketFlags.Reliable, Message.ScriptRunningReply, 10000, (message: ScriptRunningReplyMessage) => + { + if (message.Script.ItemID.equals(this.itemID) && + message.Script.ObjectID.equals(objID)) + { + return FilterResponse.Finish; + } + return FilterResponse.NoMatch; + }); + const result = await Promise.race([event, legacy]); + if (result instanceof ScriptRunningReplyMessage) + { + this.scriptRunning = result.Script.Running + } + else + { + this.scriptRunning = result.Running; + this.scriptMono = result.Mono; + } + return this.scriptRunning; } public async copyTo(target: InventoryFolder, name: string): Promise @@ -1174,7 +1200,8 @@ export class InventoryItem throw new Error('Unable to locate inventory item after copy'); } - async updateScript(scriptAsset: Buffer): Promise + // noinspection JSUnusedGlobalSymbols + public async updateScript(scriptAsset: Buffer, running = true, target: 'mono' | 'lsl2' = 'mono'): Promise { if (this.agent === undefined) { @@ -1187,16 +1214,16 @@ export class InventoryItem const result: any = await this.agent.currentRegion.caps.capsPostXML('UpdateScriptTask', { 'item_id': new LLSD.UUID(this.itemID.toString()), 'task_id': new LLSD.UUID(this.container.FullID.toString()), - 'is_script_running': true, - 'target': 'mono' + 'is_script_running': running ? 1 : 0, + 'target': target }); - if (result['uploader']) + if (result.uploader) { - const uploader = result['uploader']; + const uploader = result.uploader; const uploadResult: any = await this.agent.currentRegion.caps.capsRequestUpload(uploader, scriptAsset); - if (uploadResult['state'] && uploadResult['state'] === 'complete') + if (uploadResult.state && uploadResult.state === 'complete') { - return new UUID(uploadResult['new_asset'].toString()); + return new UUID(uploadResult.new_asset.toString()); } } throw new Error('Asset upload failed'); @@ -1212,4 +1239,54 @@ export class InventoryItem throw new Error('Agent inventory not supported just yet') } } + + public async setScriptRunning(running: boolean): Promise + { + if (this.agent === undefined) + { + throw new Error('This item was created locally and can\'t be updated'); + } + if (this.type !== AssetType.LSLText) + { + throw new Error('This is not a script'); + } + if (this.container instanceof GameObject) + { + const msg = new SetScriptRunningMessage(); + msg.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.agent.currentRegion.circuit.sessionID, + }; + msg.Script = { + ObjectID: this.container.FullID, + ItemID: this.itemID, + Running: running + }; + const ack = this.agent.currentRegion.circuit.sendMessage(msg, PacketFlags.Reliable); + return this.agent.currentRegion.circuit.waitForAck(ack, 10000); + } + else + { + throw new Error('Script must be in an object to set state') + } + } + + private async waitForCallbackID(callbackID: number): Promise + { + if (!this.agent) + { + throw new Error('No active agent'); + } + return Utils.waitOrTimeOut(this.agent.currentRegion.clientEvents.onBulkUpdateInventoryEvent, 10000, (event: BulkUpdateInventoryEvent) => + { + for (const item of event.itemData) + { + if (item.callbackID === callbackID) + { + return FilterResponse.Finish; + } + } + return FilterResponse.NoMatch; + }); + } } diff --git a/lib/classes/InventoryTypeRegistry.ts b/lib/classes/InventoryTypeRegistry.ts new file mode 100644 index 0000000..b4d5e17 --- /dev/null +++ b/lib/classes/InventoryTypeRegistry.ts @@ -0,0 +1,88 @@ +import { InventoryType } from '../enums/InventoryType'; +import { AssetType } from '../enums/AssetType'; + +export class RegisteredInventoryType +{ + public type: InventoryType; + public typeName: string; + public humanName: string; + public assetTypes: AssetType[] +} + +export class InventoryTypeRegistry +{ + private static readonly invTypeByType = new Map(); + private static readonly invTypeByName = new Map(); + private static readonly invTypeByHumanName = new Map(); + + public static registerInventoryType(type: InventoryType, typeName: string, humanName: string, assetTypes: AssetType[]): void + { + const t = new RegisteredInventoryType(); + t.type = type; + t.typeName = typeName; + t.humanName = humanName; + t.assetTypes = assetTypes; + this.invTypeByType.set(type, t); + this.invTypeByName.set(typeName, t); + this.invTypeByHumanName.set(humanName, t); + } + + public static getType(type: InventoryType): RegisteredInventoryType | undefined + { + return this.invTypeByType.get(type); + } + + public static getTypeName(type: InventoryType): string + { + const t = this.getType(type); + if (t === undefined) + { + return 'invalid'; + } + return t.typeName; + } + + public static getHumanName(type: InventoryType): string + { + const t = this.getType(type); + if (t === undefined) + { + return 'Unknown'; + } + return t.humanName; + } + + public static getTypeFromTypeName(type: string): RegisteredInventoryType | undefined + { + return this.invTypeByName.get(type); + } + + public static getTypeFromHumanName(type: string): RegisteredInventoryType | undefined + { + return this.invTypeByHumanName.get(type); + } +} + +InventoryTypeRegistry.registerInventoryType(InventoryType.Texture, 'texture', 'texture', [AssetType.Texture]); +InventoryTypeRegistry.registerInventoryType(InventoryType.Sound, 'sound', 'sound', [AssetType.Sound]); +InventoryTypeRegistry.registerInventoryType(InventoryType.CallingCard, 'callcard', 'calling card', [AssetType.CallingCard]); +InventoryTypeRegistry.registerInventoryType(InventoryType.Landmark, 'landmark', 'landmark', [AssetType.Landmark]); +InventoryTypeRegistry.registerInventoryType(InventoryType.Object, 'object', 'object', [AssetType.Object]); +InventoryTypeRegistry.registerInventoryType(InventoryType.Notecard, 'notecard', 'note card', [AssetType.Notecard]); +InventoryTypeRegistry.registerInventoryType(InventoryType.Category, 'category', 'folder', []); +InventoryTypeRegistry.registerInventoryType(InventoryType.RootCategory, 'root', 'root', []); +InventoryTypeRegistry.registerInventoryType(InventoryType.LSL, 'script', 'script', [AssetType.LSLText, AssetType.LSLBytecode]); +InventoryTypeRegistry.registerInventoryType(InventoryType.Snapshot, 'snapshot', 'snapshot', [AssetType.Texture]); +InventoryTypeRegistry.registerInventoryType(InventoryType.Attachment, 'attach', 'attachment', [AssetType.Object]); +InventoryTypeRegistry.registerInventoryType(InventoryType.Wearable, 'wearable', 'wearable', [AssetType.Clothing, AssetType.Bodypart]); +InventoryTypeRegistry.registerInventoryType(InventoryType.Animation, 'animation', 'animation', [AssetType.Animation]); +InventoryTypeRegistry.registerInventoryType(InventoryType.Gesture, 'gesture', 'gesture', [AssetType.Gesture]); +InventoryTypeRegistry.registerInventoryType(InventoryType.Mesh, 'mesh', 'mesh', [AssetType.Mesh]); +InventoryTypeRegistry.registerInventoryType(InventoryType.GLTF, 'gltf', 'gltf', [AssetType.GLTF]); +InventoryTypeRegistry.registerInventoryType(InventoryType.GLTFBin, 'glbin', 'glbin', [AssetType.GLTFBin]); +InventoryTypeRegistry.registerInventoryType(InventoryType.Widget, 'widget', 'widget', [AssetType.Widget]); +InventoryTypeRegistry.registerInventoryType(InventoryType.Person, 'person', 'person', [AssetType.Person]); +InventoryTypeRegistry.registerInventoryType(InventoryType.Settings, 'settings', 'settings', [AssetType.Settings]); +InventoryTypeRegistry.registerInventoryType(InventoryType.Material, 'material', 'render material', [AssetType.Material]); + + diff --git a/lib/classes/LLAnimation.ts b/lib/classes/LLAnimation.ts new file mode 100644 index 0000000..d8b5630 --- /dev/null +++ b/lib/classes/LLAnimation.ts @@ -0,0 +1,129 @@ +import { LLAnimationJoint } from './LLAnimationJoint'; +import { Vector3 } from './Vector3'; +import { BinaryReader } from './BinaryReader'; +import { BinaryWriter } from './BinaryWriter'; + +export class LLAnimation +{ + public priority: number; + public length: number; + public expressionName: string; + public inPoint: number; + public outPoint: number; + public loop: number; + public easeInTime: number; + public easeOutTime: number; + public handPose: number; + public jointCount: number; + public joints: LLAnimationJoint[] = []; + public constraints: { + chainLength: number, + constraintType: number, + sourceVolume: string, + sourceOffset: Vector3, + targetVolume: string, + targetOffset: Vector3, + targetDir: Vector3, + easeInStart: number, + easeInStop: number, + easeOutStart: number, + easeOutStop: number + }[]; + + public constructor(buf?: Buffer) + { + if (buf) + { + const br = new BinaryReader(buf); + this.readFromBuffer(br); + } + } + + public readFromBuffer(buf: BinaryReader): void + { + const header1 = buf.readUInt16LE(); + const header2 = buf.readUInt16LE(); + if (header1 !== 1 || header2 !== 0) + { + throw new Error('Invalid LLAnimation data'); + } + this.priority = buf.readInt32LE(); + this.length = buf.readFloatLE(); + this.expressionName = buf.readCString(); + this.inPoint = buf.readFloatLE(); + this.outPoint = buf.readFloatLE(); + this.loop = buf.readInt32LE(); + this.easeInTime = buf.readFloatLE(); + this.easeOutTime = buf.readFloatLE(); + this.handPose = buf.readUInt32LE(); + this.jointCount = buf.readUInt32LE(); + + for (let x = 0; x < this.jointCount; x++) + { + const joint = new LLAnimationJoint(); + joint.readFromBuffer(buf, this.inPoint, this.outPoint); + this.joints.push(joint); + } + + this.constraints = []; + const numConstraints = buf.readInt32LE(); + for (let x = 0; x < numConstraints; x++) + { + this.constraints.push({ + chainLength: buf.readUInt8(), + constraintType: buf.readUInt8(), + sourceVolume: buf.readFixedString(16), + sourceOffset: new Vector3(buf.readFloatLE(), buf.readFloatLE(), buf.readFloatLE()), + targetVolume: buf.readFixedString(16), + targetOffset: new Vector3(buf.readFloatLE(), buf.readFloatLE(), buf.readFloatLE()), + targetDir: new Vector3(buf.readFloatLE(), buf.readFloatLE(), buf.readFloatLE()), + easeInStart: buf.readFloatLE(), + easeInStop: buf.readFloatLE(), + easeOutStart: buf.readFloatLE(), + easeOutStop: buf.readFloatLE() + }); + } + } + + public toAsset(): Buffer + { + const writer = new BinaryWriter(); + + writer.writeUInt16LE(1); + writer.writeUInt16LE(0); + + writer.writeInt32LE(this.priority); + writer.writeFloatLE(this.length); + writer.writeCString(this.expressionName); + writer.writeFloatLE(this.inPoint); + writer.writeFloatLE(this.outPoint); + writer.writeInt32LE(this.loop); + writer.writeFloatLE(this.easeInTime); + writer.writeFloatLE(this.easeOutTime); + writer.writeUInt32LE(this.handPose); + writer.writeUInt32LE(this.jointCount); + + for (const joint of this.joints) + { + joint.writeToBuffer(writer, this.inPoint, this.outPoint); + } + + writer.writeInt32LE(this.constraints.length); + + for (const constraint of this.constraints) + { + writer.writeUInt8(constraint.chainLength); + writer.writeUInt8(constraint.constraintType); + writer.writeFixedString(constraint.sourceVolume, 16); + writer.writeVector3F(constraint.sourceOffset); + writer.writeFixedString(constraint.targetVolume, 16); + writer.writeVector3F(constraint.targetOffset); + writer.writeVector3F(constraint.targetDir); + writer.writeFloatLE(constraint.easeInStart); + writer.writeFloatLE(constraint.easeInStop); + writer.writeFloatLE(constraint.easeOutStart); + writer.writeFloatLE(constraint.easeOutStop); + } + return writer.get(); + } +} diff --git a/lib/classes/LLAnimationJoint.ts b/lib/classes/LLAnimationJoint.ts new file mode 100644 index 0000000..9502eb3 --- /dev/null +++ b/lib/classes/LLAnimationJoint.ts @@ -0,0 +1,72 @@ +import { LLAnimationJointKeyFrame } from "./LLAnimationJointKeyFrame"; +import type { BinaryReader } from './BinaryReader'; +import { Utils } from "./Utils"; +import { Vector3 } from './Vector3'; +import type { BinaryWriter } from './BinaryWriter'; + +export class LLAnimationJoint +{ + public name: string; + public priority: number; + + public rotationKeyframeCount: number; + public rotationKeyframes: LLAnimationJointKeyFrame[] = []; + + public positionKeyframeCount: number; + public positionKeyframes: LLAnimationJointKeyFrame[] = []; + + + public readFromBuffer(buf: BinaryReader, inPoint: number, outPoint: number): void + { + this.name = buf.readCString(); + this.priority = buf.readInt32LE(); + this.rotationKeyframeCount = buf.readInt32LE(); + + for (let frameNum = 0; frameNum < this.rotationKeyframeCount; frameNum++) + { + const jointKF = new LLAnimationJointKeyFrame(); + jointKF.time = Utils.UInt16ToFloat(buf.readUInt16LE(), inPoint, outPoint); + const x = Utils.UInt16ToFloat(buf.readUInt16LE(), -1.0, 1.0); + const y = Utils.UInt16ToFloat(buf.readUInt16LE(), -1.0, 1.0); + const z = Utils.UInt16ToFloat(buf.readUInt16LE(), -1.0, 1.0); + jointKF.transform = new Vector3([x, y, z]); + this.rotationKeyframes.push(jointKF); + } + + this.positionKeyframeCount = buf.readInt32LE(); + + for (let frameNum = 0; frameNum < this.positionKeyframeCount; frameNum++) + { + const jointKF = new LLAnimationJointKeyFrame(); + jointKF.time = Utils.UInt16ToFloat(buf.readUInt16LE(), inPoint, outPoint); + const x = Utils.UInt16ToFloat(buf.readUInt16LE(), -1.0, 1.0); + const y = Utils.UInt16ToFloat(buf.readUInt16LE(), -1.0, 1.0); + const z = Utils.UInt16ToFloat(buf.readUInt16LE(), -1.0, 1.0); + jointKF.transform = new Vector3([x, y, z]); + this.positionKeyframes.push(jointKF); + } + } + + public writeToBuffer(writer: BinaryWriter, inPoint: number, outPoint: number): void + { + writer.writeCString(this.name); + writer.writeInt32LE(this.priority); + writer.writeInt32LE(this.rotationKeyframeCount); + for (const keyframe of this.rotationKeyframes) + { + writer.writeUInt16LE(Utils.FloatToUInt16(keyframe.time, inPoint, outPoint)); + writer.writeUInt16LE(Utils.FloatToUInt16(keyframe.transform.x, -1.0, 1.0)); + writer.writeUInt16LE(Utils.FloatToUInt16(keyframe.transform.y, -1.0, 1.0)); + writer.writeUInt16LE(Utils.FloatToUInt16(keyframe.transform.z, -1.0, 1.0)); + } + + writer.writeInt32LE(this.positionKeyframeCount); + for (const keyframe of this.positionKeyframes) + { + writer.writeUInt16LE(Utils.FloatToUInt16(keyframe.time, inPoint, outPoint)); + writer.writeUInt16LE(Utils.FloatToUInt16(keyframe.transform.x, -1.0, 1.0)); + writer.writeUInt16LE(Utils.FloatToUInt16(keyframe.transform.y, -1.0, 1.0)); + writer.writeUInt16LE(Utils.FloatToUInt16(keyframe.transform.z, -1.0, 1.0)); + } + } +} diff --git a/lib/classes/LLAnimationJointKeyFrame.ts b/lib/classes/LLAnimationJointKeyFrame.ts new file mode 100644 index 0000000..fa87b6e --- /dev/null +++ b/lib/classes/LLAnimationJointKeyFrame.ts @@ -0,0 +1,8 @@ +import type { Vector3 } from './Vector3'; + +export class LLAnimationJointKeyFrame +{ + public time: number; + public transform: Vector3; +} + diff --git a/lib/classes/LLGLTFMaterial.ts b/lib/classes/LLGLTFMaterial.ts index dc14d34..e283ba9 100644 --- a/lib/classes/LLGLTFMaterial.ts +++ b/lib/classes/LLGLTFMaterial.ts @@ -1,6 +1,6 @@ import * as LLSD from '@caspertech/llsd'; -import { LLGLTFMaterialData } from './LLGLTFMaterialData'; - +import type { LLGLTFMaterialData } from './LLGLTFMaterialData'; + export class LLGLTFMaterial { public type?: string; @@ -11,13 +11,13 @@ export class LLGLTFMaterial { if (data !== undefined) { - const header = data.slice(0, 18).toString('utf-8'); + const header = data.subarray(0, 18).toString('utf-8'); if (header.length !== 18 || header !== '\n') { throw new Error('Failed to parse LLGLTFMaterial'); } - const body = new LLSD.Binary(Array.from(data.slice(18)), 'BINARY'); + const body = new LLSD.Binary(Array.from(data.subarray(18)), 'BINARY'); const llsd = LLSD.LLSD.parseBinary(body); if (!llsd.result) { diff --git a/lib/classes/LLGLTFMaterialData.ts b/lib/classes/LLGLTFMaterialData.ts index 39e547e..a5d3d4f 100644 --- a/lib/classes/LLGLTFMaterialData.ts +++ b/lib/classes/LLGLTFMaterialData.ts @@ -124,9 +124,9 @@ export interface LLGLTFMaterialDataPart scale?: number[]; translaction?: number[] } | { - matrix?: number[] - } - )) & LLGLTFExtensionsAndExtras[], + matrix?: number[] + } + )) & LLGLTFExtensionsAndExtras[], scenes?: ({ name?: string; nodes?: number[]; diff --git a/lib/classes/LLGLTFMaterialOverride.ts b/lib/classes/LLGLTFMaterialOverride.ts index 8f0fbf9..57d82e3 100644 --- a/lib/classes/LLGLTFMaterialOverride.ts +++ b/lib/classes/LLGLTFMaterialOverride.ts @@ -1,4 +1,4 @@ -import { +import type { LLGLTFExtensionsAndExtras, LLGLTFMaterialData, LLGLTFMaterialEntry, @@ -25,6 +25,163 @@ export class LLGLTFMaterialOverride public doubleSided?: boolean; public textureTransforms?: (LLGLTFTextureTransformOverride | null)[]; + public static fromFullMaterialJSON(json: string): LLGLTFMaterialOverride + { + const obj = JSON.parse(json) as LLGLTFMaterialData; + + const over = new LLGLTFMaterialOverride(); + + if (!obj.materials?.length) + { + return over; + } + const mat = obj.materials[0]; + + const getTexture = (idx: number): { uuid: string | null, transform: LLGLTFTextureTransformOverride | null } => + { + const found: { + uuid: string | null, + transform: LLGLTFTextureTransformOverride | null + } = { + uuid: null, + transform: null + }; + + if (obj.textures && Array.isArray(obj.textures) && obj.textures.length > idx) + { + const source = obj.textures[idx].source; + if (source !== undefined && obj.images && Array.isArray(obj.images) && obj.images.length > source) + { + const img = obj.images[source]; + if ('uri' in img) + { + found.uuid = img.uri ?? null; + if (found.uuid === UUID.zero().toString()) + { + found.uuid = null; + } + } + } + const transform = obj.textures[idx].extensions?.KHR_texture_transform as { + offset?: number[], + scale?: number[], + rotation?: number + }; + if (transform) + { + found.transform = transform ?? null; + } + } + + return found; + }; + + if (mat.pbrMetallicRoughness) + { + const pbr = mat.pbrMetallicRoughness; + if (pbr.metallicFactor !== undefined) + { + over.metallicFactor = pbr.metallicFactor; + } + if (pbr.roughnessFactor !== undefined) + { + over.roughnessFactor = pbr.roughnessFactor; + } + if (pbr.baseColorFactor !== undefined && Array.isArray(pbr.baseColorFactor) && pbr.baseColorFactor.length === 4) + { + over.baseColor = pbr.baseColorFactor; + } + if (pbr.baseColorTexture?.index !== undefined) + { + const tex = getTexture(pbr.baseColorTexture.index); + if (tex?.uuid) + { + over.setTexture(0, tex.uuid); + } + if (tex?.transform) + { + over.setTransform(0, tex.transform); + } + } + if (pbr.metallicRoughnessTexture?.index !== undefined) + { + const tex = getTexture(pbr.metallicRoughnessTexture.index); + if (tex?.uuid) + { + over.setTexture(2, tex.uuid); + } + if (tex?.transform) + { + over.setTransform(2, tex.transform); + } + } + } + + if (mat.alphaMode) + { + switch (mat.alphaMode) + { + case 'BLEND': + over.alphaMode = 1; + break; + case 'MASK': + over.alphaMode = 2; + break; + } + } + else if (mat.extras?.override_alpha_mode) + { + over.alphaMode = 0; + } + + if (mat.alphaCutoff !== undefined) + { + over.alphaCutoff = mat.alphaCutoff; + } + + if (mat.emissiveFactor !== undefined) + { + over.emissiveFactor = mat.emissiveFactor; + } + + if (mat.doubleSided === true) + { + over.doubleSided = true; + } + else if (mat.extras?.override_double_sided) + { + over.doubleSided = false; + } + + if (mat.normalTexture?.index !== undefined) + { + const tex = getTexture(mat.normalTexture?.index); + if (tex?.uuid) + { + over.setTexture(1, tex.uuid); + } + if (tex?.transform) + { + over.setTransform(1, tex.transform); + } + } + + if (mat.emissiveTexture?.index !== undefined) + { + const tex = getTexture(mat.emissiveTexture?.index); + if (tex?.uuid) + { + over.setTexture(3, tex.uuid); + } + if (tex?.transform) + { + over.setTransform(3, tex.transform); + } + } + + return over; + } + public getFullMaterialJSON(): string { const obj: LLGLTFMaterialData = {}; @@ -51,15 +208,21 @@ export class LLGLTFMaterialOverride const texture = this.textures?.[texNum]; if (texture) { - obj.images!.push({ - uri: texture - }); + if (obj.images) + { + obj.images.push({ + uri: texture + }); + } } else { - obj.images!.push({ - uri: UUID.zero().toString() - }); + if (obj.images) + { + obj.images.push({ + uri: UUID.zero().toString() + }); + } } const tex: { @@ -237,161 +400,4 @@ export class LLGLTFMaterialOverride } this.textureTransforms[idx] = trans; } - - public static fromFullMaterialJSON(json: string): LLGLTFMaterialOverride - { - const obj = JSON.parse(json) as LLGLTFMaterialData; - - const over = new LLGLTFMaterialOverride(); - - if (!obj.materials?.length) - { - return over; - } - const mat = obj.materials[0]; - - const getTexture = (idx: number): { uuid: string | null, transform: LLGLTFTextureTransformOverride | null } => - { - const found: { - uuid: string | null, - transform: LLGLTFTextureTransformOverride | null - } = { - uuid: null, - transform: null - }; - - if (obj.textures && Array.isArray(obj.textures) && obj.textures.length > idx) - { - const source = obj.textures[idx].source; - if (source !== undefined && obj.images && Array.isArray(obj.images) && obj.images.length > source) - { - const img = obj.images[source]; - if ('uri' in img) - { - found.uuid = img.uri ?? null; - if (found.uuid === UUID.zero().toString()) - { - found.uuid = null; - } - } - } - const transform = obj.textures[idx].extensions?.KHR_texture_transform as { - offset?: number[], - scale?: number[], - rotation?: number - }; - if (transform) - { - found.transform = transform ?? null; - } - } - - return found; - }; - - if (mat.pbrMetallicRoughness) - { - const pbr = mat.pbrMetallicRoughness; - if (pbr.metallicFactor !== undefined) - { - over.metallicFactor = pbr.metallicFactor; - } - if (pbr.roughnessFactor !== undefined) - { - over.roughnessFactor = pbr.roughnessFactor; - } - if (pbr.baseColorFactor !== undefined && Array.isArray(pbr.baseColorFactor) && pbr.baseColorFactor.length === 4) - { - over.baseColor = pbr.baseColorFactor; - } - if (pbr.baseColorTexture?.index !== undefined) - { - const tex = getTexture(pbr.baseColorTexture.index); - if (tex && tex.uuid) - { - over.setTexture(0, tex.uuid); - } - if (tex && tex.transform) - { - over.setTransform(0, tex.transform); - } - } - if (pbr.metallicRoughnessTexture?.index !== undefined) - { - const tex = getTexture(pbr.metallicRoughnessTexture.index); - if (tex && tex.uuid) - { - over.setTexture(2, tex.uuid); - } - if (tex && tex.transform) - { - over.setTransform(2, tex.transform); - } - } - } - - if (mat.alphaMode) - { - switch (mat.alphaMode) - { - case 'BLEND': - over.alphaMode = 1; - break; - case 'MASK': - over.alphaMode = 2; - break; - } - } - else if (mat.extras && mat.extras.override_alpha_mode) - { - over.alphaMode = 0; - } - - if (mat.alphaCutoff !== undefined) - { - over.alphaCutoff = mat.alphaCutoff; - } - - if (mat.emissiveFactor !== undefined) - { - over.emissiveFactor = mat.emissiveFactor; - } - - if (mat.doubleSided === true) - { - over.doubleSided = true; - } - else if (mat.extras && mat.extras.override_double_sided) - { - over.doubleSided = false; - } - - if (mat.normalTexture?.index !== undefined) - { - const tex = getTexture(mat.normalTexture?.index); - if (tex && tex.uuid) - { - over.setTexture(1, tex.uuid); - } - if (tex && tex.transform) - { - over.setTransform(1, tex.transform); - } - } - - if (mat.emissiveTexture?.index !== undefined) - { - const tex = getTexture(mat.emissiveTexture?.index); - if (tex && tex.uuid) - { - over.setTexture(3, tex.uuid); - } - if (tex && tex.transform) - { - over.setTransform(3, tex.transform); - } - } - - return over; - } } diff --git a/lib/classes/LLGesture.ts b/lib/classes/LLGesture.ts index d1cdce8..dab8d0a 100644 --- a/lib/classes/LLGesture.ts +++ b/lib/classes/LLGesture.ts @@ -1,4 +1,4 @@ -import { LLGestureStep } from './LLGestureStep'; +import type { LLGestureStep } from './LLGestureStep'; import { LLGestureStepType } from '../enums/LLGestureStepType'; import { LLGestureAnimationStep } from './LLGestureAnimationStep'; import { UUID } from './UUID'; @@ -8,14 +8,14 @@ import { LLGestureChatStep } from './LLGestureChatStep'; export class LLGesture { - version: number; - key: number; - mask: number; - trigger: string; - replace: string; - steps: LLGestureStep[] = []; + public version: number; + public key: number; + public mask: number; + public trigger: string; + public replace: string; + public steps: LLGestureStep[] = []; - constructor(data?: string) + public constructor(data?: string) { if (data !== undefined) { @@ -91,7 +91,7 @@ export class LLGesture break; } default: - throw new Error('Unknown gesture step type: ' + stepType); + throw new Error('Unknown gesture step type: ' + String(stepType)); } if (gestureStep !== undefined) { @@ -106,7 +106,7 @@ export class LLGesture } } - toAsset(): string + public toAsset(): string { const lines: string[] = [ String(this.version), diff --git a/lib/classes/LLGestureAnimationStep.ts b/lib/classes/LLGestureAnimationStep.ts index 6849fd2..313c5e6 100644 --- a/lib/classes/LLGestureAnimationStep.ts +++ b/lib/classes/LLGestureAnimationStep.ts @@ -1,12 +1,12 @@ import { LLGestureStep } from './LLGestureStep'; import { LLGestureStepType } from '../enums/LLGestureStepType'; -import { UUID } from './UUID'; +import type { UUID } from './UUID'; import { LLGestureAnimationFlags } from '../enums/LLGestureAnimationFlags'; export class LLGestureAnimationStep extends LLGestureStep { - stepType: LLGestureStepType = LLGestureStepType.Animation; - animationName: string; - assetID: UUID; - flags: LLGestureAnimationFlags = LLGestureAnimationFlags.None; + public stepType: LLGestureStepType = LLGestureStepType.Animation; + public animationName: string; + public assetID: UUID; + public flags: LLGestureAnimationFlags = LLGestureAnimationFlags.None; } diff --git a/lib/classes/LLGestureChatStep.ts b/lib/classes/LLGestureChatStep.ts index f4720d1..0970f35 100644 --- a/lib/classes/LLGestureChatStep.ts +++ b/lib/classes/LLGestureChatStep.ts @@ -4,7 +4,7 @@ import { LLGestureChatFlags } from '../enums/LLGestureChatFlags'; export class LLGestureChatStep extends LLGestureStep { - stepType: LLGestureStepType = LLGestureStepType.Chat; - chatText: string; - flags: LLGestureChatFlags = LLGestureChatFlags.None; + public stepType: LLGestureStepType = LLGestureStepType.Chat; + public chatText: string; + public flags: LLGestureChatFlags = LLGestureChatFlags.None; } diff --git a/lib/classes/LLGestureSoundStep.ts b/lib/classes/LLGestureSoundStep.ts index 401cfb9..2e55418 100644 --- a/lib/classes/LLGestureSoundStep.ts +++ b/lib/classes/LLGestureSoundStep.ts @@ -1,12 +1,12 @@ import { LLGestureStep } from './LLGestureStep'; import { LLGestureStepType } from '../enums/LLGestureStepType'; -import { UUID } from './UUID'; +import type { UUID } from './UUID'; import { LLGestureSoundFlags } from '../enums/LLGestureSoundFlags'; export class LLGestureSoundStep extends LLGestureStep { - stepType: LLGestureStepType = LLGestureStepType.Sound; - soundName: string; - assetID: UUID; - flags: LLGestureSoundFlags = LLGestureSoundFlags.None; + public stepType: LLGestureStepType = LLGestureStepType.Sound; + public soundName: string; + public assetID: UUID; + public flags: LLGestureSoundFlags = LLGestureSoundFlags.None; } diff --git a/lib/classes/LLGestureStep.ts b/lib/classes/LLGestureStep.ts index 0931bcb..77cf7da 100644 --- a/lib/classes/LLGestureStep.ts +++ b/lib/classes/LLGestureStep.ts @@ -1,6 +1,6 @@ -import { LLGestureStepType } from '../enums/LLGestureStepType'; +import type { LLGestureStepType } from '../enums/LLGestureStepType'; export class LLGestureStep { - stepType: LLGestureStepType + public stepType: LLGestureStepType } diff --git a/lib/classes/LLGestureWaitStep.ts b/lib/classes/LLGestureWaitStep.ts index 900825f..b0e4ad1 100644 --- a/lib/classes/LLGestureWaitStep.ts +++ b/lib/classes/LLGestureWaitStep.ts @@ -4,7 +4,7 @@ import { LLGestureWaitFlags } from '../enums/LLGestureWaitFlags'; export class LLGestureWaitStep extends LLGestureStep { - stepType: LLGestureStepType = LLGestureStepType.Wait; - waitTime: number; - flags: LLGestureWaitFlags = LLGestureWaitFlags.None; + public stepType: LLGestureStepType = LLGestureStepType.Wait; + public waitTime: number; + public flags: LLGestureWaitFlags = LLGestureWaitFlags.None; } diff --git a/lib/classes/LLLindenText.ts b/lib/classes/LLLindenText.ts index cf4e172..710ed07 100644 --- a/lib/classes/LLLindenText.ts +++ b/lib/classes/LLLindenText.ts @@ -3,9 +3,12 @@ import { Utils } from './Utils'; export class LLLindenText { - version = 2; + public version = 2; - private lineObj: { + public body = ''; + public embeddedItems = new Map(); + + private readonly lineObj: { lines: string[], lineNum: number, pos: number @@ -15,10 +18,7 @@ export class LLLindenText pos: 0 }; - body = ''; - embeddedItems: { [key: number]: InventoryItem } = {}; - - constructor(data?: Buffer) + public constructor(data?: Buffer) { if (data !== undefined) { @@ -57,8 +57,8 @@ export class LLLindenText throw new Error('Error parsing Linden Text file: ' + line); } const textLength = parseInt(this.getLastToken(line), 10); - this.body = data.slice(this.lineObj.pos, this.lineObj.pos + textLength).toString(); - const remainingBuffer = data.slice(this.lineObj.pos + textLength).toString('ascii'); + this.body = data.subarray(this.lineObj.pos, this.lineObj.pos + textLength).toString(); + const remainingBuffer = data.subarray(this.lineObj.pos + textLength).toString('ascii'); this.lineObj.lines = remainingBuffer.split('\n'); this.lineObj.lineNum = 0; @@ -70,21 +70,26 @@ export class LLLindenText } } - toAsset(): Buffer + public toAsset(): Buffer { const lines: string[] = []; lines.push('Linden text version ' + this.version); lines.push('{'); - const count = Object.keys(this.embeddedItems).length; + const count = this.embeddedItems.size; lines.push('LLEmbeddedItems version 1'); lines.push('{'); lines.push('count ' + String(count)); - for (const key of Object.keys(this.embeddedItems)) + for (const key of this.embeddedItems.keys()) { + const item = this.embeddedItems.get(key); + if (item === undefined) + { + continue; + } lines.push('{'); lines.push('ext char index ' + key); lines.push('\tinv_item\t0'); - lines.push(this.embeddedItems[parseInt(key, 10)].toAsset('\t')); + lines.push(item.toAsset('\t')); lines.push('}'); } lines.push('}'); @@ -129,7 +134,7 @@ export class LLLindenText throw new Error('Invalid LLEmbeddedItems format (no inv_item)'); } const item = InventoryItem.fromEmbeddedAsset(this.lineObj); - this.embeddedItems[index] = item; + this.embeddedItems.set(index, item); line = Utils.getNotecardLine(this.lineObj); if (line !== '}') { diff --git a/lib/classes/LLSettings.spec.ts b/lib/classes/LLSettings.spec.ts new file mode 100644 index 0000000..34398d8 --- /dev/null +++ b/lib/classes/LLSettings.spec.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'vitest'; +import * as fg from 'fast-glob'; +import * as fs from 'fs'; +import * as path from 'path'; +import { LLSettings } from './LLSettings'; +import { toDeeplyMatch } from '../../testing/TestingUtils.util.spec'; + +expect.extend({ + toDeeplyMatch, +}); + +describe('LLSettings', () => +{ + const filePath = path.join(__dirname, '..', '..', '..', '..', 'assets'); + const filteredFileNames = fg.sync(filePath + '/*.bin'); + for(const file of filteredFileNames) + { + const baseName = path.basename(file); + it('Can correctly decode ' + baseName, async () => + { + const buf = await fs.promises.readFile(file); + const str = buf.toString('utf-8'); + const settings = new LLSettings(str); + + const rebuilt = settings.toAsset(); + const reParsed = new LLSettings(rebuilt); + // @ts-ignore + expect(reParsed).toDeeplyMatch(settings); + }); + } +}); diff --git a/lib/classes/LLSettings.ts b/lib/classes/LLSettings.ts index 2906689..70d0994 100644 --- a/lib/classes/LLSettings.ts +++ b/lib/classes/LLSettings.ts @@ -1,119 +1,123 @@ -import { LLSDNotationParser } from './llsd/LLSDNotationParser'; +import { LLSDReal } from "./llsd/LLSDReal"; import { UUID } from './UUID'; -import { Vector3 } from './Vector3'; -import { Vector2 } from './Vector2'; -import { Quaternion } from './Quaternion'; -import { Vector4 } from './Vector4'; import { LLSDMap } from './llsd/LLSDMap'; +import type { Vector3 } from './Vector3'; +import type { Vector2 } from './Vector2'; +import type { Quaternion } from './Quaternion'; +import { Vector4 } from './Vector4'; +import { LLSD } from "./llsd/LLSD"; +import { LLSDArray } from './llsd/LLSDArray'; +import type { LLSDType } from './llsd/LLSDType'; +import { LLSDInteger } from './llsd/LLSDInteger'; -interface UUIDObjectLLSD -{ - mUUID: string; -} interface TermConfigLLSD { - anisotropy?: number; - constant_term: number; - exp_scale: number; - exp_term: number; - linear_term: number; - width: number; + anisotropy?: LLSDReal; + constant_term: LLSDReal; + exp_scale: LLSDReal; + exp_term: LLSDReal; + linear_term: LLSDReal; + width: LLSDReal; } -interface HazeConfigLLSD +export interface HazeConfigLLSD { - ambient: number[]; - blue_density: number[]; - blue_horizon: number[]; - density_multiplier: number; - distance_multiplier: number; - haze_density: number; - haze_horizon: number; + ambient?: LLSDReal[]; + blue_density?: LLSDReal[]; + blue_horizon?: LLSDReal[]; + density_multiplier?: LLSDReal; + distance_multiplier?: LLSDReal; + haze_density?: LLSDReal; + haze_horizon?: LLSDReal; } -interface SettingsConfigLLSD +export interface SettingsConfigLLSD { + asset_id?: UUID; + flags?: LLSDInteger; absorption_config?: TermConfigLLSD[]; - bloom_id?: UUIDObjectLLSD; - cloud_color?: number[]; - cloud_id?: UUIDObjectLLSD; - cloud_pos_density1?: number[]; - cloud_pos_density2?: number[]; - cloud_scale?: number; - cloud_scroll_rate?: number[]; - cloud_shadow?: number; - cloud_variance?: number; - dome_offset?: number; - dome_radius?: number; - droplet_radius?: number; - gamma?: number; - glow?: number[]; - halo_id?: UUIDObjectLLSD; - ice_level?: number; + bloom_id?: UUID; + cloud_color?: LLSDReal[]; + cloud_id?: UUID; + cloud_pos_density1?: LLSDReal[]; + cloud_pos_density2?: LLSDReal[]; + cloud_scale?: LLSDReal; + cloud_scroll_rate?: LLSDReal[]; + cloud_shadow?: LLSDReal; + cloud_variance?: LLSDReal; + dome_offset?: LLSDReal; + dome_radius?: LLSDReal; + droplet_radius?: LLSDReal; + gamma?: LLSDReal; + glow?: LLSDReal[]; + halo_id?: UUID; + ice_level?: LLSDReal; legacy_haze?: HazeConfigLLSD; - max_y?: number; + max_y?: LLSDReal; mie_config?: TermConfigLLSD[]; - moisture_level?: number; - moon_brightness?: number; - moon_id?: UUIDObjectLLSD; - moon_rotation?: number[]; - moon_scale?: number; + moisture_level?: LLSDReal; + moon_brightness?: LLSDReal; + moon_id?: UUID; + moon_rotation?: LLSDReal[]; + moon_scale?: LLSDReal; name?: string; - planet_radius?: number; - rainbow_id?: UUIDObjectLLSD; + planet_radius?: LLSDReal; + rainbow_id?: UUID; rayleigh_config?: TermConfigLLSD[]; - sky_bottom_radius?: number; - sky_top_radius?: number; - star_brightness?: number; - sun_arc_radians?: number; - sun_id?: UUIDObjectLLSD; - sun_rotation?: number[]; - sun_scale?: number; - sunlight_color?: number[]; + sky_bottom_radius?: LLSDReal; + sky_top_radius?: LLSDReal; + star_brightness?: LLSDReal; + sun_arc_radians?: LLSDReal; + sun_id?: UUID; + sun_rotation?: LLSDReal[]; + sun_scale?: LLSDReal; + sunlight_color?: LLSDReal[]; type?: string; - frames?: Record, - tracks?: { - key_keyframe: number, + frames?: LLSDMap, + tracks?: LLSDMap<{ + key_keyframe: LLSDReal, key_name: string - }[][], - blur_multiplier?: number; - fresnel_offset?: number; - fresnel_scale?: number; - normal_map?: UUIDObjectLLSD; - normal_scale?: number[]; - scale_above?: number; - scale_below?: number; - underwater_fog_mod?: number; - water_fog_color?: number[] - water_fog_density?: number; - wave1_direction: number[]; - wave2_direction: number[]; + }>[][], + blur_multiplier?: LLSDReal; + fresnel_offset?: LLSDReal; + fresnel_scale?: LLSDReal; + normal_map?: UUID; + normal_scale?: LLSDReal[]; + scale_above?: LLSDReal; + scale_below?: LLSDReal; + underwater_fog_mod?: LLSDReal; + water_fog_color?: LLSDReal[] + water_fog_density?: LLSDReal; + wave1_direction?: LLSDReal[]; + wave2_direction?: LLSDReal[]; } export interface LLSettingsHazeConfig { - ambient: Vector3; - blueDensity: Vector3; - blueHorizon: Vector3; - densityMultiplier: number; - distanceMultiplier: number; - hazeDensity: number; - hazeHorizon: number; + ambient?: Vector3; + blueDensity?: Vector3; + blueHorizon?: Vector3; + densityMultiplier?: number; + distanceMultiplier?: number; + hazeDensity?: number; + hazeHorizon?: number; } export interface LLSettingsTermConfig { anisotropy?: number; - constantTerm: number; - expScale: number; - expTerm: number; - linearTerm: number; - width: number; + constantTerm?: number; + expScale?: number; + expTerm?: number; + linearTerm?: number; + width?: number; } export class LLSettings { + public assetID?: UUID; + public flags?: number; public absorptionConfig?: LLSettingsTermConfig[]; public bloomID?: UUID; public cloudColor?: Vector3; @@ -150,7 +154,7 @@ export class LLSettings public sunID?: UUID; public sunRotation?: Quaternion; public sunScale?: number; - public sunlightColor?: Vector4; + public sunlightColor?: Vector4 | Vector3; public type?: string; public tracks?: { keyKeyframe: number, keyName: string }[][]; public frames?: Map; @@ -167,117 +171,127 @@ export class LLSettings public wave1Direction?: Vector2; public wave2Direction?: Vector2; - public constructor(data?: string | SettingsConfigLLSD) + public constructor(data?: string | LLSDMap) { - if (data) + if (data !== undefined) { - let settings: SettingsConfigLLSD | null = null; + let settings: LLSDMap & SettingsConfigLLSD | null = null; if (typeof data === 'string') { - const result = LLSDNotationParser.parse(data); - if (!(result instanceof LLSDMap)) + if (data.startsWith('')) { - return; + settings = LLSD.parseBinary(Buffer.from(data, 'utf-8')) as LLSDMap; + } + else + { + settings = LLSD.parseNotation(data) as LLSDMap; } - settings = JSON.parse(JSON.stringify(result.toJSON())) as SettingsConfigLLSD; } else { settings = data; } - if (settings.absorption_config !== undefined) + if (settings.asset_id) + { + this.assetID = settings.asset_id; + } + if (settings.flags !== undefined) + { + this.flags = settings.flags.valueOf(); + } + if (Array.isArray(settings.absorption_config)) { this.absorptionConfig = []; for (const conf of settings.absorption_config) { this.absorptionConfig.push({ - constantTerm: conf.constant_term, - expScale: conf.exp_scale, - expTerm: conf.exp_term, - linearTerm: conf.linear_term, - width: conf.width + constantTerm: LLSettings.validateLLSDReal(conf.constant_term).valueOf(), + expScale: LLSettings.validateLLSDReal(conf.exp_scale).valueOf(), + expTerm: LLSettings.validateLLSDReal(conf.exp_term).valueOf(), + linearTerm: LLSettings.validateLLSDReal(conf.linear_term).valueOf(), + width: LLSettings.validateLLSDReal(conf.width).valueOf() }); } } if (settings.bloom_id !== undefined) { - this.bloomID = new UUID(settings.bloom_id.mUUID); + this.bloomID = LLSettings.validateUUID(settings.bloom_id); } if (settings.cloud_color !== undefined) { - this.cloudColor = new Vector3(settings.cloud_color); + this.cloudColor = LLSDArray.toVector3(settings.cloud_color); } if (settings.cloud_id !== undefined) { - this.cloudID = new UUID(settings.cloud_id.mUUID); + this.cloudID = LLSettings.validateUUID(settings.cloud_id); } if (settings.cloud_pos_density1 !== undefined) { - this.cloudPosDensity1 = new Vector3(settings.cloud_pos_density1); + this.cloudPosDensity1 = LLSDArray.toVector3(settings.cloud_pos_density1); } if (settings.cloud_pos_density2 !== undefined) { - this.cloudPosDensity2 = new Vector3(settings.cloud_pos_density2); + this.cloudPosDensity2 = LLSDArray.toVector3(settings.cloud_pos_density2); } if (settings.cloud_scale !== undefined) { - this.cloudScale = settings.cloud_scale; + this.cloudScale = LLSettings.validateLLSDReal(settings.cloud_scale).valueOf(); } if (settings.cloud_scroll_rate !== undefined) { - this.cloudScrollRate = new Vector2(settings.cloud_scroll_rate); + this.cloudScrollRate = LLSDArray.toVector2(settings.cloud_scroll_rate); } if (settings.cloud_shadow !== undefined) { - this.cloudShadow = settings.cloud_shadow; + this.cloudShadow = LLSettings.validateLLSDReal(settings.cloud_shadow).valueOf(); } if (settings.cloud_variance !== undefined) { - this.cloudVariance = settings.cloud_variance; + this.cloudVariance = LLSettings.validateLLSDReal(settings.cloud_variance).valueOf(); } if (settings.dome_offset !== undefined) { - this.domeOffset = settings.dome_offset; + this.domeOffset = LLSettings.validateLLSDReal(settings.dome_offset).valueOf(); } if (settings.dome_radius !== undefined) { - this.domeRadius = settings.dome_radius; + this.domeRadius = LLSettings.validateLLSDReal(settings.dome_radius).valueOf(); } if (settings.droplet_radius !== undefined) { - this.dropletRadius = settings.droplet_radius; + this.dropletRadius = LLSettings.validateLLSDReal(settings.droplet_radius).valueOf(); } if (settings.gamma !== undefined) { - this.gamma = settings.gamma; + this.gamma = LLSettings.validateLLSDReal(settings.gamma).valueOf(); } if (settings.glow !== undefined) { - this.glow = new Vector3(settings.glow); + this.glow = LLSDArray.toVector3(settings.glow); } if (settings.halo_id !== undefined) { - this.haloID = new UUID(settings.halo_id.mUUID); + this.haloID = LLSettings.validateUUID(settings.halo_id); } if (settings.ice_level !== undefined) { - this.iceLevel = settings.ice_level; + this.iceLevel = LLSettings.validateLLSDReal(settings.ice_level).valueOf(); } if (settings.legacy_haze !== undefined) { this.legacyHaze = { - ambient: new Vector3(settings.legacy_haze.ambient), - blueDensity: new Vector3(settings.legacy_haze.blue_density), - blueHorizon: new Vector3(settings.legacy_haze.blue_horizon), - densityMultiplier: settings.legacy_haze.density_multiplier, - distanceMultiplier: settings.legacy_haze.distance_multiplier, - hazeDensity: settings.legacy_haze.haze_density, - hazeHorizon: settings.legacy_haze.haze_horizon + ambient: settings.legacy_haze.ambient !== undefined ? LLSDArray.toVector3(settings.legacy_haze.ambient) : undefined, + blueDensity: settings.legacy_haze.blue_density !== undefined ? LLSDArray.toVector3(settings.legacy_haze.blue_density) : undefined, + blueHorizon: settings.legacy_haze.blue_horizon !== undefined ? LLSDArray.toVector3(settings.legacy_haze.blue_horizon) : undefined, + densityMultiplier: settings.legacy_haze.density_multiplier !== undefined ? LLSettings.validateLLSDReal(settings.legacy_haze.density_multiplier).valueOf() : undefined, + distanceMultiplier: settings.legacy_haze.distance_multiplier !== undefined ? LLSettings.validateLLSDReal(settings.legacy_haze.distance_multiplier).valueOf() : undefined, + hazeDensity: settings.legacy_haze.haze_density !== undefined ? LLSettings.validateLLSDReal(settings.legacy_haze.haze_density).valueOf() : undefined, + hazeHorizon: settings.legacy_haze.haze_horizon !== undefined ? LLSettings.validateLLSDReal(settings.legacy_haze.haze_horizon).valueOf() : undefined } } if (settings.max_y !== undefined) { - this.maxY = settings.max_y; + this.maxY = LLSettings.validateLLSDReal(settings.max_y).valueOf(); } if (settings.mie_config !== undefined) { @@ -285,34 +299,34 @@ export class LLSettings for (const mie of settings.mie_config) { this.mieConfig.push({ - anisotropy: mie.anisotropy, - constantTerm: mie.constant_term, - expScale: mie.exp_scale, - expTerm: mie.exp_term, - linearTerm: mie.linear_term, - width: mie.width + anisotropy: LLSettings.getRealOrUndef(mie.anisotropy), + constantTerm: LLSettings.validateLLSDReal(mie.constant_term).valueOf(), + expScale: LLSettings.validateLLSDReal(mie.exp_scale).valueOf(), + expTerm: LLSettings.validateLLSDReal(mie.exp_term).valueOf(), + linearTerm: LLSettings.validateLLSDReal(mie.linear_term).valueOf(), + width: LLSettings.validateLLSDReal(mie.width).valueOf() }); } } if (settings.moisture_level !== undefined) { - this.moistureLevel = settings.moisture_level; + this.moistureLevel = LLSettings.validateLLSDReal(settings.moisture_level).valueOf(); } if (settings.moon_brightness !== undefined) { - this.moonBrightness = settings.moon_brightness; + this.moonBrightness = LLSettings.validateLLSDReal(settings.moon_brightness).valueOf(); } if (settings.moon_id !== undefined) { - this.moonID = new UUID(settings.moon_id.mUUID); + this.moonID = LLSettings.validateUUID(settings.moon_id); } if (settings.moon_rotation !== undefined) { - this.moonRotation = new Quaternion(settings.moon_rotation); + this.moonRotation = LLSDArray.toQuaternion(settings.moon_rotation); } if (settings.moon_scale !== undefined) { - this.moonScale = settings.moon_scale; + this.moonScale = LLSettings.validateLLSDReal(settings.moon_scale).valueOf(); } if (settings.name !== undefined) { @@ -320,58 +334,65 @@ export class LLSettings } if (settings.planet_radius !== undefined) { - this.planetRadius = settings.planet_radius; + this.planetRadius = LLSettings.validateLLSDReal(settings.planet_radius).valueOf(); } if (settings.rainbow_id !== undefined) { - this.rainbowID = new UUID(settings.rainbow_id.mUUID); + this.rainbowID = LLSettings.validateUUID(settings.rainbow_id); } - if (settings.rayleigh_config !== undefined) + if (Array.isArray(settings.rayleigh_config)) { this.rayleighConfig = []; for (const ray of settings.rayleigh_config) { this.rayleighConfig.push({ - anisotropy: ray.anisotropy, - constantTerm: ray.constant_term, - expScale: ray.exp_scale, - expTerm: ray.exp_term, - linearTerm: ray.linear_term, - width: ray.width + anisotropy: LLSettings.getRealOrUndef(ray.anisotropy), + constantTerm: LLSettings.validateLLSDReal(ray.constant_term).valueOf(), + expScale: LLSettings.validateLLSDReal(ray.exp_scale).valueOf(), + expTerm: LLSettings.validateLLSDReal(ray.exp_term).valueOf(), + linearTerm: LLSettings.validateLLSDReal(ray.linear_term).valueOf(), + width: LLSettings.validateLLSDReal(ray.width).valueOf() }); } } if (settings.sky_bottom_radius !== undefined) { - this.skyBottomRadius = settings.sky_bottom_radius; + this.skyBottomRadius = LLSettings.validateLLSDReal(settings.sky_bottom_radius).valueOf(); } if (settings.sky_top_radius !== undefined) { - this.skyTopRadius = settings.sky_top_radius; + this.skyTopRadius = LLSettings.validateLLSDReal(settings.sky_top_radius).valueOf(); } if (settings.star_brightness !== undefined) { - this.starBrightness = settings.star_brightness; + this.starBrightness = LLSettings.validateLLSDReal(settings.star_brightness).valueOf(); } if (settings.sun_arc_radians !== undefined) { - this.sunArcRadians = settings.sun_arc_radians; + this.sunArcRadians = LLSettings.validateLLSDReal(settings.sun_arc_radians).valueOf(); } if (settings.sun_id !== undefined) { - this.sunID = new UUID(settings.sun_id.mUUID); + this.sunID = LLSettings.validateUUID(settings.sun_id); } if (settings.sun_rotation !== undefined) { - this.sunRotation = new Quaternion(settings.sun_rotation); + this.sunRotation = LLSDArray.toQuaternion(settings.sun_rotation); } if (settings.sun_scale !== undefined) { - this.sunScale = settings.sun_scale; + this.sunScale = LLSettings.validateLLSDReal(settings.sun_scale).valueOf(); } if (settings.sunlight_color !== undefined) { - this.sunlightColor = new Vector4(settings.sunlight_color); + if (settings?.sunlight_color.length === 4) + { + this.sunlightColor = LLSDArray.toVector4(settings.sunlight_color); + } + else + { + this.sunlightColor = LLSDArray.toVector3(settings.sunlight_color); + } } if (settings.type !== undefined) { @@ -389,8 +410,8 @@ export class LLSettings for (const tr of track) { t.push({ - keyKeyframe: tr.key_keyframe, - keyName: tr.key_name + keyKeyframe: LLSettings.validateLLSDReal(tr.key_keyframe).valueOf(), + keyName: LLSettings.validateString(tr.key_name).valueOf() }); } this.tracks.push(t); @@ -401,58 +422,259 @@ export class LLSettings this.frames = new Map(); for (const keyFrame of Object.keys(settings.frames)) { - const frame = settings.frames[keyFrame]; + const frame = settings.frames[keyFrame] as LLSDMap; this.frames.set(keyFrame, new LLSettings(frame)); } } if (settings.blur_multiplier !== undefined) { - this.blurMultiplier = settings.blur_multiplier; + this.blurMultiplier = LLSettings.validateLLSDReal(settings.blur_multiplier).valueOf(); } if (settings.fresnel_offset !== undefined) { - this.fresnelOffset = settings.fresnel_offset; + this.fresnelOffset = LLSettings.validateLLSDReal(settings.fresnel_offset).valueOf(); } if (settings.fresnel_scale !== undefined) { - this.fresnelScale = settings.fresnel_scale; + this.fresnelScale = LLSettings.validateLLSDReal(settings.fresnel_scale).valueOf(); } if (settings.normal_map !== undefined) { - this.normalMap = new UUID(settings.normal_map.mUUID); + this.normalMap = LLSettings.validateUUID(settings.normal_map); } if (settings.normal_scale !== undefined) { - this.normalScale = new Vector3(settings.normal_scale); + this.normalScale = LLSDArray.toVector3(settings.normal_scale); } if (settings.scale_above !== undefined) { - this.scaleAbove = settings.scale_above; + this.scaleAbove = LLSettings.validateLLSDReal(settings.scale_above).valueOf(); } if (settings.scale_below !== undefined) { - this.scaleBelow = settings.scale_below; + this.scaleBelow = LLSettings.validateLLSDReal(settings.scale_below).valueOf(); } if (settings.underwater_fog_mod !== undefined) { - this.underwaterFogMod = settings.underwater_fog_mod; + this.underwaterFogMod = LLSettings.validateLLSDReal(settings.underwater_fog_mod).valueOf(); } if (settings.water_fog_color !== undefined) { - this.waterFogColor = new Vector3(settings.water_fog_color); + this.waterFogColor = LLSDArray.toVector3(settings.water_fog_color); } if (settings.water_fog_density !== undefined) { - this.waterFogDensity = settings.water_fog_density; + this.waterFogDensity = LLSettings.validateLLSDReal(settings.water_fog_density).valueOf(); } if (settings.wave1_direction !== undefined) { - this.wave1Direction = new Vector2(settings.wave1_direction); + this.wave1Direction = LLSDArray.toVector2(settings.wave1_direction); } if (settings.wave2_direction !== undefined) { - this.wave2Direction = new Vector2(settings.wave2_direction); + this.wave2Direction = LLSDArray.toVector2(settings.wave2_direction); } } } + + public static encodeSettings(settings: LLSettings): LLSDType + { + return new LLSDMap({ + asset_id: settings.assetID, + flags: settings.flags !== undefined ? new LLSDInteger(settings.flags) : undefined, + absorption_config: LLSettings.encodeTermConfig(settings.absorptionConfig), + bloom_id: settings.bloomID, + cloud_color: LLSDArray.fromVector3(settings.cloudColor), + cloud_id: settings.cloudID, + cloud_pos_density1: LLSDArray.fromVector3(settings.cloudPosDensity1), + cloud_pos_density2: LLSDArray.fromVector3(settings.cloudPosDensity2), + cloud_scale: LLSDReal.parseReal(settings.cloudScale), + cloud_scroll_rate: LLSDArray.fromVector2(settings.cloudScrollRate), + cloud_shadow: LLSDReal.parseReal(settings.cloudShadow), + cloud_variance: LLSDReal.parseReal(settings.cloudVariance), + dome_offset: LLSDReal.parseReal(settings.domeOffset), + dome_radius: LLSDReal.parseReal(settings.domeRadius), + droplet_radius: LLSDReal.parseReal(settings.dropletRadius), + gamma: LLSDReal.parseReal(settings.gamma), + glow: LLSDArray.fromVector3(settings.glow), + halo_id: LLSettings.validateUUID(settings.haloID), + ice_level: LLSDReal.parseReal(settings.iceLevel), + legacy_haze: LLSettings.encodeHazeConfig(settings.legacyHaze), + max_y: LLSDReal.parseReal(settings.maxY), + mie_config: LLSettings.encodeTermConfig(settings.mieConfig), + moisture_level: LLSDReal.parseReal(settings.moistureLevel), + moon_brightness: LLSDReal.parseReal(settings.moonBrightness), + moon_id: LLSettings.validateUUID(settings.moonID), + moon_rotation: LLSDArray.fromQuaternion(settings.moonRotation), + moon_scale: LLSDReal.parseReal(settings.moonScale), + name: settings.name, + planet_radius: LLSDReal.parseReal(settings.planetRadius), + rainbow_id: settings.rainbowID, + rayleigh_config: LLSettings.encodeTermConfig(settings.rayleighConfig), + sky_bottom_radius: LLSDReal.parseReal(settings.skyBottomRadius), + sky_top_radius: LLSDReal.parseReal(settings.skyTopRadius), + star_brightness: LLSDReal.parseReal(settings.starBrightness), + sun_arc_radians: LLSDReal.parseReal(settings.sunArcRadians), + sun_id: LLSettings.validateUUID(settings.sunID), + sun_rotation: LLSDArray.fromQuaternion(settings.sunRotation), + sun_scale: LLSDReal.parseReal(settings.sunScale), + sunlight_color: (settings.sunlightColor instanceof Vector4) ? LLSDArray.fromVector4(settings.sunlightColor) : LLSDArray.fromVector3(settings.sunlightColor), + type: settings.type, + frames: LLSettings.encodeFrames(settings.frames), + tracks: LLSettings.encodeTracks(settings.tracks), + blur_multiplier: LLSDReal.parseReal(settings.blurMultiplier), + fresnel_offset: LLSDReal.parseReal(settings.fresnelOffset), + fresnel_scale: LLSDReal.parseReal(settings.fresnelScale), + normal_map: LLSettings.validateUUID(settings.normalMap), + normal_scale: LLSDArray.fromVector3(settings.normalScale), + scale_above: LLSDReal.parseReal(settings.scaleAbove), + scale_below: LLSDReal.parseReal(settings.scaleBelow), + underwater_fog_mod: LLSDReal.parseReal(settings.underwaterFogMod), + water_fog_color: LLSDArray.fromVector3(settings.waterFogColor), + water_fog_density: LLSDReal.parseReal(settings.waterFogDensity), + wave1_direction: LLSDArray.fromVector2(settings.wave1Direction), + wave2_direction: LLSDArray.fromVector2(settings.wave2Direction) + }); + } + + public toAsset(): string + { + return LLSD.toNotation(LLSettings.encodeSettings(this)); + } + + private static encodeTermConfig(conf: LLSettingsTermConfig[] | undefined): LLSDType | undefined + { + if (conf === undefined) + { + return undefined; + } + + const termConfig: LLSDType[] = []; + for(const entry of conf) + { + termConfig.push(new LLSDMap({ + anisotropy: LLSDReal.parseReal(entry.anisotropy), + constant_term: LLSDReal.parseReal(entry.constantTerm), + exp_scale: LLSDReal.parseReal(entry.expScale), + exp_term: LLSDReal.parseReal(entry.expTerm), + linear_term: LLSDReal.parseReal(entry.linearTerm), + width: LLSDReal.parseReal(entry.width), + })); + } + return termConfig; + } + + private static encodeHazeConfig(conf: LLSettingsHazeConfig | undefined): LLSDType | undefined + { + if (conf === undefined) + { + return undefined; + } + + return new LLSDMap({ + ambient: LLSDArray.fromVector3(conf.ambient), + blue_density: LLSDArray.fromVector3(conf.blueDensity), + blue_horizon: LLSDArray.fromVector3(conf.blueHorizon), + density_multiplier: LLSDReal.parseReal(conf.densityMultiplier), + distance_multiplier: LLSDReal.parseReal(conf.distanceMultiplier), + haze_density: LLSDReal.parseReal(conf.hazeDensity), + haze_horizon: LLSDReal.parseReal(conf.hazeHorizon), + }); + } + + private static encodeTracks(tr?: { keyKeyframe: number, keyName: string }[][]): LLSDMap<{ + key_keyframe: LLSDReal, + key_name: string + }>[][] | undefined + { + if (tr === undefined) + { + return undefined; + } + const outerArray: LLSDMap<{ + key_keyframe: LLSDReal, + key_name: string + }>[][] = []; + for(const inner of tr) + { + const innerArray: LLSDMap<{ + key_keyframe: LLSDReal, + key_name: string + }>[] = []; + for(const m of inner) + { + innerArray.push(new LLSDMap<{ + key_keyframe: LLSDReal, + key_name: string + }>({ + key_keyframe: new LLSDReal(m.keyKeyframe), + key_name: m.keyName + })) + } + + outerArray.push(innerArray); + } + return outerArray; + } + + private static encodeFrames(fr?: Map): LLSDMap | undefined + { + if (fr === undefined) + { + return undefined; + } + + const frames = new LLSDMap(); + for(const frameKey of fr.keys()) + { + const set = fr.get(frameKey); + if (set === undefined) + { + continue + } + frames.add(frameKey, this.encodeSettings(set)); + } + + return frames; + } + + private static validateString(val?: unknown): string + { + if (typeof val === 'string') + { + return val; + } + throw new Error('Value is not a string'); + } + + private static getRealOrUndef(val?: LLSDType): number | undefined + { + if (val === undefined) + { + return undefined; + } + return LLSettings.validateLLSDReal(val).valueOf(); + } + + private static validateLLSDReal(val?: unknown): LLSDReal + { + if (val instanceof LLSDReal) + { + return val; + } + throw new Error('Value is not an LLSDReal'); + } + + private static validateUUID(val?: LLSDType | null): UUID | undefined + { + if (val === undefined || val === null) + { + return undefined; + } + if (val instanceof UUID) + { + return val; + } + throw new Error('Value is not a UUID'); + } } diff --git a/lib/classes/LLWearable.ts b/lib/classes/LLWearable.ts index e6accc2..992ef41 100644 --- a/lib/classes/LLWearable.ts +++ b/lib/classes/LLWearable.ts @@ -1,16 +1,16 @@ import { UUID } from './UUID'; -import { WearableType } from '../enums/WearableType'; -import { SaleType } from '../enums/SaleType'; +import type { WearableType } from '../enums/WearableType'; +import type { SaleType } from '../enums/SaleType'; import { SaleTypeLL } from '../enums/SaleTypeLL'; import { Utils } from './Utils'; export class LLWearable { - name: string; - type: WearableType; - parameters: { [key: number]: number } = {}; - textures: { [key: number]: UUID } = {}; - permission: { + public name: string; + public type: WearableType; + public parameters: Record = {}; + public textures: Record = {}; + public permission: { baseMask: number, ownerMask: number, groupMask: number, @@ -31,9 +31,10 @@ export class LLWearable lastOwnerID: UUID.zero(), groupID: UUID.zero() }; - saleType: SaleType; - salePrice: number; - constructor(data?: string) + public saleType: SaleType; + public salePrice: number; + + public constructor(data?: string) { if (data !== undefined) { @@ -131,6 +132,10 @@ export class LLWearable const max = index + num; for (index; index < max; index++) { + if (lines[index + 1] === undefined) + { + break; + } const texLine = Utils.parseLine(lines[index + 1]); if (texLine.key !== null) { @@ -146,7 +151,6 @@ export class LLWearable // ignore break; default: - console.log('skipping: ' + lines[index]); break; } } @@ -155,7 +159,7 @@ export class LLWearable } } - toAsset(): string + public toAsset(): string { const lines: string[] = [ 'LLWearable version 22' @@ -190,7 +194,7 @@ export class LLWearable for (const num of Object.keys(this.textures)) { const val = this.textures[parseInt(num, 10)]; - lines.push(num + ' ' + val); + lines.push(num + ' ' + val.toString()); } return lines.join('\n') + '\n'; } diff --git a/lib/classes/Logger.ts b/lib/classes/Logger.ts index 5775f87..af8298d 100644 --- a/lib/classes/Logger.ts +++ b/lib/classes/Logger.ts @@ -2,7 +2,7 @@ import * as logger from 'winston'; import * as winston from 'winston'; import * as moment from 'moment'; import * as chalk from 'chalk'; -import { TransformableInfo } from 'logform'; +import type { TransformableInfo } from 'logform'; const formatLevel = function(text: string, level: string): string { @@ -58,22 +58,22 @@ logger.configure({ export class Logger { + public static prefix = ''; private static prefixLevel = 0; - static prefix = ''; - static increasePrefixLevel(): void + public static increasePrefixLevel(): void { this.prefixLevel++; this.generatePrefix(); } - static decreasePrefixLevel(): void + public static decreasePrefixLevel(): void { this.prefixLevel--; this.generatePrefix(); } - static generatePrefix(): void + public static generatePrefix(): void { this.prefix = ''; for (let x = 0; x < this.prefixLevel; x++) @@ -86,7 +86,7 @@ export class Logger } } - static Debug(message: string | object): void + public static Debug(message: string | object): void { if (typeof message === 'string') { @@ -94,7 +94,7 @@ export class Logger } this.Log('debug', message); } - static Info(message: string | object): void + public static Info(message: string | object): void { if (typeof message === 'string') { @@ -102,7 +102,7 @@ export class Logger } this.Log('info', message); } - static Warn(message: string | object): void + public static Warn(message: string | object): void { if (typeof message === 'string') { @@ -110,7 +110,7 @@ export class Logger } this.Log('warn', message); } - static Error(message: string | object | unknown): void + public static Error(message: unknown): void { if (typeof message !== 'object') { @@ -119,7 +119,7 @@ export class Logger this.Log('error', message); } - static Log(type: string, message: string | object | unknown): void + public static Log(type: string, message: unknown): void { if (typeof message === 'object') { diff --git a/lib/classes/LoginParameters.ts b/lib/classes/LoginParameters.ts index 2efe91c..2ae4eab 100644 --- a/lib/classes/LoginParameters.ts +++ b/lib/classes/LoginParameters.ts @@ -2,19 +2,19 @@ import { Utils } from './Utils'; export class LoginParameters { - firstName: string; - lastName: string; - password: string; - start = 'last'; - url = 'https://login.agni.lindenlab.com/cgi-bin/login.cgi'; - token?: string; - mfa_hash?: string; - agreeToTOS?: true; - readCritical?: true; + public firstName: string; + public lastName: string; + public password: string; + public start = 'last'; + public url = 'https://login.agni.lindenlab.com/cgi-bin/login.cgi'; + public token?: string; + public mfa_hash?: string; + public agreeToTOS?: true; + public readCritical?: true; - passwordPrehashed = false; + public passwordPrehashed = false; - getHashedPassword(): string + public getHashedPassword(): string { if (this.passwordPrehashed) { diff --git a/lib/classes/LoginResponse.ts b/lib/classes/LoginResponse.ts index d362d73..881f6e1 100644 --- a/lib/classes/LoginResponse.ts +++ b/lib/classes/LoginResponse.ts @@ -3,18 +3,19 @@ import { Agent } from './Agent'; import { Region } from './Region'; import { Vector3 } from './Vector3'; import * as Long from 'long'; -import { ClientEvents } from './ClientEvents'; +import type { ClientEvents } from './ClientEvents'; import { InventoryFolder } from './InventoryFolder'; -import { BotOptionFlags, LoginFlags } from '..'; +import type { BotOptionFlags} from '..'; +import { LoginFlags } from '..'; import { InventoryLibrary } from '../enums/InventoryLibrary'; export class LoginResponse { - loginFlags: LoginFlags; - loginMessage: string; - agent: Agent; - region: Region; - events: { + public loginFlags: LoginFlags; + public loginMessage: string; + public agent: Agent; + public region: Region; + public events: { categories: { categoryID: number, categoryName: string @@ -22,7 +23,7 @@ export class LoginResponse } = { categories: [] }; - classifieds: { + public classifieds: { categories: { categoryID: number, categoryName: string @@ -31,14 +32,272 @@ export class LoginResponse } = { categories: [] }; - textures: { - 'cloudTextureID'?: UUID, - 'sunTextureID'?: UUID, - 'moonTextureID'?: UUID, - } = {}; - searchToken: string; - mfaHash?: string; - clientEvents: ClientEvents; + public searchToken: string; + public mfaHash?: string; + public clientEvents: ClientEvents; + + public constructor(json: any, clientEvents: ClientEvents, options: BotOptionFlags) + { + this.clientEvents = clientEvents; + this.agent = new Agent(this.clientEvents); + this.region = new Region(this.agent, this.clientEvents, options); + if (json.agent_id) + { + this.agent.agentID = new UUID(json.agent_id); + } + + for (const key of Object.keys(json)) + { + const val: any = json[key]; + switch (key) + { + case 'inventory-skeleton': + for (const item of val) + { + const folder = new InventoryFolder(InventoryLibrary.Main, this.agent.inventory.main, this.agent); + folder.typeDefault = parseInt(item.type_default, 10); + folder.version = parseInt(item.version, 10); + folder.name = String(item.name); + folder.folderID = new UUID(item.folder_id); + folder.parentID = new UUID(item.parent_id); + this.agent.inventory.main.skeleton.set(folder.folderID.toString(), folder); + } + break; + case 'inventory-skel-lib': + for (const item of val) + { + const folder = new InventoryFolder(InventoryLibrary.Library, this.agent.inventory.library, this.agent); + folder.typeDefault = parseInt(item.type_default, 10); + folder.version = parseInt(item.version, 10); + folder.name = String(item.name); + folder.folderID = new UUID(item.folder_id); + folder.parentID = new UUID(item.parent_id); + this.agent.inventory.library.skeleton.set(folder.folderID.toString(), folder); + } + break; + case 'inventory-root': + { + this.agent.inventory.main.root = new UUID(val[0].folder_id); + const folder = new InventoryFolder(InventoryLibrary.Main, this.agent.inventory.main, this.agent); + folder.typeDefault = 0; + folder.version = 0; + folder.name = 'root'; + folder.folderID = new UUID(val[0].folder_id); + folder.parentID = UUID.zero(); + this.agent.inventory.main.skeleton.set(folder.folderID.toString(), folder); + break; + } + case 'inventory-lib-owner': + this.agent.inventory.library.owner = new UUID(val[0].agent_id); + break; + case 'inventory-lib-root': + { + this.agent.inventory.library.root = new UUID(val[0].folder_id); + const folder = new InventoryFolder(InventoryLibrary.Library, this.agent.inventory.library, this.agent); + folder.typeDefault = 0; + folder.version = 0; + folder.name = 'root'; + folder.folderID = new UUID(val[0].folder_id); + folder.parentID = UUID.zero(); + this.agent.inventory.library.skeleton.set(folder.folderID.toString(), folder); + break; + } + case 'agent_access_max': + this.agent.accessMax = String(val); + break; + case 'event_notifications': + // dunno what this does just yet + break; + case 'secure_session_id': + this.region.circuit.secureSessionID = new UUID(val); + break; + case 'openid_token': + this.agent.openID.token = String(val); + break; + case 'region_x': + this.region.xCoordinate = parseInt(val, 10); + break; + case 'ao_transition': + this.agent.AOTransition = (val !== 0); + break; + case 'global-textures': + for (const obj of val) + { + if (obj.cloud_texture_id) + { + this.region.textures.cloudTextureID = obj.cloud_texture_id; + } + if (obj.sun_texture_id) + { + this.region.textures.sunTextureID = obj.sun_texture_id; + } + if (obj.moon_texture_id) + { + this.region.textures.moonTextureID = obj.moon_texture_id; + } + } + break; + case 'mfa_hash': + this.mfaHash = String(val); + break; + case 'search_token': + this.searchToken = String(val); + break; + case 'login-flags': + { + let flags: LoginFlags = 0 as LoginFlags; + for (const obj of val) + { + if (obj.ever_logged_in === 'Y') + { + flags = flags | LoginFlags.everLoggedIn; + } + if (obj.daylight_savings === 'Y') + { + flags = flags | LoginFlags.daylightSavings; + } + if (obj.stipend_since_login === 'Y') + { + flags = flags | LoginFlags.stipendSinceLogin; + } + if (obj.gendered === 'Y') + { + flags = flags | LoginFlags.gendered; + } + } + this.loginFlags = flags; + break; + } + case 'buddy-list': + { + for (const obj of val) + { + this.agent.buddyList.push({ + buddyRightsGiven: obj.buddy_rights_given !== 0, + buddyID: new UUID(obj.buddy_id), + buddyRightsHas: obj.buddy_rights_has !== 0, + }); + } + break; + } + case 'sim_port': + this.region.circuit.port = parseInt(val, 10); + break; + case 'sim_ip': + this.region.circuit.ipAddress = String(val); + break; + case 'agent_appearance_service': + { + this.agent.agentAppearanceService = val; + break; + } + case 'ui-config': + for (const item of val) + { + if (item.allow_first_life === 'Y') + { + this.agent.uiFlags.allowFirstLife = true; + } + } + break; + case 'look_at': + try + { + this.agent.cameraLookAt = LoginResponse.parseVector3(val); + } + catch (_error: unknown) + { + console.error('Invalid look_at from LoginResponse'); + } + break; + case 'openid_url': + this.agent.openID.url = String(val); + break; + case 'max-agent-groups': + this.agent.maxGroups = parseInt(val, 10); + break; + case 'session_id': + this.region.circuit.sessionID = new UUID(val); + break; + case 'agent_flags': + this.agent.agentFlags = parseInt(val, 10); + break; + case 'event_categories': + for (const item of val) + { + this.events.categories.push({ + 'categoryID': parseInt(item.category_id, 10), + 'categoryName': String(item.category_name) + }); + } + break; + case 'start_location': + this.agent.startLocation = String(val); + break; + case 'agent_region_access': + this.agent.regionAccess = String(val); + break; + case 'last_name': + this.agent.lastName = String(val); + break; + case 'cof_version': + this.agent.cofVersion = parseInt(val, 10); + break; + case 'home': + this.agent.home = LoginResponse.parseHome(val); + break; + case 'classified_categories': + for (const item of val) + { + this.classifieds.categories.push({ + 'categoryID': parseInt(item.category_id, 10), + 'categoryName': String(item.category_name) + }); + } + break; + case 'snapshot_config_url': + this.agent.snapshotConfigURL = String(val); + break; + case 'region_y': + this.region.yCoordinate = parseInt(val, 10); + break; + case 'agent_access': + this.agent.agentAccess = String(val); + break; + case 'circuit_code': + this.region.circuit.circuitCode = parseInt(val, 10); + break; + case 'message': + this.loginMessage = String(val); + break; + case 'gestures': + for (const item of val) + { + this.agent.gestures.push({ + 'assetID': new UUID(item.asset_id), + 'itemID': new UUID(item.item_id) + }); + } + break; + case 'udp_blacklist': + { + const list = String(val).split(','); + this.region.circuit.udpBlacklist = list; + break; + } + case 'seconds_since_epoch': + this.region.circuit.timestamp = parseInt(val, 10); + break; + case 'seed_capability': + this.region.activateCaps(String(val)); + break; + case 'first_name': + this.agent.firstName = String(val).replace(/"/g, ''); + break; + } + } + this.agent.setCurrentRegion(this.region); + } private static toRegionHandle(x_global: number, y_global: number): Long { @@ -51,7 +310,7 @@ export class LoginResponse private static parseVector3(str: string): Vector3 { - const num = str.replace(/[\[\]r\s]/g, '').split(','); + const num = str.replace(/[[\]r\s]/g, '').split(','); const x = parseFloat(num[0]); const y = parseFloat(num[1]); const z = parseFloat(num[2]); @@ -77,7 +336,7 @@ export class LoginResponse let parseStart: { region_handle: string, look_at: string, position: string } | null = null; if (typeof home === 'string') { - const json = home.replace(/[\[\]']/g, '\"'); + const json = home.replace(/[[\]']/g, '"'); parseStart = JSON.parse(json); } else @@ -85,290 +344,35 @@ export class LoginResponse parseStart = home; } const parsed = parseStart as { region_handle: string, look_at: string, position: string }; - if (parsed['region_handle']) + if (parsed.region_handle) { - const coords = parsed['region_handle'].replace(/r/g, '').split(', '); - result['regionHandle'] = LoginResponse.toRegionHandle(parseInt(coords[0], 10), parseInt(coords[1], 10)); + const coords = parsed.region_handle.replace(/r/g, '').split(', '); + result.regionHandle = LoginResponse.toRegionHandle(parseInt(coords[0], 10), parseInt(coords[1], 10)); } - if (parsed['position']) + if (parsed.position) { try { - result['position'] = this.parseVector3('[' + parsed['position'] + ']'); + result.position = this.parseVector3('[' + parsed.position + ']'); } - catch (error) + catch (_error: unknown) { - result['position'] = new Vector3([128.0, 128.0, 0.0]); + result.position = new Vector3([128.0, 128.0, 0.0]); } } - if (parsed['look_at']) + if (parsed.look_at) { try { - result['lookAt'] = this.parseVector3('[' + parsed['look_at'] + ']'); + result.lookAt = this.parseVector3('[' + parsed.look_at + ']'); } - catch (error) + catch (_error: unknown) { - result['lookAt'] = new Vector3([128.0, 128.0, 0.0]); + result.lookAt = new Vector3([128.0, 128.0, 0.0]); } } return result; } - - constructor(json: any, clientEvents: ClientEvents, options: BotOptionFlags) - { - this.clientEvents = clientEvents; - this.agent = new Agent(this.clientEvents); - this.region = new Region(this.agent, this.clientEvents, options); - if (json['agent_id']) - { - this.agent.agentID = new UUID(json['agent_id']); - } - - for (const key of Object.keys(json)) - { - const val: any = json[key]; - switch (key) - { - case 'inventory-skeleton': - for (const item of val) - { - const folder = new InventoryFolder(InventoryLibrary.Main, this.agent.inventory.main, this.agent); - folder.typeDefault = parseInt(item['type_default'], 10); - folder.version = parseInt(item['version'], 10); - folder.name = String(item['name']); - folder.folderID = new UUID(item['folder_id']); - folder.parentID = new UUID(item['parent_id']); - this.agent.inventory.main.skeleton[folder.folderID.toString()] = folder; - } - break; - case 'inventory-skel-lib': - for (const item of val) - { - const folder = new InventoryFolder(InventoryLibrary.Library, this.agent.inventory.library, this.agent); - folder.typeDefault = parseInt(item['type_default'], 10); - folder.version = parseInt(item['version'], 10); - folder.name = String(item['name']); - folder.folderID = new UUID(item['folder_id']); - folder.parentID = new UUID(item['parent_id']); - this.agent.inventory.library.skeleton[folder.folderID.toString()] = folder; - } - break; - case 'inventory-root': - { - this.agent.inventory.main.root = new UUID(val[0]['folder_id']); - const folder = new InventoryFolder(InventoryLibrary.Main, this.agent.inventory.main, this.agent); - folder.typeDefault = 0; - folder.version = 0; - folder.name = 'root'; - folder.folderID = new UUID(val[0]['folder_id']); - folder.parentID = UUID.zero(); - this.agent.inventory.main.skeleton[folder.folderID.toString()] = folder; - break; - } - case 'inventory-lib-owner': - this.agent.inventory.library.owner = new UUID(val[0]['agent_id']); - break; - case 'inventory-lib-root': - { - this.agent.inventory.library.root = new UUID(val[0]['folder_id']); - const folder = new InventoryFolder(InventoryLibrary.Library, this.agent.inventory.library, this.agent); - folder.typeDefault = 0; - folder.version = 0; - folder.name = 'root'; - folder.folderID = new UUID(val[0]['folder_id']); - folder.parentID = UUID.zero(); - this.agent.inventory.library.skeleton[folder.folderID.toString()] = folder; - break; - } - case 'agent_access_max': - this.agent.accessMax = String(val); - break; - case 'event_notifications': - // dunno what this does just yet - break; - case 'secure_session_id': - this.region.circuit.secureSessionID = new UUID(val); - break; - case 'openid_token': - this.agent.openID.token = String(val); - break; - case 'region_x': - this.region.xCoordinate = parseInt(val, 10); - break; - case 'ao_transition': - this.agent.AOTransition = (val !== 0); - break; - case 'global-textures': - for (const obj of val) - { - if (obj['cloud_texture_id']) - { - this.textures.cloudTextureID = obj['cloud_texture_id']; - } - if (obj['sun_texture_id']) - { - this.textures.sunTextureID = obj['sun_texture_id']; - } - if (obj['moon_texture_id']) - { - this.textures.moonTextureID = obj['moon_texture_id']; - } - } - break; - case 'mfa_hash': - this.mfaHash = String(val); - break; - case 'search_token': - this.searchToken = String(val); - break; - case 'login-flags': - let flags: LoginFlags = 0 as LoginFlags; - for (const obj of val) - { - if (obj['ever_logged_in'] === 'Y') - { - flags = flags | LoginFlags.everLoggedIn; - } - if (obj['daylight_savings'] === 'Y') - { - flags = flags | LoginFlags.daylightSavings; - } - if (obj['stipend_since_login'] === 'Y') - { - flags = flags | LoginFlags.stipendSinceLogin; - } - if (obj['gendered'] === 'Y') - { - flags = flags | LoginFlags.gendered; - } - } - this.loginFlags = flags; - break; - case 'buddy-list': - for (const obj of val) - { - this.agent.buddyList.push({ - buddyRightsGiven: obj['buddy_rights_given'] !== 0, - buddyID: new UUID(obj['buddy_id']), - buddyRightsHas: obj['buddy_rights_has'] !== 0, - }); - } - break; - case 'sim_port': - this.region.circuit.port = parseInt(val, 10); - break; - case 'sim_ip': - this.region.circuit.ipAddress = String(val); - break; - case 'agent_appearance_service': - this.agent.agentAppearanceService = val; - break; - case 'ui-config': - for (const item of val) - { - if (item['allow_first_life'] === 'Y') - { - this.agent.uiFlags.allowFirstLife = true; - } - } - break; - case 'look_at': - try - { - this.agent.cameraLookAt = LoginResponse.parseVector3(val); - } - catch (error) - { - console.error('Invalid look_at from LoginResponse'); - } - break; - case 'openid_url': - this.agent.openID.url = String(val); - break; - case 'max-agent-groups': - this.agent.maxGroups = parseInt(val, 10); - break; - case 'session_id': - this.region.circuit.sessionID = new UUID(val); - break; - case 'agent_flags': - this.agent.agentFlags = parseInt(val, 10); - break; - case 'event_categories': - for (const item of val) - { - this.events.categories.push({ - 'categoryID': parseInt(item['category_id'], 10), - 'categoryName': String(item['category_name']) - }); - } - break; - case 'start_location': - this.agent.startLocation = String(val); - break; - case 'agent_region_access': - this.agent.regionAccess = String(val); - break; - case 'last_name': - this.agent.lastName = String(val); - break; - case 'cof_version': - this.agent.cofVersion = parseInt(val, 10); - break; - case 'home': - this.agent.home = LoginResponse.parseHome(val); - break; - case 'classified_categories': - for (const item of val) - { - this.classifieds.categories.push({ - 'categoryID': parseInt(item['category_id'], 10), - 'categoryName': String(item['category_name']) - }); - } - break; - case 'snapshot_config_url': - this.agent.snapshotConfigURL = String(val); - break; - case 'region_y': - this.region.yCoordinate = parseInt(val, 10); - break; - case 'agent_access': - this.agent.agentAccess = String(val); - break; - case 'circuit_code': - this.region.circuit.circuitCode = parseInt(val, 10); - break; - case 'message': - this.loginMessage = String(val); - break; - case 'gestures': - for (const item of val) - { - this.agent.gestures.push({ - 'assetID': new UUID(item['asset_id']), - 'itemID': new UUID(item['item_id']) - }); - } - break; - case 'udp_blacklist': - const list = String(val).split(','); - this.region.circuit.udpBlacklist = list; - break; - case 'seconds_since_epoch': - this.region.circuit.timestamp = parseInt(val, 10); - break; - case 'seed_capability': - this.region.activateCaps(String(val)); - break; - case 'first_name': - this.agent.firstName = String(val).replace(/"/g, ''); - break; - } - } - this.agent.setCurrentRegion(this.region); - } } diff --git a/lib/classes/MapBlock.ts b/lib/classes/MapBlock.ts index bd1522d..bc28ff5 100644 --- a/lib/classes/MapBlock.ts +++ b/lib/classes/MapBlock.ts @@ -1,13 +1,13 @@ -import { UUID } from './UUID'; -import { RegionFlags } from '../enums/RegionFlags'; +import type { UUID } from './UUID'; +import type { RegionFlags } from '../enums/RegionFlags'; export class MapBlock { - name: string; - mapImage: UUID; - accessFlags: number; - x: number; - y: number; - waterHeight: number; - regionFlags: RegionFlags; + public name: string; + public mapImage: UUID; + public accessFlags: number; + public x: number; + public y: number; + public waterHeight: number; + public regionFlags: RegionFlags; } diff --git a/lib/classes/Matrix3.ts b/lib/classes/Matrix3.ts new file mode 100644 index 0000000..ca0e7cf --- /dev/null +++ b/lib/classes/Matrix3.ts @@ -0,0 +1,463 @@ +import { Vector2 } from './Vector2'; +import { Vector3 } from './Vector3'; +import { Matrix4 } from './Matrix4'; +import { Quaternion } from './Quaternion'; + +export class Matrix3 +{ + public static readonly identity: Matrix3 = new Matrix3().setIdentity(); + + private static readonly EPSILON: number = 1e-6; + + private values: Float32Array = new Float32Array(9); + + public constructor(values?: number[]) + { + if (values) + { + this.init(values); + } + } + + public at(index: number): number + { + return this.values[index]; + } + + public init(values: number[]): this + { + if (values.length !== 9) + { + throw new Error("Matrix3 requires exactly 9 values."); + } + + let i = 0; + for (const value of values) + { + this.values[i] = value; + i++; + } + + return this; + } + + public reset(): void + { + let i = 0; + for (const _ of this.values) + { + this.values[i] = 0; + i++; + } + } + + public copy(dest?: Matrix3): Matrix3 + { + if (!dest) + { + dest = new Matrix3(); + } + + let i = 0; + for (const value of this.values) + { + dest.values[i] = value; + i++; + } + + return dest; + } + + public all(): number[] + { + const data: number[] = []; + for (const value of this.values) + { + data.push(value); + } + + return data; + } + + public row(index: number): number[] + { + return [ + this.values[index * 3], + this.values[index * 3 + 1], + this.values[index * 3 + 2] + ]; + } + + public col(index: number): number[] + { + return [ + this.values[index], + this.values[index + 3], + this.values[index + 6] + ]; + } + + public equals(matrix: Matrix3, threshold: number = Matrix3.EPSILON): boolean + { + let i = 0; + for (const value of this.values) + { + if (Math.abs(value - matrix.at(i)) > threshold) + { + return false; + } + i++; + } + + return true; + } + + public determinant(): number + { + const a00: number = this.values[0]; + const a01: number = this.values[1]; + const a02: number = this.values[2]; + const a10: number = this.values[3]; + const a11: number = this.values[4]; + const a12: number = this.values[5]; + const a20: number = this.values[6]; + const a21: number = this.values[7]; + const a22: number = this.values[8]; + + const det01: number = a22 * a11 - a12 * a21; + const det11: number = -a22 * a10 + a12 * a20; + const det21: number = a21 * a10 - a11 * a20; + + return a00 * det01 + a01 * det11 + a02 * det21; + } + + public setIdentity(): this + { + this.reset(); + + this.values[0] = 1; + this.values[4] = 1; + this.values[8] = 1; + + return this; + } + + public transpose(): Matrix3 + { + const transposedValues: number[] = [ + this.values[0], + this.values[3], + this.values[6], + + this.values[1], + this.values[4], + this.values[7], + + this.values[2], + this.values[5], + this.values[8] + ]; + + return new Matrix3(transposedValues); + } + + public inverse(): Matrix3 | null + { + const a00: number = this.values[0]; + const a01: number = this.values[1]; + const a02: number = this.values[2]; + const a10: number = this.values[3]; + const a11: number = this.values[4]; + const a12: number = this.values[5]; + const a20: number = this.values[6]; + const a21: number = this.values[7]; + const a22: number = this.values[8]; + + const det01: number = a22 * a11 - a12 * a21; + const det11: number = -a22 * a10 + a12 * a20; + const det21: number = a21 * a10 - a11 * a20; + + let det: number = a00 * det01 + a01 * det11 + a02 * det21; + + if (Math.abs(det) < Matrix3.EPSILON) + { + return null; + } + + det = 1.0 / det; + + const invValues: number[] = [ + det01 * det, + (-a22 * a01 + a02 * a21) * det, + (a12 * a01 - a02 * a11) * det, + + det11 * det, + (a22 * a00 - a02 * a20) * det, + (-a12 * a00 + a02 * a10) * det, + + det21 * det, + (-a21 * a00 + a01 * a20) * det, + (a11 * a00 - a01 * a10) * det + ]; + + return new Matrix3(invValues); + } + + public multiply(matrix: Matrix3): Matrix3 + { + const a00: number = this.values[0]; + const a01: number = this.values[1]; + const a02: number = this.values[2]; + const a10: number = this.values[3]; + const a11: number = this.values[4]; + const a12: number = this.values[5]; + const a20: number = this.values[6]; + const a21: number = this.values[7]; + const a22: number = this.values[8]; + + const b00: number = matrix.at(0); + const b01: number = matrix.at(1); + const b02: number = matrix.at(2); + const b10: number = matrix.at(3); + const b11: number = matrix.at(4); + const b12: number = matrix.at(5); + const b20: number = matrix.at(6); + const b21: number = matrix.at(7); + const b22: number = matrix.at(8); + + const resultValues: number[] = [ + b00 * a00 + b01 * a10 + b02 * a20, + b00 * a01 + b01 * a11 + b02 * a21, + b00 * a02 + b01 * a12 + b02 * a22, + + b10 * a00 + b11 * a10 + b12 * a20, + b10 * a01 + b11 * a11 + b12 * a21, + b10 * a02 + b11 * a12 + b12 * a22, + + b20 * a00 + b21 * a10 + b22 * a20, + b20 * a01 + b21 * a11 + b22 * a21, + b20 * a02 + b21 * a12 + b22 * a22 + ]; + + return new Matrix3(resultValues); + } + + public multiplyVector2(vector: Vector2, result?: Vector2): Vector2 + { + const x: number = vector.x; + const y: number = vector.y; + + if (result) + { + result.x = x * this.values[0] + y * this.values[3] + this.values[6]; + result.y = x * this.values[1] + y * this.values[4] + this.values[7]; + return result; + } + else + { + return new Vector2([ + x * this.values[0] + y * this.values[3] + this.values[6], + x * this.values[1] + y * this.values[4] + this.values[7] + ]); + } + } + + public multiplyVector3(vector: Vector3, result?: Vector3): Vector3 + { + const x: number = vector.x; + const y: number = vector.y; + const z: number = vector.z; + + if (result) + { + result.x = x * this.values[0] + y * this.values[3] + z * this.values[6]; + result.y = x * this.values[1] + y * this.values[4] + z * this.values[7]; + result.z = x * this.values[2] + y * this.values[5] + z * this.values[8]; + return result; + } + else + { + return new Vector3([ + x * this.values[0] + y * this.values[3] + z * this.values[6], + x * this.values[1] + y * this.values[4] + z * this.values[7], + x * this.values[2] + y * this.values[5] + z * this.values[8] + ]); + } + } + + public toMatrix4(result?: Matrix4): Matrix4 + { + const mat4Values: number[] = [ + this.values[0], + this.values[1], + this.values[2], + 0, + + this.values[3], + this.values[4], + this.values[5], + 0, + + this.values[6], + this.values[7], + this.values[8], + 0, + + 0, + 0, + 0, + 1 + ]; + + if (result) + { + result.init(mat4Values); + return result; + } + else + { + return new Matrix4(mat4Values); + } + } + + public toQuaternion(): Quaternion + { + const m00: number = this.values[0]; + const m01: number = this.values[1]; + const m02: number = this.values[2]; + const m10: number = this.values[3]; + const m11: number = this.values[4]; + const m12: number = this.values[5]; + const m20: number = this.values[6]; + const m21: number = this.values[7]; + const m22: number = this.values[8]; + + const fourXSquaredMinus1: number = m00 - m11 - m22; + const fourYSquaredMinus1: number = m11 - m00 - m22; + const fourZSquaredMinus1: number = m22 - m00 - m11; + const fourWSquaredMinus1: number = m00 + m11 + m22; + + let biggestIndex = 0; + let fourBiggestSquaredMinus1: number = fourWSquaredMinus1; + + if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) + { + fourBiggestSquaredMinus1 = fourXSquaredMinus1; + biggestIndex = 1; + } + + if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) + { + fourBiggestSquaredMinus1 = fourYSquaredMinus1; + biggestIndex = 2; + } + + if (fourZSquaredMinus1 > fourBiggestSquaredMinus1) + { + fourBiggestSquaredMinus1 = fourZSquaredMinus1; + biggestIndex = 3; + } + + const biggestVal: number = Math.sqrt(fourBiggestSquaredMinus1 + 1) * 0.5; + const mult: number = 0.25 / biggestVal; + + const quat: Quaternion = new Quaternion(); + + switch (biggestIndex) + { + case 0: + quat.w = biggestVal; + quat.x = (m12 - m21) * mult; + quat.y = (m20 - m02) * mult; + quat.z = (m01 - m10) * mult; + break; + + case 1: + quat.w = (m12 - m21) * mult; + quat.x = biggestVal; + quat.y = (m01 + m10) * mult; + quat.z = (m20 + m02) * mult; + break; + + case 2: + quat.w = (m20 - m02) * mult; + quat.x = (m01 + m10) * mult; + quat.y = biggestVal; + quat.z = (m12 + m21) * mult; + break; + + case 3: + quat.w = (m01 - m10) * mult; + quat.x = (m20 + m02) * mult; + quat.y = (m12 + m21) * mult; + quat.z = biggestVal; + break; + } + + return quat; + } + + public rotate(angle: number, axis: Vector3): Matrix3 | null + { + let x: number = axis.x; + let y: number = axis.y; + let z: number = axis.z; + + let length: number = Math.sqrt(x * x + y * y + z * z); + + if (length < Matrix3.EPSILON) + { + return null; + } + + if (length !== 1) + { + length = 1 / length; + x *= length; + y *= length; + z *= length; + } + + const s: number = Math.sin(angle); + const c: number = Math.cos(angle); + const t: number = 1.0 - c; + + const b00: number = x * x * t + c; + const b01: number = y * x * t + z * s; + const b02: number = z * x * t - y * s; + + const b10: number = x * y * t - z * s; + const b11: number = y * y * t + c; + const b12: number = z * y * t + x * s; + + const b20: number = x * z * t + y * s; + const b21: number = y * z * t - x * s; + const b22: number = z * z * t + c; + + const a00: number = this.values[0]; + const a01: number = this.values[1]; + const a02: number = this.values[2]; + const a10: number = this.values[3]; + const a11: number = this.values[4]; + const a12: number = this.values[5]; + const a20: number = this.values[6]; + const a21: number = this.values[7]; + const a22: number = this.values[8]; + + const resultValues: number[] = [ + a00 * b00 + a10 * b01 + a20 * b02, + a01 * b00 + a11 * b01 + a21 * b02, + a02 * b00 + a12 * b01 + a22 * b02, + + a00 * b10 + a10 * b11 + a20 * b12, + a01 * b10 + a11 * b11 + a21 * b12, + a02 * b10 + a12 * b11 + a22 * b12, + + a00 * b20 + a10 * b21 + a20 * b22, + a01 * b20 + a11 * b21 + a21 * b22, + a02 * b20 + a12 * b21 + a22 * b22 + ]; + + return new Matrix3(resultValues); + } +} diff --git a/lib/classes/Matrix4.ts b/lib/classes/Matrix4.ts new file mode 100644 index 0000000..08a593c --- /dev/null +++ b/lib/classes/Matrix4.ts @@ -0,0 +1,547 @@ +import { Vector4 } from './Vector4'; + +const EPSILON = 1e-6; + +import { Vector3 } from './Vector3'; +import { Matrix3 } from './Matrix3'; + +export class Matrix4 +{ + public static readonly identity: Matrix4 = new Matrix4().setIdentity(); + + private readonly values: Float32Array; + + public constructor(values: number[] | null = null) + { + this.values = new Float32Array(16); + if (values) + { + this.init(values); + } + else + { + this.setIdentity(); + } + } + + public static frustum( + left: number, + right: number, + bottom: number, + top: number, + near: number, + far: number + ): Matrix4 + { + const rl: number = right - left; + const tb: number = top - bottom; + const fn: number = far - near; + + return new Matrix4([ + (near * 2) / rl, 0, 0, 0, + 0, (near * 2) / tb, 0, 0, + (right + left) / rl, (top + bottom) / tb, -(far + near) / fn, -1, + 0, 0, -(far * near * 2) / fn, 0 + ]); + } + + public static perspective( + fov: number, + aspect: number, + near: number, + far: number + ): Matrix4 + { + const top: number = near * Math.tan((fov * Math.PI) / 360.0); + const right: number = top * aspect; + + return Matrix4.frustum(-right, right, -top, top, near, far); + } + + public static orthographic( + left: number, + right: number, + bottom: number, + top: number, + near: number, + far: number + ): Matrix4 + { + const rl: number = right - left; + const tb: number = top - bottom; + const fn: number = far - near; + + return new Matrix4([ + 2 / rl, 0, 0, 0, + 0, 2 / tb, 0, 0, + 0, 0, -2 / fn, 0, + -(left + right) / rl, -(top + bottom) / tb, -(far + near) / fn, 1 + ]); + } + + public static lookAt( + position: Vector3, + target: Vector3, + up: Vector3 = Vector3.up + ): Matrix4 + { + if (position.equals(target)) + { + return this.identity.copy(); + } + + const z: Vector3 = position.difference(target).normalize(); + const x: Vector3 = up.cross(z).normalize(); + const y: Vector3 = z.cross(x).normalize(); + + return new Matrix4([ + x.x, y.x, z.x, 0, + x.y, y.y, z.y, 0, + x.z, y.z, z.z, 0, + -x.dot(position), -y.dot(position), -z.dot(position), 1 + ]); + } + + public static product(m1: Matrix4, m2: Matrix4, result: Matrix4 | null = null): Matrix4 + { + const a: Float32Array = m1.values; + const b: Float32Array = m2.values; + + const productValues: number[] = [ + b[0] * a[0] + b[1] * a[4] + b[2] * a[8] + b[3] * a[12], + b[0] * a[1] + b[1] * a[5] + b[2] * a[9] + b[3] * a[13], + b[0] * a[2] + b[1] * a[6] + b[2] * a[10] + b[3] * a[14], + b[0] * a[3] + b[1] * a[7] + b[2] * a[11] + b[3] * a[15], + + b[4] * a[0] + b[5] * a[4] + b[6] * a[8] + b[7] * a[12], + b[4] * a[1] + b[5] * a[5] + b[6] * a[9] + b[7] * a[13], + b[4] * a[2] + b[5] * a[6] + b[6] * a[10] + b[7] * a[14], + b[4] * a[3] + b[5] * a[7] + b[6] * a[11] + b[7] * a[15], + + b[8] * a[0] + b[9] * a[4] + b[10] * a[8] + b[11] * a[12], + b[8] * a[1] + b[9] * a[5] + b[10] * a[9] + b[11] * a[13], + b[8] * a[2] + b[9] * a[6] + b[10] * a[10] + b[11] * a[14], + b[8] * a[3] + b[9] * a[7] + b[10] * a[11] + b[11] * a[15], + + b[12] * a[0] + b[13] * a[4] + b[14] * a[8] + b[15] * a[12], + b[12] * a[1] + b[13] * a[5] + b[14] * a[9] + b[15] * a[13], + b[12] * a[2] + b[13] * a[6] + b[14] * a[10] + b[15] * a[14], + b[12] * a[3] + b[13] * a[7] + b[14] * a[11] + b[15] * a[15], + ]; + + if (result) + { + result.init(productValues); + return result; + } + else + { + return new Matrix4(productValues); + } + } + + public at(index: number): number + { + return this.values[index]; + } + + public init(values: unknown[]): this + { + if (values.length !== 16) + { + throw new Error('Initialization array must have exactly 16 elements.'); + } + + for (const val of values) + { + if (typeof val !== 'number') + { + throw new Error('Array contains non-numbers'); + } + } + + let i = 0; + for (const value of values) + { + this.values[i++] = Number(value); + } + + return this; + } + + public reset(): void + { + for (let i = 0; i < this.values.length; i++) + { + this.values[i] = 0; + } + } + + public copy(dest: Matrix4 | null = null): Matrix4 + { + const destination: Matrix4 = dest ?? new Matrix4(); + for (let i = 0; i < this.values.length; i++) + { + destination.values[i] = this.values[i]; + } + return destination; + } + + public all(): number[] + { + const data: number[] = []; + for (const value of this.values) + { + data.push(value); + } + return data; + } + + public row(index: number): number[] + { + if (index < 0 || index > 3) + { + throw new RangeError('Row index must be between 0 and 3.'); + } + + return [ + this.values[index * 4], + this.values[index * 4 + 1], + this.values[index * 4 + 2], + this.values[index * 4 + 3] + ]; + } + + public col(index: number): number[] + { + if (index < 0 || index > 3) + { + throw new RangeError('Column index must be between 0 and 3.'); + } + + return [ + this.values[index], + this.values[index + 4], + this.values[index + 8], + this.values[index + 12] + ]; + } + + public equals(matrix: Matrix4, threshold: number = EPSILON): boolean + { + for (let i = 0; i < this.values.length; i++) + { + if (Math.abs(this.values[i] - matrix.at(i)) > threshold) + { + return false; + } + } + return true; + } + + public determinant(): number + { + const m = this.values; + const det00 = m[0] * m[5] - m[1] * m[4]; + const det01 = m[0] * m[6] - m[2] * m[4]; + const det02 = m[0] * m[7] - m[3] * m[4]; + const det03 = m[1] * m[6] - m[2] * m[5]; + const det04 = m[1] * m[7] - m[3] * m[5]; + const det05 = m[2] * m[7] - m[3] * m[6]; + const det06 = m[4] * m[9] - m[5] * m[8]; + const det07 = m[4] * m[10] - m[6] * m[8]; + const det08 = m[4] * m[11] - m[7] * m[8]; + const det09 = m[5] * m[10] - m[6] * m[9]; + const det10 = m[5] * m[11] - m[7] * m[9]; + const det11 = m[6] * m[11] - m[7] * m[10]; + + return ( + det00 * det11 + - det01 * det10 + + det02 * det09 + + det03 * det08 + - det04 * det07 + + det05 * det06 + ); + } + + public setIdentity(): this + { + this.reset(); + this.values[0] = 1; + this.values[5] = 1; + this.values[10] = 1; + this.values[15] = 1; + return this; + } + + public transpose(): Matrix4 + { + const m: Float32Array = this.values; + const transposed: Float32Array = new Float32Array(16); + + for (let row = 0; row < 4; row++) + { + for (let col = 0; col < 4; col++) + { + transposed[col * 4 + row] = m[row * 4 + col]; + } + } + + return new Matrix4(Array.from(transposed)); + } + + public inverse(): Matrix4 | null + { + const m = this.values; + const inv: number[] = new Array(16); + + inv[0] = m[5] * m[10] * m[15] - + m[5] * m[11] * m[14] - + m[9] * m[6] * m[15] + + m[9] * m[7] * m[14] + + m[13] * m[6] * m[11] - + m[13] * m[7] * m[10]; + + inv[4] = -m[4] * m[10] * m[15] + + m[4] * m[11] * m[14] + + m[8] * m[6] * m[15] - + m[8] * m[7] * m[14] - + m[12] * m[6] * m[11] + + m[12] * m[7] * m[10]; + + inv[8] = m[4] * m[9] * m[15] - + m[4] * m[11] * m[13] - + m[8] * m[5] * m[15] + + m[8] * m[7] * m[13] + + m[12] * m[5] * m[11] - + m[12] * m[7] * m[9]; + + inv[12] = -m[4] * m[9] * m[14] + + m[4] * m[10] * m[13] + + m[8] * m[5] * m[14] - + m[8] * m[6] * m[13] - + m[12] * m[5] * m[10] + + m[12] * m[6] * m[9]; + + inv[1] = -m[1] * m[10] * m[15] + + m[1] * m[11] * m[14] + + m[9] * m[2] * m[15] - + m[9] * m[3] * m[14] - + m[13] * m[2] * m[11] + + m[13] * m[3] * m[10]; + + inv[5] = m[0] * m[10] * m[15] - + m[0] * m[11] * m[14] - + m[8] * m[2] * m[15] + + m[8] * m[3] * m[14] + + m[12] * m[2] * m[11] - + m[12] * m[3] * m[10]; + + inv[9] = -m[0] * m[9] * m[15] + + m[0] * m[11] * m[13] + + m[8] * m[1] * m[15] - + m[8] * m[3] * m[13] - + m[12] * m[1] * m[11] + + m[12] * m[3] * m[9]; + + inv[13] = m[0] * m[9] * m[14] - + m[0] * m[10] * m[13] - + m[8] * m[1] * m[14] + + m[8] * m[2] * m[13] + + m[12] * m[1] * m[10] - + m[12] * m[2] * m[9]; + + inv[2] = m[1] * m[6] * m[15] - + m[1] * m[7] * m[14] - + m[5] * m[2] * m[15] + + m[5] * m[3] * m[14] + + m[13] * m[2] * m[7] - + m[13] * m[3] * m[6]; + + inv[6] = -m[0] * m[6] * m[15] + + m[0] * m[7] * m[14] + + m[4] * m[2] * m[15] - + m[4] * m[3] * m[14] - + m[12] * m[2] * m[7] + + m[12] * m[3] * m[6]; + + inv[10] = m[0] * m[5] * m[15] - + m[0] * m[7] * m[13] - + m[4] * m[1] * m[15] + + m[4] * m[3] * m[13] + + m[12] * m[1] * m[7] - + m[12] * m[3] * m[5]; + + inv[14] = -m[0] * m[5] * m[14] + + m[0] * m[6] * m[13] + + m[4] * m[1] * m[14] - + m[4] * m[2] * m[13] - + m[12] * m[1] * m[6] + + m[12] * m[2] * m[5]; + + inv[3] = -m[1] * m[6] * m[11] + + m[1] * m[7] * m[10] + + m[5] * m[2] * m[11] - + m[5] * m[3] * m[10] - + m[9] * m[2] * m[7] + + m[9] * m[3] * m[6]; + + inv[7] = m[0] * m[6] * m[11] - + m[0] * m[7] * m[10] - + m[4] * m[2] * m[11] + + m[4] * m[3] * m[10] + + m[8] * m[2] * m[7] - + m[8] * m[3] * m[6]; + + inv[11] = -m[0] * m[5] * m[11] + + m[0] * m[7] * m[9] + + m[4] * m[1] * m[11] - + m[4] * m[3] * m[9] - + m[8] * m[1] * m[7] + + m[8] * m[3] * m[5]; + + inv[15] = m[0] * m[5] * m[10] - + m[0] * m[6] * m[9] - + m[4] * m[1] * m[10] + + m[4] * m[2] * m[9] + + m[8] * m[1] * m[6] - + m[8] * m[2] * m[5]; + + const det: number = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12]; + + if (Math.abs(det) < EPSILON) + { + return null; + } + + const inverseDet: number = 1.0 / det; + for (let i = 0; i < 16; i++) + { + inv[i] *= inverseDet; + } + + return new Matrix4(inv); + } + + public multiply(matrix: Matrix4): Matrix4 + { + return Matrix4.product(this, matrix); + } + + public multiplyVector3(vector: Vector3): Vector3 + { + const x: number = vector.x; + const y: number = vector.y; + const z: number = vector.z; + + const m: Float32Array = this.values; + + return new Vector3([ + m[0] * x + m[4] * y + m[8] * z + m[12], + m[1] * x + m[5] * y + m[9] * z + m[13], + m[2] * x + m[6] * y + m[10] * z + m[14] + ]); + } + + public multiplyVector4(vector: Vector4): Vector4 + { + const m: Float32Array = this.values; + const x: number = vector.x; + const y: number = vector.y; + const z: number = vector.z; + const w: number = vector.w; + + return new Vector4([ + m[0] * x + m[4] * y + m[8] * z + m[12] * w, + m[1] * x + m[5] * y + m[9] * z + m[13] * w, + m[2] * x + m[6] * y + m[10] * z + m[14] * w, + m[3] * x + m[7] * y + m[11] * z + m[15] * w + ]); + } + + public toMatrix3(): Matrix3 + { + return new Matrix3([ + this.values[0], + this.values[1], + this.values[2], + this.values[4], + this.values[5], + this.values[6], + this.values[8], + this.values[9], + this.values[10] + ]); + } + + public toInverseMatrix3(): Matrix3 | null + { + const matrix3 = this.toMatrix3(); + return matrix3.inverse(); + } + + public translate(vector: Vector3): Matrix4 + { + const translation = new Matrix4([ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + vector.x, vector.y, vector.z, 1 + ]); + + return Matrix4.product(this, translation); + } + + public scale(vector: Vector3): Matrix4 + { + const scaling = new Matrix4([ + vector.x, 0, 0, 0, + 0, vector.y, 0, 0, + 0, 0, vector.z, 0, + 0, 0, 0, 1 + ]); + + return Matrix4.product(this, scaling); + } + + public rotate(angle: number, axis: Vector3): Matrix4 | null + { + const x: number = axis.x; + const y: number = axis.y; + const z: number = axis.z; + + const length: number = Math.sqrt(x * x + y * y + z * z); + + if (length === 0) + { + return null; + } + + if (length !== 1) + { + const invLength: number = 1 / length; + this.rotate(angle, new Vector3([x * invLength, y * invLength, z * invLength])); + return this; + } + + const s: number = Math.sin(angle); + const c: number = Math.cos(angle); + const t: number = 1 - c; + + const m: number[] = [ + x * x * t + c, y * x * t + z * s, z * x * t - y * s, 0, + x * y * t - z * s, y * y * t + c, z * y * t + x * s, 0, + x * z * t + y * s, y * z * t - x * s, z * z * t + c, 0, + 0, 0, 0, 1 + ]; + + const rotationMatrix = new Matrix4(m); + return Matrix4.product(this, rotationMatrix); + } + + public toArray(): number[] + { + return Array.from(this.values); + } +} diff --git a/lib/classes/MessageBase.ts b/lib/classes/MessageBase.ts index 3960681..0ff8410 100644 --- a/lib/classes/MessageBase.ts +++ b/lib/classes/MessageBase.ts @@ -1,5 +1,5 @@ -import { Message } from '../enums/Message'; -import { MessageFlags } from '../enums/MessageFlags'; +import type { Message } from '../enums/Message'; +import type { MessageFlags } from '../enums/MessageFlags'; export interface MessageBase { @@ -7,7 +7,7 @@ export interface MessageBase messageFlags: MessageFlags; id: Message; - getSize(): number; - writeToBuffer(buf: Buffer, pos: number): number; - readFromBuffer(buf: Buffer, pos: number): number; + getSize: () => number; + writeToBuffer: (buf: Buffer, pos: number) => number; + readFromBuffer: (buf: Buffer, pos: number) => number; } diff --git a/lib/classes/MessageClasses.ts b/lib/classes/MessageClasses.ts index 20d6b0c..47f5fdc 100644 --- a/lib/classes/MessageClasses.ts +++ b/lib/classes/MessageClasses.ts @@ -480,485 +480,485 @@ export * from './messages/RezRestoreToWorld'; export * from './messages/LinkInventoryItem'; import { Message } from '../enums/Message'; -const messages: { [index: number]: string } = {}; -messages[Message.TestMessage] = 'TestMessageMessage'; -messages[Message.PacketAck] = 'PacketAckMessage'; -messages[Message.OpenCircuit] = 'OpenCircuitMessage'; -messages[Message.CloseCircuit] = 'CloseCircuitMessage'; -messages[Message.StartPingCheck] = 'StartPingCheckMessage'; -messages[Message.CompletePingCheck] = 'CompletePingCheckMessage'; -messages[Message.AddCircuitCode] = 'AddCircuitCodeMessage'; -messages[Message.UseCircuitCode] = 'UseCircuitCodeMessage'; -messages[Message.NeighborList] = 'NeighborListMessage'; -messages[Message.AvatarTextureUpdate] = 'AvatarTextureUpdateMessage'; -messages[Message.SimulatorMapUpdate] = 'SimulatorMapUpdateMessage'; -messages[Message.SimulatorSetMap] = 'SimulatorSetMapMessage'; -messages[Message.SubscribeLoad] = 'SubscribeLoadMessage'; -messages[Message.UnsubscribeLoad] = 'UnsubscribeLoadMessage'; -messages[Message.SimulatorReady] = 'SimulatorReadyMessage'; -messages[Message.TelehubInfo] = 'TelehubInfoMessage'; -messages[Message.SimulatorPresentAtLocation] = 'SimulatorPresentAtLocationMessage'; -messages[Message.SimulatorLoad] = 'SimulatorLoadMessage'; -messages[Message.SimulatorShutdownRequest] = 'SimulatorShutdownRequestMessage'; -messages[Message.RegionPresenceRequestByRegionID] = 'RegionPresenceRequestByRegionIDMessage'; -messages[Message.RegionPresenceRequestByHandle] = 'RegionPresenceRequestByHandleMessage'; -messages[Message.RegionPresenceResponse] = 'RegionPresenceResponseMessage'; -messages[Message.UpdateSimulator] = 'UpdateSimulatorMessage'; -messages[Message.LogDwellTime] = 'LogDwellTimeMessage'; -messages[Message.FeatureDisabled] = 'FeatureDisabledMessage'; -messages[Message.LogFailedMoneyTransaction] = 'LogFailedMoneyTransactionMessage'; -messages[Message.UserReportInternal] = 'UserReportInternalMessage'; -messages[Message.SetSimStatusInDatabase] = 'SetSimStatusInDatabaseMessage'; -messages[Message.SetSimPresenceInDatabase] = 'SetSimPresenceInDatabaseMessage'; -messages[Message.EconomyDataRequest] = 'EconomyDataRequestMessage'; -messages[Message.EconomyData] = 'EconomyDataMessage'; -messages[Message.AvatarPickerRequest] = 'AvatarPickerRequestMessage'; -messages[Message.AvatarPickerRequestBackend] = 'AvatarPickerRequestBackendMessage'; -messages[Message.AvatarPickerReply] = 'AvatarPickerReplyMessage'; -messages[Message.PlacesQuery] = 'PlacesQueryMessage'; -messages[Message.PlacesReply] = 'PlacesReplyMessage'; -messages[Message.DirFindQuery] = 'DirFindQueryMessage'; -messages[Message.DirFindQueryBackend] = 'DirFindQueryBackendMessage'; -messages[Message.DirPlacesQuery] = 'DirPlacesQueryMessage'; -messages[Message.DirPlacesQueryBackend] = 'DirPlacesQueryBackendMessage'; -messages[Message.DirPlacesReply] = 'DirPlacesReplyMessage'; -messages[Message.DirPeopleReply] = 'DirPeopleReplyMessage'; -messages[Message.DirEventsReply] = 'DirEventsReplyMessage'; -messages[Message.DirGroupsReply] = 'DirGroupsReplyMessage'; -messages[Message.DirClassifiedQuery] = 'DirClassifiedQueryMessage'; -messages[Message.DirClassifiedQueryBackend] = 'DirClassifiedQueryBackendMessage'; -messages[Message.DirClassifiedReply] = 'DirClassifiedReplyMessage'; -messages[Message.AvatarClassifiedReply] = 'AvatarClassifiedReplyMessage'; -messages[Message.ClassifiedInfoRequest] = 'ClassifiedInfoRequestMessage'; -messages[Message.ClassifiedInfoReply] = 'ClassifiedInfoReplyMessage'; -messages[Message.ClassifiedInfoUpdate] = 'ClassifiedInfoUpdateMessage'; -messages[Message.ClassifiedDelete] = 'ClassifiedDeleteMessage'; -messages[Message.ClassifiedGodDelete] = 'ClassifiedGodDeleteMessage'; -messages[Message.DirLandQuery] = 'DirLandQueryMessage'; -messages[Message.DirLandQueryBackend] = 'DirLandQueryBackendMessage'; -messages[Message.DirLandReply] = 'DirLandReplyMessage'; -messages[Message.DirPopularQuery] = 'DirPopularQueryMessage'; -messages[Message.DirPopularQueryBackend] = 'DirPopularQueryBackendMessage'; -messages[Message.DirPopularReply] = 'DirPopularReplyMessage'; -messages[Message.ParcelInfoRequest] = 'ParcelInfoRequestMessage'; -messages[Message.ParcelInfoReply] = 'ParcelInfoReplyMessage'; -messages[Message.ParcelObjectOwnersRequest] = 'ParcelObjectOwnersRequestMessage'; -messages[Message.ParcelObjectOwnersReply] = 'ParcelObjectOwnersReplyMessage'; -messages[Message.GroupNoticesListRequest] = 'GroupNoticesListRequestMessage'; -messages[Message.GroupNoticesListReply] = 'GroupNoticesListReplyMessage'; -messages[Message.GroupNoticeRequest] = 'GroupNoticeRequestMessage'; -messages[Message.GroupNoticeAdd] = 'GroupNoticeAddMessage'; -messages[Message.TeleportRequest] = 'TeleportRequestMessage'; -messages[Message.TeleportLocationRequest] = 'TeleportLocationRequestMessage'; -messages[Message.TeleportLocal] = 'TeleportLocalMessage'; -messages[Message.TeleportLandmarkRequest] = 'TeleportLandmarkRequestMessage'; -messages[Message.TeleportProgress] = 'TeleportProgressMessage'; -messages[Message.DataHomeLocationRequest] = 'DataHomeLocationRequestMessage'; -messages[Message.DataHomeLocationReply] = 'DataHomeLocationReplyMessage'; -messages[Message.TeleportFinish] = 'TeleportFinishMessage'; -messages[Message.StartLure] = 'StartLureMessage'; -messages[Message.TeleportLureRequest] = 'TeleportLureRequestMessage'; -messages[Message.TeleportCancel] = 'TeleportCancelMessage'; -messages[Message.TeleportStart] = 'TeleportStartMessage'; -messages[Message.TeleportFailed] = 'TeleportFailedMessage'; -messages[Message.Undo] = 'UndoMessage'; -messages[Message.Redo] = 'RedoMessage'; -messages[Message.UndoLand] = 'UndoLandMessage'; -messages[Message.AgentPause] = 'AgentPauseMessage'; -messages[Message.AgentResume] = 'AgentResumeMessage'; -messages[Message.AgentUpdate] = 'AgentUpdateMessage'; -messages[Message.ChatFromViewer] = 'ChatFromViewerMessage'; -messages[Message.AgentThrottle] = 'AgentThrottleMessage'; -messages[Message.AgentFOV] = 'AgentFOVMessage'; -messages[Message.AgentHeightWidth] = 'AgentHeightWidthMessage'; -messages[Message.AgentSetAppearance] = 'AgentSetAppearanceMessage'; -messages[Message.AgentAnimation] = 'AgentAnimationMessage'; -messages[Message.AgentRequestSit] = 'AgentRequestSitMessage'; -messages[Message.AgentSit] = 'AgentSitMessage'; -messages[Message.AgentQuitCopy] = 'AgentQuitCopyMessage'; -messages[Message.RequestImage] = 'RequestImageMessage'; -messages[Message.ImageNotInDatabase] = 'ImageNotInDatabaseMessage'; -messages[Message.RebakeAvatarTextures] = 'RebakeAvatarTexturesMessage'; -messages[Message.SetAlwaysRun] = 'SetAlwaysRunMessage'; -messages[Message.ObjectAdd] = 'ObjectAddMessage'; -messages[Message.ObjectDelete] = 'ObjectDeleteMessage'; -messages[Message.ObjectDuplicate] = 'ObjectDuplicateMessage'; -messages[Message.ObjectDuplicateOnRay] = 'ObjectDuplicateOnRayMessage'; -messages[Message.MultipleObjectUpdate] = 'MultipleObjectUpdateMessage'; -messages[Message.RequestMultipleObjects] = 'RequestMultipleObjectsMessage'; -messages[Message.ObjectPosition] = 'ObjectPositionMessage'; -messages[Message.ObjectScale] = 'ObjectScaleMessage'; -messages[Message.ObjectRotation] = 'ObjectRotationMessage'; -messages[Message.ObjectFlagUpdate] = 'ObjectFlagUpdateMessage'; -messages[Message.ObjectClickAction] = 'ObjectClickActionMessage'; -messages[Message.ObjectImage] = 'ObjectImageMessage'; -messages[Message.ObjectMaterial] = 'ObjectMaterialMessage'; -messages[Message.ObjectShape] = 'ObjectShapeMessage'; -messages[Message.ObjectExtraParams] = 'ObjectExtraParamsMessage'; -messages[Message.ObjectOwner] = 'ObjectOwnerMessage'; -messages[Message.ObjectGroup] = 'ObjectGroupMessage'; -messages[Message.ObjectBuy] = 'ObjectBuyMessage'; -messages[Message.BuyObjectInventory] = 'BuyObjectInventoryMessage'; -messages[Message.DerezContainer] = 'DerezContainerMessage'; -messages[Message.ObjectPermissions] = 'ObjectPermissionsMessage'; -messages[Message.ObjectSaleInfo] = 'ObjectSaleInfoMessage'; -messages[Message.ObjectName] = 'ObjectNameMessage'; -messages[Message.ObjectDescription] = 'ObjectDescriptionMessage'; -messages[Message.ObjectCategory] = 'ObjectCategoryMessage'; -messages[Message.ObjectSelect] = 'ObjectSelectMessage'; -messages[Message.ObjectDeselect] = 'ObjectDeselectMessage'; -messages[Message.ObjectAttach] = 'ObjectAttachMessage'; -messages[Message.ObjectDetach] = 'ObjectDetachMessage'; -messages[Message.ObjectDrop] = 'ObjectDropMessage'; -messages[Message.ObjectLink] = 'ObjectLinkMessage'; -messages[Message.ObjectDelink] = 'ObjectDelinkMessage'; -messages[Message.ObjectGrab] = 'ObjectGrabMessage'; -messages[Message.ObjectGrabUpdate] = 'ObjectGrabUpdateMessage'; -messages[Message.ObjectDeGrab] = 'ObjectDeGrabMessage'; -messages[Message.ObjectSpinStart] = 'ObjectSpinStartMessage'; -messages[Message.ObjectSpinUpdate] = 'ObjectSpinUpdateMessage'; -messages[Message.ObjectSpinStop] = 'ObjectSpinStopMessage'; -messages[Message.ObjectExportSelected] = 'ObjectExportSelectedMessage'; -messages[Message.ModifyLand] = 'ModifyLandMessage'; -messages[Message.VelocityInterpolateOn] = 'VelocityInterpolateOnMessage'; -messages[Message.VelocityInterpolateOff] = 'VelocityInterpolateOffMessage'; -messages[Message.StateSave] = 'StateSaveMessage'; -messages[Message.ReportAutosaveCrash] = 'ReportAutosaveCrashMessage'; -messages[Message.SimWideDeletes] = 'SimWideDeletesMessage'; -messages[Message.RequestObjectPropertiesFamily] = 'RequestObjectPropertiesFamilyMessage'; -messages[Message.TrackAgent] = 'TrackAgentMessage'; -messages[Message.ViewerStats] = 'ViewerStatsMessage'; -messages[Message.ScriptAnswerYes] = 'ScriptAnswerYesMessage'; -messages[Message.UserReport] = 'UserReportMessage'; -messages[Message.AlertMessage] = 'AlertMessageMessage'; -messages[Message.AgentAlertMessage] = 'AgentAlertMessageMessage'; -messages[Message.MeanCollisionAlert] = 'MeanCollisionAlertMessage'; -messages[Message.ViewerFrozenMessage] = 'ViewerFrozenMessageMessage'; -messages[Message.HealthMessage] = 'HealthMessageMessage'; -messages[Message.ChatFromSimulator] = 'ChatFromSimulatorMessage'; -messages[Message.SimStats] = 'SimStatsMessage'; -messages[Message.RequestRegionInfo] = 'RequestRegionInfoMessage'; -messages[Message.RegionInfo] = 'RegionInfoMessage'; -messages[Message.GodUpdateRegionInfo] = 'GodUpdateRegionInfoMessage'; -messages[Message.NearestLandingRegionRequest] = 'NearestLandingRegionRequestMessage'; -messages[Message.NearestLandingRegionReply] = 'NearestLandingRegionReplyMessage'; -messages[Message.NearestLandingRegionUpdated] = 'NearestLandingRegionUpdatedMessage'; -messages[Message.TeleportLandingStatusChanged] = 'TeleportLandingStatusChangedMessage'; -messages[Message.RegionHandshake] = 'RegionHandshakeMessage'; -messages[Message.RegionHandshakeReply] = 'RegionHandshakeReplyMessage'; -messages[Message.CoarseLocationUpdate] = 'CoarseLocationUpdateMessage'; -messages[Message.ImageData] = 'ImageDataMessage'; -messages[Message.ImagePacket] = 'ImagePacketMessage'; -messages[Message.LayerData] = 'LayerDataMessage'; -messages[Message.ObjectUpdate] = 'ObjectUpdateMessage'; -messages[Message.ObjectUpdateCompressed] = 'ObjectUpdateCompressedMessage'; -messages[Message.ObjectUpdateCached] = 'ObjectUpdateCachedMessage'; -messages[Message.ImprovedTerseObjectUpdate] = 'ImprovedTerseObjectUpdateMessage'; -messages[Message.KillObject] = 'KillObjectMessage'; -messages[Message.CrossedRegion] = 'CrossedRegionMessage'; -messages[Message.SimulatorViewerTimeMessage] = 'SimulatorViewerTimeMessageMessage'; -messages[Message.EnableSimulator] = 'EnableSimulatorMessage'; -messages[Message.DisableSimulator] = 'DisableSimulatorMessage'; -messages[Message.ConfirmEnableSimulator] = 'ConfirmEnableSimulatorMessage'; -messages[Message.TransferRequest] = 'TransferRequestMessage'; -messages[Message.TransferInfo] = 'TransferInfoMessage'; -messages[Message.TransferPacket] = 'TransferPacketMessage'; -messages[Message.TransferAbort] = 'TransferAbortMessage'; -messages[Message.RequestXfer] = 'RequestXferMessage'; -messages[Message.SendXferPacket] = 'SendXferPacketMessage'; -messages[Message.ConfirmXferPacket] = 'ConfirmXferPacketMessage'; -messages[Message.AbortXfer] = 'AbortXferMessage'; -messages[Message.AvatarAnimation] = 'AvatarAnimationMessage'; -messages[Message.AvatarAppearance] = 'AvatarAppearanceMessage'; -messages[Message.AvatarSitResponse] = 'AvatarSitResponseMessage'; -messages[Message.SetFollowCamProperties] = 'SetFollowCamPropertiesMessage'; -messages[Message.ClearFollowCamProperties] = 'ClearFollowCamPropertiesMessage'; -messages[Message.CameraConstraint] = 'CameraConstraintMessage'; -messages[Message.ObjectProperties] = 'ObjectPropertiesMessage'; -messages[Message.ObjectPropertiesFamily] = 'ObjectPropertiesFamilyMessage'; -messages[Message.RequestPayPrice] = 'RequestPayPriceMessage'; -messages[Message.PayPriceReply] = 'PayPriceReplyMessage'; -messages[Message.KickUser] = 'KickUserMessage'; -messages[Message.KickUserAck] = 'KickUserAckMessage'; -messages[Message.GodKickUser] = 'GodKickUserMessage'; -messages[Message.SystemKickUser] = 'SystemKickUserMessage'; -messages[Message.EjectUser] = 'EjectUserMessage'; -messages[Message.FreezeUser] = 'FreezeUserMessage'; -messages[Message.AvatarPropertiesRequest] = 'AvatarPropertiesRequestMessage'; -messages[Message.AvatarPropertiesRequestBackend] = 'AvatarPropertiesRequestBackendMessage'; -messages[Message.AvatarPropertiesReply] = 'AvatarPropertiesReplyMessage'; -messages[Message.AvatarInterestsReply] = 'AvatarInterestsReplyMessage'; -messages[Message.AvatarGroupsReply] = 'AvatarGroupsReplyMessage'; -messages[Message.AvatarPropertiesUpdate] = 'AvatarPropertiesUpdateMessage'; -messages[Message.AvatarInterestsUpdate] = 'AvatarInterestsUpdateMessage'; -messages[Message.AvatarNotesReply] = 'AvatarNotesReplyMessage'; -messages[Message.AvatarNotesUpdate] = 'AvatarNotesUpdateMessage'; -messages[Message.AvatarPicksReply] = 'AvatarPicksReplyMessage'; -messages[Message.EventInfoRequest] = 'EventInfoRequestMessage'; -messages[Message.EventInfoReply] = 'EventInfoReplyMessage'; -messages[Message.EventNotificationAddRequest] = 'EventNotificationAddRequestMessage'; -messages[Message.EventNotificationRemoveRequest] = 'EventNotificationRemoveRequestMessage'; -messages[Message.EventGodDelete] = 'EventGodDeleteMessage'; -messages[Message.PickInfoReply] = 'PickInfoReplyMessage'; -messages[Message.PickInfoUpdate] = 'PickInfoUpdateMessage'; -messages[Message.PickDelete] = 'PickDeleteMessage'; -messages[Message.PickGodDelete] = 'PickGodDeleteMessage'; -messages[Message.ScriptQuestion] = 'ScriptQuestionMessage'; -messages[Message.ScriptControlChange] = 'ScriptControlChangeMessage'; -messages[Message.ScriptDialog] = 'ScriptDialogMessage'; -messages[Message.ScriptDialogReply] = 'ScriptDialogReplyMessage'; -messages[Message.ForceScriptControlRelease] = 'ForceScriptControlReleaseMessage'; -messages[Message.RevokePermissions] = 'RevokePermissionsMessage'; -messages[Message.LoadURL] = 'LoadURLMessage'; -messages[Message.ScriptTeleportRequest] = 'ScriptTeleportRequestMessage'; -messages[Message.ParcelOverlay] = 'ParcelOverlayMessage'; -messages[Message.ParcelPropertiesRequest] = 'ParcelPropertiesRequestMessage'; -messages[Message.ParcelPropertiesRequestByID] = 'ParcelPropertiesRequestByIDMessage'; -messages[Message.ParcelProperties] = 'ParcelPropertiesMessage'; -messages[Message.ParcelPropertiesUpdate] = 'ParcelPropertiesUpdateMessage'; -messages[Message.ParcelReturnObjects] = 'ParcelReturnObjectsMessage'; -messages[Message.ParcelSetOtherCleanTime] = 'ParcelSetOtherCleanTimeMessage'; -messages[Message.ParcelDisableObjects] = 'ParcelDisableObjectsMessage'; -messages[Message.ParcelSelectObjects] = 'ParcelSelectObjectsMessage'; -messages[Message.EstateCovenantRequest] = 'EstateCovenantRequestMessage'; -messages[Message.EstateCovenantReply] = 'EstateCovenantReplyMessage'; -messages[Message.ForceObjectSelect] = 'ForceObjectSelectMessage'; -messages[Message.ParcelBuyPass] = 'ParcelBuyPassMessage'; -messages[Message.ParcelDeedToGroup] = 'ParcelDeedToGroupMessage'; -messages[Message.ParcelReclaim] = 'ParcelReclaimMessage'; -messages[Message.ParcelClaim] = 'ParcelClaimMessage'; -messages[Message.ParcelJoin] = 'ParcelJoinMessage'; -messages[Message.ParcelDivide] = 'ParcelDivideMessage'; -messages[Message.ParcelRelease] = 'ParcelReleaseMessage'; -messages[Message.ParcelBuy] = 'ParcelBuyMessage'; -messages[Message.ParcelGodForceOwner] = 'ParcelGodForceOwnerMessage'; -messages[Message.ParcelAccessListRequest] = 'ParcelAccessListRequestMessage'; -messages[Message.ParcelAccessListReply] = 'ParcelAccessListReplyMessage'; -messages[Message.ParcelAccessListUpdate] = 'ParcelAccessListUpdateMessage'; -messages[Message.ParcelDwellRequest] = 'ParcelDwellRequestMessage'; -messages[Message.ParcelDwellReply] = 'ParcelDwellReplyMessage'; -messages[Message.RequestParcelTransfer] = 'RequestParcelTransferMessage'; -messages[Message.UpdateParcel] = 'UpdateParcelMessage'; -messages[Message.RemoveParcel] = 'RemoveParcelMessage'; -messages[Message.MergeParcel] = 'MergeParcelMessage'; -messages[Message.LogParcelChanges] = 'LogParcelChangesMessage'; -messages[Message.CheckParcelSales] = 'CheckParcelSalesMessage'; -messages[Message.ParcelSales] = 'ParcelSalesMessage'; -messages[Message.ParcelGodMarkAsContent] = 'ParcelGodMarkAsContentMessage'; -messages[Message.ViewerStartAuction] = 'ViewerStartAuctionMessage'; -messages[Message.StartAuction] = 'StartAuctionMessage'; -messages[Message.ConfirmAuctionStart] = 'ConfirmAuctionStartMessage'; -messages[Message.CompleteAuction] = 'CompleteAuctionMessage'; -messages[Message.CancelAuction] = 'CancelAuctionMessage'; -messages[Message.CheckParcelAuctions] = 'CheckParcelAuctionsMessage'; -messages[Message.ParcelAuctions] = 'ParcelAuctionsMessage'; -messages[Message.UUIDNameRequest] = 'UUIDNameRequestMessage'; -messages[Message.UUIDNameReply] = 'UUIDNameReplyMessage'; -messages[Message.UUIDGroupNameRequest] = 'UUIDGroupNameRequestMessage'; -messages[Message.UUIDGroupNameReply] = 'UUIDGroupNameReplyMessage'; -messages[Message.ChatPass] = 'ChatPassMessage'; -messages[Message.EdgeDataPacket] = 'EdgeDataPacketMessage'; -messages[Message.SimStatus] = 'SimStatusMessage'; -messages[Message.ChildAgentUpdate] = 'ChildAgentUpdateMessage'; -messages[Message.ChildAgentAlive] = 'ChildAgentAliveMessage'; -messages[Message.ChildAgentPositionUpdate] = 'ChildAgentPositionUpdateMessage'; -messages[Message.ChildAgentDying] = 'ChildAgentDyingMessage'; -messages[Message.ChildAgentUnknown] = 'ChildAgentUnknownMessage'; -messages[Message.AtomicPassObject] = 'AtomicPassObjectMessage'; -messages[Message.KillChildAgents] = 'KillChildAgentsMessage'; -messages[Message.GetScriptRunning] = 'GetScriptRunningMessage'; -messages[Message.ScriptRunningReply] = 'ScriptRunningReplyMessage'; -messages[Message.SetScriptRunning] = 'SetScriptRunningMessage'; -messages[Message.ScriptReset] = 'ScriptResetMessage'; -messages[Message.ScriptSensorRequest] = 'ScriptSensorRequestMessage'; -messages[Message.ScriptSensorReply] = 'ScriptSensorReplyMessage'; -messages[Message.CompleteAgentMovement] = 'CompleteAgentMovementMessage'; -messages[Message.AgentMovementComplete] = 'AgentMovementCompleteMessage'; -messages[Message.DataServerLogout] = 'DataServerLogoutMessage'; -messages[Message.LogoutRequest] = 'LogoutRequestMessage'; -messages[Message.LogoutReply] = 'LogoutReplyMessage'; -messages[Message.ImprovedInstantMessage] = 'ImprovedInstantMessageMessage'; -messages[Message.RetrieveInstantMessages] = 'RetrieveInstantMessagesMessage'; -messages[Message.FindAgent] = 'FindAgentMessage'; -messages[Message.RequestGodlikePowers] = 'RequestGodlikePowersMessage'; -messages[Message.GrantGodlikePowers] = 'GrantGodlikePowersMessage'; -messages[Message.GodlikeMessage] = 'GodlikeMessageMessage'; -messages[Message.EstateOwnerMessage] = 'EstateOwnerMessageMessage'; -messages[Message.GenericMessage] = 'GenericMessageMessage'; -messages[Message.GenericStreamingMessage] = 'GenericStreamingMessageMessage'; -messages[Message.LargeGenericMessage] = 'LargeGenericMessageMessage'; -messages[Message.MuteListRequest] = 'MuteListRequestMessage'; -messages[Message.UpdateMuteListEntry] = 'UpdateMuteListEntryMessage'; -messages[Message.RemoveMuteListEntry] = 'RemoveMuteListEntryMessage'; -messages[Message.CopyInventoryFromNotecard] = 'CopyInventoryFromNotecardMessage'; -messages[Message.UpdateInventoryItem] = 'UpdateInventoryItemMessage'; -messages[Message.UpdateCreateInventoryItem] = 'UpdateCreateInventoryItemMessage'; -messages[Message.MoveInventoryItem] = 'MoveInventoryItemMessage'; -messages[Message.CopyInventoryItem] = 'CopyInventoryItemMessage'; -messages[Message.RemoveInventoryItem] = 'RemoveInventoryItemMessage'; -messages[Message.ChangeInventoryItemFlags] = 'ChangeInventoryItemFlagsMessage'; -messages[Message.SaveAssetIntoInventory] = 'SaveAssetIntoInventoryMessage'; -messages[Message.CreateInventoryFolder] = 'CreateInventoryFolderMessage'; -messages[Message.UpdateInventoryFolder] = 'UpdateInventoryFolderMessage'; -messages[Message.MoveInventoryFolder] = 'MoveInventoryFolderMessage'; -messages[Message.RemoveInventoryFolder] = 'RemoveInventoryFolderMessage'; -messages[Message.FetchInventoryDescendents] = 'FetchInventoryDescendentsMessage'; -messages[Message.InventoryDescendents] = 'InventoryDescendentsMessage'; -messages[Message.FetchInventory] = 'FetchInventoryMessage'; -messages[Message.FetchInventoryReply] = 'FetchInventoryReplyMessage'; -messages[Message.BulkUpdateInventory] = 'BulkUpdateInventoryMessage'; -messages[Message.RequestInventoryAsset] = 'RequestInventoryAssetMessage'; -messages[Message.InventoryAssetResponse] = 'InventoryAssetResponseMessage'; -messages[Message.RemoveInventoryObjects] = 'RemoveInventoryObjectsMessage'; -messages[Message.PurgeInventoryDescendents] = 'PurgeInventoryDescendentsMessage'; -messages[Message.UpdateTaskInventory] = 'UpdateTaskInventoryMessage'; -messages[Message.RemoveTaskInventory] = 'RemoveTaskInventoryMessage'; -messages[Message.MoveTaskInventory] = 'MoveTaskInventoryMessage'; -messages[Message.RequestTaskInventory] = 'RequestTaskInventoryMessage'; -messages[Message.ReplyTaskInventory] = 'ReplyTaskInventoryMessage'; -messages[Message.DeRezObject] = 'DeRezObjectMessage'; -messages[Message.DeRezAck] = 'DeRezAckMessage'; -messages[Message.RezObject] = 'RezObjectMessage'; -messages[Message.RezObjectFromNotecard] = 'RezObjectFromNotecardMessage'; -messages[Message.TransferInventory] = 'TransferInventoryMessage'; -messages[Message.TransferInventoryAck] = 'TransferInventoryAckMessage'; -messages[Message.AcceptFriendship] = 'AcceptFriendshipMessage'; -messages[Message.DeclineFriendship] = 'DeclineFriendshipMessage'; -messages[Message.FormFriendship] = 'FormFriendshipMessage'; -messages[Message.TerminateFriendship] = 'TerminateFriendshipMessage'; -messages[Message.OfferCallingCard] = 'OfferCallingCardMessage'; -messages[Message.AcceptCallingCard] = 'AcceptCallingCardMessage'; -messages[Message.DeclineCallingCard] = 'DeclineCallingCardMessage'; -messages[Message.RezScript] = 'RezScriptMessage'; -messages[Message.CreateInventoryItem] = 'CreateInventoryItemMessage'; -messages[Message.CreateLandmarkForEvent] = 'CreateLandmarkForEventMessage'; -messages[Message.EventLocationRequest] = 'EventLocationRequestMessage'; -messages[Message.EventLocationReply] = 'EventLocationReplyMessage'; -messages[Message.RegionHandleRequest] = 'RegionHandleRequestMessage'; -messages[Message.RegionIDAndHandleReply] = 'RegionIDAndHandleReplyMessage'; -messages[Message.MoneyTransferRequest] = 'MoneyTransferRequestMessage'; -messages[Message.MoneyTransferBackend] = 'MoneyTransferBackendMessage'; -messages[Message.MoneyBalanceRequest] = 'MoneyBalanceRequestMessage'; -messages[Message.MoneyBalanceReply] = 'MoneyBalanceReplyMessage'; -messages[Message.RoutedMoneyBalanceReply] = 'RoutedMoneyBalanceReplyMessage'; -messages[Message.ActivateGestures] = 'ActivateGesturesMessage'; -messages[Message.DeactivateGestures] = 'DeactivateGesturesMessage'; -messages[Message.MuteListUpdate] = 'MuteListUpdateMessage'; -messages[Message.UseCachedMuteList] = 'UseCachedMuteListMessage'; -messages[Message.GrantUserRights] = 'GrantUserRightsMessage'; -messages[Message.ChangeUserRights] = 'ChangeUserRightsMessage'; -messages[Message.OnlineNotification] = 'OnlineNotificationMessage'; -messages[Message.OfflineNotification] = 'OfflineNotificationMessage'; -messages[Message.SetStartLocationRequest] = 'SetStartLocationRequestMessage'; -messages[Message.SetStartLocation] = 'SetStartLocationMessage'; -messages[Message.NetTest] = 'NetTestMessage'; -messages[Message.SetCPURatio] = 'SetCPURatioMessage'; -messages[Message.SimCrashed] = 'SimCrashedMessage'; -messages[Message.NameValuePair] = 'NameValuePairMessage'; -messages[Message.RemoveNameValuePair] = 'RemoveNameValuePairMessage'; -messages[Message.UpdateAttachment] = 'UpdateAttachmentMessage'; -messages[Message.RemoveAttachment] = 'RemoveAttachmentMessage'; -messages[Message.SoundTrigger] = 'SoundTriggerMessage'; -messages[Message.AttachedSound] = 'AttachedSoundMessage'; -messages[Message.AttachedSoundGainChange] = 'AttachedSoundGainChangeMessage'; -messages[Message.PreloadSound] = 'PreloadSoundMessage'; -messages[Message.ObjectAnimation] = 'ObjectAnimationMessage'; -messages[Message.AssetUploadRequest] = 'AssetUploadRequestMessage'; -messages[Message.AssetUploadComplete] = 'AssetUploadCompleteMessage'; -messages[Message.EmailMessageRequest] = 'EmailMessageRequestMessage'; -messages[Message.EmailMessageReply] = 'EmailMessageReplyMessage'; -messages[Message.InternalScriptMail] = 'InternalScriptMailMessage'; -messages[Message.ScriptDataRequest] = 'ScriptDataRequestMessage'; -messages[Message.ScriptDataReply] = 'ScriptDataReplyMessage'; -messages[Message.CreateGroupRequest] = 'CreateGroupRequestMessage'; -messages[Message.CreateGroupReply] = 'CreateGroupReplyMessage'; -messages[Message.UpdateGroupInfo] = 'UpdateGroupInfoMessage'; -messages[Message.GroupRoleChanges] = 'GroupRoleChangesMessage'; -messages[Message.JoinGroupRequest] = 'JoinGroupRequestMessage'; -messages[Message.JoinGroupReply] = 'JoinGroupReplyMessage'; -messages[Message.EjectGroupMemberRequest] = 'EjectGroupMemberRequestMessage'; -messages[Message.EjectGroupMemberReply] = 'EjectGroupMemberReplyMessage'; -messages[Message.LeaveGroupRequest] = 'LeaveGroupRequestMessage'; -messages[Message.LeaveGroupReply] = 'LeaveGroupReplyMessage'; -messages[Message.InviteGroupRequest] = 'InviteGroupRequestMessage'; -messages[Message.InviteGroupResponse] = 'InviteGroupResponseMessage'; -messages[Message.GroupProfileRequest] = 'GroupProfileRequestMessage'; -messages[Message.GroupProfileReply] = 'GroupProfileReplyMessage'; -messages[Message.GroupAccountSummaryRequest] = 'GroupAccountSummaryRequestMessage'; -messages[Message.GroupAccountSummaryReply] = 'GroupAccountSummaryReplyMessage'; -messages[Message.GroupAccountDetailsRequest] = 'GroupAccountDetailsRequestMessage'; -messages[Message.GroupAccountDetailsReply] = 'GroupAccountDetailsReplyMessage'; -messages[Message.GroupAccountTransactionsRequest] = 'GroupAccountTransactionsRequestMessage'; -messages[Message.GroupAccountTransactionsReply] = 'GroupAccountTransactionsReplyMessage'; -messages[Message.GroupActiveProposalsRequest] = 'GroupActiveProposalsRequestMessage'; -messages[Message.GroupActiveProposalItemReply] = 'GroupActiveProposalItemReplyMessage'; -messages[Message.GroupVoteHistoryRequest] = 'GroupVoteHistoryRequestMessage'; -messages[Message.GroupVoteHistoryItemReply] = 'GroupVoteHistoryItemReplyMessage'; -messages[Message.StartGroupProposal] = 'StartGroupProposalMessage'; -messages[Message.GroupProposalBallot] = 'GroupProposalBallotMessage'; -messages[Message.TallyVotes] = 'TallyVotesMessage'; -messages[Message.GroupMembersRequest] = 'GroupMembersRequestMessage'; -messages[Message.GroupMembersReply] = 'GroupMembersReplyMessage'; -messages[Message.ActivateGroup] = 'ActivateGroupMessage'; -messages[Message.SetGroupContribution] = 'SetGroupContributionMessage'; -messages[Message.SetGroupAcceptNotices] = 'SetGroupAcceptNoticesMessage'; -messages[Message.GroupRoleDataRequest] = 'GroupRoleDataRequestMessage'; -messages[Message.GroupRoleDataReply] = 'GroupRoleDataReplyMessage'; -messages[Message.GroupRoleMembersRequest] = 'GroupRoleMembersRequestMessage'; -messages[Message.GroupRoleMembersReply] = 'GroupRoleMembersReplyMessage'; -messages[Message.GroupTitlesRequest] = 'GroupTitlesRequestMessage'; -messages[Message.GroupTitlesReply] = 'GroupTitlesReplyMessage'; -messages[Message.GroupTitleUpdate] = 'GroupTitleUpdateMessage'; -messages[Message.GroupRoleUpdate] = 'GroupRoleUpdateMessage'; -messages[Message.LiveHelpGroupRequest] = 'LiveHelpGroupRequestMessage'; -messages[Message.LiveHelpGroupReply] = 'LiveHelpGroupReplyMessage'; -messages[Message.AgentWearablesRequest] = 'AgentWearablesRequestMessage'; -messages[Message.AgentWearablesUpdate] = 'AgentWearablesUpdateMessage'; -messages[Message.AgentIsNowWearing] = 'AgentIsNowWearingMessage'; -messages[Message.AgentCachedTexture] = 'AgentCachedTextureMessage'; -messages[Message.AgentCachedTextureResponse] = 'AgentCachedTextureResponseMessage'; -messages[Message.AgentDataUpdateRequest] = 'AgentDataUpdateRequestMessage'; -messages[Message.AgentDataUpdate] = 'AgentDataUpdateMessage'; -messages[Message.GroupDataUpdate] = 'GroupDataUpdateMessage'; -messages[Message.AgentGroupDataUpdate] = 'AgentGroupDataUpdateMessage'; -messages[Message.AgentDropGroup] = 'AgentDropGroupMessage'; -messages[Message.LogTextMessage] = 'LogTextMessageMessage'; -messages[Message.ViewerEffect] = 'ViewerEffectMessage'; -messages[Message.CreateTrustedCircuit] = 'CreateTrustedCircuitMessage'; -messages[Message.DenyTrustedCircuit] = 'DenyTrustedCircuitMessage'; -messages[Message.RequestTrustedCircuit] = 'RequestTrustedCircuitMessage'; -messages[Message.RezSingleAttachmentFromInv] = 'RezSingleAttachmentFromInvMessage'; -messages[Message.RezMultipleAttachmentsFromInv] = 'RezMultipleAttachmentsFromInvMessage'; -messages[Message.DetachAttachmentIntoInv] = 'DetachAttachmentIntoInvMessage'; -messages[Message.CreateNewOutfitAttachments] = 'CreateNewOutfitAttachmentsMessage'; -messages[Message.UserInfoRequest] = 'UserInfoRequestMessage'; -messages[Message.UserInfoReply] = 'UserInfoReplyMessage'; -messages[Message.UpdateUserInfo] = 'UpdateUserInfoMessage'; -messages[Message.ParcelRename] = 'ParcelRenameMessage'; -messages[Message.InitiateDownload] = 'InitiateDownloadMessage'; -messages[Message.SystemMessage] = 'SystemMessageMessage'; -messages[Message.MapLayerRequest] = 'MapLayerRequestMessage'; -messages[Message.MapLayerReply] = 'MapLayerReplyMessage'; -messages[Message.MapBlockRequest] = 'MapBlockRequestMessage'; -messages[Message.MapNameRequest] = 'MapNameRequestMessage'; -messages[Message.MapBlockReply] = 'MapBlockReplyMessage'; -messages[Message.MapItemRequest] = 'MapItemRequestMessage'; -messages[Message.MapItemReply] = 'MapItemReplyMessage'; -messages[Message.SendPostcard] = 'SendPostcardMessage'; -messages[Message.RpcChannelRequest] = 'RpcChannelRequestMessage'; -messages[Message.RpcChannelReply] = 'RpcChannelReplyMessage'; -messages[Message.RpcScriptRequestInbound] = 'RpcScriptRequestInboundMessage'; -messages[Message.RpcScriptRequestInboundForward] = 'RpcScriptRequestInboundForwardMessage'; -messages[Message.RpcScriptReplyInbound] = 'RpcScriptReplyInboundMessage'; -messages[Message.ScriptMailRegistration] = 'ScriptMailRegistrationMessage'; -messages[Message.ParcelMediaCommandMessage] = 'ParcelMediaCommandMessageMessage'; -messages[Message.ParcelMediaUpdate] = 'ParcelMediaUpdateMessage'; -messages[Message.LandStatRequest] = 'LandStatRequestMessage'; -messages[Message.LandStatReply] = 'LandStatReplyMessage'; -messages[Message.Error] = 'ErrorMessage'; -messages[Message.ObjectIncludeInSearch] = 'ObjectIncludeInSearchMessage'; -messages[Message.RezRestoreToWorld] = 'RezRestoreToWorldMessage'; -messages[Message.LinkInventoryItem] = 'LinkInventoryItemMessage'; +const messages: Record = {}; +messages[(Message.TestMessage as number)] = 'TestMessageMessage'; +messages[(Message.PacketAck as number)] = 'PacketAckMessage'; +messages[(Message.OpenCircuit as number)] = 'OpenCircuitMessage'; +messages[(Message.CloseCircuit as number)] = 'CloseCircuitMessage'; +messages[(Message.StartPingCheck as number)] = 'StartPingCheckMessage'; +messages[(Message.CompletePingCheck as number)] = 'CompletePingCheckMessage'; +messages[(Message.AddCircuitCode as number)] = 'AddCircuitCodeMessage'; +messages[(Message.UseCircuitCode as number)] = 'UseCircuitCodeMessage'; +messages[(Message.NeighborList as number)] = 'NeighborListMessage'; +messages[(Message.AvatarTextureUpdate as number)] = 'AvatarTextureUpdateMessage'; +messages[(Message.SimulatorMapUpdate as number)] = 'SimulatorMapUpdateMessage'; +messages[(Message.SimulatorSetMap as number)] = 'SimulatorSetMapMessage'; +messages[(Message.SubscribeLoad as number)] = 'SubscribeLoadMessage'; +messages[(Message.UnsubscribeLoad as number)] = 'UnsubscribeLoadMessage'; +messages[(Message.SimulatorReady as number)] = 'SimulatorReadyMessage'; +messages[(Message.TelehubInfo as number)] = 'TelehubInfoMessage'; +messages[(Message.SimulatorPresentAtLocation as number)] = 'SimulatorPresentAtLocationMessage'; +messages[(Message.SimulatorLoad as number)] = 'SimulatorLoadMessage'; +messages[(Message.SimulatorShutdownRequest as number)] = 'SimulatorShutdownRequestMessage'; +messages[(Message.RegionPresenceRequestByRegionID as number)] = 'RegionPresenceRequestByRegionIDMessage'; +messages[(Message.RegionPresenceRequestByHandle as number)] = 'RegionPresenceRequestByHandleMessage'; +messages[(Message.RegionPresenceResponse as number)] = 'RegionPresenceResponseMessage'; +messages[(Message.UpdateSimulator as number)] = 'UpdateSimulatorMessage'; +messages[(Message.LogDwellTime as number)] = 'LogDwellTimeMessage'; +messages[(Message.FeatureDisabled as number)] = 'FeatureDisabledMessage'; +messages[(Message.LogFailedMoneyTransaction as number)] = 'LogFailedMoneyTransactionMessage'; +messages[(Message.UserReportInternal as number)] = 'UserReportInternalMessage'; +messages[(Message.SetSimStatusInDatabase as number)] = 'SetSimStatusInDatabaseMessage'; +messages[(Message.SetSimPresenceInDatabase as number)] = 'SetSimPresenceInDatabaseMessage'; +messages[(Message.EconomyDataRequest as number)] = 'EconomyDataRequestMessage'; +messages[(Message.EconomyData as number)] = 'EconomyDataMessage'; +messages[(Message.AvatarPickerRequest as number)] = 'AvatarPickerRequestMessage'; +messages[(Message.AvatarPickerRequestBackend as number)] = 'AvatarPickerRequestBackendMessage'; +messages[(Message.AvatarPickerReply as number)] = 'AvatarPickerReplyMessage'; +messages[(Message.PlacesQuery as number)] = 'PlacesQueryMessage'; +messages[(Message.PlacesReply as number)] = 'PlacesReplyMessage'; +messages[(Message.DirFindQuery as number)] = 'DirFindQueryMessage'; +messages[(Message.DirFindQueryBackend as number)] = 'DirFindQueryBackendMessage'; +messages[(Message.DirPlacesQuery as number)] = 'DirPlacesQueryMessage'; +messages[(Message.DirPlacesQueryBackend as number)] = 'DirPlacesQueryBackendMessage'; +messages[(Message.DirPlacesReply as number)] = 'DirPlacesReplyMessage'; +messages[(Message.DirPeopleReply as number)] = 'DirPeopleReplyMessage'; +messages[(Message.DirEventsReply as number)] = 'DirEventsReplyMessage'; +messages[(Message.DirGroupsReply as number)] = 'DirGroupsReplyMessage'; +messages[(Message.DirClassifiedQuery as number)] = 'DirClassifiedQueryMessage'; +messages[(Message.DirClassifiedQueryBackend as number)] = 'DirClassifiedQueryBackendMessage'; +messages[(Message.DirClassifiedReply as number)] = 'DirClassifiedReplyMessage'; +messages[(Message.AvatarClassifiedReply as number)] = 'AvatarClassifiedReplyMessage'; +messages[(Message.ClassifiedInfoRequest as number)] = 'ClassifiedInfoRequestMessage'; +messages[(Message.ClassifiedInfoReply as number)] = 'ClassifiedInfoReplyMessage'; +messages[(Message.ClassifiedInfoUpdate as number)] = 'ClassifiedInfoUpdateMessage'; +messages[(Message.ClassifiedDelete as number)] = 'ClassifiedDeleteMessage'; +messages[(Message.ClassifiedGodDelete as number)] = 'ClassifiedGodDeleteMessage'; +messages[(Message.DirLandQuery as number)] = 'DirLandQueryMessage'; +messages[(Message.DirLandQueryBackend as number)] = 'DirLandQueryBackendMessage'; +messages[(Message.DirLandReply as number)] = 'DirLandReplyMessage'; +messages[(Message.DirPopularQuery as number)] = 'DirPopularQueryMessage'; +messages[(Message.DirPopularQueryBackend as number)] = 'DirPopularQueryBackendMessage'; +messages[(Message.DirPopularReply as number)] = 'DirPopularReplyMessage'; +messages[(Message.ParcelInfoRequest as number)] = 'ParcelInfoRequestMessage'; +messages[(Message.ParcelInfoReply as number)] = 'ParcelInfoReplyMessage'; +messages[(Message.ParcelObjectOwnersRequest as number)] = 'ParcelObjectOwnersRequestMessage'; +messages[(Message.ParcelObjectOwnersReply as number)] = 'ParcelObjectOwnersReplyMessage'; +messages[(Message.GroupNoticesListRequest as number)] = 'GroupNoticesListRequestMessage'; +messages[(Message.GroupNoticesListReply as number)] = 'GroupNoticesListReplyMessage'; +messages[(Message.GroupNoticeRequest as number)] = 'GroupNoticeRequestMessage'; +messages[(Message.GroupNoticeAdd as number)] = 'GroupNoticeAddMessage'; +messages[(Message.TeleportRequest as number)] = 'TeleportRequestMessage'; +messages[(Message.TeleportLocationRequest as number)] = 'TeleportLocationRequestMessage'; +messages[(Message.TeleportLocal as number)] = 'TeleportLocalMessage'; +messages[(Message.TeleportLandmarkRequest as number)] = 'TeleportLandmarkRequestMessage'; +messages[(Message.TeleportProgress as number)] = 'TeleportProgressMessage'; +messages[(Message.DataHomeLocationRequest as number)] = 'DataHomeLocationRequestMessage'; +messages[(Message.DataHomeLocationReply as number)] = 'DataHomeLocationReplyMessage'; +messages[(Message.TeleportFinish as number)] = 'TeleportFinishMessage'; +messages[(Message.StartLure as number)] = 'StartLureMessage'; +messages[(Message.TeleportLureRequest as number)] = 'TeleportLureRequestMessage'; +messages[(Message.TeleportCancel as number)] = 'TeleportCancelMessage'; +messages[(Message.TeleportStart as number)] = 'TeleportStartMessage'; +messages[(Message.TeleportFailed as number)] = 'TeleportFailedMessage'; +messages[(Message.Undo as number)] = 'UndoMessage'; +messages[(Message.Redo as number)] = 'RedoMessage'; +messages[(Message.UndoLand as number)] = 'UndoLandMessage'; +messages[(Message.AgentPause as number)] = 'AgentPauseMessage'; +messages[(Message.AgentResume as number)] = 'AgentResumeMessage'; +messages[(Message.AgentUpdate as number)] = 'AgentUpdateMessage'; +messages[(Message.ChatFromViewer as number)] = 'ChatFromViewerMessage'; +messages[(Message.AgentThrottle as number)] = 'AgentThrottleMessage'; +messages[(Message.AgentFOV as number)] = 'AgentFOVMessage'; +messages[(Message.AgentHeightWidth as number)] = 'AgentHeightWidthMessage'; +messages[(Message.AgentSetAppearance as number)] = 'AgentSetAppearanceMessage'; +messages[(Message.AgentAnimation as number)] = 'AgentAnimationMessage'; +messages[(Message.AgentRequestSit as number)] = 'AgentRequestSitMessage'; +messages[(Message.AgentSit as number)] = 'AgentSitMessage'; +messages[(Message.AgentQuitCopy as number)] = 'AgentQuitCopyMessage'; +messages[(Message.RequestImage as number)] = 'RequestImageMessage'; +messages[(Message.ImageNotInDatabase as number)] = 'ImageNotInDatabaseMessage'; +messages[(Message.RebakeAvatarTextures as number)] = 'RebakeAvatarTexturesMessage'; +messages[(Message.SetAlwaysRun as number)] = 'SetAlwaysRunMessage'; +messages[(Message.ObjectAdd as number)] = 'ObjectAddMessage'; +messages[(Message.ObjectDelete as number)] = 'ObjectDeleteMessage'; +messages[(Message.ObjectDuplicate as number)] = 'ObjectDuplicateMessage'; +messages[(Message.ObjectDuplicateOnRay as number)] = 'ObjectDuplicateOnRayMessage'; +messages[(Message.MultipleObjectUpdate as number)] = 'MultipleObjectUpdateMessage'; +messages[(Message.RequestMultipleObjects as number)] = 'RequestMultipleObjectsMessage'; +messages[(Message.ObjectPosition as number)] = 'ObjectPositionMessage'; +messages[(Message.ObjectScale as number)] = 'ObjectScaleMessage'; +messages[(Message.ObjectRotation as number)] = 'ObjectRotationMessage'; +messages[(Message.ObjectFlagUpdate as number)] = 'ObjectFlagUpdateMessage'; +messages[(Message.ObjectClickAction as number)] = 'ObjectClickActionMessage'; +messages[(Message.ObjectImage as number)] = 'ObjectImageMessage'; +messages[(Message.ObjectMaterial as number)] = 'ObjectMaterialMessage'; +messages[(Message.ObjectShape as number)] = 'ObjectShapeMessage'; +messages[(Message.ObjectExtraParams as number)] = 'ObjectExtraParamsMessage'; +messages[(Message.ObjectOwner as number)] = 'ObjectOwnerMessage'; +messages[(Message.ObjectGroup as number)] = 'ObjectGroupMessage'; +messages[(Message.ObjectBuy as number)] = 'ObjectBuyMessage'; +messages[(Message.BuyObjectInventory as number)] = 'BuyObjectInventoryMessage'; +messages[(Message.DerezContainer as number)] = 'DerezContainerMessage'; +messages[(Message.ObjectPermissions as number)] = 'ObjectPermissionsMessage'; +messages[(Message.ObjectSaleInfo as number)] = 'ObjectSaleInfoMessage'; +messages[(Message.ObjectName as number)] = 'ObjectNameMessage'; +messages[(Message.ObjectDescription as number)] = 'ObjectDescriptionMessage'; +messages[(Message.ObjectCategory as number)] = 'ObjectCategoryMessage'; +messages[(Message.ObjectSelect as number)] = 'ObjectSelectMessage'; +messages[(Message.ObjectDeselect as number)] = 'ObjectDeselectMessage'; +messages[(Message.ObjectAttach as number)] = 'ObjectAttachMessage'; +messages[(Message.ObjectDetach as number)] = 'ObjectDetachMessage'; +messages[(Message.ObjectDrop as number)] = 'ObjectDropMessage'; +messages[(Message.ObjectLink as number)] = 'ObjectLinkMessage'; +messages[(Message.ObjectDelink as number)] = 'ObjectDelinkMessage'; +messages[(Message.ObjectGrab as number)] = 'ObjectGrabMessage'; +messages[(Message.ObjectGrabUpdate as number)] = 'ObjectGrabUpdateMessage'; +messages[(Message.ObjectDeGrab as number)] = 'ObjectDeGrabMessage'; +messages[(Message.ObjectSpinStart as number)] = 'ObjectSpinStartMessage'; +messages[(Message.ObjectSpinUpdate as number)] = 'ObjectSpinUpdateMessage'; +messages[(Message.ObjectSpinStop as number)] = 'ObjectSpinStopMessage'; +messages[(Message.ObjectExportSelected as number)] = 'ObjectExportSelectedMessage'; +messages[(Message.ModifyLand as number)] = 'ModifyLandMessage'; +messages[(Message.VelocityInterpolateOn as number)] = 'VelocityInterpolateOnMessage'; +messages[(Message.VelocityInterpolateOff as number)] = 'VelocityInterpolateOffMessage'; +messages[(Message.StateSave as number)] = 'StateSaveMessage'; +messages[(Message.ReportAutosaveCrash as number)] = 'ReportAutosaveCrashMessage'; +messages[(Message.SimWideDeletes as number)] = 'SimWideDeletesMessage'; +messages[(Message.RequestObjectPropertiesFamily as number)] = 'RequestObjectPropertiesFamilyMessage'; +messages[(Message.TrackAgent as number)] = 'TrackAgentMessage'; +messages[(Message.ViewerStats as number)] = 'ViewerStatsMessage'; +messages[(Message.ScriptAnswerYes as number)] = 'ScriptAnswerYesMessage'; +messages[(Message.UserReport as number)] = 'UserReportMessage'; +messages[(Message.AlertMessage as number)] = 'AlertMessageMessage'; +messages[(Message.AgentAlertMessage as number)] = 'AgentAlertMessageMessage'; +messages[(Message.MeanCollisionAlert as number)] = 'MeanCollisionAlertMessage'; +messages[(Message.ViewerFrozenMessage as number)] = 'ViewerFrozenMessageMessage'; +messages[(Message.HealthMessage as number)] = 'HealthMessageMessage'; +messages[(Message.ChatFromSimulator as number)] = 'ChatFromSimulatorMessage'; +messages[(Message.SimStats as number)] = 'SimStatsMessage'; +messages[(Message.RequestRegionInfo as number)] = 'RequestRegionInfoMessage'; +messages[(Message.RegionInfo as number)] = 'RegionInfoMessage'; +messages[(Message.GodUpdateRegionInfo as number)] = 'GodUpdateRegionInfoMessage'; +messages[(Message.NearestLandingRegionRequest as number)] = 'NearestLandingRegionRequestMessage'; +messages[(Message.NearestLandingRegionReply as number)] = 'NearestLandingRegionReplyMessage'; +messages[(Message.NearestLandingRegionUpdated as number)] = 'NearestLandingRegionUpdatedMessage'; +messages[(Message.TeleportLandingStatusChanged as number)] = 'TeleportLandingStatusChangedMessage'; +messages[(Message.RegionHandshake as number)] = 'RegionHandshakeMessage'; +messages[(Message.RegionHandshakeReply as number)] = 'RegionHandshakeReplyMessage'; +messages[(Message.CoarseLocationUpdate as number)] = 'CoarseLocationUpdateMessage'; +messages[(Message.ImageData as number)] = 'ImageDataMessage'; +messages[(Message.ImagePacket as number)] = 'ImagePacketMessage'; +messages[(Message.LayerData as number)] = 'LayerDataMessage'; +messages[(Message.ObjectUpdate as number)] = 'ObjectUpdateMessage'; +messages[(Message.ObjectUpdateCompressed as number)] = 'ObjectUpdateCompressedMessage'; +messages[(Message.ObjectUpdateCached as number)] = 'ObjectUpdateCachedMessage'; +messages[(Message.ImprovedTerseObjectUpdate as number)] = 'ImprovedTerseObjectUpdateMessage'; +messages[(Message.KillObject as number)] = 'KillObjectMessage'; +messages[(Message.CrossedRegion as number)] = 'CrossedRegionMessage'; +messages[(Message.SimulatorViewerTimeMessage as number)] = 'SimulatorViewerTimeMessageMessage'; +messages[(Message.EnableSimulator as number)] = 'EnableSimulatorMessage'; +messages[(Message.DisableSimulator as number)] = 'DisableSimulatorMessage'; +messages[(Message.ConfirmEnableSimulator as number)] = 'ConfirmEnableSimulatorMessage'; +messages[(Message.TransferRequest as number)] = 'TransferRequestMessage'; +messages[(Message.TransferInfo as number)] = 'TransferInfoMessage'; +messages[(Message.TransferPacket as number)] = 'TransferPacketMessage'; +messages[(Message.TransferAbort as number)] = 'TransferAbortMessage'; +messages[(Message.RequestXfer as number)] = 'RequestXferMessage'; +messages[(Message.SendXferPacket as number)] = 'SendXferPacketMessage'; +messages[(Message.ConfirmXferPacket as number)] = 'ConfirmXferPacketMessage'; +messages[(Message.AbortXfer as number)] = 'AbortXferMessage'; +messages[(Message.AvatarAnimation as number)] = 'AvatarAnimationMessage'; +messages[(Message.AvatarAppearance as number)] = 'AvatarAppearanceMessage'; +messages[(Message.AvatarSitResponse as number)] = 'AvatarSitResponseMessage'; +messages[(Message.SetFollowCamProperties as number)] = 'SetFollowCamPropertiesMessage'; +messages[(Message.ClearFollowCamProperties as number)] = 'ClearFollowCamPropertiesMessage'; +messages[(Message.CameraConstraint as number)] = 'CameraConstraintMessage'; +messages[(Message.ObjectProperties as number)] = 'ObjectPropertiesMessage'; +messages[(Message.ObjectPropertiesFamily as number)] = 'ObjectPropertiesFamilyMessage'; +messages[(Message.RequestPayPrice as number)] = 'RequestPayPriceMessage'; +messages[(Message.PayPriceReply as number)] = 'PayPriceReplyMessage'; +messages[(Message.KickUser as number)] = 'KickUserMessage'; +messages[(Message.KickUserAck as number)] = 'KickUserAckMessage'; +messages[(Message.GodKickUser as number)] = 'GodKickUserMessage'; +messages[(Message.SystemKickUser as number)] = 'SystemKickUserMessage'; +messages[(Message.EjectUser as number)] = 'EjectUserMessage'; +messages[(Message.FreezeUser as number)] = 'FreezeUserMessage'; +messages[(Message.AvatarPropertiesRequest as number)] = 'AvatarPropertiesRequestMessage'; +messages[(Message.AvatarPropertiesRequestBackend as number)] = 'AvatarPropertiesRequestBackendMessage'; +messages[(Message.AvatarPropertiesReply as number)] = 'AvatarPropertiesReplyMessage'; +messages[(Message.AvatarInterestsReply as number)] = 'AvatarInterestsReplyMessage'; +messages[(Message.AvatarGroupsReply as number)] = 'AvatarGroupsReplyMessage'; +messages[(Message.AvatarPropertiesUpdate as number)] = 'AvatarPropertiesUpdateMessage'; +messages[(Message.AvatarInterestsUpdate as number)] = 'AvatarInterestsUpdateMessage'; +messages[(Message.AvatarNotesReply as number)] = 'AvatarNotesReplyMessage'; +messages[(Message.AvatarNotesUpdate as number)] = 'AvatarNotesUpdateMessage'; +messages[(Message.AvatarPicksReply as number)] = 'AvatarPicksReplyMessage'; +messages[(Message.EventInfoRequest as number)] = 'EventInfoRequestMessage'; +messages[(Message.EventInfoReply as number)] = 'EventInfoReplyMessage'; +messages[(Message.EventNotificationAddRequest as number)] = 'EventNotificationAddRequestMessage'; +messages[(Message.EventNotificationRemoveRequest as number)] = 'EventNotificationRemoveRequestMessage'; +messages[(Message.EventGodDelete as number)] = 'EventGodDeleteMessage'; +messages[(Message.PickInfoReply as number)] = 'PickInfoReplyMessage'; +messages[(Message.PickInfoUpdate as number)] = 'PickInfoUpdateMessage'; +messages[(Message.PickDelete as number)] = 'PickDeleteMessage'; +messages[(Message.PickGodDelete as number)] = 'PickGodDeleteMessage'; +messages[(Message.ScriptQuestion as number)] = 'ScriptQuestionMessage'; +messages[(Message.ScriptControlChange as number)] = 'ScriptControlChangeMessage'; +messages[(Message.ScriptDialog as number)] = 'ScriptDialogMessage'; +messages[(Message.ScriptDialogReply as number)] = 'ScriptDialogReplyMessage'; +messages[(Message.ForceScriptControlRelease as number)] = 'ForceScriptControlReleaseMessage'; +messages[(Message.RevokePermissions as number)] = 'RevokePermissionsMessage'; +messages[(Message.LoadURL as number)] = 'LoadURLMessage'; +messages[(Message.ScriptTeleportRequest as number)] = 'ScriptTeleportRequestMessage'; +messages[(Message.ParcelOverlay as number)] = 'ParcelOverlayMessage'; +messages[(Message.ParcelPropertiesRequest as number)] = 'ParcelPropertiesRequestMessage'; +messages[(Message.ParcelPropertiesRequestByID as number)] = 'ParcelPropertiesRequestByIDMessage'; +messages[(Message.ParcelProperties as number)] = 'ParcelPropertiesMessage'; +messages[(Message.ParcelPropertiesUpdate as number)] = 'ParcelPropertiesUpdateMessage'; +messages[(Message.ParcelReturnObjects as number)] = 'ParcelReturnObjectsMessage'; +messages[(Message.ParcelSetOtherCleanTime as number)] = 'ParcelSetOtherCleanTimeMessage'; +messages[(Message.ParcelDisableObjects as number)] = 'ParcelDisableObjectsMessage'; +messages[(Message.ParcelSelectObjects as number)] = 'ParcelSelectObjectsMessage'; +messages[(Message.EstateCovenantRequest as number)] = 'EstateCovenantRequestMessage'; +messages[(Message.EstateCovenantReply as number)] = 'EstateCovenantReplyMessage'; +messages[(Message.ForceObjectSelect as number)] = 'ForceObjectSelectMessage'; +messages[(Message.ParcelBuyPass as number)] = 'ParcelBuyPassMessage'; +messages[(Message.ParcelDeedToGroup as number)] = 'ParcelDeedToGroupMessage'; +messages[(Message.ParcelReclaim as number)] = 'ParcelReclaimMessage'; +messages[(Message.ParcelClaim as number)] = 'ParcelClaimMessage'; +messages[(Message.ParcelJoin as number)] = 'ParcelJoinMessage'; +messages[(Message.ParcelDivide as number)] = 'ParcelDivideMessage'; +messages[(Message.ParcelRelease as number)] = 'ParcelReleaseMessage'; +messages[(Message.ParcelBuy as number)] = 'ParcelBuyMessage'; +messages[(Message.ParcelGodForceOwner as number)] = 'ParcelGodForceOwnerMessage'; +messages[(Message.ParcelAccessListRequest as number)] = 'ParcelAccessListRequestMessage'; +messages[(Message.ParcelAccessListReply as number)] = 'ParcelAccessListReplyMessage'; +messages[(Message.ParcelAccessListUpdate as number)] = 'ParcelAccessListUpdateMessage'; +messages[(Message.ParcelDwellRequest as number)] = 'ParcelDwellRequestMessage'; +messages[(Message.ParcelDwellReply as number)] = 'ParcelDwellReplyMessage'; +messages[(Message.RequestParcelTransfer as number)] = 'RequestParcelTransferMessage'; +messages[(Message.UpdateParcel as number)] = 'UpdateParcelMessage'; +messages[(Message.RemoveParcel as number)] = 'RemoveParcelMessage'; +messages[(Message.MergeParcel as number)] = 'MergeParcelMessage'; +messages[(Message.LogParcelChanges as number)] = 'LogParcelChangesMessage'; +messages[(Message.CheckParcelSales as number)] = 'CheckParcelSalesMessage'; +messages[(Message.ParcelSales as number)] = 'ParcelSalesMessage'; +messages[(Message.ParcelGodMarkAsContent as number)] = 'ParcelGodMarkAsContentMessage'; +messages[(Message.ViewerStartAuction as number)] = 'ViewerStartAuctionMessage'; +messages[(Message.StartAuction as number)] = 'StartAuctionMessage'; +messages[(Message.ConfirmAuctionStart as number)] = 'ConfirmAuctionStartMessage'; +messages[(Message.CompleteAuction as number)] = 'CompleteAuctionMessage'; +messages[(Message.CancelAuction as number)] = 'CancelAuctionMessage'; +messages[(Message.CheckParcelAuctions as number)] = 'CheckParcelAuctionsMessage'; +messages[(Message.ParcelAuctions as number)] = 'ParcelAuctionsMessage'; +messages[(Message.UUIDNameRequest as number)] = 'UUIDNameRequestMessage'; +messages[(Message.UUIDNameReply as number)] = 'UUIDNameReplyMessage'; +messages[(Message.UUIDGroupNameRequest as number)] = 'UUIDGroupNameRequestMessage'; +messages[(Message.UUIDGroupNameReply as number)] = 'UUIDGroupNameReplyMessage'; +messages[(Message.ChatPass as number)] = 'ChatPassMessage'; +messages[(Message.EdgeDataPacket as number)] = 'EdgeDataPacketMessage'; +messages[(Message.SimStatus as number)] = 'SimStatusMessage'; +messages[(Message.ChildAgentUpdate as number)] = 'ChildAgentUpdateMessage'; +messages[(Message.ChildAgentAlive as number)] = 'ChildAgentAliveMessage'; +messages[(Message.ChildAgentPositionUpdate as number)] = 'ChildAgentPositionUpdateMessage'; +messages[(Message.ChildAgentDying as number)] = 'ChildAgentDyingMessage'; +messages[(Message.ChildAgentUnknown as number)] = 'ChildAgentUnknownMessage'; +messages[(Message.AtomicPassObject as number)] = 'AtomicPassObjectMessage'; +messages[(Message.KillChildAgents as number)] = 'KillChildAgentsMessage'; +messages[(Message.GetScriptRunning as number)] = 'GetScriptRunningMessage'; +messages[(Message.ScriptRunningReply as number)] = 'ScriptRunningReplyMessage'; +messages[(Message.SetScriptRunning as number)] = 'SetScriptRunningMessage'; +messages[(Message.ScriptReset as number)] = 'ScriptResetMessage'; +messages[(Message.ScriptSensorRequest as number)] = 'ScriptSensorRequestMessage'; +messages[(Message.ScriptSensorReply as number)] = 'ScriptSensorReplyMessage'; +messages[(Message.CompleteAgentMovement as number)] = 'CompleteAgentMovementMessage'; +messages[(Message.AgentMovementComplete as number)] = 'AgentMovementCompleteMessage'; +messages[(Message.DataServerLogout as number)] = 'DataServerLogoutMessage'; +messages[(Message.LogoutRequest as number)] = 'LogoutRequestMessage'; +messages[(Message.LogoutReply as number)] = 'LogoutReplyMessage'; +messages[(Message.ImprovedInstantMessage as number)] = 'ImprovedInstantMessageMessage'; +messages[(Message.RetrieveInstantMessages as number)] = 'RetrieveInstantMessagesMessage'; +messages[(Message.FindAgent as number)] = 'FindAgentMessage'; +messages[(Message.RequestGodlikePowers as number)] = 'RequestGodlikePowersMessage'; +messages[(Message.GrantGodlikePowers as number)] = 'GrantGodlikePowersMessage'; +messages[(Message.GodlikeMessage as number)] = 'GodlikeMessageMessage'; +messages[(Message.EstateOwnerMessage as number)] = 'EstateOwnerMessageMessage'; +messages[(Message.GenericMessage as number)] = 'GenericMessageMessage'; +messages[(Message.GenericStreamingMessage as number)] = 'GenericStreamingMessageMessage'; +messages[(Message.LargeGenericMessage as number)] = 'LargeGenericMessageMessage'; +messages[(Message.MuteListRequest as number)] = 'MuteListRequestMessage'; +messages[(Message.UpdateMuteListEntry as number)] = 'UpdateMuteListEntryMessage'; +messages[(Message.RemoveMuteListEntry as number)] = 'RemoveMuteListEntryMessage'; +messages[(Message.CopyInventoryFromNotecard as number)] = 'CopyInventoryFromNotecardMessage'; +messages[(Message.UpdateInventoryItem as number)] = 'UpdateInventoryItemMessage'; +messages[(Message.UpdateCreateInventoryItem as number)] = 'UpdateCreateInventoryItemMessage'; +messages[(Message.MoveInventoryItem as number)] = 'MoveInventoryItemMessage'; +messages[(Message.CopyInventoryItem as number)] = 'CopyInventoryItemMessage'; +messages[(Message.RemoveInventoryItem as number)] = 'RemoveInventoryItemMessage'; +messages[(Message.ChangeInventoryItemFlags as number)] = 'ChangeInventoryItemFlagsMessage'; +messages[(Message.SaveAssetIntoInventory as number)] = 'SaveAssetIntoInventoryMessage'; +messages[(Message.CreateInventoryFolder as number)] = 'CreateInventoryFolderMessage'; +messages[(Message.UpdateInventoryFolder as number)] = 'UpdateInventoryFolderMessage'; +messages[(Message.MoveInventoryFolder as number)] = 'MoveInventoryFolderMessage'; +messages[(Message.RemoveInventoryFolder as number)] = 'RemoveInventoryFolderMessage'; +messages[(Message.FetchInventoryDescendents as number)] = 'FetchInventoryDescendentsMessage'; +messages[(Message.InventoryDescendents as number)] = 'InventoryDescendentsMessage'; +messages[(Message.FetchInventory as number)] = 'FetchInventoryMessage'; +messages[(Message.FetchInventoryReply as number)] = 'FetchInventoryReplyMessage'; +messages[(Message.BulkUpdateInventory as number)] = 'BulkUpdateInventoryMessage'; +messages[(Message.RequestInventoryAsset as number)] = 'RequestInventoryAssetMessage'; +messages[(Message.InventoryAssetResponse as number)] = 'InventoryAssetResponseMessage'; +messages[(Message.RemoveInventoryObjects as number)] = 'RemoveInventoryObjectsMessage'; +messages[(Message.PurgeInventoryDescendents as number)] = 'PurgeInventoryDescendentsMessage'; +messages[(Message.UpdateTaskInventory as number)] = 'UpdateTaskInventoryMessage'; +messages[(Message.RemoveTaskInventory as number)] = 'RemoveTaskInventoryMessage'; +messages[(Message.MoveTaskInventory as number)] = 'MoveTaskInventoryMessage'; +messages[(Message.RequestTaskInventory as number)] = 'RequestTaskInventoryMessage'; +messages[(Message.ReplyTaskInventory as number)] = 'ReplyTaskInventoryMessage'; +messages[(Message.DeRezObject as number)] = 'DeRezObjectMessage'; +messages[(Message.DeRezAck as number)] = 'DeRezAckMessage'; +messages[(Message.RezObject as number)] = 'RezObjectMessage'; +messages[(Message.RezObjectFromNotecard as number)] = 'RezObjectFromNotecardMessage'; +messages[(Message.TransferInventory as number)] = 'TransferInventoryMessage'; +messages[(Message.TransferInventoryAck as number)] = 'TransferInventoryAckMessage'; +messages[(Message.AcceptFriendship as number)] = 'AcceptFriendshipMessage'; +messages[(Message.DeclineFriendship as number)] = 'DeclineFriendshipMessage'; +messages[(Message.FormFriendship as number)] = 'FormFriendshipMessage'; +messages[(Message.TerminateFriendship as number)] = 'TerminateFriendshipMessage'; +messages[(Message.OfferCallingCard as number)] = 'OfferCallingCardMessage'; +messages[(Message.AcceptCallingCard as number)] = 'AcceptCallingCardMessage'; +messages[(Message.DeclineCallingCard as number)] = 'DeclineCallingCardMessage'; +messages[(Message.RezScript as number)] = 'RezScriptMessage'; +messages[(Message.CreateInventoryItem as number)] = 'CreateInventoryItemMessage'; +messages[(Message.CreateLandmarkForEvent as number)] = 'CreateLandmarkForEventMessage'; +messages[(Message.EventLocationRequest as number)] = 'EventLocationRequestMessage'; +messages[(Message.EventLocationReply as number)] = 'EventLocationReplyMessage'; +messages[(Message.RegionHandleRequest as number)] = 'RegionHandleRequestMessage'; +messages[(Message.RegionIDAndHandleReply as number)] = 'RegionIDAndHandleReplyMessage'; +messages[(Message.MoneyTransferRequest as number)] = 'MoneyTransferRequestMessage'; +messages[(Message.MoneyTransferBackend as number)] = 'MoneyTransferBackendMessage'; +messages[(Message.MoneyBalanceRequest as number)] = 'MoneyBalanceRequestMessage'; +messages[(Message.MoneyBalanceReply as number)] = 'MoneyBalanceReplyMessage'; +messages[(Message.RoutedMoneyBalanceReply as number)] = 'RoutedMoneyBalanceReplyMessage'; +messages[(Message.ActivateGestures as number)] = 'ActivateGesturesMessage'; +messages[(Message.DeactivateGestures as number)] = 'DeactivateGesturesMessage'; +messages[(Message.MuteListUpdate as number)] = 'MuteListUpdateMessage'; +messages[(Message.UseCachedMuteList as number)] = 'UseCachedMuteListMessage'; +messages[(Message.GrantUserRights as number)] = 'GrantUserRightsMessage'; +messages[(Message.ChangeUserRights as number)] = 'ChangeUserRightsMessage'; +messages[(Message.OnlineNotification as number)] = 'OnlineNotificationMessage'; +messages[(Message.OfflineNotification as number)] = 'OfflineNotificationMessage'; +messages[(Message.SetStartLocationRequest as number)] = 'SetStartLocationRequestMessage'; +messages[(Message.SetStartLocation as number)] = 'SetStartLocationMessage'; +messages[(Message.NetTest as number)] = 'NetTestMessage'; +messages[(Message.SetCPURatio as number)] = 'SetCPURatioMessage'; +messages[(Message.SimCrashed as number)] = 'SimCrashedMessage'; +messages[(Message.NameValuePair as number)] = 'NameValuePairMessage'; +messages[(Message.RemoveNameValuePair as number)] = 'RemoveNameValuePairMessage'; +messages[(Message.UpdateAttachment as number)] = 'UpdateAttachmentMessage'; +messages[(Message.RemoveAttachment as number)] = 'RemoveAttachmentMessage'; +messages[(Message.SoundTrigger as number)] = 'SoundTriggerMessage'; +messages[(Message.AttachedSound as number)] = 'AttachedSoundMessage'; +messages[(Message.AttachedSoundGainChange as number)] = 'AttachedSoundGainChangeMessage'; +messages[(Message.PreloadSound as number)] = 'PreloadSoundMessage'; +messages[(Message.ObjectAnimation as number)] = 'ObjectAnimationMessage'; +messages[(Message.AssetUploadRequest as number)] = 'AssetUploadRequestMessage'; +messages[(Message.AssetUploadComplete as number)] = 'AssetUploadCompleteMessage'; +messages[(Message.EmailMessageRequest as number)] = 'EmailMessageRequestMessage'; +messages[(Message.EmailMessageReply as number)] = 'EmailMessageReplyMessage'; +messages[(Message.InternalScriptMail as number)] = 'InternalScriptMailMessage'; +messages[(Message.ScriptDataRequest as number)] = 'ScriptDataRequestMessage'; +messages[(Message.ScriptDataReply as number)] = 'ScriptDataReplyMessage'; +messages[(Message.CreateGroupRequest as number)] = 'CreateGroupRequestMessage'; +messages[(Message.CreateGroupReply as number)] = 'CreateGroupReplyMessage'; +messages[(Message.UpdateGroupInfo as number)] = 'UpdateGroupInfoMessage'; +messages[(Message.GroupRoleChanges as number)] = 'GroupRoleChangesMessage'; +messages[(Message.JoinGroupRequest as number)] = 'JoinGroupRequestMessage'; +messages[(Message.JoinGroupReply as number)] = 'JoinGroupReplyMessage'; +messages[(Message.EjectGroupMemberRequest as number)] = 'EjectGroupMemberRequestMessage'; +messages[(Message.EjectGroupMemberReply as number)] = 'EjectGroupMemberReplyMessage'; +messages[(Message.LeaveGroupRequest as number)] = 'LeaveGroupRequestMessage'; +messages[(Message.LeaveGroupReply as number)] = 'LeaveGroupReplyMessage'; +messages[(Message.InviteGroupRequest as number)] = 'InviteGroupRequestMessage'; +messages[(Message.InviteGroupResponse as number)] = 'InviteGroupResponseMessage'; +messages[(Message.GroupProfileRequest as number)] = 'GroupProfileRequestMessage'; +messages[(Message.GroupProfileReply as number)] = 'GroupProfileReplyMessage'; +messages[(Message.GroupAccountSummaryRequest as number)] = 'GroupAccountSummaryRequestMessage'; +messages[(Message.GroupAccountSummaryReply as number)] = 'GroupAccountSummaryReplyMessage'; +messages[(Message.GroupAccountDetailsRequest as number)] = 'GroupAccountDetailsRequestMessage'; +messages[(Message.GroupAccountDetailsReply as number)] = 'GroupAccountDetailsReplyMessage'; +messages[(Message.GroupAccountTransactionsRequest as number)] = 'GroupAccountTransactionsRequestMessage'; +messages[(Message.GroupAccountTransactionsReply as number)] = 'GroupAccountTransactionsReplyMessage'; +messages[(Message.GroupActiveProposalsRequest as number)] = 'GroupActiveProposalsRequestMessage'; +messages[(Message.GroupActiveProposalItemReply as number)] = 'GroupActiveProposalItemReplyMessage'; +messages[(Message.GroupVoteHistoryRequest as number)] = 'GroupVoteHistoryRequestMessage'; +messages[(Message.GroupVoteHistoryItemReply as number)] = 'GroupVoteHistoryItemReplyMessage'; +messages[(Message.StartGroupProposal as number)] = 'StartGroupProposalMessage'; +messages[(Message.GroupProposalBallot as number)] = 'GroupProposalBallotMessage'; +messages[(Message.TallyVotes as number)] = 'TallyVotesMessage'; +messages[(Message.GroupMembersRequest as number)] = 'GroupMembersRequestMessage'; +messages[(Message.GroupMembersReply as number)] = 'GroupMembersReplyMessage'; +messages[(Message.ActivateGroup as number)] = 'ActivateGroupMessage'; +messages[(Message.SetGroupContribution as number)] = 'SetGroupContributionMessage'; +messages[(Message.SetGroupAcceptNotices as number)] = 'SetGroupAcceptNoticesMessage'; +messages[(Message.GroupRoleDataRequest as number)] = 'GroupRoleDataRequestMessage'; +messages[(Message.GroupRoleDataReply as number)] = 'GroupRoleDataReplyMessage'; +messages[(Message.GroupRoleMembersRequest as number)] = 'GroupRoleMembersRequestMessage'; +messages[(Message.GroupRoleMembersReply as number)] = 'GroupRoleMembersReplyMessage'; +messages[(Message.GroupTitlesRequest as number)] = 'GroupTitlesRequestMessage'; +messages[(Message.GroupTitlesReply as number)] = 'GroupTitlesReplyMessage'; +messages[(Message.GroupTitleUpdate as number)] = 'GroupTitleUpdateMessage'; +messages[(Message.GroupRoleUpdate as number)] = 'GroupRoleUpdateMessage'; +messages[(Message.LiveHelpGroupRequest as number)] = 'LiveHelpGroupRequestMessage'; +messages[(Message.LiveHelpGroupReply as number)] = 'LiveHelpGroupReplyMessage'; +messages[(Message.AgentWearablesRequest as number)] = 'AgentWearablesRequestMessage'; +messages[(Message.AgentWearablesUpdate as number)] = 'AgentWearablesUpdateMessage'; +messages[(Message.AgentIsNowWearing as number)] = 'AgentIsNowWearingMessage'; +messages[(Message.AgentCachedTexture as number)] = 'AgentCachedTextureMessage'; +messages[(Message.AgentCachedTextureResponse as number)] = 'AgentCachedTextureResponseMessage'; +messages[(Message.AgentDataUpdateRequest as number)] = 'AgentDataUpdateRequestMessage'; +messages[(Message.AgentDataUpdate as number)] = 'AgentDataUpdateMessage'; +messages[(Message.GroupDataUpdate as number)] = 'GroupDataUpdateMessage'; +messages[(Message.AgentGroupDataUpdate as number)] = 'AgentGroupDataUpdateMessage'; +messages[(Message.AgentDropGroup as number)] = 'AgentDropGroupMessage'; +messages[(Message.LogTextMessage as number)] = 'LogTextMessageMessage'; +messages[(Message.ViewerEffect as number)] = 'ViewerEffectMessage'; +messages[(Message.CreateTrustedCircuit as number)] = 'CreateTrustedCircuitMessage'; +messages[(Message.DenyTrustedCircuit as number)] = 'DenyTrustedCircuitMessage'; +messages[(Message.RequestTrustedCircuit as number)] = 'RequestTrustedCircuitMessage'; +messages[(Message.RezSingleAttachmentFromInv as number)] = 'RezSingleAttachmentFromInvMessage'; +messages[(Message.RezMultipleAttachmentsFromInv as number)] = 'RezMultipleAttachmentsFromInvMessage'; +messages[(Message.DetachAttachmentIntoInv as number)] = 'DetachAttachmentIntoInvMessage'; +messages[(Message.CreateNewOutfitAttachments as number)] = 'CreateNewOutfitAttachmentsMessage'; +messages[(Message.UserInfoRequest as number)] = 'UserInfoRequestMessage'; +messages[(Message.UserInfoReply as number)] = 'UserInfoReplyMessage'; +messages[(Message.UpdateUserInfo as number)] = 'UpdateUserInfoMessage'; +messages[(Message.ParcelRename as number)] = 'ParcelRenameMessage'; +messages[(Message.InitiateDownload as number)] = 'InitiateDownloadMessage'; +messages[(Message.SystemMessage as number)] = 'SystemMessageMessage'; +messages[(Message.MapLayerRequest as number)] = 'MapLayerRequestMessage'; +messages[(Message.MapLayerReply as number)] = 'MapLayerReplyMessage'; +messages[(Message.MapBlockRequest as number)] = 'MapBlockRequestMessage'; +messages[(Message.MapNameRequest as number)] = 'MapNameRequestMessage'; +messages[(Message.MapBlockReply as number)] = 'MapBlockReplyMessage'; +messages[(Message.MapItemRequest as number)] = 'MapItemRequestMessage'; +messages[(Message.MapItemReply as number)] = 'MapItemReplyMessage'; +messages[(Message.SendPostcard as number)] = 'SendPostcardMessage'; +messages[(Message.RpcChannelRequest as number)] = 'RpcChannelRequestMessage'; +messages[(Message.RpcChannelReply as number)] = 'RpcChannelReplyMessage'; +messages[(Message.RpcScriptRequestInbound as number)] = 'RpcScriptRequestInboundMessage'; +messages[(Message.RpcScriptRequestInboundForward as number)] = 'RpcScriptRequestInboundForwardMessage'; +messages[(Message.RpcScriptReplyInbound as number)] = 'RpcScriptReplyInboundMessage'; +messages[(Message.ScriptMailRegistration as number)] = 'ScriptMailRegistrationMessage'; +messages[(Message.ParcelMediaCommandMessage as number)] = 'ParcelMediaCommandMessageMessage'; +messages[(Message.ParcelMediaUpdate as number)] = 'ParcelMediaUpdateMessage'; +messages[(Message.LandStatRequest as number)] = 'LandStatRequestMessage'; +messages[(Message.LandStatReply as number)] = 'LandStatReplyMessage'; +messages[(Message.Error as number)] = 'ErrorMessage'; +messages[(Message.ObjectIncludeInSearch as number)] = 'ObjectIncludeInSearchMessage'; +messages[(Message.RezRestoreToWorld as number)] = 'RezRestoreToWorldMessage'; +messages[(Message.LinkInventoryItem as number)] = 'LinkInventoryItemMessage'; export function nameFromID(id: Message): string { diff --git a/lib/classes/NameValue.ts b/lib/classes/NameValue.ts index b996df4..3420e9c 100644 --- a/lib/classes/NameValue.ts +++ b/lib/classes/NameValue.ts @@ -1,7 +1,7 @@ export class NameValue { - type: string; - class: string; - sendTo: string; - value: string; + public type: string; + public class: string; + public sendTo: string; + public value: string; } diff --git a/lib/classes/ObjectResolver.ts b/lib/classes/ObjectResolver.ts index 28f0e9c..6938a8c 100644 --- a/lib/classes/ObjectResolver.ts +++ b/lib/classes/ObjectResolver.ts @@ -1,20 +1,21 @@ -import { GameObject } from './public/GameObject'; +import type { GameObject } from './public/GameObject'; import { PCode, PrimFlags, UUID } from '..'; -import { Region } from './Region'; -import { Subscription } from 'rxjs'; -import { GetObjectsOptions } from './commands/RegionCommands'; -import { ObjectResolvedEvent } from '../events/ObjectResolvedEvent'; +import type { Region } from './Region'; +import type { Subscription } from 'rxjs'; +import type { GetObjectsOptions } from './commands/RegionCommands'; +import type { ObjectResolvedEvent } from '../events/ObjectResolvedEvent'; import { clearTimeout } from 'timers'; import { BatchQueue } from './BatchQueue'; export class ObjectResolver { - private resolveQueue = new BatchQueue(256, this.resolveInternal.bind(this)); - private getCostsQueue = new BatchQueue(64, this.getCostsInternal.bind(this)); + private readonly resolveQueue = new BatchQueue(256, this.resolveInternal.bind(this)); + private readonly getCostsQueue = new BatchQueue(64, this.getCostsInternal.bind(this)); + private region?: Region; - constructor(private region?: Region) + public constructor(region?: Region) { - + this.region = region; } public async resolveObjects(objects: GameObject[], options: GetObjectsOptions): Promise @@ -29,6 +30,7 @@ export class ObjectResolver const failed: GameObject[] = []; for (const obj of objects) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison if (!obj.IsAttachment && !options.includeTempObjects && ((obj.Flags ?? 0) & PrimFlags.TemporaryOnRez) === PrimFlags.TemporaryOnRez) { continue; @@ -38,6 +40,22 @@ export class ObjectResolver continue; } this.region.objects.populateChildren(obj); + let fullyResolved = obj.resolvedAt !== undefined; + if (fullyResolved && obj.children) + { + for(const o of obj.children) + { + if (o.resolvedAt === undefined) + { + fullyResolved = false; + break; + } + } + } + if (fullyResolved && !options.forceReResolve) + { + continue; + } this.scanObject(obj, objs); } @@ -46,7 +64,7 @@ export class ObjectResolver return failed; } - return await this.resolveQueue.add(Array.from(objs.values())); + return this.resolveQueue.add(Array.from(objs.values())); } public async getInventory(object: GameObject): Promise @@ -147,6 +165,10 @@ export class ObjectResolver private async getCostsInternal(objs: Set): Promise> { const failed = new Set(); + if (objs.size === 0) + { + return failed; + } const submitted: Map = new Map(); for (const obj of objs.values()) @@ -174,11 +196,11 @@ export class ObjectResolver continue; } const obj: GameObject = this.region.objects.getObjectByUUID(new UUID(key)); - obj.linkPhysicsImpact = parseFloat(costs['linked_set_physics_cost']); - obj.linkResourceImpact = parseFloat(costs['linked_set_resource_cost']); - obj.physicaImpact = parseFloat(costs['physics_cost']); - obj.resourceImpact = parseFloat(costs['resource_cost']); - obj.limitingType = costs['resource_limiting_type']; + obj.linkPhysicsImpact = parseFloat(costs.linked_set_physics_cost); + obj.linkResourceImpact = parseFloat(costs.linked_set_resource_cost); + obj.physicaImpact = parseFloat(costs.physics_cost); + obj.resourceImpact = parseFloat(costs.resource_cost); + obj.limitingType = costs.resource_limiting_type; obj.landImpact = Math.round(obj.linkPhysicsImpact); @@ -187,19 +209,22 @@ export class ObjectResolver obj.landImpact = Math.round(obj.linkResourceImpact); } obj.calculatedLandImpact = obj.landImpact; + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison if (obj.Flags !== undefined && ((obj.Flags & PrimFlags.TemporaryOnRez) === PrimFlags.TemporaryOnRez) && obj.limitingType === 'legacy') { obj.calculatedLandImpact = 0; } submitted.delete(key); } - catch (error) + catch (_error: unknown) { + // Nothing } } } - catch (error) + catch (_error) { + // Nothing } for (const go of submitted.values()) @@ -210,7 +235,7 @@ export class ObjectResolver return failed; } - private async waitForResolve(objs: Map, timeout: number = 10000): Promise + private async waitForResolve(objs: Map, timeout = 10000): Promise { const entries = objs.entries(); for (const [localID, entry] of entries) diff --git a/lib/classes/ObjectStoreFull.ts b/lib/classes/ObjectStoreFull.ts index 5c7779e..488d542 100644 --- a/lib/classes/ObjectStoreFull.ts +++ b/lib/classes/ObjectStoreFull.ts @@ -1,16 +1,16 @@ -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 type { Circuit } from './Circuit'; +import type { ObjectUpdateMessage } from './messages/ObjectUpdate'; +import type { ObjectUpdateCachedMessage } from './messages/ObjectUpdateCached'; +import type { ObjectUpdateCompressedMessage } from './messages/ObjectUpdateCompressed'; +import type { ImprovedTerseObjectUpdateMessage } from './messages/ImprovedTerseObjectUpdate'; import { RequestMultipleObjectsMessage } from './messages/RequestMultipleObjects'; -import { Agent } from './Agent'; +import type { 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 type { ClientEvents } from './ClientEvents'; +import type { IObjectStore } from './interfaces/IObjectStore'; import { RBush3D } from 'rbush-3d/dist'; import { Vector4 } from './Vector4'; import { TextureEntry } from './TextureEntry'; @@ -23,17 +23,17 @@ import { ExtraParams } from './public/ExtraParams'; import { CompressedFlags } from '../enums/CompressedFlags'; import { PCode } from '../enums/PCode'; import { BotOptionFlags } from '../enums/BotOptionFlags'; -import { PacketFlags } from '../enums/PacketFlags'; +import type { PacketFlags } from '../enums/PacketFlags'; export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore { - rtree?: RBush3D; + public rtree?: RBush3D; - constructor(circuit: Circuit, agent: Agent, clientEvents: ClientEvents, options: BotOptionFlags) + public constructor(circuit: Circuit, agent: Agent, clientEvents: ClientEvents, options: BotOptionFlags) { super(circuit, agent, clientEvents, options); this.rtree = new RBush3D(); - this.fullStore = true; + } protected objectUpdate(objectUpdate: ObjectUpdateMessage): void @@ -106,6 +106,10 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore { this.cachedMaterialOverrides.set(obj.ID, obj.TextureEntry.gltfMaterialOverrides); } + if (obj.FullID.toString() === '1efb961a-0c5e-0ea5-84c7-68da50ae2659') + { + console.log('Whoopie'); + } obj.TextureEntry = TextureEntry.from(objData.TextureEntry); const override = this.cachedMaterialOverrides.get(obj.ID); if (override) @@ -139,8 +143,7 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore obj.TreeSpecies = pcodeData[0]; } break; - case PCode.Prim: - + default: break; } @@ -150,9 +153,9 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore if (this.options & BotOptionFlags.StoreMyAttachmentsOnly) { - for (const objParentID of Object.keys(this.objectsByParent)) + for (const objParentID of this.objectsByParent.keys()) { - const parent = parseInt(objParentID, 10); + const parent = objParentID; if (parent !== this.agent.localID) { let foundAvatars = false; @@ -186,7 +189,7 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore obj.extraParams = ExtraParams.from(objData.ExtraParams); obj.NameValue = this.parseNameValues(Utils.BufferToStringSimple(objData.NameValue)); - obj.IsAttachment = obj.NameValue['AttachItemID'] !== undefined; + obj.IsAttachment = obj.NameValue.get('AttachItemID') !== undefined; if (obj.IsAttachment && obj.State !== undefined) { obj.attachmentPoint = this.decodeAttachPoint(obj.State); @@ -216,11 +219,7 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore const parentObj = this.objects.get(objData.ParentID ?? 0); if (objData.ParentID !== undefined && objData.ParentID !== 0 && !parentObj && !obj.IsAttachment) { - this.requestMissingObject(objData.ParentID).then(() => - { - }).catch(() => - { - }); + void this.requestMissingObject(objData.ParentID); } this.notifyObjectUpdate(newObject, obj); obj.onTextureUpdate.next(); @@ -260,7 +259,7 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore } } - protected async objectUpdateCompressed(objectUpdateCompressed: ObjectUpdateCompressedMessage): Promise + protected objectUpdateCompressed(objectUpdateCompressed: ObjectUpdateCompressedMessage): void { for (const obj of objectUpdateCompressed.ObjectData) { @@ -272,7 +271,7 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore pos += 16; const localID = buf.readUInt32LE(pos); pos += 4; - const pcode = buf.readUInt8(pos++); + const pcode: PCode = buf.readUInt8(pos++); let newObj = false; let o = this.objects.get(localID); if (!o) @@ -354,7 +353,7 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore { if (o.ParentID !== undefined && o.ParentID !== 0 && !this.objects.has(o.ParentID) && !o.IsAttachment) { - this.requestMissingObject(o.ParentID).catch((e) => + this.requestMissingObject(o.ParentID).catch((e: unknown) => { console.error(e); }); @@ -393,13 +392,13 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore } if (compressedflags & CompressedFlags.HasParticles) { - o.Particles = ParticleSystem.from(buf.slice(pos, pos + 86)); + o.Particles = ParticleSystem.from(buf.subarray(pos, pos + 86)); pos += 86; } // Extra params const extraParamsLength = ExtraParams.getLengthOfParams(buf, pos); - o.extraParams = ExtraParams.from(buf.slice(pos, pos + extraParamsLength)); + o.extraParams = ExtraParams.from(buf.subarray(pos, pos + extraParamsLength)); pos += extraParamsLength; if (compressedflags & CompressedFlags.HasSound) @@ -447,7 +446,7 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore { this.cachedMaterialOverrides.set(o.ID, o.TextureEntry.gltfMaterialOverrides); } - o.TextureEntry = TextureEntry.from(buf.slice(pos, pos + textureEntryLength)); + o.TextureEntry = TextureEntry.from(buf.subarray(pos, pos + textureEntryLength)); const override = this.cachedMaterialOverrides.get(o.ID); if (override) { @@ -460,7 +459,7 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore { const textureAnimLength = buf.readUInt32LE(pos); pos = pos + 4; - o.textureAnim = TextureAnim.from(buf.slice(pos, pos + textureAnimLength)); + o.textureAnim = TextureAnim.from(buf.subarray(pos, pos + textureAnimLength)); } o.IsAttachment = (compressedflags & CompressedFlags.HasNameValues) !== 0 && o.ParentID !== 0; @@ -482,9 +481,8 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore const dilation = objectUpdateTerse.RegionData.TimeDilation / 65535.0; this.clientEvents.onRegionTimeDilation.next(dilation); - for (let i = 0; i < objectUpdateTerse.ObjectData.length; i++) + for(const objectData of objectUpdateTerse.ObjectData) { - const objectData = objectUpdateTerse.ObjectData[i]; if (!(this.options & BotOptionFlags.StoreMyAttachmentsOnly)) { let pos = 0; @@ -535,7 +533,7 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore { this.cachedMaterialOverrides.set(o.ID, o.TextureEntry.gltfMaterialOverrides); } - o.TextureEntry = TextureEntry.from(objectData.TextureEntry.slice(4)); + o.TextureEntry = TextureEntry.from(objectData.TextureEntry.subarray(4)); const override = this.cachedMaterialOverrides.get(o.ID); if (override) { @@ -551,10 +549,7 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore else { // We don't know about this object, so request it - this.requestMissingObject(localID).catch(() => - { - - }); + void this.requestMissingObject(localID); } } } diff --git a/lib/classes/ObjectStoreLite.ts b/lib/classes/ObjectStoreLite.ts index 5b601be..42cb97e 100644 --- a/lib/classes/ObjectStoreLite.ts +++ b/lib/classes/ObjectStoreLite.ts @@ -1,29 +1,29 @@ -import { Circuit } from './Circuit'; +import type { Circuit } from './Circuit'; import { Logger } from './Logger'; -import { Packet } from './Packet'; +import type { 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 type { ObjectUpdateMessage } from './messages/ObjectUpdate'; +import type { ObjectUpdateCachedMessage } from './messages/ObjectUpdateCached'; +import type { ObjectUpdateCompressedMessage } from './messages/ObjectUpdateCompressed'; +import type { ImprovedTerseObjectUpdateMessage } from './messages/ImprovedTerseObjectUpdate'; import { RequestMultipleObjectsMessage } from './messages/RequestMultipleObjects'; -import { Agent } from './Agent'; +import type { 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 type { ClientEvents } from './ClientEvents'; +import type { KillObjectMessage } from './messages/KillObject'; +import type { 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 type { RBush3D } from 'rbush-3d/dist'; +import type { 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 type { Subscription } from 'rxjs'; import { ExtraParams } from './public/ExtraParams'; -import { ObjectPropertiesMessage } from './messages/ObjectProperties'; +import type { ObjectPropertiesMessage } from './messages/ObjectProperties'; import { SelectedObjectEvent } from '../events/SelectedObjectEvent'; import { PrimFlags } from '../enums/PrimFlags'; import { PacketFlags } from '../enums/PacketFlags'; @@ -33,17 +33,23 @@ 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 type { ObjectPhysicsDataEvent } from '../events/ObjectPhysicsDataEvent'; import { ObjectResolvedEvent } from '../events/ObjectResolvedEvent'; import { Avatar } from './public/Avatar'; -import { GenericStreamingMessageMessage } from './messages/GenericStreamingMessage'; -import { LLSDNotationParser } from './llsd/LLSDNotationParser'; +import type { GenericStreamingMessageMessage } from './messages/GenericStreamingMessage'; +import { LLGLTFMaterialOverride } from './LLGLTFMaterialOverride'; +import type * as Long from 'long'; +import { LLSD } from './llsd/LLSD'; import { LLSDMap } from './llsd/LLSDMap'; -import { LLGLTFMaterialOverride, LLGLTFTextureTransformOverride } from './LLGLTFMaterialOverride'; -import * as Long from 'long'; +import { LLSDInteger } from './llsd/LLSDInteger'; +import type { LLSDReal } from './llsd/LLSDReal'; +import { LLSDArray } from './llsd/LLSDArray'; +import type { GetObjectsOptions } from './commands/RegionCommands'; export class ObjectStoreLite implements IObjectStore { + public rtree?: RBush3D; + protected circuit?: Circuit; protected agent: Agent; protected objects = new Map(); @@ -85,15 +91,13 @@ export class ObjectStoreLite implements IObjectStore SitName: Buffer; TextureID: Buffer; }>; - private physicsSubscription: Subscription; - private selectedPrimsWithoutUpdate = new Map(); + private readonly physicsSubscription: Subscription; + private readonly selectedPrimsWithoutUpdate = new Map(); private selectedChecker?: NodeJS.Timeout; - private blacklist: Map = new Map(); - private pendingResolves: Set = new Set(); + private readonly blacklist: Map = new Map(); + private readonly pendingResolves: Set = new Set(); - rtree?: RBush3D; - - constructor(circuit: Circuit, agent: Agent, clientEvents: ClientEvents, options: BotOptionFlags) + public constructor(circuit: Circuit, agent: Agent, clientEvents: ClientEvents, options: BotOptionFlags) { agent.localID = 0; this.options = options; @@ -105,11 +109,12 @@ export class ObjectStoreLite implements IObjectStore Message.ObjectUpdate, Message.ObjectUpdateCached, Message.ObjectUpdateCompressed, + Message.MultipleObjectUpdate, Message.ImprovedTerseObjectUpdate, Message.ObjectProperties, Message.KillObject, Message.GenericStreamingMessage - ], async(packet: Packet) => + ], (packet: Packet): void => { switch (packet.message.id) { @@ -123,11 +128,11 @@ export class ObjectStoreLite implements IObjectStore if (genMsg.MethodData.Method === 0x4175) { // LLSD Notation format - const result = LLSDNotationParser.parse(genMsg.DataBlock.Data.toString('utf-8')); + const result = LLSD.parseNotation(genMsg.DataBlock.Data.toString('utf-8')); if (result instanceof LLSDMap) { const localID = result.get('id'); - if (typeof localID !== 'number') + if (!(localID instanceof LLSDInteger)) { return; } @@ -141,7 +146,7 @@ export class ObjectStoreLite implements IObjectStore for (let x = 0; x < tes.length; x++) { const te = tes[x]; - if (typeof te !== 'number') + if (!(te instanceof LLSDInteger)) { continue; } @@ -152,114 +157,93 @@ export class ObjectStoreLite implements IObjectStore continue; } - const textureIDs = params.get('tex'); - const baseColor = params.get('bc'); - const emissiveColor = params.get('ec'); - const metallicFactor = params.get('mf'); - const roughnessFactor = params.get('rf'); - const alphaMode = params.get('am'); - const alphaCutoff = params.get('ac'); - const doubleSided = params.get('ds'); - const textureTransforms = params.get('ti'); + const textureIDs = params.get('tex') as (UUID | null)[] | undefined; + const baseColor = params.get('bc') as [LLSDReal, LLSDReal, LLSDReal, LLSDReal] | undefined; + const emissiveColor = params.get('ec') as LLSDReal | undefined; + const metallicFactor = params.get('mf') as LLSDReal | undefined; + const roughnessFactor = params.get('rf') as LLSDReal | undefined; + const alphaMode = params.get('am') as LLSDInteger | undefined; + const alphaCutoff = params.get('ac') as LLSDReal | undefined; + const doubleSided = params.get('ds') as boolean | undefined; + const textureTransforms = params.get('ti') as (LLSDMap &{ + o: LLSDReal[] + s: LLSDReal[], + r: LLSDReal + })[] | undefined; const override = new LLGLTFMaterialOverride(); - overrides.set(te, override); + overrides.set(te.valueOf(), override); if (textureIDs !== undefined && Array.isArray(textureIDs)) { override.textures = []; for (const tex of textureIDs) { - if (typeof tex === 'string') - { - override.textures.push(tex); - } - else if (tex instanceof UUID) + if (tex instanceof UUID) { override.textures.push(tex.toString()); } + else + { + override.textures.push(null); + } } } - function isNumberArray(array: unknown[]): array is number[] + if (baseColor !== undefined && Array.isArray(baseColor) && baseColor.length === 4) { - return array.every(element => typeof element === 'number'); + override.baseColor = LLSDArray.toNumberArray(baseColor); } - if (baseColor !== undefined && Array.isArray(baseColor) && baseColor.length === 4 && isNumberArray(baseColor)) + if (emissiveColor !== undefined && Array.isArray(emissiveColor) && emissiveColor.length === 3) { - override.baseColor = baseColor; + override.emissiveFactor = LLSDArray.toNumberArray(emissiveColor); } - if (emissiveColor !== undefined && Array.isArray(emissiveColor) && emissiveColor.length === 3 && isNumberArray(emissiveColor)) + if (metallicFactor !== undefined) { - override.emissiveFactor = emissiveColor; + override.metallicFactor = metallicFactor.valueOf(); } - if (metallicFactor !== undefined && typeof metallicFactor === 'number') + if (roughnessFactor !== undefined) { - override.metallicFactor = metallicFactor; + override.roughnessFactor = roughnessFactor.valueOf(); } - if (roughnessFactor !== undefined && typeof roughnessFactor === 'number') + if (alphaMode !== undefined) { - override.roughnessFactor = roughnessFactor; + override.alphaMode = alphaMode.valueOf(); } - if (alphaMode !== undefined && typeof alphaMode === 'number') + if (alphaCutoff !== undefined) { - override.alphaMode = alphaMode; + override.alphaCutoff = alphaCutoff.valueOf(); } - if (alphaCutoff !== undefined && typeof alphaCutoff === 'number') - { - override.alphaCutoff = alphaCutoff; - } - - if (doubleSided !== undefined && typeof doubleSided === 'boolean') + if (doubleSided !== undefined) { override.doubleSided = doubleSided; } - function isLLGLTFTextureTransformOverride(objToCheck: unknown): objToCheck is LLGLTFTextureTransformOverride - { - const isArrayOfNumbers = (value: unknown): value is number[] => - { - return Array.isArray(value) && value.every(item => typeof item === 'number'); - }; - - // Validate the object structure and types - return ( - typeof objToCheck === 'object' && - objToCheck !== null && - 'offset' in objToCheck && isArrayOfNumbers((objToCheck as LLGLTFTextureTransformOverride).offset) && - 'scale' in objToCheck && isArrayOfNumbers((objToCheck as LLGLTFTextureTransformOverride).scale) && - 'rotation' in objToCheck && typeof (objToCheck as LLGLTFTextureTransformOverride).rotation === 'number' - ); - } - if (textureTransforms !== undefined && Array.isArray(textureTransforms)) { override.textureTransforms = []; for (const transform of textureTransforms) { - if (transform instanceof LLSDMap) - { - const tObj = { - offset: transform.get('o'), - scale: transform.get('s'), - rotation: transform.get('r') - } - if (isLLGLTFTextureTransformOverride(tObj)) - { - override.textureTransforms.push(tObj); - } + const o = transform.get('o') as LLSDReal[] | undefined; + const s = transform.get('s') as LLSDReal[] | undefined; + const r = transform.get('r') as LLSDReal | undefined; + const tObj = { + offset: o !== undefined ? LLSDArray.toNumberArray(o) : undefined, + scale: s !== undefined ? LLSDArray.toNumberArray(s) : undefined, + rotation: r !== undefined ? r.valueOf() : undefined } + override.textureTransforms.push(tObj); } } } - const obj = this.objects.get(localID); + const obj = this.objects.get(localID.valueOf()); const textureEntry = obj?.TextureEntry; if (textureEntry) { @@ -267,7 +251,7 @@ export class ObjectStoreLite implements IObjectStore } else { - this.cachedMaterialOverrides.set(localID, overrides); + this.cachedMaterialOverrides.set(localID.valueOf(), overrides); } } } @@ -307,7 +291,7 @@ export class ObjectStoreLite implements IObjectStore case Message.ObjectUpdateCompressed: { const objectUpdateCompressed = packet.message as ObjectUpdateCompressedMessage; - await this.objectUpdateCompressed(objectUpdateCompressed); + this.objectUpdateCompressed(objectUpdateCompressed); break; } case Message.ImprovedTerseObjectUpdate: @@ -322,6 +306,8 @@ export class ObjectStoreLite implements IObjectStore this.killObject(killObj); break; } + default: + break; } }); @@ -338,60 +324,421 @@ export class ObjectStoreLite implements IObjectStore } }); - this.selectedChecker = setInterval(() => + if (!(this.options & BotOptionFlags.LiteObjectStore)) { - if (this.circuit === undefined) + this.selectedChecker = setInterval(() => { + if (this.circuit === undefined) + { + return; + } + try + { + let selectObjects = []; + for (const key of this.selectedPrimsWithoutUpdate.keys()) + { + selectObjects.push(key); + } + + function shuffle(a: string[]): string[] + { + let j = 0, x = '', i = 0; + 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 as unknown[] as string[]); + 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) + } + } + + public setPersist(persist: boolean): void + { + this.persist = persist; + if (!this.persist) + { + for (const d of this.deadObjects) + { + this.deleteObject(d); + } + this.deadObjects = []; + } + } + + public deleteObject(objectID: number): void + { + const obj = this.objects.get(objectID); + if (obj) + { + const objectUUID = obj.FullID; + obj.deleted = true; + + if (this.persist) + { + this.deadObjects.push(objectID); return; } + + if (obj.IsAttachment && obj.ParentID !== undefined) + { + const parent = this.objects.get(obj.ParentID); + if (parent !== undefined && parent.PCode === PCode.Avatar) + { + const agent = this.agent.currentRegion.agents.get(parent.FullID.toString()); + if (agent !== undefined) + { + agent.removeAttachment(obj); + } + } + } + + const foundAgent = this.agent.currentRegion.agents.get(objectUUID.toString()); + if (foundAgent !== undefined) + { + foundAgent.isVisible = false; + } + + // First, kill all children (not the people kind) + const objsByParent = this.objectsByParent.get(objectID); + if (objsByParent) + { + for (const childObjID of objsByParent) + { + this.deleteObject(childObjID); + } + } + this.objectsByParent.delete(objectID); + + // Now delete this object + const uuid = obj.FullID.toString(); + + this.objectsByUUID.delete(uuid); + + if (obj.ParentID !== undefined) + { + const parentID = obj.ParentID; + const objsByParentParent = this.objectsByParent.get(parentID); + if (objsByParentParent) + { + const ind = objsByParentParent.indexOf(objectID); + if (ind !== -1) + { + objsByParentParent.splice(ind, 1); + } + } + } + if (this.rtree && obj.rtreeEntry !== undefined) + { + this.rtree.remove(obj.rtreeEntry); + } + this.objects.delete(objectID); + this.cachedMaterialOverrides.delete(objectID); + } + } + + public getObjectsByParent(parentID: number): GameObject[] + { + const list = this.objectsByParent.get(parentID); + if (list === undefined) + { + return []; + } + const result: GameObject[] = []; + for (const localID of list) + { + const obj = this.objects.get(localID); + if (obj) + { + result.push(obj); + } + } + return result; + } + + public parseNameValues(str: string): Map + { + const nv = new Map(); + 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.set(kv[0], namevalue); + } + } + } + return nv; + } + + public shutdown(): void + { + if (this.selectedChecker !== undefined) + { + clearInterval(this.selectedChecker); + delete this.selectedChecker; + } + this.physicsSubscription.unsubscribe(); + this.objects.clear(); + if (this.rtree) + { + this.rtree.clear(); + } + this.objectsByUUID.clear(); + this.objectsByParent.clear() + delete this.circuit; + } + + public 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); + } + } + obj.childrenPopulated = true; + } + } + + public getAllObjects(options: GetObjectsOptions): GameObject[] + { + const results = []; + const found: Record = {}; + for (const localID of this.objects.keys()) + { + const go = this.objects.get(localID); + if (!go) + { + continue; + } + if (!options.includeAvatars && go.PCode === PCode.Avatar) + { + continue; + } + if (!options.includeAttachments && go.IsAttachment) + { + continue; + } try { - let selectObjects = []; - for (const key of Object.keys(this.selectedPrimsWithoutUpdate)) + const parent = this.findParent(go); + if (parent.ParentID === 0) { - selectObjects.push(key); - } + const uuid = parent.FullID.toString(); - function shuffle(a: string[]): string[] - { - let j, x, i; - for (i = a.length - 1; i > 0; i--) + if (found[uuid] === undefined) { - j = Math.floor(Math.random() * (i + 1)); - x = a[i]; - a[i] = a[j]; - a[j] = x; + found[uuid] = parent; + results.push(parent); } - return a; } - - selectObjects = shuffle(selectObjects); - if (selectObjects.length > 10) + if (go.ParentID) { - 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) + let objects = this.objectsByParent.get(go.ParentID) + if (!objects?.includes(localID)) { - selectObject.ObjectData.push({ - ObjectLocalID: parseInt(id, 10) - }); + if (objects === undefined) + { + objects = []; + } + objects.push(localID); + this.objectsByParent.set(go.ParentID, objects); } - this.circuit.sendMessage(selectObject, PacketFlags.Reliable); } } - catch (e: unknown) + catch (error) { - Logger.Error(e); + console.log('Failed to find parent for ' + go.FullID.toString()); + console.error(error); + // Unable to find parent, full object probably not fully loaded yet } - }, 1000) + } + + // Now populate children of each found object + for (const obj of results) + { + this.populateChildren(obj); + } + + return results; + } + + + public getNumberOfObjects(): number + { + return this.objects.size; + } + + public getObjectsInArea(minX: number, maxX: number, minY: number, maxY: number, minZ: number, maxZ: number): GameObject[] + { + 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: Record = {}; + const objs: GameObject[] = []; + for (const obj of result) + { + const o = obj as ITreeBoundingBox; + const go = o.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; + } + + public getObjectByUUID(fullID: UUID | string): GameObject + { + if (fullID instanceof UUID) + { + fullID = fullID.toString(); + } + const localID = this.objectsByUUID.get(fullID); + const go = this.objects.get(localID ?? 0); + if (localID === undefined || go === undefined) + { + throw new Error('No object found with that UUID'); + } + return go; + } + + public getObjectByLocalID(localID: number): GameObject + { + const go = this.objects.get(localID); + if (!go) + { + throw new Error('No object found with that UUID'); + } + return go; + } + + public 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).multiplyQuaternion(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); + } + + public pendingResolve(id: number): void + { + this.pendingResolves.add(id); } private applyObjectProperties(o: GameObject, obj: any): void @@ -418,6 +765,7 @@ export class ObjectStoreLite implements IObjectStore o.fromTaskID = obj.FromTaskID; o.groupID = obj.GroupID; o.lastOwnerID = obj.LastOwnerID; + o.OwnerID = obj.OwnerID; o.name = Utils.BufferToStringSimple(obj.Name); o.description = Utils.BufferToStringSimple(obj.Description); o.touchName = Utils.BufferToStringSimple(obj.TouchName); @@ -434,8 +782,7 @@ export class ObjectStoreLite implements IObjectStore } if (o.Flags !== undefined) { - // tslint:disable-next-line:no-bitwise - // noinspection JSBitwiseOperatorUsage + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison if ((o.Flags & PrimFlags.CreateSelected) === PrimFlags.CreateSelected) { const evt = new SelectedObjectEvent(); @@ -508,7 +855,7 @@ export class ObjectStoreLite implements IObjectStore }); this.requestedObjects.delete(localID); } - catch (error) + catch (_error: unknown) { this.requestedObjects.delete(localID); if (attempt < 5) @@ -579,92 +926,98 @@ export class ObjectStoreLite implements IObjectStore } obj = this.objects.get(localID); - obj!.deleted = false; - obj!.ID = objData.ID; - obj!.FullID = objData.FullID; - obj!.ParentID = objData.ParentID; - obj!.OwnerID = objData.OwnerID; - obj!.PCode = objData.PCode; - - obj!.NameValue = this.parseNameValues(Utils.BufferToStringSimple(objData.NameValue)); - - obj!.IsAttachment = obj!.NameValue.AttachItemID !== undefined; - if (obj!.IsAttachment && obj!.State !== undefined) + if (obj) { - obj!.attachmentPoint = this.decodeAttachPoint(obj!.State); - } + obj.deleted = false; + obj.ID = objData.ID; + obj.FullID = objData.FullID; + obj.ParentID = objData.ParentID; + obj.OwnerID = objData.OwnerID; + obj.PCode = objData.PCode; - if (objData.PCode === PCode.Avatar && obj!.FullID.toString() === this.agent.agentID.toString()) - { - this.agent.localID = localID; + obj.NameValue = this.parseNameValues(Utils.BufferToStringSimple(objData.NameValue)); - if (this.options & BotOptionFlags.StoreMyAttachmentsOnly) + obj.IsAttachment = obj.NameValue.get('AttachItemID') !== undefined; + if (obj.IsAttachment && obj.State !== undefined) { - for (const objParentID of Object.keys(this.objectsByParent)) + obj.attachmentPoint = this.decodeAttachPoint(obj.State); + } + + if (objData.PCode === PCode.Avatar && obj.FullID.toString() === this.agent.agentID.toString()) + { + this.agent.localID = localID; + + if (this.options & BotOptionFlags.StoreMyAttachmentsOnly) { - const parent = parseInt(objParentID, 10); - if (parent !== this.agent.localID) + for (const objParentID of this.objectsByParent.keys()) { - let foundAvatars = false; - const p = this.objectsByParent.get(parent); - if (p !== undefined) + const parent = objParentID; + if (parent !== this.agent.localID) { - for (const objID of p) + let foundAvatars = false; + const p = this.objectsByParent.get(parent); + if (p !== undefined) { - const childObj = this.objects.get(objID); - if (childObj) + for (const objID of p) { - if (childObj.PCode === PCode.Avatar) + const childObj = this.objects.get(objID); + if (childObj) { - foundAvatars = true; + if (childObj.PCode === PCode.Avatar) + { + foundAvatars = true; + } } } } - } - const parentObj = this.objects.get(parent); - if (parentObj) - { - if (parentObj.PCode === PCode.Avatar) + const parentObj = this.objects.get(parent); + if (parentObj) { - foundAvatars = true; + if (parentObj.PCode === PCode.Avatar) + { + foundAvatars = true; + } + } + if (!foundAvatars) + { + this.deleteObject(parent); } - } - if (!foundAvatars) - { - this.deleteObject(parent); } } } } - } - this.objectsByUUID.set(objData.FullID.toString(), localID); - let objByParent = this.objectsByParent.get(parentID); - if (!objByParent) - { - objByParent = []; - this.objectsByParent.set(parentID, objByParent); - } - if (addToParentList) - { - objByParent.push(localID); - } - - if (objData.PCode !== PCode.Avatar && this.options & BotOptionFlags.StoreMyAttachmentsOnly) - { - if (this.agent.localID !== 0 && obj!.ParentID !== this.agent.localID) + this.objectsByUUID.set(objData.FullID.toString(), localID); + let objByParent = this.objectsByParent.get(parentID); + if (!objByParent) { - // Drop object - this.deleteObject(localID); - return; + objByParent = []; + this.objectsByParent.set(parentID, objByParent); + } + if (addToParentList) + { + objByParent.push(localID); } - } - this.notifyObjectUpdate(newObject, obj!); + 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; + } + } - if (objData.ParentID !== undefined && objData.ParentID !== 0 && !this.objects.get(objData.ParentID) && !obj?.IsAttachment) - { - this.requestMissingObject(objData.ParentID); + this.notifyObjectUpdate(newObject, obj); + + if (objData.ParentID !== undefined && objData.ParentID !== 0 && !this.objects.get(objData.ParentID) && !obj?.IsAttachment) + { + if (this.fullStore) + { + void this.requestMissingObject(objData.ParentID); + } + } } } } @@ -675,9 +1028,10 @@ export class ObjectStoreLite implements IObjectStore { if (obj.PCode === PCode.Avatar) { - if (this.agent.currentRegion.agents[obj.FullID.toString()] !== undefined) + const agent = this.agent.currentRegion.agents.get(obj.FullID.toString()); + if (agent !== undefined) { - this.agent.currentRegion.agents[obj.FullID.toString()].processObjectUpdate(obj); + agent.processObjectUpdate(obj); } else { @@ -699,22 +1053,24 @@ export class ObjectStoreLite implements IObjectStore const avatarID = obj.FullID.toString(); if (newObject) { - if (this.agent.currentRegion.agents[avatarID] === undefined) + const agent = this.agent.currentRegion.agents.get(avatarID); + if (agent === undefined) { const av = Avatar.fromGameObject(obj); - this.agent.currentRegion.agents[avatarID] = av; + this.agent.currentRegion.agents.set(avatarID, av); this.clientEvents.onAvatarEnteredRegion.next(av) } else { - this.agent.currentRegion.agents[avatarID].processObjectUpdate(obj); + agent.processObjectUpdate(obj); } } else { - if (this.agent.currentRegion.agents[avatarID] !== undefined) + const agent = this.agent.currentRegion.agents.get(avatarID); + if (agent !== undefined) { - this.agent.currentRegion.agents[avatarID].processObjectUpdate(obj); + agent.processObjectUpdate(obj); } else { @@ -731,12 +1087,13 @@ export class ObjectStoreLite implements IObjectStore { if (parentObj !== undefined && parentObj.PCode === PCode.Avatar) { - const avatar = this.agent.currentRegion.agents[parentObj.FullID.toString()]; + const avatar = this.agent.currentRegion.agents.get(parentObj.FullID.toString()); let invItemID = UUID.zero(); - if (obj.NameValue['AttachItemID']) + const attach = obj.NameValue.get('AttachItemID'); + if (attach) { - invItemID = new UUID(obj.NameValue['AttachItemID'].value); + invItemID = new UUID(attach.value); } this.agent.currentRegion.clientCommands.region.resolveObject(obj, {}).then(() => @@ -769,10 +1126,10 @@ export class ObjectStoreLite implements IObjectStore newObj.localID = obj.ID; newObj.objectID = obj.FullID; newObj.object = obj; + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison newObj.createSelected = obj.Flags !== undefined && (obj.Flags & PrimFlags.CreateSelected) === PrimFlags.CreateSelected; obj.createdSelected = newObj.createSelected; - // tslint:disable-next-line:no-bitwise - // noinspection JSBitwiseOperatorUsage + // noinspection JSBitwiseOperatorUsage if (obj.Flags !== undefined && obj.Flags & PrimFlags.CreateSelected && !this.pendingObjectProperties.get(obj.FullID.toString())) { this.selectedPrimsWithoutUpdate.set(obj.ID, true); @@ -796,11 +1153,6 @@ export class ObjectStoreLite implements IObjectStore } } - public pendingResolve(id: number): void - { - this.pendingResolves.add(id); - } - protected objectUpdateCached(objectUpdateCached: ObjectUpdateCachedMessage): void { if (this.circuit === undefined) @@ -834,7 +1186,7 @@ export class ObjectStoreLite implements IObjectStore pos += 16; const localID = buf.readUInt32LE(pos); pos += 4; - const pcode = buf.readUInt8(pos++); + const pcode: PCode = buf.readUInt8(pos++); const newObj = false; let o = this.objects.get(localID); if (!o) @@ -908,10 +1260,13 @@ export class ObjectStoreLite implements IObjectStore } if (o.ParentID !== undefined && o.ParentID !== 0 && !this.objects.has(o.ParentID) && !o.IsAttachment) { - this.requestMissingObject(o.ParentID).catch((e) => + if (this.fullStore) { - console.error(e); - }); + this.requestMissingObject(o.ParentID).catch((e: unknown) => + { + console.error(e); + }); + } } if (compressedflags & CompressedFlags.Tree) { @@ -944,7 +1299,7 @@ export class ObjectStoreLite implements IObjectStore // Extra params const extraParamsLength = ExtraParams.getLengthOfParams(buf, pos); - o.extraParams = ExtraParams.from(buf.slice(pos, pos + extraParamsLength)); + o.extraParams = ExtraParams.from(buf.subarray(pos, pos + extraParamsLength)); pos = pos + extraParamsLength; if (compressedflags & CompressedFlags.HasSound) @@ -985,7 +1340,7 @@ export class ObjectStoreLite implements IObjectStore protected objectUpdateTerse(_objectUpdateTerse: ImprovedTerseObjectUpdateMessage): void { - + // Not implemented } protected killObject(killObj: KillObjectMessage): void @@ -1000,160 +1355,10 @@ export class ObjectStoreLite implements IObjectStore } } - 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 - { - const obj = this.objects.get(objectID); - if (obj) - { - const objectUUID = obj.FullID; - obj.deleted = true; - - if (this.persist) - { - this.deadObjects.push(objectID); - return; - } - - if (obj.IsAttachment && obj.ParentID !== undefined) - { - const parent = this.objects.get(obj.ParentID); - if (parent !== undefined && parent.PCode === PCode.Avatar) - { - this.agent.currentRegion.agents[parent.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) - const objsByParent = this.objectsByParent.get(objectID); - if (objsByParent) - { - for (const childObjID of objsByParent) - { - this.deleteObject(childObjID); - } - } - this.objectsByParent.delete(objectID); - - // Now delete this object - const uuid = obj.FullID.toString(); - - this.objectsByUUID.delete(uuid); - - if (obj.ParentID !== undefined) - { - const parentID = obj.ParentID; - const objsByParentParent = this.objectsByParent.get(parentID); - if (objsByParentParent) - { - const ind = objsByParentParent.indexOf(objectID); - if (ind !== -1) - { - objsByParentParent.splice(ind, 1); - } - } - } - if (this.rtree && obj.rtreeEntry !== undefined) - { - this.rtree.remove(obj.rtreeEntry); - } - this.objects.delete(objectID); - this.cachedMaterialOverrides.delete(objectID); - } - } - getObjectsByParent(parentID: number): GameObject[] - { - const list = this.objectsByParent.get(parentID); - if (list === undefined) - { - return []; - } - const result: GameObject[] = []; - for (const localID of list) - { - const obj = this.objects.get(localID); - if (obj) - { - result.push(obj); - } - } - 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.clear(); - if (this.rtree) - { - this.rtree.clear(); - } - this.objectsByUUID.clear(); - this.objectsByParent.clear() - delete this.circuit; - } - protected findParent(go: GameObject): GameObject { const parentObj = this.objects.get(go.ParentID ?? 0); - if (go.ParentID !== undefined && go.ParentID !== 0 && parentObj) + if (go.ParentID !== undefined && go.ParentID !== 0 && parentObj) { return this.findParent(parentObj); } @@ -1161,204 +1366,15 @@ export class ObjectStoreLite implements IObjectStore { if (go.ParentID !== undefined && go.ParentID !== 0 && !parentObj && !go.IsAttachment) { - this.requestMissingObject(go.ParentID).catch((e: unknown) => + if (this.fullStore) { - Logger.Error(e); - }); + 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); - } - } - obj.childrenPopulated = true; - } - } - - async getAllObjects(): Promise - { - const results = []; - const found: { [key: string]: GameObject } = {}; - for (const localID of this.objects.keys()) - { - const go = this.objects.get(localID); - if (go && 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); - } - } - if (go.ParentID) - { - let objects = this.objectsByParent.get(go.ParentID) - if (!objects?.includes(localID)) - { - if (objects === undefined) - { - objects = []; - } - objects.push(localID); - this.objectsByParent.set(go.ParentID, objects); - } - } - } - 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(); - } - const localID = this.objectsByUUID.get(fullID); - const go = this.objects.get(localID ?? 0); - if (localID === undefined || go === undefined) - { - throw new Error('No object found with that UUID'); - } - return go; - } - - getObjectByLocalID(localID: number): GameObject - { - const go = this.objects.get(localID); - if (!go) - { - throw new Error('No object found with that UUID'); - } - return go; - } - - 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 2d3fb67..c7745a6 100644 --- a/lib/classes/Packet.ts +++ b/lib/classes/Packet.ts @@ -1,19 +1,19 @@ -import { MessageBase } from './MessageBase'; +import type { MessageBase } from './MessageBase'; import { Zerocoder } from './Zerocoder'; import * as MessageClass from './MessageClasses'; import { nameFromID } from './MessageClasses'; import { MessageFlags } from '../enums/MessageFlags'; import { PacketFlags } from '../enums/PacketFlags'; -import { DecodeFlags } from '../enums/DecodeFlags'; +import type { DecodeFlags } from '../enums/DecodeFlags'; export class Packet { - packetFlags: PacketFlags = 0 as PacketFlags; - sequenceNumber = 0; - extraHeader: Buffer = Buffer.allocUnsafe(0); - message: MessageBase; + public packetFlags: PacketFlags = 0 as PacketFlags; + public sequenceNumber = 0; + public extraHeader: Buffer = Buffer.allocUnsafe(0); + public message: MessageBase; - getSize(): number + public getSize(): number { let idSize = 4; if (this.message.messageFlags & MessageFlags.FrequencyHigh) @@ -27,7 +27,7 @@ export class Packet return 1 + 4 + 1 + this.extraHeader.length + idSize + this.message.getSize(); } - writeToBuffer(buf: Buffer, pos: number, options?: DecodeFlags): Buffer + public writeToBuffer(buf: Buffer, pos: number, options?: DecodeFlags): Buffer { if (options === undefined) { @@ -81,7 +81,7 @@ export class Packet return buf; } - readFromBuffer(buf: Buffer, pos: number, ackReceived: (sequenceID: number) => void, sendAck: (sequenceID: number) => void): number + public readFromBuffer(buf: Buffer, pos: number, ackReceived: (sequenceID: number) => void, sendAck: (sequenceID: number) => void): number { this.packetFlags = buf.readUInt8(pos++); this.sequenceNumber = buf.readUInt32BE(pos); @@ -93,7 +93,7 @@ export class Packet const extraBytes = buf.readUInt8(pos++); if (extraBytes > 0) { - this.extraHeader = buf.slice(pos, pos + extraBytes); + this.extraHeader = buf.subarray(pos, pos + extraBytes); pos += extraBytes; } else @@ -140,7 +140,7 @@ export class Packet pos++; } - this.message = new (MessageClass)[nameFromID(messageID)]() as MessageBase; + this.message = new (MessageClass as any)[nameFromID(messageID)]() as MessageBase; pos += this.message.readFromBuffer(buf, pos); diff --git a/lib/classes/ParticleSystem.ts b/lib/classes/ParticleSystem.ts index 64e4be7..9ec061c 100644 --- a/lib/classes/ParticleSystem.ts +++ b/lib/classes/ParticleSystem.ts @@ -8,36 +8,36 @@ import { SourcePattern } from '../enums/SourcePattern'; export class ParticleSystem { - startGlow = 0.0; - endGlow = 0.0; - blendFuncSource: BlendFunc = BlendFunc.SourceAlpha; - blendFuncDest: BlendFunc = BlendFunc.OneMinusSourceAlpha; - crc = 0; - pattern: SourcePattern = SourcePattern.None; - maxAge = 0.0; - startAge = 0.0; - innerAngle = 0.0; - outerAngle = 0.0; - burstRate = 0.0; - burstRadius = 0.0; - burstSpeedMin = 0.0; - burstSpeedMax = 0.0; - burstPartCount = 0; - angularVelocity = Vector3.getZero(); - acceleration = Vector3.getZero(); - texture = UUID.zero(); - target = UUID.zero(); - dataFlags: ParticleDataFlags = ParticleDataFlags.None; - partMaxAge = 0.0; - startColor = Color4.black; - endColor = Color4.black; - startScaleX = 0.0; - startScaleY = 0.0; - endScaleX = 0.0; - endScaleY = 0.0; - flags = 0; + public startGlow = 0.0; + public endGlow = 0.0; + public blendFuncSource: BlendFunc = BlendFunc.SourceAlpha; + public blendFuncDest: BlendFunc = BlendFunc.OneMinusSourceAlpha; + public crc = 0; + public pattern: SourcePattern = SourcePattern.None; + public maxAge = 0.0; + public startAge = 0.0; + public innerAngle = 0.0; + public outerAngle = 0.0; + public burstRate = 0.0; + public burstRadius = 0.0; + public burstSpeedMin = 0.0; + public burstSpeedMax = 0.0; + public burstPartCount = 0; + public angularVelocity = Vector3.getZero(); + public acceleration = Vector3.getZero(); + public texture = UUID.zero(); + public target = UUID.zero(); + public dataFlags: ParticleDataFlags = ParticleDataFlags.None; + public partMaxAge = 0.0; + public startColor = Color4.black; + public endColor = Color4.black; + public startScaleX = 0.0; + public startScaleY = 0.0; + public endScaleX = 0.0; + public endScaleY = 0.0; + public flags = 0; - static from(buf: Buffer): ParticleSystem + public static from(buf: Buffer): ParticleSystem { const ps = new ParticleSystem(); let pos = 0; @@ -61,14 +61,14 @@ export class ParticleSystem pos += 4; pos = this.unpackLegacyData(ps, buf, pos); - if ((ps.dataFlags & ParticleDataFlags.DataGlow) === ParticleDataFlags.DataGlow) + if ((ps.dataFlags & ParticleDataFlags.DataGlow) as ParticleDataFlags === ParticleDataFlags.DataGlow) { let glow = buf.readUInt8(pos++); ps.startGlow = glow / 255.0; glow = buf.readUInt8(pos++); ps.endGlow = glow / 255.0; } - if ((ps.dataFlags & ParticleDataFlags.DataBlend) === ParticleDataFlags.DataBlend) + if ((ps.dataFlags & ParticleDataFlags.DataBlend) as ParticleDataFlags === ParticleDataFlags.DataBlend) { ps.blendFuncSource = buf.readUInt8(pos++); ps.blendFuncDest = buf.readUInt8(pos); @@ -77,10 +77,10 @@ export class ParticleSystem return ps; } - static packFixed(buf: Buffer, pos: number, data: number, signed: boolean, intBits: number, fracBits: number): number + public static packFixed(buf: Buffer, pos: number, data: number, signed: boolean, intBits: number, fracBits: number): number { let totalBits = intBits + fracBits; - let min; + let min = 0; if (signed) { @@ -119,7 +119,7 @@ export class ParticleSystem throw new Error('Total bits greater than 32'); } - static unpackFixed(buf: Buffer, pos: number, signed: boolean, intBits: number, fracBits: number): number + public static unpackFixed(buf: Buffer, pos: number, signed: boolean, intBits: number, fracBits: number): number { let totalBits = intBits + fracBits; let fixedVal = 0.0; @@ -155,7 +155,7 @@ export class ParticleSystem return fixedVal; } - static unpackSystem(ps: ParticleSystem, buf: Buffer, pos: number): number + public static unpackSystem(ps: ParticleSystem, buf: Buffer, pos: number): number { const startPos = pos; ps.crc = buf.readUInt32LE(pos); @@ -201,7 +201,7 @@ export class ParticleSystem return pos; } - static unpackLegacyData(ps: ParticleSystem, buf: Buffer, pos: number): number + public static unpackLegacyData(ps: ParticleSystem, buf: Buffer, pos: number): number { ps.dataFlags = buf.readUInt32LE(pos); pos += 4; @@ -226,7 +226,7 @@ export class ParticleSystem return pos; } - toBuffer(): Buffer + public toBuffer(): Buffer { if (this.crc === 0) { @@ -271,25 +271,25 @@ export class ParticleSystem pos = pos + ParticleSystem.packFixed(legacyBlock, pos, this.endScaleX, false, 3, 5); pos = pos + ParticleSystem.packFixed(legacyBlock, pos, this.endScaleY, false, 3, 5); - if ((this.dataFlags & ParticleDataFlags.DataGlow) === ParticleDataFlags.DataGlow || (this.dataFlags & ParticleDataFlags.DataBlend) === ParticleDataFlags.DataBlend) + if ((this.dataFlags & ParticleDataFlags.DataGlow) as ParticleDataFlags === ParticleDataFlags.DataGlow || (this.dataFlags & ParticleDataFlags.DataBlend) as ParticleDataFlags === ParticleDataFlags.DataBlend) { let extraBytes = 0; - if ((this.dataFlags & ParticleDataFlags.DataGlow) === ParticleDataFlags.DataGlow) + if ((this.dataFlags & ParticleDataFlags.DataGlow) as ParticleDataFlags === ParticleDataFlags.DataGlow) { extraBytes += 2; } - if ((this.dataFlags & ParticleDataFlags.DataBlend) === ParticleDataFlags.DataBlend) + if ((this.dataFlags & ParticleDataFlags.DataBlend) as ParticleDataFlags === ParticleDataFlags.DataBlend) { extraBytes += 2; } const extraBuf = Buffer.allocUnsafe(extraBytes); pos = 0; - if ((this.dataFlags & ParticleDataFlags.DataGlow) === ParticleDataFlags.DataGlow) + if ((this.dataFlags & ParticleDataFlags.DataGlow) as ParticleDataFlags === ParticleDataFlags.DataGlow) { extraBuf.writeUInt8(this.startGlow * 255, pos++); extraBuf.writeUInt8(this.endGlow * 255, pos++); } - if ((this.dataFlags & ParticleDataFlags.DataBlend) === ParticleDataFlags.DataBlend) + if ((this.dataFlags & ParticleDataFlags.DataBlend) as ParticleDataFlags === ParticleDataFlags.DataBlend) { extraBuf.writeUInt8(this.blendFuncSource, pos++); extraBuf.writeUInt8(this.blendFuncDest, pos++); @@ -311,7 +311,7 @@ export class ParticleSystem } } - toBase64(): string + public toBase64(): string { return this.toBuffer().toString('base64'); } diff --git a/lib/classes/PrimFacesHelper.ts b/lib/classes/PrimFacesHelper.ts new file mode 100644 index 0000000..f99ae14 --- /dev/null +++ b/lib/classes/PrimFacesHelper.ts @@ -0,0 +1,100 @@ +import type { GameObject } from './public/GameObject'; +import type { Subscription } from 'rxjs'; +import { Subject } from 'rxjs'; +import { UUID } from './UUID'; +import type { Bot } from '../Bot'; +import type { ChatEvent } from '../events/ChatEvent'; +import { ChatType } from '../enums/ChatType'; +import { Logger } from './Logger'; + +export class PrimFacesHelper +{ + public readerID: string; + + private chatSubs?: Subscription; + private readonly onGotFaces = new Subject(); + private readonly finished = false; + private sides = 0; + + public constructor(private readonly bot: Bot, private readonly container: GameObject) + { + this.readerID = UUID.random().toString(); + } + + public async getFaces(): Promise + { + const scriptName = UUID.random().toString(); + const script = await this.container.rezScript(scriptName, ''); + this.chatSubs = this.bot.clientEvents.onNearbyChat.subscribe((value: ChatEvent) => + { + if (value.chatType === ChatType.OwnerSay) + { + const msg = value.message.split(this.readerID + ' '); + if (msg.length > 1) + { + this.sides = parseInt(msg[1], 10); + if (this.chatSubs !== undefined) + { + this.chatSubs.unsubscribe(); + delete this.chatSubs; + } + this.onGotFaces.next(); + this.onGotFaces.complete(); + } + } + + }); + script.updateScript(Buffer.from(`default{state_entry(){llOwnerSay("${this.readerID} " + (string)llGetNumberOfSides());llRemoveInventory(llGetScriptName());}}`, 'utf-8')).then(() => { /* ignore */ }).catch((error: unknown) => { Logger.Error(error) }); + return this.waitForSides(); + } + + private async waitForSides(): Promise + { + return new Promise((resolve, reject) => + { + let subscription: Subscription | null = null; + + if (this.finished) + { + if (this.chatSubs !== undefined) + { + this.chatSubs.unsubscribe(); + delete this.chatSubs; + } + resolve(this.sides); + return; + } + + const timeout = setTimeout(() => + { + if (subscription !== null) + { + subscription.unsubscribe(); + subscription = null; + } + if (this.chatSubs !== undefined) + { + this.chatSubs.unsubscribe(); + delete this.chatSubs; + } + reject(new Error('Timed out waiting for number of sides')); + }, 60000); + + subscription = this.onGotFaces.subscribe(() => + { + clearTimeout(timeout); + if (subscription !== null) + { + subscription.unsubscribe(); + subscription = null; + } + if (this.chatSubs !== undefined) + { + this.chatSubs.unsubscribe(); + delete this.chatSubs; + } + resolve(this.sides); + }); + }); + } +} diff --git a/lib/classes/Quaternion.ts b/lib/classes/Quaternion.ts index 6c3312d..7257967 100644 --- a/lib/classes/Quaternion.ts +++ b/lib/classes/Quaternion.ts @@ -1,16 +1,47 @@ -import { TSMQuat } from '../tsm/quat'; -import { XMLNode } from 'xmlbuilder'; +import type { Vector3 } from "./Vector3"; +import type { XMLNode } from 'xmlbuilder'; -export class Quaternion extends TSMQuat +export class Quaternion { - static getIdentity(): Quaternion + public x = 0; + public y = 0; + public z = 0; + public w = 1.0; + + public constructor(buf?: Buffer | number[] | Quaternion | number, pos?: number, z?: number, w?: number) { - const q = new Quaternion(); - q.setIdentity(); - return q; + if (typeof buf === 'number' && typeof pos === 'number' && typeof z === 'number' && typeof w === 'number') + { + this.x = buf; + this.y = pos; + this.z = z; + this.w = w; + } + else if (buf instanceof Quaternion) + { + this.x = buf.x; + this.y = buf.y; + this.z = buf.z; + this.w = buf.w; + } + else + { + if (buf !== undefined && pos !== undefined && buf instanceof Buffer) + { + this.x = buf.readFloatLE(pos); + this.y = buf.readFloatLE(pos + 4); + this.z = buf.readFloatLE(pos + 8); + const xyzsum = 1.0 - this.x * this.x - this.y * this.y - this.z * this.z; + this.w = xyzsum > 0.0 ? Math.sqrt(xyzsum) : 0; + } + else if (buf !== undefined && Array.isArray(buf) && buf.length > 3) + { + [this.x, this.y, this.z, this.w] = buf; + } + } } - static getXML(doc: XMLNode, v?: Quaternion): void + public static getXML(doc: XMLNode, v?: Quaternion): void { if (v === undefined) { @@ -22,7 +53,7 @@ export class Quaternion extends TSMQuat doc.ele('W', v.w); } - static fromXMLJS(obj: any, param: string): Quaternion | false + public static fromXMLJS(obj: any, param: string): Quaternion | false { if (!obj[param]) { @@ -35,12 +66,12 @@ export class Quaternion extends TSMQuat } if (typeof value === 'object') { - if (value['X'] !== undefined && value['Y'] !== undefined && value['Z'] !== undefined && value['W'] !== undefined) + if (value.X !== undefined && value.Y !== undefined && value.Z !== undefined && value.W !== undefined) { - let x = value['X']; - let y = value['Y']; - let z = value['Z']; - let w = value['W']; + let x = value.X; + let y = value.Y; + let z = value.Z; + let w = value.W; if (Array.isArray(x) && x.length > 0) { x = x[0]; @@ -57,89 +88,71 @@ export class Quaternion extends TSMQuat { w = w[0]; } - return new Quaternion([x, y, z, w]); + return new Quaternion([Number(x), Number(y), Number(z), Number(w)]); } return false; } return false; } - constructor(buf?: Buffer | number[] | Quaternion | TSMQuat, pos?: number) + public static fromAxis(axis: Vector3, angle: number): Quaternion { - if (buf instanceof Quaternion) + const halfAngle = angle / 2; + const sinHalfAngle = Math.sin(halfAngle); + const cosHalfAngle = Math.cos(halfAngle); + + const normalizedAxis = axis.copy().normalize(); + + const axisLengthSquared = axis.x * axis.x + axis.y * axis.y + axis.z * axis.z; + + if (axisLengthSquared === 0) { - super(); - this.x = buf.x; - this.y = buf.y; - this.z = buf.z; - this.w = buf.w; - } - else if (buf instanceof TSMQuat) - { - super(); - this.x = buf.x; - this.y = buf.y; - this.z = buf.z; - this.w = buf.w; - } - else - { - if (buf !== undefined && pos !== undefined && buf instanceof Buffer) - { - const x = buf.readFloatLE(pos); - const y = buf.readFloatLE(pos + 4); - const z = buf.readFloatLE(pos + 8); - const xyzsum = 1.0 - x * x - y * y - z * z; - const w = (xyzsum > 0.0) ? Math.sqrt(xyzsum) : 0; - super([x, y, z, w]); - } - else if (buf !== undefined && Array.isArray(buf)) - { - super(buf); - } - else - { - super(); - } + return Quaternion.getIdentity(); } + + return new Quaternion([ + normalizedAxis.x * sinHalfAngle, + normalizedAxis.y * sinHalfAngle, + normalizedAxis.z * sinHalfAngle, + cosHalfAngle, + ]); } - writeToBuffer(buf: Buffer, pos: number): void + public static getIdentity(): Quaternion { - const q: TSMQuat = this.normalize(); + return new Quaternion(); + } + + public writeToBuffer(buf: Buffer, pos: number): void + { + const q = this.normalize(); buf.writeFloatLE(q.x, pos); buf.writeFloatLE(q.y, pos + 4); buf.writeFloatLE(q.z, pos + 8); } - toString(): string + public toString(): string { return '<' + this.x + ', ' + this.y + ', ' + this.z + ', ' + this.w + '>'; } - getBuffer(): Buffer + public getBuffer(): Buffer { const j = Buffer.allocUnsafe(12); this.writeToBuffer(j, 0); return j; } - compareApprox(rot: Quaternion): boolean + public angleBetween(b: Quaternion): number { - return this.angleBetween(rot) < 0.0001 || rot.equals(this, 0.0001); - } - - angleBetween(b: Quaternion): number - { - const a = this; - const aa = (a.x * a.x + a.y * a.y + a.z * a.z + a.w * a.w); - const bb = (b.x * b.x + b.y * b.y + b.z * b.z + b.w * b.w); + const aa = this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; + const bb = b.x * b.x + b.y * b.y + b.z * b.z + b.w * b.w; const aa_bb = aa * bb; if (aa_bb === 0) { return 0.0; } - const ab = (a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w); + const ab = this.x * b.x + this.y * b.y + this.z * b.z + this.w * b.w; const quotient = (ab * ab) / aa_bb; if (quotient >= 1.0) { @@ -147,4 +160,275 @@ export class Quaternion extends TSMQuat } return Math.acos(2 * quotient - 1); } + + public dot(q: Quaternion): number + { + return this.x * q.x + this.y * q.y + this.z * q.z + this.w * q.w; + } + + public sum(q: Quaternion): Quaternion + { + return new Quaternion([ + this.x + q.x, + this.y + q.y, + this.z + q.z, + this.w + q.w, + ]); + } + + public product(q: Quaternion): Quaternion + { + return new Quaternion([ + this.x * q.x, + this.y * q.y, + this.z * q.z, + this.w * q.w, + ]); + } + + public cross(q: Quaternion): Quaternion + { + // Cross product of vector parts; scalar part is zero + return new Quaternion([ + this.y * q.z - this.z * q.y, + this.z * q.x - this.x * q.z, + this.x * q.y - this.y * q.x, + 0, + ]); + } + + public shortMix(q: Quaternion, t: number): Quaternion + { + if (t <= 0.0) + { + return new Quaternion(this); + } + else if (t >= 1.0) + { + return new Quaternion(q); + } + + let cosHalfTheta = this.dot(q); + + if (cosHalfTheta < 0) + { + q = q.negate(); + cosHalfTheta = -cosHalfTheta; + } + + let k0 = 0; + let k1 = 0; + + if (cosHalfTheta > 0.9999) + { + // Quaternions are very close; use linear interpolation + k0 = 1 - t; + k1 = t; + } + else + { + const sinHalfTheta = Math.sqrt(1.0 - cosHalfTheta * cosHalfTheta); + const halfTheta = Math.atan2(sinHalfTheta, cosHalfTheta); + const oneOverSinHalfTheta = 1.0 / sinHalfTheta; + + k0 = Math.sin((1 - t) * halfTheta) * oneOverSinHalfTheta; + k1 = Math.sin(t * halfTheta) * oneOverSinHalfTheta; + } + + return new Quaternion([ + this.x * k0 + q.x * k1, + this.y * k0 + q.y * k1, + this.z * k0 + q.z * k1, + this.w * k0 + q.w * k1, + ]).normalize(); + } + + public mix(q: Quaternion, t: number): Quaternion + { + const cosHalfTheta = this.dot(q); + + if (Math.abs(cosHalfTheta) >= 1.0) + { + // Quaternions are very close; return this quaternion + return new Quaternion(this); + } + + const halfTheta = Math.acos(cosHalfTheta); + const sinHalfTheta = Math.sqrt(1.0 - cosHalfTheta * cosHalfTheta); + + if (Math.abs(sinHalfTheta) < 0.001) + { + // Quaternions are too close; use linear interpolation + return new Quaternion([ + this.x * (1 - t) + q.x * t, + this.y * (1 - t) + q.y * t, + this.z * (1 - t) + q.z * t, + this.w * (1 - t) + q.w * t, + ]).normalize(); + } + + const ratioA = Math.sin((1 - t) * halfTheta) / sinHalfTheta; + const ratioB = Math.sin(t * halfTheta) / sinHalfTheta; + + return new Quaternion([ + this.x * ratioA + q.x * ratioB, + this.y * ratioA + q.y * ratioB, + this.z * ratioA + q.z * ratioB, + this.w * ratioA + q.w * ratioB, + ]); + } + + public copy(): Quaternion + { + return new Quaternion(this); + } + + public roll(): number + { + // Rotation around the x-axis + return Math.atan2( + 2 * (this.w * this.x + this.y * this.z), + 1 - 2 * (this.x * this.x + this.y * this.y) + ); + } + + public pitch(): number + { + // Rotation around the y-axis + const sinp = 2 * (this.w * this.y - this.z * this.x); + if (Math.abs(sinp) >= 1) + { + return (Math.PI / 2) * Math.sign(sinp); // Use 90 degrees if out of range + } + else + { + return Math.asin(sinp); + } + } + + public yaw(): number + { + // Rotation around the z-axis + return Math.atan2( + 2 * (this.w * this.z + this.x * this.y), + 1 - 2 * (this.y * this.y + this.z * this.z) + ); + } + + public equals(q: Quaternion, epsilon = Number.EPSILON): boolean + { + return ( + Math.abs(this.x - q.x) < epsilon && + Math.abs(this.y - q.y) < epsilon && + Math.abs(this.z - q.z) < epsilon && + Math.abs(this.w - q.w) < epsilon + ); + } + + public inverse(): Quaternion + { + const lengthSq = this.lengthSquared(); + if (lengthSq === 0) + { + throw new Error('Cannot invert a quaternion with zero length'); + } + return this.conjugate().scale(1 / lengthSq); + } + + public conjugate(): Quaternion + { + return new Quaternion([-this.x, -this.y, -this.z, this.w]); + } + + public length(): number + { + return Math.sqrt(this.lengthSquared()); + } + + public lengthSquared(): number + { + return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; + } + + public normalize(): Quaternion + { + const len = this.length(); + if (len === 0) + { + return Quaternion.getIdentity(); + } + else + { + return new Quaternion([ + this.x / len, + this.y / len, + this.z / len, + this.w / len, + ]); + } + } + + public add(q: Quaternion): Quaternion + { + return new Quaternion([ + this.x + q.x, + this.y + q.y, + this.z + q.z, + this.w + q.w, + ]); + } + + public multiply(value: number | Quaternion): Quaternion + { + if (typeof value === 'number') + { + // Scalar multiplication + return new Quaternion([ + this.x * value, + this.y * value, + this.z * value, + this.w * value, + ]); + } + else + { + // Quaternion multiplication + const x1 = this.x, + y1 = this.y, + z1 = this.z, + w1 = this.w; + const x2 = value.x, + y2 = value.y, + z2 = value.z, + w2 = value.w; + return new Quaternion([ + w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2, + w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2, + w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2, + w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2, + ]); + } + } + + public negate(): Quaternion + { + return new Quaternion([-this.x, -this.y, -this.z, -this.w]); + } + + public scale(scalar: number): Quaternion + { + return new Quaternion([ + this.x * scalar, + this.y * scalar, + this.z * scalar, + this.w * scalar, + ]); + } + + public calculateW(): Quaternion + { + const t = 1.0 - (this.x * this.x + this.y * this.y + this.z * this.z); + const w = t < 0 ? 0 : Math.sqrt(t); + return new Quaternion([this.x, this.y, this.z, w]); + } } diff --git a/lib/classes/Region.ts b/lib/classes/Region.ts index ab00434..357ea7a 100644 --- a/lib/classes/Region.ts +++ b/lib/classes/Region.ts @@ -1,313 +1,170 @@ import { Circuit } from './Circuit'; -import { Agent } from './Agent'; +import type { Agent } from './Agent'; import { Caps } from './Caps'; import { Comms } from './Comms'; -import { ClientEvents } from './ClientEvents'; -import { IObjectStore } from './interfaces/IObjectStore'; +import type { ClientEvents } from './ClientEvents'; +import type { IObjectStore } from './interfaces/IObjectStore'; import { ObjectStoreFull } from './ObjectStoreFull'; import { ObjectStoreLite } from './ObjectStoreLite'; import { RequestRegionInfoMessage } from './messages/RequestRegionInfo'; -import { RegionInfoMessage } from './messages/RegionInfo'; +import type { RegionInfoMessage } from './messages/RegionInfo'; import { Message } from '../enums/Message'; import { Utils } from './Utils'; -import { RegionHandshakeMessage } from './messages/RegionHandshake'; +import type { RegionHandshakeMessage } from './messages/RegionHandshake'; import { MapNameRequestMessage } from './messages/MapNameRequest'; import { GridLayerType } from '../enums/GridLayerType'; -import { MapBlockReplyMessage } from './messages/MapBlockReply'; +import type { MapBlockReplyMessage } from './messages/MapBlockReply'; import { FilterResponse } from '../enums/FilterResponse'; -import * as Long from 'long'; -import { Packet } from './Packet'; -import { LayerDataMessage } from './messages/LayerData'; +import type * as Long from 'long'; +import type { Packet } from './Packet'; +import type { LayerDataMessage } from './messages/LayerData'; import { LayerType } from '../enums/LayerType'; -import { Subject, Subscription } from 'rxjs'; +import type { Subscription } from 'rxjs'; +import { Subject } from 'rxjs'; import { BitPack } from './BitPack'; import * as builder from 'xmlbuilder'; import { SimAccessFlags } from '../enums/SimAccessFlags'; import { ParcelDwellRequestMessage } from './messages/ParcelDwellRequest'; -import { ParcelDwellReplyMessage } from './messages/ParcelDwellReply'; +import type { ParcelDwellReplyMessage } from './messages/ParcelDwellReply'; import { Parcel } from './public/Parcel'; -import { RegionEnvironment } from './public/RegionEnvironment'; -import { Color4 } from './Color4'; -import { SkyPreset } from './public/interfaces/SkyPreset'; -import { Vector4 } from './Vector4'; -import { WaterPreset } from './public/interfaces/WaterPreset'; -import { ClientCommands } from './ClientCommands'; -import { SimulatorViewerTimeMessageMessage } from './messages/SimulatorViewerTimeMessage'; -import { ParcelOverlayMessage } from './messages/ParcelOverlay'; -import { ILandBlock } from './interfaces/ILandBlock'; +import type { ClientCommands } from './ClientCommands'; +import type { SimulatorViewerTimeMessageMessage } from './messages/SimulatorViewerTimeMessage'; +import type { ParcelOverlayMessage } from './messages/ParcelOverlay'; +import type { ILandBlock } from './interfaces/ILandBlock'; import { LandFlags } from '../enums/LandFlags'; import { ParcelPropertiesRequestMessage } from './messages/ParcelPropertiesRequest'; import { UUID } from './UUID'; import { RegionFlags } from '../enums/RegionFlags'; import { BotOptionFlags } from '../enums/BotOptionFlags'; -import { ParcelPropertiesEvent } from '../events/ParcelPropertiesEvent'; +import type { ParcelPropertiesEvent } from '../events/ParcelPropertiesEvent'; import { PacketFlags } from '../enums/PacketFlags'; import { Vector3 } from './Vector3'; -import { Vector2 } from './Vector2'; import { ObjectResolver } from './ObjectResolver'; -import { SimStatsMessage } from './messages/SimStats'; +import type { SimStatsMessage } from './messages/SimStats'; import { SimStatsEvent } from '../events/SimStatsEvent'; import { StatID } from '../enums/StatID'; -import { CoarseLocationUpdateMessage } from './messages/CoarseLocationUpdate'; +import type { CoarseLocationUpdateMessage } from './messages/CoarseLocationUpdate'; import { Avatar } from './public/Avatar'; -import { MoneyBalanceReplyMessage } from './messages/MoneyBalanceReply'; +import type { MoneyBalanceReplyMessage } from './messages/MoneyBalanceReply'; import { BalanceUpdatedEvent } from '../events/BalanceUpdatedEvent'; import { Logger } from './Logger'; import { EconomyDataRequestMessage } from './messages/EconomyDataRequest'; -import { EconomyDataMessage } from './messages/EconomyData'; +import type { EconomyDataMessage } from './messages/EconomyData'; +import { RegionEnvironment } from './public/RegionEnvironment'; +import { LLSD } from './llsd/LLSD'; export class Region { - static CopyMatrix16: number[] = []; - static CosineTable16: number[] = []; - static DequantizeTable16: number[] = []; - static setup = false; - static OO_SQRT_2 = 0.7071067811865475244008443621049; + public static CopyMatrix16: number[] = []; + public static CosineTable16: number[] = []; + public static DequantizeTable16: number[] = []; + public static setup = false; + public static OO_SQRT_2 = 0.707106781186547; - regionName: string; - regionOwner: UUID; - regionID: UUID; - regionSizeX = 256; - regionSizeY = 256; - regionHandle: Long; - xCoordinate: number; - yCoordinate: number; - estateID: number; - parentEstateID: number; - regionFlags: RegionFlags; - mapImage: UUID; + public regionName: string; + public regionOwner: UUID; + public regionID: UUID; + public regionSizeX = 256; + public regionSizeY = 256; + public regionHandle: Long; + public xCoordinate: number; + public yCoordinate: number; + public estateID: number; + public parentEstateID: number; + public regionFlags: RegionFlags; + public mapImage: UUID; - simAccess: number; - maxAgents: number; - billableFactor: number; - objectBonusFactor: number; - waterHeight: number; - terrainRaiseLimit: number; - terrainLowerLimit: number; - pricePerMeter: number; - redirectGridX: number; - redirectGridY: number; - useEstateSun: boolean; - sunHour: number; - productSKU: string; - productName: string; - maxAgents32: number; - hardMaxAgents: number; - hardMaxObjects: number; - cacheID: UUID; - cpuClassID: number; - cpuRatio: number; - coloName: string; + public simAccess: number; + public maxAgents: number; + public billableFactor: number; + public objectBonusFactor: number; + public waterHeight: number; + public terrainRaiseLimit: number; + public terrainLowerLimit: number; + public pricePerMeter: number; + public redirectGridX: number; + public redirectGridY: number; + public useEstateSun: boolean; + public sunHour: number; + public productSKU: string; + public productName: string; + public maxAgents32: number; + public hardMaxAgents: number; + public hardMaxObjects: number; + public cacheID: UUID; + public cpuClassID: number; + public cpuRatio: number; + public coloName: string; - terrainBase0: UUID; - terrainBase1: UUID; - terrainBase2: UUID; - terrainBase3: UUID; - terrainDetail0: UUID; - terrainDetail1: UUID; - terrainDetail2: UUID; - terrainDetail3: UUID; - terrainStartHeight00: number; - terrainStartHeight01: number; - terrainStartHeight10: number; - terrainStartHeight11: number; - terrainHeightRange00: number; - terrainHeightRange01: number; - terrainHeightRange10: number; - terrainHeightRange11: number; + public terrainBase0: UUID; + public terrainBase1: UUID; + public terrainBase2: UUID; + public terrainBase3: UUID; + public terrainDetail0: UUID; + public terrainDetail1: UUID; + public terrainDetail2: UUID; + public terrainDetail3: UUID; + public terrainStartHeight00: number; + public terrainStartHeight01: number; + public terrainStartHeight10: number; + public terrainStartHeight11: number; + public terrainHeightRange00: number; + public terrainHeightRange01: number; + public terrainHeightRange10: number; + public terrainHeightRange11: number; - handshakeComplete = false; - handshakeCompleteEvent: Subject = new Subject(); + public handshakeComplete = false; + public handshakeCompleteEvent: Subject = new Subject(); - circuit: Circuit; - objects: IObjectStore; - caps: Caps; - comms: Comms; - clientEvents: ClientEvents; - clientCommands: ClientCommands; - options: BotOptionFlags; - agent: Agent; - messageSubscription: Subscription; - parcelPropertiesSubscription: Subscription; + public circuit: Circuit; + public objects: IObjectStore; + public caps: Caps; + public comms: Comms; + public clientEvents: ClientEvents; + public clientCommands: ClientCommands; + public options: BotOptionFlags; + public agent: Agent; + public messageSubscription: Subscription; + public parcelPropertiesSubscription: Subscription; - terrain: number[][] = []; - tilesReceived = 0; - terrainComplete = false; - terrainCompleteEvent: Subject = new Subject(); + public terrain: number[][] = []; + public tilesReceived = 0; + public terrainComplete = false; + public terrainCompleteEvent: Subject = new Subject(); - parcelsComplete = false; - parcelsCompleteEvent: Subject = new Subject(); + public parcelsComplete = false; + public parcelsCompleteEvent: Subject = new Subject(); - parcelOverlayComplete = false; - parcelOverlayCompleteEvent: Subject = new Subject(); + public parcelOverlayComplete = false; + public parcelOverlayCompleteEvent: Subject = new Subject(); - parcelOverlay: ILandBlock[] = []; - parcels: { [key: number]: Parcel } = {}; - parcelsByUUID: { [key: string]: Parcel } = {}; - parcelMap: number[][] = []; + public parcelOverlay: ILandBlock[] = []; + public parcels: Record = {}; + public parcelsByUUID: Record = {}; + public parcelMap: number[][] = []; + + public textures: { + cloudTextureID?: UUID; + sunTextureID?: UUID; + moonTextureID?: UUID; + } = {}; - parcelCoordinates: { x: number, y: number }[] = []; + public parcelCoordinates: { x: number, y: number }[] = []; - environment: RegionEnvironment; + public environment: RegionEnvironment; - timeOffset = 0; + public timeOffset = 0; - resolver: ObjectResolver = new ObjectResolver(this); + public resolver: ObjectResolver = new ObjectResolver(this); - agents: { [key: string]: Avatar } = {}; + public agents = new Map(); private uploadCost: number; - private parcelOverlayReceived: { [key: number]: Buffer } = {}; + private parcelOverlayReceived: Record = {}; - static IDCTColumn16(linein: number[], lineout: number[], column: number): void - { - let total: number; - let usize: number; - - for (let n = 0; n < 16; n++) - { - total = this.OO_SQRT_2 * linein[column]; - - for (let u = 1; u < 16; u++) - { - usize = u * 16; - total += linein[usize + column] * this.CosineTable16[usize + n]; - } - - lineout[16 * n + column] = total; - } - } - - static IDCTLine16(linein: number[], lineout: number[], line: number): void - { - const oosob: number = 2.0 / 16.0; - const lineSize: number = line * 16; - let total = 0; - - for (let n = 0; n < 16; n++) - { - total = this.OO_SQRT_2 * linein[lineSize]; - - for (let u = 1; u < 16; u++) - { - total += linein[lineSize + u] * this.CosineTable16[u * 16 + n]; - } - - lineout[lineSize + n] = total * oosob; - } - } - - static InitialSetup(): void - { - // Build copy matrix 16 - { - let diag = false; - let right = true; - let i = 0; - let j = 0; - let count = 0; - - for (let x = 0; x < 16 * 16; x++) - { - this.CopyMatrix16.push(0); - this.DequantizeTable16.push(0); - this.CosineTable16.push(0); - } - while (i < 16 && j < 16) - { - this.CopyMatrix16[j * 16 + i] = count++; - - if (!diag) - { - if (right) - { - if (i < 16 - 1) - { - i++; - } - else - { - j++; - } - - right = false; - diag = true; - } - else - { - if (j < 16 - 1) - { - j++; - } - else - { - i++; - } - - right = true; - diag = true; - } - } - else - { - if (right) - { - i++; - j--; - if (i === 16 - 1 || j === 0) - { - diag = false; - } - } - else - { - i--; - j++; - if (j === 16 - 1 || i === 0) - { - diag = false; - } - } - } - } - } - { - for (let j = 0; j < 16; j++) - { - for (let i = 0; i < 16; i++) - { - this.DequantizeTable16[j * 16 + i] = 1.0 + 2.0 * (i + j); - } - } - } - { - const hposz: number = Math.PI * 0.5 / 16.0; - - for (let u = 0; u < 16; u++) - { - for (let n = 0; n < 16; n++) - { - this.CosineTable16[u * 16 + n] = Math.cos((2.0 * n + 1.0) * u * hposz); - } - } - } - this.setup = true; - } - - private static doesBitmapContainCoordinate(bitmap: Buffer, x: number, y: number): boolean - { - const mapBlockX = Math.floor(x / 4); - const mapBlockY = Math.floor(y / 4); - - let index = (mapBlockY * 64) + mapBlockX; - const bit = index % 8; - index >>= 3; - return ((bitmap[index] & (1 << bit)) !== 0); - } - - constructor(agent: Agent, clientEvents: ClientEvents, options: BotOptionFlags) + public constructor(agent: Agent, clientEvents: ClientEvents, options: BotOptionFlags) { if (!Region.setup) { @@ -343,9 +200,11 @@ export class Region } this.comms = new Comms(this.circuit, agent, clientEvents); - this.parcelPropertiesSubscription = this.clientEvents.onParcelPropertiesEvent.subscribe(async(parcelProperties: ParcelPropertiesEvent) => + this.parcelPropertiesSubscription = this.clientEvents.onParcelPropertiesEvent.subscribe((parcelProperties: ParcelPropertiesEvent) => { - await this.resolveParcel(parcelProperties); + this.resolveParcel(parcelProperties).catch((_e: unknown) => { + // ignore + }); }); this.messageSubscription = this.circuit.subscribeToMessages([ @@ -385,7 +244,7 @@ export class Region case Message.CoarseLocationUpdate: { const locations: CoarseLocationUpdateMessage = packet.message as CoarseLocationUpdateMessage; - const foundAgents: { [key: string]: Vector3 } = {}; + const foundAgents: Record = {}; for (let x = 0; x < locations.AgentData.length; x++) { const agentData = locations.AgentData[x]; @@ -393,35 +252,35 @@ export class Region const newPosition = new Vector3([location.X, location.Y, location.Z * 4]); foundAgents[agentData.AgentID.toString()] = newPosition; - if (this.agents[agentData.AgentID.toString()] === undefined) + const foundAgent = this.agents.get(agentData.AgentID.toString()); + if (foundAgent === undefined) { let resolved = await this.clientCommands.grid.avatarKey2Name(agentData.AgentID); if (Array.isArray(resolved)) { resolved = resolved[0]; } - if (this.agents[agentData.AgentID.toString()] === undefined) - { - this.agents[agentData.AgentID.toString()] = new Avatar(agentData.AgentID, resolved.getFirstName(), resolved.getLastName()); - this.clientEvents.onAvatarEnteredRegion.next(this.agents[agentData.AgentID.toString()]); - } - else - { - this.agents[agentData.AgentID.toString()].coarsePosition = newPosition; - } + const ag = new Avatar(agentData.AgentID, resolved.getFirstName(), resolved.getLastName()); + ag.coarsePosition = newPosition; + this.agents.set(agentData.AgentID.toString(), ag); + this.clientEvents.onAvatarEnteredRegion.next(ag); } else { - this.agents[agentData.AgentID.toString()].coarsePosition = newPosition; + foundAgent.coarsePosition = newPosition; } } - const keys = Object.keys(this.agents) + const keys = this.agents.keys(); for (const agentID of keys) { if (foundAgents[agentID] === undefined) { - this.agents[agentID].coarseLeftRegion(); - delete this.agents[agentID]; + const foundAgent = this.agents.get(agentID); + if (foundAgent !== undefined) + { + foundAgent.coarseLeftRegion(); + this.agents.delete(agentID); + } } } break; @@ -651,11 +510,12 @@ export class Region // stride - unused for now nibbler.UnpackBits(16); const patchSize = nibbler.UnpackBits(8); - const headerLayerType = nibbler.UnpackBits(8); + const headerLayerType: LayerType = nibbler.UnpackBits(8); switch (type) { case LayerType.Land: + { if (headerLayerType === type) // Quick sanity check { let x = 0; @@ -764,9 +624,9 @@ export class Region throw new Error('IDCTPatchLarge not implemented'); } - for (let j = 0; j < block.length; j++) + for (const bl of block) { - output.push(block[j] * mult + addVal); + output.push(bl * mult + addVal); } let outputIndex = 0; @@ -791,6 +651,9 @@ export class Region } } break; + } + default: + break; } break; } @@ -801,10 +664,547 @@ export class Region this.timeOffset = (new Date().getTime() / 1000) - timeStamp; break; } + default: + break; } }) } + public static IDCTColumn16(linein: number[], lineout: number[], column: number): void + { + let total = 0; + let usize = 0; + + for (let n = 0; n < 16; n++) + { + total = this.OO_SQRT_2 * linein[column]; + + for (let u = 1; u < 16; u++) + { + usize = u * 16; + total += linein[usize + column] * this.CosineTable16[usize + n]; + } + + lineout[16 * n + column] = total; + } + } + + public static IDCTLine16(linein: number[], lineout: number[], line: number): void + { + const oosob: number = 2.0 / 16.0; + const lineSize: number = line * 16; + let total = 0; + + for (let n = 0; n < 16; n++) + { + total = this.OO_SQRT_2 * linein[lineSize]; + + for (let u = 1; u < 16; u++) + { + total += linein[lineSize + u] * this.CosineTable16[u * 16 + n]; + } + + lineout[lineSize + n] = total * oosob; + } + } + + public static InitialSetup(): void + { + // Build copy matrix 16 + { + let diag = false; + let right = true; + let i = 0; + let j = 0; + let count = 0; + + for (let x = 0; x < 16 * 16; x++) + { + this.CopyMatrix16.push(0); + this.DequantizeTable16.push(0); + this.CosineTable16.push(0); + } + while (i < 16 && j < 16) + { + this.CopyMatrix16[j * 16 + i] = count++; + + if (!diag) + { + if (right) + { + if (i < 16 - 1) + { + i++; + } + else + { + j++; + } + + right = false; + diag = true; + } + else + { + if (j < 16 - 1) + { + j++; + } + else + { + i++; + } + + right = true; + diag = true; + } + } + else + { + if (right) + { + i++; + j--; + if (i === 16 - 1 || j === 0) + { + diag = false; + } + } + else + { + i--; + j++; + if (j === 16 - 1 || i === 0) + { + diag = false; + } + } + } + } + } + { + for (let j = 0; j < 16; j++) + { + for (let i = 0; i < 16; i++) + { + this.DequantizeTable16[j * 16 + i] = 1.0 + 2.0 * (i + j); + } + } + } + { + const hposz: number = Math.PI * 0.5 / 16.0; + + for (let u = 0; u < 16; u++) + { + for (let n = 0; n < 16; n++) + { + this.CosineTable16[u * 16 + n] = Math.cos((2.0 * n + 1.0) * u * hposz); + } + } + } + this.setup = true; + } + + public async getUploadCost(): Promise + { + if (this.uploadCost !== undefined) + { + return this.uploadCost; + } + + const msg = new EconomyDataRequestMessage(); + this.circuit.sendMessage(msg, PacketFlags.Reliable); + const economyReply = await this.circuit.waitForMessage(Message.EconomyData, 10000, (_message: EconomyDataMessage): FilterResponse => + { + return FilterResponse.Finish; + }); + + this.uploadCost = economyReply.Info.PriceUpload; + return this.uploadCost; + } + + public async getParcelProperties(x: number, y: number): Promise + { + return new Promise((resolve, reject) => + { + const request = new ParcelPropertiesRequestMessage(); + request.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.circuit.sessionID + }; + request.ParcelData = { + North: y + 1, + East: x + 1, + South: y, + West: x, + SequenceID: -10000, + SnapSelection: false + }; + this.circuit.sendMessage(request, PacketFlags.Reliable); + let messageAwait: Subscription | undefined = undefined; + let messageWaitTimer: number | undefined = undefined; + + messageAwait = this.clientEvents.onParcelPropertiesEvent.subscribe((parcelProperties: ParcelPropertiesEvent) => + { + if (Region.doesBitmapContainCoordinate(parcelProperties.Bitmap, x, y)) + { + if (messageAwait !== undefined) + { + messageAwait.unsubscribe(); + messageAwait = undefined; + } + if (messageWaitTimer !== undefined) + { + clearTimeout(messageWaitTimer); + messageWaitTimer = undefined; + } + this.resolveParcel(parcelProperties).then((value: Parcel) => + { + resolve(value); + }).catch((e: unknown) => + { + reject(e as Error); + }) + } + }); + + messageWaitTimer = setTimeout(() => + { + if (messageAwait !== undefined) + { + messageAwait.unsubscribe(); + messageAwait = undefined; + } + if (messageWaitTimer !== undefined) + { + clearTimeout(messageWaitTimer); + messageWaitTimer = undefined; + } + reject(new Error('Timed out')); + }, 10000) as unknown as number; + }); + } + + public async getParcels(): Promise + { + await this.waitForParcelOverlay(); + const parcels: Parcel[] = []; + for (const parcel of this.parcelCoordinates) + { + try + { + parcels.push(await this.getParcelProperties(parcel.x * 4.0, parcel.y * 4.0)); + } + catch (error) + { + console.error(error); + } + } + return parcels; + } + + public resetParcels(): void + { + this.parcelMap = []; + for (let x = 0; x < 64; x++) + { + this.parcelMap.push([]); + for (let y = 0; y < 64; y++) + { + this.parcelMap[x].push(0); + } + } + this.parcels = {}; + this.parcelsByUUID = {}; + this.parcelsComplete = false; + } + + public async waitForParcelOverlay(): Promise + { + return new Promise((resolve, reject) => + { + if (this.parcelOverlayComplete) + { + resolve(); + } + else + { + let timeout: NodeJS.Timeout | null = null; + const subscription = this.parcelOverlayCompleteEvent.subscribe(() => + { + if (timeout !== null) + { + clearTimeout(timeout); + } + subscription.unsubscribe(); + resolve(); + }); + timeout = setTimeout(() => + { + subscription.unsubscribe(); + reject(new Error('Timeout waiting for parcel overlay')); + }, 10000); + } + }); + } + + public async waitForParcels(): Promise + { + return new Promise((resolve, reject) => + { + if (this.parcelsComplete) + { + resolve(); + } + else + { + let timeout: NodeJS.Timeout | null = null; + const subscription = this.parcelsCompleteEvent.subscribe(() => + { + if (timeout !== null) + { + clearTimeout(timeout); + } + subscription.unsubscribe(); + resolve(); + }); + timeout = setTimeout(() => + { + subscription.unsubscribe(); + reject(new Error('Timeout waiting for parcels')); + }, 10000); + } + }); + } + + public async waitForTerrain(): Promise + { + return new Promise((resolve, reject) => + { + if (this.terrainComplete) + { + resolve(); + } + else + { + let timeout: NodeJS.Timeout | null = null; + const subscription = this.terrainCompleteEvent.subscribe(() => + { + if (timeout !== null) + { + clearTimeout(timeout); + } + subscription.unsubscribe(); + resolve(); + }); + timeout = setTimeout(() => + { + subscription.unsubscribe(); + reject(new Error('Timeout waiting for terrain')); + }, 10000); + } + }); + } + + public getTerrainHeightAtPoint(x: number, y: number): number + { + const patchX = Math.floor(x / 16); + const patchY = Math.floor(y / 16); + x = x % 16; + y = y % 16; + + const p = this.terrain[patchY * 16 + patchX]; + if (p === null) + { + return 0; + } + return p[y * 16 + x]; + } + + public exportXML(): string + { + const document = builder.create('RegionSettings'); + const general = document.ele('General'); + general.ele('AllowDamage', (this.regionFlags & RegionFlags.AllowDamage) ? 'True' : 'False'); + general.ele('AllowLandResell', !(this.regionFlags & RegionFlags.BlockLandResell) ? 'True' : 'False'); + general.ele('AllowLandJoinDivide', (this.regionFlags & RegionFlags.AllowParcelChanges) ? 'True' : 'False'); + general.ele('BlockFly', (this.regionFlags & RegionFlags.NoFly) ? 'True' : 'False'); + general.ele('BlockLandShowInSearch', (this.regionFlags & RegionFlags.BlockParcelSearch) ? 'True' : 'False'); + general.ele('BlockTerraform', (this.regionFlags & RegionFlags.BlockTerraform) ? 'True' : 'False'); + general.ele('DisableCollisions', (this.regionFlags & RegionFlags.SkipCollisions) ? 'True' : 'False'); + general.ele('DisablePhysics', (this.regionFlags & RegionFlags.SkipPhysics) ? 'True' : 'False'); + general.ele('DisableScripts', (this.regionFlags & RegionFlags.EstateSkipScripts) ? 'True' : 'False'); + general.ele('MaturityRating', (this.simAccess & SimAccessFlags.Mature & SimAccessFlags.Adult & SimAccessFlags.PG)); + general.ele('RestrictPushing', (this.regionFlags & RegionFlags.RestrictPushObject) ? 'True' : 'False'); + general.ele('AgentLimit', this.maxAgents); + general.ele('ObjectBonus', this.objectBonusFactor); + const groundTextures = document.ele('GroundTextures'); + groundTextures.ele('Texture1', this.terrainDetail0.toString()); + groundTextures.ele('Texture2', this.terrainDetail1.toString()); + groundTextures.ele('Texture3', this.terrainDetail2.toString()); + groundTextures.ele('Texture4', this.terrainDetail3.toString()); + + groundTextures.ele('ElevationLowSW', this.terrainStartHeight00); + groundTextures.ele('ElevationLowNW', this.terrainStartHeight01); + groundTextures.ele('ElevationLowSE', this.terrainStartHeight10); + groundTextures.ele('ElevationLowNE', this.terrainStartHeight11); + + groundTextures.ele('ElevationHighSW', this.terrainHeightRange00); + groundTextures.ele('ElevationHighNW', this.terrainHeightRange01); + groundTextures.ele('ElevationHighSE', this.terrainHeightRange10); + groundTextures.ele('ElevationHighNE', this.terrainHeightRange11); + + const terrain = document.ele('Terrain'); + terrain.ele('WaterHeight', this.waterHeight); + terrain.ele('TerrainRaiseLimit', this.terrainRaiseLimit); + terrain.ele('TerrainLowerLimit', this.terrainLowerLimit); + terrain.ele('UseEstateSun', (this.useEstateSun) ? 'True' : 'False'); + terrain.ele('FixedSun', (this.regionFlags & RegionFlags.SunFixed) ? 'True' : 'False'); + terrain.ele('SunPosition', this.sunHour); + if (this.environment) + { + const env = document.ele('Environment'); + env.ele('data', this.environment.toNotation()); + } + return document.end({ pretty: true, allowEmpty: true }); + } + + public activateCaps(seedURL: string): void + { + if (this.caps !== undefined) + { + this.caps.shutdown(); + } + this.caps = new Caps(this.agent, seedURL, this.clientEvents); + } + + public async handshake(handshake: RegionHandshakeMessage): Promise + { + this.regionName = Utils.BufferToStringSimple(handshake.RegionInfo.SimName); + this.simAccess = handshake.RegionInfo.SimAccess; + this.regionFlags = handshake.RegionInfo.RegionFlags; + this.regionOwner = handshake.RegionInfo.SimOwner; + this.agent.setIsEstateManager(handshake.RegionInfo.IsEstateManager); + this.waterHeight = handshake.RegionInfo.WaterHeight; + this.billableFactor = handshake.RegionInfo.BillableFactor; + this.cacheID = handshake.RegionInfo.CacheID; + this.terrainBase0 = handshake.RegionInfo.TerrainBase0; + this.terrainBase1 = handshake.RegionInfo.TerrainBase1; + this.terrainBase2 = handshake.RegionInfo.TerrainBase2; + this.terrainBase3 = handshake.RegionInfo.TerrainBase3; + this.terrainDetail0 = handshake.RegionInfo.TerrainDetail0; + this.terrainDetail1 = handshake.RegionInfo.TerrainDetail1; + this.terrainDetail2 = handshake.RegionInfo.TerrainDetail2; + this.terrainDetail3 = handshake.RegionInfo.TerrainDetail3; + this.terrainStartHeight00 = handshake.RegionInfo.TerrainStartHeight00; + this.terrainStartHeight01 = handshake.RegionInfo.TerrainStartHeight01; + this.terrainStartHeight10 = handshake.RegionInfo.TerrainStartHeight10; + this.terrainStartHeight11 = handshake.RegionInfo.TerrainStartHeight11; + this.terrainHeightRange00 = handshake.RegionInfo.TerrainHeightRange00; + this.terrainHeightRange01 = handshake.RegionInfo.TerrainHeightRange01; + this.terrainHeightRange10 = handshake.RegionInfo.TerrainHeightRange10; + this.terrainHeightRange11 = handshake.RegionInfo.TerrainHeightRange11; + this.regionID = handshake.RegionInfo2.RegionID; + this.cpuClassID = handshake.RegionInfo3.CPUClassID; + this.cpuRatio = handshake.RegionInfo3.CPURatio; + this.coloName = Utils.BufferToStringSimple(handshake.RegionInfo3.ColoName); + this.productSKU = Utils.BufferToStringSimple(handshake.RegionInfo3.ProductSKU); + this.productName = Utils.BufferToStringSimple(handshake.RegionInfo3.ProductName); + + + const request: RequestRegionInfoMessage = new RequestRegionInfoMessage(); + request.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.circuit.sessionID + }; + this.circuit.sendMessage(request, PacketFlags.Reliable); + const regionInfo: RegionInfoMessage = await this.circuit.waitForMessage(Message.RegionInfo, 30000); + + this.estateID = regionInfo.RegionInfo.EstateID; + this.parentEstateID = regionInfo.RegionInfo.ParentEstateID; + this.maxAgents = regionInfo.RegionInfo.MaxAgents; + this.objectBonusFactor = regionInfo.RegionInfo.ObjectBonusFactor; + this.terrainRaiseLimit = regionInfo.RegionInfo.TerrainRaiseLimit; + this.terrainLowerLimit = regionInfo.RegionInfo.TerrainLowerLimit; + this.pricePerMeter = regionInfo.RegionInfo.PricePerMeter; + this.redirectGridX = regionInfo.RegionInfo.RedirectGridX; + this.redirectGridY = regionInfo.RegionInfo.RedirectGridY; + this.useEstateSun = regionInfo.RegionInfo.UseEstateSun; + this.sunHour = regionInfo.RegionInfo.SunHour; + this.maxAgents32 = regionInfo.RegionInfo2.MaxAgents32; + this.hardMaxAgents = regionInfo.RegionInfo2.HardMaxAgents; + this.hardMaxObjects = regionInfo.RegionInfo2.HardMaxObjects; + + const msg: MapNameRequestMessage = new MapNameRequestMessage(); + msg.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.circuit.sessionID, + Flags: GridLayerType.Objects, + EstateID: 0, + Godlike: false + }; + msg.NameData = { + Name: handshake.RegionInfo.SimName + }; + this.circuit.sendMessage(msg, PacketFlags.Reliable); + await this.circuit.waitForMessage(Message.MapBlockReply, 30000, (filterMsg: MapBlockReplyMessage): FilterResponse => + { + for (const region of filterMsg.Data) + { + const name = Utils.BufferToStringSimple(region.Name); + if (name.trim().toLowerCase() === this.regionName.trim().toLowerCase()) + { + this.xCoordinate = region.X; + this.yCoordinate = region.Y; + this.mapImage = region.MapImageID; + const globalPos = Utils.RegionCoordinatesToHandle(this.xCoordinate, this.yCoordinate); + this.regionHandle = globalPos.regionHandle; + return FilterResponse.Finish; + } + } + return FilterResponse.NoMatch; + }); + + + await this.caps.waitForSeedCapability(); + + try + { + const extResponse = await this.caps.capsGetString('ExtEnvironment'); + this.environment = new RegionEnvironment(LLSD.parseXML(extResponse)); + } + catch (e: unknown) + { + Logger.Error(e); + Logger.Warn('Unable to get environment settings from region'); + } + + this.handshakeComplete = true; + this.handshakeCompleteEvent.next(); + } + + public shutdown(): void + { + this.parcelPropertiesSubscription.unsubscribe(); + this.messageSubscription.unsubscribe(); + this.comms.shutdown(); + this.caps.shutdown(); + this.objects.shutdown(); + this.resolver.shutdown(); + this.circuit.shutdown(); + } + + private static doesBitmapContainCoordinate(bitmap: Buffer, x: number, y: number): boolean + { + const mapBlockX = Math.floor(x / 4); + const mapBlockY = Math.floor(y / 4); + + let index = (mapBlockY * 64) + mapBlockX; + const bit = index % 8; + index >>= 3; + return ((bitmap[index] & (1 << bit)) !== 0); + } + private async resolveParcel(parcelProperties: ParcelPropertiesEvent): Promise { // Get the parcel UUID @@ -990,458 +1390,4 @@ export class Region this.fillParcel(parcelID, x, y + 1); } } - - public async getUploadCost(): Promise - { - if (this.uploadCost !== undefined) - { - return this.uploadCost; - } - - const msg = new EconomyDataRequestMessage(); - this.circuit.sendMessage(msg, PacketFlags.Reliable); - const economyReply = await this.circuit.waitForMessage(Message.EconomyData, 10000, (_message: EconomyDataMessage): FilterResponse => - { - return FilterResponse.Finish; - }); - - this.uploadCost = economyReply.Info.PriceUpload; - return this.uploadCost; - } - - public getParcelProperties(x: number, y: number): Promise - { - return new Promise((resolve, reject) => - { - const request = new ParcelPropertiesRequestMessage(); - request.AgentData = { - AgentID: this.agent.agentID, - SessionID: this.circuit.sessionID - }; - request.ParcelData = { - North: y + 1, - East: x + 1, - South: y, - West: x, - SequenceID: -10000, - SnapSelection: false - }; - this.circuit.sendMessage(request, PacketFlags.Reliable); - let messageAwait: Subscription | undefined = undefined; - let messageWaitTimer: number | undefined = undefined; - - messageAwait = this.clientEvents.onParcelPropertiesEvent.subscribe(async(parcelProperties: ParcelPropertiesEvent) => - { - if (Region.doesBitmapContainCoordinate(parcelProperties.Bitmap, x, y)) - { - if (messageAwait !== undefined) - { - messageAwait.unsubscribe(); - messageAwait = undefined; - } - if (messageWaitTimer !== undefined) - { - clearTimeout(messageWaitTimer); - messageWaitTimer = undefined; - } - resolve(await this.resolveParcel(parcelProperties)); - } - }); - - messageWaitTimer = setTimeout(() => - { - if (messageAwait !== undefined) - { - messageAwait.unsubscribe(); - messageAwait = undefined; - } - if (messageWaitTimer !== undefined) - { - clearTimeout(messageWaitTimer); - messageWaitTimer = undefined; - } - reject(new Error('Timed out')); - }, 10000) as any as number; - }); - } - - async getParcels(): Promise - { - await this.waitForParcelOverlay(); - const parcels: Parcel[] = []; - for (const parcel of this.parcelCoordinates) - { - try - { - parcels.push(await this.getParcelProperties(parcel.x * 4.0, parcel.y * 4.0)); - } - catch (error) - { - console.error(error); - } - } - return parcels; - } - - resetParcels(): void - { - this.parcelMap = []; - for (let x = 0; x < 64; x++) - { - this.parcelMap.push([]); - for (let y = 0; y < 64; y++) - { - this.parcelMap[x].push(0); - } - } - this.parcels = {}; - this.parcelsByUUID = {}; - this.parcelsComplete = false; - } - - waitForParcelOverlay(): Promise - { - return new Promise((resolve, reject) => - { - if (this.parcelOverlayComplete) - { - resolve(); - } - else - { - let timeout: NodeJS.Timeout | null = null; - const subscription = this.parcelOverlayCompleteEvent.subscribe(() => - { - if (timeout !== null) - { - clearTimeout(timeout); - } - subscription.unsubscribe(); - resolve(); - }); - timeout = setTimeout(() => - { - subscription.unsubscribe(); - reject(new Error('Timeout waiting for parcel overlay')); - }, 10000); - } - }); - } - - waitForParcels(): Promise - { - return new Promise((resolve, reject) => - { - if (this.parcelsComplete) - { - resolve(); - } - else - { - let timeout: NodeJS.Timeout | null = null; - const subscription = this.parcelsCompleteEvent.subscribe(() => - { - if (timeout !== null) - { - clearTimeout(timeout); - } - subscription.unsubscribe(); - resolve(); - }); - timeout = setTimeout(() => - { - subscription.unsubscribe(); - reject(new Error('Timeout waiting for parcels')); - }, 10000); - } - }); - } - - waitForTerrain(): Promise - { - return new Promise((resolve, reject) => - { - if (this.terrainComplete) - { - resolve(); - } - else - { - let timeout: NodeJS.Timeout | null = null; - const subscription = this.terrainCompleteEvent.subscribe(() => - { - if (timeout !== null) - { - clearTimeout(timeout); - } - subscription.unsubscribe(); - resolve(); - }); - timeout = setTimeout(() => - { - subscription.unsubscribe(); - reject(new Error('Timeout waiting for terrain')); - }, 10000); - } - }); - } - - getTerrainHeightAtPoint(x: number, y: number): number - { - const patchX = Math.floor(x / 16); - const patchY = Math.floor(y / 16); - x = x % 16; - y = y % 16; - - const p = this.terrain[patchY * 16 + patchX]; - if (p === null) - { - return 0; - } - return p[y * 16 + x]; - } - - exportXML(): string - { - const document = builder.create('RegionSettings'); - const general = document.ele('General'); - general.ele('AllowDamage', (this.regionFlags & RegionFlags.AllowDamage) ? 'True' : 'False'); - general.ele('AllowLandResell', !(this.regionFlags & RegionFlags.BlockLandResell) ? 'True' : 'False'); - general.ele('AllowLandJoinDivide', (this.regionFlags & RegionFlags.AllowParcelChanges) ? 'True' : 'False'); - general.ele('BlockFly', (this.regionFlags & RegionFlags.NoFly) ? 'True' : 'False'); - general.ele('BlockLandShowInSearch', (this.regionFlags & RegionFlags.BlockParcelSearch) ? 'True' : 'False'); - general.ele('BlockTerraform', (this.regionFlags & RegionFlags.BlockTerraform) ? 'True' : 'False'); - general.ele('DisableCollisions', (this.regionFlags & RegionFlags.SkipCollisions) ? 'True' : 'False'); - general.ele('DisablePhysics', (this.regionFlags & RegionFlags.SkipPhysics) ? 'True' : 'False'); - general.ele('DisableScripts', (this.regionFlags & RegionFlags.EstateSkipScripts) ? 'True' : 'False'); - general.ele('MaturityRating', (this.simAccess & SimAccessFlags.Mature & SimAccessFlags.Adult & SimAccessFlags.PG)); - general.ele('RestrictPushing', (this.regionFlags & RegionFlags.RestrictPushObject) ? 'True' : 'False'); - general.ele('AgentLimit', this.maxAgents); - general.ele('ObjectBonus', this.objectBonusFactor); - const groundTextures = document.ele('GroundTextures'); - groundTextures.ele('Texture1', this.terrainDetail0.toString()); - groundTextures.ele('Texture2', this.terrainDetail1.toString()); - groundTextures.ele('Texture3', this.terrainDetail2.toString()); - groundTextures.ele('Texture4', this.terrainDetail3.toString()); - - groundTextures.ele('ElevationLowSW', this.terrainStartHeight00); - groundTextures.ele('ElevationLowNW', this.terrainStartHeight01); - groundTextures.ele('ElevationLowSE', this.terrainStartHeight10); - groundTextures.ele('ElevationLowNE', this.terrainStartHeight11); - - groundTextures.ele('ElevationHighSW', this.terrainHeightRange00); - groundTextures.ele('ElevationHighNW', this.terrainHeightRange01); - groundTextures.ele('ElevationHighSE', this.terrainHeightRange10); - groundTextures.ele('ElevationHighNE', this.terrainHeightRange11); - - const terrain = document.ele('Terrain'); - terrain.ele('WaterHeight', this.waterHeight); - terrain.ele('TerrainRaiseLimit', this.terrainRaiseLimit); - terrain.ele('TerrainLowerLimit', this.terrainLowerLimit); - terrain.ele('UseEstateSun', (this.useEstateSun) ? 'True' : 'False'); - terrain.ele('FixedSun', (this.regionFlags & RegionFlags.SunFixed) ? 'True' : 'False'); - terrain.ele('SunPosition', this.sunHour); - this.environment.getXML(document); - return document.end({ pretty: true, allowEmpty: true }); - } - - activateCaps(seedURL: string): void - { - if (this.caps !== undefined) - { - this.caps.shutdown(); - } - this.caps = new Caps(this.agent, seedURL, this.clientEvents); - } - - async handshake(handshake: RegionHandshakeMessage): Promise - { - this.regionName = Utils.BufferToStringSimple(handshake.RegionInfo.SimName); - this.simAccess = handshake.RegionInfo.SimAccess; - this.regionFlags = handshake.RegionInfo.RegionFlags; - this.regionOwner = handshake.RegionInfo.SimOwner; - this.agent.setIsEstateManager(handshake.RegionInfo.IsEstateManager); - this.waterHeight = handshake.RegionInfo.WaterHeight; - this.billableFactor = handshake.RegionInfo.BillableFactor; - this.cacheID = handshake.RegionInfo.CacheID; - this.terrainBase0 = handshake.RegionInfo.TerrainBase0; - this.terrainBase1 = handshake.RegionInfo.TerrainBase1; - this.terrainBase2 = handshake.RegionInfo.TerrainBase2; - this.terrainBase3 = handshake.RegionInfo.TerrainBase3; - this.terrainDetail0 = handshake.RegionInfo.TerrainDetail0; - this.terrainDetail1 = handshake.RegionInfo.TerrainDetail1; - this.terrainDetail2 = handshake.RegionInfo.TerrainDetail2; - this.terrainDetail3 = handshake.RegionInfo.TerrainDetail3; - this.terrainStartHeight00 = handshake.RegionInfo.TerrainStartHeight00; - this.terrainStartHeight01 = handshake.RegionInfo.TerrainStartHeight01; - this.terrainStartHeight10 = handshake.RegionInfo.TerrainStartHeight10; - this.terrainStartHeight11 = handshake.RegionInfo.TerrainStartHeight11; - this.terrainHeightRange00 = handshake.RegionInfo.TerrainHeightRange00; - this.terrainHeightRange01 = handshake.RegionInfo.TerrainHeightRange01; - this.terrainHeightRange10 = handshake.RegionInfo.TerrainHeightRange10; - this.terrainHeightRange11 = handshake.RegionInfo.TerrainHeightRange11; - this.regionID = handshake.RegionInfo2.RegionID; - this.cpuClassID = handshake.RegionInfo3.CPUClassID; - this.cpuRatio = handshake.RegionInfo3.CPURatio; - this.coloName = Utils.BufferToStringSimple(handshake.RegionInfo3.ColoName); - this.productSKU = Utils.BufferToStringSimple(handshake.RegionInfo3.ProductSKU); - this.productName = Utils.BufferToStringSimple(handshake.RegionInfo3.ProductName); - - - const request: RequestRegionInfoMessage = new RequestRegionInfoMessage(); - request.AgentData = { - AgentID: this.agent.agentID, - SessionID: this.circuit.sessionID - }; - this.circuit.sendMessage(request, PacketFlags.Reliable); - const regionInfo: RegionInfoMessage = await this.circuit.waitForMessage(Message.RegionInfo, 10000); - - this.estateID = regionInfo.RegionInfo.EstateID; - this.parentEstateID = regionInfo.RegionInfo.ParentEstateID; - this.maxAgents = regionInfo.RegionInfo.MaxAgents; - this.objectBonusFactor = regionInfo.RegionInfo.ObjectBonusFactor; - this.terrainRaiseLimit = regionInfo.RegionInfo.TerrainRaiseLimit; - this.terrainLowerLimit = regionInfo.RegionInfo.TerrainLowerLimit; - this.pricePerMeter = regionInfo.RegionInfo.PricePerMeter; - this.redirectGridX = regionInfo.RegionInfo.RedirectGridX; - this.redirectGridY = regionInfo.RegionInfo.RedirectGridY; - this.useEstateSun = regionInfo.RegionInfo.UseEstateSun; - this.sunHour = regionInfo.RegionInfo.SunHour; - this.maxAgents32 = regionInfo.RegionInfo2.MaxAgents32; - this.hardMaxAgents = regionInfo.RegionInfo2.HardMaxAgents; - this.hardMaxObjects = regionInfo.RegionInfo2.HardMaxObjects; - - const msg: MapNameRequestMessage = new MapNameRequestMessage(); - msg.AgentData = { - AgentID: this.agent.agentID, - SessionID: this.circuit.sessionID, - Flags: GridLayerType.Objects, - EstateID: 0, - Godlike: false - }; - msg.NameData = { - Name: handshake.RegionInfo.SimName - }; - this.circuit.sendMessage(msg, PacketFlags.Reliable); - await this.circuit.waitForMessage(Message.MapBlockReply, 10000, (filterMsg: MapBlockReplyMessage): FilterResponse => - { - for (const region of filterMsg.Data) - { - const name = Utils.BufferToStringSimple(region.Name); - if (name.trim().toLowerCase() === this.regionName.trim().toLowerCase()) - { - this.xCoordinate = region.X; - this.yCoordinate = region.Y; - this.mapImage = region.MapImageID; - const globalPos = Utils.RegionCoordinatesToHandle(this.xCoordinate, this.yCoordinate); - this.regionHandle = globalPos.regionHandle; - return FilterResponse.Finish; - } - } - return FilterResponse.NoMatch; - }); - - this.environment = new RegionEnvironment(); - this.environment.dayCycleKeyframes = []; - this.environment.skyPresets = {}; - this.environment.water = { - blurMultiplier: 0, - fresnelOffset: 0, - fresnelScale: 0, - normalScale: Vector3.getZero(), - normalMap: UUID.zero(), - scaleAbove: 0, - scaleBelow: 0, - underWaterFogMod: 0, - waterFogColor: Color4.white, - waterFogDensity: 0, - wave1Dir: Vector2.getZero(), - wave2Dir: Vector2.getZero() - }; - - await this.caps.waitForSeedCapability(); - - try - { - const response = await this.caps.capsGetXML('EnvironmentSettings'); - if (response.length >= 4) - { - if (Array.isArray(response[1]) && typeof response[2] === 'object' && typeof response[3] === 'object') - { - for (const kf of response[1]) - { - this.environment.dayCycleKeyframes.push({ - time: kf[0], - preset: kf[1] - }); - } - for (const presetKey of Object.keys(response[2])) - { - const preset = response[2][presetKey]; - this.environment.skyPresets[presetKey] = new class implements SkyPreset - { - ambient = new Vector4(preset['ambient']); - blueDensity = new Vector4(preset['blue_density']); - blueHorizon = new Vector4(preset['blue_horizon']); - cloudColor = new Color4(preset['cloud_color']); - cloudPosDensity1 = new Vector4(preset['cloud_pos_density1']); - cloudPosDensity2 = new Vector4(preset['cloud_pos_density2']); - cloudScale = new Vector4(preset['cloud_scale']); - cloudScrollRate = new Vector2(preset['cloud_scroll_rate']); - cloudShadow = new Vector4(preset['cloud_shadow']); - densityMultiplier = new Vector4(preset['density_multiplier']); - distanceMultiplier = new Vector4(preset['distance_multiplier']); - eastAngle = preset['east_angle']; - enableCloudScroll = { - x: preset['enable_cloud_scroll'][0], - y: preset['enable_cloud_scroll'][1] - }; - gamma = new Vector4(preset['gamma']); - glow = new Vector4(preset['glow']); - hazeDensity = new Vector4(preset['haze_density']); - hazeHorizon = new Vector4(preset['haze_horizon']); - lightNormal = new Vector4(preset['lightnorm']); - maxY = new Vector4(preset['max_y']); - starBrightness = preset['start_brightness']; - sunAngle = preset['sun_angle']; - sunlightColor = new Color4(preset['sunlight_color']); - }; - } - const wat = response[3]; - this.environment.water = new class implements WaterPreset - { - blurMultiplier = wat['blurMultiplier']; - fresnelOffset = wat['fresnelOffset']; - fresnelScale = wat['fresnelScale']; - normalScale = new Vector3(wat['normScale']); - normalMap = new UUID(wat['normalMap'].toString()); - scaleAbove = wat['scaleAbove']; - scaleBelow = wat['scaleBelow']; - underWaterFogMod = wat['underWaterFogMod']; - waterFogColor = new Color4(wat['waterFogColor']); - waterFogDensity = wat['waterFogDensity']; - wave1Dir = new Vector2(wat['wave1Dir']); - wave2Dir = new Vector2(wat['wave2Dir']); - }; - } - } - } - catch (e) - { - Logger.Warn('Unable to get environment settings from region'); - } - this.handshakeComplete = true; - this.handshakeCompleteEvent.next(); - } - - shutdown(): void - { - this.parcelPropertiesSubscription.unsubscribe(); - this.messageSubscription.unsubscribe(); - this.comms.shutdown(); - this.caps.shutdown(); - this.objects.shutdown(); - this.resolver.shutdown(); - this.circuit.shutdown(); - } } diff --git a/lib/classes/TarArchive.ts b/lib/classes/TarArchive.ts index 1baca2c..fe6e6af 100644 --- a/lib/classes/TarArchive.ts +++ b/lib/classes/TarArchive.ts @@ -1,6 +1,6 @@ -import { TarFile } from './TarFile'; +import type { TarFile } from './TarFile'; export class TarArchive { - files: TarFile[] = []; + public files: TarFile[] = []; } diff --git a/lib/classes/TarFile.ts b/lib/classes/TarFile.ts index 53502f4..bbf1842 100644 --- a/lib/classes/TarFile.ts +++ b/lib/classes/TarFile.ts @@ -2,18 +2,18 @@ import * as fs from 'fs'; export class TarFile { - fileName: string; - fileMode: number; - userID: number; - groupID: number; - modifyTime: Date; - linkIndicator: number; - linkedFile: string; - offset: number; - fileSize: number; - archiveFile: string; + public fileName: string; + public fileMode: number; + public userID: number; + public groupID: number; + public modifyTime: Date; + public linkIndicator: number; + public linkedFile: string; + public offset: number; + public fileSize: number; + public archiveFile: string; - read(): Promise + public async read(): Promise { return new Promise((resolve, reject) => { diff --git a/lib/classes/TarReader.ts b/lib/classes/TarReader.ts index e3528ce..3c6e5f0 100644 --- a/lib/classes/TarReader.ts +++ b/lib/classes/TarReader.ts @@ -1,6 +1,6 @@ import { TarFile } from './TarFile'; import { TarArchive } from './TarArchive'; -import { Readable } from 'stream'; +import type { Readable } from 'stream'; import * as path from 'path'; import * as os from 'os'; @@ -11,12 +11,8 @@ export class TarReader { private outFile: string; - constructor() - { - } - - parse(stream: Readable): Promise + public async parse(stream: Readable): Promise { return new Promise((resolve, reject) => { @@ -47,9 +43,9 @@ export class TarReader const wantedBytes = chunk.length - remainingBytes; if (longName) { - fileChunks.push(chunk.slice(0, chunk.length - wantedBytes)); + fileChunks.push(chunk.subarray(0, chunk.length - wantedBytes)); } - queuedChunks = [chunk.slice(chunk.length - wantedBytes)]; + queuedChunks = [chunk.subarray(chunk.length - wantedBytes)]; queuedBytes = queuedChunks[0].length; remainingBytes = 0; } @@ -73,20 +69,20 @@ export class TarReader if (queuedBytes >= 512) { const buf = Buffer.concat(queuedChunks); - const header = buf.slice(0, 512); - queuedChunks = [buf.slice(512)]; + const header = buf.subarray(0, 512); + queuedChunks = [buf.subarray(512)]; queuedBytes = queuedChunks[0].length; - let hdrFileName = this.trimEntry(header.slice(0, 100)); + let hdrFileName = this.trimEntry(header.subarray(0, 100)); console.log('Filename: ' + hdrFileName); - const hdrFileMode = this.decodeOctal(header.slice(100, 100 + 8)); - const hdrUserID = this.decodeOctal(header.slice(108, 108 + 8)); - const hdrGroupID = this.decodeOctal(header.slice(116, 116 + 8)); - fileSize = this.decodeOctal(header.slice(124, 124 + 12)); - const hdrModifyTime = this.decodeOctal(header.slice(136, 136 + 12)); - const checksum = this.decodeOctal(header.slice(148, 148 + 8)); + const hdrFileMode = this.decodeOctal(header.subarray(100, 100 + 8)); + const hdrUserID = this.decodeOctal(header.subarray(108, 108 + 8)); + const hdrGroupID = this.decodeOctal(header.subarray(116, 116 + 8)); + fileSize = this.decodeOctal(header.subarray(124, 124 + 12)); + const hdrModifyTime = this.decodeOctal(header.subarray(136, 136 + 12)); + const checksum = this.decodeOctal(header.subarray(148, 148 + 8)); const linkIndicator = header[156]; - const linkedFile = this.trimEntry(header.slice(157, 157 + 100)); + const linkedFile = this.trimEntry(header.subarray(157, 157 + 100)); paddingSize = (Math.ceil(fileSize / 512) * 512) - fileSize; // Check CRC @@ -100,8 +96,8 @@ export class TarReader } if (sum !== checksum) { - readState = 3; - continue; + readState = 3; + continue; } if (linkIndicator === 76) { @@ -151,7 +147,7 @@ export class TarReader if (readState === 2 && queuedBytes >= paddingSize) { const buf = Buffer.concat(queuedChunks); - queuedChunks = [buf.slice(paddingSize)]; + queuedChunks = [buf.subarray(paddingSize)]; queuedBytes = queuedChunks[0].length; readState = 0; chunk = Buffer.alloc(0); @@ -175,7 +171,7 @@ export class TarReader }); } - close(): void + public close(): void { fs.unlinkSync(this.outFile); this.outFile = ''; @@ -188,7 +184,7 @@ export class TarReader { end = buf.length - 1; } - return buf.slice(0, end).toString('ascii'); + return buf.subarray(0, end).toString('ascii'); } private decodeOctal(buf: Buffer): number diff --git a/lib/classes/TarWriter.ts b/lib/classes/TarWriter.ts index 2843900..9c85bd9 100644 --- a/lib/classes/TarWriter.ts +++ b/lib/classes/TarWriter.ts @@ -7,16 +7,16 @@ export class TarWriter extends Transform private fileActive = false; - async newFile(archivePath: string, realPath: string): Promise + public async newFile(archivePath: string, realPath: string): Promise { if (this.fileActive) { - this.endFile(); + await this.endFile(); } const stat = fs.statSync(realPath); const buf = Buffer.from(archivePath, 'ascii'); - this.writeHeader( + await this.writeHeader( this.chopString('././@LongName', 100), stat.mode, stat.uid, @@ -27,9 +27,9 @@ export class TarWriter extends Transform ); this.thisFileSize = buf.length; await this.pipeFromBuffer(buf); - this.endFile(); + await this.endFile(); - this.writeHeader( + await this.writeHeader( this.chopString(archivePath, 100), stat.mode, stat.uid, @@ -43,7 +43,7 @@ export class TarWriter extends Transform this.fileActive = true; } - async pipeFromBuffer(buf: Buffer): Promise + public async pipeFromBuffer(buf: Buffer): Promise { const readableInstanceStream = new Readable({ read(): void @@ -55,7 +55,7 @@ export class TarWriter extends Transform return this.pipeFrom(readableInstanceStream); } - pipeFrom(str: Readable): Promise + public async pipeFrom(str: Readable): Promise { return new Promise((resolve, reject) => { @@ -71,6 +71,21 @@ export class TarWriter extends Transform }); } + public async endFile(): Promise + { + const finalSize = Math.ceil(this.thisFileSize / 512) * 512; + const remainingSize = finalSize - this.thisFileSize; + const buf = Buffer.alloc(remainingSize); + await this.pipeFromBuffer(buf); + this.fileActive = false; + } + + public _transform(chunk: any, encoding: 'ascii' | 'utf-8' | 'utf16le' | 'ucs-2' | 'base64' | 'latin1' | 'binary' | 'hex', callback: (error?: Error, data?: any) => void): void + { + this.push(chunk, encoding); + callback(); + } + private async writeHeader(fileName: string, mode: number, uid: number, gid: number, fileSize: number, mTime: Date, fileType: string): Promise { const header = Buffer.alloc(512); @@ -104,21 +119,6 @@ export class TarWriter extends Transform return this.pipeFromBuffer(header); } - async endFile(): Promise - { - const finalSize = Math.ceil(this.thisFileSize / 512) * 512; - const remainingSize = finalSize - this.thisFileSize; - const buf = Buffer.alloc(remainingSize); - await this.pipeFromBuffer(buf); - this.fileActive = false; - } - - public _transform(chunk: any, encoding: 'ascii' | 'utf-8' | 'utf16le' | 'ucs-2' | 'base64' | 'latin1' | 'binary' | 'hex', callback: (error?: Error, data?: any) => void): void - { - this.push(chunk, encoding); - callback(); - } - private chopString(str: string, maxLength: number): string { return str.substring(0, maxLength - 1); diff --git a/lib/classes/TextureEntry.ts b/lib/classes/TextureEntry.ts index 2a65dcf..77ff48f 100644 --- a/lib/classes/TextureEntry.ts +++ b/lib/classes/TextureEntry.ts @@ -2,16 +2,16 @@ import { TextureEntryFace } from './TextureEntryFace'; import { UUID } from './UUID'; import { Color4 } from './Color4'; import { Utils } from './Utils'; -import { LLGLTFMaterialOverride } from './LLGLTFMaterialOverride'; +import type { LLGLTFMaterialOverride } from './LLGLTFMaterialOverride'; export class TextureEntry { - static MAX_UINT32 = 4294967295; + public static MAX_UINT32 = 4294967295; public defaultTexture: TextureEntryFace | null; public faces: TextureEntryFace[] = []; public gltfMaterialOverrides = new Map() - static readFaceBitfield(buf: Buffer, pos: number): { + public static readFaceBitfield(buf: Buffer, pos: number): { result: boolean, pos: number, faceBits: number, @@ -41,7 +41,7 @@ export class TextureEntry return result; } - static getFaceBitfieldBuffer(bitfield: number): Buffer + public static getFaceBitfieldBuffer(bitfield: number): Buffer { let byteLength = 0; let tmpBitfield = bitfield; @@ -70,7 +70,7 @@ export class TextureEntry return bytes; } - static from(buf: Buffer): TextureEntry + public static from(buf: Buffer): TextureEntry { const te = new TextureEntry(); if (buf.length < 16) @@ -380,24 +380,17 @@ export class TextureEntry return te; } - constructor() + public getEffectiveEntryForFace(face: number): TextureEntryFace { - + const def = new TextureEntryFace(this.defaultTexture); + if (this.faces.length > face) + { + return this.faces[face]; + } + return def; } - private createFace(face: number): void - { - if (face > 32) - { - console.error('Warning: Face number exceeds maximum number of faces: 32'); - } - while (this.faces.length <= face) - { - this.faces.push(new TextureEntryFace(this.defaultTexture)); - } - } - - toBuffer(): Buffer + public toBuffer(): Buffer { if (this.defaultTexture === null) { @@ -573,7 +566,7 @@ export class TextureEntry return Buffer.concat(chunks); } - getChunks(chunks: Buffer[], items: number[], func: (item: TextureEntryFace) => Buffer): void + public getChunks(chunks: Buffer[], items: number[], func: (item: TextureEntryFace) => Buffer): void { if (this.defaultTexture !== null) { @@ -623,8 +616,20 @@ export class TextureEntry } } - toBase64(): string + public toBase64(): string { return this.toBuffer().toString('base64'); } + + private createFace(face: number): void + { + if (face > 32) + { + console.error('Warning: Face number exceeds maximum number of faces: 32'); + } + while (this.faces.length <= face) + { + this.faces.push(new TextureEntryFace(this.defaultTexture)); + } + } } diff --git a/lib/classes/TextureEntryFace.ts b/lib/classes/TextureEntryFace.ts index a77d435..fc5a3dc 100644 --- a/lib/classes/TextureEntryFace.ts +++ b/lib/classes/TextureEntryFace.ts @@ -1,5 +1,5 @@ -import { UUID } from './UUID'; -import { Color4 } from './Color4'; +import type { UUID } from './UUID'; +import type { Color4 } from './Color4'; import { TextureFlags } from '../enums/TextureFlags'; import { Bumpiness } from '../enums/Bumpiness'; import { Shininess } from '../enums/Shininess'; @@ -7,11 +7,11 @@ import { MappingType } from '../enums/MappingType'; export class TextureEntryFace { - static BUMP_MASK = 0x1F; - static FULLBRIGHT_MASK = 0x20; - static SHINY_MASK = 0xC0; - static MEDIA_MASK = 0x01; - static TEX_MAP_MASK = 0x06; + public static BUMP_MASK = 0x1F; + public static FULLBRIGHT_MASK = 0x20; + public static SHINY_MASK = 0xC0; + public static MEDIA_MASK = 0x01; + public static TEX_MAP_MASK = 0x06; private _textureID: UUID; private _rgba: Color4; @@ -30,10 +30,10 @@ export class TextureEntryFace private _material: number; private _media: number; - private hasAttribute: TextureFlags; - private defaultTexture: TextureEntryFace | null; + private readonly hasAttribute: TextureFlags; + private readonly defaultTexture: TextureEntryFace | null; - constructor(def: TextureEntryFace | null) + public constructor(def: TextureEntryFace | null) { this.defaultTexture = def; if (this.defaultTexture == null) @@ -46,7 +46,7 @@ export class TextureEntryFace } } - get rgba(): Color4 + public get rgba(): Color4 { if (this._rgba === undefined && this.defaultTexture !== null) { @@ -55,12 +55,12 @@ export class TextureEntryFace return this._rgba; } - set rgba(value: Color4) + public set rgba(value: Color4) { this._rgba = value; } - get repeatU(): number + public get repeatU(): number { if (this._repeatU === undefined && this.defaultTexture !== null) { @@ -69,12 +69,12 @@ export class TextureEntryFace return this._repeatU; } - set repeatU(value: number) + public set repeatU(value: number) { this._repeatU = value; } - get repeatV(): number + public get repeatV(): number { if (this._repeatV === undefined && this.defaultTexture !== null) { @@ -83,12 +83,12 @@ export class TextureEntryFace return this._repeatV; } - set repeatV(value: number) + public set repeatV(value: number) { this._repeatV = value; } - get offsetU(): number + public get offsetU(): number { if (this._offsetU === undefined && this.defaultTexture !== null) { @@ -97,12 +97,12 @@ export class TextureEntryFace return this._offsetU; } - set offsetU(value: number) + public set offsetU(value: number) { this._offsetU = value; } - get offsetV(): number + public get offsetV(): number { if (this._offsetV === undefined && this.defaultTexture !== null) { @@ -111,12 +111,12 @@ export class TextureEntryFace return this._offsetV; } - set offsetV(value: number) + public set offsetV(value: number) { this._offsetV = value; } - get rotation(): number + public get rotation(): number { if (this._rotation === undefined && this.defaultTexture !== null) { @@ -125,12 +125,12 @@ export class TextureEntryFace return this._rotation; } - set rotation(value: number) + public set rotation(value: number) { this._rotation = value; } - get glow(): number + public get glow(): number { if (this._glow === undefined && this.defaultTexture !== null) { @@ -139,12 +139,12 @@ export class TextureEntryFace return this._glow; } - set glow(value: number) + public set glow(value: number) { this._glow = value; } - get textureID(): UUID + public get textureID(): UUID { if (this._textureID === undefined && this.defaultTexture !== null) { @@ -153,12 +153,12 @@ export class TextureEntryFace return this._textureID; } - set textureID(value: UUID) + public set textureID(value: UUID) { this._textureID = value; } - get materialID(): UUID + public get materialID(): UUID { if (this._materialID === undefined && this.defaultTexture !== null) { @@ -167,12 +167,12 @@ export class TextureEntryFace return this._materialID; } - set materialID(value: UUID) + public set materialID(value: UUID) { this._materialID = value; } - get material(): number + public get material(): number { if (this._material === undefined && this.defaultTexture !== null) { @@ -181,7 +181,7 @@ export class TextureEntryFace return this._material; } - set material(material: number) + public set material(material: number) { this._material = material; if ((this.hasAttribute & TextureFlags.Material) !== 0) @@ -198,7 +198,7 @@ export class TextureEntryFace } } - get media(): number + public get media(): number { if (this._media === undefined && this.defaultTexture !== null) { @@ -207,7 +207,7 @@ export class TextureEntryFace return this._media; } - set media(media: number) + public set media(media: number) { this._media = media; if ((this.hasAttribute & TextureFlags.Media) !== 0) @@ -226,7 +226,7 @@ export class TextureEntryFace } } - get mappingType(): number + public get mappingType(): number { if (this._mappingType === undefined && this.defaultTexture !== null) { @@ -235,12 +235,12 @@ export class TextureEntryFace return this._mappingType; } - set mappingType(value: number) + public set mappingType(value: number) { this._mappingType = value; } - get mediaFlags(): boolean + public get mediaFlags(): boolean { if (this._mediaFlags === undefined && this.defaultTexture !== null) { @@ -249,7 +249,7 @@ export class TextureEntryFace return this._mediaFlags; } - set mediaFlags(value: boolean) + public set mediaFlags(value: boolean) { this._mediaFlags = value; } diff --git a/lib/classes/TimeoutError.ts b/lib/classes/TimeoutError.ts index 93a0975..2566dac 100644 --- a/lib/classes/TimeoutError.ts +++ b/lib/classes/TimeoutError.ts @@ -1,5 +1,5 @@ export class TimeoutError extends Error { - timeout: true; - waitingForMessage: number; + public timeout: true; + public waitingForMessage: number; } diff --git a/lib/classes/UUID.ts b/lib/classes/UUID.ts index 560508a..32aae9d 100644 --- a/lib/classes/UUID.ts +++ b/lib/classes/UUID.ts @@ -1,12 +1,41 @@ import validator from 'validator'; import * as Long from 'long'; -import { XMLNode } from 'xmlbuilder'; +import type { XMLNode } from 'xmlbuilder'; import * as uuid from 'uuid'; export class UUID { private mUUID = '00000000-0000-0000-0000-000000000000'; + public constructor(buf?: Buffer | string, pos?: number) + { + if (buf !== undefined) + { + if (typeof buf === 'string') + { + this.setUUID(buf); + } + else if (pos !== undefined) + { + const uuidBuf: Buffer = buf.subarray(pos, pos + 16); + const hexString = uuidBuf.toString('hex'); + this.setUUID(hexString.substring(0, 8) + '-' + + hexString.substring(8, 12) + '-' + + hexString.substring(12, 16) + '-' + + hexString.substring(16, 20) + '-' + + hexString.substring(20, 32)); + } + else if (typeof buf === 'object' && buf.toString !== undefined) + { + this.setUUID(buf.toString()); + } + else + { + console.error('Can\'t accept UUIDs of type ' + typeof buf); + } + } + } + public static zero(): UUID { return new UUID(); @@ -55,9 +84,9 @@ export class UUID } if (typeof obj[param] === 'object') { - if (obj[param]['UUID'] !== undefined && Array.isArray(obj[param]['UUID']) && obj[param]['UUID'].length > 0) + if (obj[param].UUID !== undefined && Array.isArray(obj[param].UUID) && obj[param].UUID.length > 0) { - const u = obj[param]['UUID'][0]; + const u = obj[param].UUID[0]; if (typeof u === 'string') { if (validator.isUUID(u)) @@ -73,35 +102,6 @@ export class UUID return false; } - public constructor(buf?: Buffer | string, pos?: number) - { - if (buf !== undefined) - { - if (typeof buf === 'string') - { - this.setUUID(buf); - } - else if (pos !== undefined) - { - const uuidBuf: Buffer = buf.slice(pos, pos + 16); - const hexString = uuidBuf.toString('hex'); - this.setUUID(hexString.substring(0, 8) + '-' - + hexString.substring(8, 12) + '-' - + hexString.substring(12, 16) + '-' - + hexString.substring(16, 20) + '-' - + hexString.substring(20, 32)); - } - else if (typeof buf === 'object' && buf.toString !== undefined) - { - this.setUUID(buf.toString()); - } - else - { - console.error('Can\'t accept UUIDs of type ' + typeof buf); - } - } - } - public setUUID(val: string): boolean { const test = val.trim(); diff --git a/lib/classes/Utils.ts b/lib/classes/Utils.ts index ba215d4..c708369 100644 --- a/lib/classes/Utils.ts +++ b/lib/classes/Utils.ts @@ -1,14 +1,10 @@ import * as Long from 'long'; -import { Subject, Subscription } from 'rxjs'; +import type { Subscription } from 'rxjs'; +import { Subject } from 'rxjs'; import * as xml2js from 'xml2js'; - import * as zlib from 'zlib'; -import { AssetType } from '../enums/AssetType'; import { FilterResponse } from '../enums/FilterResponse'; -import { HTTPAssets } from '../enums/HTTPAssets'; -import { InventoryType } from '../enums/InventoryType'; -import { Logger } from './Logger'; -import { GlobalPosition } from './public/interfaces/GlobalPosition'; +import type { GlobalPosition } from './public/interfaces/GlobalPosition'; import { Quaternion } from './Quaternion'; import { Vector3 } from './Vector3'; import * as crypto from 'crypto'; @@ -16,30 +12,32 @@ import Timeout = NodeJS.Timeout; export class Utils { - static TWO_PI = 6.283185307179586476925286766559; - static CUT_QUANTA = 0.00002; - static SCALE_QUANTA = 0.01; - static SHEAR_QUANTA = 0.01; - static TAPER_QUANTA = 0.01; - static REV_QUANTA = 0.015; - static HOLLOW_QUANTA = 0.00002; + public static TWO_PI = 6.283185307179586; + public static CUT_QUANTA = 0.00002; + public static SCALE_QUANTA = 0.01; + public static SHEAR_QUANTA = 0.01; + public static TAPER_QUANTA = 0.01; + public static REV_QUANTA = 0.015; + public static HOLLOW_QUANTA = 0.00002; - static StringToBuffer(str: string): Buffer + public static StringToBuffer(str: string): Buffer { return Buffer.from(str + '\0', 'utf8'); } - static SHA1String(str: string): string + public static SHA1String(str: string): string { return crypto.createHash('sha1').update(str).digest('hex'); } - static MD5String(str: string): string + public static MD5String(input: Buffer | string): string { - return crypto.createHash('md5').update(str).digest('hex'); + const hash = crypto.createHash('md5'); + hash.update(input); + return hash.digest('hex'); } - static BufferToStringSimple(buf: Buffer): string + public static BufferToStringSimple(buf: Buffer): string { if (buf.length === 0) { @@ -47,7 +45,7 @@ export class Utils } if (buf[buf.length - 1] === 0) { - return buf.slice(0, buf.length - 1).toString('utf8'); + return buf.subarray(0, buf.length - 1).toString('utf8'); } else { @@ -55,14 +53,14 @@ export class Utils } } - static Clamp(value: number, min: number, max: number): number + public static Clamp(value: number, min: number, max: number): number { value = (value > max) ? max : value; value = (value < min) ? min : value; return value; } - static fillArray(value: T, count: number): T[] + public static fillArray(value: T, count: number): T[] { const arr: T[] = new Array(count); while (count--) @@ -72,20 +70,20 @@ export class Utils return arr; } - static JSONStringify(obj: object, space: number): string + public static JSONStringify(obj: object, space: number): string { const cache: any[] = []; return JSON.stringify(obj, function(_: string, value): unknown { if (typeof value === 'object' && value !== null) { - if (cache.indexOf(value) !== -1) + if (cache.includes(value)) { try { return JSON.parse(JSON.stringify(value)); } - catch (error) + catch (_error: unknown) { return 'Circular Reference'; } @@ -96,7 +94,7 @@ export class Utils }, space); } - static BufferToString(buf: Buffer, startPos?: number): + public static BufferToString(buf: Buffer, startPos?: number): { readLength: number, result: string @@ -130,11 +128,11 @@ export class Utils } return { readLength: (foundNull - startPos) + 1, - result: buf.slice(startPos, foundNull).toString('utf8') + result: buf.subarray(startPos, foundNull).toString('utf8') } } - static RegionCoordinatesToHandle(regionX: number, regionY: number): GlobalPosition + public static RegionCoordinatesToHandle(regionX: number, regionY: number): GlobalPosition { const realRegionX = Math.floor(regionX / 256) * 256; const realRegionY = Math.floor(regionY / 256) * 256; @@ -150,266 +148,7 @@ export class Utils }; } - static InventoryTypeToLLInventoryType(type: InventoryType): string - { - switch (type) - { - case InventoryType.Texture: - return 'texture'; - case InventoryType.Sound: - return 'sound'; - case InventoryType.CallingCard: - return 'callcard'; - case InventoryType.Landmark: - return 'landmark'; - case InventoryType.Object: - return 'object'; - case InventoryType.Notecard: - return 'notecard'; - case InventoryType.Category: - return 'category'; - case InventoryType.RootCategory: - return 'root'; - case InventoryType.Script: - return 'script'; - case InventoryType.Snapshot: - return 'snapshot'; - case InventoryType.Attachment: - return 'attach'; - case InventoryType.Bodypart: - return 'bodypart'; - case InventoryType.Wearable: - return 'wearable'; - case InventoryType.Animation: - return 'animation'; - case InventoryType.Gesture: - return 'gesture'; - case InventoryType.Mesh: - return 'mesh'; - case InventoryType.LSL: - return 'script'; - case InventoryType.Widget: - return 'widget'; - case InventoryType.Person: - return 'person'; - case InventoryType.Settings: - return 'settings'; - case InventoryType.Material: - return 'material'; - default: - console.error('Unknown inventory type: ' + InventoryType[type]); - return 'texture'; - } - } - - static HTTPAssetTypeToAssetType(HTTPAssetType: string): AssetType - { - switch (HTTPAssetType) - { - case HTTPAssets.ASSET_TEXTURE: - return AssetType.Texture; - case HTTPAssets.ASSET_SOUND: - return AssetType.Sound; - case HTTPAssets.ASSET_ANIMATION: - return AssetType.Animation; - case HTTPAssets.ASSET_GESTURE: - return AssetType.Gesture; - case HTTPAssets.ASSET_LANDMARK: - return AssetType.Landmark; - case HTTPAssets.ASSET_CALLINGCARD: - return AssetType.CallingCard; - case HTTPAssets.ASSET_SCRIPT: - return AssetType.Script; - case HTTPAssets.ASSET_CLOTHING: - return AssetType.Clothing; - case HTTPAssets.ASSET_OBJECT: - return AssetType.Object; - case HTTPAssets.ASSET_NOTECARD: - return AssetType.Notecard; - case HTTPAssets.ASSET_LSL_TEXT: - return AssetType.LSLText; - case HTTPAssets.ASSET_LSL_BYTECODE: - return AssetType.LSLBytecode; - case HTTPAssets.ASSET_BODYPART: - return AssetType.Bodypart; - case HTTPAssets.ASSET_MESH: - return AssetType.Mesh; - case HTTPAssets.ASSET_SETTINGS: - return AssetType.Settings; - case HTTPAssets.ASSET_WIDGET: - return AssetType.Widget; - case HTTPAssets.ASSET_PERSON: - return AssetType.Person; - case HTTPAssets.ASSET_MATERIAL: - return AssetType.Material; - default: - return 0; - } - } - - static AssetTypeToHTTPAssetType(assetType: AssetType): HTTPAssets - { - switch (assetType) - { - case AssetType.Texture: - return HTTPAssets.ASSET_TEXTURE; - case AssetType.Sound: - return HTTPAssets.ASSET_SOUND; - case AssetType.Animation: - return HTTPAssets.ASSET_ANIMATION; - case AssetType.Gesture: - return HTTPAssets.ASSET_GESTURE; - case AssetType.Landmark: - return HTTPAssets.ASSET_LANDMARK; - case AssetType.CallingCard: - return HTTPAssets.ASSET_CALLINGCARD; - case AssetType.Script: - return HTTPAssets.ASSET_SCRIPT; - case AssetType.Clothing: - return HTTPAssets.ASSET_CLOTHING; - case AssetType.Object: - return HTTPAssets.ASSET_OBJECT; - case AssetType.Notecard: - return HTTPAssets.ASSET_NOTECARD; - case AssetType.LSLText: - return HTTPAssets.ASSET_LSL_TEXT; - case AssetType.LSLBytecode: - return HTTPAssets.ASSET_LSL_BYTECODE; - case AssetType.Bodypart: - return HTTPAssets.ASSET_BODYPART; - case AssetType.Mesh: - return HTTPAssets.ASSET_MESH; - case AssetType.Settings: - return HTTPAssets.ASSET_SETTINGS; - case AssetType.Person: - return HTTPAssets.ASSET_PERSON; - case AssetType.Widget: - return HTTPAssets.ASSET_WIDGET; - case AssetType.Material: - return HTTPAssets.ASSET_MATERIAL; - default: - return HTTPAssets.ASSET_TEXTURE; - } - - } - - static HTTPAssetTypeToInventoryType(HTTPAssetType: string): InventoryType - { - switch (HTTPAssetType) - { - case HTTPAssets.ASSET_TEXTURE: - return InventoryType.Texture; - case HTTPAssets.ASSET_SOUND: - return InventoryType.Sound; - case HTTPAssets.ASSET_ANIMATION: - return InventoryType.Animation; - case HTTPAssets.ASSET_GESTURE: - return InventoryType.Gesture; - case HTTPAssets.ASSET_LANDMARK: - return InventoryType.Landmark; - case HTTPAssets.ASSET_CALLINGCARD: - return InventoryType.CallingCard; - case HTTPAssets.ASSET_SCRIPT: - return InventoryType.LSL; - case HTTPAssets.ASSET_CLOTHING: - return InventoryType.Wearable; - case HTTPAssets.ASSET_OBJECT: - return InventoryType.Object; - case HTTPAssets.ASSET_NOTECARD: - return InventoryType.Notecard; - case HTTPAssets.ASSET_LSL_TEXT: - return InventoryType.LSL; - case HTTPAssets.ASSET_LSL_BYTECODE: - return InventoryType.LSL; - case HTTPAssets.ASSET_BODYPART: - return InventoryType.Wearable; - case HTTPAssets.ASSET_MESH: - return InventoryType.Mesh; - case HTTPAssets.ASSET_MATERIAL: - return InventoryType.Material; - default: - return 0; - } - } - static capInventoryTypeToAssetType(capInventoryType: string): AssetType - { - switch (capInventoryType) - { - case 'texture': - return AssetType.Texture; - case 'sound': - return AssetType.Sound; - case 'animation': - return AssetType.Animation; - case 'gesture': - return AssetType.Gesture; - case 'landmark': - return AssetType.Landmark; - case 'callcard': - return AssetType.CallingCard; - case 'script': - return AssetType.LSLText; - case 'wearable': - return AssetType.Bodypart; - case 'object': - return AssetType.Object; - case 'notecard': - return AssetType.Notecard; - case 'category': - return AssetType.Category; - case 'mesh': - return AssetType.Mesh; - case 'settings': - return AssetType.Settings; - case 'material': - return AssetType.Material; - default: - console.error('Unrecognised cap inventory type: ' + capInventoryType); - return AssetType.Unknown - } - } - - - static HTTPAssetTypeToCapInventoryType(HTTPAssetType: string): String - { - switch (HTTPAssetType) - { - case HTTPAssets.ASSET_TEXTURE: - return 'texture'; - case HTTPAssets.ASSET_SOUND: - return 'sound'; - case HTTPAssets.ASSET_ANIMATION: - return 'animation'; - case HTTPAssets.ASSET_GESTURE: - return 'gesture'; - case HTTPAssets.ASSET_LANDMARK: - return 'landmark'; - case HTTPAssets.ASSET_CALLINGCARD: - return 'callcard'; - case HTTPAssets.ASSET_SCRIPT: - return 'script'; - case HTTPAssets.ASSET_CLOTHING: - return 'wearable'; - case HTTPAssets.ASSET_OBJECT: - return 'object'; - case HTTPAssets.ASSET_NOTECARD: - return 'notecard'; - case HTTPAssets.ASSET_CATEGORY: - return 'category'; - case HTTPAssets.ASSET_LSL_TEXT: - return 'script'; - case HTTPAssets.ASSET_LSL_BYTECODE: - return 'script'; - case HTTPAssets.ASSET_BODYPART: - return 'wearable'; - case HTTPAssets.ASSET_MESH: - return 'mesh'; - default: - return ''; - } - } - - static FloatToByte(val: number, lower: number, upper: number): number + public static FloatToByte(val: number, lower: number, upper: number): number { val = Utils.Clamp(val, lower, upper); val -= lower; @@ -417,7 +156,7 @@ export class Utils return Math.round(val * 255); } - static ByteToFloat(byte: number, lower: number, upper: number): number + public static ByteToFloat(byte: number, lower: number, upper: number): number { const ONE_OVER_BYTEMAX: number = 1.0 / 255; @@ -434,7 +173,7 @@ export class Utils return fval; } - static UInt16ToFloat(val: number, lower: number, upper: number): number + public static UInt16ToFloat(val: number, lower: number, upper: number, correctError = true): number { const ONE_OVER_U16_MAX = 1.0 / 65535; let fval = val * ONE_OVER_U16_MAX; @@ -442,27 +181,40 @@ export class Utils fval *= delta; fval += lower; - const maxError = delta * ONE_OVER_U16_MAX; - if (Math.abs(fval) < maxError) + if (correctError) { - fval = 0.0; + const maxError = delta * ONE_OVER_U16_MAX; + if (Math.abs(fval) < maxError) + { + fval = 0.0; + } } return fval; } - static Base64EncodeString(str: string): string + public static FloatToUInt16(val: number, lower: number, upper: number): number + { + const U16_MAX = 65535; + const delta = upper - lower; + + let normalized = (val - lower) / delta; + normalized = Math.max(0, Math.min(1, normalized)); + return Math.round(normalized * U16_MAX); + } + + public static Base64EncodeString(str: string): string { const buff = Buffer.from(str, 'utf8'); return buff.toString('base64'); } - static Base64DecodeString(str: string): string + public static Base64DecodeString(str: string): string { const buff = Buffer.from(str, 'base64'); return buff.toString('utf8'); } - static HexToLong(hex: string): Long + public static HexToLong(hex: string): Long { while (hex.length < 16) { @@ -471,30 +223,30 @@ export class Utils return new Long(parseInt(hex.substring(8), 16), parseInt(hex.substring(0, 8), 16)); } - static ReadRotationFloat(buf: Buffer, pos: number): number + public static ReadRotationFloat(buf: Buffer, pos: number): number { return ((buf[pos] | (buf[pos + 1] << 8)) / 32768.0) * Utils.TWO_PI; } - static ReadGlowFloat(buf: Buffer, pos: number): number + public static ReadGlowFloat(buf: Buffer, pos: number): number { return buf[pos] / 255; } - static ReadOffsetFloat(buf: Buffer, pos: number): number + public static ReadOffsetFloat(buf: Buffer, pos: number): number { const offset = buf.readInt16LE(pos); return offset / 32767.0; } - static TEOffsetShort(num: number): number + public static TEOffsetShort(num: number): number { num = Utils.Clamp(num, -1.0, 1.0); num *= 32767.0; return Math.round(num); } - static IEEERemainder(x: number, y: number): number + public static IEEERemainder(x: number, y: number): number { if (isNaN(x)) { @@ -540,12 +292,12 @@ export class Utils } } - static TERotationShort(rotation: number): number + public static TERotationShort(rotation: number): number { return Math.floor(((Utils.IEEERemainder(rotation, Utils.TWO_PI) / Utils.TWO_PI) * 32768.0) + 0.5); } - static OctetsToUInt32BE(octets: number[]): number + public static OctetsToUInt32BE(octets: number[]): number { const buf = Buffer.allocUnsafe(4); let pos = 0; @@ -563,7 +315,7 @@ export class Utils return buf.readUInt32BE(0); } - static OctetsToUInt32LE(octets: number[]): number + public static OctetsToUInt32LE(octets: number[]): number { const buf = Buffer.allocUnsafe(4); let pos = 0; @@ -581,7 +333,7 @@ export class Utils return buf.readUInt32LE(0); } - static numberToFixedHex(num: number): string + public static numberToFixedHex(num: number): string { let str = num.toString(16); while (str.length < 8) @@ -591,33 +343,33 @@ export class Utils return str; } - static TEGlowByte(glow: number): number + public static TEGlowByte(glow: number): number { return (glow * 255.0); } - static NumberToByteBuffer(num: number): Buffer + public static NumberToByteBuffer(num: number): Buffer { const buf = Buffer.allocUnsafe(1); buf.writeUInt8(num, 0); return buf; } - static NumberToShortBuffer(num: number): Buffer + public static NumberToShortBuffer(num: number): Buffer { const buf = Buffer.allocUnsafe(2); buf.writeInt16LE(num, 0); return buf; } - static NumberToFloatBuffer(num: number): Buffer + public static NumberToFloatBuffer(num: number): Buffer { const buf = Buffer.allocUnsafe(4); buf.writeFloatLE(num, 0); return buf; } - static numberOrZero(num: number | undefined): number + public static numberOrZero(num: number | undefined): number { if (num === undefined) { @@ -626,7 +378,7 @@ export class Utils return num; } - static vector3OrZero(vec: Vector3 | undefined): Vector3 + public static vector3OrZero(vec: Vector3 | undefined): Vector3 { if (vec === undefined) { @@ -635,7 +387,7 @@ export class Utils return vec; } - static quaternionOrZero(quat: Quaternion | undefined): Quaternion + public static quaternionOrZero(quat: Quaternion | undefined): Quaternion { if (quat === undefined) { @@ -644,87 +396,87 @@ export class Utils return quat; } - static packBeginCut(beginCut: number): number + public static packBeginCut(beginCut: number): number { return Math.round(beginCut / Utils.CUT_QUANTA); } - static packEndCut(endCut: number): number + public static packEndCut(endCut: number): number { return (50000 - Math.round(endCut / Utils.CUT_QUANTA)); } - static packPathScale(pathScale: number): number + public static packPathScale(pathScale: number): number { return (200 - Math.round(pathScale / Utils.SCALE_QUANTA)); } - static packPathShear(pathShear: number): number + public static packPathShear(pathShear: number): number { return Math.round(pathShear / Utils.SHEAR_QUANTA); } - static packPathTwist(pathTwist: number): number + public static packPathTwist(pathTwist: number): number { return Math.round(pathTwist / Utils.SCALE_QUANTA); } - static packPathTaper(pathTaper: number): number + public static packPathTaper(pathTaper: number): number { return Math.round(pathTaper / Utils.TAPER_QUANTA); } - static packPathRevolutions(pathRevolutions: number): number + public static packPathRevolutions(pathRevolutions: number): number { return Math.round((pathRevolutions - 1) / Utils.REV_QUANTA); } - static packProfileHollow(profileHollow: number): number + public static packProfileHollow(profileHollow: number): number { return Math.round(profileHollow / Utils.HOLLOW_QUANTA); } - static unpackBeginCut(beginCut: number): number + public static unpackBeginCut(beginCut: number): number { return beginCut * Utils.CUT_QUANTA; } - static unpackEndCut(endCut: number): number + public static unpackEndCut(endCut: number): number { return (50000 - endCut) * Utils.CUT_QUANTA; } - static unpackPathScale(pathScale: number): number + public static unpackPathScale(pathScale: number): number { return (200 - pathScale) * Utils.SCALE_QUANTA; } - static unpackPathShear(pathShear: number): number + public static unpackPathShear(pathShear: number): number { return pathShear * Utils.SHEAR_QUANTA; } - static unpackPathTwist(pathTwist: number): number + public static unpackPathTwist(pathTwist: number): number { return pathTwist * Utils.SCALE_QUANTA; } - static unpackPathTaper(pathTaper: number): number + public static unpackPathTaper(pathTaper: number): number { return pathTaper * Utils.TAPER_QUANTA; } - static unpackPathRevolutions(pathRevolutions: number): number + public static unpackPathRevolutions(pathRevolutions: number): number { return pathRevolutions * Utils.REV_QUANTA + 1; } - static unpackProfileHollow(profileHollow: number): number + public static unpackProfileHollow(profileHollow: number): number { return profileHollow * Utils.HOLLOW_QUANTA; } - static nullTerminatedString(str: string): string + public static nullTerminatedString(str: string): string { const index = str.indexOf('\0'); if (index === -1) @@ -737,101 +489,97 @@ export class Utils } } - static promiseConcurrent(promises: (() => Promise)[], concurrency: number, timeout: number): Promise<{ results: T[], errors: Error[] }> + public static async promiseConcurrent(promises: (() => Promise)[], concurrency: number, timeout: number): Promise<{ results: T[], errors: unknown[] }> { - return new Promise<{ results: T[], errors: Error[] }>(async(resolve) => + const originalConcurrency = concurrency; + const promiseQueue: (() => Promise)[] = []; + for (const promise of promises) { - const originalConcurrency = concurrency; - const promiseQueue: (() => Promise)[] = []; - Logger.Info('PromiseConcurrent: ' + promiseQueue.length + ' in queue. Concurrency: ' + concurrency); - for (const promise of promises) - { - promiseQueue.push(promise); - } - const slotAvailable: Subject = new Subject(); - const errors: Error[] = []; - const results: T[] = []; + promiseQueue.push(promise); + } + const slotAvailable: Subject = new Subject(); + const errors: unknown[] = []; + const results: T[] = []; - function waitForAvailable(): Promise + async function waitForAvailable(): Promise + { + return new Promise((resolve1) => { - return new Promise((resolve1) => + const subs = slotAvailable.subscribe(() => { - const subs = slotAvailable.subscribe(() => - { - subs.unsubscribe(); - resolve1(); - }); + subs.unsubscribe(); + resolve1(); }); - } + }); + } - function runPromise(promise: () => Promise): void + function runPromise(promise: () => Promise): void + { + concurrency--; + let timedOut = false; + let timeo: Timeout | undefined = undefined; + promise().then((result: T) => { - concurrency--; - let timedOut = false; - let timeo: Timeout | undefined = undefined; - promise().then((result: T) => + if (timedOut) { - if (timedOut) - { - return; - } - if (timeo !== undefined) - { - clearTimeout(timeo); - } - results.push(result); + return; + } + if (timeo !== undefined) + { + clearTimeout(timeo); + } + results.push(result); + concurrency++; + slotAvailable.next(); + }).catch((err: unknown) => + { + if (timedOut) + { + return; + } + if (timeo !== undefined) + { + clearTimeout(timeo); + } + errors.push(err); + concurrency++; + slotAvailable.next(); + }); + if (timeout > 0) + { + timeo = setTimeout(() => + { + timedOut = true; + errors.push(new Error('Promise timed out')); concurrency++; slotAvailable.next(); - }).catch((err) => - { - if (timedOut) - { - return; - } - if (timeo !== undefined) - { - clearTimeout(timeo); - } - errors.push(err); - concurrency++; - slotAvailable.next(); - }); - if (timeout > 0) - { - timeo = setTimeout(() => - { - timedOut = true; - errors.push(new Error('Promise timed out')); - concurrency++; - slotAvailable.next(); - }, timeout); - } + }, timeout); } + } - while (promiseQueue.length > 0) - { - if (concurrency < 1) - { - await waitForAvailable(); - } - else - { - const thunk = promiseQueue.shift(); - if (thunk !== undefined) - { - runPromise(thunk); - } - } - } - while (concurrency < originalConcurrency) + while (promiseQueue.length > 0) + { + if (concurrency < 1) { await waitForAvailable(); } - resolve({ results: results, errors: errors }); - }); + else + { + const thunk = promiseQueue.shift(); + if (thunk !== undefined) + { + runPromise(thunk); + } + } + } + while (concurrency < originalConcurrency) + { + await waitForAvailable(); + } + return ({ results: results, errors: errors }); } - static waitFor(timeout: number): Promise + public static async waitFor(timeout: number): Promise { return new Promise((resolve) => { @@ -842,13 +590,13 @@ export class Utils }) } - static getFromXMLJS(obj: any, param: string): any + public static getFromXMLJS(obj: any, param: string): any { if (obj[param] === undefined) { return undefined; } - let retParam; + let retParam: any = ''; if (Array.isArray(obj[param])) { retParam = obj[param][0]; @@ -875,7 +623,7 @@ export class Utils } return retParam; } - static inflate(buf: Buffer): Promise + public static async inflate(buf: Buffer): Promise { return new Promise((resolve, reject) => { @@ -892,7 +640,7 @@ export class Utils }) }); } - static deflate(buf: Buffer): Promise + public static async deflate(buf: Buffer): Promise { return new Promise((resolve, reject) => { @@ -909,7 +657,7 @@ export class Utils }) }); } - static waitOrTimeOut(subject: Subject, timeout?: number, callback?: (msg: T) => FilterResponse): Promise + public static async waitOrTimeOut(subject: Subject, timeout?: number, callback?: (msg: T) => FilterResponse): Promise { return new Promise((resolve, reject) => { @@ -957,7 +705,7 @@ export class Utils }) } - static parseLine(line: string): { + public static parseLine(line: string): { 'key': string | null, 'value': string } @@ -999,12 +747,12 @@ export class Utils } } - static sanitizePath(input: string): string + public static sanitizePath(input: string): string { return input.replace(/[^a-z0-9]/gi, '').replace(/ /gi, '_'); } - static parseXML(input: string): Promise + public static async parseXML(input: string): Promise { return new Promise((resolve, reject) => { @@ -1033,7 +781,7 @@ export class Utils return line.replace(/\r/, '').trim().replace(/[\t ]+/g, ' '); } - public static sleep(ms: number): Promise + public static async sleep(ms: number): Promise { return new Promise((resolve) => { diff --git a/lib/classes/Vector2.ts b/lib/classes/Vector2.ts index 56b5552..b8709a6 100644 --- a/lib/classes/Vector2.ts +++ b/lib/classes/Vector2.ts @@ -1,14 +1,58 @@ -import { TSMVec2 } from '../tsm/vec2'; -import { XMLNode } from 'xmlbuilder'; +import type { XMLNode } from 'xmlbuilder'; -export class Vector2 extends TSMVec2 +export class Vector2 { - static getZero(): Vector2 + public x: number; + public y: number; + + public constructor(buf?: Buffer | number[] | Vector2 | number, pos?: number, double?: boolean | number) { - return new Vector2(); + if (typeof buf === 'number' && typeof pos === 'number') + { + this.x = buf; + this.y = pos; + } + else if (buf instanceof Vector2) + { + this.x = buf.x; + this.y = buf.y; + } + else + { + if (double === undefined) + { + double = false; + } + if (buf !== undefined && pos !== undefined && buf instanceof Buffer) + { + if (double === false) + { + this.x = buf.readFloatLE(pos); + this.y = buf.readFloatLE(pos + 4); + } + else + { + this.x = buf.readDoubleLE(pos); + this.y = buf.readDoubleLE(pos + 8); + } + } + else if (buf !== undefined && Array.isArray(buf) && buf.length >= 2) + { + if (typeof buf[0] !== 'number' || typeof buf[1] !== 'number') + { + throw new Error('Array contains non-numbers'); + } + [this.x, this.y] = buf; + } + else + { + this.x = 0; + this.y = 0; + } + } } - static getXML(doc: XMLNode, v?: Vector2): void + public static getXML(doc: XMLNode, v?: Vector2): void { if (v === undefined) { @@ -18,38 +62,12 @@ export class Vector2 extends TSMVec2 doc.ele('Y', v.y); } - constructor(buf?: Buffer | number[], pos?: number, double?: boolean) + public static getZero(): Vector2 { - if (double === undefined) - { - double = false; - } - if (buf !== undefined && pos !== undefined && buf instanceof Buffer) - { - if (!double) - { - const x = buf.readFloatLE(pos); - const y = buf.readFloatLE(pos + 4); - super([x, y]); - } - else - { - const x = buf.readDoubleLE(pos); - const y = buf.readDoubleLE(pos + 8); - super([x, y]); - } - } - else if (buf !== undefined && Array.isArray(buf)) - { - super(buf); - } - else - { - super(); - } + return new Vector2(0, 0); } - writeToBuffer(buf: Buffer, pos: number, double: boolean): void + public writeToBuffer(buf: Buffer, pos: number, double = false): void { if (double) { @@ -62,13 +80,235 @@ export class Vector2 extends TSMVec2 buf.writeFloatLE(this.y, pos + 4); } } - toString(): string + + public toString(): string { - return '<' + this.x + ', ' + this.y + '>'; + return `<${this.x}, ${this.y}>`; } - toArray(): number[] + public getBuffer(double = false): Buffer + { + const buf = Buffer.allocUnsafe(double ? 16 : 8); + this.writeToBuffer(buf, 0, double); + return buf; + } + + public compareApprox(vec: Vector2): boolean + { + return this.equals(vec, 0.00001); + } + + public toArray(): number[] { return [this.x, this.y]; } + + public equals(vec: Vector2, epsilon = Number.EPSILON): boolean + { + return ( + Math.abs(this.x - vec.x) < epsilon && + Math.abs(this.y - vec.y) < epsilon + ); + } + + public dot(vec: Vector2): number + { + return this.x * vec.x + this.y * vec.y; + } + + public cross(vec: Vector2): number + { + // In 2D, the cross product is a scalar representing the magnitude of the perpendicular vector + return this.x * vec.y - this.y * vec.x; + } + + public distance(vec: Vector2): number + { + return Math.sqrt(this.squaredDistance(vec)); + } + + public squaredDistance(vec: Vector2): number + { + const dx = this.x - vec.x; + const dy = this.y - vec.y; + return dx * dx + dy * dy; + } + + public direction(vec: Vector2): Vector2 + { + const diff = vec.subtract(this).normalize(); + if (diff.x === 0.0 && diff.y === 0.0) + { + diff.x = NaN; + diff.y = NaN; + } + return diff; + } + + public mix(vec: Vector2, t: number): Vector2 + { + return new Vector2([ + this.x + t * (vec.x - this.x), + this.y + t * (vec.y - this.y), + ]); + } + + public sum(vec: Vector2): Vector2 + { + return new Vector2([ + this.x + vec.x, + this.y + vec.y, + ]); + } + + public difference(vec: Vector2): Vector2 + { + return new Vector2([ + this.x - vec.x, + this.y - vec.y, + ]); + } + + public product(vec: Vector2): Vector2 + { + return new Vector2([ + this.x * vec.x, + this.y * vec.y, + ]); + } + + public quotient(vec: Vector2): Vector2 + { + return new Vector2([ + this.x / vec.x, + this.y / vec.y, + ]); + } + + public reset(): void + { + this.x = 0; + this.y = 0; + } + + public copy(): Vector2 + { + return new Vector2(this); + } + + public negate(): Vector2 + { + return new Vector2([ + -this.x, + -this.y, + ]); + } + + public length(): number + { + return Math.sqrt(this.squaredLength()); + } + + public squaredLength(): number + { + return this.x * this.x + this.y * this.y; + } + + public add(vec: Vector2): Vector2 + { + return new Vector2([ + this.x + vec.x, + this.y + vec.y, + ]); + } + + public subtract(vec: Vector2): Vector2 + { + return new Vector2([ + this.x - vec.x, + this.y - vec.y, + ]); + } + + public multiply(value: number | Vector2): Vector2 + { + if (typeof value === 'number') + { + return new Vector2([ + this.x * value, + this.y * value, + ]); + } + else + { + return new Vector2([ + this.x * value.x, + this.y * value.y, + ]); + } + } + + public divide(value: number | Vector2): Vector2 + { + if (typeof value === 'number') + { + return new Vector2([ + this.x / value, + this.y / value, + ]); + } + else + { + return new Vector2([ + this.x / value.x, + this.y / value.y, + ]); + } + } + + public scale(scalar: number): Vector2 + { + return this.multiply(scalar); + } + + public normalize(): Vector2 + { + const len = this.length(); + if (len > 0) + { + return this.scale(1 / len); + } + else + { + return this.copy(); + } + } + + public angle(): number + { + // Returns the angle in radians between this vector and the positive x-axis + return Math.atan2(this.y, this.x); + } + + public rotate(angle: number): Vector2 + { + const cos = Math.cos(angle); + const sin = Math.sin(angle); + return new Vector2([ + this.x * cos - this.y * sin, + this.x * sin + this.y * cos, + ]); + } + + public perpendicular(): Vector2 + { + // Returns a vector perpendicular to this vector (rotated 90 degrees counter-clockwise) + return new Vector2([-this.y, this.x]); + } + + public projectOnto(vec: Vector2): Vector2 + { + const scalar = this.dot(vec) / vec.squaredLength(); + return vec.scale(scalar); + } } diff --git a/lib/classes/Vector3.ts b/lib/classes/Vector3.ts index dfd130d..bab18dd 100644 --- a/lib/classes/Vector3.ts +++ b/lib/classes/Vector3.ts @@ -1,14 +1,87 @@ -import { XMLNode } from 'xmlbuilder'; -import { TSMVec3 } from '../tsm/vec3'; +import { Quaternion } from "./Quaternion"; +import type { XMLNode } from 'xmlbuilder'; -export class Vector3 extends TSMVec3 +export class Vector3 { - static getZero(): Vector3 + public static zero = new Vector3(0, 0, 0); + + public static up = new Vector3(0, 1, 0); + public static right = new Vector3(1, 0, 0); + public static forward = new Vector3(0, 0, 1); + + public x: number; + public y: number; + public z: number; + + public constructor(buf?: Buffer | number[] | Vector3 | number, pos?: number, doubleOrX?: boolean | number) { - return new Vector3(); + if (typeof buf === 'number' && typeof pos === 'number' && typeof doubleOrX === 'number') + { + this.x = buf; + this.y = pos; + this.z = doubleOrX; + } + else if (buf instanceof Vector3) + { + this.x = buf.x; + this.y = buf.y; + this.z = buf.z; + } + else + { + if (doubleOrX === undefined) + { + doubleOrX = false; + } + if (buf instanceof Buffer) + { + if (pos === undefined) + { + pos = 0; + } + if (doubleOrX === false) + { + this.x = buf.readFloatLE(pos); + this.y = buf.readFloatLE(pos + 4); + this.z = buf.readFloatLE(pos + 8); + } + else + { + this.x = buf.readDoubleLE(pos); + this.y = buf.readDoubleLE(pos + 8); + this.z = buf.readDoubleLE(pos + 16); + } + } + else if (buf !== undefined && Array.isArray(buf) && buf.length > 2) + { + if (typeof buf[0] !== 'number' || typeof buf[1] !== 'number' || typeof buf[2] !== 'number') + { + throw new Error('Array contains non-numbers'); + } + [this.x, this.y, this.z] = buf; + } + else + { + this.x = 0.0; + this.y = 0.0; + this.z = 0.0; + } + } + if (isNaN(this.x)) + { + throw new Error('X component is NaN'); + } + if (isNaN(this.y)) + { + throw new Error('Y component is NaN'); + } + if (isNaN(this.z)) + { + throw new Error('Z component is NaN'); + } } - static getXML(doc: XMLNode, v?: Vector3): void + public static getXML(doc: XMLNode, v?: Vector3): void { if (v === undefined) { @@ -19,7 +92,7 @@ export class Vector3 extends TSMVec3 doc.ele('Z', v.z); } - static fromXMLJS(obj: any, param: string): Vector3 | false + public static fromXMLJS(obj: any, param: string): Vector3 | false { if (!obj[param]) { @@ -32,11 +105,11 @@ export class Vector3 extends TSMVec3 } if (typeof value === 'object') { - if (value['X'] !== undefined && value['Y'] !== undefined && value['Z'] !== undefined) + if (value.X !== undefined && value.Y !== undefined && value.Z !== undefined) { - let x = value['X']; - let y = value['Y']; - let z = value['Z']; + let x = value.X; + let y = value.Y; + let z = value.Z; if (Array.isArray(x) && x.length > 0) { x = x[0]; @@ -49,66 +122,21 @@ export class Vector3 extends TSMVec3 { z = z[0]; } - return new Vector3([x, y, z]); + return new Vector3([Number(x), Number(y), Number(z)]); } return false; } return false; } - constructor(buf?: Buffer | number[] | Vector3 | TSMVec3, pos?: number, double?: boolean) + public static getZero(): Vector3 { - if (buf instanceof Vector3) - { - super(); - this.x = buf.x; - this.y = buf.y; - this.z = buf.z; - } - else if (buf instanceof TSMVec3) - { - super(); - this.x = buf.x; - this.y = buf.y; - this.z = buf.z; - } - else - { - if (double === undefined) - { - double = false; - } - if (buf !== undefined && pos !== undefined && buf instanceof Buffer) - { - if (!double) - { - const x = buf.readFloatLE(pos); - const y = buf.readFloatLE(pos + 4); - const z = buf.readFloatLE(pos + 8); - super([x, y, z]); - } - else - { - const x = buf.readDoubleLE(pos); - const y = buf.readDoubleLE(pos + 8); - const z = buf.readDoubleLE(pos + 16); - super([x, y, z]); - } - } - else if (buf !== undefined && Array.isArray(buf)) - { - super(buf); - } - else - { - super(); - } - } + return new Vector3(); } - writeToBuffer(buf: Buffer, pos: number, double: boolean): void + public writeToBuffer(buf: Buffer, pos: number, double = false): void { - if (double) + if(double) { buf.writeDoubleLE(this.x, pos); buf.writeDoubleLE(this.y, pos + 8); @@ -121,24 +149,249 @@ export class Vector3 extends TSMVec3 buf.writeFloatLE(this.z, pos + 8); } } - toString(): string + + public toString(): string { return '<' + this.x + ', ' + this.y + ', ' + this.z + '>'; } - getBuffer(double: boolean = false): Buffer + + public getBuffer(double = false): Buffer { - const buf = Buffer.allocUnsafe((double) ? 24 : 12); + const buf = Buffer.allocUnsafe(double ? 24 : 12); this.writeToBuffer(buf, 0, double); return buf; } - compareApprox(vec: Vector3): boolean + public compareApprox(vec: Vector3): boolean { return vec.equals(this, 0.00001); } - toArray(): number[] + public toArray(): number[] { return [this.x, this.y, this.z]; } + + public equals(vec: Vector3, epsilon = Number.EPSILON): boolean + { + return ( + Math.abs(this.x - vec.x) < epsilon && + Math.abs(this.y - vec.y) < epsilon && + Math.abs(this.z - vec.z) < epsilon + ); + } + + public cross(vec: Vector3): Vector3 + { + return new Vector3([ + this.y * vec.z - this.z * vec.y, + this.z * vec.x - this.x * vec.z, + this.x * vec.y - this.y * vec.x, + ]); + } + + public dot(vec: Vector3): number + { + return this.x * vec.x + this.y * vec.y + this.z * vec.z; + } + + public distance(vec: Vector3): number + { + return Math.sqrt(this.squaredDistance(vec)); + } + + public squaredDistance(vec: Vector3): number + { + const dx = this.x - vec.x; + const dy = this.y - vec.y; + const dz = this.z - vec.z; + return dx * dx + dy * dy + dz * dz; + } + + public direction(vec: Vector3): Vector3 + { + const diff = vec.difference(this).normalize(); + if (diff.x == 0.0 && diff.y == 0.0 && diff.z == 0.0) + { + diff.x = NaN; + diff.y = NaN; + diff.z = NaN; + } + return diff; + } + + public toJSON(): object + { + return { + values: { + '0': this.x, + '1': this.y, + '2': this.z + } + } + } + + public mix(vec: Vector3, t: number): Vector3 + { + return new Vector3([ + this.x + t * (vec.x - this.x), + this.y + t * (vec.y - this.y), + this.z + t * (vec.z - this.z), + ]); + } + + public sum(vec: Vector3): Vector3 + { + return new Vector3([ + this.x + vec.x, + this.y + vec.y, + this.z + vec.z, + ]); + } + + public difference(vec: Vector3): Vector3 + { + return new Vector3([ + this.x - vec.x, + this.y - vec.y, + this.z - vec.z, + ]); + } + + public product(vec: Vector3): Vector3 + { + return new Vector3([ + this.x * vec.x, + this.y * vec.y, + this.z * vec.z, + ]); + } + + public quotient(vec: Vector3): Vector3 + { + return new Vector3([ + this.x / vec.x, + this.y / vec.y, + this.z / vec.z, + ]); + } + + public reset(): void + { + this.x = 0; + this.y = 0; + this.z = 0; + } + + public copy(): Vector3 + { + return new Vector3(this); + } + + public negate(): Vector3 + { + return new Vector3([ + -this.x, + -this.y, + -this.z, + ]); + } + + public length(): number + { + return Math.sqrt(this.squaredLength()); + } + + public squaredLength(): number + { + return this.x * this.x + this.y * this.y + this.z * this.z; + } + + public add(vec: Vector3): Vector3 + { + return new Vector3([ + this.x + vec.x, + this.y + vec.y, + this.z + vec.z, + ]); + } + + public subtract(vec: Vector3): Vector3 + { + return new Vector3([ + this.x - vec.x, + this.y - vec.y, + this.z - vec.z, + ]); + } + + public multiply(value: number | Vector3): Vector3 + { + if (typeof value === 'number') + { + return new Vector3([ + this.x * value, + this.y * value, + this.z * value, + ]); + } + else + { + return new Vector3([ + this.x * value.x, + this.y * value.y, + this.z * value.z, + ]); + } + } + + public divide(value: number | Vector3): Vector3 + { + if (typeof value === 'number') + { + return new Vector3([ + this.x / value, + this.y / value, + this.z / value, + ]); + } + else + { + return new Vector3([ + this.x / value.x, + this.y / value.y, + this.z / value.z, + ]); + } + } + + public scale(scalar: number): Vector3 + { + return this.multiply(scalar); + } + + public normalize(): Vector3 + { + const len = this.length(); + if (len > 0) + { + return this.scale(1 / len); + } + else + { + return this.copy(); + } + } + + public multiplyQuaternion(quat: Quaternion): Vector3 + { + const qVec = new Quaternion([this.x, this.y, this.z, 0]); + const result = quat.multiply(qVec).multiply(quat.conjugate()); + return new Vector3([result.x, result.y, result.z]); + } + + public toQuaternion(): Quaternion + { + return new Quaternion([this.x, this.y, this.z, 0]); + } } diff --git a/lib/classes/Vector4.ts b/lib/classes/Vector4.ts index 8a1dc02..2b1af64 100644 --- a/lib/classes/Vector4.ts +++ b/lib/classes/Vector4.ts @@ -1,14 +1,75 @@ -import { TSMVec4 } from '../tsm/vec4'; -import { XMLNode } from 'xmlbuilder'; +import { Quaternion } from "./Quaternion"; +import type { XMLNode } from 'xmlbuilder'; -export class Vector4 extends TSMVec4 +export class Vector4 { - static getZero(): Vector4 + public x: number; + public y: number; + public z: number; + public w: number; + + public constructor(buf?: Buffer | number[] | Vector4 | number, pos?: number, double?: boolean | number, w?: number) { - return new Vector4(); + if (typeof buf === 'number' && typeof pos === 'number' && typeof double === 'number' && typeof w === 'number') + { + this.x = buf; + this.y = pos; + this.z = double; + this.w = w; + } + else if (buf instanceof Vector4) + { + this.x = buf.x; + this.y = buf.y; + this.z = buf.z; + this.w = buf.w; + } + else + { + if (double === undefined) + { + double = false; + } + if (buf instanceof Buffer) + { + if (pos === undefined) + { + pos = 0; + } + if (double === true) + { + this.x = buf.readDoubleLE(pos); + this.y = buf.readDoubleLE(pos + 8); + this.z = buf.readDoubleLE(pos + 16); + this.w = buf.readDoubleLE(pos + 24); + } + else + { + this.x = buf.readFloatLE(pos); + this.y = buf.readFloatLE(pos + 4); + this.z = buf.readFloatLE(pos + 8); + this.w = buf.readFloatLE(pos + 12); + } + } + else if (buf !== undefined && Array.isArray(buf) && buf.length > 3) + { + if (typeof buf[0] !== 'number' || typeof buf[1] !== 'number' || typeof buf[2] !== 'number' || typeof buf[3] !== 'number') + { + throw new Error('Array contains non-numbers'); + } + [this.x, this.y, this.z, this.w] = buf; + } + else + { + this.x = 0; + this.y = 0; + this.z = 0; + this.w = 0; + } + } } - static getXML(doc: XMLNode, v?: Vector4): void + public static getXML(doc: XMLNode, v?: Vector4): void { if (v === undefined) { @@ -20,36 +81,254 @@ export class Vector4 extends TSMVec4 doc.ele('W', v.w); } - constructor(buf?: Buffer | number[], pos?: number) + + public static getZero(): Vector4 { - if (buf !== undefined && pos !== undefined && buf instanceof Buffer) + return new Vector4(); + } + + public writeToBuffer(buf: Buffer, pos: number, double = false): void + { + if (double) { - const x = buf.readFloatLE(pos); - const y = buf.readFloatLE(pos + 4); - const z = buf.readFloatLE(pos + 8); - const w = buf.readFloatLE(pos + 12); - super([x, y, z, w]); - } - else if (buf !== undefined && Array.isArray(buf)) - { - super(buf); + buf.writeDoubleLE(this.x, pos); + buf.writeDoubleLE(this.y, pos + 8); + buf.writeDoubleLE(this.z, pos + 16); + buf.writeDoubleLE(this.w, pos + 24); } else { - super(); + buf.writeFloatLE(this.x, pos); + buf.writeFloatLE(this.y, pos + 4); + buf.writeFloatLE(this.z, pos + 8); + buf.writeFloatLE(this.w, pos + 12); } } - writeToBuffer(buf: Buffer, pos: number): void + public toString(): string { - buf.writeFloatLE(this.x, pos); - buf.writeFloatLE(this.y, pos + 4); - buf.writeFloatLE(this.z, pos + 8); - buf.writeFloatLE(this.w, pos + 12); + return `<${this.x}, ${this.y}, ${this.z}, ${this.w}>`; } - toString(): string + public getBuffer(double = false): Buffer { - return '<' + this.x + ', ' + this.y + ', ' + this.z + ', ' + this.w + '>'; + const buf = Buffer.allocUnsafe(double ? 32 : 16); + this.writeToBuffer(buf, 0, double); + return buf; + } + + public compareApprox(vec: Vector4): boolean + { + return this.equals(vec, 0.00001); + } + + public toArray(): number[] + { + return [this.x, this.y, this.z, this.w]; + } + + public equals(vec: Vector4, epsilon = Number.EPSILON): boolean + { + return ( + Math.abs(this.x - vec.x) < epsilon && + Math.abs(this.y - vec.y) < epsilon && + Math.abs(this.z - vec.z) < epsilon && + Math.abs(this.w - vec.w) < epsilon + ); + } + + public dot(vec: Vector4): number + { + return this.x * vec.x + this.y * vec.y + this.z * vec.z + this.w * vec.w; + } + + public distance(vec: Vector4): number + { + return Math.sqrt(this.squaredDistance(vec)); + } + + public squaredDistance(vec: Vector4): number + { + const dx = this.x - vec.x; + const dy = this.y - vec.y; + const dz = this.z - vec.z; + const dw = this.w - vec.w; + return dx * dx + dy * dy + dz * dz + dw * dw; + } + + public direction(vec: Vector4): Vector4 + { + return vec.difference(this).normalize(); + } + + public mix(vec: Vector4, t: number): Vector4 + { + return new Vector4([ + this.x + t * (vec.x - this.x), + this.y + t * (vec.y - this.y), + this.z + t * (vec.z - this.z), + this.w + t * (vec.w - this.w), + ]); + } + + public sum(vec: Vector4): Vector4 + { + return new Vector4([ + this.x + vec.x, + this.y + vec.y, + this.z + vec.z, + this.w + vec.w, + ]); + } + + public difference(vec: Vector4): Vector4 + { + return new Vector4([ + this.x - vec.x, + this.y - vec.y, + this.z - vec.z, + this.w - vec.w, + ]); + } + + public product(vec: Vector4): Vector4 + { + return new Vector4([ + this.x * vec.x, + this.y * vec.y, + this.z * vec.z, + this.w * vec.w, + ]); + } + + public quotient(vec: Vector4): Vector4 + { + return new Vector4([ + this.x / vec.x, + this.y / vec.y, + this.z / vec.z, + this.w / vec.w, + ]); + } + + public reset(): void + { + this.x = 0; + this.y = 0; + this.z = 0; + this.w = 0; + } + + public copy(): Vector4 + { + return new Vector4(this); + } + + public toJSON(): object + { + return { + values: { + '0': this.x, + '1': this.y, + '2': this.z, + '3': this.w + } + } + } + + public negate(): Vector4 + { + return new Vector4([ + -this.x, + -this.y, + -this.z, + -this.w, + ]); + } + + public length(): number + { + return Math.sqrt(this.squaredLength()); + } + + public squaredLength(): number + { + return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; + } + + public add(vec: Vector4): this + { + this.x += vec.x; + this.y += vec.y; + this.z += vec.z; + this.w += vec.w; + return this; + } + + public subtract(vec: Vector4): this + { + this.x -= vec.x; + this.y -= vec.y; + this.z -= vec.z; + this.w -= vec.w; + return this; + } + + public multiply(value: number | Vector4): this + { + if (typeof value === 'number') + { + this.x *= value; + this.y *= value; + this.z *= value; + this.w *= value; + } + else + { + this.x *= value.x; + this.y *= value.y; + this.z *= value.z; + this.w *= value.w; + } + return this; + } + + public divide(value: number | Vector4): this + { + if (typeof value === 'number') + { + this.x /= value; + this.y /= value; + this.z /= value; + this.w /= value; + } + else + { + this.x /= value.x; + this.y /= value.y; + this.z /= value.z; + this.w /= value.w; + } + return this; + } + + public scale(scalar: number): this + { + return this.multiply(scalar); + } + + public normalize(): this + { + const len = this.length(); + if (len > 0) + { + this.scale(1 / len); + } + return this; + } + + public toQuaternion(): Quaternion + { + return new Quaternion(this.x, this.y, this.z, this.w); } } diff --git a/lib/classes/Wearable.ts b/lib/classes/Wearable.ts index d3fd92d..3eda041 100644 --- a/lib/classes/Wearable.ts +++ b/lib/classes/Wearable.ts @@ -1,8 +1,8 @@ -import { UUID } from './UUID'; +import type { UUID } from './UUID'; export class Wearable { - itemID: UUID; - assetID: UUID; - wearableType: number; + public itemID: UUID; + public assetID: UUID; + public wearableType: number; } diff --git a/lib/classes/Zerocoder.ts b/lib/classes/Zerocoder.ts index fdd8450..40c8e00 100644 --- a/lib/classes/Zerocoder.ts +++ b/lib/classes/Zerocoder.ts @@ -1,6 +1,6 @@ export class Zerocoder { - static Encode(buf: Buffer, start: number, end: number): Buffer + public static Encode(buf: Buffer, start: number, end: number): Buffer { // First, run through the data and calculate how many bytes we will save let bytes = 0; @@ -54,14 +54,14 @@ export class Zerocoder } return newBuf; } - static Decode(buf: Buffer, start: number, end: number, tail: number): Buffer + public static Decode(buf: Buffer, start: number, end: number, tail: number): Buffer { // First, run through the data and calculate how many bytes have been compressed let bytes = 0; let zero = false; for (let i = start; i <= end; i++) { - if (zero === true) + if (zero) { zero = false; // Minus two bytes for the overhead @@ -79,7 +79,7 @@ export class Zerocoder zero = false; for (let i = start; i <= end; i++) { - if (zero === true) + if (zero) { zero = false; const zeroCount = buf.readUInt8(i); diff --git a/lib/classes/commands/AgentCommands.ts b/lib/classes/commands/AgentCommands.ts index dd1a066..9825eb7 100644 --- a/lib/classes/commands/AgentCommands.ts +++ b/lib/classes/commands/AgentCommands.ts @@ -1,53 +1,32 @@ -import { InventoryFolder } from '../InventoryFolder'; +import type { InventoryFolder } from '../InventoryFolder'; import { UUID } from '../UUID'; import { AgentAnimationMessage } from '../messages/AgentAnimation'; import { PacketFlags } from '../../enums/PacketFlags'; import { CommandsBase } from './CommandsBase'; -import { Vector3 } from '../Vector3'; +import type { Vector3 } from '../Vector3'; import { Message } from '../../enums/Message'; import { Utils } from '../Utils'; import { FilterResponse } from '../../enums/FilterResponse'; -import { AvatarPropertiesReplyMessage } from '../messages/AvatarPropertiesReply'; +import type { AvatarPropertiesReplyMessage } from '../messages/AvatarPropertiesReply'; import { AvatarPropertiesRequestMessage } from '../messages/AvatarPropertiesRequest'; -import { AvatarPropertiesReplyEvent } from '../../events/AvatarPropertiesReplyEvent'; -import { Subscription } from 'rxjs'; -import { Avatar } from '../public/Avatar'; +import type { AvatarPropertiesReplyEvent } from '../../events/AvatarPropertiesReplyEvent'; +import type { Subscription } from 'rxjs'; +import type { Avatar } from '../public/Avatar'; +import type { GameObject } from '../public/GameObject'; export class AgentCommands extends CommandsBase { - private async animate(anim: UUID[], run: boolean): Promise + public async startAnimations(anim: UUID[]): Promise { - - const circuit = this.currentRegion.circuit; - const animPacket = new AgentAnimationMessage(); - animPacket.AgentData = { - AgentID: this.agent.agentID, - SessionID: circuit.sessionID - }; - animPacket.PhysicalAvatarEventList = []; - animPacket.AnimationList = []; - for (const a of anim) - { - animPacket.AnimationList.push({ - AnimID: a, - StartAnim: run - }); - } - - return await circuit.waitForAck(circuit.sendMessage(animPacket, PacketFlags.Reliable), 10000); + return this.animate(anim, true); } - async startAnimations(anim: UUID[]): Promise + public async stopAnimations(anim: UUID[]): Promise { - return await this.animate(anim, true); + return this.animate(anim, false); } - async stopAnimations(anim: UUID[]): Promise - { - return await this.animate(anim, false); - } - - setCamera(position: Vector3, lookAt: Vector3, viewDistance?: number, leftAxis?: Vector3, upAxis?: Vector3): void + public setCamera(position: Vector3, lookAt: Vector3, viewDistance?: number, leftAxis?: Vector3, upAxis?: Vector3): void { this.agent.cameraCenter = position; this.agent.cameraLookAt = lookAt; @@ -66,18 +45,28 @@ export class AgentCommands extends CommandsBase this.agent.sendAgentUpdate(); } - setViewDistance(viewDistance: number): void + // noinspection JSUnusedGlobalSymbols + public setViewDistance(viewDistance: number): void { this.agent.cameraFar = viewDistance; this.agent.sendAgentUpdate(); } - async getWearables(): Promise + // noinspection JSUnusedGlobalSymbols + public getGameObject(): GameObject + { + const agentLocalID = this.currentRegion.agent.localID; + return this.currentRegion.objects.getObjectByLocalID(agentLocalID); + } + + // noinspection JSUnusedGlobalSymbols + public async getWearables(): Promise { return this.agent.getWearables(); } - waitForAppearanceComplete(timeout: number = 30000): Promise + // noinspection JSUnusedGlobalSymbols + public async waitForAppearanceComplete(timeout = 30000): Promise { return new Promise((resolve, reject) => { @@ -87,8 +76,8 @@ export class AgentCommands extends CommandsBase } else { - let appearanceSubscription: Subscription | undefined; - let timeoutTimer: number | undefined; + let appearanceSubscription: Subscription | undefined = undefined; + let timeoutTimer: number | undefined = undefined; appearanceSubscription = this.agent.appearanceCompleteEvent.subscribe(() => { if (timeoutTimer !== undefined) @@ -116,7 +105,7 @@ export class AgentCommands extends CommandsBase timeoutTimer = undefined; reject(new Error('Timeout')); } - }, timeout) as any as number; + }, timeout) as unknown as number; if (this.agent.appearanceComplete) { if (appearanceSubscription !== undefined) @@ -135,7 +124,8 @@ export class AgentCommands extends CommandsBase }); } - getAvatar(avatarID: UUID | string = UUID.zero()): Avatar | undefined + // noinspection JSUnusedGlobalSymbols + public getAvatar(avatarID: UUID | string = UUID.zero()): Avatar | undefined { if (typeof avatarID === 'string') { @@ -145,10 +135,11 @@ export class AgentCommands extends CommandsBase { avatarID = this.agent.agentID; } - return this.currentRegion.agents[avatarID.toString()]; + return this.currentRegion.agents.get(avatarID.toString()); } - async getAvatarProperties(avatarID: UUID | string): Promise + // noinspection JSUnusedGlobalSymbols + public async getAvatarProperties(avatarID: UUID | string): Promise { if (typeof avatarID === 'string') { @@ -167,25 +158,46 @@ export class AgentCommands extends CommandsBase const avatarPropertiesReply: AvatarPropertiesReplyMessage = (await this.circuit.waitForMessage(Message.AvatarPropertiesReply, 10000, (packet: AvatarPropertiesReplyMessage): FilterResponse => { - const replyMessage: AvatarPropertiesReplyMessage = packet as AvatarPropertiesReplyMessage; - if (replyMessage.AgentData.AvatarID.equals(avatarID)) + if (packet.AgentData.AvatarID.equals(avatarID)) { return FilterResponse.Finish; } return FilterResponse.NoMatch; - })) as AvatarPropertiesReplyMessage; + })); return new class implements AvatarPropertiesReplyEvent { - ImageID = avatarPropertiesReply.PropertiesData.ImageID; - FLImageID = avatarPropertiesReply.PropertiesData.FLImageID; - PartnerID = avatarPropertiesReply.PropertiesData.PartnerID; - AboutText = Utils.BufferToStringSimple(avatarPropertiesReply.PropertiesData.AboutText); - FLAboutText = Utils.BufferToStringSimple(avatarPropertiesReply.PropertiesData.FLAboutText); - BornOn = Utils.BufferToStringSimple(avatarPropertiesReply.PropertiesData.BornOn); - ProfileURL = Utils.BufferToStringSimple(avatarPropertiesReply.PropertiesData.ProfileURL); - CharterMember = parseInt(Utils.BufferToStringSimple(avatarPropertiesReply.PropertiesData.CharterMember), 10); // avatarPropertiesReply.PropertiesData.CharterMember; - Flags = avatarPropertiesReply.PropertiesData.Flags; + public ImageID = avatarPropertiesReply.PropertiesData.ImageID; + public FLImageID = avatarPropertiesReply.PropertiesData.FLImageID; + public PartnerID = avatarPropertiesReply.PropertiesData.PartnerID; + public AboutText = Utils.BufferToStringSimple(avatarPropertiesReply.PropertiesData.AboutText); + public FLAboutText = Utils.BufferToStringSimple(avatarPropertiesReply.PropertiesData.FLAboutText); + public BornOn = Utils.BufferToStringSimple(avatarPropertiesReply.PropertiesData.BornOn); + public ProfileURL = Utils.BufferToStringSimple(avatarPropertiesReply.PropertiesData.ProfileURL); + public CharterMember = parseInt(Utils.BufferToStringSimple(avatarPropertiesReply.PropertiesData.CharterMember), 10); // avatarPropertiesReply.PropertiesData.CharterMember; + public Flags = avatarPropertiesReply.PropertiesData.Flags; }; } + + private async animate(anim: UUID[], run: boolean): Promise + { + + const {circuit} = this.currentRegion; + const animPacket = new AgentAnimationMessage(); + animPacket.AgentData = { + AgentID: this.agent.agentID, + SessionID: circuit.sessionID + }; + animPacket.PhysicalAvatarEventList = []; + animPacket.AnimationList = []; + for (const a of anim) + { + animPacket.AnimationList.push({ + AnimID: a, + StartAnim: run + }); + } + + await circuit.waitForAck(circuit.sendMessage(animPacket, PacketFlags.Reliable), 10000); + } } diff --git a/lib/classes/commands/AssetCommands.ts b/lib/classes/commands/AssetCommands.ts index 7929516..591b845 100644 --- a/lib/classes/commands/AssetCommands.ts +++ b/lib/classes/commands/AssetCommands.ts @@ -5,27 +5,25 @@ import { Utils } from '../Utils'; import { TransferRequestMessage } from '../messages/TransferRequest'; import { TransferChannelType } from '../../enums/TransferChannelType'; import { TransferSourceType } from '../../enums/TransferSourceTypes'; -import { TransferInfoMessage } from '../messages/TransferInfo'; +import type { TransferInfoMessage } from '../messages/TransferInfo'; import { Message } from '../../enums/Message'; -import { Packet } from '../Packet'; -import { TransferPacketMessage } from '../messages/TransferPacket'; -import { TransferAbortMessage } from '../messages/TransferAbort'; +import type { Packet } from '../Packet'; +import type { TransferPacketMessage } from '../messages/TransferPacket'; +import type { TransferAbortMessage } from '../messages/TransferAbort'; import { AssetType } from '../../enums/AssetType'; import { PacketFlags } from '../../enums/PacketFlags'; import { TransferStatus } from '../../enums/TransferStatus'; import { Material } from '../public/Material'; -import { HTTPAssets } from '../../enums/HTTPAssets'; -import { InventoryFolder } from '../InventoryFolder'; -import { InventoryItem } from '../InventoryItem'; -import { BulkUpdateInventoryEvent } from '../../events/BulkUpdateInventoryEvent'; +import type { InventoryFolder } from '../InventoryFolder'; +import type { InventoryItem } from '../InventoryItem'; +import type { BulkUpdateInventoryEvent } from '../../events/BulkUpdateInventoryEvent'; import { FilterResponse } from '../../enums/FilterResponse'; -import { LLLindenText } from '../LLLindenText'; -import { Subscription } from 'rxjs'; +import type { Subscription } from 'rxjs'; import { Logger } from '../Logger'; export class AssetCommands extends CommandsBase { - async downloadAsset(type: HTTPAssets, uuid: UUID | string): Promise + public async downloadAsset(type: AssetType, uuid: UUID | string): Promise { if (typeof uuid === 'string') { @@ -36,34 +34,25 @@ export class AssetCommands extends CommandsBase { switch (type) { - case HTTPAssets.ASSET_TEXTURE: - case HTTPAssets.ASSET_SOUND: - case HTTPAssets.ASSET_ANIMATION: - case HTTPAssets.ASSET_GESTURE: - case HTTPAssets.ASSET_LANDMARK: - case HTTPAssets.ASSET_CLOTHING: - case HTTPAssets.ASSET_MATERIAL: - case HTTPAssets.ASSET_BODYPART: - case HTTPAssets.ASSET_MESH: - case HTTPAssets.ASSET_SETTINGS: - return this.currentRegion.caps.downloadAsset(uuid, type); - case HTTPAssets.ASSET_CALLINGCARD: - case HTTPAssets.ASSET_SCRIPT: - case HTTPAssets.ASSET_OBJECT: - case HTTPAssets.ASSET_NOTECARD: - case HTTPAssets.ASSET_CATEGORY: - case HTTPAssets.ASSET_LSL_TEXT: - case HTTPAssets.ASSET_LSL_BYTECODE: - case HTTPAssets.ASSET_SIMSTATE: - case HTTPAssets.ASSET_LINK: - case HTTPAssets.ASSET_LINK_FOLDER: - case HTTPAssets.ASSET_WIDGET: - case HTTPAssets.ASSET_PERSON: + case AssetType.Texture: + case AssetType.Sound: + case AssetType.Animation: + case AssetType.Gesture: + case AssetType.Landmark: + case AssetType.Clothing: + case AssetType.Material: + case AssetType.Bodypart: + case AssetType.Mesh: + case AssetType.Settings: + { + return await this.currentRegion.caps.downloadAsset(uuid, type); + } + default: { const transferParams = Buffer.allocUnsafe(20); uuid.writeToBuffer(transferParams, 0); - transferParams.writeInt32LE(Utils.HTTPAssetTypeToAssetType(type), 16); - return this.transfer(TransferChannelType.Asset, TransferSourceType.Asset, false, transferParams); + transferParams.writeInt32LE(type, 16); + return await this.transfer(TransferChannelType.Asset, TransferSourceType.Asset, false, transferParams); } } } @@ -71,17 +60,16 @@ export class AssetCommands extends CommandsBase { if (e instanceof Error) { - Logger.Error('Failed to download ' + type + ' asset ' + uuid.toString() + ' - ' + e.message) + throw new Error('Failed to download ' + type + ' asset ' + uuid.toString() + ' - ' + e.message) } else { - Logger.Error('Failed to download ' + type + ' asset ' + uuid.toString() + ' - ' + String(e)); + throw new Error('Failed to download ' + type + ' asset ' + uuid.toString() + ' - ' + String(e)); } - throw e; } } - async copyInventoryFromNotecard(notecardID: UUID, folder: InventoryFolder, itemID: UUID, objectID: UUID = UUID.zero()): Promise + public async copyInventoryFromNotecard(notecardID: UUID, folder: InventoryFolder, itemID: UUID, objectID: UUID = UUID.zero()): Promise { const gotCap = await this.currentRegion.caps.isCapAvailable('CopyInventoryFromNotecard'); if (gotCap) @@ -94,13 +82,8 @@ export class AssetCommands extends CommandsBase 'notecard-id': new LLSD.UUID(notecardID.toString()), 'object-id': new LLSD.UUID(objectID.toString()) }; - this.currentRegion.caps.capsPostXML('CopyInventoryFromNotecard', request).then(() => - { - - }).catch((err) => - { - throw err; - }); + // Dispatch request, don't wait + void this.currentRegion.caps.capsPostXML('CopyInventoryFromNotecard', request); const evt: BulkUpdateInventoryEvent = await Utils.waitOrTimeOut(this.currentRegion.clientEvents.onBulkUpdateInventoryEvent, 10000, (event: BulkUpdateInventoryEvent) => { for (const item of event.itemData) @@ -127,7 +110,123 @@ export class AssetCommands extends CommandsBase } } - transfer(channelType: TransferChannelType, sourceType: TransferSourceType, priority: boolean, transferParams: Buffer, outAssetID?: { assetID: UUID }): Promise + public async downloadInventoryAsset( + itemID: UUID, + ownerID: UUID, + type: AssetType, + priority: boolean, + objectID: UUID = UUID.zero(), + assetID: UUID = UUID.zero(), + outAssetID?: { assetID: UUID }, + sourceType: TransferSourceType = TransferSourceType.SimInventoryItem, + channelType: TransferChannelType = TransferChannelType.Asset): Promise + { + const transferParams = Buffer.allocUnsafe(100); + let pos = 0; + this.agent.agentID.writeToBuffer(transferParams, pos); + pos = pos + 16; + this.circuit.sessionID.writeToBuffer(transferParams, pos); + pos = pos + 16; + ownerID.writeToBuffer(transferParams, pos); + pos = pos + 16; + objectID.writeToBuffer(transferParams, pos); + pos = pos + 16; + itemID.writeToBuffer(transferParams, pos); + pos = pos + 16; + assetID.writeToBuffer(transferParams, pos); + pos = pos + 16; + transferParams.writeInt32LE(type, pos); + + return this.transfer(channelType, sourceType, priority, transferParams, outAssetID); + } + + public async getMaterials(uuids: Record): Promise + { + let uuidArray: any[] = []; + let submittedUUIDS: Record = {}; + for (const uuid of Object.keys(uuids)) + { + if (uuidArray.length > 49) + { + let attempts = 5; + let err: unknown = null; + while(uuidArray.length > 0 && attempts-- > 0) + { + if (attempts < 4) + { + await Utils.sleep(1000); + } + try + { + await this.getMaterialsLimited(uuidArray, submittedUUIDS); + for (const uu of Object.keys(submittedUUIDS)) + { + if (submittedUUIDS[uu] !== null) + { + uuids[uu] = submittedUUIDS[uu]; + } + } + uuidArray = []; + submittedUUIDS = {}; + } + catch (error) + { + err = error; + } + } + if (uuidArray.length > 0) + { + Logger.Error('Error fetching materials:'); + Logger.Error(err); + } + } + if (!submittedUUIDS[uuid]) + { + submittedUUIDS[uuid] = uuids[uuid]; + uuidArray.push(new LLSD.Binary(Array.from(new UUID(uuid).getBuffer()))) + } + } + try + { + let attempts = 5; + let err: unknown = null; + while(uuidArray.length > 0 && attempts-- > 0) + { + if (attempts < 4) + { + await Utils.sleep(1000); + } + try + { + await this.getMaterialsLimited(uuidArray, submittedUUIDS); + for (const uu of Object.keys(submittedUUIDS)) + { + if (submittedUUIDS[uu] !== null) + { + uuids[uu] = submittedUUIDS[uu]; + } + } + uuidArray = []; + submittedUUIDS = {}; + } + catch (error) + { + err = error; + } + } + if (uuidArray.length > 0) + { + Logger.Error('Error fetching materials:'); + Logger.Error(err); + } + } + catch (error) + { + console.error(error); + } + } + + private async transfer(channelType: TransferChannelType, sourceType: TransferSourceType, priority: boolean, transferParams: Buffer, outAssetID?: { assetID: UUID }): Promise { return new Promise((resolve, reject) => { @@ -142,9 +241,9 @@ export class AssetCommands extends CommandsBase }; let gotInfo = true; let expectedSize = 0; - const packets: { [key: number]: Buffer } = {}; + const packets: Record = {}; let subscription: Subscription | undefined = undefined; - let timeout: number | undefined; + let timeout: number | undefined = undefined; function cleanup(): void { @@ -166,7 +265,7 @@ export class AssetCommands extends CommandsBase { cleanup(); reject(new Error('Timeout')); - }, 10000) as any as number; + }, 10000) as unknown as number; } function resetTimeout(): void @@ -197,7 +296,7 @@ export class AssetCommands extends CommandsBase } resetTimeout(); packets[messg.TransferData.Packet] = messg.TransferData.Data; - switch (messg.TransferData.Status) + switch (messg.TransferData.Status as TransferStatus) { case TransferStatus.Abort: cleanup(); @@ -218,6 +317,9 @@ export class AssetCommands extends CommandsBase cleanup(); reject(new Error('Not Found')); break; + case TransferStatus.OK: + case TransferStatus.Done: + break; } break; } @@ -260,6 +362,8 @@ export class AssetCommands extends CommandsBase cleanup(); reject(new Error('Not Found')); break; + case TransferStatus.Done: + break; } break; @@ -276,6 +380,8 @@ export class AssetCommands extends CommandsBase reject(new Error('Transfer Aborted')); return; } + default: + break; } if (gotInfo) { @@ -304,7 +410,11 @@ export class AssetCommands extends CommandsBase catch (error) { cleanup(); - reject(error); + if (error instanceof Error) + { + reject(error); + } + throw new Error('Unknown error: ' + String(error)); } }); placeTimeout(); @@ -312,67 +422,19 @@ export class AssetCommands extends CommandsBase }); } - downloadInventoryAsset( - itemID: UUID, - ownerID: UUID, - type: AssetType, - priority: boolean, - objectID: UUID = UUID.zero(), - assetID: UUID = UUID.zero(), - outAssetID?: { assetID: UUID }, - sourceType: TransferSourceType = TransferSourceType.SimInventoryItem, - channelType: TransferChannelType = TransferChannelType.Asset): Promise - { - return new Promise((resolve, reject) => - { - if (type === AssetType.Notecard && assetID.isZero()) - { - // Empty notecard - const note = new LLLindenText(); - resolve(note.toAsset()); - } - - const transferParams = Buffer.allocUnsafe(100); - let pos = 0; - this.agent.agentID.writeToBuffer(transferParams, pos); - pos = pos + 16; - this.circuit.sessionID.writeToBuffer(transferParams, pos); - pos = pos + 16; - ownerID.writeToBuffer(transferParams, pos); - pos = pos + 16; - objectID.writeToBuffer(transferParams, pos); - pos = pos + 16; - itemID.writeToBuffer(transferParams, pos); - pos = pos + 16; - assetID.writeToBuffer(transferParams, pos); - pos = pos + 16; - transferParams.writeInt32LE(type, pos); - - this.transfer(channelType, sourceType, priority, transferParams, outAssetID).then((result) => - { - resolve(result); - }).catch((err) => - { - reject(err); - }); - - }); - } - - private async getMaterialsLimited(uuidArray: any[], uuids: { [key: string]: Material | null }): Promise + private async getMaterialsLimited(uuidArray: unknown[], uuids: Record): Promise { const binary = LLSD.LLSD.formatBinary(uuidArray); const res: Buffer = await Utils.deflate(Buffer.from(binary.toArray())); const result = await this.currentRegion.caps.capsPostXML('RenderMaterials', { 'Zipped': LLSD.LLSD.asBinary(res.toString('base64')) }); - - const resultZipped = Buffer.from(result['Zipped'].octets); + const resultZipped = Buffer.from(result.Zipped.octets); const reslt: Buffer = await Utils.inflate(resultZipped); const binData = new LLSD.Binary(Array.from(reslt), 'BASE64'); const llsdResult = LLSD.LLSD.parseBinary(binData); let obj = []; - if (llsdResult.result) + if (llsdResult.result !== undefined) { obj = llsdResult.result; } @@ -380,15 +442,15 @@ export class AssetCommands extends CommandsBase { for (const mat of obj) { - if (mat['ID']) + if (mat.ID !== undefined) { - const nbuf = Buffer.from(mat['ID'].toArray()); + const nbuf = Buffer.from(mat.ID.toArray()); const nuuid = new UUID(nbuf, 0).toString(); if (uuids[nuuid] !== undefined) { - if (mat['Material']) + if (mat.Material !== undefined) { - uuids[nuuid] = Material.fromLLSDObject(mat['Material']); + uuids[nuuid] = Material.fromLLSDObject(mat.Material); } } } @@ -399,53 +461,4 @@ export class AssetCommands extends CommandsBase throw new Error('Material data not found'); } } - - async getMaterials(uuids: { [key: string]: Material | null }): Promise - { - let uuidArray: any[] = []; - let submittedUUIDS: { [key: string]: Material | null } = {}; - for (const uuid of Object.keys(uuids)) - { - if (uuidArray.length > 32) - { - try - { - await this.getMaterialsLimited(uuidArray, submittedUUIDS); - for (const uu of Object.keys(submittedUUIDS)) - { - if (submittedUUIDS[uu] !== null) - { - uuids[uu] = submittedUUIDS[uu]; - } - } - } - catch (error) - { - console.error(error); - } - uuidArray = []; - submittedUUIDS = {}; - } - if (!submittedUUIDS[uuid]) - { - submittedUUIDS[uuid] = uuids[uuid]; - uuidArray.push(new LLSD.Binary(Array.from(new UUID(uuid).getBuffer()))) - } - } - try - { - await this.getMaterialsLimited(uuidArray, submittedUUIDS); - for (const uu of Object.keys(submittedUUIDS)) - { - if (submittedUUIDS[uu] !== null) - { - uuids[uu] = submittedUUIDS[uu]; - } - } - } - catch (error) - { - console.error(error); - } - } } diff --git a/lib/classes/commands/CommandsBase.ts b/lib/classes/commands/CommandsBase.ts index 9c242d0..0f49b0b 100644 --- a/lib/classes/commands/CommandsBase.ts +++ b/lib/classes/commands/CommandsBase.ts @@ -1,7 +1,7 @@ -import { Region } from '../Region'; -import { Bot } from '../../Bot'; -import { Agent } from '../Agent'; -import { Circuit } from '../Circuit'; +import type { Region } from '../Region'; +import type { Bot } from '../../Bot'; +import type { Agent } from '../Agent'; +import type { Circuit } from '../Circuit'; export class CommandsBase { @@ -10,7 +10,7 @@ export class CommandsBase protected bot: Bot; protected circuit: Circuit; - constructor(region: Region, agent: Agent, bot: Bot) + public constructor(region: Region, agent: Agent, bot: Bot) { this.currentRegion = region; this.agent = agent; @@ -18,8 +18,8 @@ export class CommandsBase this.circuit = this.currentRegion.circuit; } - shutdown(): void + public shutdown(): void { - + // optional override } } diff --git a/lib/classes/commands/CommunicationsCommands.ts b/lib/classes/commands/CommunicationsCommands.ts index 83ef666..e5adb0a 100644 --- a/lib/classes/commands/CommunicationsCommands.ts +++ b/lib/classes/commands/CommunicationsCommands.ts @@ -5,9 +5,9 @@ import { FilterResponse } from '../../enums/FilterResponse'; import { InstantMessageDialog } from '../../enums/InstantMessageDialog'; import { InstantMessageOnline } from '../../enums/InstantMessageOnline'; import { PacketFlags } from '../../enums/PacketFlags'; -import { GroupChatSessionJoinEvent } from '../../events/GroupChatSessionJoinEvent'; -import { ScriptDialogEvent } from '../../events/ScriptDialogEvent'; -import { InventoryFolder } from '../InventoryFolder'; +import type { GroupChatSessionJoinEvent } from '../../events/GroupChatSessionJoinEvent'; +import type { ScriptDialogEvent } from '../../events/ScriptDialogEvent'; +import type { InventoryFolder } from '../InventoryFolder'; import { InventoryItem } from '../InventoryItem'; import { ChatFromViewerMessage } from '../messages/ChatFromViewer'; import { ImprovedInstantMessageMessage } from '../messages/ImprovedInstantMessage'; @@ -20,9 +20,9 @@ import { CommandsBase } from './CommandsBase'; export class CommunicationsCommands extends CommandsBase { - async giveInventory(to: UUID | string, itemOrFolder: InventoryItem | InventoryFolder): Promise + public async giveInventory(to: UUID | string, itemOrFolder: InventoryItem | InventoryFolder): Promise { - const circuit = this.circuit; + const {circuit} = this; if (typeof to === 'string') { to = new UUID(to); @@ -39,7 +39,7 @@ export class CommunicationsCommands extends CommandsBase await itemOrFolder.populate(false); bucket = Buffer.allocUnsafe(17 * (itemOrFolder.items.length + 1)); let offset = 0; - bucket.writeUInt8(AssetType.Folder, offset++); + bucket.writeUInt8(AssetType.Category, offset++); itemOrFolder.folderID.writeToBuffer(bucket, offset); offset += 16; for (const item of itemOrFolder.items) { @@ -71,12 +71,12 @@ export class CommunicationsCommands extends CommandsBase EstateID: 0 }; const sequenceNo = circuit.sendMessage(im, PacketFlags.Reliable); - return await circuit.waitForAck(sequenceNo, 10000); + await circuit.waitForAck(sequenceNo, 10000); } - async sendInstantMessage(to: UUID | string, message: string): Promise + public async sendInstantMessage(to: UUID | string, message: string): Promise { - const circuit = this.circuit; + const {circuit} = this; if (typeof to === 'string') { to = new UUID(to); @@ -105,10 +105,10 @@ export class CommunicationsCommands extends CommandsBase EstateID: 0 }; const sequenceNo = circuit.sendMessage(im, PacketFlags.Reliable); - return await circuit.waitForAck(sequenceNo, 10000); + await circuit.waitForAck(sequenceNo, 10000); } - async nearbyChat(message: string, type: ChatType, channel?: number): Promise + public async nearbyChat(message: string, type: ChatType, channel?: number): Promise { if (channel === undefined) { @@ -125,25 +125,25 @@ export class CommunicationsCommands extends CommandsBase Channel: channel }; const sequenceNo = this.circuit.sendMessage(cfv, PacketFlags.Reliable); - return await this.circuit.waitForAck(sequenceNo, 10000); + await this.circuit.waitForAck(sequenceNo, 10000); } - async say(message: string, channel?: number): Promise + public async say(message: string, channel?: number): Promise { - return await this.nearbyChat(message, ChatType.Normal, channel); + await this.nearbyChat(message, ChatType.Normal, channel); } - async whisper(message: string, channel?: number): Promise + public async whisper(message: string, channel?: number): Promise { - return await this.nearbyChat(message, ChatType.Whisper, channel); + await this.nearbyChat(message, ChatType.Whisper, channel); } - async shout(message: string, channel?: number): Promise + public async shout(message: string, channel?: number): Promise { - return await this.nearbyChat(message, ChatType.Shout, channel); + await this.nearbyChat(message, ChatType.Shout, channel); } - async startTypingLocal(): Promise + public async startTypingLocal(): Promise { const cfv = new ChatFromViewerMessage(); cfv.AgentData = { @@ -156,10 +156,10 @@ export class CommunicationsCommands extends CommandsBase Channel: 0 }; const sequenceNo = this.circuit.sendMessage(cfv, PacketFlags.Reliable); - return await this.circuit.waitForAck(sequenceNo, 10000); + await this.circuit.waitForAck(sequenceNo, 10000); } - sendTeleport(target: UUID | string, message?: string): Promise + public async sendTeleport(target: UUID | string, message?: string): Promise { if (typeof target === 'string') { @@ -185,7 +185,7 @@ export class CommunicationsCommands extends CommandsBase return this.circuit.waitForAck(sequenceNo, 10000); } - async stopTypingLocal(): Promise + public async stopTypingLocal(): Promise { const cfv = new ChatFromViewerMessage(); cfv.AgentData = { @@ -198,16 +198,16 @@ export class CommunicationsCommands extends CommandsBase Channel: 0 }; const sequenceNo = this.circuit.sendMessage(cfv, PacketFlags.Reliable); - return await this.circuit.waitForAck(sequenceNo, 10000); + await this.circuit.waitForAck(sequenceNo, 10000); } - async startTypingIM(to: UUID | string): Promise + public async startTypingIM(to: UUID | string): Promise { if (typeof to === 'string') { to = new UUID(to); } - const circuit = this.circuit; + const {circuit} = this; const agentName = this.agent.firstName + ' ' + this.agent.lastName; const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage(); im.AgentData = { @@ -232,16 +232,16 @@ export class CommunicationsCommands extends CommandsBase EstateID: 0 }; const sequenceNo = circuit.sendMessage(im, PacketFlags.Reliable); - return await circuit.waitForAck(sequenceNo, 10000); + await circuit.waitForAck(sequenceNo, 10000); } - async stopTypingIM(to: UUID | string): Promise + public async stopTypingIM(to: UUID | string): Promise { if (typeof to === 'string') { to = new UUID(to); } - const circuit = this.circuit; + const {circuit} = this; const agentName = this.agent.firstName + ' ' + this.agent.lastName; const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage(); im.AgentData = { @@ -266,125 +266,62 @@ export class CommunicationsCommands extends CommandsBase EstateID: 0 }; const sequenceNo = circuit.sendMessage(im, PacketFlags.Reliable); - return await circuit.waitForAck(sequenceNo, 10000); + await circuit.waitForAck(sequenceNo, 10000); } - typeInstantMessage(to: UUID | string, message: string, thinkingTime?: number, charactersPerSecond?: number): Promise + public async typeInstantMessage(to: UUID | string, message: string, thinkingTime?: number, charactersPerSecond?: number): Promise { - return new Promise((resolve, reject) => + if (thinkingTime === undefined) { - if (thinkingTime === undefined) - { - thinkingTime = 2000; - } - setTimeout(() => - { - if (typeof to === 'string') - { - to = new UUID(to); - } - let typeTimer: NodeJS.Timeout | null = null; - this.startTypingIM(to).then(() => - { - typeTimer = setInterval(() => - { - this.startTypingIM(to).catch(() => - { - // ignore - }); - }, 5000); - if (charactersPerSecond === undefined) - { - charactersPerSecond = 5; - } + thinkingTime = 2000; + } + await Utils.sleep(thinkingTime); - const timeToWait = (message.length / charactersPerSecond) * 1000; - setTimeout(() => - { - if (typeTimer !== null) - { - clearInterval(typeTimer); - typeTimer = null; - } - this.stopTypingIM(to).then(() => - { - this.sendInstantMessage(to, message).then(() => - { - resolve(); - }).catch((err) => - { - reject(err); - }); - }).catch((err) => - { - reject(err); - }); - }, timeToWait); - }).catch((err) => - { - if (typeTimer !== null) - { - clearInterval(typeTimer); - typeTimer = null; - } - reject(err); - }); - }, thinkingTime); - }); + if (typeof to === 'string') + { + to = new UUID(to); + } + let typeTimer: NodeJS.Timeout | null = null; + await this.startTypingIM(to); + typeTimer = setInterval(() => + { + // Send a new typing message ever 5 secs + // or it will time out at the other end + void this.startTypingIM(to); + }, 5000); + if (charactersPerSecond === undefined) + { + charactersPerSecond = 5; + } + const timeToWait = (message.length / charactersPerSecond) * 1000; + await Utils.sleep(timeToWait); + if (typeTimer !== null) + { + clearInterval(typeTimer); + typeTimer = null; + } + await this.stopTypingIM(to); + await this.sendInstantMessage(to, message); } - typeLocalMessage(message: string, thinkingTime?: number, charactersPerSecond?: number): Promise + public async typeLocalMessage(message: string, thinkingTime?: number, charactersPerSecond?: number): Promise { - return new Promise((resolve, reject) => + if (thinkingTime === undefined) { - if (thinkingTime === undefined) - { - thinkingTime = 0; - } - setTimeout(() => - { - this.startTypingLocal().then(() => - { - this.bot.clientCommands.agent.startAnimations([new UUID('c541c47f-e0c0-058b-ad1a-d6ae3a4584d9')]).then(() => - { - if (charactersPerSecond === undefined) - { - charactersPerSecond = 5; - } - - const timeToWait = (message.length / charactersPerSecond) * 1000; - setTimeout(() => - { - this.stopTypingLocal().then(() => - { - this.bot.clientCommands.agent.stopAnimations([new UUID('c541c47f-e0c0-058b-ad1a-d6ae3a4584d9')]).then(() => - { - this.say(message).then(() => - { - resolve(); - }).catch((err) => - { - reject(err); - }); - }).catch((err) => - { - reject(err); - }); - }).catch((err) => - { - reject(err); - }); - }, timeToWait); - }).catch((err) => - { - reject(err); - }); - }).catch((err) => - { - reject(err); - }); - }, thinkingTime); - }); + thinkingTime = 0; + } + await Utils.sleep(thinkingTime); + await this.startTypingLocal(); + await this.bot.clientCommands.agent.startAnimations([new UUID('c541c47f-e0c0-058b-ad1a-d6ae3a4584d9')]); + if (charactersPerSecond === undefined) + { + charactersPerSecond = 5; + } + const timeToWait = (message.length / charactersPerSecond) * 1000; + await Utils.sleep(timeToWait); + await this.stopTypingLocal(); + await this.bot.clientCommands.agent.stopAnimations([new UUID('c541c47f-e0c0-058b-ad1a-d6ae3a4584d9')]); + await this.say(message); } public async endGroupChatSession(groupID: UUID | string): Promise @@ -397,7 +334,7 @@ export class CommunicationsCommands extends CommandsBase { throw new Error('Group session does not exist'); } - const circuit = this.circuit; + const {circuit} = this; const agentName = this.agent.firstName + ' ' + this.agent.lastName; const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage(); im.AgentData = { @@ -438,7 +375,7 @@ export class CommunicationsCommands extends CommandsBase return; } - const circuit = this.circuit; + const {circuit} = this; const agentName = this.agent.firstName + ' ' + this.agent.lastName; const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage(); im.AgentData = { @@ -473,7 +410,7 @@ export class CommunicationsCommands extends CommandsBase }); } - async moderateGroupChat(groupID: UUID | string, memberID: UUID | string, muteText: boolean, muteVoice: boolean): Promise + public async moderateGroupChat(groupID: UUID | string, memberID: UUID | string, muteText: boolean, muteVoice: boolean): Promise { if (typeof groupID === 'object') { @@ -498,7 +435,7 @@ export class CommunicationsCommands extends CommandsBase return this.currentRegion.caps.capsPostXML('ChatSessionRequest', requested); } - async sendGroupMessage(groupID: UUID | string, message: string): Promise + public async sendGroupMessage(groupID: UUID | string, message: string): Promise { if (typeof groupID === 'string') { @@ -510,7 +447,7 @@ export class CommunicationsCommands extends CommandsBase await this.startGroupChatSession(groupID, message); } - const circuit = this.circuit; + const {circuit} = this; const agentName = this.agent.firstName + ' ' + this.agent.lastName; const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage(); im.AgentData = { @@ -540,7 +477,7 @@ export class CommunicationsCommands extends CommandsBase return this.bot.clientCommands.group.getSessionAgentCount(groupID); } - respondToScriptDialog(event: ScriptDialogEvent, buttonIndex: number): Promise + public async respondToScriptDialog(event: ScriptDialogEvent, buttonIndex: number): Promise { const dialog: ScriptDialogReplyMessage = new ScriptDialogReplyMessage(); dialog.AgentData = { diff --git a/lib/classes/commands/FriendCommands.ts b/lib/classes/commands/FriendCommands.ts index 1e9b0f1..3fa2fd9 100644 --- a/lib/classes/commands/FriendCommands.ts +++ b/lib/classes/commands/FriendCommands.ts @@ -1,19 +1,19 @@ import { CommandsBase } from './CommandsBase'; -import { Region } from '../Region'; -import { Agent } from '../Agent'; -import { Bot } from '../../Bot'; -import { Subscription } from 'rxjs'; +import type { Region } from '../Region'; +import type { Agent } from '../Agent'; +import type { Bot } from '../../Bot'; +import type { Subscription } from 'rxjs'; import { Message } from '../../enums/Message'; -import { Packet } from '../Packet'; -import { OnlineNotificationMessage } from '../messages/OnlineNotification'; -import { OfflineNotificationMessage } from '../messages/OfflineNotification'; -import { TerminateFriendshipMessage } from '../messages/TerminateFriendship'; +import type { Packet } from '../Packet'; +import type { OnlineNotificationMessage } from '../messages/OnlineNotification'; +import type { OfflineNotificationMessage } from '../messages/OfflineNotification'; +import type { TerminateFriendshipMessage } from '../messages/TerminateFriendship'; import { AcceptFriendshipMessage } from '../messages/AcceptFriendship'; import { ImprovedInstantMessageMessage } from '../messages/ImprovedInstantMessage'; import { InstantMessageDialog } from '../../enums/InstantMessageDialog'; import { Utils } from '../Utils'; import { DeclineFriendshipMessage } from '../messages/DeclineFriendship'; -import { ChangeUserRightsMessage } from '../messages/ChangeUserRights'; +import type { ChangeUserRightsMessage } from '../messages/ChangeUserRights'; import { FindAgentMessage } from '../messages/FindAgent'; import { IPAddress } from '../IPAddress'; import { FilterResponse } from '../../enums/FilterResponse'; @@ -25,19 +25,17 @@ import { FriendRemovedEvent } from '../../events/FriendRemovedEvent'; import { FriendRightsEvent } from '../../events/FriendRightsEvent'; import { UUID } from '../UUID'; import { PacketFlags } from '../../enums/PacketFlags'; -import { MapLocation } from '../public/interfaces/MapLocation'; -import { FriendRequestEvent } from '../../events/FriendRequestEvent'; +import type { MapLocation } from '../public/interfaces/MapLocation'; +import type { FriendRequestEvent } from '../../events/FriendRequestEvent'; import { FolderType } from '../../enums/FolderType'; import { Vector3 } from '../Vector3'; export class FriendCommands extends CommandsBase { - friendMessages: Subscription; - friendsList: { - [key: string]: Friend - } = {}; + private friendMessages?: Subscription; + private readonly friendsList = new Map(); - constructor(region: Region, agent: Agent, bot: Bot) + public constructor(region: Region, agent: Agent, bot: Bot) { super(region, agent, bot); @@ -49,116 +47,14 @@ export class FriendCommands extends CommandsBase Message.OfflineNotification, Message.TerminateFriendship, Message.ChangeUserRights - ], async(packet: Packet) => + ], (packet: Packet) => { - switch (packet.message.id) - { - case Message.OnlineNotification: - { - const msg = packet.message as OnlineNotificationMessage; - for (const agentEntry of msg.AgentBlock) - { - const uuidStr = agentEntry.AgentID.toString(); - if (this.friendsList[uuidStr] === undefined) - { - this.friendsList[uuidStr] = await this.bot.clientCommands.grid.avatarKey2Name(agentEntry.AgentID) as Friend; - this.friendsList[uuidStr].online = false; - this.friendsList[uuidStr].myRights = RightsFlags.None; - this.friendsList[uuidStr].theirRights = RightsFlags.None; - } - if (this.friendsList[uuidStr].online !== true) - { - this.friendsList[uuidStr].online = true; - const friendOnlineEvent = new FriendOnlineEvent(); - friendOnlineEvent.friend = this.friendsList[uuidStr]; - friendOnlineEvent.online = true; - this.bot.clientEvents.onFriendOnline.next(friendOnlineEvent); - } - } - break; - } - case Message.OfflineNotification: - { - const msg = packet.message as OfflineNotificationMessage; - for (const agentEntry of msg.AgentBlock) - { - const uuidStr = agentEntry.AgentID.toString(); - if (this.friendsList[uuidStr] === undefined) - { - this.friendsList[uuidStr] = await this.bot.clientCommands.grid.avatarKey2Name(agentEntry.AgentID) as Friend; - this.friendsList[uuidStr].online = false; - this.friendsList[uuidStr].myRights = RightsFlags.None; - this.friendsList[uuidStr].theirRights = RightsFlags.None; - } - if (this.friendsList[uuidStr].online !== false) - { - this.friendsList[uuidStr].online = false; - const friendOnlineEvent = new FriendOnlineEvent(); - friendOnlineEvent.friend = this.friendsList[uuidStr]; - friendOnlineEvent.online = false; - this.bot.clientEvents.onFriendOnline.next(friendOnlineEvent); - } - } - break; - } - case Message.TerminateFriendship: - { - const msg = packet.message as TerminateFriendshipMessage; - const friendID = msg.ExBlock.OtherID; - const uuidStr = friendID.toString(); - if (this.friendsList[uuidStr] !== undefined) - { - const event = new FriendRemovedEvent(); - event.friend = this.friendsList[uuidStr]; - this.bot.clientEvents.onFriendRemoved.next(event); - delete this.friendsList[uuidStr]; - } - break; - } - case Message.ChangeUserRights: - { - const msg = packet.message as ChangeUserRightsMessage; - for (const rightsEntry of msg.Rights) - { - let uuidStr = ''; - if (rightsEntry.AgentRelated.equals(this.agent.agentID)) - { - // My rights - uuidStr = msg.AgentData.AgentID.toString(); - if (this.friendsList[uuidStr] === undefined) - { - this.friendsList[uuidStr] = await this.bot.clientCommands.grid.avatarKey2Name(rightsEntry.AgentRelated) as Friend; - this.friendsList[uuidStr].online = false; - this.friendsList[uuidStr].myRights = RightsFlags.None; - this.friendsList[uuidStr].theirRights = RightsFlags.None; - } - this.friendsList[uuidStr].myRights = rightsEntry.RelatedRights; - } - else - { - uuidStr = rightsEntry.AgentRelated.toString(); - if (this.friendsList[uuidStr] === undefined) - { - this.friendsList[uuidStr] = await this.bot.clientCommands.grid.avatarKey2Name(rightsEntry.AgentRelated) as Friend; - this.friendsList[uuidStr].online = false; - this.friendsList[uuidStr].myRights = RightsFlags.None; - this.friendsList[uuidStr].theirRights = RightsFlags.None; - } - this.friendsList[uuidStr].theirRights = rightsEntry.RelatedRights; - } - const friendRightsEvent = new FriendRightsEvent(); - friendRightsEvent.friend = this.friendsList[uuidStr]; - friendRightsEvent.theirRights = this.friendsList[uuidStr].theirRights; - friendRightsEvent.myRights = this.friendsList[uuidStr].myRights; - this.bot.clientEvents.onFriendRights.next(friendRightsEvent); - } - break; - } - } + void this.processPacket(packet); }); } - async grantFriendRights(friend: Friend | UUID | string, rights: RightsFlags): Promise + // noinspection JSUnusedGlobalSymbols + public async grantFriendRights(friend: Friend | UUID | string, rights: RightsFlags): Promise { let friendKey = UUID.zero(); if (friend instanceof UUID) @@ -169,13 +65,9 @@ export class FriendCommands extends CommandsBase { friendKey = friend.getKey(); } - else if (typeof friend === 'string') - { - friendKey = new UUID(friend); - } else { - throw new Error('"Friend" parameter must be Friend, UUID or string'); + friendKey = new UUID(friend); } const request: GrantUserRightsMessage = new GrantUserRightsMessage(); request.AgentData = { @@ -189,10 +81,10 @@ export class FriendCommands extends CommandsBase } ]; const sequenceNo = this.circuit.sendMessage(request, PacketFlags.Reliable); - return await this.circuit.waitForAck(sequenceNo, 10000); + await this.circuit.waitForAck(sequenceNo, 10000); } - async getFriendMapLocation(friend: Friend | UUID | string): Promise + public async getFriendMapLocation(friend: Friend | UUID | string): Promise { let friendKey = UUID.zero(); if (friend instanceof UUID) @@ -203,13 +95,9 @@ export class FriendCommands extends CommandsBase { friendKey = friend.getKey(); } - else if (typeof friend === 'string') - { - friendKey = new UUID(friend); - } else { - throw new Error('"Friend" parameter must be Friend, UUID or string'); + friendKey = new UUID(friend); } const request: FindAgentMessage = new FindAgentMessage(); request.AgentBlock = { @@ -246,7 +134,13 @@ export class FriendCommands extends CommandsBase }; } - async acceptFriendRequest(event: FriendRequestEvent): Promise + // noinspection JSUnusedGlobalSymbols + public getFriend(key: UUID): Friend | undefined + { + return this.friendsList.get(key.toString()); + } + + public async acceptFriendRequest(event: FriendRequestEvent): Promise { const accept: AcceptFriendshipMessage = new AcceptFriendshipMessage(); accept.AgentData = { @@ -263,10 +157,10 @@ export class FriendCommands extends CommandsBase } ); const sequenceNo = this.circuit.sendMessage(accept, PacketFlags.Reliable); - return await this.circuit.waitForAck(sequenceNo, 10000); + await this.circuit.waitForAck(sequenceNo, 10000); } - async rejectFriendRequest(event: FriendRequestEvent): Promise + public async rejectFriendRequest(event: FriendRequestEvent): Promise { const reject: DeclineFriendshipMessage = new DeclineFriendshipMessage(); reject.AgentData = { @@ -277,10 +171,10 @@ export class FriendCommands extends CommandsBase TransactionID: event.requestID }; const sequenceNo = this.circuit.sendMessage(reject, PacketFlags.Reliable); - return await this.circuit.waitForAck(sequenceNo, 10000); + await this.circuit.waitForAck(sequenceNo, 10000); } - async sendFriendRequest(to: UUID | string, message: string): Promise + public async sendFriendRequest(to: UUID | string, message: string): Promise { if (typeof to === 'string') { @@ -311,11 +205,145 @@ export class FriendCommands extends CommandsBase EstateID: 0 }; const sequenceNo = this.circuit.sendMessage(im, PacketFlags.Reliable); - return await this.circuit.waitForAck(sequenceNo, 10000); + await this.circuit.waitForAck(sequenceNo, 10000); } - shutdown(): void + public override shutdown(): void { - this.friendMessages.unsubscribe(); + if (this.friendMessages) + { + this.friendMessages.unsubscribe(); + delete this.friendMessages; + } + } + + private async processPacket(packet: Packet): Promise + { + switch (packet.message.id) + { + case Message.OnlineNotification: + { + const msg = packet.message as OnlineNotificationMessage; + for (const agentEntry of msg.AgentBlock) + { + const uuidStr = agentEntry.AgentID.toString(); + if (this.friendsList.has(uuidStr) === undefined) + { + const friend = await this.bot.clientCommands.grid.avatarKey2Name(agentEntry.AgentID) as Friend; + friend.online = false; + friend.myRights = RightsFlags.None; + friend.theirRights = RightsFlags.None; + this.friendsList.set(uuidStr, friend); + } + const friend = this.friendsList.get(uuidStr); + if (friend && !friend.online) + { + friend.online = true; + const friendOnlineEvent = new FriendOnlineEvent(); + friendOnlineEvent.friend = friend; + friendOnlineEvent.online = true; + this.bot.clientEvents.onFriendOnline.next(friendOnlineEvent); + } + } + break; + } + case Message.OfflineNotification: + { + const msg = packet.message as OfflineNotificationMessage; + for (const agentEntry of msg.AgentBlock) + { + const uuidStr = agentEntry.AgentID.toString(); + if (this.friendsList.has(uuidStr) === undefined) + { + const friend = await this.bot.clientCommands.grid.avatarKey2Name(agentEntry.AgentID) as Friend; + friend.online = false; + friend.myRights = RightsFlags.None; + friend.theirRights = RightsFlags.None; + this.friendsList.set(uuidStr, friend); + } + const friend = this.friendsList.get(uuidStr); + // eslint-disable-next-line @typescript-eslint/prefer-optional-chain + if (friend !== undefined && friend.online) + { + friend.online = false; + const friendOnlineEvent = new FriendOnlineEvent(); + friendOnlineEvent.friend = friend; + friendOnlineEvent.online = false; + this.bot.clientEvents.onFriendOnline.next(friendOnlineEvent); + } + } + break; + } + case Message.TerminateFriendship: + { + const msg = packet.message as TerminateFriendshipMessage; + const friendID = msg.ExBlock.OtherID; + const uuidStr = friendID.toString(); + const friend = this.friendsList.get(uuidStr); + if (friend !== undefined) + { + const event = new FriendRemovedEvent(); + event.friend = friend; + this.bot.clientEvents.onFriendRemoved.next(event); + this.friendsList.delete(uuidStr); + } + break; + } + case Message.ChangeUserRights: + { + const msg = packet.message as ChangeUserRightsMessage; + for (const rightsEntry of msg.Rights) + { + let uuidStr = ''; + if (rightsEntry.AgentRelated.equals(this.agent.agentID)) + { + // My rights + uuidStr = msg.AgentData.AgentID.toString(); + if (!this.friendsList.has(uuidStr)) + { + const friend = await this.bot.clientCommands.grid.avatarKey2Name(rightsEntry.AgentRelated) as Friend; + friend.online = false; + friend.myRights = RightsFlags.None; + friend.theirRights = RightsFlags.None; + this.friendsList.set(uuidStr, friend); + } + const friend = this.friendsList.get(uuidStr); + if (friend !== undefined) + { + friend.myRights = rightsEntry.RelatedRights; + } + } + else + { + uuidStr = rightsEntry.AgentRelated.toString(); + if (!this.friendsList.has(uuidStr)) + { + const friend = await this.bot.clientCommands.grid.avatarKey2Name(rightsEntry.AgentRelated) as Friend; + friend.online = false; + friend.myRights = RightsFlags.None; + friend.theirRights = RightsFlags.None; + this.friendsList.set(uuidStr, friend); + } + const friend = this.friendsList.get(uuidStr); + if (friend !== undefined) + { + friend.theirRights = rightsEntry.RelatedRights; + } + } + const friend = this.friendsList.get(uuidStr); + if (friend) + { + const friendRightsEvent = new FriendRightsEvent(); + friendRightsEvent.friend = friend; + friendRightsEvent.theirRights = friend.theirRights; + friendRightsEvent.myRights = friend.myRights; + this.bot.clientEvents.onFriendRights.next(friendRightsEvent); + } + } + break; + } + default: + break; + } } } diff --git a/lib/classes/commands/GridCommands.ts b/lib/classes/commands/GridCommands.ts index 14a9667..f3811fa 100644 --- a/lib/classes/commands/GridCommands.ts +++ b/lib/classes/commands/GridCommands.ts @@ -1,7 +1,7 @@ -import * as Long from 'long'; -import { MapItemReplyMessage } from '../messages/MapItemReply'; +import type * as Long from 'long'; +import type { MapItemReplyMessage } from '../messages/MapItemReply'; import { Message } from '../../enums/Message'; -import { MapBlockReplyMessage } from '../messages/MapBlockReply'; +import type { MapBlockReplyMessage } from '../messages/MapBlockReply'; import { MapBlockRequestMessage } from '../messages/MapBlockRequest'; import { UUID } from '../UUID'; import { MapItemRequestMessage } from '../messages/MapItemRequest'; @@ -9,15 +9,15 @@ import { Utils } from '../Utils'; import { GridItemType } from '../../enums/GridItemType'; import { CommandsBase } from './CommandsBase'; import { AvatarPickerRequestMessage } from '../messages/AvatarPickerRequest'; -import { AvatarPickerReplyMessage } from '../messages/AvatarPickerReply'; +import type { AvatarPickerReplyMessage } from '../messages/AvatarPickerReply'; import { FilterResponse } from '../../enums/FilterResponse'; import { MapNameRequestMessage } from '../messages/MapNameRequest'; import { GridLayerType } from '../../enums/GridLayerType'; import { MapBlock } from '../MapBlock'; import { TimeoutError } from '../TimeoutError'; import { UUIDNameRequestMessage } from '../messages/UUIDNameRequest'; -import { UUIDNameReplyMessage } from '../messages/UUIDNameReply'; -import { RegionInfoReplyEvent } from '../../events/RegionInfoReplyEvent'; +import type { UUIDNameReplyMessage } from '../messages/UUIDNameReply'; +import type { RegionInfoReplyEvent } from '../../events/RegionInfoReplyEvent'; import { MapInfoReplyEvent } from '../../events/MapInfoReplyEvent'; import { PacketFlags } from '../../enums/PacketFlags'; import { Vector2 } from '../Vector2'; @@ -26,192 +26,172 @@ import { AvatarQueryResult } from '../public/AvatarQueryResult'; import { MoneyTransferRequestMessage } from '../messages/MoneyTransferRequest'; import { MoneyTransactionType } from '../../enums/MoneyTransactionType'; import { TransactionFlags } from '../../enums/TransactionFlags'; -import { MoneyBalanceReplyMessage } from '../messages/MoneyBalanceReply'; +import type { MoneyBalanceReplyMessage } from '../messages/MoneyBalanceReply'; import { MoneyBalanceRequestMessage } from '../messages/MoneyBalanceRequest'; -import { GameObject } from '../public/GameObject'; +import type { GameObject } from '../public/GameObject'; export class GridCommands extends CommandsBase { - getRegionByName(regionName: string): Promise + public async getRegionByName(regionName: string): Promise { - return new Promise((resolve, reject) => + const {circuit} = this.currentRegion; + const msg: MapNameRequestMessage = new MapNameRequestMessage(); + msg.AgentData = { + AgentID: this.agent.agentID, + SessionID: circuit.sessionID, + Flags: GridLayerType.Objects, + EstateID: 0, + Godlike: false + }; + msg.NameData = { + Name: Utils.StringToBuffer(regionName) + }; + circuit.sendMessage(msg, PacketFlags.Reliable); + const responseMsg: MapBlockReplyMessage = await circuit.waitForMessage(Message.MapBlockReply, 10000, (filterMsg: MapBlockReplyMessage): FilterResponse => { - const circuit = this.currentRegion.circuit; - const msg: MapNameRequestMessage = new MapNameRequestMessage(); - msg.AgentData = { - AgentID: this.agent.agentID, - SessionID: circuit.sessionID, - Flags: GridLayerType.Objects, - EstateID: 0, - Godlike: false - }; - msg.NameData = { - Name: Utils.StringToBuffer(regionName) - }; - circuit.sendMessage(msg, PacketFlags.Reliable); - circuit.waitForMessage(Message.MapBlockReply, 10000, (filterMsg: MapBlockReplyMessage): FilterResponse => + let found = false; + for (const region of filterMsg.Data) { - let found = false; - for (const region of filterMsg.Data) + const name = Utils.BufferToStringSimple(region.Name); + if (name.trim().toLowerCase() === regionName.trim().toLowerCase()) { - const name = Utils.BufferToStringSimple(region.Name); - if (name.trim().toLowerCase() === regionName.trim().toLowerCase()) - { - found = true; - } + found = true; } - if (found) - { - return FilterResponse.Finish; - } - return FilterResponse.NoMatch; - }).then((responseMsg: MapBlockReplyMessage) => + } + if (found) { - for (const region of responseMsg.Data) - { - const name = Utils.BufferToStringSimple(region.Name); - if (name.trim().toLowerCase() === regionName.trim().toLowerCase() && !(region.X === 0 && region.Y === 0)) - { - const reply = new class implements RegionInfoReplyEvent - { - X = region.X; - Y = region.Y; - name = name; - access = region.Access; - regionFlags = region.RegionFlags; - waterHeight = region.WaterHeight; - agents = region.Agents; - mapImageID = region.MapImageID; - handle = Utils.RegionCoordinatesToHandle(region.X * 256, region.Y * 256).regionHandle; - }; - resolve(reply); - } - } - }).catch((err) => - { - reject(err); - }); + return FilterResponse.Finish; + } + return FilterResponse.NoMatch; }); - } - getRegionMapInfo(gridX: number, gridY: number): Promise - { - return new Promise((resolve, reject) => + for (const region of responseMsg.Data) { - const circuit = this.currentRegion.circuit; - const response = new MapInfoReplyEvent(); - const msg: MapBlockRequestMessage = new MapBlockRequestMessage(); - msg.AgentData = { - AgentID: this.agent.agentID, - SessionID: circuit.sessionID, - Flags: 0, - EstateID: 0, - Godlike: false - }; - msg.PositionData = { - MinX: gridX, - MaxX: gridX, - MinY: gridY, - MaxY: gridY - }; - circuit.sendMessage(msg, PacketFlags.Reliable); - circuit.waitForMessage(Message.MapBlockReply, 10000, (filterMsg: MapBlockReplyMessage): FilterResponse => + const name = Utils.BufferToStringSimple(region.Name); + if (name.trim().toLowerCase() === regionName.trim().toLowerCase() && !(region.X === 0 && region.Y === 0)) { - let found = false; - for (const data of filterMsg.Data) + return new class implements RegionInfoReplyEvent { - if (data.X === gridX && data.Y === gridY) - { - found = true; - } - } - if (found) - { - return FilterResponse.Finish; - } - return FilterResponse.NoMatch; - }).then((responseMsg: MapBlockReplyMessage) => - { - for (const data of responseMsg.Data) - { - if (data.X === gridX && data.Y === gridY) - { - response.block = new MapBlock(); - response.block.name = Utils.BufferToStringSimple(data.Name); - response.block.accessFlags = data.Access; - response.block.mapImage = data.MapImageID; - } - } - - // Now get the region handle - const regionHandle: Long = Utils.RegionCoordinatesToHandle(gridX * 256, gridY * 256).regionHandle; - - const mi = new MapItemRequestMessage(); - mi.AgentData = { - AgentID: this.agent.agentID, - SessionID: circuit.sessionID, - Flags: 2, - EstateID: 0, - Godlike: false + public X = region.X; + public Y = region.Y; + public name = name; + public access = region.Access; + public regionFlags = region.RegionFlags; + public waterHeight = region.WaterHeight; + public agents = region.Agents; + public mapImageID = region.MapImageID; + public handle = Utils.RegionCoordinatesToHandle(region.X * 256, region.Y * 256).regionHandle; }; - mi.RequestData = { - ItemType: GridItemType.AgentLocations, - RegionHandle: regionHandle - }; - circuit.sendMessage(mi, PacketFlags.Reliable); - const minX = gridX * 256; - const maxX = minX + 256; - const minY = gridY * 256; - const maxY = minY + 256; - response.avatars = []; - circuit.waitForMessage(Message.MapItemReply, 10000, (filterMsg: MapItemReplyMessage): FilterResponse => - { - let found = false; - for (const data of filterMsg.Data) - { - // Check if avatar is within our bounds - if (data.X >= minX && data.X <= maxX && data.Y >= minY && data.Y <= maxY) - { - found = true; - } - } - if (found) - { - return FilterResponse.Finish; - } - else - { - return FilterResponse.NoMatch; - } - }).then((responseMsg2: MapItemReplyMessage) => - { - for (const data of responseMsg2.Data) - { - for (let index = 0; index <= data.Extra; index++) - { - response.avatars.push(new Vector2([ - data.X, - data.Y - ])); - } - } - resolve(response); - }).catch((err) => - { - reject(err); - }); - }).catch((err) => - { - reject(err); - }); - }); + } + } + throw new Error('Region not found'); } - getRegionMapInfoRange(minX: number, minY: number, maxX: number, maxY: number): Promise + public async getRegionMapInfo(gridX: number, gridY: number): Promise { - return new Promise((resolve, reject) => + const {circuit} = this.currentRegion; + const response = new MapInfoReplyEvent(); + const msg: MapBlockRequestMessage = new MapBlockRequestMessage(); + msg.AgentData = { + AgentID: this.agent.agentID, + SessionID: circuit.sessionID, + Flags: 0, + EstateID: 0, + Godlike: false + }; + msg.PositionData = { + MinX: gridX, + MaxX: gridX, + MinY: gridY, + MaxY: gridY + }; + circuit.sendMessage(msg, PacketFlags.Reliable); + const responseMsg: MapBlockReplyMessage = await circuit.waitForMessage(Message.MapBlockReply, 10000, (filterMsg: MapBlockReplyMessage): FilterResponse => { - const circuit = this.currentRegion.circuit; - const response = new MapInfoRangeReplyEvent(); + let found = false; + for (const data of filterMsg.Data) + { + if (data.X === gridX && data.Y === gridY) + { + found = true; + } + } + if (found) + { + return FilterResponse.Finish; + } + return FilterResponse.NoMatch; + }); + for (const data of responseMsg.Data) + { + if (data.X === gridX && data.Y === gridY) + { + response.block = new MapBlock(); + response.block.name = Utils.BufferToStringSimple(data.Name); + response.block.accessFlags = data.Access; + response.block.mapImage = data.MapImageID; + } + } + + // Now get the region handle + const regionHandle: Long = Utils.RegionCoordinatesToHandle(gridX * 256, gridY * 256).regionHandle; + + const mi = new MapItemRequestMessage(); + mi.AgentData = { + AgentID: this.agent.agentID, + SessionID: circuit.sessionID, + Flags: 2, + EstateID: 0, + Godlike: false + }; + mi.RequestData = { + ItemType: GridItemType.AgentLocations, + RegionHandle: regionHandle + }; + circuit.sendMessage(mi, PacketFlags.Reliable); + const minX = gridX * 256; + const maxX = minX + 256; + const minY = gridY * 256; + const maxY = minY + 256; + response.avatars = []; + const responseMsg2: MapItemReplyMessage = await circuit.waitForMessage(Message.MapItemReply, 10000, (filterMsg: MapItemReplyMessage): FilterResponse => + { + let found = false; + for (const data of filterMsg.Data) + { + // Check if avatar is within our bounds + if (data.X >= minX && data.X <= maxX && data.Y >= minY && data.Y <= maxY) + { + found = true; + } + } + if (found) + { + return FilterResponse.Finish; + } + else + { + return FilterResponse.NoMatch; + } + }); + for (const data of responseMsg2.Data) + { + for (let index = 0; index <= data.Extra; index++) + { + response.avatars.push(new Vector2([ + data.X, + data.Y + ])); + } + } + return response; + } + + public async getRegionMapInfoRange(minX: number, minY: number, maxX: number, maxY: number): Promise + { + const response = new MapInfoRangeReplyEvent(); + try + { + const {circuit} = this.currentRegion; const msg: MapBlockRequestMessage = new MapBlockRequestMessage(); msg.AgentData = { AgentID: this.agent.agentID, @@ -228,7 +208,7 @@ export class GridCommands extends CommandsBase }; response.regions = []; circuit.sendMessage(msg, PacketFlags.Reliable); - circuit.waitForMessage(Message.MapBlockReply, 30000, (filterMsg: MapBlockReplyMessage): FilterResponse => + await circuit.waitForMessage(Message.MapBlockReply, 30000, (filterMsg: MapBlockReplyMessage): FilterResponse => { let found = false; for (const data of filterMsg.Data) @@ -252,28 +232,28 @@ export class GridCommands extends CommandsBase return FilterResponse.Match; } return FilterResponse.NoMatch; - }).then((_ignore: MapBlockReplyMessage) => - { - - }).catch((err) => - { - if (err instanceof TimeoutError && err.timeout) - { - resolve(response); - } - else - { - reject(err); - } }); - }); + + return response; + } + catch(err: unknown) + { + if (err instanceof TimeoutError && err.timeout) + { + return response; + } + else + { + throw err; + } + } } - async avatarName2KeyAndName(name: string, useCap = true): Promise<{ avatarKey: UUID, avatarName: string }> + public async avatarName2KeyAndName(name: string, useCap = true): Promise<{ avatarKey: UUID, avatarName: string }> { name = name.trim().replace('.', ' '); name = name.toLowerCase(); - if (name.trim().indexOf(' ') === -1) + if (!name.trim().includes(' ')) { name = name.trim() + ' resident'; } @@ -281,12 +261,19 @@ export class GridCommands extends CommandsBase if (useCap && await this.currentRegion.caps.isCapAvailable('AvatarPickerSearch')) { const trimmedName = name.replace(' resident', ''); - const result = await this.currentRegion.caps.capsGetXML(['AvatarPickerSearch', { page_size: '100', names: trimmedName }]); + const result = await this.currentRegion.caps.capsGetXML(['AvatarPickerSearch', { page_size: '100', names: trimmedName }]) as { + agents?: { + username?: string + legacy_first_name: string, + legacy_last_name: string, + id: string + }[] + }; if (result.agents) { for (const agent of result.agents) { - if (!agent.username) + if (agent.username === undefined) { continue; } @@ -304,7 +291,6 @@ export class GridCommands extends CommandsBase const queryID = UUID.random(); - const aprm = new AvatarPickerRequestMessage(); aprm.AgentData = { AgentID: this.agent.agentID, @@ -328,8 +314,8 @@ export class GridCommands extends CommandsBase } }); - let foundKey: UUID | undefined; - let foundName: string | undefined; + let foundKey: UUID | undefined = undefined; + let foundName: string | undefined = undefined; for (const dataBlock of apr.Data) { const resultName = (Utils.BufferToStringSimple(dataBlock.FirstName) + ' ' + @@ -354,13 +340,13 @@ export class GridCommands extends CommandsBase } } - async avatarName2Key(name: string, useCap = true): Promise + public async avatarName2Key(name: string, useCap = true): Promise { const result = await this.avatarName2KeyAndName(name, useCap); return result.avatarKey; } - async getBalance(): Promise + public async getBalance(): Promise { const msg = new MoneyBalanceRequestMessage(); msg.AgentData = { @@ -375,15 +361,15 @@ export class GridCommands extends CommandsBase return result.MoneyData.MoneyBalance; } - async payObject(target: GameObject, amount: number): Promise + public async payObject(target: GameObject, amount: number): Promise { - const description = target.name || 'Object'; + const description = target.name ?? 'Object'; const targetUUID = target.FullID; return this.pay(targetUUID, amount, description, MoneyTransactionType.PayObject); } - async payGroup(target: UUID | string, amount: number, description: string): Promise + public async payGroup(target: UUID | string, amount: number, description: string): Promise { if (typeof target === 'string') { @@ -393,7 +379,7 @@ export class GridCommands extends CommandsBase return this.pay(target, amount, description, MoneyTransactionType.Gift, TransactionFlags.DestGroup); } - async payAvatar(target: UUID | string, amount: number, description: string): Promise + public async payAvatar(target: UUID | string, amount: number, description: string): Promise { if (typeof target === 'string') { @@ -403,6 +389,85 @@ export class GridCommands extends CommandsBase return this.pay(target, amount, description, MoneyTransactionType.Gift, TransactionFlags.None); } + public async avatarKey2Name(uuid: UUID | UUID[]): Promise + { + const req = new UUIDNameRequestMessage(); + req.UUIDNameBlock = []; + let arr = true; + if (!Array.isArray(uuid)) + { + arr = false; + uuid = [uuid]; + } + + const waitingFor: Record = {}; + let remaining = 0; + + for (const id of uuid) + { + waitingFor[id.toString()] = null; + req.UUIDNameBlock.push({ 'ID': id }); + remaining++; + } + + this.circuit.sendMessage(req, PacketFlags.Reliable); + await this.circuit.waitForMessage(Message.UUIDNameReply, 10000, (reply: UUIDNameReplyMessage): FilterResponse => + { + let found = false; + for (const name of reply.UUIDNameBlock) + { + if (waitingFor[name.ID.toString()] !== undefined) + { + found = true; + if (waitingFor[name.ID.toString()] === null) + { + waitingFor[name.ID.toString()] = { + firstName: Utils.BufferToStringSimple(name.FirstName), + lastName: Utils.BufferToStringSimple(name.LastName) + }; + remaining--; + } + } + } + if (remaining < 1) + { + return FilterResponse.Finish; + } + else if (found) + { + return FilterResponse.Match; + } + return FilterResponse.NoMatch; + }); + if (!arr) + { + const result = waitingFor[uuid[0].toString()]; + if (result === null) + { + throw new Error('Avatar not found'); + } + return new AvatarQueryResult(uuid[0], result.firstName, result.lastName); + } + else + { + const response: AvatarQueryResult[] = []; + for (const k of uuid) + { + const result = waitingFor[k.toString()]; + if (result === null) + { + throw new Error('Avatar not found'); + } + const av = new AvatarQueryResult(k, result.firstName, result.lastName); + response.push(av); + } + return response; + } + } + private async pay(target: UUID, amount: number, description: string, type: MoneyTransactionType, flags: TransactionFlags = TransactionFlags.None): Promise { if (amount % 1 !== 0) @@ -439,83 +504,4 @@ export class GridCommands extends CommandsBase throw new Error('Payment failed'); } } - - avatarKey2Name(uuid: UUID | UUID[]): Promise - { - return new Promise(async(resolve, reject) => - { - const req = new UUIDNameRequestMessage(); - req.UUIDNameBlock = []; - let arr = true; - if (!Array.isArray(uuid)) - { - arr = false; - uuid = [uuid]; - } - - const waitingFor: any = {}; - let remaining = 0; - - for (const id of uuid) - { - waitingFor[id.toString()] = null; - req.UUIDNameBlock.push({ 'ID': id }); - remaining++; - } - - this.circuit.sendMessage(req, PacketFlags.Reliable); - try - { - await this.circuit.waitForMessage(Message.UUIDNameReply, 10000, (reply: UUIDNameReplyMessage): FilterResponse => - { - let found = false; - for (const name of reply.UUIDNameBlock) - { - if (waitingFor[name.ID.toString()] !== undefined) - { - found = true; - if (waitingFor[name.ID.toString()] === null) - { - waitingFor[name.ID.toString()] = { - 'firstName': Utils.BufferToStringSimple(name.FirstName), - 'lastName': Utils.BufferToStringSimple(name.LastName) - }; - remaining--; - } - } - } - if (remaining < 1) - { - return FilterResponse.Finish; - } - else if (found) - { - return FilterResponse.Match; - } - return FilterResponse.NoMatch; - }); - if (!arr) - { - const result = waitingFor[uuid[0].toString()]; - const av = new AvatarQueryResult(uuid[0], result.firstName, result.lastName); - resolve(av); - } - else - { - const response: AvatarQueryResult[] = []; - for (const k of uuid) - { - const result = waitingFor[k.toString()]; - const av = new AvatarQueryResult(k, result.firstName, result.lastName); - response.push(av); - } - resolve(response); - } - } - catch (e) - { - reject(e); - } - }); - } } diff --git a/lib/classes/commands/GroupCommands.ts b/lib/classes/commands/GroupCommands.ts index 09d0e8f..1262fe6 100644 --- a/lib/classes/commands/GroupCommands.ts +++ b/lib/classes/commands/GroupCommands.ts @@ -9,27 +9,27 @@ import { InviteGroupRequestMessage } from '../messages/InviteGroupRequest'; import { GroupRole } from '../GroupRole'; import { GroupRoleDataRequestMessage } from '../messages/GroupRoleDataRequest'; import { Message } from '../../enums/Message'; -import { GroupRoleDataReplyMessage } from '../messages/GroupRoleDataReply'; +import type { GroupRoleDataReplyMessage } from '../messages/GroupRoleDataReply'; import { GroupMember } from '../GroupMember'; import { FilterResponse } from '../../enums/FilterResponse'; import * as LLSD from '@caspertech/llsd'; import { EjectGroupMemberRequestMessage } from '../messages/EjectGroupMemberRequest'; import { GroupProfileRequestMessage } from '../messages/GroupProfileRequest'; -import { GroupProfileReplyMessage } from '../messages/GroupProfileReply'; +import type { GroupProfileReplyMessage } from '../messages/GroupProfileReply'; import { GroupBanAction } from '../../enums/GroupBanAction'; import { GroupBan } from '../GroupBan'; -import { GroupInviteEvent } from '../../events/GroupInviteEvent'; -import { GroupProfileReplyEvent } from '../../events/GroupProfileReplyEvent'; +import type { GroupInviteEvent } from '../../events/GroupInviteEvent'; +import type { GroupProfileReplyEvent } from '../../events/GroupProfileReplyEvent'; export class GroupCommands extends CommandsBase { - async sendGroupNotice(groupID: UUID | string, subject: string, message: string): Promise + public async sendGroupNotice(groupID: UUID | string, subject: string, message: string): Promise { if (typeof groupID === 'string') { groupID = new UUID(groupID); } - const circuit = this.circuit; + const {circuit} = this; const agentName = this.agent.firstName + ' ' + this.agent.lastName; const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage(); im.AgentData = { @@ -54,10 +54,10 @@ export class GroupCommands extends CommandsBase EstateID: 0 }; const sequenceNo = circuit.sendMessage(im, PacketFlags.Reliable); - return await circuit.waitForAck(sequenceNo, 10000); + await circuit.waitForAck(sequenceNo, 10000); } - async sendGroupInviteBulk(groupID: UUID | string, sendTo: { + public async sendGroupInviteBulk(groupID: UUID | string, sendTo: { avatarID: UUID | string, roleID: UUID | string | undefined }[]): Promise @@ -96,10 +96,10 @@ export class GroupCommands extends CommandsBase } const sequenceNo = this.circuit.sendMessage(igr, PacketFlags.Reliable); - return await this.circuit.waitForAck(sequenceNo, 10000); + await this.circuit.waitForAck(sequenceNo, 10000); } - getSessionAgentCount(sessionID: UUID | string): number + public getSessionAgentCount(sessionID: UUID | string): number { if (typeof sessionID === 'string') { @@ -108,18 +108,18 @@ export class GroupCommands extends CommandsBase return this.agent.getSessionAgentCount(sessionID); } - async sendGroupInvite(groupID: UUID | string, to: UUID | string, role: UUID | string | undefined): Promise + public async sendGroupInvite(groupID: UUID | string, to: UUID | string, role: UUID | string | undefined): Promise { const sendTo = [{ avatarID: to, roleID: role }]; - return await this.sendGroupInviteBulk(groupID, sendTo); + await this.sendGroupInviteBulk(groupID, sendTo); } - async acceptGroupInvite(event: GroupInviteEvent): Promise + public async acceptGroupInvite(event: GroupInviteEvent): Promise { - const circuit = this.circuit; + const {circuit} = this; const agentName = this.agent.firstName + ' ' + this.agent.lastName; const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage(); im.AgentData = { @@ -144,12 +144,12 @@ export class GroupCommands extends CommandsBase EstateID: 0 }; const sequenceNo = circuit.sendMessage(im, PacketFlags.Reliable); - return await circuit.waitForAck(sequenceNo, 10000); + await circuit.waitForAck(sequenceNo, 10000); } - async rejectGroupInvite(event: GroupInviteEvent): Promise + public async rejectGroupInvite(event: GroupInviteEvent): Promise { - const circuit = this.circuit; + const {circuit} = this; const agentName = this.agent.firstName + ' ' + this.agent.lastName; const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage(); im.AgentData = { @@ -174,15 +174,15 @@ export class GroupCommands extends CommandsBase EstateID: 0 }; const sequenceNo = circuit.sendMessage(im, PacketFlags.Reliable); - return await circuit.waitForAck(sequenceNo, 10000); + await circuit.waitForAck(sequenceNo, 10000); } - async unbanMembers(groupID: UUID | string, avatars: UUID | string | string[] | UUID[]): Promise + public async unbanMembers(groupID: UUID | string, avatars: UUID | string | string[] | UUID[]): Promise { return this.banMembers(groupID, avatars, GroupBanAction.Unban); } - async banMembers(groupID: UUID | string, avatars: UUID | string | string[] | UUID[], groupAction: GroupBanAction = GroupBanAction.Ban): Promise + public async banMembers(groupID: UUID | string, avatars: UUID | string | string[] | UUID[], groupAction: GroupBanAction = GroupBanAction.Ban): Promise { const listOfIDs: string[] = []; if (typeof groupID === 'string') @@ -212,7 +212,10 @@ export class GroupCommands extends CommandsBase listOfIDs.push(avatars.toString()); } - const requestData: any = { + const requestData: { + ban_action: GroupBanAction, + ban_ids: unknown[] + } = { 'ban_action': groupAction, 'ban_ids': [] }; @@ -224,7 +227,7 @@ export class GroupCommands extends CommandsBase await this.currentRegion.caps.capsPostXML(['GroupAPIv1', { 'group_id': groupID.toString() }], requestData); } - async getBanList(groupID: UUID | string): Promise + public async getBanList(groupID: UUID | string): Promise { if (typeof groupID === 'string') { @@ -242,7 +245,7 @@ export class GroupCommands extends CommandsBase return bans; } - async getMemberList(groupID: UUID | string): Promise + public async getMemberList(groupID: UUID | string): Promise { if (typeof groupID === 'string') { @@ -253,28 +256,39 @@ export class GroupCommands extends CommandsBase 'group_id': new LLSD.UUID(groupID.toString()) }; - const response: any = await this.currentRegion.caps.capsPostXML('GroupMemberData', requestData); - if (response['members']) + const response = await this.currentRegion.caps.capsPostXML('GroupMemberData', requestData) as { + members?: Record, + titles: Record, + defaults: { + default_powers: string + } + }; + if (response.members !== undefined) { - for (const uuid of Object.keys(response['members'])) + for (const uuid of Object.keys(response.members)) { const member = new GroupMember(); - const data = response['members'][uuid]; + const data = response.members[uuid]; member.AgentID = new UUID(uuid); - member.OnlineStatus = data['last_login']; - let powers = response['defaults']['default_powers']; - if (data['powers']) + member.OnlineStatus = data.last_login; + let powers = response.defaults.default_powers; + if (data.powers) { - powers = data['powers']; + powers = data.powers; } - member.IsOwner = data['owner'] === 'Y'; + member.IsOwner = data.owner === 'Y'; let titleIndex = 0; - if (data['title']) + if (data.title) { - titleIndex = data['title']; + titleIndex = data.title; } - member.Title = response['titles'][titleIndex]; + member.Title = response.titles[titleIndex]; member.AgentPowers = Utils.HexToLong(powers); result.push(member); @@ -287,75 +301,60 @@ export class GroupCommands extends CommandsBase } } - getGroupRoles(groupID: UUID | string): Promise + public async getGroupRoles(groupID: UUID | string): Promise { - return new Promise((resolve, reject) => + const result: GroupRole[] = []; + if (typeof groupID === 'string') { - const result: GroupRole[] = []; - if (typeof groupID === 'string') - { - groupID = new UUID(groupID); - } - const grdr = new GroupRoleDataRequestMessage(); - grdr.AgentData = { - AgentID: this.agent.agentID, - SessionID: this.circuit.sessionID - }; - const requestID = UUID.random(); - grdr.GroupData = { - GroupID: groupID, - RequestID: requestID - }; - let totalRoleCount = 0; + groupID = new UUID(groupID); + } + const grdr = new GroupRoleDataRequestMessage(); + grdr.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.circuit.sessionID + }; + const requestID = UUID.random(); + grdr.GroupData = { + GroupID: groupID, + RequestID: requestID + }; + let totalRoleCount = 0; - this.circuit.sendMessage(grdr, PacketFlags.Reliable); - this.circuit.waitForMessage(Message.GroupRoleDataReply, 10000, (gmr: GroupRoleDataReplyMessage): FilterResponse => + this.circuit.sendMessage(grdr, PacketFlags.Reliable); + await this.circuit.waitForMessage(Message.GroupRoleDataReply, 10000, (gmr: GroupRoleDataReplyMessage): FilterResponse => + { + if (gmr.GroupData.RequestID.toString() === requestID.toString()) { - if (gmr.GroupData.RequestID.toString() === requestID.toString()) + totalRoleCount = gmr.GroupData.RoleCount; + for (const role of gmr.RoleData) { - totalRoleCount = gmr.GroupData.RoleCount; - for (const role of gmr.RoleData) - { - const gr = new GroupRole(); - gr.RoleID = role.RoleID; - gr.Name = Utils.BufferToStringSimple(role.Name); - gr.Title = Utils.BufferToStringSimple(role.Title); - gr.Description = Utils.BufferToStringSimple(role.Description); - gr.Powers = role.Powers; - gr.Members = role.Members; - result.push(gr); - } - if (totalRoleCount > result.length) - { - return FilterResponse.Match; - } - else - { - return FilterResponse.Finish; - } + const gr = new GroupRole(); + gr.RoleID = role.RoleID; + gr.Name = Utils.BufferToStringSimple(role.Name); + gr.Title = Utils.BufferToStringSimple(role.Title); + gr.Description = Utils.BufferToStringSimple(role.Description); + gr.Powers = role.Powers; + gr.Members = role.Members; + result.push(gr); + } + if (totalRoleCount > result.length) + { + return FilterResponse.Match; } else { - return FilterResponse.NoMatch; + return FilterResponse.Finish; } - }).then(() => + } + else { - resolve(result); - }).catch((err) => - { - if (result.length === 0) - { - reject(err); - } - else - { - resolve(err); - } - }); + return FilterResponse.NoMatch; + } }); + return result; } - async ejectFromGroupBulk(groupID: UUID | string, sendTo: UUID[] | string[]): Promise + public async ejectFromGroupBulk(groupID: UUID | string, sendTo: UUID[] | string[]): Promise { if (typeof groupID === 'string') { @@ -385,10 +384,10 @@ export class GroupCommands extends CommandsBase }; const sequenceNo = this.circuit.sendMessage(msg, PacketFlags.Reliable); - return await this.circuit.waitForAck(sequenceNo, 10000); + await this.circuit.waitForAck(sequenceNo, 10000); } - async ejectFromGroup(groupID: UUID | string, ejecteeID: UUID | string): Promise + public async ejectFromGroup(groupID: UUID | string, ejecteeID: UUID | string): Promise { if (typeof ejecteeID === 'string') { @@ -397,10 +396,10 @@ export class GroupCommands extends CommandsBase const sendTo: UUID[] = [ejecteeID]; - return await this.ejectFromGroupBulk(groupID, sendTo); + await this.ejectFromGroupBulk(groupID, sendTo); } - async getGroupProfile(groupID: UUID | string): Promise + public async getGroupProfile(groupID: UUID | string): Promise { if (typeof groupID === 'string') { @@ -421,7 +420,7 @@ export class GroupCommands extends CommandsBase const groupProfileReply: GroupProfileReplyMessage = (await this.circuit.waitForMessage(Message.GroupProfileReply, 10000, (packet: GroupProfileReplyMessage): FilterResponse => { - const replyMessage: GroupProfileReplyMessage = packet as GroupProfileReplyMessage; + const replyMessage: GroupProfileReplyMessage = packet; if (replyMessage.GroupData.GroupID.equals(groupID)) { console.log('groupProfileReply Finish'); @@ -429,26 +428,26 @@ export class GroupCommands extends CommandsBase } console.log('groupProfileReply NoMatch'); return FilterResponse.NoMatch; - })) as GroupProfileReplyMessage; + })); return new class implements GroupProfileReplyEvent { - GroupID = groupProfileReply.GroupData.GroupID; - Name = Utils.BufferToStringSimple(groupProfileReply.GroupData.Name); - Charter = Utils.BufferToStringSimple(groupProfileReply.GroupData.Charter); - ShowInList = groupProfileReply.GroupData.ShowInList; - MemberTitle = Utils.BufferToStringSimple(groupProfileReply.GroupData.MemberTitle); - PowersMask = groupProfileReply.GroupData.PowersMask; - InsigniaID = groupProfileReply.GroupData.InsigniaID; - FounderID = groupProfileReply.GroupData.FounderID; - MembershipFee = groupProfileReply.GroupData.MembershipFee; - OpenEnrollment = groupProfileReply.GroupData.OpenEnrollment; - Money = groupProfileReply.GroupData.Money; - GroupMembershipCount = groupProfileReply.GroupData.GroupMembershipCount; - GroupRolesCount = groupProfileReply.GroupData.GroupRolesCount; - AllowPublish = groupProfileReply.GroupData.AllowPublish; - MaturePublish = groupProfileReply.GroupData.MaturePublish; - OwnerRole = groupProfileReply.GroupData.OwnerRole; + public GroupID = groupProfileReply.GroupData.GroupID; + public Name = Utils.BufferToStringSimple(groupProfileReply.GroupData.Name); + public Charter = Utils.BufferToStringSimple(groupProfileReply.GroupData.Charter); + public ShowInList = groupProfileReply.GroupData.ShowInList; + public MemberTitle = Utils.BufferToStringSimple(groupProfileReply.GroupData.MemberTitle); + public PowersMask = groupProfileReply.GroupData.PowersMask; + public InsigniaID = groupProfileReply.GroupData.InsigniaID; + public FounderID = groupProfileReply.GroupData.FounderID; + public MembershipFee = groupProfileReply.GroupData.MembershipFee; + public OpenEnrollment = groupProfileReply.GroupData.OpenEnrollment; + public Money = groupProfileReply.GroupData.Money; + public GroupMembershipCount = groupProfileReply.GroupData.GroupMembershipCount; + public GroupRolesCount = groupProfileReply.GroupData.GroupRolesCount; + public AllowPublish = groupProfileReply.GroupData.AllowPublish; + public MaturePublish = groupProfileReply.GroupData.MaturePublish; + public OwnerRole = groupProfileReply.GroupData.OwnerRole; }; } } diff --git a/lib/classes/commands/InventoryCommands.ts b/lib/classes/commands/InventoryCommands.ts index 37ba611..8de64b5 100644 --- a/lib/classes/commands/InventoryCommands.ts +++ b/lib/classes/commands/InventoryCommands.ts @@ -1,26 +1,68 @@ import { CommandsBase } from './CommandsBase'; -import { InventoryFolder } from '../InventoryFolder'; +import type { InventoryFolder } from '../InventoryFolder'; import { InstantMessageDialog } from '../../enums/InstantMessageDialog'; import { ImprovedInstantMessageMessage } from '../messages/ImprovedInstantMessage'; import { Utils } from '../Utils'; -import { FolderType } from '../../enums/FolderType'; -import { InventoryOfferedEvent } from '../../events/InventoryOfferedEvent'; +import type { FolderType } from '../../enums/FolderType'; +import type { InventoryOfferedEvent } from '../../events/InventoryOfferedEvent'; import { UUID } from '../UUID'; import { Vector3 } from '../Vector3'; import { PacketFlags } from '../../enums/PacketFlags'; import { ChatSourceType } from '../../enums/ChatSourceType'; -import { InventoryItem } from '../InventoryItem'; +import type { InventoryItem } from '../InventoryItem'; export class InventoryCommands extends CommandsBase { - getInventoryRoot(): InventoryFolder + public getInventoryRoot(): InventoryFolder { return this.agent.inventory.getRootFolderMain(); } - getLibraryRoot(): InventoryFolder + public getLibraryRoot(): InventoryFolder { return this.agent.inventory.getRootFolderLibrary(); } + + public async getInventoryItem(item: UUID | string): Promise + { + if (typeof item === 'string') + { + item = new UUID(item); + } + const result = await this.currentRegion.agent.inventory.fetchInventoryItem(item); + if (result === null) + { + throw new Error('Unable to get inventory item'); + } + else + { + return result; + } + } + + public async acceptInventoryOffer(event: InventoryOfferedEvent): Promise + { + if (event.source === ChatSourceType.Object) + { + return this.respondToInventoryOffer(event, InstantMessageDialog.TaskInventoryAccepted); + } + else + { + return this.respondToInventoryOffer(event, InstantMessageDialog.InventoryAccepted); + } + } + + public async rejectInventoryOffer(event: InventoryOfferedEvent): Promise + { + if (event.source === ChatSourceType.Object) + { + await this.respondToInventoryOffer(event, InstantMessageDialog.TaskInventoryDeclined); return; + } + else + { + await this.respondToInventoryOffer(event, InstantMessageDialog.InventoryDeclined); return; + } + } + private async respondToInventoryOffer(event: InventoryOfferedEvent, response: InstantMessageDialog): Promise { const agentName = this.agent.firstName + ' ' + this.agent.lastName; @@ -49,47 +91,6 @@ export class InventoryCommands extends CommandsBase BinaryBucket: binary }; const sequenceNo = this.circuit.sendMessage(im, PacketFlags.Reliable); - return await this.circuit.waitForAck(sequenceNo, 10000); - } - - async getInventoryItem(item: UUID | string): Promise - { - if (typeof item === 'string') - { - item = new UUID(item); - } - const result = await this.currentRegion.agent.inventory.fetchInventoryItem(item); - if (result === null) - { - throw new Error('Unable to get inventory item'); - } - else - { - return result; - } - } - - async acceptInventoryOffer(event: InventoryOfferedEvent): Promise - { - if (event.source === ChatSourceType.Object) - { - return await this.respondToInventoryOffer(event, InstantMessageDialog.TaskInventoryAccepted); - } - else - { - return await this.respondToInventoryOffer(event, InstantMessageDialog.InventoryAccepted); - } - } - - async rejectInventoryOffer(event: InventoryOfferedEvent): Promise - { - if (event.source === ChatSourceType.Object) - { - return await this.respondToInventoryOffer(event, InstantMessageDialog.TaskInventoryDeclined); - } - else - { - return await this.respondToInventoryOffer(event, InstantMessageDialog.InventoryDeclined); - } + return this.circuit.waitForAck(sequenceNo, 10000); } } diff --git a/lib/classes/commands/MovementCommands.ts b/lib/classes/commands/MovementCommands.ts index b3dccc3..f569c6d 100644 --- a/lib/classes/commands/MovementCommands.ts +++ b/lib/classes/commands/MovementCommands.ts @@ -1,24 +1,24 @@ import { ControlFlags, PacketFlags } from "../.."; import { AgentRequestSitMessage, AgentSitMessage } from "../MessageClasses"; -import { UUID } from "../UUID"; -import { Vector3 } from "../Vector3"; +import type { UUID } from "../UUID"; +import type { Vector3 } from "../Vector3"; import { CommandsBase } from "./CommandsBase"; export class MovementCommands extends CommandsBase { - async sitOnObject(targetID: UUID, offset: Vector3): Promise + public async sitOnObject(targetID: UUID, offset: Vector3): Promise { await this.requestSitOnObject(targetID, offset); await this.sitOn(); } - sitOnGround(): void + public sitOnGround(): void { this.agent.setControlFlag(ControlFlags.AGENT_CONTROL_SIT_ON_GROUND); this.agent.sendAgentUpdate(); } - stand(): void + public stand(): void { this.agent.clearControlFlag(ControlFlags.AGENT_CONTROL_SIT_ON_GROUND); this.agent.setControlFlag(ControlFlags.AGENT_CONTROL_STAND_UP); diff --git a/lib/classes/commands/NetworkCommands.ts b/lib/classes/commands/NetworkCommands.ts index e24c444..6f794db 100644 --- a/lib/classes/commands/NetworkCommands.ts +++ b/lib/classes/commands/NetworkCommands.ts @@ -6,7 +6,7 @@ export class NetworkCommands extends CommandsBase { private throttleGenCounter = 0; - async setBandwidth(total: number): Promise + public async setBandwidth(total: number): Promise { const agentThrottle: AgentThrottleMessage = new AgentThrottleMessage(); agentThrottle.AgentData = { @@ -46,6 +46,6 @@ export class NetworkCommands extends CommandsBase Throttles: throttleData }; const sequenceNo = this.circuit.sendMessage(agentThrottle, PacketFlags.Reliable); - return await this.circuit.waitForAck(sequenceNo, 10000); + return this.circuit.waitForAck(sequenceNo, 10000); } } diff --git a/lib/classes/commands/ParcelCommands.ts b/lib/classes/commands/ParcelCommands.ts index b576190..7ebde14 100644 --- a/lib/classes/commands/ParcelCommands.ts +++ b/lib/classes/commands/ParcelCommands.ts @@ -2,35 +2,29 @@ import { CommandsBase } from './CommandsBase'; import { ParcelInfoRequestMessage } from '../messages/ParcelInfoRequest'; import { UUID } from '../UUID'; import { Message } from '../../enums/Message'; -import { ParcelInfoReplyMessage } from '../messages/ParcelInfoReply'; +import type { ParcelInfoReplyMessage } from '../messages/ParcelInfoReply'; import { FilterResponse } from '../../enums/FilterResponse'; import { Utils } from '../Utils'; -import { ParcelInfoReplyEvent } from '../../events/ParcelInfoReplyEvent'; +import type { ParcelInfoReplyEvent } from '../../events/ParcelInfoReplyEvent'; import { PacketFlags } from '../../enums/PacketFlags'; import { Vector3 } from '../Vector3'; import { LandStatRequestMessage } from '../messages/LandStatRequest'; -import { LandStatReportType } from '../../enums/LandStatReportType'; -import { LandStatFlags } from '../../enums/LandStatFlags'; -import { LandStatsEvent } from '../../events/LandStatsEvent'; +import type { LandStatReportType } from '../../enums/LandStatReportType'; +import type { LandStatFlags } from '../../enums/LandStatFlags'; +import type { LandStatsEvent } from '../../events/LandStatsEvent'; // This class was added to provide a new "Category" of commands, since we don't have any parcel specific functionality yet. export class ParcelCommands extends CommandsBase { - async getParcelInfo(parcelID: UUID | string): Promise + public async getParcelInfo(parcelID: UUID | string): Promise { - // Since this is a userspace command, we are kind and accept the UUID as a string. - // If it's a string, then we convert to UUID. - if (typeof parcelID === 'string') { parcelID = new UUID(parcelID); } - // Create a new ParcelInfoRequest message, which is the type that we want const msg: ParcelInfoRequestMessage = new ParcelInfoRequestMessage(); - - // Fill the message with the correct data (see ParcelInfoRequest.ts) msg.AgentData = { AgentID: this.agent.agentID, SessionID: this.circuit.sessionID @@ -39,49 +33,35 @@ export class ParcelCommands extends CommandsBase ParcelID: parcelID }; - // Shove the message into our send queue this.circuit.sendMessage(msg, PacketFlags.Reliable); - // And wait for a reply. It's okay to do this after we send since we haven't yielded until this subscription is set up. const parcelInfoReply = (await this.circuit.waitForMessage(Message.ParcelInfoRequest, 10000, (replyMessage: ParcelInfoReplyMessage): FilterResponse => { - // This function is here as a filter to ensure we get the correct message. - // It compares every incoming ParcelInfoReplyMessage, checks the ParcelID and compares to the one we requested. if (replyMessage.Data.ParcelID.equals(parcelID)) { - // We received a reply for the ParcelID that we requested info for, so return with "Finish" because we don't want any more after this. - // If we are expecting multiple replies we can reply with FilterResponse.Match which will keep the listener open. return FilterResponse.Finish; } return FilterResponse.NoMatch; })); - // parcelInfoReply will now contain the message that we issued a "Finish" response for. - // In the event of an error or timeout, an exception would have been thrown and this code won't be reached. - - // Rather than simply returning the message, we convert the data into an "Event" which is supposed to be - // a bit more user friendly for the user. - return new class implements ParcelInfoReplyEvent { - OwnerID = parcelInfoReply.Data.OwnerID; - - // Because Data.Name is a buffer, we have a helper function to decode it. - ParcelName = Utils.BufferToStringSimple(parcelInfoReply.Data.Name); - ParcelDescription = Utils.BufferToStringSimple(parcelInfoReply.Data.Desc); - Area = parcelInfoReply.Data.ActualArea; - BillableArea = parcelInfoReply.Data.BillableArea; - Flags = parcelInfoReply.Data.Flags; - GlobalCoordinates = new Vector3([parcelInfoReply.Data.GlobalX, parcelInfoReply.Data.GlobalY, parcelInfoReply.Data.GlobalZ]); - RegionName = Utils.BufferToStringSimple(parcelInfoReply.Data.SimName); - SnapshotID = parcelInfoReply.Data.SnapshotID; - Traffic = parcelInfoReply.Data.Dwell; - SalePrice = parcelInfoReply.Data.SalePrice; - AuctionID = parcelInfoReply.Data.AuctionID; + public OwnerID = parcelInfoReply.Data.OwnerID; + public ParcelName = Utils.BufferToStringSimple(parcelInfoReply.Data.Name); + public ParcelDescription = Utils.BufferToStringSimple(parcelInfoReply.Data.Desc); + public Area = parcelInfoReply.Data.ActualArea; + public BillableArea = parcelInfoReply.Data.BillableArea; + public Flags = parcelInfoReply.Data.Flags; + public GlobalCoordinates = new Vector3([parcelInfoReply.Data.GlobalX, parcelInfoReply.Data.GlobalY, parcelInfoReply.Data.GlobalZ]); + public RegionName = Utils.BufferToStringSimple(parcelInfoReply.Data.SimName); + public SnapshotID = parcelInfoReply.Data.SnapshotID; + public Traffic = parcelInfoReply.Data.Dwell; + public SalePrice = parcelInfoReply.Data.SalePrice; + public AuctionID = parcelInfoReply.Data.AuctionID; }; } - async getLandStats(parcelID: string | UUID | number, reportType: LandStatReportType, flags: LandStatFlags, filter?: string): Promise + public async getLandStats(parcelID: string | UUID | number, reportType: LandStatReportType, flags: LandStatFlags, filter?: string): Promise { if (parcelID instanceof UUID) { @@ -90,7 +70,6 @@ export class ParcelCommands extends CommandsBase if (typeof parcelID === 'string') { - // Find the parcel localID const parcels = await this.bot.clientCommands.region.getParcels(); for (const parcel of parcels) { diff --git a/lib/classes/commands/RegionCommands.ts b/lib/classes/commands/RegionCommands.ts index 59e0312..63daf78 100644 --- a/lib/classes/commands/RegionCommands.ts +++ b/lib/classes/commands/RegionCommands.ts @@ -1,8 +1,8 @@ import * as LLSD from '@caspertech/llsd'; -import * as Long from 'long'; +import type * as Long from 'long'; import * as micromatch from 'micromatch'; -import { Subscription } from 'rxjs'; +import type { Subscription } from 'rxjs'; import { SculptType } from '../../enums/SculptType'; import { FilterResponse } from '../../enums/FilterResponse'; import { InventoryType } from '../../enums/InventoryType'; @@ -10,8 +10,7 @@ import { Message } from '../../enums/Message'; import { PacketFlags } from '../../enums/PacketFlags'; import { PCode } from '../../enums/PCode'; import { PrimFlags } from '../../enums/PrimFlags'; -import { NewObjectEvent } from '../../events/NewObjectEvent'; -import { AssetMap } from '../AssetMap'; +import type { NewObjectEvent } from '../../events/NewObjectEvent'; import { BuildMap } from '../BuildMap'; import { EstateOwnerMessageMessage } from '../messages/EstateOwnerMessage'; import { ObjectAddMessage } from '../messages/ObjectAdd'; @@ -19,32 +18,39 @@ import { ObjectDeGrabMessage } from '../messages/ObjectDeGrab'; import { ObjectDeselectMessage } from '../messages/ObjectDeselect'; import { ObjectGrabMessage } from '../messages/ObjectGrab'; import { ObjectGrabUpdateMessage } from '../messages/ObjectGrabUpdate'; -import { ObjectPropertiesMessage } from '../messages/ObjectProperties'; +import type { ObjectPropertiesMessage } from '../messages/ObjectProperties'; import { ObjectSelectMessage } from '../messages/ObjectSelect'; import { RegionHandleRequestMessage } from '../messages/RegionHandleRequest'; -import { RegionIDAndHandleReplyMessage } from '../messages/RegionIDAndHandleReply'; -import { Avatar } from '../public/Avatar'; +import type { RegionIDAndHandleReplyMessage } from '../messages/RegionIDAndHandleReply'; +import type { Avatar } from '../public/Avatar'; import { GameObject } from '../public/GameObject'; -import { Parcel } from '../public/Parcel'; +import type { Parcel } from '../public/Parcel'; import { Quaternion } from '../Quaternion'; import { Utils } from '../Utils'; import { UUID } from '../UUID'; import { Vector3 } from '../Vector3'; import { CommandsBase } from './CommandsBase'; +import { PrimFacesHelper } from '../PrimFacesHelper'; +import { Logger } from '../Logger'; +import { AssetType } from '../../enums/AssetType'; import Timeout = NodeJS.Timeout; +import type { AssetRegistry } from '../AssetRegistry'; export interface GetObjectsOptions { resolve?: boolean; + forceReResolve?: boolean; includeTempObjects?: boolean; includeAvatars?: boolean; + includeAttachments?: boolean; } export class RegionCommands extends CommandsBase { - async getRegionHandle(regionID: UUID): Promise + // noinspection JSUnusedGlobalSymbols + public async getRegionHandle(regionID: UUID): Promise { - const circuit = this.currentRegion.circuit; + const {circuit} = this.currentRegion; const msg: RegionHandleRequestMessage = new RegionHandleRequestMessage(); msg.RequestBlock = { RegionID: regionID, @@ -64,7 +70,8 @@ export class RegionCommands extends CommandsBase return responseMsg.ReplyBlock.RegionHandle; } - waitForHandshake(timeout: number = 10000): Promise + // noinspection JSUnusedGlobalSymbols + public async waitForHandshake(timeout = 10000): Promise { return new Promise((resolve, reject) => { @@ -74,8 +81,8 @@ export class RegionCommands extends CommandsBase } else { - let handshakeSubscription: Subscription | undefined; - let timeoutTimer: number | undefined; + let handshakeSubscription: Subscription | undefined = undefined; + let timeoutTimer: number | undefined = undefined; handshakeSubscription = this.currentRegion.handshakeCompleteEvent.subscribe(() => { if (timeoutTimer !== undefined) @@ -103,7 +110,7 @@ export class RegionCommands extends CommandsBase timeoutTimer = undefined; reject(new Error('Timeout')); } - }, timeout) as any as number; + }, timeout) as unknown as number; if (this.currentRegion.handshakeComplete) { if (handshakeSubscription !== undefined) @@ -122,7 +129,7 @@ export class RegionCommands extends CommandsBase }); } - estateMessage(method: string, params: string[]): Promise + public async estateMessage(method: string, params: string[]): Promise { const msg = new EstateOwnerMessageMessage(); msg.AgentData = { @@ -145,12 +152,12 @@ export class RegionCommands extends CommandsBase return this.circuit.waitForAck(sequenceID, 10000); } - restartRegion(secs: number): Promise + public async restartRegion(secs: number): Promise { return this.estateMessage('restart', [String(secs)]); } - simulatorMessage(message: string): Promise + public async simulatorMessage(message: string): Promise { return this.estateMessage('simulatormessage', [ '-1', @@ -161,17 +168,17 @@ export class RegionCommands extends CommandsBase ]); } - cancelRestart(): Promise + public async cancelRestart(): Promise { return this.restartRegion(-1); } - getAvatarsInRegion(): Avatar[] + public getAvatarsInRegion(): Avatar[] { - return Object.values(this.currentRegion.agents); + return Array.from(this.currentRegion.agents.values()); } - async deselectObjects(objects: GameObject[]): Promise + public async deselectObjects(objects: GameObject[]): Promise { // Limit to 255 objects at once const selectLimit = 255; @@ -199,7 +206,7 @@ export class RegionCommands extends CommandsBase SessionID: this.circuit.sessionID }; deselectObject.ObjectData = []; - const uuidMap: { [key: string]: GameObject } = {}; + const uuidMap = new Set(); let skipped = 0; for (const obj of objects) { @@ -209,9 +216,9 @@ export class RegionCommands extends CommandsBase continue; } const uuidStr = obj.FullID.toString(); - if (!uuidMap[uuidStr]) + if (!uuidMap.has(uuidStr)) { - uuidMap[uuidStr] = obj; + uuidMap.add(uuidStr); deselectObject.ObjectData.push({ ObjectLocalID: obj.ID }); @@ -227,12 +234,14 @@ export class RegionCommands extends CommandsBase } } - countObjects(): number + // noinspection JSUnusedGlobalSymbols + public countObjects(): number { return this.currentRegion.objects.getNumberOfObjects(); } - getTerrainTextures(): UUID[] + // noinspection JSUnusedGlobalSymbols + public getTerrainTextures(): UUID[] { const textures: UUID[] = []; textures.push(this.currentRegion.terrainDetail0); @@ -242,12 +251,14 @@ export class RegionCommands extends CommandsBase return textures; } - exportSettings(): string + // noinspection JSUnusedGlobalSymbols + public exportSettings(): string { return this.currentRegion.exportXML(); } - async getTerrain(): Promise + // noinspection JSUnusedGlobalSymbols + public async getTerrain(): Promise { await this.currentRegion.waitForTerrain(); const buf = Buffer.allocUnsafe(262144); @@ -263,7 +274,7 @@ export class RegionCommands extends CommandsBase return buf; } - async selectObjects(objects: GameObject[]): Promise + public async selectObjects(objects: GameObject[]): Promise { // Limit to 255 objects at once const selectLimit = 255; @@ -291,7 +302,7 @@ export class RegionCommands extends CommandsBase SessionID: this.circuit.sessionID }; selectObject.ObjectData = []; - const uuidMap: { [key: string]: GameObject } = {}; + const uuidMap = new Map(); let skipped = 0; for (const obj of objects) { @@ -301,9 +312,9 @@ export class RegionCommands extends CommandsBase continue; } const uuidStr = obj.FullID.toString(); - if (!uuidMap[uuidStr]) + if (!uuidMap.has(uuidStr)) { - uuidMap[uuidStr] = obj; + uuidMap.set(uuidStr, obj); selectObject.ObjectData.push({ ObjectLocalID: obj.ID }); @@ -324,9 +335,9 @@ export class RegionCommands extends CommandsBase for (const objData of propertiesMessage.ObjectData) { const objDataUUID = objData.ObjectID.toString(); - if (uuidMap[objDataUUID] !== undefined) + const obj = uuidMap.get(objDataUUID); + if (obj !== undefined) { - const obj = uuidMap[objDataUUID]; obj.creatorID = objData.CreatorID; obj.creationDate = objData.CreationDate; obj.baseMask = objData.BaseMask; @@ -354,7 +365,7 @@ export class RegionCommands extends CommandsBase obj.sitName = Utils.BufferToStringSimple(objData.SitName); obj.textureID = Utils.BufferToStringSimple(objData.TextureID); obj.resolvedAt = new Date().getTime() / 1000; - delete uuidMap[objDataUUID]; + uuidMap.delete(objDataUUID); found = true; } } @@ -372,55 +383,720 @@ export class RegionCommands extends CommandsBase } }); } - catch (error) + catch (_error: unknown) { - + // Ignore any errors } } } - getName(): string + // noinspection JSUnusedGlobalSymbols + public getName(): string { return this.currentRegion.regionName; } - async resolveObject(object: GameObject, options: GetObjectsOptions): Promise + public async resolveObject(object: GameObject, options: GetObjectsOptions): Promise { return this.currentRegion.resolver.resolveObjects([object], options); } - async resolveObjects(objects: GameObject[], options: GetObjectsOptions): Promise + // noinspection JSUnusedGlobalSymbols + public async resolveObjects(objects: GameObject[], options: GetObjectsOptions): Promise { return this.currentRegion.resolver.resolveObjects(objects, options); } + // noinspection JSUnusedGlobalSymbols public async fetchObjectInventory(object: GameObject): Promise { return this.currentRegion.resolver.getInventory(object); } + // noinspection JSUnusedGlobalSymbols public async fetchObjectInventories(objects: GameObject[]): Promise { return this.currentRegion.resolver.getInventories(objects); } + // noinspection JSUnusedGlobalSymbols public async fetchObjectCost(object: GameObject): Promise { return this.currentRegion.resolver.getCosts([object]); } + // noinspection JSUnusedGlobalSymbols public async fetchObjectCosts(objects: GameObject[]): Promise { return this.currentRegion.resolver.getCosts(objects); } + // noinspection JSUnusedGlobalSymbols + public async buildObjectNew(obj: GameObject, map: AssetRegistry, callback: (map: AssetRegistry) => Promise, costOnly = false, skipMove = false): Promise + { + const buildMap = new BuildMap(map, callback, costOnly); + this.gatherAssets(obj, buildMap); + const bomTextures = [ + '5a9f4a74-30f2-821c-b88d-70499d3e7183', + 'ae2de45c-d252-50b8-5c6e-19f39ce79317', + '24daea5f-0539-cfcf-047f-fbc40b2786ba', + '52cc6bb6-2ee5-e632-d3ad-50197b1dcb8a', + '43529ce8-7faa-ad92-165a-bc4078371687', + '09aac1fb-6bce-0bee-7d44-caac6dbb6c63', + 'ff62763f-d60a-9855-890b-0c96f8f8cd98', + '8e915e25-31d1-cc95-ae08-d58a47488251', + '9742065b-19b5-297c-858a-29711d539043', + '03642e83-2bd1-4eb9-34b4-4c47ed586d2d', + 'edd51b77-fc10-ce7a-4b3d-011dfc349e4f', + 'c228d1cf-4b5d-4ba8-84f4-899a0796aa97' // 'non existent asset' + ]; + for (const bomTexture of bomTextures) + { + buildMap.assetMap.textures.delete(bomTexture); + } + await callback(map); - private waitForObjectByLocalID(localID: number, timeout: number): Promise + if (costOnly) + { + return null; + } + + let agentPos = new Vector3([128, 128, 2048]); + try + { + const agentLocalID = this.currentRegion.agent.localID; + const agentObject = this.currentRegion.objects.getObjectByLocalID(agentLocalID); + if (agentObject.Position !== undefined) + { + agentPos = new Vector3(agentObject.Position); + } + else + { + throw new Error('Agent position is undefined'); + } + } + catch (_error: unknown) + { + console.warn('Unable to find avatar location, rezzing at ' + agentPos.toString()); + } + agentPos.z += 2.0; + buildMap.rezLocation = agentPos; + // Set camera above target location for fast acquisition + const campos = new Vector3(agentPos); + campos.z += 2.0; + this.currentRegion.clientCommands.agent.setCamera(campos, agentPos, 10, new Vector3([-1.0, 0, 0]), new Vector3([0.0, 1.0, 0])); + + if (buildMap.primsNeeded > 0) + { + buildMap.primReservoir = await this.createPrims(buildMap.primsNeeded, agentPos); + } + + let storedPosition: Vector3 | undefined = undefined; + if (skipMove) + { + storedPosition = obj.Position; + obj.Position = new Vector3(buildMap.rezLocation); + } + + const parts = []; + parts.push(async() => + { + return { + index: 1, + object: await this.buildPart(obj, Vector3.getZero(), Quaternion.getIdentity(), buildMap, true) + } + }); + + if (obj.children) + { + if (obj.Position === undefined) + { + obj.Position = Vector3.getZero(); + } + if (obj.Rotation === undefined) + { + obj.Rotation = Quaternion.getIdentity(); + } + let childIndex = 2; + for (const child of obj.children) + { + if (child.Position !== undefined && child.Rotation !== undefined) + { + const index = childIndex++; + parts.push(async() => + { + return { + index, + object: await this.buildPart(child, new Vector3(obj.Position), new Quaternion(obj.Rotation), buildMap, false) + } + }); + } + } + } + + let results: { + results: { + index: number, + object: GameObject + }[], + errors: unknown[] + } = { + results: [], + errors: [] + }; + results = await Utils.promiseConcurrent<{ + index: number, + object: GameObject + }>(parts, 5, 0); + if (results.errors.length > 0) + { + for (const err of results.errors) + { + console.error(err); + } + } + + let rootObj: GameObject | null = null; + for (const childObject of results.results) + { + if (childObject.object.isMarkedRoot) + { + rootObj = childObject.object; + break; + } + } + if (rootObj === null) + { + throw new Error('Failed to find root prim..'); + } + + const childPrims: GameObject[] = []; + results.results.sort((a: { index: number, object: GameObject }, b: { index: number, object: GameObject }) => + { + return a.index - b.index; + }); + for (const childObject of results.results) + { + if (childObject.object !== rootObj) + { + childPrims.push(childObject.object); + } + } + try + { + await rootObj.linkFrom(childPrims); + } + catch (err) + { + console.error('Link failed:'); + console.error(err); + } + if (storedPosition !== undefined) + { + obj.Position = storedPosition; + } + return rootObj; + } + + // noinspection JSUnusedGlobalSymbols + public async getObjectByLocalID(id: number, resolve: boolean, waitFor = 0): Promise + { + let obj = null; + try + { + obj = this.currentRegion.objects.getObjectByLocalID(id); + } + catch (error) + { + if (waitFor > 0) + { + obj = await this.waitForObjectByLocalID(id, waitFor); + } + else + { + throw (error); + } + } + if (resolve) + { + await this.currentRegion.resolver.resolveObjects([obj], {}); + } + return obj; + } + + // noinspection JSUnusedGlobalSymbols + public async getObjectByUUID(id: UUID, resolve: boolean, waitFor = 0): Promise + { + let obj = null; + try + { + obj = this.currentRegion.objects.getObjectByUUID(id); + } + catch (error) + { + if (waitFor > 0) + { + obj = await this.waitForObjectByUUID(id, waitFor); + } + else + { + throw (error); + } + } + if (resolve) + { + await this.currentRegion.resolver.resolveObjects([obj], {}); + } + return obj; + } + + // noinspection JSUnusedGlobalSymbols + public async findObjectsByName(pattern: string | RegExp, minX?: number, maxX?: number, minY?: number, maxY?: number, minZ?: number, maxZ?: number): Promise + { + let objects: GameObject[] = []; + if (minX !== undefined && maxX !== undefined && minY !== undefined && maxY !== undefined && minZ !== undefined && maxZ !== undefined) + { + objects = await this.getObjectsInArea(minX, maxX, minY, maxY, minZ, maxZ, true); + } + else + { + objects = await this.getAllObjects({ resolve: true }); + } + const idCheck: Record = {}; + const matches: GameObject[] = []; + const it = function(go: GameObject): void + { + if (go.name !== undefined) + { + let match = false; + if (pattern instanceof RegExp) + { + if (pattern.test(go.name)) + { + match = true; + } + } + else + { + match = micromatch.isMatch(go.name, pattern, { nocase: true }); + } + + if (match) + { + const fullID = go.FullID.toString(); + if (!idCheck[fullID]) + { + matches.push(go); + idCheck[fullID] = true; + } + } + } + if (go.children && go.children.length > 0) + { + for (const child of go.children) + { + it(child); + } + } + }; + for (const go of objects) + { + it(go); + } + return matches; + } + + public async getParcelAt(x: number, y: number): Promise + { + return this.currentRegion.getParcelProperties(x, y); + } + + public async getParcels(): Promise + { + return this.currentRegion.getParcels(); + } + + public async getAllObjects(options: GetObjectsOptions): Promise + { + const objs = this.currentRegion.objects.getAllObjects(options); + if (options.resolve === true) + { + await this.currentRegion.resolver.resolveObjects(objs, options); + } + return objs; + } + + public async getObjectsInArea(minX: number, maxX: number, minY: number, maxY: number, minZ: number, maxZ: number, resolve = false): Promise + { + const objs = this.currentRegion.objects.getObjectsInArea(minX, maxX, minY, maxY, minZ, maxZ); + if (resolve) + { + await this.currentRegion.resolver.resolveObjects(objs, {}); + } + return objs; + } + + // noinspection JSUnusedGlobalSymbols + public async pruneObjects(checkList: GameObject[]): Promise + { + let uuids = []; + let objects = []; + const stillAlive: Record = {}; + const checkObjects = async(uuidList: any[], objectList: GameObject[]): Promise => + { + + const objRef: Record = {}; + for (const obj of objectList) + { + objRef[obj.FullID.toString()] = obj; + } + const result = await this.currentRegion.caps.capsPostXML('GetObjectCost', { + 'object_ids': uuidList + }); + for (const u of Object.keys(result)) + { + stillAlive[u] = objRef[u]; + } + }; + + for (const o of checkList) + { + if (o.FullID !== undefined) + { + uuids.push(new LLSD.UUID(o.FullID)); + objects.push(o); + if (uuids.length > 256) + { + await checkObjects(uuids, objects); + uuids = []; + objects = []; + } + } + } + if (uuids.length > 0) + { + await checkObjects(uuids, objects); + } + const deadObjects: GameObject[] = []; + for (const o of checkList) + { + let found = false; + if (o.FullID !== undefined) + { + const fullID = o.FullID.toString(); + if (stillAlive[fullID] !== undefined) + { + found = true; + } + } + if (!found) + { + deadObjects.push(o); + } + } + return deadObjects; + } + + // noinspection JSUnusedGlobalSymbols + public setPersist(persist: boolean): void + { + this.currentRegion.objects.setPersist(persist); + } + + public async grabObject(localID: number | UUID, + grabOffset: Vector3 = Vector3.getZero(), + uvCoordinate: Vector3 = Vector3.getZero(), + stCoordinate: Vector3 = Vector3.getZero(), + faceIndex = 0, + position: Vector3 = Vector3.getZero(), + normal: Vector3 = Vector3.getZero(), + binormal: Vector3 = Vector3.getZero()): Promise + { + if (localID instanceof UUID) + { + const obj: GameObject = this.currentRegion.objects.getObjectByUUID(localID); + localID = obj.ID; + } + const msg = new ObjectGrabMessage(); + msg.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.circuit.sessionID + }; + msg.ObjectData = { + LocalID: localID, + GrabOffset: grabOffset + }; + msg.SurfaceInfo = [ + { + UVCoord: uvCoordinate, + STCoord: stCoordinate, + FaceIndex: faceIndex, + Position: position, + Normal: normal, + Binormal: binormal + } + ]; + const seqID = this.circuit.sendMessage(msg, PacketFlags.Reliable); + return this.circuit.waitForAck(seqID, 10000); + } + + public async deGrabObject(localID: number | UUID, + _grabOffset: Vector3 = Vector3.getZero(), + uvCoordinate: Vector3 = Vector3.getZero(), + stCoordinate: Vector3 = Vector3.getZero(), + faceIndex = 0, + position: Vector3 = Vector3.getZero(), + normal: Vector3 = Vector3.getZero(), + binormal: Vector3 = Vector3.getZero()): Promise + { + if (localID instanceof UUID) + { + const obj: GameObject = this.currentRegion.objects.getObjectByUUID(localID); + localID = obj.ID; + } + const msg = new ObjectDeGrabMessage(); + msg.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.circuit.sessionID + }; + msg.ObjectData = { + LocalID: localID + }; + msg.SurfaceInfo = [ + { + UVCoord: uvCoordinate, + STCoord: stCoordinate, + FaceIndex: faceIndex, + Position: position, + Normal: normal, + Binormal: binormal + } + ]; + const seqID = this.circuit.sendMessage(msg, PacketFlags.Reliable); + return this.circuit.waitForAck(seqID, 10000); + } + + // noinspection JSUnusedGlobalSymbols + public async dragGrabbedObject(localID: number | UUID, + grabPosition: Vector3, + grabOffset: Vector3 = Vector3.getZero(), + uvCoordinate: Vector3 = Vector3.getZero(), + stCoordinate: Vector3 = Vector3.getZero(), + faceIndex = 0, + position: Vector3 = Vector3.getZero(), + normal: Vector3 = Vector3.getZero(), + binormal: Vector3 = Vector3.getZero()): Promise + { + // For some reason this message takes a UUID when the others take a LocalID - wtf? + if (!(localID instanceof UUID)) + { + const obj: GameObject = this.currentRegion.objects.getObjectByLocalID(localID); + localID = obj.FullID; + } + const msg = new ObjectGrabUpdateMessage(); + msg.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.circuit.sessionID + }; + msg.ObjectData = { + ObjectID: localID, + GrabOffsetInitial: grabOffset, + GrabPosition: grabPosition, + TimeSinceLast: 0 + }; + msg.SurfaceInfo = [ + { + UVCoord: uvCoordinate, + STCoord: stCoordinate, + FaceIndex: faceIndex, + Position: position, + Normal: normal, + Binormal: binormal + } + ]; + const seqID = this.circuit.sendMessage(msg, PacketFlags.Reliable); + return this.circuit.waitForAck(seqID, 10000); + } + + // noinspection JSUnusedGlobalSymbols + public async touchObject(localID: number | UUID, + grabOffset: Vector3 = Vector3.getZero(), + uvCoordinate: Vector3 = Vector3.getZero(), + stCoordinate: Vector3 = Vector3.getZero(), + faceIndex = 0, + position: Vector3 = Vector3.getZero(), + normal: Vector3 = Vector3.getZero(), + binormal: Vector3 = Vector3.getZero()): Promise + { + if (localID instanceof UUID) + { + const obj: GameObject = this.currentRegion.objects.getObjectByUUID(localID); + localID = obj.ID; + } + await this.grabObject(localID, grabOffset, uvCoordinate, stCoordinate, faceIndex, position, normal, binormal); + return this.deGrabObject(localID, grabOffset, uvCoordinate, stCoordinate, faceIndex, position, normal, binormal); + } + + // noinspection JSUnusedGlobalSymbols + public async rezPrims(count: number): Promise + { + let agentPos = new Vector3([128, 128, 2048]); + try + { + const agentLocalID = this.currentRegion.agent.localID; + const agentObject = this.currentRegion.objects.getObjectByLocalID(agentLocalID); + if (agentObject.Position !== undefined) + { + agentPos = new Vector3(agentObject.Position); + } + else + { + throw new Error('Agent position is undefined'); + } + } + catch (_error: unknown) + { + console.warn('Unable to find avatar location, rezzing at ' + agentPos.toString()); + } + agentPos.z += 2.0; + // Set camera above target location for fast acquisition + const campos = new Vector3(agentPos); + campos.z += 2.0; + this.currentRegion.clientCommands.agent.setCamera(campos, agentPos, 10, new Vector3([-1.0, 0, 0]), new Vector3([0.0, 1.0, 0])); + + return this.createPrims(count, agentPos); + } + + private async createPrims(count: number, position: Vector3): Promise + { + return new Promise((resolve, reject) => + { + const gatheredPrims: GameObject[] = []; + let objSub: Subscription | undefined = undefined; + let timeout: Timeout | undefined = setTimeout(() => + { + if (objSub !== undefined) + { + objSub.unsubscribe(); + objSub = undefined; + } + if (timeout !== undefined) + { + clearTimeout(timeout); + timeout = undefined; + } + reject(new Error('Failed to gather ' + count + ' prims - only gathered ' + gatheredPrims.length)); + }, 30000); + objSub = this.currentRegion.clientEvents.onNewObjectEvent.subscribe((evt: NewObjectEvent) => + { + (async(): Promise => + { + if (evt.object.resolvedAt === undefined) + { + // We need to get the full ObjectProperties so we can be sure this is or isn't a rez from inventory + await this.resolveObject(evt.object, {}); + } + if (evt.createSelected && !evt.object.claimedForBuild) + { + if (evt.object.itemID === undefined || evt.object.itemID.isZero()) + { + if ( + evt.object.PCode === PCode.Prim && + evt.object.Material === 3 && + evt.object.PathCurve === 16 && + evt.object.ProfileCurve === 1 && + evt.object.PathBegin === 0 && + evt.object.PathEnd === 1 && + evt.object.PathScaleX === 1 && + evt.object.PathScaleY === 1 && + evt.object.PathShearX === 0 && + evt.object.PathShearY === 0 && + evt.object.PathTwist === 0 && + evt.object.PathTwistBegin === 0 && + evt.object.PathRadiusOffset === 0 && + evt.object.PathTaperX === 0 && + evt.object.PathTaperY === 0 && + evt.object.PathRevolutions === 1 && + evt.object.PathSkew === 0 && + evt.object.ProfileBegin === 0 && + evt.object.ProfileHollow === 0 + ) + { + evt.object.claimedForBuild = true; + gatheredPrims.push(evt.object); + if (gatheredPrims.length === count) + { + if (objSub !== undefined) + { + objSub.unsubscribe(); + objSub = undefined; + } + if (timeout !== undefined) + { + clearTimeout(timeout); + timeout = undefined; + } + resolve(gatheredPrims); + } + } + } + } + })().catch((_e: unknown) => + { + // ignore + }) + }); + + for (let x = 0; x < count; x++) + { + const msg = new ObjectAddMessage(); + msg.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.circuit.sessionID, + GroupID: UUID.zero() + }; + msg.ObjectData = { + PCode: PCode.Prim, + Material: 3, + AddFlags: PrimFlags.CreateSelected, + PathCurve: 16, + ProfileCurve: 1, + PathBegin: 0, + PathEnd: 0, + PathScaleX: 100, + PathScaleY: 100, + PathShearX: 0, + PathShearY: 0, + PathTwist: 0, + PathTwistBegin: 0, + PathRadiusOffset: 0, + PathTaperX: 0, + PathTaperY: 0, + PathRevolutions: 0, + PathSkew: 0, + ProfileBegin: 0, + ProfileEnd: 0, + ProfileHollow: 0, + BypassRaycast: 1, + RayStart: position, + RayEnd: position, + RayTargetID: UUID.zero(), + RayEndIsIntersection: 0, + Scale: new Vector3([0.5, 0.5, 0.5]), + Rotation: Quaternion.getIdentity(), + State: 0 + }; + this.circuit.sendMessage(msg, PacketFlags.Reliable); + } + }); + } + + private async waitForObjectByLocalID(localID: number, timeout: number): Promise { return new Promise((resolve, reject) => { let tmr: NodeJS.Timeout | null = null; - const subscription = this.currentRegion.clientEvents.onNewObjectEvent.subscribe(async(event: NewObjectEvent) => + const subscription = this.currentRegion.clientEvents.onNewObjectEvent.subscribe((event: NewObjectEvent) => { if (event.localID === localID) { @@ -440,12 +1116,12 @@ export class RegionCommands extends CommandsBase }); } - private waitForObjectByUUID(objectID: UUID, timeout: number): Promise + private async waitForObjectByUUID(objectID: UUID, timeout: number): Promise { return new Promise((resolve, reject) => { let tmr: NodeJS.Timeout | null = null; - const subscription = this.currentRegion.clientEvents.onNewObjectEvent.subscribe(async(event: NewObjectEvent) => + const subscription = this.currentRegion.clientEvents.onNewObjectEvent.subscribe((event: NewObjectEvent) => { if (event.objectID.equals(objectID)) { @@ -481,7 +1157,7 @@ export class RegionCommands extends CommandsBase } else { - const adjustedPos = new Vector3(objectPosition).multiplyByTSMQuat(new Quaternion(rotOffset)); + const adjustedPos = new Vector3(objectPosition).multiplyQuaternion(new Quaternion(rotOffset)); finalPos = new Vector3(new Vector3(posOffset).add(adjustedPos)); const baseRot = new Quaternion(rotOffset); @@ -491,26 +1167,26 @@ export class RegionCommands extends CommandsBase // Is this a mesh part? let object: GameObject | null = null; let rezzedMesh = false; - if (obj.extraParams !== undefined && obj.extraParams.meshData !== null) + if (obj.extraParams?.meshData !== null && obj.extraParams?.meshData.type === SculptType.Mesh) { - if (buildMap.assetMap.mesh[obj.extraParams.meshData.meshData.toString()] !== undefined) + const meshEntry = buildMap.assetMap.mesh.get(obj.extraParams.meshData.meshData.toString()); + if (meshEntry !== undefined) { - const meshEntry = buildMap.assetMap.mesh[obj.extraParams.meshData.meshData.toString()]; const rezLocation = new Vector3(buildMap.rezLocation); rezLocation.z += (objectScale.z / 2); - if (meshEntry.item !== null) + if (meshEntry.item !== undefined) { try { object = await meshEntry.item.rezInWorld(rezLocation); + rezzedMesh = true; } catch (err) { console.error('Failed to rez object ' + obj.name + ' in-world'); console.error(err); } - rezzedMesh = true; } else { @@ -518,7 +1194,7 @@ export class RegionCommands extends CommandsBase } } } - else if (buildMap.primReservoir.length > 0) + if (!rezzedMesh && buildMap.primReservoir.length > 0) { const newPrim = buildMap.primReservoir.shift(); if (newPrim !== undefined) @@ -554,7 +1230,7 @@ export class RegionCommands extends CommandsBase } } } - else + else if (!rezzedMesh) { console.error('Exhausted prim reservoir!!'); } @@ -585,14 +1261,37 @@ export class RegionCommands extends CommandsBase if (obj.extraParams.sculptData.type !== SculptType.Mesh) { const oldTextureID = obj.extraParams.sculptData.texture.toString(); - const item = buildMap.assetMap.textures[oldTextureID]; - if (item !== null && item !== undefined && item.item !== null) + const item = buildMap.assetMap.textures.get(oldTextureID); + if (item?.item) { obj.extraParams.sculptData.texture = item.item.assetID; } } } + if (obj.extraParams.lightImageData !== null) + { + const oldTextureID = obj.extraParams.lightImageData.texture.toString(); + const item = buildMap.assetMap.textures.get(oldTextureID); + if (item?.item) + { + obj.extraParams.lightImageData.texture = item.item.assetID; + } + } + + if (obj.extraParams.renderMaterialData !== null) + { + for(const entry of obj.extraParams.renderMaterialData.params) + { + const oldTextureID = entry.textureUUID.toString(); + const item = buildMap.assetMap.gltfMaterials.get(oldTextureID); + if (item?.item) + { + entry.textureUUID = item.item.assetID; + } + } + } + if (rezzedMesh) { obj.extraParams.meshData = object.extraParams.meshData; @@ -628,38 +1327,73 @@ export class RegionCommands extends CommandsBase 'FullMaterialsPerFace': [] }; - const materialFaces: { [key: string]: boolean } = {}; + let defaultMaterial: { + ID: number, + Material: any + } | null = null; + const materialFaces: Record = {}; if (obj.TextureEntry.defaultTexture !== undefined && obj.TextureEntry.defaultTexture !== null) { - const materialID = obj.TextureEntry.defaultTexture.materialID; + const {materialID} = obj.TextureEntry.defaultTexture; if (!materialID.isZero()) { - const storedMat = buildMap.assetMap.materials[materialID.toString()]; - if (storedMat !== null && storedMat !== undefined) + const storedMat = buildMap.assetMap.materials.get(materialID.toString()); + if (storedMat?.item) { - materialUpload.FullMaterialsPerFace.push({ + defaultMaterial = { ID: object.ID, - Material: storedMat.toLLSDObject() - }); - materialFaces[-1] = true; + Material: storedMat.item.toLLSDObject() + }; } } } for (let face = 0; face < obj.TextureEntry.faces.length; face++) { - const materialID = obj.TextureEntry.faces[face].materialID; + const {materialID} = obj.TextureEntry.faces[face]; if (!materialID.isZero()) { - const storedMat = buildMap.assetMap.materials[materialID.toString()]; - if (storedMat !== null && storedMat !== undefined) + const storedMat = buildMap.assetMap.materials.get(materialID.toString()); + if (storedMat?.item) { materialUpload.FullMaterialsPerFace.push({ Face: face, ID: object.ID, - Material: storedMat.toLLSDObject() + Material: storedMat.item.toLLSDObject() }); - materialFaces[face] = true; + materialFaces[face] = { + added: true, + complete: false + }; + } + } + } + + if (defaultMaterial) + { + // This is a pretty nasty hack but we don't have much choice, without full + // prim decomposition code to figure out how many faces belong in a particular volume. + // Second Life requires that the material be specified per face, but per the defaultTexture + // in the texture entry we don't actually know how many faces there are + Logger.Info('Inserting script to determine face count..'); + const helper = new PrimFacesHelper(this.bot, object); + const sides = await helper.getFaces(); + Logger.Info(String(sides) + ' faces'); + for(let side = 0; side < sides; side++) + { + if (!materialFaces[side]) + { + materialUpload.FullMaterialsPerFace.push({ + Face: side, + ...defaultMaterial + }); + materialFaces[side] = { + added: true, + complete: false + }; } } } @@ -670,87 +1404,28 @@ export class RegionCommands extends CommandsBase const newMat = { 'Zipped': new LLSD.Binary(Array.from(zipped), 'BASE64') }; - this.currentRegion.caps.capsPutXML('RenderMaterials', newMat).then(() => - { - }).catch((err) => + const result = await this.currentRegion.caps.capsPutXML('RenderMaterials', newMat); + if (result.Zipped) { - console.error(err); - }); - try - { - let complete = false; - do + const resultUnzipped = await Utils.inflate(Buffer.from(result.Zipped.octets)); + const binData = new LLSD.Binary(Array.from(resultUnzipped), 'BASE64'); + const llsdResult = LLSD.LLSD.parseBinary(binData); + for(const result2 of llsdResult.result) { - complete = true; - await object.waitForTextureUpdate(10000); - for (const materialFace of Object.keys(materialFaces)) + const face = result2.Face; + if (materialFaces[face] !== undefined) { - const entry = object.TextureEntry; - if (entry === undefined) - { - complete = false; - } - else if (parseInt(materialFace, 10) === -1) - { - const def = entry.defaultTexture - if (def === undefined || def === null) - { - complete = false; - } - else - { - if (def.materialID.equals(UUID.zero())) - { - complete = false; - } - } - } - else - { - const fc = parseInt(materialFace, 10); - const thisFace = entry.faces[fc]; - if (thisFace === undefined) - { - complete = false; - } - else - { - if (thisFace.materialID.equals(UUID.zero())) - { - complete = false; - } - } - } + materialFaces[face].complete = true; } } - while (!complete); - } - catch (error) - { - console.error(obj.name + ':Timed out while waiting for RenderMaterials update'); - } - if (object.TextureEntry !== undefined) - { - for (let face = 0; face < object.TextureEntry.faces.length; face++) + for(const face of Object.keys(materialFaces)) { - const oldFace = obj.TextureEntry.faces[face]; - if (!oldFace.materialID.isZero()) + if (!materialFaces[face].complete) { - obj.TextureEntry.faces[face].materialID = object.TextureEntry.faces[face].materialID; - if (obj.TextureEntry.defaultTexture !== null) - { - if (oldFace.materialID.equals(obj.TextureEntry.defaultTexture.materialID)) - { - obj.TextureEntry.defaultTexture.materialID = obj.TextureEntry.faces[face].materialID; - } - } + console.error('Failed to update material on face ' + String(face)); } } - if (obj.TextureEntry.defaultTexture !== null && object.TextureEntry.defaultTexture !== null) - { - obj.TextureEntry.defaultTexture.materialID = object.TextureEntry.defaultTexture.materialID; - } } } @@ -758,8 +1433,8 @@ export class RegionCommands extends CommandsBase { const oldTextureID = obj.TextureEntry.defaultTexture.textureID.toString(); - const item = buildMap.assetMap.textures[oldTextureID]; - if (item !== null && item !== undefined && item.item !== null) + const item = buildMap.assetMap.textures.get(oldTextureID); + if (item?.item) { obj.TextureEntry.defaultTexture.textureID = item.item.assetID; } @@ -768,8 +1443,8 @@ export class RegionCommands extends CommandsBase { const oldTextureID = j.textureID.toString(); - const item = buildMap.assetMap.textures[oldTextureID]; - if (item !== null && item !== undefined && item.item !== null) + const item = buildMap.assetMap.textures.get(oldTextureID); + if (item?.item) { j.textureID = item.item.assetID; } @@ -834,7 +1509,9 @@ export class RegionCommands extends CommandsBase } } - for (const invItem of obj.inventory) + // Copy so the list doesn't mutate as we're processing + const fixedInventory = [...obj.inventory]; + for (const invItem of fixedInventory) { try { @@ -844,65 +1521,37 @@ export class RegionCommands extends CommandsBase } switch (invItem.inventoryType) { - case InventoryType.Clothing: - { - if (buildMap.assetMap.clothing[invItem.assetID.toString()] !== undefined) - { - const item = buildMap.assetMap.clothing[invItem.assetID.toString()].item; - if (item !== null) - { - await object.dropInventoryIntoContents(item); - await object.updateInventory(); - for (const taskItem of object.inventory) - { - if (taskItem.name === item.name) - { - taskItem.permissions = invItem.permissions; - await taskItem.renameInTask(object, invItem.name); - } - } - } - } - break; - } case InventoryType.Settings: { - if (buildMap.assetMap.settings[invItem.assetID.toString()] !== undefined) + const item = buildMap.assetMap.settings.get(invItem.assetID.toString()); + if (item?.item) { - const item = buildMap.assetMap.settings[invItem.assetID.toString()].item; - if (item !== null) + await object.dropInventoryIntoContents(item.item); + await object.updateInventory(); + for (const taskItem of object.inventory) { - await object.dropInventoryIntoContents(item); - await object.updateInventory(); - for (const taskItem of object.inventory) + if (taskItem.name === item.item.name) { - if (taskItem.name === item.name) - { - taskItem.permissions = invItem.permissions; - await taskItem.renameInTask(object, invItem.name); - } + taskItem.permissions.nextOwnerMask = invItem.permissions.nextOwnerMask; + await taskItem.rename(invItem.name); } } } break; } case InventoryType.Wearable: - case InventoryType.Bodypart: { - if (buildMap.assetMap.bodyparts[invItem.assetID.toString()] !== undefined) + const item = buildMap.assetMap.wearables.get(invItem.assetID.toString()); + if (item?.item) { - const item = buildMap.assetMap.bodyparts[invItem.assetID.toString()].item; - if (item !== null) + await object.dropInventoryIntoContents(item.item); + await object.updateInventory(); + for (const taskItem of object.inventory) { - await object.dropInventoryIntoContents(item); - await object.updateInventory(); - for (const taskItem of object.inventory) + if (taskItem.name === item.item.name) { - if (taskItem.name === item.name) - { - taskItem.permissions = invItem.permissions; - await taskItem.renameInTask(object, invItem.name); - } + taskItem.permissions.nextOwnerMask = invItem.permissions.nextOwnerMask; + await taskItem.rename(invItem.name); } } } @@ -910,20 +1559,17 @@ export class RegionCommands extends CommandsBase } case InventoryType.Notecard: { - if (buildMap.assetMap.notecards[invItem.assetID.toString()] !== undefined) + const item = buildMap.assetMap.notecards.get(invItem.assetID.toString()); + if (item?.item) { - const item = buildMap.assetMap.notecards[invItem.assetID.toString()].item; - if (item !== null) + await object.dropInventoryIntoContents(item.item); + await object.updateInventory(); + for (const taskItem of object.inventory) { - await object.dropInventoryIntoContents(item); - await object.updateInventory(); - for (const taskItem of object.inventory) + if (taskItem.name === item.item.name) { - if (taskItem.name === item.name) - { - taskItem.permissions = invItem.permissions; - await taskItem.renameInTask(object, invItem.name); - } + taskItem.permissions.nextOwnerMask = invItem.permissions.nextOwnerMask; + await taskItem.rename(invItem.name); } } } @@ -931,20 +1577,17 @@ export class RegionCommands extends CommandsBase } case InventoryType.Sound: { - if (buildMap.assetMap.sounds[invItem.assetID.toString()] !== undefined) + const item = buildMap.assetMap.sounds.get(invItem.assetID.toString()); + if (item?.item) { - const item = buildMap.assetMap.sounds[invItem.assetID.toString()].item; - if (item !== null) + await object.dropInventoryIntoContents(item.item); + await object.updateInventory(); + for (const taskItem of object.inventory) { - await object.dropInventoryIntoContents(item); - await object.updateInventory(); - for (const taskItem of object.inventory) + if (taskItem.name === item.item.name) { - if (taskItem.name === item.name) - { - taskItem.permissions = invItem.permissions; - await taskItem.renameInTask(object, invItem.name); - } + taskItem.permissions.nextOwnerMask = invItem.permissions.nextOwnerMask; + await taskItem.rename(invItem.name); } } } @@ -952,42 +1595,72 @@ export class RegionCommands extends CommandsBase } case InventoryType.Gesture: { - if (buildMap.assetMap.gestures[invItem.assetID.toString()] !== undefined) + const item = buildMap.assetMap.gestures.get(invItem.assetID.toString()); + if (item?.item) { - const item = buildMap.assetMap.gestures[invItem.assetID.toString()].item; - if (item !== null) + await object.dropInventoryIntoContents(item.item); + await object.updateInventory(); + for (const taskItem of object.inventory) { - await object.dropInventoryIntoContents(item); - await object.updateInventory(); - for (const taskItem of object.inventory) + if (taskItem.name === item.item.name) { - if (taskItem.name === item.name) + taskItem.permissions.nextOwnerMask = invItem.permissions.nextOwnerMask; + await taskItem.rename(invItem.name); + } + } + } + break; + } + case InventoryType.LSL: + { + const item = buildMap.assetMap.scripts.get(invItem.assetID.toString()); + if (item?.item) + { + await object.dropInventoryIntoContents(item.item); + await object.updateInventory(); + for (const taskItem of object.inventory) + { + if (taskItem.name === item.item.name) + { + taskItem.permissions = invItem.permissions; + await taskItem.rename(invItem.name); + + if (taskItem.type === AssetType.LSLText) { - taskItem.permissions = invItem.permissions; - await taskItem.renameInTask(object, invItem.name); + let compileQueue = buildMap.assetMap.scriptsToCompile.get(object.FullID.toString()); + if (compileQueue === undefined) + { + compileQueue = { + gameObject: object, + scripts: [] + }; + } + compileQueue.scripts.push({ + item: taskItem, + oldAssetID: invItem.assetID, + shouldStart: invItem.scriptRunning === true, + mono: invItem.scriptMono !== false + }); + buildMap.assetMap.scriptsToCompile.set(object.FullID.toString(), compileQueue); } } } } break; } - case InventoryType.Script: - case InventoryType.LSL: + case InventoryType.Material: { - if (buildMap.assetMap.scripts[invItem.assetID.toString()] !== undefined) + const item = buildMap.assetMap.gltfMaterials.get(invItem.assetID.toString()); + if (item?.item) { - const item = buildMap.assetMap.scripts[invItem.assetID.toString()].item; - if (item !== null) + await object.dropInventoryIntoContents(item.item); + await object.updateInventory(); + for (const taskItem of object.inventory) { - await object.dropInventoryIntoContents(item); - await object.updateInventory(); - for (const taskItem of object.inventory) + if (taskItem.name === item.item.name) { - if (taskItem.name === item.name) - { - taskItem.permissions = invItem.permissions; - await taskItem.renameInTask(object, invItem.name); - } + taskItem.permissions.nextOwnerMask = invItem.permissions.nextOwnerMask; + await taskItem.rename(invItem.name); } } } @@ -995,20 +1668,17 @@ export class RegionCommands extends CommandsBase } case InventoryType.Animation: { - if (buildMap.assetMap.animations[invItem.assetID.toString()] !== undefined) + const item = buildMap.assetMap.animations.get(invItem.assetID.toString()); + if (item?.item) { - const item = buildMap.assetMap.animations[invItem.assetID.toString()].item; - if (item !== null) + await object.dropInventoryIntoContents(item.item); + await object.updateInventory(); + for (const taskItem of object.inventory) { - await object.dropInventoryIntoContents(item); - await object.updateInventory(); - for (const taskItem of object.inventory) + if (taskItem.name === item.item.name) { - if (taskItem.name === item.name) - { - taskItem.permissions = invItem.permissions; - await taskItem.renameInTask(object, invItem.name); - } + taskItem.permissions = invItem.permissions; + await taskItem.rename(invItem.name); } } } @@ -1016,52 +1686,38 @@ export class RegionCommands extends CommandsBase } case InventoryType.Object: { - if (buildMap.assetMap.objects[invItem.itemID.toString()] !== undefined) + const inventoryItem = buildMap.assetMap.objects.get(invItem.itemID.toString()); + if (inventoryItem?.item) { - const inventoryItem = buildMap.assetMap.objects[invItem.itemID.toString()]; - if (inventoryItem !== null) + await object.dropInventoryIntoContents(inventoryItem.item); + await object.updateInventory(); + for (const taskItem of object.inventory) { - await object.dropInventoryIntoContents(inventoryItem); - await object.updateInventory(); - for (const taskItem of object.inventory) + if (taskItem.name === inventoryItem.item.name) { - if (taskItem.name === inventoryItem.name) - { - taskItem.permissions = invItem.permissions; - await taskItem.renameInTask(object, invItem.name); - } + taskItem.permissions.nextOwnerMask = invItem.permissions.nextOwnerMask; + await taskItem.rename(invItem.name); } } - else - { - console.error('Unable to drop object: item is null'); - } } break; } case InventoryType.Texture: case InventoryType.Snapshot: { - if (buildMap.assetMap.textures[invItem.assetID.toString()] !== undefined) + const texItem = buildMap.assetMap.textures.get(invItem.assetID); + if (texItem?.item) { - const texItem = buildMap.assetMap.textures[invItem.assetID.toString()]; - if (texItem.item !== null) + await object.dropInventoryIntoContents(texItem.item); + await object.updateInventory(); + for (const taskItem of object.inventory) { - await object.dropInventoryIntoContents(texItem.item); - await object.updateInventory(); - for (const taskItem of object.inventory) + if (taskItem.name === texItem.item.name) { - if (taskItem.name === texItem.item.name) - { - taskItem.permissions = invItem.permissions; - await taskItem.renameInTask(object, invItem.name); - } + taskItem.permissions.nextOwnerMask = invItem.permissions.nextOwnerMask; + await taskItem.rename(invItem.name); } } - else - { - console.error('Unable to drop object: item is null'); - } } break; } @@ -1084,14 +1740,10 @@ export class RegionCommands extends CommandsBase { if (obj.extraParams.meshData !== null) { - if (buildMap.assetMap.mesh[obj.extraParams.meshData.meshData.toString()] === undefined) - { - buildMap.assetMap.mesh[obj.extraParams.meshData.meshData.toString()] = { - name: obj.name || 'Object', - description: obj.description || '(no description)', - item: null - }; - } + buildMap.assetMap.mesh.request(obj.extraParams.meshData.meshData, { + name: obj.name ?? 'Object', + description: obj.description ?? '(no description)' + }); } else { @@ -1101,195 +1753,146 @@ export class RegionCommands extends CommandsBase { if (obj.extraParams.sculptData.type !== SculptType.Mesh) { - if (buildMap.assetMap.textures[obj.extraParams.sculptData.texture.toString()] === undefined) + buildMap.assetMap.textures.request(obj.extraParams.sculptData.texture); + } + } + if (obj.extraParams.lightImageData != null && !obj.extraParams.lightImageData.texture.isZero()) + { + buildMap.assetMap.textures.request(obj.extraParams.lightImageData.texture); + } + if (obj.extraParams.renderMaterialData != null) + { + for(const item of obj.extraParams.renderMaterialData.params) + { + if (!item.textureUUID.isZero()) { - buildMap.assetMap.textures[obj.extraParams.sculptData.texture.toString()] = { - item: null - }; + buildMap.assetMap.gltfMaterials.request(item.textureUUID); } } } - if (obj.TextureEntry !== undefined) + } + if (obj.TextureEntry !== undefined) + { + for (const j of obj.TextureEntry.faces) { - for (const j of obj.TextureEntry.faces) + const {textureID} = j; + buildMap.assetMap.textures.request(textureID); + const {materialID} = j; + if (!materialID.isZero()) { - const textureID = j.textureID; - if (buildMap.assetMap.textures[textureID.toString()] === undefined) - { - buildMap.assetMap.textures[textureID.toString()] = { - item: null - } - } - const materialID = j.materialID; - if (!materialID.isZero()) - { - if (buildMap.assetMap.materials[materialID.toString()] === undefined) - { - buildMap.assetMap.materials[materialID.toString()] = null - } - } - } - if (obj.TextureEntry.defaultTexture !== null) - { - const textureID = obj.TextureEntry.defaultTexture.textureID; - if (buildMap.assetMap.textures[textureID.toString()] === undefined) - { - buildMap.assetMap.textures[textureID.toString()] = { - item: null - } - } - const materialID = obj.TextureEntry.defaultTexture.materialID; - if (!materialID.isZero()) - { - if (buildMap.assetMap.materials[materialID.toString()] === undefined) - { - buildMap.assetMap.materials[materialID.toString()] = null - } - } + buildMap.assetMap.materials.request(materialID); } } - if (obj.inventory !== undefined) + if (obj.TextureEntry.defaultTexture !== null) { - for (const j of obj.inventory) + const {textureID} = obj.TextureEntry.defaultTexture; + buildMap.assetMap.textures.request(textureID); + const {materialID} = obj.TextureEntry.defaultTexture; + if (!materialID.isZero()) { - const assetID = j.assetID; - switch (j.inventoryType) + buildMap.assetMap.materials.request(materialID); + } + } + } + if (obj.inventory !== undefined) + { + // Copy so the list doesn't mutate as we're processing + const invList = [...obj.inventory]; + for (const j of invList) + { + const {assetID} = j; + switch (j.inventoryType) + { + case InventoryType.Animation: { - case InventoryType.Animation: - { - if (buildMap.assetMap.animations[assetID.toString()] === undefined) - { - buildMap.assetMap.animations[assetID.toString()] = { - name: j.name, - description: j.description, - item: null - }; - } - break; - } - case InventoryType.Wearable: - case InventoryType.Bodypart: - { - if (buildMap.assetMap.bodyparts[assetID.toString()] === undefined) - { - buildMap.assetMap.bodyparts[assetID.toString()] = { - name: j.name, - description: j.description, - item: null - }; - } - break; - } - case InventoryType.CallingCard: - { - if (buildMap.assetMap.callingcards[assetID.toString()] === undefined) - { - buildMap.assetMap.callingcards[assetID.toString()] = { - name: j.name, - description: j.description, - item: null - }; - } - break; - } - case InventoryType.Clothing: - { - if (buildMap.assetMap.clothing[assetID.toString()] === undefined) - { - buildMap.assetMap.clothing[assetID.toString()] = { - name: j.name, - description: j.description, - item: null - }; - } - break; - } - case InventoryType.Gesture: - { - if (buildMap.assetMap.gestures[assetID.toString()] === undefined) - { - buildMap.assetMap.gestures[assetID.toString()] = { - name: j.name, - description: j.description, - item: null - }; - } - break; - } - case InventoryType.Script: - case InventoryType.LSL: - { - if (buildMap.assetMap.scripts[assetID.toString()] === undefined) - { - buildMap.assetMap.scripts[assetID.toString()] = { - name: j.name, - description: j.description, - item: null - }; - } - break; - } - case InventoryType.Texture: - case InventoryType.Snapshot: - { - if (buildMap.assetMap.textures[assetID.toString()] === undefined) - { - buildMap.assetMap.textures[assetID.toString()] = { - name: j.name, - description: j.description, - item: null - }; - } - break; - } - case InventoryType.Notecard: - { - if (buildMap.assetMap.notecards[assetID.toString()] === undefined) - { - buildMap.assetMap.notecards[assetID.toString()] = { - name: j.name, - description: j.description, - item: null - }; - } - break; - } - case InventoryType.Sound: - { - if (buildMap.assetMap.sounds[assetID.toString()] === undefined) - { - buildMap.assetMap.sounds[assetID.toString()] = { - name: j.name, - description: j.description, - item: null - }; - } - break; - } - case InventoryType.Object: - { - if (buildMap.assetMap.objects[assetID.toString()] === undefined) - { - buildMap.assetMap.objects[assetID.toString()] = null; - } - break; - } - case InventoryType.Settings: - { - if (buildMap.assetMap.settings[assetID.toString()] === undefined) - { - buildMap.assetMap.settings[assetID.toString()] = { - name: j.name, - description: j.description, - item: null - }; - } - break; - } - default: - console.error('Unsupported inventory type: ' + j.inventoryType); - break; + buildMap.assetMap.animations.request(assetID, { + name: j.name, + description: j.description + }); + break; } + case InventoryType.Wearable: + { + buildMap.assetMap.wearables.request(assetID, { + name: j.name, + description: j.description, + assetType: j.type + }); + break; + } + case InventoryType.CallingCard: + { + buildMap.assetMap.callingcards.request(assetID, { + name: j.name, + description: j.description + }); + break; + } + case InventoryType.Gesture: + { + buildMap.assetMap.gestures.request(assetID, { + name: j.name, + description: j.description + }); + break; + } + case InventoryType.LSL: + { + buildMap.assetMap.scripts.request(assetID, { + name: j.name, + description: j.description + }); + break; + } + case InventoryType.Texture: + case InventoryType.Snapshot: + { + buildMap.assetMap.textures.request(assetID, { + name: j.name, + description: j.description + }); + break; + } + case InventoryType.Notecard: + { + buildMap.assetMap.notecards.request(assetID, { + name: j.name, + description: j.description + }); + break; + } + case InventoryType.Sound: + { + buildMap.assetMap.sounds.request(assetID, { + name: j.name, + description: j.description + }); + break; + } + case InventoryType.Object: + { + buildMap.assetMap.objects.request(assetID); + break; + } + case InventoryType.Settings: + { + buildMap.assetMap.settings.request(assetID, { + name: j.name, + description: j.description + }); + break; + } + case InventoryType.Material: + { + buildMap.assetMap.gltfMaterials.request(assetID, { + name: j.name, + description: j.description + }); + break; + } + default: + console.error('Unsupported inventory type: ' + j.inventoryType); + break; } } } @@ -1301,622 +1904,4 @@ export class RegionCommands extends CommandsBase } } } - - async buildObjectNew(obj: GameObject, map: AssetMap, callback: (map: AssetMap) => void, costOnly: boolean = false, skipMove = false): Promise - { - const buildMap = new BuildMap(map, callback, costOnly); - this.gatherAssets(obj, buildMap); - const bomTextures = [ - '5a9f4a74-30f2-821c-b88d-70499d3e7183', - 'ae2de45c-d252-50b8-5c6e-19f39ce79317', - '24daea5f-0539-cfcf-047f-fbc40b2786ba', - '52cc6bb6-2ee5-e632-d3ad-50197b1dcb8a', - '43529ce8-7faa-ad92-165a-bc4078371687', - '09aac1fb-6bce-0bee-7d44-caac6dbb6c63', - 'ff62763f-d60a-9855-890b-0c96f8f8cd98', - '8e915e25-31d1-cc95-ae08-d58a47488251', - '9742065b-19b5-297c-858a-29711d539043', - '03642e83-2bd1-4eb9-34b4-4c47ed586d2d', - 'edd51b77-fc10-ce7a-4b3d-011dfc349e4f', - 'c228d1cf-4b5d-4ba8-84f4-899a0796aa97' // 'non existent asset' - ]; - for (const bomTexture of bomTextures) - { - if (buildMap.assetMap.textures[bomTexture] !== undefined) - { - delete buildMap.assetMap.textures[bomTexture]; - } - } - await callback(map); - - if (costOnly) - { - return null; - } - - let agentPos = new Vector3([128, 128, 2048]); - try - { - const agentLocalID = this.currentRegion.agent.localID; - const agentObject = this.currentRegion.objects.getObjectByLocalID(agentLocalID); - if (agentObject.Position !== undefined) - { - agentPos = new Vector3(agentObject.Position); - } - else - { - throw new Error('Agent position is undefined'); - } - } - catch (error) - { - console.warn('Unable to find avatar location, rezzing at ' + agentPos.toString()); - } - agentPos.z += 2.0; - buildMap.rezLocation = agentPos; - // Set camera above target location for fast acquisition - const campos = new Vector3(agentPos); - campos.z += 2.0; - await this.currentRegion.clientCommands.agent.setCamera(campos, agentPos, 10, new Vector3([-1.0, 0, 0]), new Vector3([0.0, 1.0, 0])); - - if (buildMap.primsNeeded > 0) - { - buildMap.primReservoir = await this.createPrims(buildMap.primsNeeded, agentPos); - } - - let storedPosition: Vector3 | undefined = undefined; - if (skipMove) - { - storedPosition = obj.Position; - obj.Position = new Vector3(buildMap.rezLocation); - } - - const parts = []; - parts.push(async() => - { - return { - index: 1, - object: await this.buildPart(obj, Vector3.getZero(), Quaternion.getIdentity(), buildMap, true) - } - }); - - if (obj.children) - { - if (obj.Position === undefined) - { - obj.Position = Vector3.getZero(); - } - if (obj.Rotation === undefined) - { - obj.Rotation = Quaternion.getIdentity(); - } - let childIndex = 2; - for (const child of obj.children) - { - if (child.Position !== undefined && child.Rotation !== undefined) - { - const index = childIndex++; - parts.push(async() => - { - return { - index, - object: await this.buildPart(child, new Vector3(obj.Position), new Quaternion(obj.Rotation), buildMap, false) - } - }); - } - } - } - let results: { - results: { - index: number, - object: GameObject - }[], - errors: Error[] - } = { - results: [], - errors: [] - }; - results = await Utils.promiseConcurrent<{ - index: number, - object: GameObject - }>(parts, 5, 0); - if (results.errors.length > 0) - { - for (const err of results.errors) - { - console.error(err); - } - } - - let rootObj: GameObject | null = null; - for (const childObject of results.results) - { - if (childObject.object.isMarkedRoot) - { - rootObj = childObject.object; - break; - } - } - if (rootObj === null) - { - throw new Error('Failed to find root prim..'); - } - - const childPrims: GameObject[] = []; - results.results.sort((a: { index: number, object: GameObject }, b: { index: number, object: GameObject }) => - { - return a.index - b.index; - }); - for (const childObject of results.results) - { - if (childObject.object !== rootObj) - { - childPrims.push(childObject.object); - } - } - try - { - await rootObj.linkFrom(childPrims); - } - catch (err) - { - console.error('Link failed:'); - console.error(err); - } - if (storedPosition !== undefined) - { - obj.Position = storedPosition; - } - return rootObj; - } - - private createPrims(count: number, position: Vector3): Promise - { - return new Promise((resolve, reject) => - { - const gatheredPrims: GameObject[] = []; - let objSub: Subscription | undefined = undefined; - let timeout: Timeout | undefined = setTimeout(() => - { - if (objSub !== undefined) - { - objSub.unsubscribe(); - objSub = undefined; - } - if (timeout !== undefined) - { - clearTimeout(timeout); - timeout = undefined; - } - reject(new Error('Failed to gather ' + count + ' prims - only gathered ' + gatheredPrims.length)); - }, 30000); - objSub = this.currentRegion.clientEvents.onNewObjectEvent.subscribe(async(evt: NewObjectEvent) => - { - if (!evt.object.resolvedAt) - { - // We need to get the full ObjectProperties so we can be sure this is or isn't a rez from inventory - await this.resolveObject(evt.object, {}); - } - if (evt.createSelected && !evt.object.claimedForBuild) - { - if (evt.object.itemID === undefined || evt.object.itemID.isZero()) - { - if ( - evt.object.PCode === PCode.Prim && - evt.object.Material === 3 && - evt.object.PathCurve === 16 && - evt.object.ProfileCurve === 1 && - evt.object.PathBegin === 0 && - evt.object.PathEnd === 1 && - evt.object.PathScaleX === 1 && - evt.object.PathScaleY === 1 && - evt.object.PathShearX === 0 && - evt.object.PathShearY === 0 && - evt.object.PathTwist === 0 && - evt.object.PathTwistBegin === 0 && - evt.object.PathRadiusOffset === 0 && - evt.object.PathTaperX === 0 && - evt.object.PathTaperY === 0 && - evt.object.PathRevolutions === 1 && - evt.object.PathSkew === 0 && - evt.object.ProfileBegin === 0 && - evt.object.ProfileHollow === 0 - ) - { - evt.object.claimedForBuild = true; - gatheredPrims.push(evt.object); - if (gatheredPrims.length === count) - { - if (objSub !== undefined) - { - objSub.unsubscribe(); - objSub = undefined; - } - if (timeout !== undefined) - { - clearTimeout(timeout); - timeout = undefined; - } - resolve(gatheredPrims); - } - } - } - } - }); - - for (let x = 0; x < count; x++) - { - const msg = new ObjectAddMessage(); - msg.AgentData = { - AgentID: this.agent.agentID, - SessionID: this.circuit.sessionID, - GroupID: UUID.zero() - }; - msg.ObjectData = { - PCode: PCode.Prim, - Material: 3, - AddFlags: PrimFlags.CreateSelected, - PathCurve: 16, - ProfileCurve: 1, - PathBegin: 0, - PathEnd: 0, - PathScaleX: 100, - PathScaleY: 100, - PathShearX: 0, - PathShearY: 0, - PathTwist: 0, - PathTwistBegin: 0, - PathRadiusOffset: 0, - PathTaperX: 0, - PathTaperY: 0, - PathRevolutions: 0, - PathSkew: 0, - ProfileBegin: 0, - ProfileEnd: 0, - ProfileHollow: 0, - BypassRaycast: 1, - RayStart: position, - RayEnd: position, - RayTargetID: UUID.zero(), - RayEndIsIntersection: 0, - Scale: new Vector3([0.5, 0.5, 0.5]), - Rotation: Quaternion.getIdentity(), - State: 0 - }; - this.circuit.sendMessage(msg, PacketFlags.Reliable); - } - }); - } - - async getObjectByLocalID(id: number, resolve: boolean, waitFor: number = 0): Promise - { - let obj = null; - try - { - obj = this.currentRegion.objects.getObjectByLocalID(id); - } - catch (error) - { - if (waitFor > 0) - { - obj = await this.waitForObjectByLocalID(id, waitFor); - } - else - { - throw (error); - } - } - if (resolve) - { - await this.currentRegion.resolver.resolveObjects([obj], {}); - } - return obj; - } - - async getObjectByUUID(id: UUID, resolve: boolean, waitFor: number = 0): Promise - { - let obj = null; - try - { - obj = this.currentRegion.objects.getObjectByUUID(id); - } - catch (error) - { - if (waitFor > 0) - { - obj = await this.waitForObjectByUUID(id, waitFor); - } - else - { - throw (error); - } - } - if (resolve) - { - await this.currentRegion.resolver.resolveObjects([obj], {}); - } - return obj; - } - - async findObjectsByName(pattern: string | RegExp, minX?: number, maxX?: number, minY?: number, maxY?: number, minZ?: number, maxZ?: number): Promise - { - let objects: GameObject[] = []; - if (minX !== undefined && maxX !== undefined && minY !== undefined && maxY !== undefined && minZ !== undefined && maxZ !== undefined) - { - objects = await this.getObjectsInArea(minX, maxX, minY, maxY, minZ, maxZ, true); - } - else - { - objects = await this.getAllObjects({ resolve: true }); - } - const idCheck: { [key: string]: boolean } = {}; - const matches: GameObject[] = []; - const it = function(go: GameObject): void - { - if (go.name !== undefined) - { - let match = false; - if (pattern instanceof RegExp) - { - if (pattern.test(go.name)) - { - match = true; - } - } - else - { - match = micromatch.isMatch(go.name, pattern, { nocase: true }); - } - - if (match) - { - const fullID = go.FullID.toString(); - if (!idCheck[fullID]) - { - matches.push(go); - idCheck[fullID] = true; - } - } - } - if (go.children && go.children.length > 0) - { - for (const child of go.children) - { - it(child); - } - } - }; - for (const go of objects) - { - it(go); - } - return matches; - } - - getParcelAt(x: number, y: number): Promise - { - return this.currentRegion.getParcelProperties(x, y); - } - - getParcels(): Promise - { - return this.currentRegion.getParcels(); - } - - async getAllObjects(options: GetObjectsOptions): Promise - { - const objs = await this.currentRegion.objects.getAllObjects(); - if (options.resolve) - { - await this.currentRegion.resolver.resolveObjects(objs, options); - } - return objs; - } - - async getObjectsInArea(minX: number, maxX: number, minY: number, maxY: number, minZ: number, maxZ: number, resolve: boolean = false): Promise - { - const objs = await this.currentRegion.objects.getObjectsInArea(minX, maxX, minY, maxY, minZ, maxZ); - if (resolve) - { - await this.currentRegion.resolver.resolveObjects(objs, {}); - } - return objs; - } - - async pruneObjects(checkList: GameObject[]): Promise - { - let uuids = []; - let objects = []; - const stillAlive: { [key: string]: GameObject } = {}; - const checkObjects = async(uuidList: any[], objectList: GameObject[]) => - { - - const objRef: { [key: string]: GameObject } = {}; - for (const obj of objectList) - { - objRef[obj.FullID.toString()] = obj; - } - const result = await this.currentRegion.caps.capsPostXML('GetObjectCost', { - 'object_ids': uuidList - }); - for (const u of Object.keys(result)) - { - stillAlive[u] = objRef[u]; - } - }; - - for (const o of checkList) - { - if (o.FullID) - { - uuids.push(new LLSD.UUID(o.FullID)); - objects.push(o); - if (uuids.length > 256) - { - await checkObjects(uuids, objects); - uuids = []; - objects = []; - } - } - } - if (uuids.length > 0) - { - await checkObjects(uuids, objects); - } - const deadObjects: GameObject[] = []; - for (const o of checkList) - { - let found = false; - if (o.FullID) - { - const fullID = o.FullID.toString(); - if (stillAlive[fullID]) - { - found = true; - } - } - if (!found) - { - deadObjects.push(o); - } - } - return deadObjects; - } - - setPersist(persist: boolean): void - { - this.currentRegion.objects.setPersist(persist); - } - - async grabObject(localID: number | UUID, - grabOffset: Vector3 = Vector3.getZero(), - uvCoordinate: Vector3 = Vector3.getZero(), - stCoordinate: Vector3 = Vector3.getZero(), - faceIndex: number = 0, - position: Vector3 = Vector3.getZero(), - normal: Vector3 = Vector3.getZero(), - binormal: Vector3 = Vector3.getZero()): Promise - { - if (localID instanceof UUID) - { - const obj: GameObject = this.currentRegion.objects.getObjectByUUID(localID); - localID = obj.ID; - } - const msg = new ObjectGrabMessage(); - msg.AgentData = { - AgentID: this.agent.agentID, - SessionID: this.circuit.sessionID - }; - msg.ObjectData = { - LocalID: localID, - GrabOffset: grabOffset - }; - msg.SurfaceInfo = [ - { - UVCoord: uvCoordinate, - STCoord: stCoordinate, - FaceIndex: faceIndex, - Position: position, - Normal: normal, - Binormal: binormal - } - ]; - const seqID = this.circuit.sendMessage(msg, PacketFlags.Reliable); - return this.circuit.waitForAck(seqID, 10000); - } - - async deGrabObject(localID: number | UUID, - _grabOffset: Vector3 = Vector3.getZero(), - uvCoordinate: Vector3 = Vector3.getZero(), - stCoordinate: Vector3 = Vector3.getZero(), - faceIndex: number = 0, - position: Vector3 = Vector3.getZero(), - normal: Vector3 = Vector3.getZero(), - binormal: Vector3 = Vector3.getZero()): Promise - { - if (localID instanceof UUID) - { - const obj: GameObject = this.currentRegion.objects.getObjectByUUID(localID); - localID = obj.ID; - } - const msg = new ObjectDeGrabMessage(); - msg.AgentData = { - AgentID: this.agent.agentID, - SessionID: this.circuit.sessionID - }; - msg.ObjectData = { - LocalID: localID - }; - msg.SurfaceInfo = [ - { - UVCoord: uvCoordinate, - STCoord: stCoordinate, - FaceIndex: faceIndex, - Position: position, - Normal: normal, - Binormal: binormal - } - ]; - const seqID = this.circuit.sendMessage(msg, PacketFlags.Reliable); - return this.circuit.waitForAck(seqID, 10000); - } - - async dragGrabbedObject(localID: number | UUID, - grabPosition: Vector3, - grabOffset: Vector3 = Vector3.getZero(), - uvCoordinate: Vector3 = Vector3.getZero(), - stCoordinate: Vector3 = Vector3.getZero(), - faceIndex: number = 0, - position: Vector3 = Vector3.getZero(), - normal: Vector3 = Vector3.getZero(), - binormal: Vector3 = Vector3.getZero()): Promise - { - // For some reason this message takes a UUID when the others take a LocalID - wtf? - if (!(localID instanceof UUID)) - { - const obj: GameObject = this.currentRegion.objects.getObjectByLocalID(localID); - localID = obj.FullID; - } - const msg = new ObjectGrabUpdateMessage(); - msg.AgentData = { - AgentID: this.agent.agentID, - SessionID: this.circuit.sessionID - }; - msg.ObjectData = { - ObjectID: localID, - GrabOffsetInitial: grabOffset, - GrabPosition: grabPosition, - TimeSinceLast: 0 - }; - msg.SurfaceInfo = [ - { - UVCoord: uvCoordinate, - STCoord: stCoordinate, - FaceIndex: faceIndex, - Position: position, - Normal: normal, - Binormal: binormal - } - ]; - const seqID = this.circuit.sendMessage(msg, PacketFlags.Reliable); - return this.circuit.waitForAck(seqID, 10000); - } - - async touchObject(localID: number | UUID, - grabOffset: Vector3 = Vector3.getZero(), - uvCoordinate: Vector3 = Vector3.getZero(), - stCoordinate: Vector3 = Vector3.getZero(), - faceIndex: number = 0, - position: Vector3 = Vector3.getZero(), - normal: Vector3 = Vector3.getZero(), - binormal: Vector3 = Vector3.getZero()): Promise - { - if (localID instanceof UUID) - { - const obj: GameObject = this.currentRegion.objects.getObjectByUUID(localID); - localID = obj.ID; - } - await this.grabObject(localID, grabOffset, uvCoordinate, stCoordinate, faceIndex, position, normal, binormal); - return this.deGrabObject(localID, grabOffset, uvCoordinate, stCoordinate, faceIndex, position, normal, binormal); - } } diff --git a/lib/classes/commands/TeleportCommands.ts b/lib/classes/commands/TeleportCommands.ts index 85e8299..2b80550 100644 --- a/lib/classes/commands/TeleportCommands.ts +++ b/lib/classes/commands/TeleportCommands.ts @@ -2,24 +2,25 @@ import { CommandsBase } from './CommandsBase'; import { Region } from '../Region'; import { TeleportEventType } from '../../enums/TeleportEventType'; import { TeleportLureRequestMessage } from '../messages/TeleportLureRequest'; -import { Vector3 } from '../Vector3'; +import type { Vector3 } from '../Vector3'; import { TeleportLocationRequestMessage } from '../messages/TeleportLocationRequest'; -import * as Long from 'long'; -import { Agent } from '../Agent'; -import { Subscription } from 'rxjs'; -import { TeleportEvent } from '../../events/TeleportEvent'; -import { LureEvent } from '../../events/LureEvent'; +import type * as Long from 'long'; +import type { Agent } from '../Agent'; +import type { Subscription } from 'rxjs'; +import type { TeleportEvent } from '../../events/TeleportEvent'; +import type { LureEvent } from '../../events/LureEvent'; import { TeleportFlags } from '../../enums/TeleportFlags'; import { PacketFlags } from '../../enums/PacketFlags'; -import { RegionInfoReplyEvent } from '../../events/RegionInfoReplyEvent'; -import { Bot } from '../../Bot'; +import type { RegionInfoReplyEvent } from '../../events/RegionInfoReplyEvent'; +import type { Bot } from '../../Bot'; import { Utils } from '../Utils'; export class TeleportCommands extends CommandsBase { private expectingTeleport = false; - private teleportSubscription: Subscription; - constructor(region: Region, agent: Agent, bot: Bot) + private readonly teleportSubscription: Subscription; + + public constructor(region: Region, agent: Agent, bot: Bot) { super(region, agent, bot); this.teleportSubscription = this.bot.clientEvents.onTeleportEvent.subscribe((e: TeleportEvent) => @@ -46,7 +47,7 @@ export class TeleportCommands extends CommandsBase this.bot.changeRegion(newRegion, false).then(() => { // Change region successful - }).catch((error) => + }).catch((error: unknown) => { console.log('Failed to change region'); console.error(error); @@ -56,12 +57,70 @@ export class TeleportCommands extends CommandsBase }); } - shutdown(): void + public override shutdown(): void { this.teleportSubscription.unsubscribe(); } - private awaitTeleportEvent(requested: boolean): Promise + public async acceptTeleport(lure: LureEvent): Promise + { + const {circuit} = this.currentRegion; + const tlr = new TeleportLureRequestMessage(); + tlr.Info = { + AgentID: this.agent.agentID, + SessionID: circuit.sessionID, + LureID: lure.lureID, + TeleportFlags: TeleportFlags.ViaLure + }; + circuit.sendMessage(tlr, PacketFlags.Reliable); + return this.awaitTeleportEvent(true) + } + + public async teleportToRegionCoordinates(x: number, y: number, position: Vector3, lookAt: Vector3): Promise + { + const globalPos = Utils.RegionCoordinatesToHandle(x, y); + return this.teleportToHandle(globalPos.regionHandle, position, lookAt); + } + + public async teleportToHandle(handle: Long, position: Vector3, lookAt: Vector3): Promise + { + return new Promise((resolve, reject) => + { + const rtm = new TeleportLocationRequestMessage(); + rtm.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.circuit.sessionID + }; + rtm.Info = { + LookAt: lookAt, + Position: position, + RegionHandle: handle + }; + this.circuit.sendMessage(rtm, PacketFlags.Reliable); + this.awaitTeleportEvent(true).then((event: TeleportEvent) => + { + resolve(event); + }).catch((err: unknown) => + { + if (err instanceof Error) + { + reject(err); + } + else + { + reject(new Error('Failed to teleport')); + } + }); + }); + } + + public async teleportTo(regionName: string, position: Vector3, lookAt: Vector3): Promise + { + const region: RegionInfoReplyEvent = await this.bot.clientCommands.grid.getRegionByName(regionName); + return this.teleportToHandle(region.handle, position, lookAt); + } + + private async awaitTeleportEvent(requested: boolean): Promise { return new Promise((resolve, reject) => { @@ -85,7 +144,14 @@ export class TeleportCommands extends CommandsBase } if (e.eventType === TeleportEventType.TeleportFailed) { - reject(e); + reject(new class extends Error + { + public teleportEvent: TeleportEvent = e; + public constructor() + { + super('Teleport failed') + } + }); } else if (e.eventType === TeleportEventType.TeleportCompleted) { @@ -115,9 +181,16 @@ export class TeleportCommands extends CommandsBase this.bot.changeRegion(region, requested).then(() => { resolve(e); - }).catch((error) => + }).catch((error: unknown) => { - reject(error); + if (error instanceof Error) + { + reject(error); + } + else + { + reject(new Error('Failed to teleport')); + } }); } }); @@ -128,78 +201,4 @@ export class TeleportCommands extends CommandsBase } }); } - - acceptTeleport(lure: LureEvent): Promise - { - return new Promise((resolve, reject) => - { - const circuit = this.currentRegion.circuit; - const tlr = new TeleportLureRequestMessage(); - tlr.Info = { - AgentID: this.agent.agentID, - SessionID: circuit.sessionID, - LureID: lure.lureID, - TeleportFlags: TeleportFlags.ViaLure - }; - circuit.sendMessage(tlr, PacketFlags.Reliable); - this.awaitTeleportEvent(true).then((event: TeleportEvent) => - { - resolve(event); - }).catch((err) => - { - reject(err); - }); - }); - } - - teleportToRegionCoordinates(x: number, y: number, position: Vector3, lookAt: Vector3): Promise - { - const globalPos = Utils.RegionCoordinatesToHandle(x, y); - return this.teleportToHandle(globalPos.regionHandle, position, lookAt); - } - - teleportToHandle(handle: Long, position: Vector3, lookAt: Vector3): Promise - { - return new Promise((resolve, reject) => - { - const rtm = new TeleportLocationRequestMessage(); - rtm.AgentData = { - AgentID: this.agent.agentID, - SessionID: this.circuit.sessionID - }; - rtm.Info = { - LookAt: lookAt, - Position: position, - RegionHandle: handle - }; - this.circuit.sendMessage(rtm, PacketFlags.Reliable); - this.awaitTeleportEvent(true).then((event: TeleportEvent) => - { - resolve(event); - }).catch((err) => - { - reject(err); - }); - }); - } - - teleportTo(regionName: string, position: Vector3, lookAt: Vector3): Promise - { - return new Promise((resolve, reject) => - { - this.bot.clientCommands.grid.getRegionByName(regionName).then((region: RegionInfoReplyEvent) => - { - this.teleportToHandle(region.handle, position, lookAt).then((event: TeleportEvent) => - { - resolve(event); - }).catch((err) => - { - reject(err); - }) - }).catch((err) => - { - reject(err); - }); - }); - } } diff --git a/lib/classes/interfaces/IGameObjectData.ts b/lib/classes/interfaces/IGameObjectData.ts index d60bdaa..0cf7e20 100644 --- a/lib/classes/interfaces/IGameObjectData.ts +++ b/lib/classes/interfaces/IGameObjectData.ts @@ -1,20 +1,20 @@ -import { PCode } from '../../enums/PCode'; -import { SoundFlags } from '../../enums/SoundFlags'; -import { GameObject } from '../public/GameObject'; -import { UUID } from '../UUID'; -import { Vector3 } from '../Vector3'; -import { Color4 } from '../Color4'; -import { Quaternion } from '../Quaternion'; -import { Vector4 } from '../Vector4'; -import { Tree } from '../../enums/Tree'; -import { ParticleSystem } from '../ParticleSystem'; -import { NameValue } from '../NameValue'; -import { TextureEntry } from '../TextureEntry'; -import { FlexibleData } from '../public/FlexibleData'; -import { LightData } from '../public/LightData'; -import { LightImageData } from '../public/LightImageData'; -import { SculptData } from '../public/SculptData'; -import { MeshData } from '../public/MeshData'; +import type { PCode } from '../../enums/PCode'; +import type { SoundFlags } from '../../enums/SoundFlags'; +import type { GameObject } from '../public/GameObject'; +import type { UUID } from '../UUID'; +import type { Vector3 } from '../Vector3'; +import type { Color4 } from '../Color4'; +import type { Quaternion } from '../Quaternion'; +import type { Vector4 } from '../Vector4'; +import type { Tree } from '../../enums/Tree'; +import type { ParticleSystem } from '../ParticleSystem'; +import type { NameValue } from '../NameValue'; +import type { TextureEntry } from '../TextureEntry'; +import type { FlexibleData } from '../public/FlexibleData'; +import type { LightData } from '../public/LightData'; +import type { LightImageData } from '../public/LightImageData'; +import type { SculptData } from '../public/SculptData'; +import type { MeshData } from '../public/MeshData'; export interface IGameObjectData { @@ -61,7 +61,7 @@ export interface IGameObjectData ParentID?: number; OwnerID: UUID; IsAttachment: boolean; - NameValue: { [key: string]: NameValue }; + NameValue: Map; PCode: PCode; State?: number; CRC?: number; diff --git a/lib/classes/interfaces/ILandBlock.ts b/lib/classes/interfaces/ILandBlock.ts index d89127a..c669ca5 100644 --- a/lib/classes/interfaces/ILandBlock.ts +++ b/lib/classes/interfaces/ILandBlock.ts @@ -1,6 +1,6 @@ -import { LandFlags } from '../../enums/LandFlags'; -import { LandType } from '../../enums/LandType'; - +import type { LandFlags } from '../../enums/LandFlags'; +import type { LandType } from '../../enums/LandType'; + export interface ILandBlock { landType: LandType; diff --git a/lib/classes/interfaces/IObjectStore.ts b/lib/classes/interfaces/IObjectStore.ts index 8d97a0e..8f8a74f 100644 --- a/lib/classes/interfaces/IObjectStore.ts +++ b/lib/classes/interfaces/IObjectStore.ts @@ -1,17 +1,18 @@ -import { RBush3D } from 'rbush-3d/dist'; -import { UUID } from '../UUID'; -import { GameObject } from '../public/GameObject'; +import type { RBush3D } from 'rbush-3d/dist'; +import type { UUID } from '../UUID'; +import type { GameObject } from '../public/GameObject'; +import type { GetObjectsOptions } from '../commands/RegionCommands'; export interface IObjectStore { rtree?: RBush3D; - populateChildren(obj: GameObject): void; - getObjectsByParent(parentID: number): GameObject[]; - shutdown(): void; - getObjectsInArea(minX: number, maxX: number, minY: number, maxY: number, minZ: number, maxZ: number): Promise; - getObjectByUUID(fullID: UUID): GameObject; - getObjectByLocalID(ID: number): GameObject; - getNumberOfObjects(): number; - getAllObjects(): Promise; - setPersist(persist: boolean): void; + populateChildren: (obj: GameObject) => void; + getObjectsByParent: (parentID: number) => GameObject[]; + shutdown: () => void; + getObjectsInArea: (minX: number, maxX: number, minY: number, maxY: number, minZ: number, maxZ: number) => GameObject[]; + getObjectByUUID: (fullID: UUID) => GameObject; + getObjectByLocalID: (ID: number) => GameObject; + getNumberOfObjects: () => number; + getAllObjects: (options: GetObjectsOptions) => GameObject[]; + setPersist: (persist: boolean) => void; } diff --git a/lib/classes/interfaces/IResolveJob.ts b/lib/classes/interfaces/IResolveJob.ts index 2eb7239..dd825e6 100644 --- a/lib/classes/interfaces/IResolveJob.ts +++ b/lib/classes/interfaces/IResolveJob.ts @@ -1,5 +1,5 @@ -import { GameObject } from '../..'; -import { GetObjectsOptions } from '../commands/RegionCommands'; +import type { GameObject } from '../..'; +import type { GetObjectsOptions } from '../commands/RegionCommands'; export interface IResolveJob { diff --git a/lib/classes/interfaces/ITreeBoundingBox.ts b/lib/classes/interfaces/ITreeBoundingBox.ts index 4f1552f..c9b25d7 100644 --- a/lib/classes/interfaces/ITreeBoundingBox.ts +++ b/lib/classes/interfaces/ITreeBoundingBox.ts @@ -1,5 +1,5 @@ -import { BBox } from 'rbush-3d/dist'; -import { GameObject } from '../public/GameObject'; +import type { BBox } from 'rbush-3d/dist'; +import type { GameObject } from '../public/GameObject'; export interface ITreeBoundingBox extends BBox { diff --git a/lib/classes/llsd/LLSD.spec.ts b/lib/classes/llsd/LLSD.spec.ts new file mode 100644 index 0000000..51ca20e --- /dev/null +++ b/lib/classes/llsd/LLSD.spec.ts @@ -0,0 +1,391 @@ +import { describe, expect, it } from "vitest"; +import { LLSD } from "./LLSD"; +import { LLSDMap } from "./LLSDMap"; +import { LLSDInteger } from "./LLSDInteger"; +import { LLSDReal } from "./LLSDReal"; +import { UUID } from "../UUID"; +import { LLSDURI } from "./LLSDURI"; +import { LLSDNotation } from "./LLSDNotation"; +import { LLSDType } from "./LLSDType"; +import { Utils } from '../Utils'; + +describe('LLSD', () => +{ + describe('Notation', () => + { + it('correctly handles LLSD notation format', () => + { + const notation = '{"string":"Hello!","map":{"nestedMap":{"nestedArray":["one",i2,r3.14,u00000000-1111-2222-3333-444444444444,l"https://www.blahblah.com",{"date":d"2024-11-22T20:35:36.638Z"},!]}},"arr":[1,0,!,!,d"2024-11-22T20:35:36.638Z",b64"dGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==",b16"50656570657273206a65657065727321",rNaN,rNaN,r-Infinity,rInfinity,r0.0,r-0.0]}'; + const parsedType = LLSD.parseNotation(notation); + expect(parsedType).toBeInstanceOf(LLSDMap); + const parsed = parsedType as unknown as { + string: string, + map: { + nestedMap: { + nestedArray: [ + string, + LLSDInteger, + LLSDReal, + UUID, + LLSDURI, + { + date: Date + }, + null + ] + } + }, + arr: [ + boolean, + boolean, + null, + null, + Date, + Buffer, + Buffer, + number, + number, + number, + number, + number, + number + ] + } + expect(parsed.string).toBe('Hello!'); + expect(parsed.map).toBeInstanceOf(LLSDMap); + expect(parsed.map?.nestedMap).toBeInstanceOf(LLSDMap); + expect(parsed.map?.nestedMap?.nestedArray).toBeInstanceOf(Array); + expect(typeof parsed.map?.nestedMap?.nestedArray?.[0]).toBe('string'); + expect(parsed.map?.nestedMap?.nestedArray.length).toBe(7); + expect(parsed.map?.nestedMap?.nestedArray?.[0]).toBe('one'); + expect(parsed.map?.nestedMap?.nestedArray?.[1]).toBeInstanceOf(LLSDInteger); + expect(parsed.map?.nestedMap?.nestedArray?.[1].valueOf()).toBe(2); + expect(parsed.map?.nestedMap?.nestedArray?.[2]).toBeInstanceOf(LLSDReal); + expect(parsed.map?.nestedMap?.nestedArray?.[2].valueOf()).toBe(3.14); + expect(parsed.map?.nestedMap?.nestedArray?.[3]).toBeInstanceOf(UUID); + expect(parsed.map?.nestedMap?.nestedArray?.[3].toString()).toBe('00000000-1111-2222-3333-444444444444'); + expect(parsed.map?.nestedMap?.nestedArray?.[4]).toBeInstanceOf(LLSDURI); + expect(parsed.map.nestedMap?.nestedArray?.[4].valueOf()).toBe('https://www.blahblah.com'); + expect(parsed.map?.nestedMap?.nestedArray?.[5]).toBeInstanceOf(LLSDMap); + expect(parsed.map?.nestedMap?.nestedArray?.[5].date).toBeInstanceOf(Date); + expect(parsed.map?.nestedMap?.nestedArray?.[5].date.toISOString()).toBe(new Date('2024-11-22T20:35:36.638Z').toISOString()); + expect(parsed.map?.nestedMap?.nestedArray?.[6]).toBeNull(); + expect(parsed.arr).toBeInstanceOf(Array); + expect(parsed.arr.length).toBe(13); + expect(typeof parsed.arr[0]).toBe('boolean'); + expect(parsed.arr[0]).toBe(true); + expect(typeof parsed.arr[1]).toBe('boolean'); + expect(parsed.arr[1]).toBe(false); + expect(parsed.arr[2]).toBeNull(); + expect(parsed.arr[3]).toBeNull(); + expect(parsed.arr[4]).toBeInstanceOf(Date); + expect(parsed.arr[4].toISOString()).toBe(new Date('2024-11-22T20:35:36.638Z').toISOString()); + expect(parsed.arr[5]).toBeInstanceOf(Buffer); + expect(parsed.arr[5].toString('utf-8')).toBe('the quick brown fox jumps over the lazy dog'); + expect(parsed.arr[6]).toBeInstanceOf(Buffer); + expect(parsed.arr[6].toString('utf-8')).toBe('Peepers jeepers!'); + expect(parsed.arr[7]).toBeInstanceOf(LLSDReal); + expect(parsed.arr[7].valueOf()).toBe(Number.NaN); + expect(parsed.arr[8]).toBeInstanceOf(LLSDReal); + expect(parsed.arr[8].valueOf()).toBe(Number.NaN); + expect(parsed.arr[9]).toBeInstanceOf(LLSDReal); + expect(parsed.arr[9].valueOf()).toBe(Number.NEGATIVE_INFINITY); + expect(parsed.arr[10]).toBeInstanceOf(LLSDReal); + expect(parsed.arr[10].valueOf()).toBe(Number.POSITIVE_INFINITY); + expect(parsed.arr[11]).toBeInstanceOf(LLSDReal); + expect(parsed.arr[11].valueOf()).toBe(0); + expect(parsed.arr[12]).toBeInstanceOf(LLSDReal); + expect(parsed.arr[12].valueOf()).toBe(-0); + + const rebuilt = LLSD.toNotation(parsed as unknown as LLSDType); + expect(rebuilt).toBe('{"string":"Hello!","map":{"nestedMap":{"nestedArray":["one",i2,r3.14,u00000000-1111-2222-3333-444444444444,l"https://www.blahblah.com",{"date":d"2024-11-22T20:35:36.638Z"},!]}},"arr":[1,0,!,!,d"2024-11-22T20:35:36.638Z",b64"dGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==",b64"UGVlcGVycyBqZWVwZXJzIQ==",rNaN,rNaN,r-Infinity,rInfinity,r0,r-0]}'); + }); + + it('escapes special characters', () => + { + const str = '\'"ⳑ⣺⋺➍⣋▎⊻™⿨₀⏆⨣⑬⛪␹⩯\0⁇⯒'; + const encoded= LLSD.toNotation(str); + expect(encoded.valueOf()).toBe('"\'\\"ⳑ⣺⋺➍⣋▎⊻™⿨₀⏆⨣⑬⛪␹⩯\\x00⁇⯒"'); + + const parsed = LLSD.parseNotation(encoded); + expect(typeof parsed).toBe('string'); + expect(parsed).toBe(str); + }); + + it('handles alternative representations', () => + { + const str = 'This is a test\\\'" string'; + const buf = Buffer.from(str, 'utf-8'); + const notationTests: [string, unknown][] = [ + ['s(' + str.length + ')"' + str + '"', str], + ['!', null], + ['r1', 1.0], + ['r1.', 1.0], + ['r1.0000', 1.0], + ['r-0.043753', -0.043753], + ['r-0.0', -0.0], + ['rInfinity', Number.POSITIVE_INFINITY], + ['r-Infinity', Number.NEGATIVE_INFINITY], + ['r2.5e-4', 0.00025], + ['r-2.7e5', -270000], + ['r1e6', 1000000], + ['1', true], + ['t', true], + ['T', true], + ['true', true], + ['TRUE', true], + ['0', false], + ['f', false], + ['F', false], + ['false', false], + ['FALSE', false], + ['b(' + buf.length + ')"' + buf.toString('binary') + '"', buf], + ['b16"' + buf.toString('hex') + '"', buf], + ['b64"' + buf.toString('base64') + '"', buf], + ['\'' + LLSDNotation.escapeStringSimple(str, '\'') + '\'', str], + ]; + + for(const test of notationTests) + { + const parsed = LLSD.parseNotation(test[0]); + if (typeof parsed === 'string') + { + expect(parsed).toBe(test[1]); + } + else if (parsed instanceof Buffer && test[1] instanceof Buffer) + { + expect(parsed.toString('base64')).toBe(test[1].toString('base64')) + } + else if (parsed instanceof LLSDInteger || parsed instanceof LLSDReal) + { + expect(parsed.valueOf()).toBe(test[1]); + } + else + { + expect(parsed).toBe(test[1]); + } + } + }); + }); + describe('Binary', () => + { + it('correctly handles LLSD binary format', () => + { + const binary = Buffer.from('ewAAAANrAAAABnN0cmluZ3MAAAAGSGVsbG8hawAAAANtYXB7AAAAAWsAAAAJbmVzdGVkTWFwewAAAAFrAAAAC25lc3RlZEFycmF5WwAAAAdzAAAAA29uZWkAAAACckAJHrhR64UfdQAAAAARESIiMzNERERERERsAAAAGGh0dHBzOi8vd3d3LmJsYWhibGFoLmNvbXsAAAABawAAAARkYXRlZEHZ0D2OsxJvfSFdfX1rAAAAA2FyclsAAAAMMTAhIWRB2dA9jrMSb2IAAAArdGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZ3J/+AAAAAAAAHJ/+AAAAAAAAHL/8AAAAAAAAHJ/8AAAAAAAAGkAAAAAcoAAAAAAAAAAXX0=', 'base64'); + const parsedType = LLSD.parseBinary(binary); + expect(parsedType).toBeInstanceOf(LLSDMap); + const parsed = parsedType as unknown as { + string: string, + map: { + nestedMap: { + nestedArray: [ + string, + LLSDInteger, + LLSDReal, + UUID, + LLSDURI, + { + date: Date + }, + null + ] + } + }, + arr: [ + boolean, + boolean, + null, + null, + Date, + Buffer, + Buffer, + number, + number, + number, + number, + number, + number + ] + } + expect(parsed).toBeInstanceOf(LLSDMap); + expect(parsed.string).toBe('Hello!'); + expect(parsed.map).toBeInstanceOf(LLSDMap); + expect(parsed.map.nestedMap).toBeInstanceOf(LLSDMap); + expect(parsed.map.nestedMap.nestedArray).toBeInstanceOf(Array); + expect(typeof parsed.map.nestedMap.nestedArray[0]).toBe('string'); + expect(parsed.map.nestedMap.nestedArray.length).toBe(7); + expect(parsed.map.nestedMap.nestedArray[0]).toBe('one'); + expect(parsed.map.nestedMap.nestedArray[1]).toBeInstanceOf(LLSDInteger); + expect(parsed.map.nestedMap.nestedArray[1].valueOf()).toBe(2); + expect(parsed.map.nestedMap.nestedArray[2]).toBeInstanceOf(LLSDReal); + expect(parsed.map.nestedMap.nestedArray[2].valueOf()).toBe(3.14); + expect(parsed.map.nestedMap.nestedArray[3]).toBeInstanceOf(UUID); + expect(parsed.map.nestedMap.nestedArray[3].toString()).toBe('00000000-1111-2222-3333-444444444444'); + expect(parsed.map.nestedMap.nestedArray[4]).toBeInstanceOf(LLSDURI); + expect(parsed.map.nestedMap.nestedArray[4].valueOf()).toBe('https://www.blahblah.com'); + expect(parsed.map.nestedMap.nestedArray[5]).toBeInstanceOf(LLSDMap); + expect(parsed.map.nestedMap.nestedArray[5].date).toBeInstanceOf(Date); + expect(parsed.map.nestedMap.nestedArray[5].date.toISOString()).toBe(new Date('2024-11-22T21:23:06.798Z').toISOString()); + expect(parsed.map.nestedMap.nestedArray[6]).toBeNull(); + expect(parsed.arr).toBeInstanceOf(Array); + expect(parsed.arr.length).toBe(12); + expect(typeof parsed.arr[0]).toBe('boolean'); + expect(parsed.arr[0]).toBe(true); + expect(typeof parsed.arr[1]).toBe('boolean'); + expect(parsed.arr[1]).toBe(false); + expect(parsed.arr[2]).toBeNull(); + expect(parsed.arr[3]).toBeNull(); + expect(parsed.arr[4]).toBeInstanceOf(Date); + expect(parsed.arr[4].toISOString()).toBe(new Date('2024-11-22T21:23:06.798Z').toISOString()); + expect(parsed.arr[5]).toBeInstanceOf(Buffer); + expect(parsed.arr[5].toString('utf-8')).toBe('the quick brown fox jumps over the lazy dog'); + expect(parsed.arr[6]).toBeInstanceOf(LLSDReal); + expect(parsed.arr[6].valueOf()).toBe(Number.NaN); + expect(parsed.arr[7]).toBeInstanceOf(LLSDReal); + expect(parsed.arr[7].valueOf()).toBe(Number.NaN); + expect(parsed.arr[8]).toBeInstanceOf(LLSDReal); + expect(parsed.arr[8].valueOf()).toBe(Number.NEGATIVE_INFINITY); + expect(parsed.arr[9]).toBeInstanceOf(LLSDReal); + expect(parsed.arr[9].valueOf()).toBe(Number.POSITIVE_INFINITY); + expect(parsed.arr[10]).toBeInstanceOf(LLSDInteger); + expect(parsed.arr[10].valueOf()).toBe(0); + expect(parsed.arr[11]).toBeInstanceOf(LLSDReal); + expect(parsed.arr[11].valueOf()).toBe(-0); + + const rebuilt = LLSD.toBinary(parsedType); + expect(rebuilt.toString('base64')).toBe(binary.toString('base64')); + }); + }); + describe('XML', () => + { + it('correctly handles LLSD XML format', () => + { + const xml = 'stringHello!mapnestedMapnestedArrayone23.1400000000-1111-2222-3333-444444444444https://www.blahblah.comdate2024-11-22T21:23:06.798Zarrtrue2024-11-22T21:23:06.798ZdGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==NaNQNaNQ-Infinity+Infinity+Zero-Zero'; + const parsedType = LLSD.parseXML(xml); + expect(parsedType).toBeInstanceOf(LLSDMap); + const parsed = parsedType as unknown as { + string: string, + map: { + nestedMap: { + nestedArray: [ + string, + LLSDInteger, + LLSDReal, + UUID, + LLSDURI, + { + date: Date + }, + null + ] + } + }, + arr: [ + boolean, + boolean, + null, + null, + Date, + Buffer, + Buffer, + number, + number, + number, + number, + number, + number + ] + }; + expect(parsed).toBeInstanceOf(LLSDMap); + expect(parsed.string).toBe('Hello!'); + expect(parsed.map).toBeInstanceOf(LLSDMap); + expect(parsed.map.nestedMap).toBeInstanceOf(LLSDMap); + expect(parsed.map.nestedMap.nestedArray).toBeInstanceOf(Array); + expect(typeof parsed.map.nestedMap.nestedArray[0]).toBe('string'); + expect(parsed.map.nestedMap.nestedArray.length).toBe(7); + expect(parsed.map.nestedMap.nestedArray[0]).toBe('one'); + expect(parsed.map.nestedMap.nestedArray[1]).toBeInstanceOf(LLSDInteger); + expect(parsed.map.nestedMap.nestedArray[1].valueOf()).toBe(2); + expect(parsed.map.nestedMap.nestedArray[2]).toBeInstanceOf(LLSDReal); + expect(parsed.map.nestedMap.nestedArray[2].valueOf()).toBe(3.14); + expect(parsed.map.nestedMap.nestedArray[3]).toBeInstanceOf(UUID); + expect(parsed.map.nestedMap.nestedArray[3].toString()).toBe('00000000-1111-2222-3333-444444444444'); + expect(parsed.map.nestedMap.nestedArray[4]).toBeInstanceOf(LLSDURI); + expect(parsed.map.nestedMap.nestedArray[4].valueOf()).toBe('https://www.blahblah.com'); + expect(parsed.map.nestedMap.nestedArray[5]).toBeInstanceOf(LLSDMap); + expect(parsed.map.nestedMap.nestedArray[5].date).toBeInstanceOf(Date); + expect(parsed.map.nestedMap.nestedArray[5].date.toISOString()).toBe(new Date('2024-11-22T21:23:06.798Z').toISOString()); + expect(parsed.map.nestedMap.nestedArray[6]).toBeNull(); + expect(parsed.arr).toBeInstanceOf(Array); + expect(parsed.arr.length).toBe(12); + expect(typeof parsed.arr[0]).toBe('boolean'); + expect(parsed.arr[0]).toBe(true); + expect(typeof parsed.arr[1]).toBe('boolean'); + expect(parsed.arr[1]).toBe(false); + expect(parsed.arr[2]).toBeNull(); + expect(parsed.arr[3]).toBeNull(); + expect(parsed.arr[4]).toBeInstanceOf(Date); + expect(parsed.arr[4].toISOString()).toBe(new Date('2024-11-22T21:23:06.798Z').toISOString()); + expect(parsed.arr[5]).toBeInstanceOf(Buffer); + expect(parsed.arr[5].toString('utf-8')).toBe('the quick brown fox jumps over the lazy dog'); + expect(parsed.arr[6]).toBeInstanceOf(LLSDReal); + expect(parsed.arr[6].valueOf()).toBe(Number.NaN); + expect(parsed.arr[7]).toBeInstanceOf(LLSDReal); + expect(parsed.arr[7].valueOf()).toBe(Number.NaN); + expect(parsed.arr[8]).toBeInstanceOf(LLSDReal); + expect(parsed.arr[8].valueOf()).toBe(Number.NEGATIVE_INFINITY); + expect(parsed.arr[9]).toBeInstanceOf(LLSDReal); + expect(parsed.arr[9].valueOf()).toBe(Number.POSITIVE_INFINITY); + expect(parsed.arr[10]).toBeInstanceOf(LLSDReal); + expect(parsed.arr[10].valueOf()).toBe(0); + expect(parsed.arr[11]).toBeInstanceOf(LLSDReal); + expect(parsed.arr[11].valueOf()).toBe(-0); + + const newXML = LLSD.toXML(parsedType); + expect(newXML).toBe(xml); + }); + + it('test', async () => { + + const xmlLL = 'ZippedeNqrZmBgYMwGEiJupTk5voklqUWZiTnFAalFbonJqdEg2WogwQxSwgISygQyGEA8Jk+XTNmCqQtBbA6YRpBaQZAIv2NOQUaib2JxtnNpSX5aGlybgEtmWlppcSpEPj8FYSCPa16ZZ15Jal5xZkklXJTdL78o1zexoPS5tPXu1zUi56foL3x8YMsPG5AkN0jSPy2tOLUkAq4DSTASVTAotSA1EaxSXQBNMBIuyAMWzC9JLMnMz4Pr5wwuSE12zs/JLwKFCQtI/D8aEQt2Lkida0UBSMQYLgBy/5Wc89E2DpODT5dsmjr3XPohsANAkhjuRwhGogpiuB8hiOR+sCCy+2trY2sBFvKF9A=='; + const xmlUs = 'ZippedeNqrZmBgYMwGEiJupTk5voklqUWZiTnFAalFbonJqdEg2WogwQRSwuTpkilbMJsbxOaAKQXJCoJE+B1zCjISfROLs51LS/LT0jKBYgwgCQGXzLS00uJUiHx+Sipchsc1r8wzryQ1rzizpBIuyu6XX5Trm1hQmj/161yD/aqOyxfVxTR6OU4ASXKDJP3T0opTSyLgOpAEI1EFg1ILUhPBKtUF0AQj4YI8YMH8ksSSzPw8uH7O4ILUZOf8nPwiUCiwgMT/oxGxYOeC1LlWFIBEjOECIPfX38t+svcv07o/bze+uJZwERyG3CBJDPcjBCNRBTHcjxBEcj9YENn9tbWxtQCx7oNQ'; + + const xmlLL2 = 'ZippedeNqrZmBgYMwGEiJupTk5voklqUWZiTnFAalFbonJqdFACbZqIMEMUsICEsoEMhhAPCZPl0zZgjmsIDYHTCNIrSBIhN8xpyAj0TexONu5tCQ/LQ2uTcAlMy2ttDgVIp+fgjCQxzWvzDOvJDWvOLOkEi7K7pdflOubWFD6XNp69+sakfNT9Bc+PrDlhw1Ikhsk6Z+WVpxaEgHXgSQYiSoYlFqQmghWqS6AJhgJF+QBC+aXJJZk5ufB9XMGF6QmO+fn5BeBwoQFJP4fjYgFOxekzrWiACRiDBcAuf9KzvloG4fJwadLNk2dey79ENgBIEkM9yMEI1EFMdyPEERyP1gQ2f21tZhRyDgahUM9CplGo3CoRyHzaBQO9ShkGY3CoR6FrKNROLSiMLYWAAu07ns='; + + const decodedLL = LLSD.parseXML(xmlLL) as LLSDMap<{ + Zipped: Buffer + }> as unknown as { + Zipped: Buffer + }; + + const decodedLL2 = LLSD.parseXML(xmlLL2) as LLSDMap<{ + Zipped: Buffer + }> as unknown as { + Zipped: Buffer + }; + + const decodedUs = LLSD.parseXML(xmlUs) as LLSDMap<{ + Zipped: Buffer + }> as unknown as { + Zipped: Buffer + }; + + const unzippedLL = await Utils.inflate(decodedLL.Zipped); + + const unzippedLL2 = await Utils.inflate(decodedLL2.Zipped); + + const secondDecodedLL = LLSD.parseBinary(unzippedLL); + + const secondDecodedLL2 = LLSD.parseBinary(unzippedLL2); + + const unzippedUs = await Utils.inflate(decodedUs.Zipped); + + const secondDecodedUs = LLSD.parseBinary(unzippedUs); + + console.log('LL: ' + JSON.stringify(secondDecodedLL)); + console.log('Us: ' + JSON.stringify(secondDecodedUs)); + console.log('LL2: ' + JSON.stringify(secondDecodedLL2)); + + }) + }); +}); \ No newline at end of file diff --git a/lib/classes/llsd/LLSD.ts b/lib/classes/llsd/LLSD.ts new file mode 100644 index 0000000..0831ac5 --- /dev/null +++ b/lib/classes/llsd/LLSD.ts @@ -0,0 +1,184 @@ +import type { LLSDType } from "./LLSDType"; +import type { LLSDTokenGenerator } from "./LLSDTokenGenerator"; +import type { LLSDToken } from "./LLSDToken"; +import { LLSDNotation } from "./LLSDNotation"; +import { BinaryReader } from "../BinaryReader"; +import { LLSDBinary } from "./LLSDBinary"; +import { XMLBuilder, XMLParser } from "fast-xml-parser"; +import { LLSDXML } from "./LLSDXML"; +import { BinaryWriter } from "../BinaryWriter"; +import { LLSDMap } from "./LLSDMap"; +import { UUID } from '../UUID'; +import { LLSDInteger } from './LLSDInteger'; +import { LLSDReal } from './LLSDReal'; +import { LLSDURI } from './LLSDURI'; +import { isFloat, isInt } from 'validator'; + +export class LLSD +{ + public static parseNotation(input: string): LLSDType + { + const tags = [ + '', + '' + ]; + for(const tag of tags) + { + if (input.startsWith(tag)) + { + input = input.substring(tag.length + 1); + break; + } + } + const generator = LLSDNotation.tokenize(input); + const getToken: LLSDTokenGenerator = (): LLSDToken | undefined => + { + return generator.next().value as LLSDToken | undefined; + } + return LLSDNotation.parseValueToken(getToken); + } + + public static parseBinary(input: Buffer, metadata?: { + readPos: number + }): LLSDType + { + const reader = new BinaryReader(input); + const tags = [ + '\n', + '\n' + ]; + for(const tag of tags) + { + if (reader.length() > tag.length && reader.peekBuffer(tag.length).toString('utf-8') === tag) + { + reader.seek(tag.length); + break; + } + } + + const val = LLSDBinary.parseValue(reader); + if (metadata) + { + metadata.readPos = reader.getPos(); + } + return val; + } + + public static toNotation(element: LLSDType, header = true): string + { + return (header ? '\n' : '') + LLSDNotation.encodeValue(element); + } + + public static toBinary(element: LLSDType): Buffer + { + const writer = new BinaryWriter() + LLSDBinary.encodeValue(element, writer); + return writer.get(); + } + + public static toXML(element: LLSDType): string + { + const writer = new XMLBuilder({ preserveOrder: true, suppressEmptyNode: true }); + const val = LLSDXML.encodeValue(element); + const toEncode = [{ + llsd: [val] + }]; + return writer.build(toEncode) as string; + } + + public static parseXML(input: string): LLSDType + { + const parser = new XMLParser({ preserveOrder: true }); + const obj = parser.parse(input) as Record[]; + if (obj.length !== 1) + { + throw new Error('Expected only one root element'); + } + if (obj[0].llsd === undefined) + { + throw new Error('Expected LLSD element') + } + const llsd = obj[0].llsd as Record[]; + return LLSDXML.parseValue(llsd); + } + + public static toLLSD(input: unknown): LLSDType + { + if (typeof input === 'string') + { + return input; + } + else if (input instanceof Buffer) + { + return input; + } + else if (input instanceof Date) + { + return input; + } + else if (input instanceof UUID) + { + return input; + } + else if (input instanceof LLSDMap) + { + return input; + } + else if (input instanceof LLSDInteger) + { + return input; + } + else if (input instanceof LLSDReal) + { + return input; + } + else if (input instanceof LLSDURI) + { + return input; + } + else if (typeof input === 'number') + { + if (isInt(String(input))) + { + return new LLSDInteger(input); + } + else if (isFloat(String(input))) + { + return new LLSDReal(input); + } + else + { + throw new Error('Unable to convert number type ' + input); + } + } + else if (typeof input === 'boolean') + { + return input; + } + else if (input === null) + { + return null; + } + else if (Array.isArray(input)) + { + const arr: LLSDType[] = []; + for(const item of input) + { + arr.push(LLSD.toLLSD(item)) + } + return arr; + } + else if (typeof input === 'object') + { + const keys = Object.keys(input); + const obj = new LLSDMap(); + for(const k of keys) + { + const value = (input as Record)[k]; + obj.add(k, LLSD.toLLSD(value)); + } + return obj; + } + throw new Error('Cannot convert type ' + typeof input + ' to LLSD'); + } +} diff --git a/lib/classes/llsd/LLSDArray.ts b/lib/classes/llsd/LLSDArray.ts index f566442..0d6fb75 100644 --- a/lib/classes/llsd/LLSDArray.ts +++ b/lib/classes/llsd/LLSDArray.ts @@ -1,14 +1,25 @@ -import { LLSDTokenGenerator } from './LLSDTokenGenerator'; +import type { LLSDTokenGenerator } from './LLSDTokenGenerator'; import { LLSDTokenType } from './LLSDTokenType'; -import { LLSDNotationParser } from './LLSDNotationParser'; -import { LLSDType } from './LLSDType'; +import { LLSDNotation } from './LLSDNotation'; +import type { LLSDType } from './LLSDType'; +import type { BinaryReader } from "../BinaryReader"; +import { LLSDBinary } from "./LLSDBinary"; +import { LLSDXML } from "./LLSDXML"; +import type { BinaryWriter } from "../BinaryWriter"; +import { Vector3 } from '../Vector3'; +import { LLSDReal } from './LLSDReal'; +import { Vector2 } from '../Vector2'; +import { Vector4 } from '../Vector4'; +import { Quaternion } from '../Quaternion'; +import { LLSDInteger } from './LLSDInteger'; export class LLSDArray { - public static parse(gen: LLSDTokenGenerator): LLSDType[] + public static parseNotation(gen: LLSDTokenGenerator): LLSDType[] { const arr: LLSDType[] = []; let value: LLSDType | undefined = undefined; + while (true) { const token = gen(); @@ -18,11 +29,11 @@ export class LLSDArray } switch (token.type) { - case LLSDTokenType.WHITESPACE: + case LLSDTokenType.Whitespace: { continue; } - case LLSDTokenType.ARRAY_END: + case LLSDTokenType.ArrayEnd: { if (value !== undefined) { @@ -30,7 +41,7 @@ export class LLSDArray } return arr; } - case LLSDTokenType.COMMA: + case LLSDTokenType.Comma: { if (value === undefined) { @@ -40,6 +51,25 @@ export class LLSDArray value = undefined; continue; } + case LLSDTokenType.Unknown: + case LLSDTokenType.Null: + case LLSDTokenType.MapStart: + case LLSDTokenType.MapEnd: + case LLSDTokenType.Colon: + case LLSDTokenType.ArrayStart: + case LLSDTokenType.Boolean: + case LLSDTokenType.Integer: + case LLSDTokenType.Real: + case LLSDTokenType.UUID: + case LLSDTokenType.StringFixedSingle: + case LLSDTokenType.StringFixedDouble: + case LLSDTokenType.StringDynamicStart: + case LLSDTokenType.URI: + case LLSDTokenType.Date: + case LLSDTokenType.BinaryStatic: + case LLSDTokenType.BinaryDynamicStart: + default: + break; } if (value !== undefined) @@ -47,7 +77,211 @@ export class LLSDArray throw new Error('Comma or end brace expected'); } - value = LLSDNotationParser.parseValueToken(gen, token); + value = LLSDNotation.parseValueToken(gen, token); } } + + public static parseBinary(reader: BinaryReader): LLSDType[] + { + const arr: LLSDType[] = []; + const length = reader.readUInt32BE(); + for(let x = 0; x < length; x++) + { + const val = LLSDBinary.parseValue(reader); + arr.push(val); + } + const endMap = reader.readFixedString(1); + if (endMap !== ']') + { + throw new Error('Array end expected'); + } + return arr; + } + + public static parseXML(element: Record[]): LLSDType[] + { + const arr: LLSDType[] = []; + for(const val of element) + { + arr.push(LLSDXML.parseValue([val])); + } + return arr; + } + + public static toNotation(value: LLSDType[]): string + { + const builder: string[] = ['[']; + let first = true; + for(const val of value) + { + if (first) + { + first = false; + } + else + { + builder.push(','); + } + builder.push(LLSDNotation.encodeValue(val)); + } + builder.push(']'); + return builder.join(''); + } + + public static toBinary(value: LLSDType[], writer: BinaryWriter): void + { + writer.writeFixedString('['); + writer.writeUInt32BE(value.length); + for(const v of value) + { + LLSDBinary.encodeValue(v, writer); + } + writer.writeFixedString(']'); + } + + public static toXML(value: LLSDType[]): unknown + { + const arr: unknown[] = []; + for(const val of value) + { + arr.push(LLSDXML.encodeValue(val)) + } + return { + array: arr + } + } + + public static toQuaternion(arr: LLSDType[]): Quaternion + { + if (arr.length !== 4) + { + throw new Error('Expect 4 arguments'); + } + return new Quaternion(this.componentToFloat(arr[0]), this.componentToFloat(arr[1]), this.componentToFloat(arr[2]), this.componentToFloat(arr[3])); + } + + public static toVector4(arr: LLSDType[]): Vector4 + { + if (arr.length !== 4) + { + throw new Error('Expect 4 arguments'); + } + return new Vector4(this.componentToFloat(arr[0]), this.componentToFloat(arr[1]), this.componentToFloat(arr[2]), this.componentToFloat(arr[3])); + } + + public static toVector3(arr: LLSDType[]): Vector3 + { + if (arr.length !== 3) + { + throw new Error('Expect 3 arguments'); + } + return new Vector3(this.componentToFloat(arr[0]), this.componentToFloat(arr[1]), this.componentToFloat(arr[2])); + } + + public static toVector2(arr: LLSDType[]): Vector2 + { + if (arr.length !== 2) + { + throw new Error('Expect 2 arguments'); + } + return new Vector2(this.componentToFloat(arr[0]), this.componentToFloat(arr[1])); + } + + public static fromQuaternion(vec?: Quaternion): LLSDReal[] | undefined + { + if (vec === undefined) + { + return undefined; + } + return [ + new LLSDReal(vec.x), + new LLSDReal(vec.y), + new LLSDReal(vec.z), + new LLSDReal(vec.w) + ] + } + + public static fromVector4(vec?: Vector4): LLSDReal[] | undefined + { + if (vec === undefined) + { + return undefined; + } + return [ + new LLSDReal(vec.x), + new LLSDReal(vec.y), + new LLSDReal(vec.z), + new LLSDReal(vec.w), + ] + } + + public static fromVector3(vec?: Vector3): LLSDReal[] | undefined + { + if (vec === undefined) + { + return undefined; + } + return [ + new LLSDReal(vec.x), + new LLSDReal(vec.y), + new LLSDReal(vec.z), + ] + } + + public static fromVector2(vec?: Vector2): LLSDReal[] | undefined + { + if (vec === undefined) + { + return undefined; + } + return [ + new LLSDReal(vec.x), + new LLSDReal(vec.y) + ] + } + + public static toStringArray(arr: LLSDType[]): string[] + { + const a: string[] = []; + for(const v of arr) + { + a.push(String(v)); + } + return a; + } + + public static toNumberArray(arr: LLSDType[]): number[] + { + const a: number[] = []; + for(const v of arr) + { + if (v instanceof LLSDReal || v instanceof LLSDInteger) + { + a.push(v.valueOf()); + } + else + { + a.push(Number(v)); + } + } + return a; + } + + private static componentToFloat(val: LLSDType): number + { + if (val instanceof LLSDReal) + { + return val.valueOf(); + } + else if (typeof val === 'number') + { + return val; + } + let type = typeof val; + if (type === 'object' && val !== null) + { + type += ' (' + val.constructor.name + ')'; + } + throw new Error('Component is not a number (' + type + ')'); + } } diff --git a/lib/classes/llsd/LLSDBinary.ts b/lib/classes/llsd/LLSDBinary.ts new file mode 100644 index 0000000..294cf8b --- /dev/null +++ b/lib/classes/llsd/LLSDBinary.ts @@ -0,0 +1,130 @@ +import type { BinaryReader } from "../BinaryReader"; +import { LLSDMap } from "./LLSDMap"; +import { LLSDInteger } from "./LLSDInteger"; +import { LLSDReal } from "./LLSDReal"; +import { UUID } from "../UUID"; +import { LLSDURI } from "./LLSDURI"; +import { LLSDArray } from "./LLSDArray"; +import type { LLSDType } from "./LLSDType"; +import type { BinaryWriter } from "../BinaryWriter"; + +export class LLSDBinary +{ + public static parseValue(reader: BinaryReader): LLSDType + { + const token = reader.readFixedString(1); + switch (token) + { + case '{': + return LLSDMap.parseBinary(reader); + case '!': + return null; + case '1': + return true; + case '0': + return false; + case 'i': + return LLSDInteger.parseBinary(reader); + case 'r': + return LLSDReal.parseBinary(reader); + case 'u': + { + const buf = reader.readBuffer(16); + return new UUID(buf, 0); + } + case 'b': + { + const binaryLength = reader.readUInt32BE(); + return reader.readBuffer(binaryLength); + } + case 's': + { + const stringLength = reader.readUInt32BE(); + return reader.readFixedString(stringLength); + } + case 'l': + { + const stringLength = reader.readUInt32BE(); + const str = reader.readFixedString(stringLength); + return new LLSDURI(str); + } + case 'd': + { + const secs = reader.readDoubleBE(); + return new Date(secs * 1000); + } + case '[': + return LLSDArray.parseBinary(reader); + default: + throw new Error('Unexpected token: ' + token); + } + } + + public static encodeValue(value: LLSDType, writer: BinaryWriter): void + { + if (value instanceof LLSDMap) + { + value.toBinary(writer); + } + else if (value instanceof LLSDInteger) + { + writer.writeFixedString('i'); + writer.writeUInt32BE(value.valueOf()); + } + else if (value instanceof LLSDReal) + { + writer.writeFixedString('r'); + writer.writeDoubleBE(value.valueOf()); + } + else if (value instanceof UUID) + { + writer.writeFixedString('u'); + writer.writeUUID(value); + } + else if (value instanceof LLSDURI) + { + writer.writeFixedString('l'); + const str = value.toString(); + writer.writeUInt32BE(str.length); + writer.writeFixedString(str); + } + else if (value instanceof Buffer) + { + writer.writeFixedString('b'); + writer.writeUInt32BE(value.length); + writer.writeBuffer(value); + } + else if (value instanceof Date) + { + writer.writeFixedString('d'); + writer.writeDoubleBE(value.getTime() / 1000); + } + else if (value === null) + { + writer.writeFixedString('!'); + } + else if (value === true) + { + writer.writeFixedString('1'); + } + else if (value === false) + { + writer.writeFixedString('0'); + } + else if (typeof value === 'string') + { + writer.writeFixedString('s'); + const str = value.toString(); + writer.writeUInt32BE(str.length); + writer.writeFixedString(str); + } + else if (Array.isArray(value)) + { + LLSDArray.toBinary(value, writer); return; + } + else + { + throw new Error('Unknown type: ' + String(value)); + } + } +} \ No newline at end of file diff --git a/lib/classes/llsd/LLSDInteger.ts b/lib/classes/llsd/LLSDInteger.ts new file mode 100644 index 0000000..af6b0ab --- /dev/null +++ b/lib/classes/llsd/LLSDInteger.ts @@ -0,0 +1,34 @@ +import type { BinaryReader } from "../BinaryReader"; + +export class LLSDInteger +{ + private _int: number; + + public constructor(int: number) + { + this._int = int; + } + + public static parseBinary(reader: BinaryReader): LLSDInteger + { + return new LLSDInteger(reader.readUInt32BE()); + } + + public valueOf(): number + { + return this._int; + } + + public toJSON(): number + { + return this._int; + } + + public set value(newValue: number) + { + if (!Number.isInteger(newValue)) { + throw new Error("LLSDInteger must be an integer."); + } + this._int = newValue; + } +} \ No newline at end of file diff --git a/lib/classes/llsd/LLSDMap.ts b/lib/classes/llsd/LLSDMap.ts index e2b9260..85d4f70 100644 --- a/lib/classes/llsd/LLSDMap.ts +++ b/lib/classes/llsd/LLSDMap.ts @@ -1,19 +1,101 @@ import { LLSDObject } from './LLSDObject'; -import { LLSDTokenGenerator } from './LLSDTokenGenerator'; +import type { LLSDTokenGenerator } from './LLSDTokenGenerator'; import { LLSDTokenType } from './LLSDTokenType'; -import { LLSDNotationParser } from './LLSDNotationParser'; -import { LLSDType } from './LLSDType'; +import { LLSDNotation } from './LLSDNotation'; +import type { LLSDType } from './LLSDType'; +import { LLSDBinary } from "./LLSDBinary"; +import { LLSDXML } from "./LLSDXML"; +import type { BinaryReader } from '../BinaryReader'; +import type { BinaryWriter } from '../BinaryWriter'; -export class LLSDMap extends LLSDObject +export class LLSDMap> extends LLSDObject { - public data: Map = new Map(); + public ___data: Map = new Map(); - public static parse(gen: LLSDTokenGenerator): LLSDMap + public constructor(initialData?: [string, LLSDType][] | Record) + { + super(); + if (initialData) + { + if (Array.isArray(initialData)) + { + for (const d of initialData) + { + this.___data.set(d[0], d[1]); + } + } + else + { + for(const key of Object.keys(initialData)) + { + const v = initialData[key]; + if (v !== undefined) + { + this.___data.set(key, v); + } + } + } + } + return new Proxy(this as LLSDMap, { + get(target, prop, receiver): LLSDType | undefined + { + if (typeof prop === 'string' && target.___data.has(prop)) + { + return target.___data.get(prop); + } + // Handle other properties or methods + return Reflect.get(target, prop, receiver) as LLSDType | undefined; + }, + set(target, prop, value, receiver): boolean + { + if (typeof prop === 'string') + { + target.___data.set(prop, value as LLSDType); + return true; + } + return Reflect.set(target, prop, value, receiver); + }, + has(target, prop): boolean + { + if (typeof prop === 'string') + { + return target.___data.has(prop); + } + return Reflect.has(target, prop); + }, + deleteProperty(target, prop): boolean + { + if (typeof prop === 'string') + { + return target.___data.delete(prop); + } + return Reflect.deleteProperty(target, prop); + }, + ownKeys(target): string[] + { + return Array.from(target.___data.keys()).map(key => String(key)); + }, + getOwnPropertyDescriptor(target, prop): { enumerable: boolean, configurable: boolean } | undefined + { + if (typeof prop === 'string' && target.___data.has(prop)) + { + return { + enumerable: true, + configurable: true, + }; + } + return undefined; + }, + }) as LLSDMap & T; + } + + public static parseNotation>(gen: LLSDTokenGenerator): LLSDMap { const m = new LLSDMap(); let expectsKey = true let key: LLSDType | undefined = undefined; let value: LLSDType | undefined = undefined; + while (true) { const token = gen(); @@ -23,11 +105,11 @@ export class LLSDMap extends LLSDObject } switch (token.type) { - case LLSDTokenType.WHITESPACE: + case LLSDTokenType.Whitespace: { continue; } - case LLSDTokenType.MAP_END: + case LLSDTokenType.MapEnd: { if (expectsKey) { @@ -35,15 +117,15 @@ export class LLSDMap extends LLSDObject } if (key !== undefined && value !== undefined) { - m.data.set(key, value); + m.___data.set(String(key), value); } - else if (m.data.size > 0) + else if (m.___data.size > 0) { throw new Error('Expected value before end of map'); } return m; } - case LLSDTokenType.COLON: + case LLSDTokenType.Colon: { if (!expectsKey) { @@ -56,7 +138,7 @@ export class LLSDMap extends LLSDObject expectsKey = false; continue; } - case LLSDTokenType.COMMA: + case LLSDTokenType.Comma: { if (expectsKey) { @@ -68,13 +150,31 @@ export class LLSDMap extends LLSDObject } if (key !== undefined) { - m.data.set(key, value); + m.___data.set(String(key), value); } key = undefined; value = undefined; expectsKey = true; continue; } + case LLSDTokenType.Unknown: + case LLSDTokenType.Null: + case LLSDTokenType.MapStart: + case LLSDTokenType.ArrayStart: + case LLSDTokenType.ArrayEnd: + case LLSDTokenType.Boolean: + case LLSDTokenType.Integer: + case LLSDTokenType.Real: + case LLSDTokenType.UUID: + case LLSDTokenType.StringFixedSingle: + case LLSDTokenType.StringFixedDouble: + case LLSDTokenType.StringDynamicStart: + case LLSDTokenType.URI: + case LLSDTokenType.Date: + case LLSDTokenType.BinaryStatic: + case LLSDTokenType.BinaryDynamicStart: + default: + break; } if (expectsKey && key !== undefined) @@ -86,7 +186,7 @@ export class LLSDMap extends LLSDObject throw new Error('Comma or end brace expected'); } - const val = LLSDNotationParser.parseValueToken(gen, token); + const val = LLSDNotation.parseValueToken(gen, token); if (expectsKey) { key = val; @@ -98,18 +198,165 @@ export class LLSDMap extends LLSDObject } } - get length(): number + public static parseBinary>(reader: BinaryReader): LLSDMap { - return Object.keys(this.data).length; + const map = new LLSDMap(); + const length = reader.readUInt32BE(); + for(let x = 0; x < length; x++) + { + const keyTag = reader.readFixedString(1); + if (keyTag !== 'k') + { + throw new Error('Map key expected') + } + const keyLength = reader.readUInt32BE(); + const key = reader.readFixedString(keyLength); + const val = LLSDBinary.parseValue(reader); + map.add(key, val); + } + const endMap = reader.readFixedString(1); + if (endMap !== '}') + { + throw new Error('Map end expected'); + } + return map; + } + + public static parseXML>(element: Record[]): LLSDMap + { + const map = new LLSDMap(); + for(let x = 0; x < element.length; x++) + { + const key = element[x] as unknown as Record; + const keys = Object.keys(key); + if (keys.length !== 1) + { + throw new Error('Only one child of "key" expected'); + } + if (keys[0] !== 'key') + { + throw new Error('Only one child of "key" expected'); + } + const keyArr = key.key as Record[]; + if (keyArr.length !== 1) + { + throw new Error('Only one text element expected in key') + } + if (keyArr[0]['#text'] === undefined) + { + throw new Error('Key is missing') + } + + const keyStr = String(keyArr[0]['#text']); + x++; + const valElement = element[x] as unknown as Record; + const value = LLSDXML.parseValue([valElement]) + map.add(keyStr, value); + } + return map; + } + + public get length(): number + { + return Object.keys(this.___data).length; } public get(key: LLSDType): LLSDType | undefined { - return this.data.get(key); + return this.___data.get(String(key)); } public toJSON(): unknown { - return Object.fromEntries(this.data); + return Object.fromEntries(this.___data); + } + + public add(key: string, value: LLSDType): void + { + this.___data.set(key, value); + } + + public set(key: string, value: LLSDType): void + { + this.add(key, value); + } + + public toNotation(): string + { + const builder: string[] = ['{']; + let first = true; + for(const key of this.___data.keys()) + { + if (first) + { + first = false; + } + else + { + builder.push(','); + } + const v = this.___data.get(key); + if (v === undefined) + { + continue; + } + builder.push('"' + LLSDNotation.escapeStringSimple(String(key), '"') + '":'); + builder.push(LLSDNotation.encodeValue(v)); + } + builder.push('}') + return builder.join(''); + } + + public toBinary(writer: BinaryWriter): void + { + writer.writeFixedString('{'); + writer.writeUInt32BE(this.___data.size) + for(const k of this.___data.keys()) + { + const v = this.___data.get(k); + if (v === undefined) + { + continue; + } + + writer.writeFixedString('k'); + writer.writeUInt32BE(Buffer.byteLength(String(k))); + writer.writeFixedString(String(k)); + LLSDBinary.encodeValue(v, writer); + } + writer.writeFixedString('}'); + } + + public toXML(): unknown + { + const val: { + map: unknown[] + } = { + 'map': [] + }; + + for(const key of this.___data.keys()) + { + const llsdVal = this.___data.get(key); + if (llsdVal === undefined) + { + continue; + } + + val.map.push({ + 'key': [{ + '#text': String(key) + }] + }); + + val.map.push(LLSDXML.encodeValue(llsdVal)); + } + + return val; + } + + public keys(): string[] + { + return Array.from(this.___data.keys()); } } diff --git a/lib/classes/llsd/LLSDNotation.ts b/lib/classes/llsd/LLSDNotation.ts new file mode 100644 index 0000000..8d9fe04 --- /dev/null +++ b/lib/classes/llsd/LLSDNotation.ts @@ -0,0 +1,334 @@ +import { LLSDTokenType } from './LLSDTokenType'; +import type { LLSDType } from './LLSDType'; +import type { LLSDTokenSpec } from './LLSDTokenSpec'; +import type { LLSDToken } from './LLSDToken'; +import type { LLSDTokenGenerator } from './LLSDTokenGenerator'; +import { LLSDMap } from './LLSDMap'; +import { UUID } from '../UUID'; +import { LLSDArray } from './LLSDArray'; +import { LLSDInteger } from "./LLSDInteger"; +import { LLSDReal } from "./LLSDReal"; +import { LLSDURI } from "./LLSDURI"; + +export class LLSDNotation +{ + private static readonly tokenSpecs: LLSDTokenSpec[] = + [ + {regex: /^\s+/, type: LLSDTokenType.Whitespace}, + {regex: /^!/, type: LLSDTokenType.Null}, + {regex: /^\{/, type: LLSDTokenType.MapStart}, + {regex: /^}/, type: LLSDTokenType.MapEnd}, + {regex: /^:/, type: LLSDTokenType.Colon}, + {regex: /^,/, type: LLSDTokenType.Comma}, + {regex: /^\[/, type: LLSDTokenType.ArrayStart}, + {regex: /^]/, type: LLSDTokenType.ArrayEnd}, + {regex: /^(?:true|false|TRUE|FALSE|1|0|T|F|t|f)/, type: LLSDTokenType.Boolean}, + {regex: /^i(-?[0-9]+)/, type: LLSDTokenType.Integer}, + {regex: /^r(-?[0-9.]+(?:[eE]-?[0-9]+)?)/, type: LLSDTokenType.Real}, + {regex: /^rNaN/, type: LLSDTokenType.Real}, + {regex: /^rInfinity/, type: LLSDTokenType.Real}, + {regex: /^r-Infinity/, type: LLSDTokenType.Real}, + {regex: /^u([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/, type: LLSDTokenType.UUID}, + {regex: /^'([^'\\]*(?:\\.[^'\\\n]*)*)'/, type: LLSDTokenType.StringFixedSingle}, + {regex: /^"([^"\\]*(?:\\.[^"\\\n]*)*)"/, type: LLSDTokenType.StringFixedDouble}, + {regex: /^s\(([0-9]+)\)"/, type: LLSDTokenType.StringDynamicStart}, + {regex: /^l"([^"]*?)"/, type: LLSDTokenType.URI}, + {regex: /^d"([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]+Z)"/, type: LLSDTokenType.Date}, + {regex: /^b[0-9]{2}"[0-9a-zA-Z+/=]*?"/, type: LLSDTokenType.BinaryStatic}, + {regex: /^b\(([0-9]+)\)"/, type: LLSDTokenType.BinaryDynamicStart} + ]; + + public static parseValueToken(gen: LLSDTokenGenerator, initialToken?: LLSDToken): LLSDType + { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + while (true) + { + let t: LLSDToken | undefined = undefined; + if (initialToken !== undefined) + { + t = initialToken; + initialToken = undefined; + } + else + { + t = gen(); + if (t === undefined) + { + throw new Error('Unexpected end of input'); + } + } + switch (t.type) + { + case LLSDTokenType.Unknown: + { + throw new Error('Unexpected token: ' + t.value); + } + case LLSDTokenType.Null: + { + return null; + } + case LLSDTokenType.Boolean: + { + return t.value === 'true' || t.value === 'TRUE' || t.value === 'T' || t.value === 't' || t.value === '1'; + } + case LLSDTokenType.Integer: + { + return new LLSDInteger(parseInt(t.value, 10)); + } + case LLSDTokenType.Real: + { + return new LLSDReal(t.value); + } + case LLSDTokenType.UUID: + { + return new UUID(t.value); + } + case LLSDTokenType.StringFixedSingle: + { + return this.unescapeStringSimple(t.value, '\''); + } + case LLSDTokenType.StringFixedDouble: + { + return this.unescapeStringSimple(t.value, '"'); + } + case LLSDTokenType.URI: + { + return new LLSDURI(t.value); + } + case LLSDTokenType.Date: + { + return new Date(t.value); + } + case LLSDTokenType.BinaryStatic: + { + const b = /^b([0-9]{2})"([0-9a-zA-Z+/=]*?)"/.exec(t.value); + if (b === null || b.length < 3) + { + throw new Error('Invalid BINARY_STATIC'); + } + const base = parseInt(b[1], 10); + if (base !== 16 && base !== 64) + { + throw new Error('Unsupported base ' + String(base)); + } + return Buffer.from(b[2], base === 64 ? 'base64' : 'hex'); + } + case LLSDTokenType.StringDynamicStart: + { + const length = parseInt(t.value, 10); + const s = t.dataContainer.input.slice(t.dataContainer.index, t.dataContainer.index + length); + t.dataContainer.index += length; + if (t.dataContainer.input[t.dataContainer.index] !== '"') + { + throw new Error('Expected " at end of dynamic string') + } + t.dataContainer.index += 1; + return s; + } + case LLSDTokenType.BinaryDynamicStart: + { + const length = parseInt(t.value, 10); + const s = t.dataContainer.input.slice(t.dataContainer.index, t.dataContainer.index + length); + t.dataContainer.index += length; + if (t.dataContainer.input[t.dataContainer.index] !== '"') + { + throw new Error('Expected " at end of dynamic binary string') + } + t.dataContainer.index += 1; + return Buffer.from(s, 'binary'); + } + case LLSDTokenType.MapStart: + { + return LLSDMap.parseNotation(gen); + } + case LLSDTokenType.ArrayStart: + { + return LLSDArray.parseNotation(gen); + } + case LLSDTokenType.MapEnd: + case LLSDTokenType.Colon: + case LLSDTokenType.Comma: + case LLSDTokenType.ArrayEnd: + case LLSDTokenType.Whitespace: + break; + } + } + } + + public static* tokenize(input: string): Generator + { + const dataContainer = { + input: input, + index: 0 + } + while (dataContainer.index < dataContainer.input.length) + { + const currentInput = dataContainer.input.slice(dataContainer.index); + + if (currentInput.length === 0) + { + return; // End of input + } + + let matched = false; + for (const {regex, type} of this.tokenSpecs) + { + const tokenMatch = currentInput.match(regex); + if (tokenMatch) + { + matched = true; + let [value] = tokenMatch; + if (tokenMatch.length > 1) + { + value = tokenMatch[tokenMatch.length - 1]; + } + dataContainer.index += tokenMatch[0].length; // Move past this token + + yield {type, value, rawValue: tokenMatch[0], dataContainer}; + break; + } + } + if (!matched) + { + dataContainer.index++; + yield { + type: LLSDTokenType.Unknown, + value: dataContainer.input[dataContainer.index - 1], + rawValue: dataContainer.input[dataContainer.index - 1], + dataContainer + }; + } + } + } + + public static encodeValue(value: LLSDType): string + { + if (value instanceof LLSDMap) + { + return value.toNotation(); + } + else if (value instanceof LLSDInteger) + { + return 'i' + value.valueOf(); + } + else if (value instanceof LLSDReal) + { + const v = value.valueOf(); + if (isNaN(v)) + { + return 'rNaN'; + } + else if (v === -Infinity) + { + return 'r-Infinity'; + } + else if (v === Infinity) + { + return 'rInfinity'; + } + else if (Object.is(v, -0)) + { + return 'r-0'; + } + return 'r' + v; + } + else if (value instanceof UUID) + { + return 'u' + value.toString(); + } + else if (value instanceof LLSDURI) + { + return 'l"' + this.escapeStringSimple(value.toString(), '"') + '"'; + } + else if (value instanceof Buffer) + { + return 'b64"' + value.toString('base64') + '"'; + } + else if (value instanceof Date) + { + return 'd"' + value.toISOString() + '"'; + } + else if (value === null) + { + return '!'; + } + else if (value === true) + { + return '1'; + } + else if (value === false) + { + return '0'; + } + else if (typeof value === 'string') + { + return '"' + this.escapeStringSimple(value, '"') + '"'; + } + else if (Array.isArray(value)) + { + return LLSDArray.toNotation(value); + } + else + { + throw new Error('Unknown type: ' + String(value)); + } + } + + public static escapeStringSimple(input: string, quote: string): string + { + if (quote.length !== 1) + { + throw new Error('Quote must be a single character'); + } + + const escapeRegex = new RegExp(`[${quote}\x00-\x1F\x7F\\\\]`, 'g'); + + return input.replace(escapeRegex, (char) => + { + if (char === "\\") + { + return "\\\\"; + } + else if (char === quote) + { + return '\\' + quote; + } + const hex = char.charCodeAt(0).toString(16).padStart(2, "0"); + return `\\x${hex}`; + }); + } + + public static unescapeStringSimple(input: string, quote: string): string + { + if (quote.length !== 1) + { + throw new Error("Quote parameter must be a single character."); + } + + // Create a regex that matches \\, \quote, or \xHH + const unescapeRegex = new RegExp(`\\\\(\\\\|${quote}|x[0-9A-Fa-f]{2})`, 'g'); + + return input.replace(unescapeRegex, (match: string, p1: string) => + { + if (p1 === "\\") + { + return "\\"; + } + if (p1 === quote) + { + return quote; + } + if (p1.startsWith('x')) + { + const hex = p1.slice(1); + const charCode = parseInt(hex, 16); + if (isNaN(charCode)) + { + return match; + } + return String.fromCharCode(charCode); + } + return match; + }); + } +} diff --git a/lib/classes/llsd/LLSDNotationParser.spec.ts b/lib/classes/llsd/LLSDNotationParser.spec.ts deleted file mode 100644 index 73a11ee..0000000 --- a/lib/classes/llsd/LLSDNotationParser.spec.ts +++ /dev/null @@ -1,204 +0,0 @@ -import * as assert from 'assert'; -import { LLSDNotationParser } from './LLSDNotationParser'; -import { LLSDMap } from './LLSDMap'; -import { UUID } from '../UUID'; - -describe('LLSDNotationParser', () => -{ - describe('parse', () => - { - it('can parse a complex LLSD Notation document', () => - { - const notationDoc = `{ - 'nested_map': { - 'nested_again': { - 'array': [ - i0, - 'string', - "string2", - [ - "another", - "array", - r4.3, - i22, - !, - { - 'isThis': i42 - } - ] - ] - } - }, - 'undef': !, - 'booleans': [ - true, - false, - 1, - 0, - T, - F, - t, - f, - TRUE, - FALSE - ], - 'integer': i69, - 'negInt': i-69, - 'real': r3.141, - 'realNeg': r-3.141, - 'uuid': ufb54f6b1-8120-40c9-9aa3-f9abef1a168f, - 'string1': "gday\\"mate", - 'string2': 'gday\\'mate', - 'string3': s(11)"hello"there", - 'uri': l"https://secondlife.com/", - 'date': d"2023-11-10T13:32:32.93Z", - 'binary': b64"amVsbHlmaXNo", - 'binary2': b16"6261636F6E62697473", - 'binary3': b(32)"KÚ~¬\béGÀt|ϓ˜h,9µEK¹*;]ÆÁåb/" - }`; - const parsed = LLSDNotationParser.parse(notationDoc); - if (!(parsed instanceof LLSDMap)) - { - assert('Parsed document is not a map'); - return; - } - - const nested = parsed.get('nested_map'); - if (nested instanceof LLSDMap) - { - const nestedAgain = nested.get('nested_again'); - if (nestedAgain instanceof LLSDMap) - { - const arr = nestedAgain.get('array'); - if (!Array.isArray(arr)) - { - assert(false, 'Nested array is not an array'); - } - else - { - assert.equal(arr.length, 4); - assert.equal(arr[0], 0); - assert.equal(arr[1], 'string'); - assert.equal(arr[2], 'string2'); - const nestedAgainArr = arr[3]; - if (!Array.isArray(nestedAgainArr)) - { - assert(false, 'Nested again array is not an array'); - } - else - { - assert.equal(nestedAgainArr.length, 6); - assert.equal(nestedAgainArr[0], 'another'); - assert.equal(nestedAgainArr[1], 'array'); - assert.equal(nestedAgainArr[2], 4.3); - assert.equal(nestedAgainArr[3], 22); - assert.equal(nestedAgainArr[4], null); - const thirdNestedMap = nestedAgainArr[5]; - if (thirdNestedMap instanceof LLSDMap) - { - assert.equal(thirdNestedMap.get('isThis'), 42); - } - else - { - assert(false, 'Third nested map is not a map'); - } - } - } - } - else - { - assert(false, 'NestedAgain is not a map'); - } - } - else - { - assert(false, 'Nested is not a map'); - } - assert.equal(parsed.get('undef'), null); - const bools = parsed.get('booleans'); - if (!Array.isArray(bools)) - { - assert(false, 'Booleans array is not bools'); - } - else - { - assert.equal(bools.length, 10); - assert.equal(bools[0], true); - assert.equal(bools[1], false); - assert.equal(bools[2], true); - assert.equal(bools[3], false); - assert.equal(bools[4], true); - assert.equal(bools[5], false); - assert.equal(bools[6], true); - assert.equal(bools[7], false); - assert.equal(bools[8], true); - assert.equal(bools[9], false); - } - - /* - 'string1': "gday\"mate", - 'string2': 'gday\'mate', - 'string3': s(11)"hello"there", - 'uri': l"https://secondlife.com/", - 'date': d"2023-11-10T13:32:32.93Z", - 'binary': b64"amVsbHlmaXNo", - 'binary2': b16"6261636F6E62697473", - 'binary3': b(32)"KÚ~¬éGÀt|ϓ˜h,9µEK¹*;]ÆÁåb/" - */ - assert.equal(parsed.get('integer'), 69); - assert.equal(parsed.get('negInt'), -69); - assert.equal(parsed.get('real'), 3.141); - assert.equal(parsed.get('realNeg'), -3.141); - const u = parsed.get('uuid'); - if (u instanceof UUID) - { - assert.equal(u.equals('fb54f6b1-8120-40c9-9aa3-f9abef1a168f'), true); - } - else - { - assert(false, 'UUID is not a UUID'); - } - assert.equal(parsed.get('string1'), 'gday"mate'); - assert.equal(parsed.get('string2'), 'gday\'mate'); - assert.equal(parsed.get('string3'), 'hello"there'); - assert.equal(parsed.get('uri'), 'https://secondlife.com/'); - const d = parsed.get('date'); - if (d instanceof Date) - { - assert.equal(d.getTime(), 1699623152930); - } - else - { - assert(false, 'Date entry is not a date'); - } - const buf1 = parsed.get('binary'); - if (buf1 instanceof Buffer) - { - assert.equal(buf1.toString('utf-8'), 'jellyfish'); - } - else - { - assert(false, 'Buf1 is not a buffer'); - } - const buf2 = parsed.get('binary2'); - if (buf2 instanceof Buffer) - { - assert.equal(buf2.toString('utf-8'), 'baconbits'); - } - else - { - assert(false, 'Buf2 is not a buffer'); - } - const buf3 = parsed.get('binary3'); - if (buf3 instanceof Buffer) - { - assert.equal(buf3.equals(Buffer.from('KÚ~¬\béGÀt|ϓ˜h,9µEK¹*;]ÆÁåb/', 'binary')), true); - } - else - { - assert(false, 'Buf3 is not a buffer'); - } - }); - }); -}); - diff --git a/lib/classes/llsd/LLSDNotationParser.ts b/lib/classes/llsd/LLSDNotationParser.ts deleted file mode 100644 index 2dd48cb..0000000 --- a/lib/classes/llsd/LLSDNotationParser.ts +++ /dev/null @@ -1,205 +0,0 @@ -import { LLSDTokenType } from './LLSDTokenType'; -import { LLSDType } from './LLSDType'; -import { LLSDTokenSpec } from './LLSDTokenSpec'; -import { LLSDToken } from './LLSDToken'; -import { LLSDTokenGenerator } from './LLSDTokenGenerator'; -import { LLSDMap } from './LLSDMap'; -import { UUID } from '../UUID'; -import { LLSDArray } from './LLSDArray'; - -export class LLSDNotationParser -{ - private static tokenSpecs: LLSDTokenSpec[] = - [ - { regex: /^\s+/, type: LLSDTokenType.WHITESPACE }, - { regex: /^!/, type: LLSDTokenType.NULL }, - { regex: /^\{/, type: LLSDTokenType.MAP_START }, - { regex: /^}/, type: LLSDTokenType.MAP_END }, - { regex: /^:/, type: LLSDTokenType.COLON }, - { regex: /^,/, type: LLSDTokenType.COMMA }, - { regex: /^\[/, type: LLSDTokenType.ARRAY_START }, - { regex: /^]/, type: LLSDTokenType.ARRAY_END }, - { regex: /^(?:true|false|TRUE|FALSE|1|0|T|F|t|f)/, type: LLSDTokenType.BOOLEAN }, - { regex: /^i(-?[0-9]+)/, type: LLSDTokenType.INTEGER }, - { regex: /^r(-?[0-9.]+(?:[eE]-?[0-9]+)?)/, type: LLSDTokenType.REAL }, - { regex: /^u([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/, type: LLSDTokenType.UUID }, - { regex: /^'([^'\\]*(?:\\.[^'\\\n]*)*)'/, type: LLSDTokenType.STRING_FIXED_SINGLE }, - { regex: /^"([^"\\]*(?:\\.[^"\\\n]*)*)"/, type: LLSDTokenType.STRING_FIXED_DOUBLE }, - { regex: /^s\(([0-9]+)\)"/, type: LLSDTokenType.STRING_DYNAMIC_START }, - { regex: /^l"([^"]*?)"/, type: LLSDTokenType.URI }, - { regex: /^d"([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{2}Z)"/, type: LLSDTokenType.DATE }, - { regex: /^b[0-9]{2}"[0-9a-zA-Z+\/=]*?"/, type: LLSDTokenType.BINARY_STATIC }, - { regex: /^b\(([0-9]+)\)"/, type: LLSDTokenType.BINARY_DYNAMIC_START } - ]; - - private static* tokenize(inpt: string): Generator - { - const dataContainer = { - input: inpt, - index: 0 - } - while (dataContainer.index < dataContainer.input.length) - { - const currentInput = dataContainer.input.slice(dataContainer.index); - - if (currentInput.length === 0) - { - return; // End of input - } - - let matched = false; - for (const { regex, type } of this.tokenSpecs) - { - const tokenMatch = currentInput.match(regex); - if (tokenMatch) - { - matched = true; - let value = tokenMatch[0]; - if (tokenMatch.length > 1) - { - value = tokenMatch[tokenMatch.length - 1]; - } - dataContainer.index += tokenMatch[0].length; // Move past this token - - yield { type, value, rawValue: tokenMatch[0], dataContainer }; - break; - } - } - if (!matched) - { - dataContainer.index++; - yield { type: LLSDTokenType.UNKNOWN, value: dataContainer.input[dataContainer.index - 1], rawValue: dataContainer.input[dataContainer.index - 1], dataContainer }; - } - } - } - - public static parseValueToken(gen: LLSDTokenGenerator, initialToken?: LLSDToken): LLSDType - { - while (true) - { - let t: LLSDToken | undefined; - if (initialToken !== undefined) - { - t = initialToken; - initialToken = undefined; - } - else - { - t = gen(); - if (t === undefined) - { - throw new Error('Unexpected end of input'); - } - } - switch (t.type) - { - case LLSDTokenType.UNKNOWN: - { - throw new Error('Unexpected token: ' + t.value); - } - case LLSDTokenType.WHITESPACE: - { - continue; - } - case LLSDTokenType.NULL: - { - return null; - } - case LLSDTokenType.BOOLEAN: - { - return t.value === 'true' || t.value === 'TRUE' || t.value === 'T' || t.value === 't' || t.value === '1'; - } - case LLSDTokenType.INTEGER: - { - return parseInt(t.value, 10); - } - case LLSDTokenType.REAL: - { - return parseFloat(t.value); - } - case LLSDTokenType.UUID: - { - return new UUID(t.value); - } - case LLSDTokenType.STRING_FIXED_SINGLE: - { - return t.value.replace(/\\'/, '\'') - .replace(/\\\\/g, '\\'); - } - case LLSDTokenType.STRING_FIXED_DOUBLE: - { - return t.value.replace(/\\"/, '"') - .replace(/\\\\/g, '\\'); - } - case LLSDTokenType.URI: - { - return t.value; - } - case LLSDTokenType.DATE: - { - return new Date(t.value); - } - case LLSDTokenType.BINARY_STATIC: - { - const b = t.value.match(/^b([0-9]{2})"([0-9a-zA-Z+\/=]*?)"/); - if (b === null || b.length < 3) - { - throw new Error('Invalid BINARY_STATIC'); - } - const base = parseInt(b[1], 10); - if (base !== 16 && base !== 64) - { - throw new Error('Unsupported base ' + String(base)); - } - return Buffer.from(b[2], base === 64 ? 'base64' : 'hex'); - } - case LLSDTokenType.STRING_DYNAMIC_START: - { - const length = parseInt(t.value, 10); - const s = t.dataContainer.input.slice(t.dataContainer.index, t.dataContainer.index + length); - t.dataContainer.index += length; - if (t.dataContainer.input[t.dataContainer.index] !== '"') - { - throw new Error('Expected " at end of dynamic string') - } - t.dataContainer.index += 1; - return s; - } - case LLSDTokenType.BINARY_DYNAMIC_START: - { - const length = parseInt(t.value, 10); - const s = t.dataContainer.input.slice(t.dataContainer.index, t.dataContainer.index + length); - t.dataContainer.index += length; - if (t.dataContainer.input[t.dataContainer.index] !== '"') - { - throw new Error('Expected " at end of dynamic binary string') - } - t.dataContainer.index += 1; - return Buffer.from(s, 'binary'); - } - case LLSDTokenType.MAP_START: - { - return LLSDMap.parse(gen); - } - case LLSDTokenType.ARRAY_START: - { - return LLSDArray.parse(gen); - } - } - } - } - - public static parse(input: string): LLSDType - { - if (input.startsWith('')) - { - input = input.substring(20); - } - const generator = this.tokenize(input); - const getToken: LLSDTokenGenerator = (): LLSDToken | undefined => - { - return generator.next().value; - } - return this.parseValueToken(getToken); - } -} diff --git a/lib/classes/llsd/LLSDReal.ts b/lib/classes/llsd/LLSDReal.ts new file mode 100644 index 0000000..1c835ad --- /dev/null +++ b/lib/classes/llsd/LLSDReal.ts @@ -0,0 +1,87 @@ +import type { BinaryReader } from "../BinaryReader"; + +export class LLSDReal +{ + private real: number; + + public constructor(val: string | number) + { + if (typeof val === 'number') + { + this.real = val; + return; + } + switch(val) + { + case 'rInfinity': + this.real = Number.POSITIVE_INFINITY; + break; + case 'r-Infinity': + this.real = Number.NEGATIVE_INFINITY; + break; + case 'rNaN': + this.real = Number.NaN; + break; + case '+Zero': + this.real = 0; + break; + case '-Zero': + this.real = -0; + break; + case '+Infinity': + this.real = Number.POSITIVE_INFINITY; + break; + case '-Infinity': + this.real = Number.NEGATIVE_INFINITY; + break; + case 'NaNQ': + this.real = Number.NaN; + break; + case 'NaNS': + this.real = Number.NaN; + break; + case 'NaN': + this.real = Number.NaN; + break; + default: + this.real = parseFloat(val); + } + } + + public static parseBinary(reader: BinaryReader): LLSDReal + { + return new LLSDReal(reader.readDoubleBE()); + } + + public static parseReal(val?: unknown): LLSDReal | undefined + { + if (val === undefined) + { + return undefined; + } + else if (typeof val === 'number') + { + return new LLSDReal(val); + } + else if (val instanceof LLSDReal) + { + return val; + } + throw new Error('Parsed value is not a number'); + } + + public valueOf(): number + { + return this.real; + } + + public toJSON(): number + { + return this.real; + } + + public set value(newValue: number) + { + this.real = newValue; + } +} \ No newline at end of file diff --git a/lib/classes/llsd/LLSDToken.ts b/lib/classes/llsd/LLSDToken.ts index 88ceb4e..d0bbd96 100644 --- a/lib/classes/llsd/LLSDToken.ts +++ b/lib/classes/llsd/LLSDToken.ts @@ -1,4 +1,4 @@ -import { LLSDTokenType } from './LLSDTokenType'; +import type { LLSDTokenType } from './LLSDTokenType'; export interface LLSDToken { diff --git a/lib/classes/llsd/LLSDTokenContainer.ts b/lib/classes/llsd/LLSDTokenContainer.ts index 22e547e..44d21f4 100644 --- a/lib/classes/llsd/LLSDTokenContainer.ts +++ b/lib/classes/llsd/LLSDTokenContainer.ts @@ -1,5 +1,5 @@ -import { LLSDToken } from './LLSDToken'; -import { LLSDTokenGenerator } from './LLSDTokenGenerator'; +import type { LLSDToken } from './LLSDToken'; +import type { LLSDTokenGenerator } from './LLSDTokenGenerator'; export interface LLSDTokenContainer { diff --git a/lib/classes/llsd/LLSDTokenGenerator.ts b/lib/classes/llsd/LLSDTokenGenerator.ts index 721ac33..4ed2c4d 100644 --- a/lib/classes/llsd/LLSDTokenGenerator.ts +++ b/lib/classes/llsd/LLSDTokenGenerator.ts @@ -1,3 +1,3 @@ -import { LLSDToken } from './LLSDToken'; +import type { LLSDToken } from './LLSDToken'; export type LLSDTokenGenerator = () => LLSDToken | undefined; diff --git a/lib/classes/llsd/LLSDTokenSpec.ts b/lib/classes/llsd/LLSDTokenSpec.ts index 492d3e2..40fbad1 100644 --- a/lib/classes/llsd/LLSDTokenSpec.ts +++ b/lib/classes/llsd/LLSDTokenSpec.ts @@ -1,4 +1,4 @@ -import { LLSDTokenType } from './LLSDTokenType'; +import type { LLSDTokenType } from './LLSDTokenType'; export interface LLSDTokenSpec { diff --git a/lib/classes/llsd/LLSDTokenType.ts b/lib/classes/llsd/LLSDTokenType.ts index 20ef5a8..88375c5 100644 --- a/lib/classes/llsd/LLSDTokenType.ts +++ b/lib/classes/llsd/LLSDTokenType.ts @@ -1,23 +1,23 @@ export enum LLSDTokenType { - UNKNOWN = 0, - WHITESPACE, - NULL, - MAP_START, - MAP_END, - COLON, - COMMA, - ARRAY_START, - ARRAY_END, - BOOLEAN, - INTEGER, - REAL, - UUID, - STRING_FIXED_SINGLE, - STRING_FIXED_DOUBLE, - STRING_DYNAMIC_START, - URI, - DATE, - BINARY_STATIC, - BINARY_DYNAMIC_START, + Unknown = 0, + Whitespace = 1, + Null = 2, + MapStart = 3, + MapEnd = 4, + Colon = 5, + Comma = 6, + ArrayStart = 7, + ArrayEnd = 8, + Boolean = 9, + Integer = 10, + Real = 11, + UUID = 12, + StringFixedSingle = 13, + StringFixedDouble = 14, + StringDynamicStart = 15, + URI = 16, + Date = 17, + BinaryStatic = 18, + BinaryDynamicStart = 19, } diff --git a/lib/classes/llsd/LLSDType.ts b/lib/classes/llsd/LLSDType.ts index 09dafd8..8426cf3 100644 --- a/lib/classes/llsd/LLSDType.ts +++ b/lib/classes/llsd/LLSDType.ts @@ -1,4 +1,8 @@ -import { LLSDObject } from './LLSDObject'; -import { UUID } from '../UUID'; +import type { LLSDObject } from './LLSDObject'; +import type { UUID } from '../UUID'; +import type { LLSDInteger } from "./LLSDInteger"; +import type { LLSDReal } from "./LLSDReal"; +import type { LLSDURI } from "./LLSDURI"; -export type LLSDType = number | boolean | string | Buffer | LLSDObject | null | UUID | Date | LLSDType[]; +export type LLSDType = LLSDInteger | LLSDReal | LLSDURI | boolean | string | Buffer | LLSDObject | null | UUID | Date | LLSDType[]; + \ No newline at end of file diff --git a/lib/classes/llsd/LLSDURI.ts b/lib/classes/llsd/LLSDURI.ts new file mode 100644 index 0000000..b93ed8c --- /dev/null +++ b/lib/classes/llsd/LLSDURI.ts @@ -0,0 +1,24 @@ +export class LLSDURI +{ + private uri: string; + + public constructor(pUri: string) + { + this.uri = pUri; + } + + public valueOf(): string + { + return this.uri; + } + + public toString(): string + { + return this.uri; + } + + public set value(newValue: string) + { + this.uri = newValue; + } +} \ No newline at end of file diff --git a/lib/classes/llsd/LLSDXML.ts b/lib/classes/llsd/LLSDXML.ts new file mode 100644 index 0000000..42f75e8 --- /dev/null +++ b/lib/classes/llsd/LLSDXML.ts @@ -0,0 +1,283 @@ +import type { LLSDType } from "./LLSDType"; +import { LLSDMap } from "./LLSDMap"; +import { LLSDArray } from "./LLSDArray"; +import { LLSDInteger } from "./LLSDInteger"; +import { LLSDReal } from "./LLSDReal"; +import { UUID } from "../UUID"; +import { LLSDURI } from "./LLSDURI"; + +export class LLSDXML +{ + public static parseValue(element: Record[]): LLSDType + { + if (element.length !== 1) + { + throw new Error('Exactly one key expected') + } + const keys = Object.keys(element[0]); + switch(keys[0]) + { + case 'map': + { + return LLSDMap.parseXML(element[0].map as Record[]); + } + case 'string': + { + const strNode = element[0].string as Record[]; + if (strNode.length !== 1) + { + return ''; + } + const [node] = strNode; + if (node['#text'] === undefined) + { + throw new Error('String is not a text node') + } + return String(node['#text']); + } + case 'uuid': + { + const strNode = element[0].uuid as Record[]; + if (strNode.length !== 1) + { + return UUID.zero(); + } + const [node] = strNode; + if (node['#text'] === undefined) + { + throw new Error('UUID is not a text node') + } + return new UUID(String(node['#text'])); + } + case 'uri': + { + const strNode = element[0].uri as Record[]; + if (strNode.length !== 1) + { + return new LLSDURI(''); + } + const [node] = strNode; + if (node['#text'] === undefined) + { + throw new Error('URI is not a text node') + } + return new LLSDURI(String(node['#text'])); + } + case 'date': + { + const strNode = element[0].date as Record[]; + if (strNode.length !== 1) + { + return new Date(0); + } + const [node] = strNode; + if (node['#text'] === undefined) + { + throw new Error('Date is not a text node') + } + return new Date(String(node['#text'])); + } + case 'undef': + { + return null; + } + case 'binary': + { + const strNode = element[0].binary as Record[]; + if (strNode.length !== 1) + { + return Buffer.alloc(0); + } + const [node] = strNode; + if (node['#text'] === undefined) + { + throw new Error('Binary is not a text node') + } + return Buffer.from(String(node['#text']), 'base64'); + } + case 'boolean': + { + const strNode = element[0].boolean as Record[]; + if (strNode.length !== 1) + { + return false; + } + const [node] = strNode; + if (node['#text'] === undefined) + { + throw new Error('Boolean is not a text node') + } + const val = String(node['#text']); + return val.toLowerCase() === '1' || val === 'true'; + } + case 'integer': + { + const strNode = element[0].integer as Record[]; + if (strNode.length !== 1) + { + return new LLSDInteger(0); + } + const [node] = strNode; + if (node['#text'] === undefined) + { + throw new Error('Integer is not a text node') + } + return new LLSDInteger(parseInt(String(node['#text']), 10)); + } + case 'real': + { + const strNode = element[0].real as Record[]; + if (strNode.length !== 1) + { + return new LLSDReal(0); + } + const [node] = strNode; + if (node['#text'] === undefined) + { + throw new Error('Real is not a text node') + } + return new LLSDReal(String(node['#text'])); + } + case 'array': + { + return LLSDArray.parseXML(element[0].array as Record[]); + } + default: + throw new Error('Unexpected XML element: ' + keys[0]); + } + } + + public static encodeValue(value: LLSDType): unknown + { + if (value instanceof LLSDMap) + { + return value.toXML(); + } + else if (value instanceof LLSDInteger) + { + return { + 'integer': [{ + '#text': value.valueOf() + }] + }; + } + else if (value instanceof LLSDReal) + { + const val = value.valueOf(); + if (isNaN(val)) + { + return { + 'real': [{ + '#text': 'NaNQ' + }] + }; + } + else if (val === -Infinity) + { + return { + 'real': [{ + '#text': '-Infinity' + }] + }; + } + else if (val === Infinity) + { + return { + 'real': [{ + '#text': '+Infinity' + }] + }; + } + else if (Object.is(val, -0)) + { + return { + 'real': [{ + '#text': '-Zero' + }] + }; + } + else if (val === 0) + { + return { + 'real': [{ + '#text': '+Zero' + }] + }; + } + + return { + 'real': [{ + '#text': val + }] + }; + } + else if (value instanceof UUID) + { + return { + 'uuid': [{ + '#text': value.toString() + }] + }; + } + else if (value instanceof LLSDURI) + { + return { + 'uri': [{ + '#text': value.valueOf() + }] + }; + } + else if (value instanceof Buffer) + { + return { + 'binary': [{ + '#text': value.toString('base64') + }] + }; + } + else if (value instanceof Date) + { + return { + 'date': [{ + '#text': value.toISOString() + }] + }; + } + else if (value === null) + { + return { + 'undef': [] + }; + } + else if (value === true) + { + return { + 'boolean': [{ + '#text': true + }] + }; + } + else if (value === false) + { + return { + 'boolean': [] + }; + } + else if (typeof value === 'string') + { + return { + 'string': [{ + '#text': value.toString() + }] + }; + } + else if (Array.isArray(value)) + { + return LLSDArray.toXML(value); + } + else + { + throw new Error('Unknown type: ' + String(value)); + } + } +} diff --git a/lib/classes/messages/ObjectUpdate.ts b/lib/classes/messages/ObjectUpdate.ts index 2cb2cb7..668547a 100644 --- a/lib/classes/messages/ObjectUpdate.ts +++ b/lib/classes/messages/ObjectUpdate.ts @@ -4,8 +4,9 @@ import { UUID } from '../UUID'; import { Vector3 } from '../Vector3'; import * as Long from 'long'; import { MessageFlags } from '../../enums/MessageFlags'; -import { MessageBase } from '../MessageBase'; +import type { MessageBase } from '../MessageBase'; import { Message } from '../../enums/Message'; +import type { PCode } from '../../enums/PCode'; export class ObjectUpdateMessage implements MessageBase { @@ -22,7 +23,7 @@ export class ObjectUpdateMessage implements MessageBase State: number; FullID: UUID; CRC: number; - PCode: number; + PCode: PCode; Material: number; ClickAction: number; Scale: Vector3; @@ -71,7 +72,7 @@ export class ObjectUpdateMessage implements MessageBase return this.calculateVarVarSize(this.ObjectData, 'ObjectData', 1) + this.calculateVarVarSize(this.ObjectData, 'TextureEntry', 2) + this.calculateVarVarSize(this.ObjectData, 'TextureAnim', 1) + this.calculateVarVarSize(this.ObjectData, 'NameValue', 2) + this.calculateVarVarSize(this.ObjectData, 'Data', 2) + this.calculateVarVarSize(this.ObjectData, 'Text', 1) + this.calculateVarVarSize(this.ObjectData, 'MediaURL', 1) + this.calculateVarVarSize(this.ObjectData, 'PSBlock', 1) + this.calculateVarVarSize(this.ObjectData, 'ExtraParams', 1) + ((141) * this.ObjectData.length) + 11; } - calculateVarVarSize(block: { [key: string]: any }[], paramName: string, extraPerVar: number): number + calculateVarVarSize(block: Record[], paramName: string, extraPerVar: number): number { let size = 0; for (const bl of block) @@ -85,100 +86,100 @@ export class ObjectUpdateMessage implements MessageBase writeToBuffer(buf: Buffer, pos: number): number { const startPos = pos; - buf.writeInt32LE(this.RegionData['RegionHandle'].low, pos); + buf.writeInt32LE(this.RegionData.RegionHandle.low, pos); pos += 4; - buf.writeInt32LE(this.RegionData['RegionHandle'].high, pos); + buf.writeInt32LE(this.RegionData.RegionHandle.high, pos); pos += 4; - buf.writeUInt16LE(this.RegionData['TimeDilation'], pos); + buf.writeUInt16LE(this.RegionData.TimeDilation, pos); pos += 2; const count = this.ObjectData.length; buf.writeUInt8(this.ObjectData.length, pos++); for (let i = 0; i < count; i++) { - buf.writeUInt32LE(this.ObjectData[i]['ID'], pos); + buf.writeUInt32LE(this.ObjectData[i].ID, pos); pos += 4; - buf.writeUInt8(this.ObjectData[i]['State'], pos++); - this.ObjectData[i]['FullID'].writeToBuffer(buf, pos); + buf.writeUInt8(this.ObjectData[i].State, pos++); + this.ObjectData[i].FullID.writeToBuffer(buf, pos); pos += 16; - buf.writeUInt32LE(this.ObjectData[i]['CRC'], pos); + buf.writeUInt32LE(this.ObjectData[i].CRC, pos); pos += 4; - buf.writeUInt8(this.ObjectData[i]['PCode'], pos++); - buf.writeUInt8(this.ObjectData[i]['Material'], pos++); - buf.writeUInt8(this.ObjectData[i]['ClickAction'], pos++); - this.ObjectData[i]['Scale'].writeToBuffer(buf, pos, false); + buf.writeUInt8(this.ObjectData[i].PCode, pos++); + buf.writeUInt8(this.ObjectData[i].Material, pos++); + buf.writeUInt8(this.ObjectData[i].ClickAction, pos++); + this.ObjectData[i].Scale.writeToBuffer(buf, pos, false); pos += 12; - buf.writeUInt8(this.ObjectData[i]['ObjectData'].length, pos++); - this.ObjectData[i]['ObjectData'].copy(buf, pos); - pos += this.ObjectData[i]['ObjectData'].length; - buf.writeUInt32LE(this.ObjectData[i]['ParentID'], pos); + buf.writeUInt8(this.ObjectData[i].ObjectData.length, pos++); + this.ObjectData[i].ObjectData.copy(buf, pos); + pos += this.ObjectData[i].ObjectData.length; + buf.writeUInt32LE(this.ObjectData[i].ParentID, pos); pos += 4; - buf.writeUInt32LE(this.ObjectData[i]['UpdateFlags'], pos); + buf.writeUInt32LE(this.ObjectData[i].UpdateFlags, pos); pos += 4; - buf.writeUInt8(this.ObjectData[i]['PathCurve'], pos++); - buf.writeUInt8(this.ObjectData[i]['ProfileCurve'], pos++); - buf.writeUInt16LE(this.ObjectData[i]['PathBegin'], pos); + buf.writeUInt8(this.ObjectData[i].PathCurve, pos++); + buf.writeUInt8(this.ObjectData[i].ProfileCurve, pos++); + buf.writeUInt16LE(this.ObjectData[i].PathBegin, pos); pos += 2; - buf.writeUInt16LE(this.ObjectData[i]['PathEnd'], pos); + buf.writeUInt16LE(this.ObjectData[i].PathEnd, pos); pos += 2; - buf.writeUInt8(this.ObjectData[i]['PathScaleX'], pos++); - buf.writeUInt8(this.ObjectData[i]['PathScaleY'], pos++); - buf.writeUInt8(this.ObjectData[i]['PathShearX'], pos++); - buf.writeUInt8(this.ObjectData[i]['PathShearY'], pos++); - buf.writeInt8(this.ObjectData[i]['PathTwist'], pos++); - buf.writeInt8(this.ObjectData[i]['PathTwistBegin'], pos++); - buf.writeInt8(this.ObjectData[i]['PathRadiusOffset'], pos++); - buf.writeInt8(this.ObjectData[i]['PathTaperX'], pos++); - buf.writeInt8(this.ObjectData[i]['PathTaperY'], pos++); - buf.writeUInt8(this.ObjectData[i]['PathRevolutions'], pos++); - buf.writeInt8(this.ObjectData[i]['PathSkew'], pos++); - buf.writeUInt16LE(this.ObjectData[i]['ProfileBegin'], pos); + buf.writeUInt8(this.ObjectData[i].PathScaleX, pos++); + buf.writeUInt8(this.ObjectData[i].PathScaleY, pos++); + buf.writeUInt8(this.ObjectData[i].PathShearX, pos++); + buf.writeUInt8(this.ObjectData[i].PathShearY, pos++); + buf.writeInt8(this.ObjectData[i].PathTwist, pos++); + buf.writeInt8(this.ObjectData[i].PathTwistBegin, pos++); + buf.writeInt8(this.ObjectData[i].PathRadiusOffset, pos++); + buf.writeInt8(this.ObjectData[i].PathTaperX, pos++); + buf.writeInt8(this.ObjectData[i].PathTaperY, pos++); + buf.writeUInt8(this.ObjectData[i].PathRevolutions, pos++); + buf.writeInt8(this.ObjectData[i].PathSkew, pos++); + buf.writeUInt16LE(this.ObjectData[i].ProfileBegin, pos); pos += 2; - buf.writeUInt16LE(this.ObjectData[i]['ProfileEnd'], pos); + buf.writeUInt16LE(this.ObjectData[i].ProfileEnd, pos); pos += 2; - buf.writeUInt16LE(this.ObjectData[i]['ProfileHollow'], pos); + buf.writeUInt16LE(this.ObjectData[i].ProfileHollow, pos); pos += 2; - buf.writeUInt16LE(this.ObjectData[i]['TextureEntry'].length, pos); + buf.writeUInt16LE(this.ObjectData[i].TextureEntry.length, pos); pos += 2; - this.ObjectData[i]['TextureEntry'].copy(buf, pos); - pos += this.ObjectData[i]['TextureEntry'].length; - buf.writeUInt8(this.ObjectData[i]['TextureAnim'].length, pos++); - this.ObjectData[i]['TextureAnim'].copy(buf, pos); - pos += this.ObjectData[i]['TextureAnim'].length; - buf.writeUInt16LE(this.ObjectData[i]['NameValue'].length, pos); + this.ObjectData[i].TextureEntry.copy(buf, pos); + pos += this.ObjectData[i].TextureEntry.length; + buf.writeUInt8(this.ObjectData[i].TextureAnim.length, pos++); + this.ObjectData[i].TextureAnim.copy(buf, pos); + pos += this.ObjectData[i].TextureAnim.length; + buf.writeUInt16LE(this.ObjectData[i].NameValue.length, pos); pos += 2; - this.ObjectData[i]['NameValue'].copy(buf, pos); - pos += this.ObjectData[i]['NameValue'].length; - buf.writeUInt16LE(this.ObjectData[i]['Data'].length, pos); + this.ObjectData[i].NameValue.copy(buf, pos); + pos += this.ObjectData[i].NameValue.length; + buf.writeUInt16LE(this.ObjectData[i].Data.length, pos); pos += 2; - this.ObjectData[i]['Data'].copy(buf, pos); - pos += this.ObjectData[i]['Data'].length; - buf.writeUInt8(this.ObjectData[i]['Text'].length, pos++); - this.ObjectData[i]['Text'].copy(buf, pos); - pos += this.ObjectData[i]['Text'].length; - this.ObjectData[i]['TextColor'].copy(buf, pos); + this.ObjectData[i].Data.copy(buf, pos); + pos += this.ObjectData[i].Data.length; + buf.writeUInt8(this.ObjectData[i].Text.length, pos++); + this.ObjectData[i].Text.copy(buf, pos); + pos += this.ObjectData[i].Text.length; + this.ObjectData[i].TextColor.copy(buf, pos); pos += 4; - buf.writeUInt8(this.ObjectData[i]['MediaURL'].length, pos++); - this.ObjectData[i]['MediaURL'].copy(buf, pos); - pos += this.ObjectData[i]['MediaURL'].length; - buf.writeUInt8(this.ObjectData[i]['PSBlock'].length, pos++); - this.ObjectData[i]['PSBlock'].copy(buf, pos); - pos += this.ObjectData[i]['PSBlock'].length; - buf.writeUInt8(this.ObjectData[i]['ExtraParams'].length, pos++); - this.ObjectData[i]['ExtraParams'].copy(buf, pos); - pos += this.ObjectData[i]['ExtraParams'].length; - this.ObjectData[i]['Sound'].writeToBuffer(buf, pos); + buf.writeUInt8(this.ObjectData[i].MediaURL.length, pos++); + this.ObjectData[i].MediaURL.copy(buf, pos); + pos += this.ObjectData[i].MediaURL.length; + buf.writeUInt8(this.ObjectData[i].PSBlock.length, pos++); + this.ObjectData[i].PSBlock.copy(buf, pos); + pos += this.ObjectData[i].PSBlock.length; + buf.writeUInt8(this.ObjectData[i].ExtraParams.length, pos++); + this.ObjectData[i].ExtraParams.copy(buf, pos); + pos += this.ObjectData[i].ExtraParams.length; + this.ObjectData[i].Sound.writeToBuffer(buf, pos); pos += 16; - this.ObjectData[i]['OwnerID'].writeToBuffer(buf, pos); + this.ObjectData[i].OwnerID.writeToBuffer(buf, pos); pos += 16; - buf.writeFloatLE(this.ObjectData[i]['Gain'], pos); + buf.writeFloatLE(this.ObjectData[i].Gain, pos); pos += 4; - buf.writeUInt8(this.ObjectData[i]['Flags'], pos++); - buf.writeFloatLE(this.ObjectData[i]['Radius'], pos); + buf.writeUInt8(this.ObjectData[i].Flags, pos++); + buf.writeFloatLE(this.ObjectData[i].Radius, pos); pos += 4; - buf.writeUInt8(this.ObjectData[i]['JointType'], pos++); - this.ObjectData[i]['JointPivot'].writeToBuffer(buf, pos, false); + buf.writeUInt8(this.ObjectData[i].JointType, pos++); + this.ObjectData[i].JointPivot.writeToBuffer(buf, pos, false); pos += 12; - this.ObjectData[i]['JointAxisOrAnchor'].writeToBuffer(buf, pos, false); + this.ObjectData[i].JointAxisOrAnchor.writeToBuffer(buf, pos, false); pos += 12; } return pos - startPos; @@ -196,9 +197,9 @@ export class ObjectUpdateMessage implements MessageBase RegionHandle: Long.ZERO, TimeDilation: 0 }; - newObjRegionData['RegionHandle'] = new Long(buf.readInt32LE(pos), buf.readInt32LE(pos + 4)); + newObjRegionData.RegionHandle = new Long(buf.readInt32LE(pos), buf.readInt32LE(pos + 4)); pos += 8; - newObjRegionData['TimeDilation'] = buf.readUInt16LE(pos); + newObjRegionData.TimeDilation = buf.readUInt16LE(pos); pos += 2; this.RegionData = newObjRegionData; if (pos >= buf.length) @@ -304,90 +305,90 @@ export class ObjectUpdateMessage implements MessageBase JointPivot: Vector3.getZero(), JointAxisOrAnchor: Vector3.getZero() }; - newObjObjectData['ID'] = buf.readUInt32LE(pos); + newObjObjectData.ID = buf.readUInt32LE(pos); pos += 4; - newObjObjectData['State'] = buf.readUInt8(pos++); - newObjObjectData['FullID'] = new UUID(buf, pos); + newObjObjectData.State = buf.readUInt8(pos++); + newObjObjectData.FullID = new UUID(buf, pos); pos += 16; - newObjObjectData['CRC'] = buf.readUInt32LE(pos); + newObjObjectData.CRC = buf.readUInt32LE(pos); pos += 4; - newObjObjectData['PCode'] = buf.readUInt8(pos++); - newObjObjectData['Material'] = buf.readUInt8(pos++); - newObjObjectData['ClickAction'] = buf.readUInt8(pos++); - newObjObjectData['Scale'] = new Vector3(buf, pos, false); + newObjObjectData.PCode = buf.readUInt8(pos++); + newObjObjectData.Material = buf.readUInt8(pos++); + newObjObjectData.ClickAction = buf.readUInt8(pos++); + newObjObjectData.Scale = new Vector3(buf, pos, false); pos += 12; varLength = buf.readUInt8(pos++); - newObjObjectData['ObjectData'] = buf.slice(pos, pos + varLength); + newObjObjectData.ObjectData = buf.slice(pos, pos + varLength); pos += varLength; - newObjObjectData['ParentID'] = buf.readUInt32LE(pos); + newObjObjectData.ParentID = buf.readUInt32LE(pos); pos += 4; - newObjObjectData['UpdateFlags'] = buf.readUInt32LE(pos); + newObjObjectData.UpdateFlags = buf.readUInt32LE(pos); pos += 4; - newObjObjectData['PathCurve'] = buf.readUInt8(pos++); - newObjObjectData['ProfileCurve'] = buf.readUInt8(pos++); - newObjObjectData['PathBegin'] = buf.readUInt16LE(pos); + newObjObjectData.PathCurve = buf.readUInt8(pos++); + newObjObjectData.ProfileCurve = buf.readUInt8(pos++); + newObjObjectData.PathBegin = buf.readUInt16LE(pos); pos += 2; - newObjObjectData['PathEnd'] = buf.readUInt16LE(pos); + newObjObjectData.PathEnd = buf.readUInt16LE(pos); pos += 2; - newObjObjectData['PathScaleX'] = buf.readUInt8(pos++); - newObjObjectData['PathScaleY'] = buf.readUInt8(pos++); - newObjObjectData['PathShearX'] = buf.readUInt8(pos++); - newObjObjectData['PathShearY'] = buf.readUInt8(pos++); - newObjObjectData['PathTwist'] = buf.readInt8(pos++); - newObjObjectData['PathTwistBegin'] = buf.readInt8(pos++); - newObjObjectData['PathRadiusOffset'] = buf.readInt8(pos++); - newObjObjectData['PathTaperX'] = buf.readInt8(pos++); - newObjObjectData['PathTaperY'] = buf.readInt8(pos++); - newObjObjectData['PathRevolutions'] = buf.readUInt8(pos++); - newObjObjectData['PathSkew'] = buf.readInt8(pos++); - newObjObjectData['ProfileBegin'] = buf.readUInt16LE(pos); + newObjObjectData.PathScaleX = buf.readUInt8(pos++); + newObjObjectData.PathScaleY = buf.readUInt8(pos++); + newObjObjectData.PathShearX = buf.readUInt8(pos++); + newObjObjectData.PathShearY = buf.readUInt8(pos++); + newObjObjectData.PathTwist = buf.readInt8(pos++); + newObjObjectData.PathTwistBegin = buf.readInt8(pos++); + newObjObjectData.PathRadiusOffset = buf.readInt8(pos++); + newObjObjectData.PathTaperX = buf.readInt8(pos++); + newObjObjectData.PathTaperY = buf.readInt8(pos++); + newObjObjectData.PathRevolutions = buf.readUInt8(pos++); + newObjObjectData.PathSkew = buf.readInt8(pos++); + newObjObjectData.ProfileBegin = buf.readUInt16LE(pos); pos += 2; - newObjObjectData['ProfileEnd'] = buf.readUInt16LE(pos); + newObjObjectData.ProfileEnd = buf.readUInt16LE(pos); pos += 2; - newObjObjectData['ProfileHollow'] = buf.readUInt16LE(pos); + newObjObjectData.ProfileHollow = buf.readUInt16LE(pos); pos += 2; varLength = buf.readUInt16LE(pos); pos += 2; - newObjObjectData['TextureEntry'] = buf.slice(pos, pos + varLength); + newObjObjectData.TextureEntry = buf.slice(pos, pos + varLength); pos += varLength; varLength = buf.readUInt8(pos++); - newObjObjectData['TextureAnim'] = buf.slice(pos, pos + varLength); + newObjObjectData.TextureAnim = buf.slice(pos, pos + varLength); pos += varLength; varLength = buf.readUInt16LE(pos); pos += 2; - newObjObjectData['NameValue'] = buf.slice(pos, pos + varLength); + newObjObjectData.NameValue = buf.slice(pos, pos + varLength); pos += varLength; varLength = buf.readUInt16LE(pos); pos += 2; - newObjObjectData['Data'] = buf.slice(pos, pos + varLength); + newObjObjectData.Data = buf.slice(pos, pos + varLength); pos += varLength; varLength = buf.readUInt8(pos++); - newObjObjectData['Text'] = buf.slice(pos, pos + varLength); + newObjObjectData.Text = buf.slice(pos, pos + varLength); pos += varLength; - newObjObjectData['TextColor'] = buf.slice(pos, pos + 4); + newObjObjectData.TextColor = buf.slice(pos, pos + 4); pos += 4; varLength = buf.readUInt8(pos++); - newObjObjectData['MediaURL'] = buf.slice(pos, pos + varLength); + newObjObjectData.MediaURL = buf.slice(pos, pos + varLength); pos += varLength; varLength = buf.readUInt8(pos++); - newObjObjectData['PSBlock'] = buf.slice(pos, pos + varLength); + newObjObjectData.PSBlock = buf.slice(pos, pos + varLength); pos += varLength; varLength = buf.readUInt8(pos++); - newObjObjectData['ExtraParams'] = buf.slice(pos, pos + varLength); + newObjObjectData.ExtraParams = buf.slice(pos, pos + varLength); pos += varLength; - newObjObjectData['Sound'] = new UUID(buf, pos); + newObjObjectData.Sound = new UUID(buf, pos); pos += 16; - newObjObjectData['OwnerID'] = new UUID(buf, pos); + newObjObjectData.OwnerID = new UUID(buf, pos); pos += 16; - newObjObjectData['Gain'] = buf.readFloatLE(pos); + newObjObjectData.Gain = buf.readFloatLE(pos); pos += 4; - newObjObjectData['Flags'] = buf.readUInt8(pos++); - newObjObjectData['Radius'] = buf.readFloatLE(pos); + newObjObjectData.Flags = buf.readUInt8(pos++); + newObjObjectData.Radius = buf.readFloatLE(pos); pos += 4; - newObjObjectData['JointType'] = buf.readUInt8(pos++); - newObjObjectData['JointPivot'] = new Vector3(buf, pos, false); + newObjObjectData.JointType = buf.readUInt8(pos++); + newObjObjectData.JointPivot = new Vector3(buf, pos, false); pos += 12; - newObjObjectData['JointAxisOrAnchor'] = new Vector3(buf, pos, false); + newObjObjectData.JointAxisOrAnchor = new Vector3(buf, pos, false); pos += 12; this.ObjectData.push(newObjObjectData); } diff --git a/lib/classes/messages/SimStats.ts b/lib/classes/messages/SimStats.ts index 0db32b2..4152466 100644 --- a/lib/classes/messages/SimStats.ts +++ b/lib/classes/messages/SimStats.ts @@ -2,8 +2,9 @@ import * as Long from 'long'; import { MessageFlags } from '../../enums/MessageFlags'; -import { MessageBase } from '../MessageBase'; +import type { MessageBase } from '../MessageBase'; import { Message } from '../../enums/Message'; +import type { StatID } from '../../enums/StatID'; export class SimStatsMessage implements MessageBase { @@ -18,7 +19,7 @@ export class SimStatsMessage implements MessageBase ObjectCapacity: number; }; Stat: { - StatID: number; + StatID: StatID; StatValue: number; }[]; PidStat: { @@ -37,32 +38,32 @@ export class SimStatsMessage implements MessageBase writeToBuffer(buf: Buffer, pos: number): number { const startPos = pos; - buf.writeUInt32LE(this.Region['RegionX'], pos); + buf.writeUInt32LE(this.Region.RegionX, pos); pos += 4; - buf.writeUInt32LE(this.Region['RegionY'], pos); + buf.writeUInt32LE(this.Region.RegionY, pos); pos += 4; - buf.writeUInt32LE(this.Region['RegionFlags'], pos); + buf.writeUInt32LE(this.Region.RegionFlags, pos); pos += 4; - buf.writeUInt32LE(this.Region['ObjectCapacity'], pos); + buf.writeUInt32LE(this.Region.ObjectCapacity, pos); pos += 4; let count = this.Stat.length; buf.writeUInt8(this.Stat.length, pos++); for (let i = 0; i < count; i++) { - buf.writeUInt32LE(this.Stat[i]['StatID'], pos); + buf.writeUInt32LE(this.Stat[i].StatID, pos); pos += 4; - buf.writeFloatLE(this.Stat[i]['StatValue'], pos); + buf.writeFloatLE(this.Stat[i].StatValue, pos); pos += 4; } - buf.writeInt32LE(this.PidStat['PID'], pos); + buf.writeInt32LE(this.PidStat.PID, pos); pos += 4; count = this.RegionInfo.length; buf.writeUInt8(this.RegionInfo.length, pos++); for (let i = 0; i < count; i++) { - buf.writeInt32LE(this.RegionInfo[i]['RegionFlagsExtended'].low, pos); + buf.writeInt32LE(this.RegionInfo[i].RegionFlagsExtended.low, pos); pos += 4; - buf.writeInt32LE(this.RegionInfo[i]['RegionFlagsExtended'].high, pos); + buf.writeInt32LE(this.RegionInfo[i].RegionFlagsExtended.high, pos); pos += 4; } return pos - startPos; @@ -83,13 +84,13 @@ export class SimStatsMessage implements MessageBase RegionFlags: 0, ObjectCapacity: 0 }; - newObjRegion['RegionX'] = buf.readUInt32LE(pos); + newObjRegion.RegionX = buf.readUInt32LE(pos); pos += 4; - newObjRegion['RegionY'] = buf.readUInt32LE(pos); + newObjRegion.RegionY = buf.readUInt32LE(pos); pos += 4; - newObjRegion['RegionFlags'] = buf.readUInt32LE(pos); + newObjRegion.RegionFlags = buf.readUInt32LE(pos); pos += 4; - newObjRegion['ObjectCapacity'] = buf.readUInt32LE(pos); + newObjRegion.ObjectCapacity = buf.readUInt32LE(pos); pos += 4; this.Region = newObjRegion; if (pos >= buf.length) @@ -107,9 +108,9 @@ export class SimStatsMessage implements MessageBase StatID: 0, StatValue: 0 }; - newObjStat['StatID'] = buf.readUInt32LE(pos); + newObjStat.StatID = buf.readUInt32LE(pos); pos += 4; - newObjStat['StatValue'] = buf.readFloatLE(pos); + newObjStat.StatValue = buf.readFloatLE(pos); pos += 4; this.Stat.push(newObjStat); } @@ -118,7 +119,7 @@ export class SimStatsMessage implements MessageBase } = { PID: 0 }; - newObjPidStat['PID'] = buf.readInt32LE(pos); + newObjPidStat.PID = buf.readInt32LE(pos); pos += 4; this.PidStat = newObjPidStat; if (pos >= buf.length) @@ -134,7 +135,7 @@ export class SimStatsMessage implements MessageBase } = { RegionFlagsExtended: Long.ZERO }; - newObjRegionInfo['RegionFlagsExtended'] = new Long(buf.readInt32LE(pos), buf.readInt32LE(pos + 4)); + newObjRegionInfo.RegionFlagsExtended = new Long(buf.readInt32LE(pos), buf.readInt32LE(pos + 4)); pos += 8; this.RegionInfo.push(newObjRegionInfo); } diff --git a/lib/classes/public/Avatar.ts b/lib/classes/public/Avatar.ts index a622f69..263682a 100644 --- a/lib/classes/public/Avatar.ts +++ b/lib/classes/public/Avatar.ts @@ -2,51 +2,31 @@ import { AvatarQueryResult } from './AvatarQueryResult'; import { GameObject } from './GameObject'; import { Vector3 } from '../Vector3'; import { Quaternion } from '../Quaternion'; -import { Subject, Subscription } from 'rxjs'; +import type { Subscription } from 'rxjs'; +import { Subject } from 'rxjs'; import { UUID } from '../UUID'; + export class Avatar extends AvatarQueryResult { - private rotation: Quaternion = Quaternion.getIdentity(); - private title = ''; - public onMoved: Subject = new Subject(); public onTitleChanged: Subject = new Subject(); public onLeftRegion: Subject = new Subject(); public onAttachmentAdded: Subject = new Subject(); public onAttachmentRemoved: Subject = new Subject(); public onVisibleChanged: Subject = new Subject(); + + private rotation: Quaternion = Quaternion.getIdentity(); + private title = ''; + private _isVisible = false; private _gameObject?: GameObject; private _position: Vector3 = Vector3.getZero(); private _coarsePosition: Vector3 = Vector3.getZero(); - private attachments: { [key: string]: GameObject } = {}; + private readonly attachments = new Map(); - static fromGameObject(obj: GameObject): Avatar - { - let firstName = 'Unknown'; - let lastName = 'Avatar'; - - if (obj.NameValue['FirstName'] !== undefined) - { - firstName = obj.NameValue['FirstName'].value; - } - if (obj.NameValue['LastName'] !== undefined) - { - lastName = obj.NameValue['LastName'].value; - } - - const av = new Avatar(obj, firstName , lastName); - if (obj.NameValue['Title'] !== undefined) - { - av.setTitle(obj.NameValue['Title'].value); - } - av.processObjectUpdate(obj); - return av; - } - - constructor(gameObjectOrID: GameObject | UUID, firstName: string, lastName: string) + public constructor(gameObjectOrID: GameObject | UUID, firstName: string, lastName: string) { super((gameObjectOrID instanceof UUID) ? gameObjectOrID : gameObjectOrID.FullID, firstName, lastName); @@ -56,7 +36,33 @@ export class Avatar extends AvatarQueryResult } } - set gameObject(obj: GameObject) + public static fromGameObject(obj: GameObject): Avatar + { + let firstName = 'Unknown'; + let lastName = 'Avatar'; + + const fnValue = obj.NameValue.get('FirstName'); + if (fnValue !== undefined) + { + firstName = fnValue.value; + } + const lnValue = obj.NameValue.get('LastName'); + if (lnValue !== undefined) + { + lastName = lnValue.value; + } + + const av = new Avatar(obj, firstName , lastName); + const titleValue = obj.NameValue.get('Title'); + if (titleValue !== undefined) + { + av.setTitle(titleValue.value); + } + av.processObjectUpdate(obj); + return av; + } + + public set gameObject(obj: GameObject) { if (this._gameObject !== obj) { @@ -75,12 +81,12 @@ export class Avatar extends AvatarQueryResult } } - get isVisible(): boolean + public get isVisible(): boolean { return this._isVisible; } - set isVisible(value: boolean) + public set isVisible(value: boolean) { if (this._isVisible !== value) { @@ -89,7 +95,7 @@ export class Avatar extends AvatarQueryResult } } - setTitle(newTitle: string): void + public setTitle(newTitle: string): void { if (newTitle !== this.title) { @@ -98,12 +104,12 @@ export class Avatar extends AvatarQueryResult } } - getTitle(): string + public getTitle(): string { return this.title; } - get position(): Vector3 + public get position(): Vector3 { if (this._isVisible) { @@ -120,25 +126,25 @@ export class Avatar extends AvatarQueryResult } } - set coarsePosition(pos: Vector3) + public set coarsePosition(pos: Vector3) { const oldPos = this._coarsePosition; this._coarsePosition = pos; if (!this._isVisible) { - if (Vector3.distance(pos, oldPos) > 0.0001) + if (pos.distance(oldPos) > 0.0001) { this.onMoved.next(this); } } } - getRotation(): Quaternion + public getRotation(): Quaternion { return new Quaternion(this.rotation); } - processObjectUpdate(obj: GameObject): void + public processObjectUpdate(obj: GameObject): void { if (obj !== this._gameObject) { @@ -148,14 +154,15 @@ export class Avatar extends AvatarQueryResult { this.setGeometry(obj.Position, obj.Rotation); } - if (obj.NameValue['Title'] !== undefined) + const lnTitle = obj.NameValue.get('Title'); + if (lnTitle !== undefined) { - this.setTitle(obj.NameValue['Title'].value); + this.setTitle(lnTitle.value); } this.isVisible = true; } - setGeometry(position: Vector3, rotation: Quaternion): void + public setGeometry(position: Vector3, rotation: Quaternion): void { const oldPosition = this._position; const oldRotation = this.rotation; @@ -165,22 +172,28 @@ export class Avatar extends AvatarQueryResult this.rotation = new Quaternion(rotation); const rotDist = new Quaternion(this.rotation).angleBetween(oldRotation); - if (Vector3.distance(position, oldPosition) > 0.0001 || rotDist > 0.0001) + if (position.distance(oldPosition) > 0.0001 || rotDist > 0.0001) { this.onMoved.next(this); } } - getAttachment(itemID: UUID): GameObject + public getAttachment(itemID: UUID): GameObject { - if (this.attachments[itemID.toString()] !== undefined) + const attachment = this.attachments.get(itemID.toString()); + if (attachment) { - return this.attachments[itemID.toString()]; + return attachment; } throw new Error('Attachment not found'); } - waitForAttachment(itemID: UUID | string, timeout: number = 30000): Promise + public getAttachments(): Map + { + return this.attachments; + } + + public async waitForAttachment(itemID: UUID | string, timeout = 30000): Promise { return new Promise((resolve, reject) => { @@ -193,7 +206,7 @@ export class Avatar extends AvatarQueryResult const attach = this.getAttachment(itemID); resolve(attach); } - catch (ignore) + catch (_ignore: unknown) { let subs: Subscription | undefined = undefined; let timr: NodeJS.Timeout | undefined = undefined; @@ -232,32 +245,33 @@ export class Avatar extends AvatarQueryResult }); } - addAttachment(obj: GameObject): void + public addAttachment(obj: GameObject): void { if (obj.itemID !== undefined) { - if (this.attachments[obj.itemID.toString()] === undefined) + if (!this.attachments.has(obj.itemID.toString())) { - this.attachments[obj.itemID.toString()] = obj; + this.attachments.set(obj.itemID.toString(), obj); this.onAttachmentAdded.next(obj); } } } - removeAttachment(obj: GameObject): void + public removeAttachment(obj: GameObject): void { - if (obj.NameValue['AttachItemID']) + const attachItemID = obj.NameValue.get('AttachItemID'); + if (attachItemID !== undefined) { - const itemID = new UUID(obj.NameValue['AttachItemID'].value); - if (this.attachments[itemID.toString()] !== undefined) + const itemID = new UUID(attachItemID.value); + if (this.attachments.has(itemID.toString())) { this.onAttachmentRemoved.next(obj); - delete this.attachments[itemID.toString()]; + this.attachments.delete(itemID.toString()); } } } - coarseLeftRegion(): void + public coarseLeftRegion(): void { this.onLeftRegion.next(this); } diff --git a/lib/classes/public/AvatarQueryResult.ts b/lib/classes/public/AvatarQueryResult.ts index 909e2a6..4b7477e 100644 --- a/lib/classes/public/AvatarQueryResult.ts +++ b/lib/classes/public/AvatarQueryResult.ts @@ -1,28 +1,28 @@ -import { UUID } from '../UUID'; +import type { UUID } from '../UUID'; export class AvatarQueryResult { - constructor(private avatarKey: UUID, private firstName: string, private lastName: string) + public constructor(private readonly avatarKey: UUID, private readonly firstName: string, private readonly lastName: string) { } - getName(): string + public getName(): string { return this.firstName + ' ' + this.lastName; } - getFirstName(): string + public getFirstName(): string { return this.firstName; } - getLastName(): string + public getLastName(): string { return this.lastName; } - getKey(): UUID + public getKey(): UUID { return this.avatarKey; } diff --git a/lib/classes/public/ExtendedMeshData.ts b/lib/classes/public/ExtendedMeshData.ts index aad2aad..ea765b8 100644 --- a/lib/classes/public/ExtendedMeshData.ts +++ b/lib/classes/public/ExtendedMeshData.ts @@ -1,10 +1,10 @@ -import { ExtendedMeshFlags } from './ExtendedMeshFlags'; +import type { ExtendedMeshFlags } from './ExtendedMeshFlags'; export class ExtendedMeshData { public flags: ExtendedMeshFlags = 0 as ExtendedMeshFlags; - constructor(buf?: Buffer, pos?: number, length?: number) + public constructor(buf?: Buffer, pos?: number, length?: number) { if (buf !== undefined && pos !== undefined && length !== undefined) { @@ -15,12 +15,12 @@ export class ExtendedMeshData } } - writeToBuffer(buf: Buffer, pos: number): void + public writeToBuffer(buf: Buffer, pos: number): void { buf.writeUInt32LE(this.flags, pos); } - getBuffer(): Buffer + public getBuffer(): Buffer { const buf = Buffer.allocUnsafe(4); this.writeToBuffer(buf, 0); diff --git a/lib/classes/public/ExtraParams.ts b/lib/classes/public/ExtraParams.ts index 71fdf5b..b746bae 100644 --- a/lib/classes/public/ExtraParams.ts +++ b/lib/classes/public/ExtraParams.ts @@ -4,28 +4,28 @@ import { LightData } from './LightData'; import { LightImageData } from './LightImageData'; import { MeshData } from './MeshData'; import { SculptData } from './SculptData'; -import { UUID } from '../UUID'; -import { Vector3 } from '../Vector3'; -import { Color4 } from '../Color4'; +import type { UUID } from '../UUID'; +import type { Vector3 } from '../Vector3'; +import type { Color4 } from '../Color4'; import { ExtendedMeshData } from './ExtendedMeshData'; import { RenderMaterialData } from './RenderMaterialData'; import { ReflectionProbeData } from './ReflectionProbeData'; -import { ExtendedMeshFlags } from './ExtendedMeshFlags'; -import { ReflectionProbeFlags } from './ReflectionProbeFlags'; -import { RenderMaterialParam } from './RenderMaterialParam'; +import type { ExtendedMeshFlags } from './ExtendedMeshFlags'; +import type { ReflectionProbeFlags } from './ReflectionProbeFlags'; +import type { RenderMaterialParam } from './RenderMaterialParam'; export class ExtraParams { - flexibleData: FlexibleData | null = null; - lightData: LightData | null = null; - lightImageData: LightImageData | null = null; - meshData: MeshData | null = null; - sculptData: SculptData | null = null; - extendedMeshData: ExtendedMeshData | null = null; - renderMaterialData: RenderMaterialData | null = null; - reflectionProbeData: ReflectionProbeData | null = null; + public flexibleData: FlexibleData | null = null; + public lightData: LightData | null = null; + public lightImageData: LightImageData | null = null; + public meshData: MeshData | null = null; + public sculptData: SculptData | null = null; + public extendedMeshData: ExtendedMeshData | null = null; + public renderMaterialData: RenderMaterialData | null = null; + public reflectionProbeData: ReflectionProbeData | null = null; - static getLengthOfParams(buf: Buffer, pos: number): number + public static getLengthOfParams(buf: Buffer, pos: number): number { const startPos = pos; if (pos >= buf.length) @@ -42,56 +42,53 @@ export class ExtraParams } return pos - startPos; } - static from(buf: Buffer): ExtraParams + + public static from(buf: Buffer): ExtraParams { const ep = new ExtraParams(); - if (buf instanceof Buffer) + let pos = 0; + if (pos >= buf.length) { - let pos = 0; - if (pos >= buf.length) - { - return ep; - } - const extraParamCount = buf.readUInt8(pos++); - for (let k = 0; k < extraParamCount; k++) - { - const type: ExtraParamType = buf.readUInt16LE(pos); - pos = pos + 2; - const paramLength = buf.readUInt32LE(pos); - pos = pos + 4; - - switch (type) - { - case ExtraParamType.Flexible: - ep.flexibleData = new FlexibleData(buf, pos, paramLength); - break; - case ExtraParamType.Light: - ep.lightData = new LightData(buf, pos, paramLength); - break; - case ExtraParamType.LightImage: - ep.lightImageData = new LightImageData(buf, pos, paramLength); - break; - case ExtraParamType.Mesh: - ep.meshData = new MeshData(buf, pos, paramLength); - break; - case ExtraParamType.Sculpt: - ep.sculptData = new SculptData(buf, pos, paramLength); - break; - case ExtraParamType.ExtendedMesh: - ep.extendedMeshData = new ExtendedMeshData(buf, pos, paramLength); - break; - case ExtraParamType.RenderMaterial: - ep.renderMaterialData = new RenderMaterialData(buf, pos, paramLength); - break; - case ExtraParamType.ReflectionProbe: - ep.reflectionProbeData = new ReflectionProbeData(buf, pos, paramLength); - break; - } - - pos += paramLength; - } return ep; } + const extraParamCount = buf.readUInt8(pos++); + for (let k = 0; k < extraParamCount; k++) + { + const type: ExtraParamType = buf.readUInt16LE(pos); + pos = pos + 2; + const paramLength = buf.readUInt32LE(pos); + pos = pos + 4; + + switch (type) + { + case ExtraParamType.Flexible: + ep.flexibleData = new FlexibleData(buf, pos, paramLength); + break; + case ExtraParamType.Light: + ep.lightData = new LightData(buf, pos, paramLength); + break; + case ExtraParamType.LightImage: + ep.lightImageData = new LightImageData(buf, pos, paramLength); + break; + case ExtraParamType.Mesh: + ep.meshData = new MeshData(buf, pos, paramLength); + break; + case ExtraParamType.Sculpt: + ep.sculptData = new SculptData(buf, pos, paramLength); + break; + case ExtraParamType.ExtendedMesh: + ep.extendedMeshData = new ExtendedMeshData(buf, pos, paramLength); + break; + case ExtraParamType.RenderMaterial: + ep.renderMaterialData = new RenderMaterialData(buf, pos, paramLength); + break; + case ExtraParamType.ReflectionProbe: + ep.reflectionProbeData = new ReflectionProbeData(buf, pos, paramLength); + break; + } + + pos += paramLength; + } return ep; } public setMeshData(type: number, uuid: UUID): void @@ -101,12 +98,14 @@ export class ExtraParams this.meshData.meshData = uuid; } + // noinspection JSUnusedGlobalSymbols public setExtendedMeshData(flags: ExtendedMeshFlags): void { this.extendedMeshData = new ExtendedMeshData(); this.extendedMeshData.flags = flags; } + // noinspection JSUnusedGlobalSymbols public setReflectionProbeData(ambiance: number, clipDistance: number, flags: ReflectionProbeFlags): void { this.reflectionProbeData = new ReflectionProbeData(); @@ -115,6 +114,7 @@ export class ExtraParams this.reflectionProbeData.flags = flags; } + // noinspection JSUnusedGlobalSymbols public setRenderMaterialData(params: RenderMaterialParam[]): void { this.renderMaterialData = new RenderMaterialData(); @@ -127,6 +127,7 @@ export class ExtraParams this.sculptData.type = type; this.sculptData.texture = uuid; } + public setFlexiData(softness: number, tension: number, drag: number, gravity: number, wind: number, force: Vector3): void { this.flexibleData = new FlexibleData(); @@ -137,6 +138,7 @@ export class ExtraParams this.flexibleData.Wind = wind; this.flexibleData.Force = force; } + public setLightData(color: Color4, radius: number, cutoff: number, falloff: number, intensity: number): void { this.lightData = new LightData(); @@ -146,6 +148,7 @@ export class ExtraParams this.lightData.Falloff = falloff; this.lightData.Intensity = intensity; } + public toBuffer(): Buffer { let totalLength = 1; @@ -243,6 +246,7 @@ export class ExtraParams } return buf; } + public toBase64(): string { return this.toBuffer().toString('base64'); diff --git a/lib/classes/public/FlexibleData.ts b/lib/classes/public/FlexibleData.ts index 8b172ed..a5afe16 100644 --- a/lib/classes/public/FlexibleData.ts +++ b/lib/classes/public/FlexibleData.ts @@ -2,14 +2,14 @@ import { Vector3 } from '../Vector3'; export class FlexibleData { - Softness = 0; - Tension = 0.0; - Drag = 0.0; - Gravity = 0.0; - Wind = 0.0; - Force = Vector3.getZero(); + public Softness = 0; + public Tension = 0.0; + public Drag = 0.0; + public Gravity = 0.0; + public Wind = 0.0; + public Force = Vector3.getZero(); - constructor(buf?: Buffer, pos?: number, length?: number) + public constructor(buf?: Buffer, pos?: number, length?: number) { if (buf !== undefined && pos !== undefined && length !== undefined) { @@ -24,7 +24,8 @@ export class FlexibleData } } } - writeToBuffer(buf: Buffer, pos: number): void + + public writeToBuffer(buf: Buffer, pos: number): void { buf[pos] = (this.Softness & 2) << 6; buf[pos + 1] = (this.Softness & 1) << 7; @@ -34,7 +35,8 @@ export class FlexibleData buf[pos++] = (this.Wind) * 10; this.Force.writeToBuffer(buf, pos, false); } - getBuffer(): Buffer + + public getBuffer(): Buffer { const buf = Buffer.allocUnsafe(16); this.writeToBuffer(buf, 0); diff --git a/lib/classes/public/Friend.ts b/lib/classes/public/Friend.ts index 0c54bc4..70e27ee 100644 --- a/lib/classes/public/Friend.ts +++ b/lib/classes/public/Friend.ts @@ -3,7 +3,7 @@ import { RightsFlags } from '../../enums/RightsFlags'; export class Friend extends Avatar { - online: boolean; - theirRights: RightsFlags = RightsFlags.None; - myRights: RightsFlags = RightsFlags.None; + public online: boolean; + public theirRights: RightsFlags = RightsFlags.None; + public myRights: RightsFlags = RightsFlags.None; } diff --git a/lib/classes/public/GameObject.ts b/lib/classes/public/GameObject.ts index ea546b6..0e93914 100644 --- a/lib/classes/public/GameObject.ts +++ b/lib/classes/public/GameObject.ts @@ -1,18 +1,18 @@ import { Vector3 } from '../Vector3'; import { UUID } from '../UUID'; import { Quaternion } from '../Quaternion'; -import { Tree } from '../../enums/Tree'; +import type { Tree } from '../../enums/Tree'; import { Vector4 } from '../Vector4'; import { TextureEntry } from '../TextureEntry'; import { Color4 } from '../Color4'; import { ParticleSystem } from '../ParticleSystem'; -import { ITreeBoundingBox } from '../interfaces/ITreeBoundingBox'; -import { NameValue } from '../NameValue'; -import * as Long from 'long'; -import { IGameObjectData } from '../interfaces/IGameObjectData'; +import type { ITreeBoundingBox } from '../interfaces/ITreeBoundingBox'; +import type { NameValue } from '../NameValue'; +import type * as Long from 'long'; +import type { IGameObjectData } from '../interfaces/IGameObjectData'; +import type { XMLElement, XMLNode } from 'xmlbuilder'; import * as builder from 'xmlbuilder'; -import { XMLElement, XMLNode } from 'xmlbuilder'; -import { Region } from '../Region'; +import type { Region } from '../Region'; import { InventoryItem } from '../InventoryItem'; import { LLWearable } from '../LLWearable'; import { TextureAnim } from './TextureAnim'; @@ -32,156 +32,1305 @@ import { ProfileShape } from '../../enums/ProfileShape'; import { HoleType } from '../../enums/HoleType'; import { SculptType } from '../../enums/SculptType'; import { PacketFlags } from '../../enums/PacketFlags'; -import { HTTPAssets } from '../../enums/HTTPAssets'; -import { PhysicsShapeType } from '../../enums/PhysicsShapeType'; +import type { PhysicsShapeType } from '../../enums/PhysicsShapeType'; import { PCode } from '../../enums/PCode'; -import { SoundFlags } from '../../enums/SoundFlags'; +import type { SoundFlags } from '../../enums/SoundFlags'; import { DeRezObjectMessage } from '../messages/DeRezObject'; import { DeRezDestination } from '../../enums/DeRezDestination'; import { Message } from '../../enums/Message'; -import { UpdateCreateInventoryItemMessage } from '../messages/UpdateCreateInventoryItem'; +import type { UpdateCreateInventoryItemMessage } from '../messages/UpdateCreateInventoryItem'; import { FilterResponse } from '../../enums/FilterResponse'; import { UpdateTaskInventoryMessage } from '../messages/UpdateTaskInventory'; -import { ObjectPropertiesMessage } from '../messages/ObjectProperties'; +import type { ObjectPropertiesMessage } from '../messages/ObjectProperties'; import { ObjectSelectMessage } from '../messages/ObjectSelect'; import { ObjectDeselectMessage } from '../messages/ObjectDeselect'; import { AttachmentPoint } from '../../enums/AttachmentPoint'; import { RequestTaskInventoryMessage } from '../messages/RequestTaskInventory'; -import { ReplyTaskInventoryMessage } from '../messages/ReplyTaskInventory'; +import type { ReplyTaskInventoryMessage } from '../messages/ReplyTaskInventory'; import { InventoryType } from '../../enums/InventoryType'; -import { InventoryFolder } from '../InventoryFolder'; -import { ObjectUpdateMessage } from '../messages/ObjectUpdate'; +import type { InventoryFolder } from '../InventoryFolder'; +import type { ObjectUpdateMessage } from '../messages/ObjectUpdate'; import { Subject } from 'rxjs'; import { RezScriptMessage } from '../messages/RezScript'; import { PermissionMask } from '../../enums/PermissionMask'; import { AssetType } from '../../enums/AssetType'; import { LLGLTFMaterialOverride } from '../LLGLTFMaterialOverride'; +import { RemoveTaskInventoryMessage } from '../messages/RemoveTaskInventory'; import * as uuid from 'uuid'; import { Logger } from '../Logger'; +import { InventoryTypeRegistry } from '../InventoryTypeRegistry'; +import { AssetTypeRegistry } from '../AssetTypeRegistry'; export class GameObject implements IGameObjectData { - rtreeEntry?: ITreeBoundingBox; + public dateReceived: Date; + public rtreeEntry?: ITreeBoundingBox; - textureAnim: TextureAnim; - extraParams: ExtraParams; + public textureAnim: TextureAnim; + public extraParams: ExtraParams; - deleted = false; - creatorID?: UUID; - creationDate?: Long; - baseMask?: number; - ownerMask?: number; - groupMask?: number; - groupID?: UUID; - everyoneMask?: number; - nextOwnerMask?: number; - ownershipCost?: number; - saleType?: number; - salePrice?: number; - aggregatePerms?: number; - aggregatePermTextures?: number; - aggregatePermTexturesOwner?: number; - category: number; - inventorySerial: number; - itemID: UUID; - folderID: UUID; - fromTaskID: UUID; - lastOwnerID: UUID; - name?: string; - description?: string; - touchName?: string; - sitName?: string; - textureID?: string; - resolvedAt?: number; - resolvedInventory = false; - totalChildren?: number; + public deleted = false; + public creatorID?: UUID; + public creationDate?: Long; + public baseMask?: number; + public ownerMask?: number; + public groupMask?: number; + public groupID?: UUID; + public everyoneMask?: number; + public nextOwnerMask?: number; + public ownershipCost?: number; + public saleType?: number; + public salePrice?: number; + public aggregatePerms?: number; + public aggregatePermTextures?: number; + public aggregatePermTexturesOwner?: number; + public category: number; + public inventorySerial: number; + public itemID: UUID; + public folderID: UUID; + public fromTaskID: UUID; + public lastOwnerID: UUID; + public name?: string; + public description?: string; + public touchName?: string; + public sitName?: string; + public textureID?: string; + public resolvedAt?: number; + public resolvedInventory = false; + public totalChildren?: number; - landImpact?: number; - calculatedLandImpact?: number; - physicaImpact?: number; - resourceImpact?: number; - linkResourceImpact?: number; - linkPhysicsImpact?: number; - limitingType?: string; + public landImpact?: number; + public calculatedLandImpact?: number; + public physicaImpact?: number; + public resourceImpact?: number; + public linkResourceImpact?: number; + public linkPhysicsImpact?: number; + public limitingType?: string; - children?: GameObject[]; - ID = 0; - FullID = UUID.random(); - ParentID?: number; - OwnerID = UUID.zero(); - IsAttachment = false; - NameValue: { [key: string]: NameValue } = {}; - PCode: PCode = PCode.None; + public children?: GameObject[]; + public ID = 0; + public FullID = UUID.random(); + public ParentID?: number; + public _ownerID = UUID.zero(); + public IsAttachment = false; + public NameValue = new Map(); + public PCode: PCode = PCode.None; - State?: number; - CRC?: number; - Material?: number; - ClickAction?: number; - Scale?: Vector3; - Flags?: PrimFlags; - PathCurve?: number; - ProfileCurve?: number; - PathBegin?: number; - PathEnd?: number; - PathScaleX?: number; - PathScaleY?: number; - PathShearX?: number; - PathShearY?: number; - PathTwist?: number; - PathTwistBegin?: number; - PathRadiusOffset?: number; - PathTaperX?: number; - PathTaperY?: number; - PathRevolutions?: number; - PathSkew?: number; - ProfileBegin?: number; - ProfileEnd?: number; - ProfileHollow?: number; - TextureEntry?: TextureEntry; - Text?: string; - TextColor?: Color4; - MediaURL?: string; - JointType?: number; - JointPivot?: Vector3; - JointAxisOrAnchor?: Vector3; - Position?: Vector3; - Rotation?: Quaternion; - CollisionPlane?: Vector4; - Velocity?: Vector3; - Acceleration?: Vector3; - AngularVelocity?: Vector3; - TreeSpecies?: Tree; - Sound?: UUID; - SoundGain?: number; - SoundFlags?: SoundFlags; - SoundRadius?: number; - Particles?: ParticleSystem; + public State?: number; + public CRC?: number; + public Material?: number; + public ClickAction?: number; + public Scale?: Vector3; + public Flags?: PrimFlags; + public PathCurve?: number; + public ProfileCurve?: number; + public PathBegin?: number; + public PathEnd?: number; + public PathScaleX?: number; + public PathScaleY?: number; + public PathShearX?: number; + public PathShearY?: number; + public PathTwist?: number; + public PathTwistBegin?: number; + public PathRadiusOffset?: number; + public PathTaperX?: number; + public PathTaperY?: number; + public PathRevolutions?: number; + public PathSkew?: number; + public ProfileBegin?: number; + public ProfileEnd?: number; + public ProfileHollow?: number; + public TextureEntry?: TextureEntry; + public Text?: string; + public TextColor?: Color4; + public MediaURL?: string; + public JointType?: number; + public JointPivot?: Vector3; + public JointAxisOrAnchor?: Vector3; + public Position?: Vector3; + public Rotation?: Quaternion; + public CollisionPlane?: Vector4; + public Velocity?: Vector3; + public Acceleration?: Vector3; + public AngularVelocity?: Vector3; + public TreeSpecies?: Tree; + public Sound?: UUID; + public SoundGain?: number; + public SoundFlags?: SoundFlags; + public SoundRadius?: number; + public Particles?: ParticleSystem; - density?: number; - friction?: number; - gravityMultiplier?: number; - physicsShapeType?: PhysicsShapeType; - restitution?: number; - attachmentPoint: AttachmentPoint = AttachmentPoint.Default; + public density?: number; + public friction?: number; + public gravityMultiplier?: number; + public physicsShapeType?: PhysicsShapeType; + public restitution?: number; + public attachmentPoint: AttachmentPoint = AttachmentPoint.Default; + public inventory: InventoryItem[] = []; + public linksetData?: Map; - region: Region; + public region: Region; + public resolveAttempts = 0; + public childrenPopulated = false; + public claimedForBuild = false; + public createdSelected = false; + public isMarkedRoot = false; + public onTextureUpdate: Subject = new Subject(); - inventory: InventoryItem[] = []; + public get OwnerID(): UUID + { + return this._ownerID; + } - resolveAttempts = 0; - childrenPopulated = false; + public set OwnerID(owner: UUID) + { + this._ownerID = owner; + } - claimedForBuild = false; - createdSelected = false; - isMarkedRoot = false; - onTextureUpdate: Subject = new Subject(); + public constructor() + { + this.dateReceived = new Date(); + this.Position = Vector3.getZero(); + this.Rotation = Quaternion.getIdentity(); + this.AngularVelocity = Vector3.getZero(); + this.TreeSpecies = 0; + this.SoundFlags = 0; + this.SoundRadius = 1.0; + this.SoundGain = 1.0; + } + + public static async fromXML(xml: any): Promise + { + let result: any = null; + if (typeof xml === 'string') + { + const parsed = await Utils.parseXML(xml); + if (parsed.SceneObjectGroup === undefined) + { + throw new Error('SceneObjectGroup not found'); + } + result = parsed.SceneObjectGroup; + } + else + { + result = xml; + } + + let rootPartXML: any = null; + if (result.SceneObjectPart !== undefined) + { + rootPartXML = result.SceneObjectPart; + } + else if (result.RootPart?.[0]?.SceneObjectPart !== undefined) + { + rootPartXML = result.RootPart[0].SceneObjectPart; + } + else + { + throw new Error('Root part not found'); + } + + const rootPart = GameObject.partFromXMLJS(rootPartXML[0], true); + rootPart.children = []; + rootPart.totalChildren = 0; + if (result.OtherParts !== undefined && Array.isArray(result.OtherParts) && result.OtherParts.length > 0) + { + const [obj] = result.OtherParts; + if (obj.SceneObjectPart !== undefined || obj.Part !== undefined) + { + if (obj.Part !== undefined) + { + for (const part of obj.Part) + { + rootPart.children.push(GameObject.partFromXMLJS(part.SceneObjectPart[0], false)); + rootPart.totalChildren++; + } + } + else + { + for (const part of obj.SceneObjectPart) + { + rootPart.children.push(GameObject.partFromXMLJS(part, false)); + rootPart.totalChildren++; + } + } + } + } + return rootPart; + } + + public static async deRezObjects(region: Region, objects: GameObject[], destination: DeRezDestination, transactionID: UUID, destFolder: UUID): Promise + { + const msg = new DeRezObjectMessage(); + + msg.AgentData = { + AgentID: region.agent.agentID, + SessionID: region.circuit.sessionID + }; + msg.AgentBlock = { + GroupID: UUID.zero(), + Destination: destination, + DestinationID: destFolder, + TransactionID: transactionID, + PacketCount: 1, + PacketNumber: 1 + }; + msg.ObjectData = []; + for (const obj of objects) + { + msg.ObjectData.push({ + ObjectLocalID: obj.ID + }); + } + const ack = region.circuit.sendMessage(msg, PacketFlags.Reliable); + return region.circuit.waitForAck(ack, 10000); + } + + public static async takeManyToInventory(region: Region, objects: GameObject[], folder?: InventoryFolder): Promise + { + const transactionID = UUID.random(); + let enforceFolder = true; + if (folder === undefined) + { + enforceFolder = false; + folder = region.agent.inventory.getRootFolderMain(); + } + + if (folder !== undefined) + { + void GameObject.deRezObjects(region, objects, DeRezDestination.AgentInventoryTake, transactionID, folder.folderID); + + const createInventoryMsg: UpdateCreateInventoryItemMessage = await region.circuit.waitForMessage(Message.UpdateCreateInventoryItem, 20000, (message: UpdateCreateInventoryItemMessage) => + { + for (const inv of message.InventoryData) + { + const name = Utils.BufferToStringSimple(inv.Name); + if (name === objects[0].name) + { + return FilterResponse.Finish; + } + } + return FilterResponse.NoMatch; + }); + + for (const inv of createInventoryMsg.InventoryData) + { + const name = Utils.BufferToStringSimple(inv.Name); + if (name === objects[0].name) + { + const itemID = inv.ItemID; + const item = await region.agent.inventory.fetchInventoryItem(itemID); + if (item === null) + { + throw new Error('Inventory item was unable to be retrieved after take to inventory'); + } + else + { + if (enforceFolder && !item.parentID.equals(folder.folderID)) + { + await item.moveToFolder(folder); + } + } + return item; + } + } + } + throw new Error('Failed to take object') + } + + // noinspection JSUnusedGlobalSymbols + public async waitForTextureUpdate(timeout?: number): Promise + { + await Utils.waitOrTimeOut(this.onTextureUpdate, timeout); + } + + // noinspection JSUnusedGlobalSymbols + public async rezScript(name: string, description: string, perms: PermissionMask = 532480 as PermissionMask): Promise + { + const rezScriptMsg = new RezScriptMessage(); + rezScriptMsg.AgentData = { + AgentID: this.region.agent.agentID, + SessionID: this.region.circuit.sessionID, + GroupID: this.region.agent.activeGroupID + }; + rezScriptMsg.UpdateBlock = { + ObjectLocalID: this.ID, + Enabled: true + }; + const tmpName = uuid.v4(); + const invItem = new InventoryItem(this); + invItem.itemID = UUID.zero(); + invItem.parentID = this.FullID; + invItem.permissions.creator = this.region.agent.agentID; + invItem.permissions.group = UUID.zero(); + invItem.permissions.baseMask = PermissionMask.All; + invItem.permissions.ownerMask = PermissionMask.All; + invItem.permissions.groupMask = 0; + invItem.permissions.everyoneMask = 0; + invItem.permissions.nextOwnerMask = perms; + invItem.permissions.groupOwned = false; + invItem.type = AssetType.LSLText; + invItem.inventoryType = InventoryType.LSL; + invItem.flags = 0; + invItem.salePrice = this.salePrice ?? 10; + invItem.saleType = this.saleType ?? 0; + invItem.name = tmpName; + invItem.description = description; + invItem.created = new Date(); + + rezScriptMsg.InventoryBlock = { + ItemID: UUID.zero(), + FolderID: this.FullID, + CreatorID: this.region.agent.agentID, + OwnerID: this.region.agent.agentID, + GroupID: UUID.zero(), + BaseMask: PermissionMask.All, + OwnerMask: PermissionMask.All, + GroupMask: 0, + EveryoneMask: 0, + NextOwnerMask: perms, + GroupOwned: false, + TransactionID: UUID.zero(), + Type: AssetType.LSLText, + InvType: InventoryType.LSL, + Flags: 0, + SaleType: this.saleType ?? 0, + SalePrice: this.salePrice ?? 10, + Name: Utils.StringToBuffer(tmpName), + Description: Utils.StringToBuffer(description), + CreationDate: Math.floor(invItem.created.getTime() / 1000), + CRC: invItem.getCRC() + }; + await this.region.circuit.waitForAck(this.region.circuit.sendMessage(rezScriptMsg, PacketFlags.Reliable), 10000); + await this.updateInventory(); + for (const item of this.inventory) + { + if (item.name === tmpName) + { + // We are intentionally not waiting for this rename job so that the wait below succeeds + void item.rename(name); + try + { + await this.waitForInventoryUpdate(); + } + catch (_error: unknown) + { + // ignore + } + await this.updateInventory(); + for (const newItem of this.inventory) + { + if (newItem.itemID.equals(item.itemID)) + { + return newItem; + } + } + return item; + } + } + throw new Error('Failed to add script to object'); + } + + public async updateInventory(): Promise + { + if (this.PCode === PCode.Avatar) + { + return; + } + + try + { + const capURL = await this.region.caps.getCapability('RequestTaskInventory'); + const result = await this.region.caps.capsPerformXMLGet(capURL + '?task_id=' + this.FullID.toString()) as { + contents?: { + asset_id?: string, + created_at?: number, + desc?: string, + flags?: number, + inv_type?: string, + item_id?: string, + metadata?: Record, + name?: string, + parent_id?: string; + permissions?: { + base_mask?: number; + creator_id?: string; + everyone_mask?: number; + group_id?: string; + group_mask?: number; + is_owner_group?: boolean; + last_owner_id?: string; + next_owner_mask?: number; + owner_id?: string; + owner_mask?: number; + } + sale_info?: { + sale_price?: number; + sale_type?: string; + } + type?: string + }[] + }; + + if (result.contents) + { + this.inventory = []; + for(const item of result.contents) + { + const invItem = new InventoryItem(this, this.region.agent); + invItem.assetID = new UUID(item.asset_id); + invItem.created = new Date((item.created_at ?? 0) * 1000); + invItem.description = item.desc ?? ''; + invItem.flags = item.flags ?? 0; + const invType = InventoryTypeRegistry.getTypeFromTypeName(item.inv_type ?? ''); + if (invType) + { + invItem.inventoryType = invType.type; + } + const type = AssetTypeRegistry.getTypeFromTypeName(item.type ?? ''); + if (type !== undefined) + { + invItem.type = type.type; + } + invItem.itemID = new UUID(item.item_id); + invItem.name = item.name ?? ''; + invItem.parentID = new UUID(item.parent_id); + invItem.permissions = { + baseMask: item.permissions?.base_mask ?? 0, + creator: new UUID(item.permissions?.creator_id), + everyoneMask: item.permissions?.everyone_mask ?? 0, + group: new UUID(item.permissions?.group_id), + groupMask: item.permissions?.group_mask ?? 0, + groupOwned: item.permissions?.is_owner_group ?? false, + lastOwner: new UUID(item.permissions?.last_owner_id), + nextOwnerMask: item.permissions?.next_owner_mask ?? 0, + owner: new UUID(item.permissions?.owner_id), + ownerMask: item.permissions?.owner_mask ?? 0 + } + invItem.salePrice = item.sale_info?.sale_price ?? 0; + switch (item.sale_info?.sale_type) + { + case 'not': + invItem.saleType = 0; + break; + case 'orig': + invItem.saleType = 1; + break; + case 'copy': + invItem.saleType = 2; + break; + case 'cntn': + invItem.saleType = 3; + break; + case undefined: + break; + } + this.inventory.push(invItem); + } + return; + } + } + catch(_e: unknown) + { + // ignore + } + + + const req = new RequestTaskInventoryMessage(); + req.AgentData = { + AgentID: this.region.agent.agentID, + SessionID: this.region.circuit.sessionID + }; + req.InventoryData = { + LocalID: this.ID + }; + this.region.circuit.sendMessage(req, PacketFlags.Reliable); + return this.waitForTaskInventory(); + } + + public hasNameValueEntry(key: string): boolean + { + return this.NameValue.has(key); + } + + public getNameValueEntry(key: string): string + { + const entry = this.NameValue.get(key); + if(entry !== undefined) + { + return entry.value; + } + return ''; + } + + public setIfDefined(def?: number, v?: number): number + { + if (def === undefined) + { + def = 0; + } + if (v === undefined) + { + return def; + } + else + { + return v; + } + } + + public async setShape(PathCurve?: number, + ProfileCurve?: number, + PathBegin?: number, + PathEnd?: number, + PathScaleX?: number, + PathScaleY?: number, + PathShearX?: number, + PathShearY?: number, + PathTwist?: number, + PathTwistBegin?: number, + PathRadiusOffset?: number, + PathTaperX?: number, + PathTaperY?: number, + PathRevolutions?: number, + PathSkew?: number, + ProfileBegin?: number, + ProfileEnd?: number, + ProfileHollow?: number): Promise + { + this.PathCurve = this.setIfDefined(this.PathCurve, PathCurve); + this.ProfileCurve = this.setIfDefined(this.ProfileCurve, ProfileCurve); + this.PathBegin = this.setIfDefined(this.PathBegin, PathBegin); + this.PathEnd = this.setIfDefined(this.PathEnd, PathEnd); + this.PathScaleX = this.setIfDefined(this.PathScaleX, PathScaleX); + this.PathScaleY = this.setIfDefined(this.PathScaleY, PathScaleY); + this.PathShearX = this.setIfDefined(this.PathShearX, PathShearX); + this.PathShearY = this.setIfDefined(this.PathShearY, PathShearY); + this.PathTwist = this.setIfDefined(this.PathTwist, PathTwist); + this.PathTwistBegin = this.setIfDefined(this.PathTwistBegin, PathTwistBegin); + this.PathRadiusOffset = this.setIfDefined(this.PathRadiusOffset, PathRadiusOffset); + this.PathTaperX = this.setIfDefined(this.PathTaperX, PathTaperX); + this.PathTaperY = this.setIfDefined(this.PathTaperY, PathTaperY); + this.PathRevolutions = this.setIfDefined(this.PathRevolutions, PathRevolutions); + this.PathSkew = this.setIfDefined(this.PathSkew, PathSkew); + this.ProfileBegin = this.setIfDefined(this.ProfileBegin, ProfileBegin); + this.ProfileEnd = this.setIfDefined(this.ProfileEnd, ProfileEnd); + this.ProfileHollow = this.setIfDefined(this.ProfileHollow, ProfileHollow); + if (this.region === undefined) + { + return; + } + const msg = new ObjectShapeMessage(); + msg.AgentData = { + AgentID: this.region.agent.agentID, + SessionID: this.region.circuit.sessionID + }; + msg.ObjectData = [ + { + ObjectLocalID: this.ID, + PathCurve: this.PathCurve, + ProfileCurve: this.ProfileCurve, + PathBegin: Utils.packBeginCut(this.PathBegin), + PathEnd: Utils.packEndCut(this.PathEnd), + PathScaleX: Utils.packPathScale(this.PathScaleX), + PathScaleY: Utils.packPathScale(this.PathScaleY), + PathShearX: Utils.packPathShear(this.PathShearX), + PathShearY: Utils.packPathShear(this.PathShearY), + PathTwist: Utils.packPathTwist(this.PathTwist), + PathTwistBegin: Utils.packPathTwist(this.PathTwistBegin), + PathRadiusOffset: Utils.packPathTwist(this.PathRadiusOffset), + PathTaperX: Utils.packPathTaper(this.PathTaperX), + PathTaperY: Utils.packPathTaper(this.PathTaperY), + PathRevolutions: Utils.packPathRevolutions(this.PathRevolutions), + PathSkew: Utils.packPathTwist(this.PathSkew), + ProfileBegin: Utils.packBeginCut(this.ProfileBegin), + ProfileEnd: Utils.packEndCut(this.ProfileEnd), + ProfileHollow: Utils.packProfileHollow(this.ProfileHollow) + } + ]; + return this.region.circuit.waitForAck(this.region.circuit.sendMessage(msg, PacketFlags.Reliable), 10000); + } + + public async setName(name: string): Promise + { + this.name = name; + if (this.region === undefined) + { + return; + } + const msg = new ObjectNameMessage(); + msg.AgentData = { + AgentID: this.region.agent.agentID, + SessionID: this.region.circuit.sessionID + }; + msg.ObjectData = [ + { + LocalID: this.ID, + Name: Utils.StringToBuffer(name) + } + ]; + return this.region.circuit.waitForAck(this.region.circuit.sendMessage(msg, PacketFlags.Reliable), 10000); + } + + public async setGeometry(pos?: Vector3, rot?: Quaternion, scale?: Vector3, wholeLinkset = false): Promise + { + const data = []; + const linked = (wholeLinkset) ? UpdateType.Linked : 0; + if (pos !== undefined) + { + this.Position = pos; + data.push({ + ObjectLocalID: this.ID, + Type: UpdateType.Position | linked, + Data: pos.getBuffer() + }); + } + if (rot !== undefined) + { + this.Rotation = rot; + data.push({ + ObjectLocalID: this.ID, + Type: UpdateType.Rotation | linked, + Data: rot.getBuffer() + }) + } + if (scale !== undefined) + { + this.Scale = scale; + data.push({ + ObjectLocalID: this.ID, + Type: UpdateType.Scale | linked, + Data: scale.getBuffer() + }) + } + if (this.region === undefined || data.length === 0) + { + return; + } + const msg = new MultipleObjectUpdateMessage(); + msg.AgentData = { + AgentID: this.region.agent.agentID, + SessionID: this.region.circuit.sessionID + }; + msg.ObjectData = data; + return this.region.circuit.waitForAck(this.region.circuit.sendMessage(msg, PacketFlags.Reliable), 30000); + } + + // noinspection JSUnusedGlobalSymbols + public async linkTo(rootObj: GameObject): Promise + { + const msg = new ObjectLinkMessage(); + msg.AgentData = { + AgentID: this.region.agent.agentID, + SessionID: this.region.circuit.sessionID + }; + msg.ObjectData = [ + { + ObjectLocalID: rootObj.ID + }, + { + ObjectLocalID: this.ID + } + ]; + return this.region.circuit.waitForAck(this.region.circuit.sendMessage(msg, PacketFlags.Reliable), 30000); + } + + public async linkFrom(objects: GameObject[]): Promise + { + if (objects.length === 0) + { + return; + } + const primsExpectingUpdate = new Map(); + const msg = new ObjectLinkMessage(); + msg.AgentData = { + AgentID: this.region.agent.agentID, + SessionID: this.region.circuit.sessionID + }; + msg.ObjectData = [ + { + ObjectLocalID: this.ID + } + ]; + primsExpectingUpdate.set(this.ID, this); + for (const obj of objects) + { + msg.ObjectData.push( + { + ObjectLocalID: obj.ID + }); + primsExpectingUpdate.set(obj.ID, obj); + } + await this.region.circuit.sendAndWaitForMessage(msg, PacketFlags.Reliable, Message.ObjectUpdate, 10000, (message: ObjectUpdateMessage) => + { + let match = false; + for (const obj of message.ObjectData) + { + const num = obj.ID; + if (primsExpectingUpdate.has(num)) + { + primsExpectingUpdate.delete(num); + match = true; + } + } + if (match) + { + if (primsExpectingUpdate.size === 0) + { + return FilterResponse.Finish; + } + return FilterResponse.Match; + } + return FilterResponse.NoMatch; + }); + } + + public async setDescription(desc: string): Promise + { + this.description = desc; + if (this.region === undefined) + { + return; + } + const msg = new ObjectDescriptionMessage(); + msg.AgentData = { + AgentID: this.region.agent.agentID, + SessionID: this.region.circuit.sessionID + }; + msg.ObjectData = [ + { + LocalID: this.ID, + Description: Utils.StringToBuffer(desc) + } + ]; + return this.region.circuit.waitForAck(this.region.circuit.sendMessage(msg, PacketFlags.Reliable), 10000); + } + + public async setTextureEntry(e: TextureEntry): Promise + { + this.TextureEntry = e; + if (this.region === undefined) + { + return; + } + + return this.setTextureAndMediaURL(); + } + + public async setTextureAndMediaURL(): Promise + { + const msg = new ObjectImageMessage(); + msg.AgentData = { + AgentID: this.region.agent.agentID, + SessionID: this.region.circuit.sessionID + }; + if (this.MediaURL === undefined) + { + this.MediaURL = ''; + } + if (this.TextureEntry === undefined) + { + this.TextureEntry = new TextureEntry(); + } + msg.ObjectData = [ + { + ObjectLocalID: this.ID, + TextureEntry: this.TextureEntry.toBuffer(), + MediaURL: Utils.StringToBuffer(this.MediaURL) + } + ]; + return this.region.circuit.waitForAck(this.region.circuit.sendMessage(msg, PacketFlags.Reliable), 10000); + } + + public async setExtraParams(ex: ExtraParams): Promise + { + this.extraParams = ex; + if (this.region === undefined) + { + return; + } + + // Set ExtraParams + const msg = new ObjectExtraParamsMessage(); + msg.AgentData = { + AgentID: this.region.agent.agentID, + SessionID: this.region.circuit.sessionID + }; + msg.ObjectData = []; + let params = 0; + if (ex.lightData !== null) + { + params++; + const data = ex.lightData.getBuffer(); + msg.ObjectData.push({ + ObjectLocalID: this.ID, + ParamType: ExtraParamType.Light, + ParamInUse: (ex.lightData.Intensity !== 0.0), + ParamData: data, + ParamSize: data.length + }); + } + if (ex.flexibleData !== null) + { + params++; + const data = ex.flexibleData.getBuffer(); + msg.ObjectData.push({ + ObjectLocalID: this.ID, + ParamType: ExtraParamType.Flexible, + ParamInUse: true, + ParamData: data, + ParamSize: data.length + }); + } + if (ex.lightImageData !== null) + { + params++; + const data = ex.lightImageData.getBuffer(); + msg.ObjectData.push({ + ObjectLocalID: this.ID, + ParamType: ExtraParamType.LightImage, + ParamInUse: true, + ParamData: data, + ParamSize: data.length + }); + } + if (ex.sculptData !== null) + { + params++; + const data = ex.sculptData.getBuffer(); + msg.ObjectData.push({ + ObjectLocalID: this.ID, + ParamType: ExtraParamType.Sculpt, + ParamInUse: true, + ParamData: data, + ParamSize: data.length + }); + } + if (ex.meshData !== null) + { + params++; + const data = ex.meshData.getBuffer(); + msg.ObjectData.push({ + ObjectLocalID: this.ID, + ParamType: ExtraParamType.Mesh, + ParamInUse: true, + ParamData: data, + ParamSize: data.length + }); + } + if (ex.reflectionProbeData != null) + { + params++; + const data = ex.reflectionProbeData.getBuffer(); + msg.ObjectData.push({ + ObjectLocalID: this.ID, + ParamType: ExtraParamType.ReflectionProbe, + ParamInUse: true, + ParamData: data, + ParamSize: data.length + }); + } + if (ex.renderMaterialData != null) + { + params++; + const data = ex.renderMaterialData.getBuffer(); + msg.ObjectData.push({ + ObjectLocalID: this.ID, + ParamType: ExtraParamType.RenderMaterial, + ParamInUse: true, + ParamData: data, + ParamSize: data.length + }); + } + if (params > 0) + { + const ack = this.region.circuit.sendMessage(msg, PacketFlags.Reliable); + await this.region.circuit.waitForAck(ack, 10000); + } + } + + // noinspection JSUnusedGlobalSymbols + public populateChildren(): void + { + this.region.objects.populateChildren(this); + } + + public async exportXMLElement(rootNode?: string, skipInventory = false, skipResolve = false): Promise + { + const document = builder.create('SceneObjectGroup'); + let linkNum = 1; + await this.getXML(document, this, linkNum, rootNode, skipInventory, skipResolve); + if (this.children && this.children.length > 0) + { + const otherParts = document.ele('OtherParts'); + + const children = [...this.children]; + children.sort((a, b) => b.dateReceived.getTime() - a.dateReceived.getTime()); + for (const child of children) + { + await child.getXML(otherParts, this, ++linkNum, (rootNode !== undefined) ? 'Part' : undefined, skipInventory, skipResolve); + } + } + return document; + } + + public async exportXML(rootNode?: string, skipInventory = false, skipResolve = false): Promise + { + return (await this.exportXMLElement(rootNode, skipInventory, skipResolve)).end({ pretty: true, allowEmpty: true }); + } + + // noinspection JSUnusedGlobalSymbols + public toJSON(): IGameObjectData + { + return { + deleted: this.deleted, + creatorID: this.creatorID, + creationDate: this.creationDate, + baseMask: this.baseMask, + ownerMask: this.ownerMask, + groupMask: this.groupMask, + everyoneMask: this.everyoneMask, + nextOwnerMask: this.nextOwnerMask, + ownershipCost: this.ownershipCost, + saleType: this.saleType, + salePrice: this.salePrice, + aggregatePerms: this.aggregatePerms, + aggregatePermTextures: this.aggregatePermTextures, + aggregatePermTexturesOwner: this.aggregatePermTexturesOwner, + category: this.category, + inventorySerial: this.inventorySerial, + itemID: this.itemID, + folderID: this.folderID, + fromTaskID: this.fromTaskID, + lastOwnerID: this.lastOwnerID, + name: this.name, + description: this.description, + touchName: this.touchName, + sitName: this.sitName, + resolvedAt: this.resolvedAt, + resolvedInventory: this.resolvedInventory, + totalChildren: this.totalChildren, + landImpact: this.landImpact, + calculatedLandImpact: this.calculatedLandImpact, + physicaImpact: this.physicaImpact, + resourceImpact: this.resourceImpact, + linkResourceImpact: this.linkResourceImpact, + linkPhysicsImpact: this.linkPhysicsImpact, + limitingType: this.limitingType, + children: this.children, + ID: this.ID, + FullID: this.FullID, + ParentID: this.ParentID, + OwnerID: this.OwnerID, + IsAttachment: this.IsAttachment, + NameValue: this.NameValue, + PCode: this.PCode, + State: this.State, + CRC: this.CRC, + Material: this.Material, + ClickAction: this.ClickAction, + Scale: this.Scale, + Flags: this.Flags, + PathCurve: this.PathCurve, + ProfileCurve: this.ProfileCurve, + PathBegin: this.PathBegin, + PathEnd: this.PathEnd, + PathScaleX: this.PathScaleX, + PathScaleY: this.PathScaleY, + PathShearX: this.PathShearX, + PathShearY: this.PathShearY, + PathTwist: this.PathTwist, + PathTwistBegin: this.PathTwistBegin, + PathRadiusOffset: this.PathRadiusOffset, + PathTaperX: this.PathTaperX, + PathTaperY: this.PathTaperY, + PathRevolutions: this.PathRevolutions, + PathSkew: this.PathSkew, + ProfileBegin: this.ProfileBegin, + ProfileEnd: this.ProfileEnd, + ProfileHollow: this.ProfileHollow, + TextureEntry: this.TextureEntry, + Text: this.Text, + TextColor: this.TextColor, + MediaURL: this.MediaURL, + JointType: this.JointType, + JointPivot: this.JointPivot, + JointAxisOrAnchor: this.JointAxisOrAnchor, + Position: this.Position, + Rotation: this.Rotation, + CollisionPlane: this.CollisionPlane, + Velocity: this.Velocity, + Acceleration: this.Acceleration, + AngularVelocity: this.AngularVelocity, + TreeSpecies: this.TreeSpecies, + Sound: this.Sound, + SoundGain: this.SoundGain, + SoundFlags: this.SoundFlags, + SoundRadius: this.SoundRadius, + Particles: this.Particles, + density: this.density, + friction: this.friction, + gravityMultiplier: this.gravityMultiplier, + physicsShapeType: this.physicsShapeType, + restitution: this.restitution + } + } + public setObjectData(data: Buffer): void + { + let dataPos = 0; + + // noinspection FallThroughInSwitchStatementJS, TsLint + switch (data.length) + { + case 76: + // Avatar collision normal; + this.CollisionPlane = new Vector4(data, dataPos); + dataPos += 16; + // falls through + case 60: + // Position + this.Position = new Vector3(data, dataPos); + dataPos += 12; + this.Velocity = new Vector3(data, dataPos); + dataPos += 12; + this.Acceleration = new Vector3(data, dataPos); + dataPos += 12; + this.Rotation = new Quaternion(data, dataPos); + dataPos += 12; + this.AngularVelocity = new Vector3(data, dataPos); + dataPos += 12; + break; + case 48: + this.CollisionPlane = new Vector4(data, dataPos); + dataPos += 16; + // falls through + case 32: + this.Position = new Vector3([ + Utils.UInt16ToFloat(data.readUInt16LE(dataPos), -0.5 * 256.0, 1.5 * 256.0), + Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 2), -0.5 * 256.0, 1.5 * 256.0), + Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 4), -256.0, 3.0 * 256.0) + ]); + dataPos += 6; + this.Velocity = new Vector3([ + Utils.UInt16ToFloat(data.readUInt16LE(dataPos), -256.0, 256.0), + Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 2), -256.0, 256.0), + Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 4), -256.0, 256.0) + ]); + dataPos += 6; + this.Acceleration = new Vector3([ + Utils.UInt16ToFloat(data.readUInt16LE(dataPos), -256.0, 256.0), + Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 2), -256.0, 256.0), + Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 4), -256.0, 256.0) + ]); + dataPos += 6; + this.Rotation = new Quaternion([ + Utils.UInt16ToFloat(data.readUInt16LE(dataPos), -1.0, 1.0), + Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 2), -1.0, 1.0), + Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 4), -1.0, 1.0), + Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 4), -1.0, 1.0) + ]); + dataPos += 8; + this.AngularVelocity = new Vector3([ + Utils.UInt16ToFloat(data.readUInt16LE(dataPos), -256.0, 256.0), + Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 2), -256.0, 256.0), + Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 4), -256.0, 256.0) + ]); + dataPos += 6; + break; + case 16: + this.Position = new Vector3([ + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0) + ]); + this.Velocity = new Vector3([ + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0) + ]); + this.Acceleration = new Vector3([ + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0) + ]); + this.Rotation = new Quaternion([ + Utils.ByteToFloat(data.readUInt8(dataPos++), -1.0, 1.0), + Utils.ByteToFloat(data.readUInt8(dataPos++), -1.0, 1.0), + Utils.ByteToFloat(data.readUInt8(dataPos++), -1.0, 1.0), + Utils.ByteToFloat(data.readUInt8(dataPos++), -1.0, 1.0) + ]); + this.AngularVelocity = new Vector3([ + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0) + ]); + break; + } + } + + // noinspection JSUnusedGlobalSymbols + public async deRezObject(destination: DeRezDestination, transactionID: UUID, destFolder: UUID): Promise + { + return GameObject.deRezObjects(this.region, [this], destination, transactionID, destFolder); + } + + // noinspection JSUnusedGlobalSymbols + public async takeToInventory(folder?: InventoryFolder): Promise + { + return GameObject.takeManyToInventory(this.region, [this], folder); + } + + public async dropInventoryIntoContents(inventoryItem: InventoryItem | UUID): Promise + { + const transactionID = UUID.zero(); + + if (inventoryItem instanceof UUID) + { + + const item: InventoryItem | null = await this.region.agent.inventory.fetchInventoryItem(inventoryItem); + if (item === null) + { + throw new Error('Failed to drop inventory into object contents - Inventory item ' + inventoryItem.toString() + ' not found'); + } + inventoryItem = item; + } + + const msg = new UpdateTaskInventoryMessage(); + msg.AgentData = { + AgentID: this.region.agent.agentID, + SessionID: this.region.circuit.sessionID + }; + msg.UpdateData = { + Key: 0, + LocalID: this.ID + }; + msg.InventoryData = { + ItemID: inventoryItem.itemID, + FolderID: inventoryItem.parentID, + CreatorID: inventoryItem.permissions.creator, + OwnerID: this.region.agent.agentID, + GroupID: inventoryItem.permissions.group, + BaseMask: inventoryItem.permissions.baseMask, + OwnerMask: inventoryItem.permissions.ownerMask, + GroupMask: inventoryItem.permissions.groupMask, + EveryoneMask: inventoryItem.permissions.everyoneMask, + NextOwnerMask: inventoryItem.permissions.nextOwnerMask, + GroupOwned: inventoryItem.permissions.groupOwned ?? false, + TransactionID: transactionID, + Type: inventoryItem.type, + InvType: inventoryItem.inventoryType, + Flags: inventoryItem.flags, + SaleType: inventoryItem.saleType, + SalePrice: inventoryItem.salePrice, + Name: Utils.StringToBuffer(inventoryItem.name), + Description: Utils.StringToBuffer(inventoryItem.description), + CreationDate: inventoryItem.created.getTime() / 1000, + CRC: inventoryItem.getCRC() + }; + const serial = this.inventorySerial; + this.region.circuit.sendMessage(msg, PacketFlags.Reliable); + return this.waitForInventoryUpdate(serial); + } + + public async waitForInventoryUpdate(inventorySerial?: number): Promise + { + // We need to select the object, or we won't get the objectProperties message + await this.deselect(); + void this.select(); + await this.region.circuit.waitForMessage(Message.ObjectProperties, 10000, (message: ObjectPropertiesMessage) => + { + for (const obj of message.ObjectData) + { + if (obj.ObjectID.equals(this.FullID)) + { + if (inventorySerial === undefined) + { + inventorySerial = this.inventorySerial; + } + if (obj.InventorySerial > inventorySerial) + { + return FilterResponse.Finish; + } + } + } + return FilterResponse.NoMatch; + }); + await this.deselect(); + } + + public async select(): Promise + { + const selectObject = new ObjectSelectMessage(); + selectObject.AgentData = { + AgentID: this.region.agent.agentID, + SessionID: this.region.circuit.sessionID + }; + selectObject.ObjectData = [{ + ObjectLocalID: this.ID + }]; + const ack = this.region.circuit.sendMessage(selectObject, PacketFlags.Reliable); + return this.region.circuit.waitForAck(ack, 10000); + } + + public async deselect(): Promise + { + const deselectObject = new ObjectDeselectMessage(); + deselectObject.AgentData = { + AgentID: this.region.agent.agentID, + SessionID: this.region.circuit.sessionID + }; + deselectObject.ObjectData = [{ + ObjectLocalID: this.ID + }]; + const ack = this.region.circuit.sendMessage(deselectObject, PacketFlags.Reliable); + return this.region.circuit.waitForAck(ack, 10000); + } + + // noinspection JSUnusedGlobalSymbols + public async removeTaskInventory(itemKey: UUID): Promise; + // noinspection JSUnusedGlobalSymbols + public async removeTaskInventory(itemName: string): Promise; + // noinspection JSUnusedGlobalSymbols + public async removeTaskInventory(item: string | UUID): Promise + { + if (typeof item === 'string') + { + for(const invItem of this.inventory) + { + if (invItem.name === item) + { + item = invItem.itemID; + break; + } + } + } + if (typeof item === 'string') + { + throw new Error('Task inventory item not found'); + } + + const msg = new RemoveTaskInventoryMessage(); + msg.AgentData = { + AgentID: this.region.agent.agentID, + SessionID: this.region.circuit.sessionID + }; + msg.InventoryData = { + LocalID: this.ID, + ItemID: item + }; + this.region.circuit.sendMessage(msg, PacketFlags.Reliable); + await this.waitForInventoryUpdate(this.inventorySerial); + } private static partFromXMLJS(obj: any, isRoot: boolean): GameObject { const go = new GameObject(); go.Flags = 0; - let prop: any; + let prop: any = ''; if (Utils.getFromXMLJS(obj, 'AllowedDrop') !== undefined) { go.Flags = go.Flags | PrimFlags.AllowInventoryDrop; @@ -383,9 +1532,9 @@ export class GameObject implements IGameObjectData const buf = Buffer.from(prop, 'base64'); go.TextureEntry = TextureEntry.from(buf); } - if (go.TextureEntry && shape['MatOvrd'] && Array.isArray(shape['MatOvrd']) && shape['MatOvrd'].length > 0) + if (go.TextureEntry && shape.MatOvrd !== undefined && Array.isArray(shape.MatOvrd) && shape.MatOvrd.length > 0) { - const tex = Buffer.from(shape['MatOvrd'][0], 'base64'); + const tex = Buffer.from(shape.MatOvrd[0], 'base64'); let pos = 0; const entryCount = tex.readUInt8(pos++); for (let x = 0; x < entryCount; x++) @@ -393,7 +1542,7 @@ export class GameObject implements IGameObjectData const te_index = tex.readUInt8(pos++); const len = tex.readUInt16LE(pos++); pos++; - const json = tex.slice(pos, pos + len).toString('utf-8'); + const json = tex.subarray(pos, pos + len).toString('utf-8'); pos = pos + len; go.TextureEntry.gltfMaterialOverrides.set(te_index, LLGLTFMaterialOverride.fromFullMaterialJSON(json)); } @@ -478,7 +1627,7 @@ export class GameObject implements IGameObjectData } if ((prop = Utils.getFromXMLJS(shape, 'ProfileShape')) !== undefined) { - if (!go.ProfileCurve) + if (go.ProfileCurve === undefined) { go.ProfileCurve = 0; } @@ -486,7 +1635,7 @@ export class GameObject implements IGameObjectData } if ((prop = Utils.getFromXMLJS(shape, 'HollowShape')) !== undefined) { - if (!go.ProfileCurve) + if (go.ProfileCurve === undefined) { go.ProfileCurve = 0; } @@ -500,10 +1649,11 @@ export class GameObject implements IGameObjectData const id = UUID.fromXMLJS(shape, 'SculptTexture'); if (id instanceof UUID) { - if (!go.extraParams) + if (go.extraParams === undefined) { go.extraParams = new ExtraParams(); } + // noinspection JSBitwiseOperatorUsage if (type & SculptType.Mesh) { go.extraParams.setMeshData(type, id); @@ -517,28 +1667,43 @@ export class GameObject implements IGameObjectData } if (Utils.getFromXMLJS(shape, 'FlexiEntry') !== undefined) { - const flexiSoftness = Utils.getFromXMLJS(shape, 'FlexiSoftness'); - const flexiTension = Utils.getFromXMLJS(shape, 'FlexiTension'); - const flexiDrag = Utils.getFromXMLJS(shape, 'FlexiDrag'); - const flexiGravity = Utils.getFromXMLJS(shape, 'FlexiGravity'); - const flexiWind = Utils.getFromXMLJS(shape, 'FlexiWind'); - const flexiForceX = Utils.getFromXMLJS(shape, 'FlexiForceX'); - const flexiForceY = Utils.getFromXMLJS(shape, 'FlexiForceY'); - const flexiForceZ = Utils.getFromXMLJS(shape, 'FlexiForceZ'); + const flexiSoftness = Utils.getFromXMLJS(shape, 'FlexiSoftness') as number | false; + const flexiTension = Utils.getFromXMLJS(shape, 'FlexiTension') as number | false; + const flexiDrag = Utils.getFromXMLJS(shape, 'FlexiDrag') as number | false; + const flexiGravity = Utils.getFromXMLJS(shape, 'FlexiGravity') as number | false; + const flexiWind = Utils.getFromXMLJS(shape, 'FlexiWind') as number | false; + const flexiForceX = Utils.getFromXMLJS(shape, 'FlexiForceX') as number | false; + const flexiForceY = Utils.getFromXMLJS(shape, 'FlexiForceY') as number | false; + const flexiForceZ = Utils.getFromXMLJS(shape, 'FlexiForceZ') as number | false; if (flexiSoftness !== false && flexiTension !== false && - flexiDrag && false && + flexiDrag !== false && flexiGravity !== false && flexiWind !== false && flexiForceX !== false && flexiForceY !== false && flexiForceZ !== false) { - if (!go.extraParams) + let forceX = Number(flexiForceX); + let forceY = Number(flexiForceY); + let forceZ = Number(flexiForceZ); + if (isNaN(forceX)) + { + forceX = 0; + } + if (isNaN(forceY)) + { + forceY = 0; + } + if (isNaN(forceZ)) + { + forceZ = 0; + } + if (go.extraParams === undefined) { go.extraParams = new ExtraParams(); } - go.extraParams.setFlexiData(flexiSoftness, flexiTension, flexiDrag, flexiGravity, flexiWind, new Vector3([flexiForceX, flexiForceY, flexiForceZ])); + go.extraParams.setFlexiData(flexiSoftness, flexiTension, flexiDrag, flexiGravity, flexiWind, new Vector3([forceX, forceY, forceZ])); } } if (Utils.getFromXMLJS(shape, 'LightEntry') !== undefined) @@ -560,7 +1725,7 @@ export class GameObject implements IGameObjectData lightFalloff !== false && lightIntensity !== false) { - if (!go.extraParams) + if (go.extraParams === undefined) { go.extraParams = new ExtraParams(); } @@ -581,12 +1746,12 @@ export class GameObject implements IGameObjectData } if ((prop = Utils.getFromXMLJS(obj, 'TaskInventory')) !== undefined) { - if (prop.TaskInventoryItem) + if (prop.TaskInventoryItem !== undefined) { for (const invItemXML of prop.TaskInventoryItem) { const invItem = new InventoryItem(go); - let subProp: any; + let subProp: any = ''; if ((subProp = UUID.fromXMLJS(invItemXML, 'AssetID')) !== undefined) { invItem.assetID = subProp; @@ -667,6 +1832,14 @@ export class GameObject implements IGameObjectData { invItem.permsGranter = subProp; } + if ((subProp = Utils.getFromXMLJS(invItemXML, 'ScriptRunning')) !== undefined) + { + invItem.scriptRunning = subProp === true; + } + if ((subProp = Utils.getFromXMLJS(invItemXML, 'ScriptMono')) !== undefined) + { + invItem.scriptMono = subProp === true; + } go.inventory.push(invItem); } } @@ -674,387 +1847,6 @@ export class GameObject implements IGameObjectData return go; } - static async fromXML(xml: string | any): Promise - { - let result; - if (typeof xml === 'string') - { - const parsed = await Utils.parseXML(xml); - if (!parsed['SceneObjectGroup']) - { - throw new Error('SceneObjectGroup not found'); - } - result = parsed['SceneObjectGroup']; - } - else - { - result = xml; - } - - let rootPartXML; - if (result['SceneObjectPart']) - { - rootPartXML = result['SceneObjectPart']; - } - else if (result['RootPart'] && result['RootPart'][0] && result['RootPart'][0]['SceneObjectPart']) - { - rootPartXML = result['RootPart'][0]['SceneObjectPart']; - } - else - { - throw new Error('Root part not found'); - } - - const rootPart = GameObject.partFromXMLJS(rootPartXML[0], true); - rootPart.children = []; - rootPart.totalChildren = 0; - if (result['OtherParts'] && Array.isArray(result['OtherParts']) && result['OtherParts'].length > 0) - { - const obj = result['OtherParts'][0]; - if (obj['SceneObjectPart'] || obj['Part']) - { - if (obj['Part']) - { - for (const part of obj['Part']) - { - rootPart.children.push(GameObject.partFromXMLJS(part['SceneObjectPart'][0], false)); - rootPart.totalChildren++; - } - } - else - { - for (const part of obj['SceneObjectPart']) - { - rootPart.children.push(GameObject.partFromXMLJS(part, false)); - rootPart.totalChildren++; - } - } - } - } - return rootPart; - } - - static async deRezObjects(region: Region, objects: GameObject[], destination: DeRezDestination, transactionID: UUID, destFolder: UUID): Promise - { - const msg = new DeRezObjectMessage(); - - msg.AgentData = { - AgentID: region.agent.agentID, - SessionID: region.circuit.sessionID - }; - msg.AgentBlock = { - GroupID: UUID.zero(), - Destination: destination, - DestinationID: destFolder, - TransactionID: transactionID, - PacketCount: 1, - PacketNumber: 1 - }; - msg.ObjectData = []; - for (const obj of objects) - { - msg.ObjectData.push({ - ObjectLocalID: obj.ID - }); - } - const ack = region.circuit.sendMessage(msg, PacketFlags.Reliable); - return region.circuit.waitForAck(ack, 10000); - } - - static takeManyToInventory(region: Region, objects: GameObject[], folder?: InventoryFolder): Promise - { - const transactionID = UUID.random(); - let enforceFolder = true; - if (folder === undefined) - { - enforceFolder = false; - folder = region.agent.inventory.getRootFolderMain(); - } - return new Promise((resolve, reject) => - { - - region.circuit.waitForMessage(Message.UpdateCreateInventoryItem, 10000, (message: UpdateCreateInventoryItemMessage) => - { - for (const inv of message.InventoryData) - { - const name = Utils.BufferToStringSimple(inv.Name); - if (name === objects[0].name) - { - return FilterResponse.Finish; - } - } - return FilterResponse.NoMatch; - }).then((createInventoryMsg: UpdateCreateInventoryItemMessage) => - { - for (const inv of createInventoryMsg.InventoryData) - { - const name = Utils.BufferToStringSimple(inv.Name); - if (name === objects[0].name) - { - const itemID = inv.ItemID; - region.agent.inventory.fetchInventoryItem(itemID).then((item: InventoryItem | null) => - { - if (item === null) - { - reject(new Error('Inventory item was unable to be retrieved after take to inventory')); - } - else - { - if (enforceFolder && folder !== undefined && !item.parentID.equals(folder.folderID)) - { - item.moveToFolder(folder).then(() => - { - resolve(item); - }).catch((err: Error) => - { - console.error('Error moving item to correct folder'); - console.error(err); - resolve(item); - }); - } - else - { - resolve(item); - } - } - }).catch((err) => - { - reject(err); - }); - return; - } - } - }).catch(() => - { - reject(new Error('Timed out waiting for UpdateCreateInventoryItem')); - }); - if (folder !== undefined) - { - GameObject.deRezObjects(region, objects, DeRezDestination.AgentInventoryTake, transactionID, folder.folderID).then(() => - { - }).catch((err) => - { - console.error(err); - }); - } - }); - } - - constructor() - { - this.Position = Vector3.getZero(); - this.Rotation = Quaternion.getIdentity(); - this.AngularVelocity = Vector3.getZero(); - this.TreeSpecies = 0; - this.SoundFlags = 0; - this.SoundRadius = 1.0; - this.SoundGain = 1.0; - } - - async waitForTextureUpdate(timeout?: number): Promise - { - return Utils.waitOrTimeOut(this.onTextureUpdate, timeout); - } - - async rezScript(name: string, description: string, perms: PermissionMask = 532480 as PermissionMask): Promise - { - const rezScriptMsg = new RezScriptMessage(); - rezScriptMsg.AgentData = { - AgentID: this.region.agent.agentID, - SessionID: this.region.circuit.sessionID, - GroupID: this.region.agent.activeGroupID - }; - rezScriptMsg.UpdateBlock = { - ObjectLocalID: this.ID, - Enabled: true - }; - const tmpName = uuid.v4(); - const invItem = new InventoryItem(this); - invItem.itemID = UUID.zero(); - invItem.parentID = this.FullID; - invItem.permissions.creator = this.region.agent.agentID; - invItem.permissions.group = UUID.zero(); - invItem.permissions.baseMask = PermissionMask.All; - invItem.permissions.ownerMask = PermissionMask.All; - invItem.permissions.groupMask = 0; - invItem.permissions.everyoneMask = 0; - invItem.permissions.nextOwnerMask = perms; - invItem.permissions.groupOwned = false; - invItem.type = AssetType.LSLText; - invItem.inventoryType = InventoryType.LSL; - invItem.flags = 0; - invItem.salePrice = this.salePrice || 10; - invItem.saleType = this.saleType || 0; - invItem.name = tmpName; - invItem.description = description; - invItem.created = new Date(); - - rezScriptMsg.InventoryBlock = { - ItemID: UUID.zero(), - FolderID: this.FullID, - CreatorID: this.region.agent.agentID, - OwnerID: this.region.agent.agentID, - GroupID: UUID.zero(), - BaseMask: PermissionMask.All, - OwnerMask: PermissionMask.All, - GroupMask: 0, - EveryoneMask: 0, - NextOwnerMask: perms, - GroupOwned: false, - TransactionID: UUID.zero(), - Type: AssetType.LSLText, - InvType: InventoryType.LSL, - Flags: 0, - SaleType: this.saleType || 0, - SalePrice: this.salePrice || 10, - Name: Utils.StringToBuffer(tmpName), - Description: Utils.StringToBuffer(description), - CreationDate: Math.floor(invItem.created.getTime() / 1000), - CRC: invItem.getCRC() - }; - await this.region.circuit.waitForAck(this.region.circuit.sendMessage(rezScriptMsg, PacketFlags.Reliable), 10000); - await this.updateInventory(); - for (const item of this.inventory) - { - if (item.name === tmpName) - { - // We are intentionally not waiting for this rename job so that the wait below succeeds - item.renameInTask(this, name).then(() => - { - - }).catch(() => - { - - }); - try - { - await this.waitForInventoryUpdate(); - } - catch (error) - { - - } - await this.updateInventory(); - for (const newItem of this.inventory) - { - if (newItem.itemID.equals(item.itemID)) - { - return newItem; - } - } - return item; - } - } - throw new Error('Failed to add script to object'); - } - - public async updateInventory(): Promise - { - if (this.PCode === PCode.Avatar) - { - return; - } - - try - { - const capURL = await this.region.caps.getCapability('RequestTaskInventory'); - const result = await this.region.caps.capsPerformXMLGet(capURL + '?task_id=' + this.FullID) as { - contents?: { - asset_id?: string, - created_at?: number, - desc?: string, - flags?: number, - inv_type?: string, - item_id?: string, - metadata?: Record, - name?: string, - parent_id?: string; - permissions?: { - base_mask?: number; - creator_id?: string; - everyone_mask?: number; - group_id?: string; - group_mask?: number; - is_owner_group?: boolean; - last_owner_id?: string; - next_owner_mask?: number; - owner_id?: string; - owner_mask?: number; - } - sale_info?: { - sale_price?: number; - sale_type?: string; - } - type?: string - }[] - }; - - if (result.contents) - { - this.inventory = []; - for(const item of result.contents) - { - const invItem = new InventoryItem(this, this.region.agent); - invItem.assetID = new UUID(item.asset_id); - invItem.created = new Date((item.created_at ?? 0) * 1000); - invItem.description = item.desc ?? ''; - invItem.flags = item.flags ?? 0; - invItem.inventoryType = Utils.HTTPAssetTypeToInventoryType(item.inv_type ?? ''); - invItem.itemID = new UUID(item.item_id); - invItem.name = item.name ?? ''; - invItem.parentID = new UUID(item.parent_id); - invItem.permissions = { - baseMask: item.permissions?.base_mask ?? 0, - creator: new UUID(item.permissions?.creator_id), - everyoneMask: item.permissions?.everyone_mask ?? 0, - group: new UUID(item.permissions?.group_id), - groupMask: item.permissions?.group_mask ?? 0, - groupOwned: item.permissions?.is_owner_group ?? false, - lastOwner: new UUID(item.permissions?.last_owner_id), - nextOwnerMask: item.permissions?.next_owner_mask ?? 0, - owner: new UUID(item.permissions?.owner_id), - ownerMask: item.permissions?.owner_mask ?? 0 - } - invItem.salePrice = item.sale_info?.sale_price ?? 0; - switch (item.sale_info?.sale_type) - { - case 'not': - invItem.saleType = 0; - break; - case 'orig': - invItem.saleType = 1; - break; - case 'copy': - invItem.saleType = 2; - break; - case 'cntn': - invItem.saleType = 3; - break; - } - invItem.type = Utils.capInventoryTypeToAssetType(item.inv_type ?? ''); - this.inventory.push(invItem); - } - return; - } - } - catch(e) - { - // ignore - } - - - const req = new RequestTaskInventoryMessage(); - req.AgentData = { - AgentID: this.region.agent.agentID, - SessionID: this.region.circuit.sessionID - }; - req.InventoryData = { - LocalID: this.ID - }; - this.region.circuit.sendMessage(req, PacketFlags.Reliable); - return this.waitForTaskInventory(); - } - private async waitForTaskInventory(): Promise { const inventory = await this.region.circuit.waitForMessage(Message.ReplyTaskInventory, 10000, (message: ReplyTaskInventoryMessage): FilterResponse => @@ -1159,393 +1951,6 @@ export class GameObject implements IGameObjectData } } - hasNameValueEntry(key: string): boolean - { - return this.NameValue[key] !== undefined; - } - - getNameValueEntry(key: string): string - { - if (this.NameValue[key]) - { - return this.NameValue[key].value; - } - return ''; - } - - setIfDefined(def?: number, v?: number): number - { - if (def === undefined) - { - def = 0; - } - if (v === undefined) - { - return def; - } - else - { - return v; - } - } - - async setShape(PathCurve?: number, - ProfileCurve?: number, - PathBegin?: number, - PathEnd?: number, - PathScaleX?: number, - PathScaleY?: number, - PathShearX?: number, - PathShearY?: number, - PathTwist?: number, - PathTwistBegin?: number, - PathRadiusOffset?: number, - PathTaperX?: number, - PathTaperY?: number, - PathRevolutions?: number, - PathSkew?: number, - ProfileBegin?: number, - ProfileEnd?: number, - ProfileHollow?: number): Promise - { - this.PathCurve = this.setIfDefined(this.PathCurve, PathCurve); - this.ProfileCurve = this.setIfDefined(this.ProfileCurve, ProfileCurve); - this.PathBegin = this.setIfDefined(this.PathBegin, PathBegin); - this.PathEnd = this.setIfDefined(this.PathEnd, PathEnd); - this.PathScaleX = this.setIfDefined(this.PathScaleX, PathScaleX); - this.PathScaleY = this.setIfDefined(this.PathScaleY, PathScaleY); - this.PathShearX = this.setIfDefined(this.PathShearX, PathShearX); - this.PathShearY = this.setIfDefined(this.PathShearY, PathShearY); - this.PathTwist = this.setIfDefined(this.PathTwist, PathTwist); - this.PathTwistBegin = this.setIfDefined(this.PathTwistBegin, PathTwistBegin); - this.PathRadiusOffset = this.setIfDefined(this.PathRadiusOffset, PathRadiusOffset); - this.PathTaperX = this.setIfDefined(this.PathTaperX, PathTaperX); - this.PathTaperY = this.setIfDefined(this.PathTaperY, PathTaperY); - this.PathRevolutions = this.setIfDefined(this.PathRevolutions, PathRevolutions); - this.PathSkew = this.setIfDefined(this.PathSkew, PathSkew); - this.ProfileBegin = this.setIfDefined(this.ProfileBegin, ProfileBegin); - this.ProfileEnd = this.setIfDefined(this.ProfileEnd, ProfileEnd); - this.ProfileHollow = this.setIfDefined(this.ProfileHollow, ProfileHollow); - if (!this.region) - { - return; - } - const msg = new ObjectShapeMessage(); - msg.AgentData = { - AgentID: this.region.agent.agentID, - SessionID: this.region.circuit.sessionID - }; - msg.ObjectData = [ - { - ObjectLocalID: this.ID, - PathCurve: this.PathCurve, - ProfileCurve: this.ProfileCurve, - PathBegin: Utils.packBeginCut(this.PathBegin), - PathEnd: Utils.packEndCut(this.PathEnd), - PathScaleX: Utils.packPathScale(this.PathScaleX), - PathScaleY: Utils.packPathScale(this.PathScaleY), - PathShearX: Utils.packPathShear(this.PathShearX), - PathShearY: Utils.packPathShear(this.PathShearY), - PathTwist: Utils.packPathTwist(this.PathTwist), - PathTwistBegin: Utils.packPathTwist(this.PathTwistBegin), - PathRadiusOffset: Utils.packPathTwist(this.PathRadiusOffset), - PathTaperX: Utils.packPathTaper(this.PathTaperX), - PathTaperY: Utils.packPathTaper(this.PathTaperY), - PathRevolutions: Utils.packPathRevolutions(this.PathRevolutions), - PathSkew: Utils.packPathTwist(this.PathSkew), - ProfileBegin: Utils.packBeginCut(this.ProfileBegin), - ProfileEnd: Utils.packEndCut(this.ProfileEnd), - ProfileHollow: Utils.packProfileHollow(this.ProfileHollow) - } - ]; - return this.region.circuit.waitForAck(this.region.circuit.sendMessage(msg, PacketFlags.Reliable), 10000); - } - - async setName(name: string): Promise - { - this.name = name; - if (!this.region) - { - return; - } - const msg = new ObjectNameMessage(); - msg.AgentData = { - AgentID: this.region.agent.agentID, - SessionID: this.region.circuit.sessionID - }; - msg.ObjectData = [ - { - LocalID: this.ID, - Name: Utils.StringToBuffer(name) - } - ]; - return this.region.circuit.waitForAck(this.region.circuit.sendMessage(msg, PacketFlags.Reliable), 10000); - } - - async setGeometry(pos?: Vector3, rot?: Quaternion, scale?: Vector3, wholeLinkset: boolean = false): Promise - { - const data = []; - const linked = (wholeLinkset) ? UpdateType.Linked : 0; - if (pos !== undefined) - { - this.Position = pos; - data.push({ - ObjectLocalID: this.ID, - Type: UpdateType.Position | linked, - Data: pos.getBuffer() - }); - } - if (rot !== undefined) - { - this.Rotation = rot; - data.push({ - ObjectLocalID: this.ID, - Type: UpdateType.Rotation | linked, - Data: rot.getBuffer() - }) - } - if (scale !== undefined) - { - this.Scale = scale; - data.push({ - ObjectLocalID: this.ID, - Type: UpdateType.Scale | linked, - Data: scale.getBuffer() - }) - } - if (!this.region || data.length === 0) - { - return; - } - const msg = new MultipleObjectUpdateMessage(); - msg.AgentData = { - AgentID: this.region.agent.agentID, - SessionID: this.region.circuit.sessionID - }; - msg.ObjectData = data; - return this.region.circuit.waitForAck(this.region.circuit.sendMessage(msg, PacketFlags.Reliable), 30000); - } - - linkTo(rootObj: GameObject): Promise - { - const msg = new ObjectLinkMessage(); - msg.AgentData = { - AgentID: this.region.agent.agentID, - SessionID: this.region.circuit.sessionID - }; - msg.ObjectData = [ - { - ObjectLocalID: rootObj.ID - }, - { - ObjectLocalID: this.ID - } - ]; - return this.region.circuit.waitForAck(this.region.circuit.sendMessage(msg, PacketFlags.Reliable), 30000); - } - - async linkFrom(objects: GameObject[]): Promise - { - return new Promise((resolve, reject) => - { - if (objects.length === 0) - { - resolve(); - return; - } - const primsExpectingUpdate: { [key: number]: GameObject } = {}; - const msg = new ObjectLinkMessage(); - msg.AgentData = { - AgentID: this.region.agent.agentID, - SessionID: this.region.circuit.sessionID - }; - msg.ObjectData = [ - { - ObjectLocalID: this.ID - } - ]; - primsExpectingUpdate[this.ID] = this; - for (const obj of objects) - { - msg.ObjectData.push( - { - ObjectLocalID: obj.ID - }); - primsExpectingUpdate[obj.ID] = obj; - } - this.region.circuit.waitForMessage(Message.ObjectUpdate, 10000, (message: ObjectUpdateMessage) => - { - let match = false; - for (const obj of message.ObjectData) - { - const num = obj.ID; - if (primsExpectingUpdate[num] !== undefined) - { - delete primsExpectingUpdate[num]; - match = true; - } - } - if (match) - { - if (Object.keys(primsExpectingUpdate).length === 0) - { - return FilterResponse.Finish; - } - return FilterResponse.Match; - } - return FilterResponse.NoMatch; - }).then(() => - { - resolve(); - }).catch((err) => - { - reject(err); - }); - this.region.circuit.sendMessage(msg, PacketFlags.Reliable); - }); - - } - - async setDescription(desc: string): Promise - { - this.description = desc; - if (!this.region) - { - return; - } - const msg = new ObjectDescriptionMessage(); - msg.AgentData = { - AgentID: this.region.agent.agentID, - SessionID: this.region.circuit.sessionID - }; - msg.ObjectData = [ - { - LocalID: this.ID, - Description: Utils.StringToBuffer(desc) - } - ]; - return this.region.circuit.waitForAck(this.region.circuit.sendMessage(msg, PacketFlags.Reliable), 10000); - } - - async setTextureEntry(e: TextureEntry): Promise - { - this.TextureEntry = e; - if (!this.region) - { - return; - } - - return this.setTextureAndMediaURL(); - } - - setTextureAndMediaURL(): Promise - { - const msg = new ObjectImageMessage(); - msg.AgentData = { - AgentID: this.region.agent.agentID, - SessionID: this.region.circuit.sessionID - }; - if (this.MediaURL === undefined) - { - this.MediaURL = ''; - } - if (this.TextureEntry === undefined) - { - this.TextureEntry = new TextureEntry(); - } - msg.ObjectData = [ - { - ObjectLocalID: this.ID, - TextureEntry: this.TextureEntry.toBuffer(), - MediaURL: Utils.StringToBuffer(this.MediaURL) - } - ]; - return this.region.circuit.waitForAck(this.region.circuit.sendMessage(msg, PacketFlags.Reliable), 10000); - } - - async setExtraParams(ex: ExtraParams): Promise - { - this.extraParams = ex; - if (!this.region) - { - return; - } - - // Set ExtraParams - const msg = new ObjectExtraParamsMessage(); - msg.AgentData = { - AgentID: this.region.agent.agentID, - SessionID: this.region.circuit.sessionID - }; - msg.ObjectData = []; - let params = 0; - if (ex.lightData !== null) - { - params++; - const data = ex.lightData.getBuffer(); - msg.ObjectData.push({ - ObjectLocalID: this.ID, - ParamType: ExtraParamType.Light, - ParamInUse: (ex.lightData.Intensity !== 0.0), - ParamData: data, - ParamSize: data.length - }); - } - if (ex.flexibleData !== null) - { - params++; - const data = ex.flexibleData.getBuffer(); - msg.ObjectData.push({ - ObjectLocalID: this.ID, - ParamType: ExtraParamType.Flexible, - ParamInUse: true, - ParamData: data, - ParamSize: data.length - }); - } - if (ex.lightImageData !== null) - { - params++; - const data = ex.lightImageData.getBuffer(); - msg.ObjectData.push({ - ObjectLocalID: this.ID, - ParamType: ExtraParamType.LightImage, - ParamInUse: true, - ParamData: data, - ParamSize: data.length - }); - } - if (ex.sculptData !== null) - { - params++; - const data = ex.sculptData.getBuffer(); - msg.ObjectData.push({ - ObjectLocalID: this.ID, - ParamType: ExtraParamType.Sculpt, - ParamInUse: true, - ParamData: data, - ParamSize: data.length - }); - } - if (ex.meshData !== null) - { - params++; - const data = ex.meshData.getBuffer(); - msg.ObjectData.push({ - ObjectLocalID: this.ID, - ParamType: ExtraParamType.Mesh, - ParamInUse: true, - ParamData: data, - ParamSize: data.length - }); - } - if (params > 0) - { - const ack = this.region.circuit.sendMessage(msg, PacketFlags.Reliable); - return this.region.circuit.waitForAck(ack, 10000); - } - } - private async getInventoryXML(xml: XMLNode, inv: InventoryItem): Promise { if (!inv.assetID.isZero() || !inv.itemID.isZero()) @@ -1560,7 +1965,7 @@ export class GameObject implements IGameObjectData UUID.getXML(item.ele('AssetID'), inv.assetID); UUID.getXML(item.ele('ItemID'), inv.itemID); - if (inv.permissions) + if (inv.permissions !== undefined) { item.ele('BasePermissions', inv.permissions.baseMask); item.ele('EveryonePermissions', inv.permissions.everyoneMask); @@ -1583,7 +1988,7 @@ export class GameObject implements IGameObjectData { try { - const type = (inv.type === 5 ? HTTPAssets.ASSET_CLOTHING : HTTPAssets.ASSET_BODYPART); + const type = (inv.type === AssetType.Clothing) ? AssetType.Clothing : AssetType.Bodypart; const data = await this.region.clientCommands.asset.downloadAsset(type, inv.assetID); const wearable: LLWearable = new LLWearable(data.toString('utf-8')); inv.flags = inv.flags | wearable.type; @@ -1599,6 +2004,11 @@ export class GameObject implements IGameObjectData UUID.getXML(item.ele('ParentPartID'), this.FullID); item.ele('Type', inv.type); item.ele('Name', inv.name); + if (inv.type === AssetType.LSLText) + { + item.ele('ScriptRunning', inv.scriptRunning); + item.ele('ScriptMono', inv.scriptMono); + } } } @@ -1607,7 +2017,7 @@ export class GameObject implements IGameObjectData if (!skipResolve) { const resolver = this.region?.resolver; - if (resolver) + if (resolver !== undefined) { if (this.resolvedAt === undefined) { @@ -1646,11 +2056,12 @@ export class GameObject implements IGameObjectData } let root = xml; - if (rootNode) + if (rootNode !== undefined) { root = xml.ele(rootNode); } const sceneObjectPart = root.ele('SceneObjectPart').att('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance').att('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison sceneObjectPart.ele('AllowedDrop', (this.Flags !== undefined && (this.Flags & PrimFlags.AllowInventoryDrop) === PrimFlags.AllowInventoryDrop) ? 'true' : 'false'); UUID.getXML(sceneObjectPart.ele('CreatorID'), this.creatorID); UUID.getXML(sceneObjectPart.ele('FolderID'), this.folderID); @@ -1659,7 +2070,7 @@ export class GameObject implements IGameObjectData sceneObjectPart.ele('LocalId', this.ID); sceneObjectPart.ele('Name', this.name); sceneObjectPart.ele('Material', this.Material); - if (this.region) + if (this.region !== undefined) { sceneObjectPart.ele('RegionHandle', this.region.regionHandle.toString()); } @@ -1696,7 +2107,7 @@ export class GameObject implements IGameObjectData { shape.ele('TextureEntry', this.TextureEntry.toBase64()); - if (this.TextureEntry.gltfMaterialOverrides) + if (this.TextureEntry.gltfMaterialOverrides !== undefined) { const overrideKeys = Array.from(this.TextureEntry.gltfMaterialOverrides.keys()); const numEntries = overrideKeys.length; @@ -1728,7 +2139,7 @@ export class GameObject implements IGameObjectData } } } - if (this.extraParams) + if (this.extraParams !== undefined) { shape.ele('ExtraParams', this.extraParams.toBase64()); } @@ -1762,7 +2173,7 @@ export class GameObject implements IGameObjectData shape.ele('State', state); shape.ele('LastAttachPoint', 0); - if (this.ProfileCurve) + if (this.ProfileCurve !== undefined) { const profileShape: ProfileShape = this.ProfileCurve & 0x0F; @@ -1771,13 +2182,13 @@ export class GameObject implements IGameObjectData shape.ele('ProfileShape', ProfileShape[profileShape]); shape.ele('HollowShape', HoleType[holeType]); } - if (this.extraParams !== undefined && this.extraParams.meshData !== null) + if (this.extraParams?.meshData !== null) { shape.ele('SculptType', this.extraParams.meshData.type); UUID.getXML(shape.ele('SculptTexture'), this.extraParams.meshData.meshData); shape.ele('SculptEntry', true); } - else if (this.extraParams !== undefined && this.extraParams.sculptData !== null) + else if (this.extraParams?.sculptData !== null) { shape.ele('SculptType', this.extraParams.sculptData.type); UUID.getXML(shape.ele('SculptTexture'), this.extraParams.sculptData.texture); @@ -1787,7 +2198,7 @@ export class GameObject implements IGameObjectData { shape.ele('SculptEntry', false); } - if (this.extraParams !== undefined && this.extraParams.flexibleData !== null) + if (this.extraParams?.flexibleData !== null) { shape.ele('FlexiSoftness', this.extraParams.flexibleData.Softness); shape.ele('FlexiTension', this.extraParams.flexibleData.Tension); @@ -1803,7 +2214,7 @@ export class GameObject implements IGameObjectData { shape.ele('FlexiEntry', false); } - if (this.extraParams !== undefined && this.extraParams.lightData !== null) + if (this.extraParams?.lightData !== null) { shape.ele('LightColorR', this.extraParams.lightData.Color.red); shape.ele('LightColorG', this.extraParams.lightData.Color.green); @@ -1845,20 +2256,18 @@ export class GameObject implements IGameObjectData { for (const flag of Object.keys(PrimFlags)) { - if (typeof flag === 'string') + const fl: any = PrimFlags; + const flagName: string = flag; + const flagValue: number = fl[flagName]; + // noinspection JSBitwiseOperatorUsage + if (this.Flags & flagValue) { - const fl: any = PrimFlags; - const flagName: string = flag; - const flagValue: number = fl[flagName]; - if (this.Flags & flagValue) - { - flags.push(flagName); - } + flags.push(flagName); } } } sceneObjectPart.ele('Flags', flags.join(' ')); - if (this.textureAnim) + if (this.textureAnim !== undefined) { sceneObjectPart.ele('TextureAnimation', this.textureAnim.toBase64()); } @@ -1866,7 +2275,7 @@ export class GameObject implements IGameObjectData { sceneObjectPart.ele('ParticleSystem', this.Particles.toBase64()); } - if (this.physicsShapeType) + if (this.physicsShapeType != null) { sceneObjectPart.ele('PhysicsShapeType', this.physicsShapeType); } @@ -1878,7 +2287,7 @@ export class GameObject implements IGameObjectData sceneObjectPart.ele('SoundRadius', this.SoundRadius); sceneObjectPart.ele('SoundQueueing', false); } - if (this.inventory && this.inventory.length > 0) + if (this.inventory !== undefined && this.inventory.length > 0) { const inventory = sceneObjectPart.ele('TaskInventory'); for (const inv of this.inventory) @@ -1886,338 +2295,22 @@ export class GameObject implements IGameObjectData await this.getInventoryXML(inventory, inv); } } - } - - populateChildren(): void - { - this.region.objects.populateChildren(this); - } - - async exportXMLElement(rootNode?: string, skipInventory = false, skipResolve = false): Promise - { - const document = builder.create('SceneObjectGroup'); - let linkNum = 1; - await this.getXML(document, this, linkNum, rootNode, skipInventory, skipResolve); - if (this.children && this.children.length > 0) + if (this.linksetData !== undefined) { - const otherParts = document.ele('OtherParts'); - for (const child of this.children) + const lsData= sceneObjectPart.ele('LinksetData'); + for(const k of Array.from(this.linksetData.keys())) { - await child.getXML(otherParts, this, ++linkNum, (rootNode !== undefined) ? 'Part' : undefined, skipInventory, skipResolve); - } - } - return document; - } - - async exportXML(rootNode?: string, skipInventory = false, skipResolve = false): Promise - { - return (await this.exportXMLElement(rootNode, skipInventory, skipResolve)).end({ pretty: true, allowEmpty: true }); - } - - public toJSON(): IGameObjectData - { - return { - deleted: this.deleted, - creatorID: this.creatorID, - creationDate: this.creationDate, - baseMask: this.baseMask, - ownerMask: this.ownerMask, - groupMask: this.groupMask, - everyoneMask: this.everyoneMask, - nextOwnerMask: this.nextOwnerMask, - ownershipCost: this.ownershipCost, - saleType: this.saleType, - salePrice: this.salePrice, - aggregatePerms: this.aggregatePerms, - aggregatePermTextures: this.aggregatePermTextures, - aggregatePermTexturesOwner: this.aggregatePermTexturesOwner, - category: this.category, - inventorySerial: this.inventorySerial, - itemID: this.itemID, - folderID: this.folderID, - fromTaskID: this.fromTaskID, - lastOwnerID: this.lastOwnerID, - name: this.name, - description: this.description, - touchName: this.touchName, - sitName: this.sitName, - resolvedAt: this.resolvedAt, - resolvedInventory: this.resolvedInventory, - totalChildren: this.totalChildren, - landImpact: this.landImpact, - calculatedLandImpact: this.calculatedLandImpact, - physicaImpact: this.physicaImpact, - resourceImpact: this.resourceImpact, - linkResourceImpact: this.linkResourceImpact, - linkPhysicsImpact: this.linkPhysicsImpact, - limitingType: this.limitingType, - children: this.children, - ID: this.ID, - FullID: this.FullID, - ParentID: this.ParentID, - OwnerID: this.OwnerID, - IsAttachment: this.IsAttachment, - NameValue: this.NameValue, - PCode: this.PCode, - State: this.State, - CRC: this.CRC, - Material: this.Material, - ClickAction: this.ClickAction, - Scale: this.Scale, - Flags: this.Flags, - PathCurve: this.PathCurve, - ProfileCurve: this.ProfileCurve, - PathBegin: this.PathBegin, - PathEnd: this.PathEnd, - PathScaleX: this.PathScaleX, - PathScaleY: this.PathScaleY, - PathShearX: this.PathShearX, - PathShearY: this.PathShearY, - PathTwist: this.PathTwist, - PathTwistBegin: this.PathTwistBegin, - PathRadiusOffset: this.PathRadiusOffset, - PathTaperX: this.PathTaperX, - PathTaperY: this.PathTaperY, - PathRevolutions: this.PathRevolutions, - PathSkew: this.PathSkew, - ProfileBegin: this.ProfileBegin, - ProfileEnd: this.ProfileEnd, - ProfileHollow: this.ProfileHollow, - TextureEntry: this.TextureEntry, - Text: this.Text, - TextColor: this.TextColor, - MediaURL: this.MediaURL, - JointType: this.JointType, - JointPivot: this.JointPivot, - JointAxisOrAnchor: this.JointAxisOrAnchor, - Position: this.Position, - Rotation: this.Rotation, - CollisionPlane: this.CollisionPlane, - Velocity: this.Velocity, - Acceleration: this.Acceleration, - AngularVelocity: this.AngularVelocity, - TreeSpecies: this.TreeSpecies, - Sound: this.Sound, - SoundGain: this.SoundGain, - SoundFlags: this.SoundFlags, - SoundRadius: this.SoundRadius, - Particles: this.Particles, - density: this.density, - friction: this.friction, - gravityMultiplier: this.gravityMultiplier, - physicsShapeType: this.physicsShapeType, - restitution: this.restitution - } - } - setObjectData(data: Buffer): void - { - let dataPos = 0; - - // noinspection FallThroughInSwitchStatementJS, TsLint - switch (data.length) - { - // @ts-ignore - case 76: - // Avatar collision normal; - this.CollisionPlane = new Vector4(data, dataPos); - dataPos += 16; - /* falls through */ - case 60: - // Position - this.Position = new Vector3(data, dataPos); - dataPos += 12; - this.Velocity = new Vector3(data, dataPos); - dataPos += 12; - this.Acceleration = new Vector3(data, dataPos); - dataPos += 12; - this.Rotation = new Quaternion(data, dataPos); - dataPos += 12; - this.AngularVelocity = new Vector3(data, dataPos); - dataPos += 12; - break; - // @ts-ignore - case 48: - this.CollisionPlane = new Vector4(data, dataPos); - dataPos += 16; - /* falls through */ - case 32: - this.Position = new Vector3([ - Utils.UInt16ToFloat(data.readUInt16LE(dataPos), -0.5 * 256.0, 1.5 * 256.0), - Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 2), -0.5 * 256.0, 1.5 * 256.0), - Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 4), -256.0, 3.0 * 256.0) - ]); - dataPos += 6; - this.Velocity = new Vector3([ - Utils.UInt16ToFloat(data.readUInt16LE(dataPos), -256.0, 256.0), - Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 2), -256.0, 256.0), - Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 4), -256.0, 256.0) - ]); - dataPos += 6; - this.Acceleration = new Vector3([ - Utils.UInt16ToFloat(data.readUInt16LE(dataPos), -256.0, 256.0), - Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 2), -256.0, 256.0), - Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 4), -256.0, 256.0) - ]); - dataPos += 6; - this.Rotation = new Quaternion([ - Utils.UInt16ToFloat(data.readUInt16LE(dataPos), -1.0, 1.0), - Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 2), -1.0, 1.0), - Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 4), -1.0, 1.0), - Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 4), -1.0, 1.0) - ]); - dataPos += 8; - this.AngularVelocity = new Vector3([ - Utils.UInt16ToFloat(data.readUInt16LE(dataPos), -256.0, 256.0), - Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 2), -256.0, 256.0), - Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 4), -256.0, 256.0) - ]); - dataPos += 6; - break; - case 16: - this.Position = new Vector3([ - Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), - Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), - Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0) - ]); - this.Velocity = new Vector3([ - Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), - Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), - Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0) - ]); - this.Acceleration = new Vector3([ - Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), - Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), - Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0) - ]); - this.Rotation = new Quaternion([ - Utils.ByteToFloat(data.readUInt8(dataPos++), -1.0, 1.0), - Utils.ByteToFloat(data.readUInt8(dataPos++), -1.0, 1.0), - Utils.ByteToFloat(data.readUInt8(dataPos++), -1.0, 1.0), - Utils.ByteToFloat(data.readUInt8(dataPos++), -1.0, 1.0) - ]); - this.AngularVelocity = new Vector3([ - Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), - Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), - Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0) - ]); - break; - } - } - - async deRezObject(destination: DeRezDestination, transactionID: UUID, destFolder: UUID): Promise - { - return GameObject.deRezObjects(this.region, [this], destination, transactionID, destFolder); - } - - async takeToInventory(folder?: InventoryFolder): Promise - { - return GameObject.takeManyToInventory(this.region, [this], folder); - } - - async dropInventoryIntoContents(inventoryItem: InventoryItem | UUID): Promise - { - const transactionID = UUID.zero(); - - if (inventoryItem instanceof UUID) - { - - const item: InventoryItem | null = await this.region.agent.inventory.fetchInventoryItem(inventoryItem); - if (item === null) - { - throw new Error('Failed to drop inventory into object contents - Inventory item ' + inventoryItem.toString() + ' not found'); - return; - } - inventoryItem = item; - } - - const msg = new UpdateTaskInventoryMessage(); - msg.AgentData = { - AgentID: this.region.agent.agentID, - SessionID: this.region.circuit.sessionID - }; - msg.UpdateData = { - Key: 0, - LocalID: this.ID - }; - msg.InventoryData = { - ItemID: inventoryItem.itemID, - FolderID: inventoryItem.parentID, - CreatorID: inventoryItem.permissions.creator, - OwnerID: inventoryItem.permissions.owner, - GroupID: inventoryItem.permissions.group, - BaseMask: inventoryItem.permissions.baseMask, - OwnerMask: inventoryItem.permissions.ownerMask, - GroupMask: inventoryItem.permissions.groupMask, - EveryoneMask: inventoryItem.permissions.everyoneMask, - NextOwnerMask: inventoryItem.permissions.nextOwnerMask, - GroupOwned: inventoryItem.permissions.groupOwned || false, - TransactionID: transactionID, - Type: inventoryItem.type, - InvType: inventoryItem.inventoryType, - Flags: inventoryItem.flags, - SaleType: inventoryItem.saleType, - SalePrice: inventoryItem.salePrice, - Name: Utils.StringToBuffer(inventoryItem.name), - Description: Utils.StringToBuffer(inventoryItem.description), - CreationDate: inventoryItem.created.getTime() / 1000, - CRC: inventoryItem.getCRC() - }; - const serial = this.inventorySerial; - this.region.circuit.sendMessage(msg, PacketFlags.Reliable); - return this.waitForInventoryUpdate(serial); - } - - async waitForInventoryUpdate(inventorySerial?: number): Promise - { - // We need to select the object or we won't get the objectProperties message - await this.deselect(); - this.select(); - await this.region.circuit.waitForMessage(Message.ObjectProperties, 10000, (message: ObjectPropertiesMessage) => - { - for (const obj of message.ObjectData) - { - if (obj.ObjectID.equals(this.FullID)) + const d = this.linksetData.get(k); + if (d === undefined) { - if (inventorySerial === undefined) - { - inventorySerial = this.inventorySerial; - } - if (obj.InventorySerial > inventorySerial) - { - return FilterResponse.Finish; - } + continue; } + const dataEntry = lsData.ele('LinksetDataEntry'); + dataEntry.ele('Key', k); + dataEntry.ele('Value', d.value); + dataEntry.ele('Hash', d.pass); } - return FilterResponse.NoMatch; - }); - await this.deselect(); - } - select(): Promise - { - const selectObject = new ObjectSelectMessage(); - selectObject.AgentData = { - AgentID: this.region.agent.agentID, - SessionID: this.region.circuit.sessionID - }; - selectObject.ObjectData = [{ - ObjectLocalID: this.ID - }]; - const ack = this.region.circuit.sendMessage(selectObject, PacketFlags.Reliable); - return this.region.circuit.waitForAck(ack, 10000); - } - - deselect(): Promise - { - const deselectObject = new ObjectDeselectMessage(); - deselectObject.AgentData = { - AgentID: this.region.agent.agentID, - SessionID: this.region.circuit.sessionID - }; - deselectObject.ObjectData = [{ - ObjectLocalID: this.ID - }]; - const ack = this.region.circuit.sendMessage(deselectObject, PacketFlags.Reliable); - return this.region.circuit.waitForAck(ack, 10000); + } } } diff --git a/lib/classes/public/LLMesh.ts b/lib/classes/public/LLMesh.ts index e81ff1b..306a623 100644 --- a/lib/classes/public/LLMesh.ts +++ b/lib/classes/public/LLMesh.ts @@ -1,129 +1,448 @@ -import * as LLSD from '@caspertech/llsd'; import { UUID } from '../UUID'; -import { LLSubMesh } from './interfaces/LLSubMesh'; import { Vector3 } from '../Vector3'; import { Vector2 } from '../Vector2'; -import { LLSkin } from './interfaces/LLSkin'; -import { LLPhysicsConvex } from './interfaces/LLPhysicsConvex'; import { Utils } from '../Utils'; -import { TSMMat4 } from '../../tsm/mat4'; +import { Buffer } from 'buffer'; +import type { LLSubMesh } from './interfaces/LLSubMesh'; +import type { LLPhysicsConvex } from './interfaces/LLPhysicsConvex'; +import { LLSD } from '../llsd/LLSD'; +import { LLSDMap } from '../llsd/LLSDMap'; +import { LLSDInteger } from '../llsd/LLSDInteger'; +import { LLSDReal } from '../llsd/LLSDReal'; +import type { LLSkin } from './interfaces/LLSkin'; +import type { LLSDType } from '../llsd/LLSDType'; +import { Matrix4 } from '../Matrix4'; export class LLMesh { - version: number; - lodLevels: { [key: string]: LLSubMesh[] } = {}; - physicsConvex: LLPhysicsConvex; - skin?: LLSkin; - creatorID: UUID; - date: Date; + public version?: number; + public lodLevels: Record = {}; + public physicsConvex?: LLPhysicsConvex; + public physicsHavok?: { + weldingData: Buffer, + hullMassProps: { + CoM: number[], + inertia: number[], + mass: number, + volume: number + }, + meshDecompMassProps: { + CoM: number[], + inertia: number[], + mass: number, + volume: number + } + } + public skin?: LLSkin; + public creatorID?: UUID; + public date?: Date; + public costData?: { + hull: number, + hull_discounted_vertices: number, + mesh: number[], + mesh_triangles: number + } - static async from(buf: Buffer): Promise + public static async from(buf: Buffer): Promise { const llmesh = new LLMesh(); - const binData = new LLSD.Binary(Array.from(buf), 'BASE64'); - let obj = LLSD.LLSD.parseBinary(binData); - if (obj['result'] === undefined) + const metadata = { + readPos: 0 + }; + const obj = LLSD.parseBinary(buf, metadata); + if (!(obj instanceof LLSDMap)) { - throw new Error('Failed to decode header'); + throw new Error('Invalid mesh'); } - if (obj['position'] === undefined) + + for(const key of obj.keys()) { - throw new Error('Position not reported'); - } - const startPos = parseInt(obj['position'], 10); - obj = obj['result']; - if (obj['creator']) - { - llmesh.creatorID = new UUID(obj['creator'].toString()); - } - if (obj['date']) - { - llmesh.date = obj['date']; - } - if (obj['version']) - { - llmesh.version = parseInt(obj['version'], 10); - } - for (const key of Object.keys(obj)) - { - const o = obj[key]; - if (typeof o === 'object' && o !== null && o['offset'] !== undefined) + switch(key) { - const bufFrom = startPos + parseInt(o['offset'], 10); - const bufTo = startPos + parseInt(o['offset'], 10) + parseInt(o['size'], 10); - const partBuf = buf.slice(bufFrom, bufTo); + case 'creator': + { + const u = obj[key]; + if (u instanceof UUID) + { + llmesh.creatorID = u; + } + break; + } + case 'version': + { + const int = obj[key]; + if (int instanceof LLSDInteger) + { + llmesh.version = int.valueOf(); + } + break; + } + case 'date': + { + const dt = obj[key]; + if (dt instanceof Date) + { + llmesh.date = dt; + } + break; + } + case 'physics_cost_data': + { + const map = obj[key]; + if (map instanceof LLSDMap) + { + llmesh.costData = { + hull: 0, + hull_discounted_vertices: 0, + mesh: [], + mesh_triangles: 0 + } + if (map.hull instanceof LLSDReal) + { + llmesh.costData.hull = map.hull.valueOf(); + } + if (map.hull_discounted_vertices instanceof LLSDInteger) + { + llmesh.costData.hull_discounted_vertices = map.hull_discounted_vertices.valueOf(); + } + if (Array.isArray(map.mesh)) + { + for(const num of map.mesh) + { + if ((num as unknown) instanceof LLSDReal) + { + llmesh.costData.mesh.push(num.valueOf()); + } + } + } + if (map.mesh_triangles instanceof LLSDInteger) + { + llmesh.costData.mesh_triangles = map.mesh_triangles.valueOf(); + } + } + break; + } + case 'physics_shape': + case 'physics_mesh': + case 'high_lod': + case 'medium_lod': + case 'low_lod': + case 'lowest_lod': + case 'physics_convex': + case 'physics_havok': + case 'skin': + { + const skin = obj[key]; + if (skin instanceof LLSDMap) + { + const hash = skin.get('hash'); + const offset = skin.get('offset'); + const size = skin.get('size'); + if (offset instanceof LLSDInteger && size instanceof LLSDInteger) + { + const offsetVal = offset.valueOf(); + const sizeVal = size.valueOf(); + const startPos = metadata.readPos + offsetVal; + const endPos = offsetVal + sizeVal + metadata.readPos; - const deflated = await Utils.inflate(partBuf); + const bufSlice = buf.subarray(startPos, endPos); - const mesh = LLSD.LLSD.parseBinary(new LLSD.Binary(Array.from(deflated), 'BASE64')); - if (mesh['result'] === undefined) - { - throw new Error('Failed to parse compressed submesh data'); - } - if (key === 'physics_convex') - { - llmesh.physicsConvex = this.parsePhysicsConvex(mesh['result']); - } - else if (key === 'skin') - { - llmesh.skin = this.parseSkin(mesh['result']); - } - else if (key === 'physics_havok' || key === 'physics_cost_data') - { - // Used by the simulator - } - else - { - llmesh.lodLevels[key] = this.parseLODLevel(mesh['result']); - } + if (hash instanceof Buffer) + { + const inflatedHash = Utils.MD5String(bufSlice); + if (inflatedHash !== hash.toString('hex')) + { + throw new Error('Hash mismatch'); + } + } + const inflated = await Utils.inflate(bufSlice); + + const parsed = LLSD.parseBinary(inflated); + if (key === 'physics_havok') + { + if (parsed instanceof LLSDMap) + { + llmesh.physicsHavok = { + weldingData: Buffer.alloc(0), + hullMassProps: { + CoM: [], + inertia: [], + mass: 0, + volume: 0 + }, + meshDecompMassProps: { + CoM: [], + inertia: [], + mass: 0, + volume: 0 + } + } + if (parsed.HullMassProps instanceof LLSDMap) + { + if (Array.isArray(parsed.HullMassProps.CoM)) + { + for(const num of parsed.HullMassProps.CoM) + { + if ((num as unknown) instanceof LLSDReal) + { + llmesh.physicsHavok.hullMassProps.CoM.push(num.valueOf()); + } + } + } + if (Array.isArray(parsed.HullMassProps.inertia)) + { + for(const num of parsed.HullMassProps.inertia) + { + if ((num as unknown) instanceof LLSDReal) + { + llmesh.physicsHavok.hullMassProps.inertia.push(num.valueOf()); + } + } + } + if (parsed.HullMassProps.mass instanceof LLSDReal) + { + llmesh.physicsHavok.hullMassProps.mass = parsed.HullMassProps.mass.valueOf(); + } + if (parsed.HullMassProps.volume instanceof LLSDReal) + { + llmesh.physicsHavok.hullMassProps.volume = parsed.HullMassProps.volume.valueOf(); + } + } + if (parsed.MeshDecompMassProps instanceof LLSDMap) + { + if (Array.isArray(parsed.MeshDecompMassProps.CoM)) + { + for(const num of parsed.MeshDecompMassProps.CoM) + { + if ((num as unknown) instanceof LLSDReal) + { + llmesh.physicsHavok.meshDecompMassProps.CoM.push(num.valueOf()); + } + } + } + if (Array.isArray(parsed.MeshDecompMassProps.inertia)) + { + for(const num of parsed.MeshDecompMassProps.inertia) + { + if ((num as unknown) instanceof LLSDReal) + { + llmesh.physicsHavok.meshDecompMassProps.inertia.push(num.valueOf()); + } + } + } + if (parsed.MeshDecompMassProps.mass instanceof LLSDReal) + { + llmesh.physicsHavok.meshDecompMassProps.mass = parsed.MeshDecompMassProps.mass.valueOf(); + } + if (parsed.MeshDecompMassProps.volume instanceof LLSDReal) + { + llmesh.physicsHavok.meshDecompMassProps.volume = parsed.MeshDecompMassProps.volume.valueOf(); + } + } + if (parsed.WeldingData instanceof Buffer) + { + llmesh.physicsHavok.weldingData = parsed.WeldingData; + } + } + } + else if (key === 'skin') + { + if (parsed instanceof LLSDMap) + { + llmesh.skin = this.parseSkin(parsed); + } + } + else if (key === 'physics_convex') + { + if (parsed instanceof LLSDMap) + { + llmesh.physicsConvex = this.parsePhysicsConvex(parsed); + } + } + else + { + if (Array.isArray(parsed)) + { + const subMeshes: LLSDMap[] = []; + for (const sm of parsed) + { + if (sm instanceof LLSDMap) + { + subMeshes.push(sm); + } + } + llmesh.lodLevels[key] = this.parseLODLevel(subMeshes) + } + } + } + } + break; + } + default: + { + console.warn('Unrecognised mesh property: ' + key); + } } } + return llmesh; } - static parseSkin(mesh: any): LLSkin + + public async toAsset(): Promise { - if (!mesh['joint_names']) + const llsd = new LLSDMap(); + if (this.creatorID) { - throw new Error('Joint names missing from skin'); + llsd.add('creator', this.creatorID); } - if (!mesh['bind_shape_matrix']) + if (this.version !== undefined) { - throw new Error('Bind shape matrix missing from skin'); + llsd.add('version', new LLSDInteger(this.version)); } - if (!mesh['inverse_bind_matrix']) + if (this.date !== undefined) { - throw new Error('Inverse bind matrix missing from skin'); + llsd.add('date', this.date); } + let offset = 0; + const bufs = []; + for (const lod of Object.keys(this.lodLevels)) + { + const lodBlob = await this.encodeLODLevel(lod, this.lodLevels[lod]); + llsd.add(lod, new LLSDMap([ + ['offset', new LLSDInteger(offset)], + ['size', new LLSDInteger(lodBlob.length)] + ])); + offset += lodBlob.length; + bufs.push(lodBlob); + } + if (this.costData) + { + llsd.add('physics_cost_data', new LLSDMap([ + ['hull', new LLSDReal(this.costData.hull)], + ['hull_discounted_vertices', new LLSDInteger(this.costData.hull_discounted_vertices)], + ['mesh', LLMesh.toLLSDReal(this.costData.mesh)], + ['mesh_triangles', new LLSDInteger(this.costData.mesh_triangles)] + ])); + } + if (this.physicsHavok) + { + const physHavok = await this.encodePhysicsHavok(); + llsd.add('physics_havok', new LLSDMap([ + ['offset', new LLSDInteger(offset)], + ['size', new LLSDInteger(physHavok.length)] + ])); + offset += physHavok.length; + bufs.push(physHavok); + } + if (this.physicsConvex) + { + const physBlob = await this.encodePhysicsConvex(this.physicsConvex); + llsd.add('physics_convex', new LLSDMap([ + ['offset', new LLSDInteger(offset)], + ['size', new LLSDInteger(physBlob.length)] + ])); + offset += physBlob.length; + bufs.push(physBlob); + } + if (this.skin) + { + const skinBlob = await this.encodeSkin(this.skin); + llsd.add('skin', new LLSDMap([ + ['offset', new LLSDInteger(offset)], + ['size', new LLSDInteger(skinBlob.length)] + ])); + bufs.push(skinBlob); + } + bufs.unshift(LLSD.toBinary(llsd)); + return Buffer.concat(bufs); + } + + private static parseSkin(mesh: LLSDMap): LLSkin + { const skin: LLSkin = { - jointNames: mesh['joint_names'], - bindShapeMatrix: new TSMMat4(mesh['bind_shape_matrix']), + jointNames: [], + bindShapeMatrix: new Matrix4(), inverseBindMatrix: [] }; - if (mesh['inverse_bind_matrix']) + + if (Array.isArray(mesh.joint_names)) + { + for(const joint of mesh.joint_names) + { + if (typeof (joint as unknown) === 'string') + { + skin.jointNames.push(joint); + } + } + } + if (Array.isArray(mesh.bind_shape_matrix)) + { + const params = []; + for(const num of mesh.bind_shape_matrix) + { + if ((num as unknown) instanceof LLSDReal) + { + params.push(num.valueOf()); + } + } + skin.bindShapeMatrix = new Matrix4(params); + } + + if (Array.isArray(mesh.inverse_bind_matrix)) { skin.inverseBindMatrix = []; - for (const inv of mesh['inverse_bind_matrix']) + for (const inv of mesh.inverse_bind_matrix) { - skin.inverseBindMatrix.push(new TSMMat4(inv)); + const mtrx: number[] = []; + if (Array.isArray(inv)) + { + for(const num of inv) + { + if ((num as unknown) instanceof LLSDReal) + { + mtrx.push(num.valueOf()); + } + } + } + skin.inverseBindMatrix.push(new Matrix4(mtrx)); } } - if (mesh['alt_inverse_bind_matrix']) + if (Array.isArray(mesh.alt_inverse_bind_matrix)) { skin.altInverseBindMatrix = []; - for (const inv of mesh['alt_inverse_bind_matrix']) + for (const inv of mesh.alt_inverse_bind_matrix) { - skin.altInverseBindMatrix.push(new TSMMat4(inv)); + const mtrx: number[] = []; + if (Array.isArray(inv)) + { + for(const num of inv) + { + if ((num as unknown) instanceof LLSDReal) + { + mtrx.push(num.valueOf()); + } + } + } + skin.altInverseBindMatrix.push(new Matrix4(mtrx)); } } - if (mesh['pelvis_offset']) + if (Array.isArray(mesh.pelvis_offset)) { - skin.pelvisOffset = new TSMMat4(mesh['pelvis_offset']); + const mtrx: number[] = []; + for(const num of mesh.pelvis_offset) + { + if ((num as unknown) instanceof LLSDReal) + { + mtrx.push(num.valueOf()); + } + } + skin.pelvisOffset = new Matrix4(mtrx); } return skin; } - static fixReal(arr: number[]): number[] + private static fixReal(arr: number[]): number[] { const newArr = []; for (let num of arr) @@ -136,7 +455,22 @@ export class LLMesh } return newArr; } - static parsePhysicsConvex(mesh: any): LLPhysicsConvex + + private static fixRealLLSD(arr: number[]): LLSDReal[] + { + const newArr: LLSDReal[]= []; + for (let num of arr) + { + if ((num >> 0) === num && !((num === 0) && ((1 / num) === -Infinity))) + { + num += 0.0000000001; + } + newArr.push(new LLSDReal(num)); + } + return newArr; + } + + private static parsePhysicsConvex(mesh: LLSDMap): LLPhysicsConvex { const conv: LLPhysicsConvex = { boundingVerts: [], @@ -145,51 +479,45 @@ export class LLMesh max: new Vector3([0.5, 0.5, 0.5]) } }; - if (mesh['Min']) + if (Array.isArray(mesh.Min)) { - conv.domain.min.x = mesh['Min'][0]; - conv.domain.min.y = mesh['Min'][1]; - conv.domain.min.z = mesh['Min'][2]; + conv.domain.min.x = mesh.Min[0].valueOf(); + conv.domain.min.y = mesh.Min[1].valueOf(); + conv.domain.min.z = mesh.Min[2].valueOf(); } - if (mesh['Max']) + if (Array.isArray(mesh.Max)) { - conv.domain.max.x = mesh['Max'][0]; - conv.domain.max.y = mesh['Max'][1]; - conv.domain.max.z = mesh['Max'][2]; + conv.domain.max.x = mesh.Max[0].valueOf(); + conv.domain.max.y = mesh.Max[1].valueOf(); + conv.domain.max.z = mesh.Max[2].valueOf(); } - if (mesh['HullList']) + if (mesh.HullList instanceof Buffer) { - if (!mesh['Positions']) + if (!(mesh.Positions instanceof Buffer)) { throw new Error('Positions must be supplied if hull list is present'); } - conv.positions = this.decodeByteDomain3(mesh['Positions'].toArray(), conv.domain.min, conv.domain.max); - conv.hullList = mesh['HullList'].toArray(); - if (conv.hullList === undefined) + conv.positions = this.decodeByteDomain3(mesh.Positions, conv.domain.min, conv.domain.max); + conv.hullList = Array.from(mesh.HullList); + let totalPoints = 0; + for (const hull of conv.hullList) { - throw new Error('HullList undefined'); + totalPoints += hull; } - else + if (conv.positions.length !== totalPoints) { - let totalPoints = 0; - for (const hull of conv.hullList) - { - totalPoints += hull; - } - if (conv.positions.length !== totalPoints) - { - throw new Error('Hull list expected number of points does not match number of positions: ' + totalPoints + ' vs ' + conv.positions.length); - } + throw new Error('Hull list expected number of points does not match number of positions: ' + totalPoints + ' vs ' + conv.positions.length); } } - if (!mesh['BoundingVerts']) + if (!(mesh.BoundingVerts instanceof Buffer)) { throw new Error('BoundingVerts is required'); } - conv.boundingVerts = this.decodeByteDomain3(mesh['BoundingVerts'].toArray(), conv.domain.min, conv.domain.max); + conv.boundingVerts = this.decodeByteDomain3(mesh.BoundingVerts, conv.domain.min, conv.domain.max); return conv; } - static parseLODLevel(mesh: any): LLSubMesh[] + + private static parseLODLevel(mesh: LLSDMap[]): LLSubMesh[] { const list: LLSubMesh[] = []; for (const submesh of mesh) @@ -200,7 +528,7 @@ export class LLMesh max: new Vector3([0.5, 0.5, 0.5]) } }; - if (submesh['NoGeometry']) + if (submesh.NoGeometry !== undefined) { decoded.noGeometry = true; list.push(decoded); @@ -208,71 +536,123 @@ export class LLMesh else { decoded.position = []; - if (!submesh['Position']) + if (!(submesh.Position instanceof Buffer)) { throw new Error('Submesh does not contain position data'); } if (decoded.positionDomain !== undefined) { - if (submesh['PositionDomain']) + if (submesh.PositionDomain instanceof LLSDMap) { - if (submesh['PositionDomain']['Max'] !== undefined) + if (Array.isArray(submesh.PositionDomain.Max)) { - const dom = submesh['PositionDomain']['Max']; - decoded.positionDomain.max.x = dom[0]; - decoded.positionDomain.max.y = dom[1]; - decoded.positionDomain.max.z = dom[2]; + const dom = submesh.PositionDomain.Max; + if (dom[0] instanceof LLSDReal) + { + decoded.positionDomain.max.x = dom[0].valueOf(); + } + if (dom[1] instanceof LLSDReal) + { + decoded.positionDomain.max.y = dom[1].valueOf(); + } + if (dom[2] instanceof LLSDReal) + { + decoded.positionDomain.max.z = dom[2].valueOf(); + } } - if (submesh['PositionDomain']['Min'] !== undefined) + if (Array.isArray(submesh.PositionDomain.Min)) { - const dom = submesh['PositionDomain']['Min']; - decoded.positionDomain.min.x = dom[0]; - decoded.positionDomain.min.y = dom[1]; - decoded.positionDomain.min.z = dom[2]; + const dom = submesh.PositionDomain.Min; + if (dom[0] instanceof LLSDReal) + { + decoded.positionDomain.min.x = dom[0].valueOf(); + } + if (dom[1] instanceof LLSDReal) + { + decoded.positionDomain.min.y = dom[1].valueOf(); + } + if (dom[2] instanceof LLSDReal) + { + decoded.positionDomain.min.z = dom[2].valueOf(); + } } } - decoded.position = this.decodeByteDomain3(submesh['Position'].toArray(), decoded.positionDomain.min, decoded.positionDomain.max); + decoded.position = this.decodeByteDomain3(submesh.Position, decoded.positionDomain.min, decoded.positionDomain.max); } - if (submesh['Normal']) + if (submesh.Normal instanceof Buffer) { - decoded.normal = this.decodeByteDomain3(submesh['Normal'].toArray(), new Vector3([-1.0, -1.0, -1.0]), new Vector3([1.0, 1.0, 1.0])); + decoded.normal = this.decodeByteDomain3(submesh.Normal, new Vector3([-1.0, -1.0, -1.0]), new Vector3([1.0, 1.0, 1.0])); if (decoded.normal.length !== decoded.position.length) { throw new Error('Normal length does not match vertex position length'); } } - if (submesh['TexCoord0']) + if (submesh.TexCoord0 !== undefined) { decoded.texCoord0Domain = { min: new Vector2([-0.5, -0.5]), max: new Vector2([0.5, 0.5]) }; - if (submesh['TexCoord0Domain']) + if (submesh.TexCoord0Domain instanceof LLSDMap) { - if (submesh['TexCoord0Domain']['Max'] !== undefined) + if (submesh.TexCoord0Domain.Max !== undefined) { - const dom = submesh['TexCoord0Domain']['Max']; - decoded.texCoord0Domain.max.x = dom[0]; - decoded.texCoord0Domain.max.y = dom[1]; + const dom = submesh.TexCoord0Domain.Max; + if (Array.isArray(dom)) + { + if (dom[0] instanceof LLSDReal) + { + decoded.texCoord0Domain.max.x = dom[0].valueOf(); + } + else + { + throw new Error('Unexpected type'); + } + if (dom[1] instanceof LLSDReal) + { + decoded.texCoord0Domain.max.y = dom[1].valueOf(); + } + else + { + throw new Error('Unexpected type'); + } + } } - if (submesh['TexCoord0Domain']['Min'] !== undefined) + if (Array.isArray(submesh.TexCoord0Domain.Min)) { - const dom = submesh['TexCoord0Domain']['Min']; - decoded.texCoord0Domain.min.x = dom[0]; - decoded.texCoord0Domain.min.y = dom[1]; + const dom = submesh.TexCoord0Domain.Min; + if (dom[0] instanceof LLSDReal) + { + decoded.texCoord0Domain.min.x = dom[0].valueOf(); + } + else + { + throw new Error('Unexpected type'); + } + if (dom[1] instanceof LLSDReal) + { + decoded.texCoord0Domain.min.y = dom[1].valueOf(); + } + else + { + throw new Error('Unexpected type'); + } } } else { throw new Error('TexCoord0Domain is required if Texcoord0 is present'); } - decoded.texCoord0 = this.decodeByteDomain2(submesh['TexCoord0'].toArray(), decoded.texCoord0Domain.min, decoded.texCoord0Domain.max); + if (submesh.TexCoord0 instanceof Buffer) + { + decoded.texCoord0 = this.decodeByteDomain2(submesh.TexCoord0, decoded.texCoord0Domain.min, decoded.texCoord0Domain.max); + } } - if (!submesh['TriangleList']) + if (!(submesh.TriangleList instanceof Buffer)) { throw new Error('TriangleList is required'); } - const indexBuf = Buffer.from(submesh['TriangleList'].toArray()); + const indexBuf = Buffer.from(submesh.TriangleList); decoded.triangleList = []; for (let pos = 0; pos < indexBuf.length; pos = pos + 2) { @@ -283,14 +663,14 @@ export class LLMesh } decoded.triangleList.push(vertIndex); } - if (submesh['Weights']) + if (submesh.Weights instanceof Buffer) { - const skinBuf = Buffer.from(submesh['Weights'].toArray()); + const skinBuf = submesh.Weights; decoded.weights = []; let pos = 0; while (pos < skinBuf.length) { - const entry: { [key: number]: number } = {}; + const entry: Record = {}; for (let x = 0; x < 4; x++) { const jointNum = skinBuf.readUInt8(pos++); @@ -313,43 +693,46 @@ export class LLMesh } return list; } - static decodeByteDomain3(posArray: number[], minDomain: Vector3, maxDomain: Vector3): Vector3[] + private static decodeByteDomain3(buf: Buffer, minDomain: Vector3, maxDomain: Vector3): Vector3[] { const result: Vector3[] = []; - const buf = Buffer.from(posArray); - for (let idx = 0; idx < posArray.length; idx = idx + 6) + for (let idx = 0; idx < buf.length; idx = idx + 6) { - const posX = this.normalizeDomain(buf.readUInt16LE(idx), minDomain.x, maxDomain.x); - const posY = this.normalizeDomain(buf.readUInt16LE(idx + 2), minDomain.y, maxDomain.y); - const posZ = this.normalizeDomain(buf.readUInt16LE(idx + 4), minDomain.z, maxDomain.z); + const posX = Utils.UInt16ToFloat(buf.readUInt16LE(idx), minDomain.x, maxDomain.x, false); + const posY = Utils.UInt16ToFloat(buf.readUInt16LE(idx + 2), minDomain.y, maxDomain.y, false); + const posZ = Utils.UInt16ToFloat(buf.readUInt16LE(idx + 4), minDomain.z, maxDomain.z, false); result.push(new Vector3([posX, posY, posZ])); } return result; } - static decodeByteDomain2(posArray: number[], minDomain: Vector2, maxDomain: Vector2): Vector2[] + private static decodeByteDomain2(buf: Buffer, minDomain: Vector2, maxDomain: Vector2): Vector2[] { const result: Vector2[] = []; - const buf = Buffer.from(posArray); - for (let idx = 0; idx < posArray.length; idx = idx + 4) + for (let idx = 0; idx < buf.length; idx = idx + 4) { - const posX = this.normalizeDomain(buf.readUInt16LE(idx), minDomain.x, maxDomain.x); - const posY = this.normalizeDomain(buf.readUInt16LE(idx + 2), minDomain.y, maxDomain.y); + const posX = Utils.UInt16ToFloat(buf.readUInt16LE(idx), minDomain.x, maxDomain.x, false); + const posY = Utils.UInt16ToFloat(buf.readUInt16LE(idx + 2), minDomain.y, maxDomain.y, false); result.push(new Vector2([posX, posY])); } return result; } - static normalizeDomain(value: number, min: number, max: number): number + private static toLLSDReal(num: number[]): LLSDReal[] { - return ((value / 65535) * (max - min)) + min; + const real: LLSDReal[] = []; + for(const n of num) + { + real.push(new LLSDReal(n)); + } + return real; } - private encodeSubMesh(mesh: LLSubMesh): LLSubMesh + private encodeSubMesh(mesh: LLSubMesh): LLSDType { - const data: LLSubMesh = {}; + const data = new LLSDMap(); if (mesh.noGeometry === true) { - data.noGeometry = true; + data.add('NoGeometry', true); return data; } if (!mesh.position) @@ -358,33 +741,37 @@ export class LLMesh } if (mesh.positionDomain !== undefined) { - data.position = new LLSD.Binary(Array.from(this.expandFromDomain(mesh.position, mesh.positionDomain.min, mesh.positionDomain.max))); - data.positionDomain = { - min: new Vector3(LLMesh.fixReal(mesh.positionDomain.min.toArray())), - max: new Vector3(LLMesh.fixReal(mesh.positionDomain.max.toArray())) - }; + data.add('Position', this.expandFromDomain(mesh.position, mesh.positionDomain.min, mesh.positionDomain.max)); + const min = new Vector3(LLMesh.fixReal(mesh.positionDomain.min.toArray())); + const max = new Vector3(LLMesh.fixReal(mesh.positionDomain.max.toArray())); + data.add('PositionDomain', new LLSDMap([ + ['Min', [new LLSDReal(min.x), new LLSDReal(min.y), new LLSDReal(min.z)]], + ['Max', [new LLSDReal(max.x), new LLSDReal(max.y), new LLSDReal(max.z)]] + ])); } if (mesh.texCoord0 && mesh.texCoord0Domain !== undefined) { - data.texCoord0 = new LLSD.Binary(Array.from(this.expandFromDomain(mesh.texCoord0, mesh.texCoord0Domain.min, mesh.texCoord0Domain.max))); - data.texCoord0Domain = { - min: new Vector2(LLMesh.fixReal(mesh.texCoord0Domain.min.toArray())), - max: new Vector2(LLMesh.fixReal(mesh.texCoord0Domain.max.toArray())) - }; + data.add('TexCoord0', this.expandFromDomain(mesh.texCoord0, mesh.texCoord0Domain.min, mesh.texCoord0Domain.max)); + const domainMin = new Vector2(LLMesh.fixReal(mesh.texCoord0Domain.min.toArray())); + const domainMax = new Vector2(LLMesh.fixReal(mesh.texCoord0Domain.max.toArray())); + data.add('TexCoord0Domain', new LLSDMap([ + ['Min', [new LLSDReal(domainMin.x), new LLSDReal(domainMin.y)]], + ['Max', [new LLSDReal(domainMax.x), new LLSDReal(domainMax.y)]] + ])); } if (mesh.normal) { - data.normal = new LLSD.Binary(Array.from(this.expandFromDomain(mesh.normal, new Vector3([-1.0, -1.0, -1.0]), new Vector3([1.0, 1.0, 1.0])))); + data.add('Normal', this.expandFromDomain(mesh.normal, new Vector3([-1.0, -1.0, -1.0]), new Vector3([1.0, 1.0, 1.0]))); } if (mesh.triangleList) { const triangles = Buffer.allocUnsafe(mesh.triangleList.length * 2); let pos = 0; - for (let x = 0; x < mesh.triangleList.length; x++) + for(const triangle of mesh.triangleList) { - triangles.writeUInt16LE(mesh.triangleList[x], pos); pos = pos + 2; + triangles.writeUInt16LE(triangle, pos); pos = pos + 2; } - data.triangleList = new LLSD.Binary(Array.from(triangles)); + data.add('TriangleList', triangles); } else { @@ -418,7 +805,7 @@ export class LLMesh weightBuff.writeUInt8(0xFF, pos++); } } - data.weights = new LLSD.Binary(Array.from(weightBuff)); + data.add('Weights', weightBuff); } return data; } @@ -454,27 +841,42 @@ export class LLMesh private async encodeLODLevel(_: string, submeshes: LLSubMesh[]): Promise { - const smList = []; + const smList: LLSDType[] = []; for (const sub of submeshes) { smList.push(this.encodeSubMesh(sub)) } - const mesh = LLSD.LLSD.formatBinary(smList); - return Utils.deflate(Buffer.from(mesh.toArray())); + return Utils.deflate(LLSD.toBinary(smList)); } + private async encodePhysicsHavok(): Promise + { + if (!this.physicsHavok) + { + return Buffer.alloc(0); + } + return Utils.deflate(LLSD.toBinary(new LLSDMap([ + ['WeldingData', this.physicsHavok.weldingData], + ['HullMassProps', new LLSDMap([ + ['CoM', LLMesh.toLLSDReal(this.physicsHavok.hullMassProps.CoM)], + ['inertia', LLMesh.toLLSDReal(this.physicsHavok.hullMassProps.inertia)], + ['mass', new LLSDReal(this.physicsHavok.hullMassProps.mass)], + ['volume', new LLSDReal(this.physicsHavok.hullMassProps.volume)] + ])], + ['MeshDecompMassProps', new LLSDMap([ + ['CoM', LLMesh.toLLSDReal(this.physicsHavok.meshDecompMassProps.CoM)], + ['inertia', LLMesh.toLLSDReal(this.physicsHavok.meshDecompMassProps.inertia)], + ['mass', new LLSDReal(this.physicsHavok.meshDecompMassProps.mass)], + ['volume', new LLSDReal(this.physicsHavok.meshDecompMassProps.volume)] + ])] + ]))); + } private async encodePhysicsConvex(conv: LLPhysicsConvex): Promise { - const llsd: { - 'HullList'?: any, - 'Positions'?: any, - 'BoundingVerts'?: any, - 'Min': number[], - 'Max': number[]; - } = { - Min: LLMesh.fixReal(conv.domain.min.toArray()), - Max: LLMesh.fixReal(conv.domain.max.toArray()) - }; + const llsd = new LLSDMap(); + llsd.add('Min', LLMesh.fixRealLLSD(conv.domain.min.toArray())); + llsd.add('Max', LLMesh.fixRealLLSD(conv.domain.max.toArray())); + const sizeX = conv.domain.max.x - conv.domain.min.x; const sizeY = conv.domain.max.y - conv.domain.min.y; const sizeZ = conv.domain.max.z - conv.domain.min.z; @@ -484,7 +886,7 @@ export class LLMesh { throw new Error('Positions must be present if hullList is set.') } - llsd.HullList = new LLSD.Binary(conv.hullList); + llsd.add('HullList', Buffer.from(conv.hullList)); const buf = Buffer.allocUnsafe(conv.positions.length * 6); let pos = 0; for (const vec of conv.positions) @@ -493,91 +895,51 @@ export class LLMesh buf.writeUInt16LE(Math.round(((vec.y - conv.domain.min.y) / sizeY) * 65535), pos); pos = pos + 2; buf.writeUInt16LE(Math.round(((vec.z - conv.domain.min.z) / sizeZ) * 65535), pos); pos = pos + 2; } - llsd.Positions = new LLSD.Binary(Array.from(buf)); + llsd.add('Positions', buf); } { const buf = Buffer.allocUnsafe(conv.boundingVerts.length * 6); let pos = 0; for (const vec of conv.boundingVerts) { - buf.writeUInt16LE(Math.round(((vec.x - conv.domain.min.x) / sizeX)) * 65535, pos); + buf.writeUInt16LE(Math.round(((vec.x - conv.domain.min.x) / sizeX) * 65535), pos); pos = pos + 2; - buf.writeUInt16LE(Math.round(((vec.y - conv.domain.min.y) / sizeY)) * 65535, pos); + buf.writeUInt16LE(Math.round(((vec.y - conv.domain.min.y) / sizeY) * 65535), pos); pos = pos + 2; - buf.writeUInt16LE(Math.round(((vec.z - conv.domain.min.z) / sizeZ)) * 65535, pos); + buf.writeUInt16LE(Math.round(((vec.z - conv.domain.min.z) / sizeZ) * 65535), pos); pos = pos + 2; } - llsd.BoundingVerts = new LLSD.Binary(Array.from(buf)); + llsd.add('BoundingVerts', buf); } - const mesh = LLSD.LLSD.formatBinary(llsd); - return await Utils.deflate(Buffer.from(mesh.toArray())); + return Utils.deflate(LLSD.toBinary(llsd)); } private async encodeSkin(skin: LLSkin): Promise { - const llsd: { [key: string]: any } = {}; - llsd['joint_names'] = skin.jointNames; - llsd['bind_shape_matrix'] = skin.bindShapeMatrix.toArray(); - llsd['inverse_bind_matrix'] = []; + const llsd = new LLSDMap(); + llsd.add('joint_names', skin.jointNames); + llsd.add('bind_shape_matrix', LLMesh.toLLSDReal(skin.bindShapeMatrix.toArray())); + + + const inverseBindMatrix: LLSDType[] = []; for (const matrix of skin.inverseBindMatrix) { - llsd['inverse_bind_matrix'].push(matrix.toArray()) + inverseBindMatrix.push(LLMesh.toLLSDReal(matrix.toArray())) } + llsd.add('inverse_bind_matrix', inverseBindMatrix); + if (skin.altInverseBindMatrix) { - llsd['alt_inverse_bind_matrix'] = []; + const altInverseBindMatrix: LLSDType[] = []; for (const matrix of skin.altInverseBindMatrix) { - llsd['alt_inverse_bind_matrix'].push(matrix.toArray()) + altInverseBindMatrix.push(LLMesh.toLLSDReal(matrix.toArray())) } + llsd.add('alt_inverse_bind_matrix', altInverseBindMatrix); } if (skin.pelvisOffset) { - llsd['pelvis_offset'] = skin.pelvisOffset.toArray(); + llsd.add('pelvis_offset', LLMesh.toLLSDReal(skin.pelvisOffset.toArray())); } - const mesh = LLSD.LLSD.formatBinary(llsd); - return await Utils.deflate(Buffer.from(mesh.toArray())); - } - async toAsset(): Promise - { - const llsd: { [key: string]: any } = { - 'creator': new LLSD.UUID(this.creatorID.toString()), - 'version': this.version, - 'date': null - }; - let offset = 0; - const bufs = []; - for (const lod of Object.keys(this.lodLevels)) - { - const lodBlob = await this.encodeLODLevel(lod, this.lodLevels[lod]); - llsd[lod] = { - 'offset': offset, - 'size': lodBlob.length - }; - offset += lodBlob.length; - bufs.push(lodBlob); - } - if (this.physicsConvex) - { - const physBlob = await this.encodePhysicsConvex(this.physicsConvex); - llsd['physics_convex'] = { - 'offset': offset, - 'size': physBlob.length - }; - offset += physBlob.length; - bufs.push(physBlob); - - } - if (this.skin) - { - const skinBlob = await this.encodeSkin(this.skin); - llsd['skin'] = { - 'offset': offset, - 'size': skinBlob.length - }; - offset += skinBlob.length; - bufs.push(skinBlob); - } - bufs.unshift(Buffer.from(LLSD.LLSD.formatBinary(llsd).toArray())); - return Buffer.concat(bufs); + return Utils.deflate(LLSD.toBinary(llsd)); } } diff --git a/lib/classes/public/LightData.ts b/lib/classes/public/LightData.ts index dc0f9a4..02cf0b5 100644 --- a/lib/classes/public/LightData.ts +++ b/lib/classes/public/LightData.ts @@ -2,13 +2,13 @@ import { Color4 } from '../Color4'; export class LightData { - Color: Color4 = Color4.black; - Radius = 0.0; - Cutoff = 0.0; - Falloff = 0.0; - Intensity = 0.0; + public Color: Color4 = Color4.black; + public Radius = 0.0; + public Cutoff = 0.0; + public Falloff = 0.0; + public Intensity = 0.0; - constructor(buf?: Buffer, pos?: number, length?: number) + public constructor(buf?: Buffer, pos?: number, length?: number) { if (buf !== undefined && pos !== undefined && length !== undefined) { @@ -29,7 +29,8 @@ export class LightData } } } - writeToBuffer(buf: Buffer, pos: number): void + + public writeToBuffer(buf: Buffer, pos: number): void { const tmpColour = new Color4(this.Color.getRed(), this.Color.getGreen(), this.Color.getBlue(), this.Color.getAlpha()); tmpColour.alpha = this.Intensity; @@ -38,7 +39,8 @@ export class LightData buf.writeFloatLE(this.Cutoff, pos); pos = pos + 4; buf.writeFloatLE(this.Falloff, pos); } - getBuffer(): Buffer + + public getBuffer(): Buffer { const buf = Buffer.allocUnsafe(16); this.writeToBuffer(buf, 0); diff --git a/lib/classes/public/LightImageData.ts b/lib/classes/public/LightImageData.ts index a03e546..b2f9e66 100644 --- a/lib/classes/public/LightImageData.ts +++ b/lib/classes/public/LightImageData.ts @@ -3,10 +3,10 @@ import { Vector3 } from '../Vector3'; export class LightImageData { - texture: UUID = UUID.zero(); - params: Vector3 = Vector3.getZero(); + public texture: UUID = UUID.zero(); + public params: Vector3 = Vector3.getZero(); - constructor(buf: Buffer, pos: number, length: number) + public constructor(buf: Buffer, pos: number, length: number) { if (length >= 28) { @@ -15,12 +15,14 @@ export class LightImageData this.params = new Vector3(buf, pos); } } - writeToBuffer(buf: Buffer, pos: number): void + + public writeToBuffer(buf: Buffer, pos: number): void { this.texture.writeToBuffer(buf, pos); pos = pos + 16; this.params.writeToBuffer(buf, pos, false); } - getBuffer(): Buffer + + public getBuffer(): Buffer { const buf = Buffer.allocUnsafe(28); this.writeToBuffer(buf, 0); diff --git a/lib/classes/public/Material.ts b/lib/classes/public/Material.ts index 6963aee..3142c9b 100644 --- a/lib/classes/public/Material.ts +++ b/lib/classes/public/Material.ts @@ -5,109 +5,110 @@ import { Utils } from '../Utils'; export class Material { - alphaMaskCutoff: number; - diffuseAlphaMode: number; - envIntensity: number; - normMap: UUID; - normOffsetX: number; - normOffsetY: number; - normRepeatX: number; - normRepeatY: number; - normRotation: number; - specColor: Color4; - specExp: number; - specMap: UUID; - specOffsetX: number; - specOffsetY: number; - specRepeatX: number; - specRepeatY: number; - specRotation: number; + public alphaMaskCutoff: number; + public diffuseAlphaMode: number; + public envIntensity: number; + public normMap: UUID; + public normOffsetX: number; + public normOffsetY: number; + public normRepeatX: number; + public normRepeatY: number; + public normRotation: number; + public specColor: Color4; + public specExp: number; + public specMap: UUID; + public specOffsetX: number; + public specOffsetY: number; + public specRepeatX: number; + public specRepeatY: number; + public specRotation: number; - static fromLLSD(llsd: string): Material + public static fromLLSD(llsd: string): Material { const parsed = LLSD.LLSD.parseXML(llsd); return this.fromLLSDObject(parsed); } - static fromLLSDObject(parsed: any): Material + + public static fromLLSDObject(parsed: any): Material { const material = new Material(); - if (parsed['AlphaMaskCutoff'] !== undefined) + if (parsed.AlphaMaskCutoff !== undefined) { - material.alphaMaskCutoff = parsed['AlphaMaskCutoff']; + material.alphaMaskCutoff = parsed.AlphaMaskCutoff; } - if (parsed['DiffuseAlphaMode'] !== undefined) + if (parsed.DiffuseAlphaMode !== undefined) { - material.diffuseAlphaMode = parsed['DiffuseAlphaMode']; + material.diffuseAlphaMode = parsed.DiffuseAlphaMode; } - if (parsed['EnvIntensity'] !== undefined) + if (parsed.EnvIntensity !== undefined) { - material.envIntensity = parsed['EnvIntensity']; + material.envIntensity = parsed.EnvIntensity; } - if (parsed['NormMap'] !== undefined) + if (parsed.NormMap !== undefined) { - material.normMap = new UUID(parsed['NormMap'].toString()) + material.normMap = new UUID(parsed.NormMap.toString()) } - if (parsed['NormOffsetX'] !== undefined) + if (parsed.NormOffsetX !== undefined) { - material.normOffsetX = parsed['NormOffsetX']; + material.normOffsetX = parsed.NormOffsetX; } - if (parsed['NormOffsetY'] !== undefined) + if (parsed.NormOffsetY !== undefined) { - material.normOffsetY = parsed['NormOffsetY']; + material.normOffsetY = parsed.NormOffsetY; } - if (parsed['NormRepeatX'] !== undefined) + if (parsed.NormRepeatX !== undefined) { - material.normRepeatX = parsed['NormRepeatX']; + material.normRepeatX = parsed.NormRepeatX; } - if (parsed['NormRepeatY'] !== undefined) + if (parsed.NormRepeatY !== undefined) { - material.normRepeatY = parsed['NormRepeatY']; + material.normRepeatY = parsed.NormRepeatY; } - if (parsed['NormRotation'] !== undefined) + if (parsed.NormRotation !== undefined) { - material.normRotation = parsed['NormRotation']; + material.normRotation = parsed.NormRotation; } - if (parsed['SpecColor'] !== undefined && Array.isArray(parsed['SpecColor']) && parsed['SpecColor'].length > 3) + if (parsed.SpecColor !== undefined && Array.isArray(parsed.SpecColor) && parsed.SpecColor.length > 3) { material.specColor = new Color4([ - parsed['SpecColor'][0], - parsed['SpecColor'][1], - parsed['SpecColor'][2], - parsed['SpecColor'][3] + parsed.SpecColor[0], + parsed.SpecColor[1], + parsed.SpecColor[2], + parsed.SpecColor[3] ]); } - if (parsed['SpecExp'] !== undefined) + if (parsed.SpecExp !== undefined) { - material.specExp = parsed['SpecExp']; + material.specExp = parsed.SpecExp; } - if (parsed['SpecMap'] !== undefined) + if (parsed.SpecMap !== undefined) { - material.specMap = new UUID(parsed['SpecMap'].toString()) + material.specMap = new UUID(parsed.SpecMap.toString()) } - if (parsed['SpecOffsetX'] !== undefined) + if (parsed.SpecOffsetX !== undefined) { - material.specOffsetX = parsed['SpecOffsetX']; + material.specOffsetX = parsed.SpecOffsetX; } - if (parsed['SpecOffsetY'] !== undefined) + if (parsed.SpecOffsetY !== undefined) { - material.specOffsetY = parsed['SpecOffsetY']; + material.specOffsetY = parsed.SpecOffsetY; } - if (parsed['SpecRepeatX'] !== undefined) + if (parsed.SpecRepeatX !== undefined) { - material.specRepeatX = parsed['SpecRepeatX']; + material.specRepeatX = parsed.SpecRepeatX; } - if (parsed['SpecRepeatY'] !== undefined) + if (parsed.SpecRepeatY !== undefined) { - material.specRepeatY = parsed['SpecRepeatY']; + material.specRepeatY = parsed.SpecRepeatY; } - if (parsed['SpecRotation'] !== undefined) + if (parsed.SpecRotation !== undefined) { - material.specRotation = parsed['SpecRotation']; + material.specRotation = parsed.SpecRotation; } return material; } - toLLSDObject(): any + public toLLSDObject(): any { return { 'AlphaMaskCutoff': this.alphaMaskCutoff, @@ -135,18 +136,18 @@ export class Material }; } - toLLSD(): string + public toLLSD(): string { - return LLSD.LLSD.formatXML(this.toLLSDObject()); + return String(LLSD.LLSD.formatXML(this.toLLSDObject())); } - async toAsset(uuid: UUID): Promise + public async toAsset(uuid: UUID): Promise { const asset = { 'ID': new LLSD.UUID(uuid.toString()), 'Material': this.toLLSD() }; const binary = LLSD.LLSD.formatBinary(asset); - return await Utils.deflate(Buffer.from(binary.toArray())); + return Utils.deflate(Buffer.from(binary.toArray())); } } diff --git a/lib/classes/public/MeshData.ts b/lib/classes/public/MeshData.ts index b03e1f4..2f986b7 100644 --- a/lib/classes/public/MeshData.ts +++ b/lib/classes/public/MeshData.ts @@ -3,10 +3,10 @@ import { SculptType } from '../../enums/SculptType'; export class MeshData { - meshData: UUID = UUID.zero(); - type: SculptType = SculptType.None; + public meshData: UUID = UUID.zero(); + public type: SculptType = SculptType.None; - constructor(buf?: Buffer, pos?: number, length?: number) + public constructor(buf?: Buffer, pos?: number, length?: number) { if (buf !== undefined && pos !== undefined && length !== undefined) { @@ -18,12 +18,15 @@ export class MeshData } } } - writeToBuffer(buf: Buffer, pos: number): void + + public writeToBuffer(buf: Buffer, pos: number): void { - this.meshData.writeToBuffer(buf, pos); pos = pos + 16; + this.meshData.writeToBuffer(buf, pos); + pos = pos + 16; buf.writeUInt8(this.type, pos); } - getBuffer(): Buffer + + public getBuffer(): Buffer { const buf = Buffer.allocUnsafe(17); this.writeToBuffer(buf, 0); diff --git a/lib/classes/public/Parcel.ts b/lib/classes/public/Parcel.ts index 08a5fa6..b70fc71 100644 --- a/lib/classes/public/Parcel.ts +++ b/lib/classes/public/Parcel.ts @@ -1,85 +1,85 @@ -import { Vector3 } from '../Vector3'; -import { UUID } from '../UUID'; +import type { Vector3 } from '../Vector3'; +import type { UUID } from '../UUID'; import * as builder from 'xmlbuilder'; import { ParcelFlags } from '../../enums/ParcelFlags'; -import { Region } from '../Region'; +import type { Region } from '../Region'; export class Parcel { - LocalID: number; - ParcelID: UUID; + public LocalID: number; + public ParcelID: UUID; - RegionDenyAgeUnverified: boolean; + public RegionDenyAgeUnverified: boolean; - MediaDesc: string; - MediaWidth: number; - MediaHeight: number; - MediaLoop: number; - MediaType: string; - ObscureMedia: number; - ObscureMusic: number; + public MediaDesc: string; + public MediaWidth: number; + public MediaHeight: number; + public MediaLoop: number; + public MediaType: string; + public ObscureMedia: number; + public ObscureMusic: number; - AABBMax: Vector3; - AABBMin: Vector3; - AnyAVSounds: boolean; - Area: number; - AuctionID: number; - AuthBuyerID: UUID; - Bitmap: Buffer; - Category: number; - ClaimDate: number; - ClaimPrice: number; - Desc: string; - Dwell: number; - GroupAVSounds: boolean; - GroupID: UUID; - GroupPrims: number; - IsGroupOwned: boolean; - LandingType: number; - MaxPrims: number; - MediaAutoScale: number; - MediaID: UUID; - MediaURL: string; - MusicURL: string; - Name: string; - OtherCleanTime: number; - OtherCount: number; - OtherPrims: number; - OwnerID: UUID; - OwnerPrims: number; - ParcelFlags: ParcelFlags; - ParcelPrimBonus: number; - PassHours: number; - PassPrice: number; - PublicCount: number; - RegionDenyAnonymous: boolean; - RegionDenyIdentified: boolean; - RegionDenyTransacted: boolean; - RegionPushOverride: boolean; - RentPrice: number; - RequestResult: number; - SalePrice: number; - SeeAvs: boolean; - SelectedPrims: number; - SelfCount: number; - SequenceID: number; - SimWideMaxPrims: number; - SimWideTotalPrims: number; - SnapSelection: boolean; - SnapshotID: UUID; - Status: number; - TotalPrims: number; - UserLocation: Vector3; - UserLookAt: Vector3; + public AABBMax: Vector3; + public AABBMin: Vector3; + public AnyAVSounds: boolean; + public Area: number; + public AuctionID: number; + public AuthBuyerID: UUID; + public Bitmap: Buffer; + public Category: number; + public ClaimDate: number; + public ClaimPrice: number; + public Desc: string; + public Dwell: number; + public GroupAVSounds: boolean; + public GroupID: UUID; + public GroupPrims: number; + public IsGroupOwned: boolean; + public LandingType: number; + public MaxPrims: number; + public MediaAutoScale: number; + public MediaID: UUID; + public MediaURL: string; + public MusicURL: string; + public Name: string; + public OtherCleanTime: number; + public OtherCount: number; + public OtherPrims: number; + public OwnerID: UUID; + public OwnerPrims: number; + public ParcelFlags: ParcelFlags; + public ParcelPrimBonus: number; + public PassHours: number; + public PassPrice: number; + public PublicCount: number; + public RegionDenyAnonymous: boolean; + public RegionDenyIdentified: boolean; + public RegionDenyTransacted: boolean; + public RegionPushOverride: boolean; + public RentPrice: number; + public RequestResult: number; + public SalePrice: number; + public SeeAvs: boolean; + public SelectedPrims: number; + public SelfCount: number; + public SequenceID: number; + public SimWideMaxPrims: number; + public SimWideTotalPrims: number; + public SnapSelection: boolean; + public SnapshotID: UUID; + public Status: number; + public TotalPrims: number; + public UserLocation: Vector3; + public UserLookAt: Vector3; - RegionAllowAccessOverride: boolean; + public RegionAllowAccessOverride: boolean; - constructor(private region: Region) + public constructor(private readonly region: Region) { } - canIRez(): boolean + public canIRez(): boolean { if (this.ParcelFlags & ParcelFlags.CreateObjects) { @@ -96,7 +96,7 @@ export class Parcel return false; } - exportXML(): string + public exportXML(): string { const document = builder.create('LandData'); document.ele('Area', this.Area); diff --git a/lib/classes/public/ReflectionProbeData.ts b/lib/classes/public/ReflectionProbeData.ts index e1df434..8b13d16 100644 --- a/lib/classes/public/ReflectionProbeData.ts +++ b/lib/classes/public/ReflectionProbeData.ts @@ -1,4 +1,4 @@ -import { ReflectionProbeFlags } from './ReflectionProbeFlags'; +import type { ReflectionProbeFlags } from './ReflectionProbeFlags'; export class ReflectionProbeData { @@ -6,7 +6,7 @@ export class ReflectionProbeData public clipDistance = 0.0; public flags: ReflectionProbeFlags = 0 as ReflectionProbeFlags; - constructor(buf?: Buffer, pos?: number, length?: number) + public constructor(buf?: Buffer, pos?: number, length?: number) { if (buf !== undefined && pos !== undefined && length !== undefined) { @@ -21,7 +21,7 @@ export class ReflectionProbeData } } - writeToBuffer(buf: Buffer, pos: number): void + public writeToBuffer(buf: Buffer, pos: number): void { buf.writeFloatLE(this.ambiance, pos); pos = pos + 4; @@ -30,7 +30,7 @@ export class ReflectionProbeData buf.writeUInt8(this.flags, pos); } - getBuffer(): Buffer + public getBuffer(): Buffer { const buf = Buffer.allocUnsafe(9); this.writeToBuffer(buf, 0); diff --git a/lib/classes/public/ReflectionProbeFlags.ts b/lib/classes/public/ReflectionProbeFlags.ts index ad32a25..dd8c8ba 100644 --- a/lib/classes/public/ReflectionProbeFlags.ts +++ b/lib/classes/public/ReflectionProbeFlags.ts @@ -3,3 +3,4 @@ export enum ReflectionProbeFlags FLAG_BOX_VOLUME = 0x01, FLAG_DYNAMIC = 0x02, } + diff --git a/lib/classes/public/RegionEnvironment.ts b/lib/classes/public/RegionEnvironment.ts index 7c7b4ad..b9c16e1 100644 --- a/lib/classes/public/RegionEnvironment.ts +++ b/lib/classes/public/RegionEnvironment.ts @@ -1,75 +1,163 @@ -import { UUID } from '../UUID'; -import { Vector4 } from '../Vector4'; -import { Color4 } from '../Color4'; -import { Vector2 } from '../Vector2'; -import { SkyPreset } from './interfaces/SkyPreset'; -import { WaterPreset } from './interfaces/WaterPreset'; -import { XMLNode } from 'xmlbuilder'; - -export class RegionEnvironment -{ - regionID: UUID; - dayCycleKeyframes: { - time: number, - preset: string - }[]; - skyPresets: { - [key: string]: SkyPreset - } = {}; - water: WaterPreset; - - getXML(xml: XMLNode): void - { - const env = xml.ele('Environment'); - const dayCycle = env.ele('DayCycle'); - for (const keyFrame of this.dayCycleKeyframes) - { - const kf = dayCycle.ele('KeyFrame'); - kf.ele('Time', keyFrame.time); - kf.ele('Preset', keyFrame.preset); - } - const skyPresets = env.ele('SkyPresets'); - for (const presetKey of Object.keys(this.skyPresets)) - { - const preset = this.skyPresets[presetKey]; - const pre = skyPresets.ele('Preset'); - pre.att('name', presetKey); - Vector4.getXML(pre.ele('Ambient'), preset.ambient); - Vector4.getXML(pre.ele('BlueDensity'), preset.blueDensity); - Vector4.getXML(pre.ele('BlueHorizon'), preset.blueHorizon); - Color4.getXML(pre.ele('CloudColor'), preset.cloudColor); - Vector4.getXML(pre.ele('CloudPosDensity1'), preset.cloudPosDensity1); - Vector4.getXML(pre.ele('CloudPosDensity2'), preset.cloudPosDensity2); - Vector4.getXML(pre.ele('CloudScale'), preset.cloudScale); - Vector2.getXML(pre.ele('CloudScrollRate'), preset.cloudScrollRate); - Vector4.getXML(pre.ele('CloudShadow'), preset.cloudScale); - Vector4.getXML(pre.ele('DensityMultiplier'), preset.cloudScale); - Vector4.getXML(pre.ele('DistanceMultiplier'), preset.cloudScale); - pre.ele('EastAngle', preset.eastAngle); - const cloudScroll = pre.ele('EnableCloudScroll'); - cloudScroll.ele('X', preset.enableCloudScroll.x); - cloudScroll.ele('Y', preset.enableCloudScroll.y); - Vector4.getXML(pre.ele('Gamma'), preset.gamma); - Vector4.getXML(pre.ele('Glow'), preset.glow); - Vector4.getXML(pre.ele('HazeDensity'), preset.hazeDensity); - Vector4.getXML(pre.ele('HazeHorizon'), preset.hazeHorizon); - Vector4.getXML(pre.ele('LightNormal'), preset.lightNormal); - Vector4.getXML(pre.ele('MaxY'), preset.maxY); - pre.ele('StarBrightness', preset.starBrightness); - pre.ele('SunAngle', preset.sunAngle); - Color4.getXML(pre.ele('SunLightColor'), preset.sunlightColor); - } - const water = env.ele('Water'); - water.ele('BlurMultiplier', this.water.blurMultiplier); - water.ele('FresnelOffset', this.water.fresnelOffset); - water.ele('FresnelScale', this.water.fresnelScale); - UUID.getXML(water.ele('NormalMap'), this.water.normalMap); - water.ele('ScaleAbove', this.water.scaleAbove); - water.ele('ScaleBelow', this.water.scaleBelow); - water.ele('UnderWaterFogMod', this.water.underWaterFogMod); - Color4.getXML(water.ele('WaterFogColor'), this.water.waterFogColor); - water.ele('WaterFogDensity', this.water.waterFogDensity); - Vector2.getXML(water.ele('Wave1Dir'), this.water.wave1Dir); - Vector2.getXML(water.ele('Wave2Dir'), this.water.wave2Dir); - } -} +import type { LLSDType } from '../llsd/LLSDType'; +import { LLSDMap } from '../llsd/LLSDMap'; +import { LLSDInteger } from '../llsd/LLSDInteger'; +import { UUID } from '../UUID'; +import type { SettingsConfigLLSD } from '../LLSettings'; +import { LLSettings } from '../LLSettings'; +import { LLSDArray } from '../llsd/LLSDArray'; +import { LLSD } from '../llsd/LLSD'; + +export class RegionEnvironment +{ + public regionID?: UUID; + public parcelID?: number | UUID; + public isDefault?: boolean; + public envVersion?: number; + public trackAltitudes?: [number, number, number]; + public dayOffset?: number; + public dayNames?: string[]; + public dayLength?: number; + public dayHash?: number; + public dayCycle?: LLSettings; + + public constructor(data: LLSDType) + { + if (data instanceof LLSDMap) + { + const d = data as LLSDMap & { + success?: boolean; + environment?: LLSDMap & { + day_cycle?: LLSDMap, + day_hash?: LLSDInteger, + day_length?: LLSDInteger, + day_names?: string[], + day_offset?: LLSDInteger, + env_version?: LLSDInteger, + is_default?: boolean, + parcel_id?: LLSDInteger | UUID, + region_id?: UUID, + track_altitudes?: [LLSDInteger, LLSDInteger, LLSDInteger] + } + }; + if (!d.success || !d.environment) + { + throw new Error('Failed to parse region settings'); + } + const env = d.environment; + if (env.day_cycle) + { + this.dayCycle = new LLSettings(env.day_cycle); + } + if (env.day_hash) + { + this.dayHash = env.day_hash.valueOf(); + } + if (env.day_length) + { + this.dayLength = env.day_length.valueOf(); + } + if (env.day_names) + { + this.dayNames = LLSDArray.toStringArray(env.day_names); + } + if (env.day_offset) + { + this.dayOffset = env.day_offset.valueOf(); + } + if (env.env_version) + { + this.envVersion = env.env_version.valueOf(); + } + if (env.is_default) + { + this.isDefault = env.is_default; + } + if (env.parcel_id) + { + if (env.parcel_id instanceof UUID) + { + this.parcelID = env.parcel_id; + } + else + { + this.parcelID = Number(env.parcel_id.valueOf()); + } + } + if (env.region_id) + { + this.regionID = env.region_id; + } + if (env.track_altitudes) + { + if (env.track_altitudes.length === 3) + { + this.trackAltitudes = [ + env.track_altitudes[0].valueOf(), + env.track_altitudes[1].valueOf(), + env.track_altitudes[2].valueOf(), + ]; + } + } + } + } + + public toNotation(): string + { + const envMap = new LLSDMap(); + if (this.dayCycle !== undefined) + { + envMap.set('day_cycle', LLSettings.encodeSettings(this.dayCycle)); + } + if (this.dayHash !== undefined) + { + envMap.set('day_hash', new LLSDInteger(this.dayHash)); + } + if (this.dayLength !== undefined) + { + envMap.set('day_length', new LLSDInteger(this.dayLength)); + } + if (this.dayNames !== undefined) + { + envMap.set('day_names', this.dayNames); + } + if (this.dayOffset !== undefined) + { + envMap.set('day_offset', new LLSDInteger(this.dayOffset)); + } + if (this.envVersion !== undefined) + { + envMap.set('env_version', new LLSDInteger(this.envVersion)); + } + if (this.isDefault !== undefined) + { + envMap.set('is_default', this.isDefault); + } + if (this.parcelID !== undefined) + { + if (typeof this.parcelID === 'number') + { + envMap.set('parcel_id', new LLSDInteger(this.parcelID)); + } + else + { + envMap.set('parcel_id', this.parcelID); + } + } + if (this.regionID !== undefined) + { + envMap.set('region_id', this.regionID); + } + if (this.trackAltitudes !== undefined) + { + const arr: LLSDInteger[] = []; + for(const val of this.trackAltitudes) + { + arr.push(new LLSDInteger(val)); + } + envMap.set('track_altitudes', arr); + } + + return LLSD.toNotation(envMap); + } + +} diff --git a/lib/classes/public/RenderMaterialData.ts b/lib/classes/public/RenderMaterialData.ts index d8a9bce..791a341 100644 --- a/lib/classes/public/RenderMaterialData.ts +++ b/lib/classes/public/RenderMaterialData.ts @@ -5,7 +5,7 @@ export class RenderMaterialData { public params: RenderMaterialParam[] = []; - constructor(buf?: Buffer, pos?: number, length?: number) + public constructor(buf?: Buffer, pos?: number, length?: number) { let localPos = 0; if (buf !== undefined && pos !== undefined && length !== undefined) @@ -31,7 +31,7 @@ export class RenderMaterialData } } - writeToBuffer(buf: Buffer, pos: number): void + public writeToBuffer(buf: Buffer, pos: number): void { buf.writeUInt8(this.params.length, pos++); for (const param of this.params) @@ -42,7 +42,7 @@ export class RenderMaterialData } } - getBuffer(): Buffer + public getBuffer(): Buffer { const buf = Buffer.allocUnsafe(1 + (this.params.length * 17)); this.writeToBuffer(buf, 0); diff --git a/lib/classes/public/RenderMaterialParam.ts b/lib/classes/public/RenderMaterialParam.ts index c457cd7..2b0d2d3 100644 --- a/lib/classes/public/RenderMaterialParam.ts +++ b/lib/classes/public/RenderMaterialParam.ts @@ -1,4 +1,4 @@ -import { UUID } from '../UUID'; +import type { UUID } from '../UUID'; export class RenderMaterialParam { diff --git a/lib/classes/public/SculptData.ts b/lib/classes/public/SculptData.ts index 5150847..cde71f1 100644 --- a/lib/classes/public/SculptData.ts +++ b/lib/classes/public/SculptData.ts @@ -3,10 +3,10 @@ import { SculptType } from '../../enums/SculptType'; export class SculptData { - texture: UUID = UUID.zero(); - type: SculptType = SculptType.None; + public texture: UUID = UUID.zero(); + public type: SculptType = SculptType.None; - constructor(buf?: Buffer, pos?: number, length?: number) + public constructor(buf?: Buffer, pos?: number, length?: number) { if (buf !== undefined && pos !== undefined && length !== undefined) { @@ -18,12 +18,14 @@ export class SculptData } } } - writeToBuffer(buf: Buffer, pos: number): void + + public writeToBuffer(buf: Buffer, pos: number): void { this.texture.writeToBuffer(buf, pos); pos = pos + 16; buf.writeUInt8(this.type, pos); } - getBuffer(): Buffer + + public getBuffer(): Buffer { const buf = Buffer.allocUnsafe(17); this.writeToBuffer(buf, 0); diff --git a/lib/classes/public/TextureAnim.ts b/lib/classes/public/TextureAnim.ts index eb20a30..a244bae 100644 --- a/lib/classes/public/TextureAnim.ts +++ b/lib/classes/public/TextureAnim.ts @@ -3,14 +3,14 @@ import { Vector2 } from '../Vector2'; export class TextureAnim { - textureAnimFlags: TextureAnimFlags = 0; - textureAnimFace = 0; - textureAnimSize = Vector2.getZero(); - textureAnimStart = 0; - textureAnimLength = 0; - textureAnimRate = 0; + public textureAnimFlags: TextureAnimFlags = 0; + public textureAnimFace = 0; + public textureAnimSize = Vector2.getZero(); + public textureAnimStart = 0; + public textureAnimLength = 0; + public textureAnimRate = 0; - static from(buf: Buffer): TextureAnim + public static from(buf: Buffer): TextureAnim { const obj = new TextureAnim(); let animPos = 0; @@ -30,9 +30,9 @@ export class TextureAnim } return obj; } - toBuffer(): Buffer + public toBuffer(): Buffer { - if (this.textureAnimFlags === 0 && this.textureAnimFace === 0 && this.textureAnimStart === 0 && this.textureAnimLength === 0 && this.textureAnimRate === 0) + if (this.textureAnimFlags === TextureAnimFlags.ANIM_OFF && this.textureAnimFace === 0 && this.textureAnimStart === 0 && this.textureAnimLength === 0 && this.textureAnimRate === 0) { return Buffer.allocUnsafe(0); } @@ -47,7 +47,7 @@ export class TextureAnim buf.writeFloatLE(this.textureAnimRate, animPos); return buf; } - toBase64(): string + public toBase64(): string { const bin = this.toBuffer(); return bin.toString('base64'); diff --git a/lib/classes/public/interfaces/GlobalPosition.ts b/lib/classes/public/interfaces/GlobalPosition.ts index 5a2564b..53eb26f 100644 --- a/lib/classes/public/interfaces/GlobalPosition.ts +++ b/lib/classes/public/interfaces/GlobalPosition.ts @@ -1,4 +1,4 @@ -import * as Long from 'long'; +import type * as Long from 'long'; export interface GlobalPosition { diff --git a/lib/classes/public/interfaces/LLPhysicsConvex.ts b/lib/classes/public/interfaces/LLPhysicsConvex.ts index 841d43d..54c790d 100644 --- a/lib/classes/public/interfaces/LLPhysicsConvex.ts +++ b/lib/classes/public/interfaces/LLPhysicsConvex.ts @@ -1,4 +1,4 @@ -import { Vector3 } from '../../Vector3'; +import type { Vector3 } from '../../Vector3'; export interface LLPhysicsConvex { diff --git a/lib/classes/public/interfaces/LLSkin.ts b/lib/classes/public/interfaces/LLSkin.ts index c7e9ad6..5e61c1f 100644 --- a/lib/classes/public/interfaces/LLSkin.ts +++ b/lib/classes/public/interfaces/LLSkin.ts @@ -1,10 +1,10 @@ -import { TSMMat4 } from '../../../tsm/mat4'; +import type { Matrix4 } from '../../Matrix4'; export interface LLSkin { jointNames: string[]; - bindShapeMatrix: TSMMat4; - inverseBindMatrix: TSMMat4[]; - altInverseBindMatrix?: TSMMat4[]; - pelvisOffset?: TSMMat4; + bindShapeMatrix: Matrix4; + inverseBindMatrix: Matrix4[]; + altInverseBindMatrix?: Matrix4[]; + pelvisOffset?: Matrix4; } diff --git a/lib/classes/public/interfaces/LLSubMesh.ts b/lib/classes/public/interfaces/LLSubMesh.ts index c2d7524..330b59a 100644 --- a/lib/classes/public/interfaces/LLSubMesh.ts +++ b/lib/classes/public/interfaces/LLSubMesh.ts @@ -1,9 +1,9 @@ -import { Vector3 } from '../../Vector3'; -import { Vector2 } from '../../Vector2'; +import type { Vector2 } from '../../Vector2'; +import type { Vector3 } from '../../Vector3'; export interface LLSubMesh { - noGeometry?: true, + noGeometry?: boolean, position?: Vector3[], positionDomain?: { min: Vector3, @@ -16,6 +16,6 @@ export interface LLSubMesh max: Vector2 } triangleList?: number[], - weights?: { [key: number]: number }[], + weights?: Record[], } diff --git a/lib/classes/public/interfaces/MapLocation.ts b/lib/classes/public/interfaces/MapLocation.ts index 6d23f04..ce5418a 100644 --- a/lib/classes/public/interfaces/MapLocation.ts +++ b/lib/classes/public/interfaces/MapLocation.ts @@ -1,6 +1,6 @@ -import { UUID } from '../../UUID'; -import * as Long from 'long'; -import { Vector2 } from '../../Vector2'; +import type { UUID } from '../../UUID'; +import type * as Long from 'long'; +import type { Vector2 } from '../../Vector2'; export interface MapLocation { diff --git a/lib/classes/public/interfaces/SkyPreset.ts b/lib/classes/public/interfaces/SkyPreset.ts deleted file mode 100644 index 1f51d45..0000000 --- a/lib/classes/public/interfaces/SkyPreset.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Vector4 } from '../../Vector4'; -import { Color4 } from '../../Color4'; -import { Vector2 } from '../../Vector2'; - -export interface SkyPreset -{ - ambient: Vector4, - blueDensity: Vector4, - blueHorizon: Vector4, - cloudColor: Color4, - cloudPosDensity1: Vector4, - cloudPosDensity2: Vector4, - cloudScale: Vector4, - cloudScrollRate: Vector2, - cloudShadow: Vector4, - densityMultiplier: Vector4, - distanceMultiplier: Vector4, - eastAngle: number, - enableCloudScroll: { - x: boolean, - y: boolean - }, - gamma: Vector4, - glow: Vector4, - hazeDensity: Vector4, - hazeHorizon: Vector4, - lightNormal: Vector4, - maxY: Vector4, - starBrightness: number, - sunAngle: number, - sunlightColor: Color4 -} diff --git a/lib/classes/public/interfaces/WaterPreset.ts b/lib/classes/public/interfaces/WaterPreset.ts deleted file mode 100644 index 00d0355..0000000 --- a/lib/classes/public/interfaces/WaterPreset.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Vector3 } from '../../Vector3'; -import { UUID } from '../../UUID'; -import { Color4 } from '../../Color4'; -import { Vector2 } from '../../Vector2'; - -export interface WaterPreset -{ - blurMultiplier: number, - fresnelOffset: number, - fresnelScale: number, - normalScale: Vector3, - normalMap: UUID, - scaleAbove: number, - scaleBelow: number, - underWaterFogMod: number, - waterFogColor: Color4, - waterFogDensity: number, - wave1Dir: Vector2, - wave2Dir: Vector2 -} diff --git a/lib/enums/AssetType.ts b/lib/enums/AssetType.ts index 26f32c5..affe8e6 100644 --- a/lib/enums/AssetType.ts +++ b/lib/enums/AssetType.ts @@ -1,6 +1,7 @@ export enum AssetType { - Unknown = -1, + LegacyMaterial = -2, + None = -1, Texture = 0, Sound = 1, CallingCard = 2, @@ -9,7 +10,6 @@ export enum AssetType Clothing = 5, Object = 6, Notecard = 7, - Folder = 8, Category = 8, LSLText = 10, LSLBytecode = 11, @@ -24,10 +24,13 @@ export enum AssetType Simstate = 22, Link = 24, LinkFolder = 25, - MarketplaceFolder = 26, Widget = 40, Person = 46, Mesh = 49, Settings = 56, - Material = 57 + Material = 57, + GLTF = 58, + GLTFBin = 59, + + Unknown = 255, } diff --git a/lib/enums/AssetTypeLL.ts b/lib/enums/AssetTypeLL.ts deleted file mode 100644 index 29745d5..0000000 --- a/lib/enums/AssetTypeLL.ts +++ /dev/null @@ -1,28 +0,0 @@ -export enum AssetTypeLL -{ - texture = 0, - sound = 1, - callcard = 2, - landmark = 3, - script = 4, - clothing = 5, - object = 6, - notecard = 7, - category = 8, - lsltext = 10, - lslbyte = 11, - txtr_tga = 12, - bodypart = 13, - snd_wav = 17, - img_tga = 18, - jpeg = 19, - animatn = 20, - gesture = 21, - simstate = 22, - link = 24, - link_f = 25, - widget = 40, - person = 45, - mesh = 49, - settings = 56, -} diff --git a/lib/enums/AttachmentPoint.ts b/lib/enums/AttachmentPoint.ts index dee09a0..c1f91f6 100644 --- a/lib/enums/AttachmentPoint.ts +++ b/lib/enums/AttachmentPoint.ts @@ -45,7 +45,6 @@ export enum AttachmentPoint // Extras Neck, // FS: Neck Root = 40, // FS: Avatar Center - Center = 40, LefHandRing1 = 41, // FS: Left Ring Finger RightHandRing1 = 42, // FS: Right Ring Finger TailBase = 43, // FS: Tail Base diff --git a/lib/enums/HTTPAssets.ts b/lib/enums/HTTPAssets.ts deleted file mode 100644 index bdbe947..0000000 --- a/lib/enums/HTTPAssets.ts +++ /dev/null @@ -1,25 +0,0 @@ -export enum HTTPAssets -{ - ASSET_TEXTURE = 'texture', - ASSET_SOUND = 'sound', - ASSET_ANIMATION = 'animatn', - ASSET_GESTURE = 'gesture', - ASSET_LANDMARK = 'landmark', - ASSET_CALLINGCARD = 'callcard', - ASSET_SCRIPT = 'script', - ASSET_CLOTHING = 'clothing', - ASSET_OBJECT = 'object', - ASSET_NOTECARD = 'notecard', - ASSET_CATEGORY = 'category', - ASSET_LSL_TEXT = 'lsltext', - ASSET_LSL_BYTECODE = 'lslbyte', - ASSET_BODYPART = 'bodypart', - ASSET_SIMSTATE = 'simstate', - ASSET_LINK = 'link', - ASSET_LINK_FOLDER = 'link_f', - ASSET_MESH = 'mesh', - ASSET_WIDGET = 'widget', - ASSET_PERSON = 'person', - ASSET_MATERIAL = 'material', - ASSET_SETTINGS = 'settings', -} diff --git a/lib/enums/InventoryType.ts b/lib/enums/InventoryType.ts index 6844b97..bb176a9 100644 --- a/lib/enums/InventoryType.ts +++ b/lib/enums/InventoryType.ts @@ -5,15 +5,11 @@ export enum InventoryType Sound = 1, CallingCard = 2, Landmark = 3, - Script = 4, - Clothing = 5, Object = 6, Notecard = 7, Category = 8, - Folder = 8, RootCategory = 9, LSL = 10, - Bodypart = 13, Snapshot = 15, Attachment = 17, Wearable = 18, @@ -24,4 +20,6 @@ export enum InventoryType Person = 24, Settings = 25, Material = 26, + GLTF = 27, + GLTFBin = 28 } diff --git a/lib/enums/LayerType.ts b/lib/enums/LayerType.ts index c595405..e3baa0e 100644 --- a/lib/enums/LayerType.ts +++ b/lib/enums/LayerType.ts @@ -3,7 +3,7 @@ export enum LayerType Land = 0x4C, LandExtended = 0x4D, Water = 0x57, - WaterExtended = 0x57, + WaterExtended = 0x58, Wind = 0x37, WindExtended = 0x39, Cloud = 0x38, diff --git a/lib/events/AvatarPropertiesReplyEvent.ts b/lib/events/AvatarPropertiesReplyEvent.ts index a89d975..34b7cb5 100644 --- a/lib/events/AvatarPropertiesReplyEvent.ts +++ b/lib/events/AvatarPropertiesReplyEvent.ts @@ -1,14 +1,14 @@ -import { UUID } from '../classes/UUID'; +import type { UUID } from '../classes/UUID'; export class AvatarPropertiesReplyEvent { - ImageID: UUID; - FLImageID: UUID; - PartnerID: UUID; - AboutText: string; - FLAboutText: string; - BornOn: string; - ProfileURL: string; - CharterMember: number; - Flags: number; + public ImageID: UUID; + public FLImageID: UUID; + public PartnerID: UUID; + public AboutText: string; + public FLAboutText: string; + public BornOn: string; + public ProfileURL: string; + public CharterMember: number; + public Flags: number; } diff --git a/lib/events/BalanceUpdatedEvent.ts b/lib/events/BalanceUpdatedEvent.ts index b62a8f0..7484413 100644 --- a/lib/events/BalanceUpdatedEvent.ts +++ b/lib/events/BalanceUpdatedEvent.ts @@ -1,10 +1,10 @@ -import { UUID } from '../classes/UUID'; -import { MoneyTransactionType } from '../enums/MoneyTransactionType'; +import type { UUID } from '../classes/UUID'; +import type { MoneyTransactionType } from '../enums/MoneyTransactionType'; export class BalanceUpdatedEvent { - balance: number; - transaction: { + public balance: number; + public transaction: { type: MoneyTransactionType, success: boolean, from: UUID, diff --git a/lib/events/BulkUpdateInventoryEvent.ts b/lib/events/BulkUpdateInventoryEvent.ts index 62dabe2..4cf5cf2 100644 --- a/lib/events/BulkUpdateInventoryEvent.ts +++ b/lib/events/BulkUpdateInventoryEvent.ts @@ -1,8 +1,8 @@ -import { InventoryFolder } from '../classes/InventoryFolder'; -import { InventoryItem } from '../classes/InventoryItem'; +import type { InventoryFolder } from '../classes/InventoryFolder'; +import type { InventoryItem } from '../classes/InventoryItem'; export class BulkUpdateInventoryEvent { - folderData: InventoryFolder[] = []; - itemData: InventoryItem[] = []; + public folderData: InventoryFolder[] = []; + public itemData: InventoryItem[] = []; } diff --git a/lib/events/ChatEvent.ts b/lib/events/ChatEvent.ts index df2916b..4ec0bb1 100644 --- a/lib/events/ChatEvent.ts +++ b/lib/events/ChatEvent.ts @@ -1,17 +1,17 @@ -import { ChatAudibleLevel } from '../enums/ChatAudible'; -import { ChatType } from '../enums/ChatType'; -import { UUID } from '../classes/UUID'; -import { ChatSourceType } from '../enums/ChatSourceType'; -import { Vector3 } from '../classes/Vector3'; +import type { ChatAudibleLevel } from '../enums/ChatAudible'; +import type { ChatType } from '../enums/ChatType'; +import type { UUID } from '../classes/UUID'; +import type { ChatSourceType } from '../enums/ChatSourceType'; +import type { Vector3 } from '../classes/Vector3'; export class ChatEvent { - from: UUID; - ownerID: UUID; - fromName: string; - chatType: ChatType; - sourceType: ChatSourceType; - audible: ChatAudibleLevel; - position: Vector3; - message: string; + public from: UUID; + public ownerID: UUID; + public fromName: string; + public chatType: ChatType; + public sourceType: ChatSourceType; + public audible: ChatAudibleLevel; + public position: Vector3; + public message: string; } diff --git a/lib/events/DisconnectEvent.ts b/lib/events/DisconnectEvent.ts index d694217..ee78f0a 100644 --- a/lib/events/DisconnectEvent.ts +++ b/lib/events/DisconnectEvent.ts @@ -1,5 +1,5 @@ export class DisconnectEvent { - requested: boolean; - message: string; + public requested: boolean; + public message: string; } diff --git a/lib/events/EventQueueStateChangeEvent.ts b/lib/events/EventQueueStateChangeEvent.ts index 3781d15..5f91612 100644 --- a/lib/events/EventQueueStateChangeEvent.ts +++ b/lib/events/EventQueueStateChangeEvent.ts @@ -1,4 +1,4 @@ export class EventQueueStateChangeEvent { - active: boolean; + public active: boolean; } diff --git a/lib/events/FriendOnlineEvent.ts b/lib/events/FriendOnlineEvent.ts index 16ab353..8f36c16 100644 --- a/lib/events/FriendOnlineEvent.ts +++ b/lib/events/FriendOnlineEvent.ts @@ -1,7 +1,7 @@ -import { Friend } from '../classes/public/Friend'; +import type { Friend } from '../classes/public/Friend'; export class FriendOnlineEvent { - friend: Friend; - online: boolean; + public friend: Friend; + public online: boolean; } diff --git a/lib/events/FriendRemovedEvent.ts b/lib/events/FriendRemovedEvent.ts index e80d147..9be31e8 100644 --- a/lib/events/FriendRemovedEvent.ts +++ b/lib/events/FriendRemovedEvent.ts @@ -1,6 +1,6 @@ -import { Friend } from '../classes/public/Friend'; +import type { Friend } from '../classes/public/Friend'; export class FriendRemovedEvent { - friend: Friend; + public friend: Friend; } diff --git a/lib/events/FriendRequestEvent.ts b/lib/events/FriendRequestEvent.ts index 0038d0f..22c4b9f 100644 --- a/lib/events/FriendRequestEvent.ts +++ b/lib/events/FriendRequestEvent.ts @@ -1,9 +1,9 @@ -import { UUID } from '../classes/UUID'; +import type { UUID } from '../classes/UUID'; export class FriendRequestEvent { - from: UUID; - fromName: string; - requestID: UUID; - message: string; + public from: UUID; + public fromName: string; + public requestID: UUID; + public message: string; } diff --git a/lib/events/FriendResponseEvent.ts b/lib/events/FriendResponseEvent.ts index 8134b84..947fc8e 100644 --- a/lib/events/FriendResponseEvent.ts +++ b/lib/events/FriendResponseEvent.ts @@ -1,10 +1,10 @@ -import { UUID } from '../classes/UUID'; +import type { UUID } from '../classes/UUID'; export class FriendResponseEvent { - from: UUID; - fromName: string; - message: string; - accepted: boolean; - requestID: UUID; + public from: UUID; + public fromName: string; + public message: string; + public accepted: boolean; + public requestID: UUID; } diff --git a/lib/events/FriendRightsEvent.ts b/lib/events/FriendRightsEvent.ts index f30884c..86f814a 100644 --- a/lib/events/FriendRightsEvent.ts +++ b/lib/events/FriendRightsEvent.ts @@ -1,9 +1,9 @@ -import { Friend } from '../classes/public/Friend'; -import { RightsFlags } from '../enums/RightsFlags'; +import type { Friend } from '../classes/public/Friend'; +import type { RightsFlags } from '../enums/RightsFlags'; export class FriendRightsEvent { - friend: Friend; - myRights: RightsFlags; - theirRights: RightsFlags; + public friend: Friend; + public myRights: RightsFlags; + public theirRights: RightsFlags; } diff --git a/lib/events/GroupChatClosedEvent.ts b/lib/events/GroupChatClosedEvent.ts index c865428..9f2f8c4 100644 --- a/lib/events/GroupChatClosedEvent.ts +++ b/lib/events/GroupChatClosedEvent.ts @@ -1,6 +1,6 @@ -import { UUID } from '../classes/UUID'; +import type { UUID } from '../classes/UUID'; export class GroupChatClosedEvent { - groupID: UUID; + public groupID: UUID; } diff --git a/lib/events/GroupChatEvent.ts b/lib/events/GroupChatEvent.ts index cb940fb..7bd8659 100644 --- a/lib/events/GroupChatEvent.ts +++ b/lib/events/GroupChatEvent.ts @@ -1,9 +1,9 @@ -import { UUID } from '../classes/UUID'; +import type { UUID } from '../classes/UUID'; export class GroupChatEvent { - groupID: UUID; - from: UUID; - fromName: string; - message: string; + public groupID: UUID; + public from: UUID; + public fromName: string; + public message: string; } diff --git a/lib/events/GroupChatSessionAgentListEvent.ts b/lib/events/GroupChatSessionAgentListEvent.ts index 8964688..a1b7c91 100644 --- a/lib/events/GroupChatSessionAgentListEvent.ts +++ b/lib/events/GroupChatSessionAgentListEvent.ts @@ -1,10 +1,10 @@ -import { UUID } from '../classes/UUID'; +import type { UUID } from '../classes/UUID'; export class GroupChatSessionAgentListEvent { - groupID: UUID; - agentID: UUID; - isModerator: boolean; - canVoiceChat: boolean; - entered: boolean; + public groupID: UUID; + public agentID: UUID; + public isModerator: boolean; + public canVoiceChat: boolean; + public entered: boolean; } diff --git a/lib/events/GroupChatSessionJoinEvent.ts b/lib/events/GroupChatSessionJoinEvent.ts index b425654..a8ad22e 100644 --- a/lib/events/GroupChatSessionJoinEvent.ts +++ b/lib/events/GroupChatSessionJoinEvent.ts @@ -1,7 +1,7 @@ -import { UUID } from '../classes/UUID'; +import type { UUID } from '../classes/UUID'; export class GroupChatSessionJoinEvent { - sessionID: UUID; - success: boolean; + public sessionID: UUID; + public success: boolean; } diff --git a/lib/events/GroupInviteEvent.ts b/lib/events/GroupInviteEvent.ts index 2629e59..450688e 100644 --- a/lib/events/GroupInviteEvent.ts +++ b/lib/events/GroupInviteEvent.ts @@ -1,9 +1,9 @@ -import { UUID } from '../classes/UUID'; +import type { UUID } from '../classes/UUID'; export class GroupInviteEvent { - from: UUID; - fromName: string; - message: string; - inviteID: UUID; + public from: UUID; + public fromName: string; + public message: string; + public inviteID: UUID; } diff --git a/lib/events/GroupNoticeEvent.ts b/lib/events/GroupNoticeEvent.ts index f631114..4f2d0ae 100644 --- a/lib/events/GroupNoticeEvent.ts +++ b/lib/events/GroupNoticeEvent.ts @@ -1,10 +1,10 @@ -import { UUID } from '../classes/UUID'; +import type { UUID } from '../classes/UUID'; export class GroupNoticeEvent { - groupID: UUID; - from: UUID; - fromName: string; - subject: string; - message: string; + public groupID: UUID; + public from: UUID; + public fromName: string; + public subject: string; + public message: string; } diff --git a/lib/events/GroupProfileReplyEvent.ts b/lib/events/GroupProfileReplyEvent.ts index 8011c0f..363e88e 100644 --- a/lib/events/GroupProfileReplyEvent.ts +++ b/lib/events/GroupProfileReplyEvent.ts @@ -1,22 +1,22 @@ -import { UUID } from '../classes/UUID'; -import * as Long from 'long'; +import type { UUID } from '../classes/UUID'; +import type * as Long from 'long'; export class GroupProfileReplyEvent { - GroupID: UUID; - Name: string; - Charter: string; - ShowInList: boolean; - MemberTitle: string; - PowersMask: Long; - InsigniaID: UUID; - FounderID: UUID; - MembershipFee: number; - OpenEnrollment: boolean; - Money: number; - GroupMembershipCount: number; - GroupRolesCount: number; - AllowPublish: boolean; - MaturePublish: boolean; - OwnerRole: UUID; + public GroupID: UUID; + public Name: string; + public Charter: string; + public ShowInList: boolean; + public MemberTitle: string; + public PowersMask: Long; + public InsigniaID: UUID; + public FounderID: UUID; + public MembershipFee: number; + public OpenEnrollment: boolean; + public Money: number; + public GroupMembershipCount: number; + public GroupRolesCount: number; + public AllowPublish: boolean; + public MaturePublish: boolean; + public OwnerRole: UUID; } diff --git a/lib/events/InstantMessageEvent.ts b/lib/events/InstantMessageEvent.ts index 94eacb2..46a39d7 100644 --- a/lib/events/InstantMessageEvent.ts +++ b/lib/events/InstantMessageEvent.ts @@ -1,13 +1,13 @@ -import { ChatSourceType } from '../enums/ChatSourceType'; -import { UUID } from '../classes/UUID'; -import { InstantMessageEventFlags } from '../enums/InstantMessageEventFlags'; +import type { ChatSourceType } from '../enums/ChatSourceType'; +import type { UUID } from '../classes/UUID'; +import type { InstantMessageEventFlags } from '../enums/InstantMessageEventFlags'; export class InstantMessageEvent { - source: ChatSourceType; - fromName: string; - from: UUID; - owner: UUID; - message: string; - flags: InstantMessageEventFlags; + public source: ChatSourceType; + public fromName: string; + public from: UUID; + public owner: UUID; + public message: string; + public flags: InstantMessageEventFlags; } diff --git a/lib/events/InventoryOfferedEvent.ts b/lib/events/InventoryOfferedEvent.ts index 70a314c..ef006b0 100644 --- a/lib/events/InventoryOfferedEvent.ts +++ b/lib/events/InventoryOfferedEvent.ts @@ -1,13 +1,13 @@ -import { UUID } from '../classes/UUID'; -import { ChatSourceType } from '../enums/ChatSourceType'; -import { AssetType } from '../enums/AssetType'; +import type { UUID } from '../classes/UUID'; +import type { ChatSourceType } from '../enums/ChatSourceType'; +import type { AssetType } from '../enums/AssetType'; export class InventoryOfferedEvent { - from: UUID; - fromName: string; - requestID: UUID; - message: string; - source: ChatSourceType; - type: AssetType; + public from: UUID; + public fromName: string; + public requestID: UUID; + public message: string; + public source: ChatSourceType; + public type: AssetType; } diff --git a/lib/events/InventoryResponseEvent.ts b/lib/events/InventoryResponseEvent.ts index 9e15fa2..fff1ce5 100644 --- a/lib/events/InventoryResponseEvent.ts +++ b/lib/events/InventoryResponseEvent.ts @@ -1,10 +1,10 @@ -import { UUID } from '../classes/UUID'; +import type { UUID } from '../classes/UUID'; export class InventoryResponseEvent { - from: UUID; - fromName: string; - message: string; - accepted: boolean; - requestID: UUID; + public from: UUID; + public fromName: string; + public message: string; + public accepted: boolean; + public requestID: UUID; } diff --git a/lib/events/LandStatsEvent.ts b/lib/events/LandStatsEvent.ts index 50bcc79..2542317 100644 --- a/lib/events/LandStatsEvent.ts +++ b/lib/events/LandStatsEvent.ts @@ -1,15 +1,15 @@ -import { LandStatReportType } from '../enums/LandStatReportType'; -import { LandStatFlags } from '../enums/LandStatFlags'; -import { Vector3 } from '../classes/Vector3'; -import { UUID } from '../classes/UUID'; +import type { LandStatReportType } from '../enums/LandStatReportType'; +import type { LandStatFlags } from '../enums/LandStatFlags'; +import type { Vector3 } from '../classes/Vector3'; +import type { UUID } from '../classes/UUID'; export class LandStatsEvent { - totalObjects: number; - reportType: LandStatReportType; - requestFlags: LandStatFlags; + public totalObjects: number; + public reportType: LandStatReportType; + public requestFlags: LandStatFlags; - objects: { + public objects: { position: Vector3, ownerName: string, score: number, diff --git a/lib/events/LureEvent.ts b/lib/events/LureEvent.ts index 8c00d4b..01df225 100644 --- a/lib/events/LureEvent.ts +++ b/lib/events/LureEvent.ts @@ -1,14 +1,14 @@ -import { UUID } from '../classes/UUID'; -import { Vector3 } from '../classes/Vector3'; +import type { UUID } from '../classes/UUID'; +import type { Vector3 } from '../classes/Vector3'; export class LureEvent { - from: UUID; - fromName: string; - lureMessage: string; - regionID: UUID; - position: Vector3; - gridX: number; - gridY: number; - lureID: UUID; + public from: UUID; + public fromName: string; + public lureMessage: string; + public regionID: UUID; + public position: Vector3; + public gridX: number; + public gridY: number; + public lureID: UUID; } diff --git a/lib/events/MapInfoRangeReplyEvent.ts b/lib/events/MapInfoRangeReplyEvent.ts index b0d1b5e..527d762 100644 --- a/lib/events/MapInfoRangeReplyEvent.ts +++ b/lib/events/MapInfoRangeReplyEvent.ts @@ -1,6 +1,6 @@ -import { MapBlock } from '../classes/MapBlock'; +import type { MapBlock } from '../classes/MapBlock'; export class MapInfoRangeReplyEvent { - regions: MapBlock[]; + public regions: MapBlock[]; } diff --git a/lib/events/MapInfoReplyEvent.ts b/lib/events/MapInfoReplyEvent.ts index 6e3d61e..c30e6bb 100644 --- a/lib/events/MapInfoReplyEvent.ts +++ b/lib/events/MapInfoReplyEvent.ts @@ -1,8 +1,8 @@ -import { MapBlock } from '../classes/MapBlock'; -import { Vector2 } from '../classes/Vector2'; +import type { MapBlock } from '../classes/MapBlock'; +import type { Vector2 } from '../classes/Vector2'; export class MapInfoReplyEvent { - block: MapBlock; - avatars: Vector2[] + public block: MapBlock; + public avatars: Vector2[] } diff --git a/lib/events/NewObjectEvent.ts b/lib/events/NewObjectEvent.ts index 8a6b56c..62bbd81 100644 --- a/lib/events/NewObjectEvent.ts +++ b/lib/events/NewObjectEvent.ts @@ -1,10 +1,10 @@ -import { GameObject } from '../classes/public/GameObject'; -import { UUID } from '../classes/UUID'; +import type { GameObject } from '../classes/public/GameObject'; +import type { UUID } from '../classes/UUID'; export class NewObjectEvent { - objectID: UUID; - localID: number; - object: GameObject; - createSelected: boolean; + public objectID: UUID; + public localID: number; + public object: GameObject; + public createSelected: boolean; } diff --git a/lib/events/ObjectKilledEvent.ts b/lib/events/ObjectKilledEvent.ts index f26cacc..9345ebd 100644 --- a/lib/events/ObjectKilledEvent.ts +++ b/lib/events/ObjectKilledEvent.ts @@ -1,9 +1,9 @@ -import { GameObject } from '../classes/public/GameObject'; -import { UUID } from '../classes/UUID'; +import type { GameObject } from '../classes/public/GameObject'; +import type { UUID } from '../classes/UUID'; export class ObjectKilledEvent { - objectID: UUID; - localID: number; - object: GameObject; + public objectID: UUID; + public localID: number; + public object: GameObject; } diff --git a/lib/events/ObjectPhysicsDataEvent.ts b/lib/events/ObjectPhysicsDataEvent.ts index 1f3a955..978d706 100644 --- a/lib/events/ObjectPhysicsDataEvent.ts +++ b/lib/events/ObjectPhysicsDataEvent.ts @@ -1,12 +1,12 @@ -import { PhysicsShapeType } from '../enums/PhysicsShapeType'; +import type { PhysicsShapeType } from '../enums/PhysicsShapeType'; export class ObjectPhysicsDataEvent { - localID: number; + public localID: number; - density: number; - friction: number; - gravityMultiplier: number; - physicsShapeType: PhysicsShapeType; - restitution: number; + public density: number; + public friction: number; + public gravityMultiplier: number; + public physicsShapeType: PhysicsShapeType; + public restitution: number; } diff --git a/lib/events/ObjectResolvedEvent.ts b/lib/events/ObjectResolvedEvent.ts index 6b363ae..8d522e7 100644 --- a/lib/events/ObjectResolvedEvent.ts +++ b/lib/events/ObjectResolvedEvent.ts @@ -1,6 +1,6 @@ -import { GameObject } from '../classes/public/GameObject'; +import type { GameObject } from '../classes/public/GameObject'; export class ObjectResolvedEvent { - object: GameObject + public object: GameObject } diff --git a/lib/events/ObjectUpdatedEvent.ts b/lib/events/ObjectUpdatedEvent.ts index 2bedbc8..563ec48 100644 --- a/lib/events/ObjectUpdatedEvent.ts +++ b/lib/events/ObjectUpdatedEvent.ts @@ -1,9 +1,9 @@ -import { GameObject } from '../classes/public/GameObject'; -import { UUID } from '../classes/UUID'; +import type { GameObject } from '../classes/public/GameObject'; +import type { UUID } from '../classes/UUID'; export class ObjectUpdatedEvent { - objectID: UUID; - localID: number; - object: GameObject; + public objectID: UUID; + public localID: number; + public object: GameObject; } diff --git a/lib/events/ParcelInfoReplyEvent.ts b/lib/events/ParcelInfoReplyEvent.ts index 6c9d018..7d2a037 100644 --- a/lib/events/ParcelInfoReplyEvent.ts +++ b/lib/events/ParcelInfoReplyEvent.ts @@ -1,19 +1,19 @@ -import { ParcelInfoFlags } from '../enums/ParcelInfoFlags'; -import { UUID } from '../classes/UUID'; -import { Vector3 } from '../classes/Vector3'; +import type { ParcelInfoFlags } from '../enums/ParcelInfoFlags'; +import type { UUID } from '../classes/UUID'; +import type { Vector3 } from '../classes/Vector3'; export class ParcelInfoReplyEvent { - OwnerID: UUID; - ParcelName: string; - ParcelDescription: string; - Area: number; - BillableArea: number; - Flags: ParcelInfoFlags; - GlobalCoordinates: Vector3; - RegionName: string; - SnapshotID: UUID; - Traffic: number; - SalePrice: number; - AuctionID: number; + public OwnerID: UUID; + public ParcelName: string; + public ParcelDescription: string; + public Area: number; + public BillableArea: number; + public Flags: ParcelInfoFlags; + public GlobalCoordinates: Vector3; + public RegionName: string; + public SnapshotID: UUID; + public Traffic: number; + public SalePrice: number; + public AuctionID: number; } diff --git a/lib/events/ParcelPropertiesEvent.ts b/lib/events/ParcelPropertiesEvent.ts index 9df628c..0da293e 100644 --- a/lib/events/ParcelPropertiesEvent.ts +++ b/lib/events/ParcelPropertiesEvent.ts @@ -1,72 +1,72 @@ -import { ParcelFlags } from '../enums/ParcelFlags'; -import { Vector3 } from '../classes/Vector3'; -import { UUID } from '../classes/UUID'; +import type { ParcelFlags } from '../enums/ParcelFlags'; +import type { Vector3 } from '../classes/Vector3'; +import type { UUID } from '../classes/UUID'; export class ParcelPropertiesEvent { - LocalID: number; + public LocalID: number; - RegionDenyAgeUnverified: boolean; + public RegionDenyAgeUnverified: boolean; - MediaDesc: string; - MediaWidth: number; - MediaHeight: number; - MediaLoop: number; - MediaType: string; - ObscureMedia: number; - ObscureMusic: number; + public MediaDesc: string; + public MediaWidth: number; + public MediaHeight: number; + public MediaLoop: number; + public MediaType: string; + public ObscureMedia: number; + public ObscureMusic: number; - AABBMax: Vector3; - AABBMin: Vector3; - AnyAVSounds: boolean; - Area: number; - AuctionID: number; - AuthBuyerID: UUID; - Bitmap: Buffer; - Category: number; - ClaimDate: number; - ClaimPrice: number; - Desc: string; - GroupAVSounds: boolean; - GroupID: UUID; - GroupPrims: number; - IsGroupOwned: boolean; - LandingType: number; - MaxPrims: number; - MediaAutoScale: number; - MediaID: UUID; - MediaURL: string; - MusicURL: string; - Name: string; - OtherCleanTime: number; - OtherCount: number; - OtherPrims: number; - OwnerID: UUID; - OwnerPrims: number; - ParcelFlags: ParcelFlags; - ParcelPrimBonus: number; - PassHours: number; - PassPrice: number; - PublicCount: number; - RegionDenyAnonymous: boolean; - RegionDenyIdentified: boolean; - RegionDenyTransacted: boolean; - RegionPushOverride: boolean; - RentPrice: number; - RequestResult: number; - SalePrice: number; - SeeAvs: boolean; - SelectedPrims: number; - SelfCount: number; - SequenceID: number; - SimWideMaxPrims: number; - SimWideTotalPrims: number; - SnapSelection: boolean; - SnapshotID: UUID; - Status: number; - TotalPrims: number; - UserLocation: Vector3; - UserLookAt: Vector3; + public AABBMax: Vector3; + public AABBMin: Vector3; + public AnyAVSounds: boolean; + public Area: number; + public AuctionID: number; + public AuthBuyerID: UUID; + public Bitmap: Buffer; + public Category: number; + public ClaimDate: number; + public ClaimPrice: number; + public Desc: string; + public GroupAVSounds: boolean; + public GroupID: UUID; + public GroupPrims: number; + public IsGroupOwned: boolean; + public LandingType: number; + public MaxPrims: number; + public MediaAutoScale: number; + public MediaID: UUID; + public MediaURL: string; + public MusicURL: string; + public Name: string; + public OtherCleanTime: number; + public OtherCount: number; + public OtherPrims: number; + public OwnerID: UUID; + public OwnerPrims: number; + public ParcelFlags: ParcelFlags; + public ParcelPrimBonus: number; + public PassHours: number; + public PassPrice: number; + public PublicCount: number; + public RegionDenyAnonymous: boolean; + public RegionDenyIdentified: boolean; + public RegionDenyTransacted: boolean; + public RegionPushOverride: boolean; + public RentPrice: number; + public RequestResult: number; + public SalePrice: number; + public SeeAvs: boolean; + public SelectedPrims: number; + public SelfCount: number; + public SequenceID: number; + public SimWideMaxPrims: number; + public SimWideTotalPrims: number; + public SnapSelection: boolean; + public SnapshotID: UUID; + public Status: number; + public TotalPrims: number; + public UserLocation: Vector3; + public UserLookAt: Vector3; - RegionAllowAccessOverride: boolean; + public RegionAllowAccessOverride: boolean; } diff --git a/lib/events/RegionInfoReplyEvent.ts b/lib/events/RegionInfoReplyEvent.ts index e76ac6b..e678590 100644 --- a/lib/events/RegionInfoReplyEvent.ts +++ b/lib/events/RegionInfoReplyEvent.ts @@ -1,15 +1,15 @@ -import * as Long from 'long'; -import { UUID } from '../classes/UUID'; +import type * as Long from 'long'; +import type { UUID } from '../classes/UUID'; export class RegionInfoReplyEvent { - X: number; - Y: number; - name: string; - access: number; - regionFlags: number; - waterHeight: number; - agents: number; - mapImageID: UUID; - handle: Long + public X: number; + public Y: number; + public name: string; + public access: number; + public regionFlags: number; + public waterHeight: number; + public agents: number; + public mapImageID: UUID; + public handle: Long } diff --git a/lib/events/ScriptDialogEvent.ts b/lib/events/ScriptDialogEvent.ts index 206f9ca..8d57161 100644 --- a/lib/events/ScriptDialogEvent.ts +++ b/lib/events/ScriptDialogEvent.ts @@ -1,14 +1,14 @@ -import { UUID } from '../classes/UUID'; +import type { UUID } from '../classes/UUID'; export class ScriptDialogEvent { - ObjectID: UUID; - FirstName: string; - LastName: string; - ObjectName: string; - Message: string; - ChatChannel: number; - ImageID: UUID; - Buttons: string[]; - Owners: UUID[]; + public ObjectID: UUID; + public FirstName: string; + public LastName: string; + public ObjectName: string; + public Message: string; + public ChatChannel: number; + public ImageID: UUID; + public Buttons: string[]; + public Owners: UUID[]; } diff --git a/lib/events/SelectedObjectEvent.ts b/lib/events/SelectedObjectEvent.ts index 19ca402..84bd44c 100644 --- a/lib/events/SelectedObjectEvent.ts +++ b/lib/events/SelectedObjectEvent.ts @@ -1,6 +1,6 @@ -import { GameObject } from '../classes/public/GameObject'; +import type { GameObject } from '../classes/public/GameObject'; export class SelectedObjectEvent { - object: GameObject + public object: GameObject } diff --git a/lib/events/SimStatsEvent.ts b/lib/events/SimStatsEvent.ts index 9b6fc1f..7e1bbf5 100644 --- a/lib/events/SimStatsEvent.ts +++ b/lib/events/SimStatsEvent.ts @@ -1,44 +1,44 @@ export class SimStatsEvent { - timeDilation: number; - fps: number; - physFPS: number; - agentUPS: number; - frameMS: number; - netMS: number; - simOtherMS: number; - simPhysicsMS: number; - agentMS: number; - imagesMS: number; - scriptMS: number; - numTasks: number; - numTasksActive: number; - numAgentMain: number; - numAgentChild: number; - numScriptsActive: number; - lslIPS: number; - inPPS: number; - outPPS: number; - pendingDownloads: number; - pendingUploads: number; - virtualSizeKB: number; - residentSizeKB: number; - pendingLocalUploads: number; - totalUnackedBytes: number; - physicsPinnedTasks: number; - physicsLODTasks: number; - simPhysicsStepMS: number; - simPhysicsShapeMS: number; - simPhysicsOtherMS: number; - simPhysicsMemory: number; - scriptEPS: number; - simSpareTime: number; - simSleepTime: number; - ioPumpTime: number; - pctScriptsRun: number; - regionIdle: number; - regionIdlePossible: number; - simAIStepTimeMS: number; - skippedAISilStepsPS: number; - pctSteppedCharacters: number; + public timeDilation: number; + public fps: number; + public physFPS: number; + public agentUPS: number; + public frameMS: number; + public netMS: number; + public simOtherMS: number; + public simPhysicsMS: number; + public agentMS: number; + public imagesMS: number; + public scriptMS: number; + public numTasks: number; + public numTasksActive: number; + public numAgentMain: number; + public numAgentChild: number; + public numScriptsActive: number; + public lslIPS: number; + public inPPS: number; + public outPPS: number; + public pendingDownloads: number; + public pendingUploads: number; + public virtualSizeKB: number; + public residentSizeKB: number; + public pendingLocalUploads: number; + public totalUnackedBytes: number; + public physicsPinnedTasks: number; + public physicsLODTasks: number; + public simPhysicsStepMS: number; + public simPhysicsShapeMS: number; + public simPhysicsOtherMS: number; + public simPhysicsMemory: number; + public scriptEPS: number; + public simSpareTime: number; + public simSleepTime: number; + public ioPumpTime: number; + public pctScriptsRun: number; + public regionIdle: number; + public regionIdlePossible: number; + public simAIStepTimeMS: number; + public skippedAISilStepsPS: number; + public pctSteppedCharacters: number; } diff --git a/lib/events/TeleportEvent.ts b/lib/events/TeleportEvent.ts index 42f05c8..57e1c34 100644 --- a/lib/events/TeleportEvent.ts +++ b/lib/events/TeleportEvent.ts @@ -1,10 +1,10 @@ -import { TeleportEventType } from '../enums/TeleportEventType'; +import type { TeleportEventType } from '../enums/TeleportEventType'; export class TeleportEvent { - eventType: TeleportEventType; - message: string; - simIP: string; - simPort: number; - seedCapability: string; + public eventType: TeleportEventType; + public message: string; + public simIP: string; + public simPort: number; + public seedCapability: string; } diff --git a/lib/index.ts b/lib/index.ts index 69830af..5521aaa 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -3,8 +3,7 @@ import { LoginParameters } from './classes/LoginParameters'; import { ClientEvents } from './classes/ClientEvents'; import { BVH } from './classes/BVH'; -import { AssetType } from './enums/AssetType'; -import { HTTPAssets } from './enums/HTTPAssets'; +import { AssetType } from './enums/AssetType';; import { InstantMessageEventFlags } from './enums/InstantMessageEventFlags'; import { InstantMessageEvent } from './events/InstantMessageEvent'; import { ChatSourceType } from './enums/ChatSourceType'; @@ -48,8 +47,8 @@ import { Avatar } from './classes/public/Avatar'; import { RightsFlags } from './enums/RightsFlags'; import { FriendRightsEvent } from './events/FriendRightsEvent'; import { FriendRemovedEvent } from './events/FriendRemovedEvent'; -import { GlobalPosition } from './classes/public/interfaces/GlobalPosition'; -import { MapLocation } from './classes/public/interfaces/MapLocation'; +import type { GlobalPosition } from './classes/public/interfaces/GlobalPosition'; +import type { MapLocation } from './classes/public/interfaces/MapLocation'; import { Vector2 } from './classes/Vector2'; import { ParticleDataFlags } from './enums/ParticleDataFlags'; import { TextureFlags } from './enums/TextureFlags'; @@ -61,7 +60,6 @@ import { ObjectPhysicsDataEvent } from './events/ObjectPhysicsDataEvent'; import { ParcelPropertiesEvent } from './events/ParcelPropertiesEvent'; import { PrimFlags } from './enums/PrimFlags'; import { TextureEntry } from './classes/TextureEntry'; -import { RegionEnvironment } from './classes/public/RegionEnvironment'; import { Parcel } from './classes/public/Parcel'; import { Material } from './classes/public/Material'; import { GameObject } from './classes/public/GameObject'; @@ -70,8 +68,6 @@ import { LightData } from './classes/public/LightData'; import { FlexibleData } from './classes/public/FlexibleData'; import { MeshData } from './classes/public/MeshData'; import { SculptData } from './classes/public/SculptData'; -import { SkyPreset } from './classes/public/interfaces/SkyPreset'; -import { WaterPreset } from './classes/public/interfaces/WaterPreset'; import { NewObjectEvent } from './events/NewObjectEvent'; import { ObjectKilledEvent } from './events/ObjectKilledEvent'; import { ObjectUpdatedEvent } from './events/ObjectUpdatedEvent'; @@ -109,131 +105,8 @@ import { LLGLTFMaterial } from './classes/LLGLTFMaterial'; import { ExtendedMeshData } from './classes/public/ExtendedMeshData'; import { ReflectionProbeData } from './classes/public/ReflectionProbeData'; import { RenderMaterialData } from './classes/public/RenderMaterialData'; -import { LLSettings, LLSettingsHazeConfig, LLSettingsTermConfig } from './classes/LLSettings'; +import type { LLSettingsHazeConfig, LLSettingsTermConfig } from './classes/LLSettings'; +import { LLSettings } from './classes/LLSettings'; -export { - Bot, - LoginParameters, - ClientEvents, - BVH, - ChatSourceType, - UUID, - Vector3, - Vector2, - Utils, - TextureEntry, - LLWearable, - LLLindenText, - LLGLTFMaterial, - LLGesture, - LLGestureAnimationStep, - LLGestureSoundStep, - LLGestureChatStep, - LLGestureWaitStep, - LLSettings, - LLSettingsTermConfig, - LLSettingsHazeConfig, - ParticleSystem, - ExtraParams, - - // Flags - AgentFlags, - BotOptionFlags, - CompressedFlags, - ControlFlags, - DecodeFlags, - InstantMessageEventFlags, - InventoryItemFlags, - LoginFlags, - MessageFlags, - ParcelInfoFlags, - PacketFlags, - RegionProtocolFlags, - SoundFlags, - TeleportFlags, - RegionFlags, - RightsFlags, - ParticleDataFlags, - TextureFlags, - PrimFlags, - ParcelFlags, - SimAccessFlags, - TextureAnimFlags, - - // Enums - InventoryType, - AssetType, - HTTPAssets, - FolderType, - TransferStatus, - SourcePattern, - BlendFunc, - PCode, - Bumpiness, - HoleType, - LayerType, - MappingType, - PhysicsShapeType, - ProfileShape, - SculptType, - Shininess, - LLGestureStepType, - - - // Events - ChatEvent, - DisconnectEvent, - FriendRequestEvent, - FriendResponseEvent, - GroupChatEvent, - GroupChatSessionAgentListEvent, - GroupChatSessionJoinEvent, - GroupNoticeEvent, - GroupInviteEvent, - InstantMessageEvent, - InventoryOfferedEvent, - LureEvent, - MapInfoRangeReplyEvent, - MapInfoReplyEvent, - ParcelInfoReplyEvent, - RegionInfoReplyEvent, - TeleportEvent, - ScriptDialogEvent, - EventQueueStateChangeEvent, - FriendOnlineEvent, - FriendRightsEvent, - FriendRemovedEvent, - ObjectPhysicsDataEvent, - ParcelPropertiesEvent, - NewObjectEvent, - ObjectKilledEvent, - ObjectUpdatedEvent, - GroupProfileReplyEvent, - AvatarPropertiesReplyEvent, - - // Public Classes - Avatar, - Friend, - FlexibleData, - LightData, - LightImageData, - GameObject, - Material, - Parcel, - RegionEnvironment, - SculptData, - MeshData, - LLMesh, - InventoryItem, - TarReader, - TarWriter, - ExtendedMeshData, - ReflectionProbeData, - RenderMaterialData, - - // Public Interfaces - GlobalPosition, - MapLocation, - SkyPreset, - WaterPreset -}; +export type { LLSettingsTermConfig, LLSettingsHazeConfig, GlobalPosition, MapLocation }; +export { Bot, LoginParameters, ClientEvents, BVH, ChatSourceType, UUID, Vector3, Vector2, Utils, TextureEntry, LLWearable, LLLindenText, LLGLTFMaterial, LLGesture, LLGestureAnimationStep, LLGestureSoundStep, LLGestureChatStep, LLGestureWaitStep, LLSettings, ParticleSystem, ExtraParams, AgentFlags, BotOptionFlags, CompressedFlags, ControlFlags, DecodeFlags, InstantMessageEventFlags, InventoryItemFlags, LoginFlags, MessageFlags, ParcelInfoFlags, PacketFlags, RegionProtocolFlags, SoundFlags, TeleportFlags, RegionFlags, RightsFlags, ParticleDataFlags, TextureFlags, PrimFlags, ParcelFlags, SimAccessFlags, TextureAnimFlags, InventoryType, AssetType, FolderType, TransferStatus, SourcePattern, BlendFunc, PCode, Bumpiness, HoleType, LayerType, MappingType, PhysicsShapeType, ProfileShape, SculptType, Shininess, LLGestureStepType, ChatEvent, DisconnectEvent, FriendRequestEvent, FriendResponseEvent, GroupChatEvent, GroupChatSessionAgentListEvent, GroupChatSessionJoinEvent, GroupNoticeEvent, GroupInviteEvent, InstantMessageEvent, InventoryOfferedEvent, LureEvent, MapInfoRangeReplyEvent, MapInfoReplyEvent, ParcelInfoReplyEvent, RegionInfoReplyEvent, TeleportEvent, ScriptDialogEvent, EventQueueStateChangeEvent, FriendOnlineEvent, FriendRightsEvent, FriendRemovedEvent, ObjectPhysicsDataEvent, ParcelPropertiesEvent, NewObjectEvent, ObjectKilledEvent, ObjectUpdatedEvent, GroupProfileReplyEvent, AvatarPropertiesReplyEvent, Avatar, Friend, FlexibleData, LightData, LightImageData, GameObject, Material, Parcel, SculptData, MeshData, LLMesh, InventoryItem, TarReader, TarWriter, ExtendedMeshData, ReflectionProbeData, RenderMaterialData }; diff --git a/lib/tools/LLMeshTest.ts b/lib/tools/LLMeshTest.ts index 62c8653..22c9547 100644 --- a/lib/tools/LLMeshTest.ts +++ b/lib/tools/LLMeshTest.ts @@ -6,5 +6,8 @@ const buf = fs.readFileSync(fileName); LLMesh.from(buf).then((mesh: LLMesh) => { console.log(mesh); +}).catch((e: unknown) => +{ + console.error(e); }); diff --git a/lib/tools/LLSDSettingsTest.ts b/lib/tools/LLSDSettingsTest.ts index c70af13..26e7af0 100644 --- a/lib/tools/LLSDSettingsTest.ts +++ b/lib/tools/LLSDSettingsTest.ts @@ -2,13 +2,20 @@ import * as fs from 'fs/promises' import * as path from 'path'; import { LLSettings } from '../classes/LLSettings'; -async function test() +async function test(): Promise { const settings = await fs.readFile(path.join(__dirname, '..', '..', '..', 'testing', 'water.bin')); const set = new LLSettings(settings.toString('utf-8')); console.log(JSON.stringify(set, null, 4)); + + + const asset = 'PD9sbHNkL2JpbmFyeT8+CnsAAAADawAAAARkYXRhcwAAAc57ImFzc2V0Ijp7InZlcnNpb24iOiIyLjAifSwiaW1hZ2VzIjpbeyJ1cmkiOiI3YWVlMzAyYy1kZjJjLWJlNTQtMWFhMC0xY2E3NDQ3N2M0OGYifSx7InVyaSI6IjZmODVlMDJlLWJkMTYtYWQyZC0wNTFlLTc3MWUyNTllYjZmOCJ9LHsidXJpIjoiY2ZlYTc5YzgtMTQ3Mi01NjJlLWM0MjMtYjEzNDViMDgwNWVmIn0seyJ1cmkiOiJjZmVhNzljOC0xNDcyLTU2MmUtYzQyMy1iMTM0NWIwODA1ZWYifV0sIm1hdGVyaWFscyI6W3sibm9ybWFsVGV4dHVyZSI6eyJpbmRleCI6MX0sIm9jY2x1c2lvblRleHR1cmUiOnsiaW5kZXgiOjN9LCJwYnJNZXRhbGxpY1JvdWdobmVzcyI6eyJiYXNlQ29sb3JUZXh0dXJlIjp7ImluZGV4IjowfSwibWV0YWxsaWNSb3VnaG5lc3NUZXh0dXJlIjp7ImluZGV4IjoyfX19XSwidGV4dHVyZXMiOlt7InNvdXJjZSI6MH0seyJzb3VyY2UiOjF9LHsic291cmNlIjoyfSx7InNvdXJjZSI6M31dfQprAAAABHR5cGVzAAAACEdMVEYgMi4wawAAAAd2ZXJzaW9ucwAAAAMxLjF9'; + const buf = Buffer.from(asset, 'base64'); + const str = buf.toString('utf-8'); + const settings2 = new LLSettings(str); + console.log(settings2); } -test().catch((e) => +test().catch((e: unknown) => { console.error(e); }); diff --git a/lib/tools/TextureEntryTest.ts b/lib/tools/TextureEntryTest.ts new file mode 100644 index 0000000..0cc0678 --- /dev/null +++ b/lib/tools/TextureEntryTest.ts @@ -0,0 +1,5 @@ +import { TextureEntry } from '../classes/TextureEntry'; + +const n = Buffer.from('CAD82CAD56668BF13CAC8CDAB40DD20C000000000000000000400000000040000000000000000000000000000000006FB900FB728A247A72B75BC55CD5257A00', 'hex'); +const entry = TextureEntry.from(n); +console.log(JSON.stringify(entry, null, 4)); diff --git a/lib/tsm/common.ts b/lib/tsm/common.ts deleted file mode 100644 index 7e2691d..0000000 --- a/lib/tsm/common.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @fileoverview TSM - A TypeScript vector and matrix math library - * @author Matthias Ferch - * @version 0.6 - */ - -/* - * Copyright (c) 2012 Matthias Ferch - * - * Project homepage: https://github.com/matthiasferch/tsm - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * - * 2. Altered source versions must be plainly marked as such, and must not - * be misrepresented as being the original software. - * - * 3. This notice may not be removed or altered from any source - * distribution. - */ - -const EPSILON = 0.000001; - - - diff --git a/lib/tsm/mat2.ts b/lib/tsm/mat2.ts deleted file mode 100644 index fe0210f..0000000 --- a/lib/tsm/mat2.ts +++ /dev/null @@ -1,276 +0,0 @@ -/** - * @fileoverview TSM - A TypeScript vector and matrix math library - * @author Matthias Ferch - * @version 0.6 - */ - -/* - * Copyright (c) 2012 Matthias Ferch - * - * Project homepage: https://github.com/matthiasferch/tsm - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * - * 2. Altered source versions must be plainly marked as such, and must not - * be misrepresented as being the original software. - * - * 3. This notice may not be removed or altered from any source - * distribution. - */ - - -import { TSMVec2 } from './vec2'; - -export class TSMMat2 -{ - static identity = new TSMMat2().setIdentity(); - - private values = new Float32Array(4); - - static product(m1: TSMMat2, m2: TSMMat2, result: TSMMat2 | null = null): TSMMat2 - { - const a11 = m1.at(0), - a12 = m1.at(1), - a21 = m1.at(2), - a22 = m1.at(3); - - if (result) - { - result.init([ - a11 * m2.at(0) + a12 * m2.at(2), - a11 * m2.at(1) + a12 * m2.at(3), - a21 * m2.at(0) + a22 * m2.at(2), - a21 * m2.at(1) + a22 * m2.at(3) - ]); - - return result; - } - else - { - return new TSMMat2([ - a11 * m2.at(0) + a12 * m2.at(2), - a11 * m2.at(1) + a12 * m2.at(3), - a21 * m2.at(0) + a22 * m2.at(2), - a21 * m2.at(1) + a22 * m2.at(3) - ]); - } - } - - constructor(values: number[] | null = null) - { - if (values) - { - this.init(values); - } - } - - at(index: number): number - { - return this.values[index]; - } - - init(values: number[]): TSMMat2 - { - for (let i = 0; i < 4; i++) - { - this.values[i] = values[i]; - } - - return this; - } - - reset(): void - { - for (let i = 0; i < 4; i++) - { - this.values[i] = 0; - } - } - - copy(dest: TSMMat2 | null = null): TSMMat2 - { - if (!dest) - { - dest = new TSMMat2(); - } - - for (let i = 0; i < 4; i++) - { - dest.values[i] = this.values[i]; - } - - return dest; - } - - all(): number[] - { - const data: number[] = []; - for (let i = 0; i < 4; i++) - { - data[i] = this.values[i]; - } - - return data; - } - - row(index: number): number[] - { - return [ - this.values[index * 2 + 0], - this.values[index * 2 + 1] - ]; - } - - col(index: number): number[] - { - return [ - this.values[index], - this.values[index + 2] - ]; - } - - equals(matrix: TSMMat2, threshold = EPSILON): boolean - { - for (let i = 0; i < 4; i++) - { - if (Math.abs(this.values[i] - matrix.at(i)) > threshold) - { - return false; - } - } - - return true; - } - - determinant(): number - { - return this.values[0] * this.values[3] - this.values[2] * this.values[1]; - } - - setIdentity(): TSMMat2 - { - this.values[0] = 1; - this.values[1] = 0; - this.values[2] = 0; - this.values[3] = 1; - - return this; - } - - transpose(): TSMMat2 - { - const temp = this.values[1]; - - this.values[1] = this.values[2]; - this.values[2] = temp; - - return this; - } - - inverse(): TSMMat2 | null - { - let det = this.determinant(); - - if (!det) - { - return null; - } - - det = 1.0 / det; - - this.values[0] = det * (this.values[3]); - this.values[1] = det * (-this.values[1]); - this.values[2] = det * (-this.values[2]); - this.values[3] = det * (this.values[0]); - - return this; - } - - multiply(matrix: TSMMat2): TSMMat2 - { - const a11 = this.values[0], - a12 = this.values[1], - a21 = this.values[2], - a22 = this.values[3]; - - this.values[0] = a11 * matrix.at(0) + a12 * matrix.at(2); - this.values[1] = a11 * matrix.at(1) + a12 * matrix.at(3); - this.values[2] = a21 * matrix.at(0) + a22 * matrix.at(2); - this.values[3] = a21 * matrix.at(1) + a22 * matrix.at(3); - - return this; - } - - rotate(angle: number): TSMMat2 - { - const a11 = this.values[0], - a12 = this.values[1], - a21 = this.values[2], - a22 = this.values[3]; - - const sin = Math.sin(angle), - cos = Math.cos(angle); - - this.values[0] = a11 * cos + a12 * sin; - this.values[1] = a11 * -sin + a12 * cos; - this.values[2] = a21 * cos + a22 * sin; - this.values[3] = a21 * -sin + a22 * cos; - - return this; - } - - multiplyTSMVec2(vector: TSMVec2, result: TSMVec2 | null = null): TSMVec2 - { - const x = vector.x, - y = vector.y; - - if (result) - { - result.xy = [ - x * this.values[0] + y * this.values[1], - x * this.values[2] + y * this.values[3] - ]; - - return result; - } - else - { - return new TSMVec2([ - x * this.values[0] + y * this.values[1], - x * this.values[2] + y * this.values[3] - ]); - } - } - - scale(vector: TSMVec2): TSMMat2 - { - const a11 = this.values[0], - a12 = this.values[1], - a21 = this.values[2], - a22 = this.values[3]; - - const x = vector.x, - y = vector.y; - - this.values[0] = a11 * x; - this.values[1] = a12 * y; - this.values[2] = a21 * x; - this.values[3] = a22 * y; - - return this; - } -} - - - diff --git a/lib/tsm/mat3.ts b/lib/tsm/mat3.ts deleted file mode 100644 index 5a5c679..0000000 --- a/lib/tsm/mat3.ts +++ /dev/null @@ -1,512 +0,0 @@ -/** - * @fileoverview TSM - A TypeScript vector and matrix math library - * @author Matthias Ferch - * @version 0.6 - */ - -/* - * Copyright (c) 2012 Matthias Ferch - * - * Project homepage: https://github.com/matthiasferch/tsm - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * - * 2. Altered source versions must be plainly marked as such, and must not - * be misrepresented as being the original software. - * - * 3. This notice may not be removed or altered from any source - * distribution. - */ - -import { TSMVec2 } from './vec2'; -import { TSMVec3 } from './vec3'; -import { TSMMat4 } from './mat4'; -import { TSMQuat } from './quat'; - -export class TSMMat3 -{ - static identity = new TSMMat3().setIdentity(); - - private values = new Float32Array(9); - - static product(m1: TSMMat3, m2: TSMMat3, result: TSMMat3 | null = null): TSMMat3 - { - const a00 = m1.at(0), a01 = m1.at(1), a02 = m1.at(2), - a10 = m1.at(3), a11 = m1.at(4), a12 = m1.at(5), - a20 = m1.at(6), a21 = m1.at(7), a22 = m1.at(8); - - const b00 = m2.at(0), b01 = m2.at(1), b02 = m2.at(2), - b10 = m2.at(3), b11 = m2.at(4), b12 = m2.at(5), - b20 = m2.at(6), b21 = m2.at(7), b22 = m2.at(8); - - if (result) - { - result.init([ - b00 * a00 + b01 * a10 + b02 * a20, - b00 * a01 + b01 * a11 + b02 * a21, - b00 * a02 + b01 * a12 + b02 * a22, - - b10 * a00 + b11 * a10 + b12 * a20, - b10 * a01 + b11 * a11 + b12 * a21, - b10 * a02 + b11 * a12 + b12 * a22, - - b20 * a00 + b21 * a10 + b22 * a20, - b20 * a01 + b21 * a11 + b22 * a21, - b20 * a02 + b21 * a12 + b22 * a22 - ]); - - return result; - } - else - { - return new TSMMat3([ - b00 * a00 + b01 * a10 + b02 * a20, - b00 * a01 + b01 * a11 + b02 * a21, - b00 * a02 + b01 * a12 + b02 * a22, - - b10 * a00 + b11 * a10 + b12 * a20, - b10 * a01 + b11 * a11 + b12 * a21, - b10 * a02 + b11 * a12 + b12 * a22, - - b20 * a00 + b21 * a10 + b22 * a20, - b20 * a01 + b21 * a11 + b22 * a21, - b20 * a02 + b21 * a12 + b22 * a22 - ]); - } - } - - constructor(values: number[] | null = null) - { - if (values) - { - this.init(values); - } - } - - at(index: number): number - { - return this.values[index]; - } - - init(values: number[]): TSMMat3 - { - for (let i = 0; i < 9; i++) - { - this.values[i] = values[i]; - } - - return this; - } - - reset(): void - { - for (let i = 0; i < 9; i++) - { - this.values[i] = 0; - } - } - - copy(dest: TSMMat3 | null = null): TSMMat3 - { - if (!dest) - { - dest = new TSMMat3(); - } - - for (let i = 0; i < 9; i++) - { - dest.values[i] = this.values[i]; - } - - return dest; - } - - all(): number[] - { - const data: number[] = []; - for (let i = 0; i < 9; i++) - { - data[i] = this.values[i]; - } - - return data; - } - - row(index: number): number[] - { - return [ - this.values[index * 3 + 0], - this.values[index * 3 + 1], - this.values[index * 3 + 2] - ]; - } - - col(index: number): number[] - { - return [ - this.values[index], - this.values[index + 3], - this.values[index + 6] - ]; - } - - equals(matrix: TSMMat3, threshold = EPSILON): boolean - { - for (let i = 0; i < 9; i++) - { - if (Math.abs(this.values[i] - matrix.at(i)) > threshold) - { - return false; - } - } - - return true; - } - - determinant(): number - { - const a00 = this.values[0], a01 = this.values[1], a02 = this.values[2], - a10 = this.values[3], a11 = this.values[4], a12 = this.values[5], - a20 = this.values[6], a21 = this.values[7], a22 = this.values[8]; - - const det01 = a22 * a11 - a12 * a21, - det11 = -a22 * a10 + a12 * a20, - det21 = a21 * a10 - a11 * a20; - - return a00 * det01 + a01 * det11 + a02 * det21; - } - - setIdentity(): TSMMat3 - { - this.values[0] = 1; - this.values[1] = 0; - this.values[2] = 0; - this.values[3] = 0; - this.values[4] = 1; - this.values[5] = 0; - this.values[6] = 0; - this.values[7] = 0; - this.values[8] = 1; - - return this; - } - - transpose(): TSMMat3 - { - const temp01 = this.values[1], - temp02 = this.values[2], - temp12 = this.values[5]; - - this.values[1] = this.values[3]; - this.values[2] = this.values[6]; - this.values[3] = temp01; - this.values[5] = this.values[7]; - this.values[6] = temp02; - this.values[7] = temp12; - - return this; - } - - inverse(): TSMMat3 | null - { - const a00 = this.values[0], a01 = this.values[1], a02 = this.values[2], - a10 = this.values[3], a11 = this.values[4], a12 = this.values[5], - a20 = this.values[6], a21 = this.values[7], a22 = this.values[8]; - - const det01 = a22 * a11 - a12 * a21, - det11 = -a22 * a10 + a12 * a20, - det21 = a21 * a10 - a11 * a20; - - let det = a00 * det01 + a01 * det11 + a02 * det21; - - if (!det) - { - return null; - } - - det = 1.0 / det; - - this.values[0] = det01 * det; - this.values[1] = (-a22 * a01 + a02 * a21) * det; - this.values[2] = (a12 * a01 - a02 * a11) * det; - this.values[3] = det11 * det; - this.values[4] = (a22 * a00 - a02 * a20) * det; - this.values[5] = (-a12 * a00 + a02 * a10) * det; - this.values[6] = det21 * det; - this.values[7] = (-a21 * a00 + a01 * a20) * det; - this.values[8] = (a11 * a00 - a01 * a10) * det; - - return this; - } - - multiply(matrix: TSMMat3): TSMMat3 - { - const a00 = this.values[0], a01 = this.values[1], a02 = this.values[2], - a10 = this.values[3], a11 = this.values[4], a12 = this.values[5], - a20 = this.values[6], a21 = this.values[7], a22 = this.values[8]; - - const b00 = matrix.at(0), b01 = matrix.at(1), b02 = matrix.at(2), - b10 = matrix.at(3), b11 = matrix.at(4), b12 = matrix.at(5), - b20 = matrix.at(6), b21 = matrix.at(7), b22 = matrix.at(8); - - this.values[0] = b00 * a00 + b01 * a10 + b02 * a20; - this.values[1] = b00 * a01 + b01 * a11 + b02 * a21; - this.values[2] = b00 * a02 + b01 * a12 + b02 * a22; - - this.values[3] = b10 * a00 + b11 * a10 + b12 * a20; - this.values[4] = b10 * a01 + b11 * a11 + b12 * a21; - this.values[5] = b10 * a02 + b11 * a12 + b12 * a22; - - this.values[6] = b20 * a00 + b21 * a10 + b22 * a20; - this.values[7] = b20 * a01 + b21 * a11 + b22 * a21; - this.values[8] = b20 * a02 + b21 * a12 + b22 * a22; - - return this; - } - - multiplyTSMVec2(vector: TSMVec2, result: TSMVec2 | null = null): TSMVec2 - { - const x = vector.x, - y = vector.y; - - if (result) - { - result.xy = [ - x * this.values[0] + y * this.values[3] + this.values[6], - x * this.values[1] + y * this.values[4] + this.values[7] - ]; - - return result; - } - else - { - return new TSMVec2([ - x * this.values[0] + y * this.values[3] + this.values[6], - x * this.values[1] + y * this.values[4] + this.values[7] - ]); - } - } - - multiplyTSMVec3(vector: TSMVec3, result: TSMVec3 | null = null): TSMVec3 - { - const x = vector.x, - y = vector.y, - z = vector.z; - - if (result) - { - result.xyz = [ - x * this.values[0] + y * this.values[3] + z * this.values[6], - x * this.values[1] + y * this.values[4] + z * this.values[7], - x * this.values[2] + y * this.values[5] + z * this.values[8] - ]; - - return result; - } - else - { - return new TSMVec3([ - x * this.values[0] + y * this.values[3] + z * this.values[6], - x * this.values[1] + y * this.values[4] + z * this.values[7], - x * this.values[2] + y * this.values[5] + z * this.values[8] - ]); - } - } - - toTSMMat4(result: TSMMat4 | null = null): TSMMat4 - { - if (result) - { - result.init([ - this.values[0], - this.values[1], - this.values[2], - 0, - - this.values[3], - this.values[4], - this.values[5], - 0, - - this.values[6], - this.values[7], - this.values[8], - 0, - - 0, - 0, - 0, - 1 - ]); - - return result; - } - else - { - return new TSMMat4([ - this.values[0], - this.values[1], - this.values[2], - 0, - - this.values[3], - this.values[4], - this.values[5], - 0, - - this.values[6], - this.values[7], - this.values[8], - 0, - - 0, - 0, - 0, - 1 - ]); - } - } - - toQuat(): TSMQuat - { - const m00 = this.values[0], m01 = this.values[1], m02 = this.values[2], - m10 = this.values[3], m11 = this.values[4], m12 = this.values[5], - m20 = this.values[6], m21 = this.values[7], m22 = this.values[8]; - - const fourXSquaredMinus1 = m00 - m11 - m22; - const fourYSquaredMinus1 = m11 - m00 - m22; - const fourZSquaredMinus1 = m22 - m00 - m11; - const fourWSquaredMinus1 = m00 + m11 + m22; - - let biggestIndex = 0; - - let fourBiggestSquaredMinus1 = fourWSquaredMinus1; - - if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) - { - fourBiggestSquaredMinus1 = fourXSquaredMinus1; - biggestIndex = 1; - } - - if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) - { - fourBiggestSquaredMinus1 = fourYSquaredMinus1; - biggestIndex = 2; - } - - if (fourZSquaredMinus1 > fourBiggestSquaredMinus1) - { - fourBiggestSquaredMinus1 = fourZSquaredMinus1; - biggestIndex = 3; - } - - const biggestVal = Math.sqrt(fourBiggestSquaredMinus1 + 1) * 0.5; - const mult = 0.25 / biggestVal; - - const result = new TSMQuat(); - - switch (biggestIndex) - { - case 0: - - result.w = biggestVal; - result.x = (m12 - m21) * mult; - result.y = (m20 - m02) * mult; - result.z = (m01 - m10) * mult; - - break; - - case 1: - - result.w = (m12 - m21) * mult; - result.x = biggestVal; - result.y = (m01 + m10) * mult; - result.z = (m20 + m02) * mult; - - break; - - case 2: - - result.w = (m20 - m02) * mult; - result.x = (m01 + m10) * mult; - result.y = biggestVal; - result.z = (m12 + m21) * mult; - - break; - - case 3: - - result.w = (m01 - m10) * mult; - result.x = (m20 + m02) * mult; - result.y = (m12 + m21) * mult; - result.z = biggestVal; - - break; - } - - return result; - } - - rotate(angle: number, axis: TSMVec3): TSMMat3 | null - { - let x = axis.x, - y = axis.y, - z = axis.z; - - let length = Math.sqrt(x * x + y * y + z * z); - - if (!length) - { - return null; - } - - if (length !== 1) - { - length = 1 / length; - x *= length; - y *= length; - z *= length; - } - - const s = Math.sin(angle); - const c = Math.cos(angle); - - const t = 1.0 - c; - - const a00 = this.values[0], a01 = this.values[1], a02 = this.values[2], - a10 = this.values[4], a11 = this.values[5], a12 = this.values[6], - a20 = this.values[8], a21 = this.values[9], a22 = this.values[10]; - - const b00 = x * x * t + c, b01 = y * x * t + z * s, b02 = z * x * t - y * s, - b10 = x * y * t - z * s, b11 = y * y * t + c, b12 = z * y * t + x * s, - b20 = x * z * t + y * s, b21 = y * z * t - x * s, b22 = z * z * t + c; - - this.values[0] = a00 * b00 + a10 * b01 + a20 * b02; - this.values[1] = a01 * b00 + a11 * b01 + a21 * b02; - this.values[2] = a02 * b00 + a12 * b01 + a22 * b02; - - this.values[3] = a00 * b10 + a10 * b11 + a20 * b12; - this.values[4] = a01 * b10 + a11 * b11 + a21 * b12; - this.values[5] = a02 * b10 + a12 * b11 + a22 * b12; - - this.values[6] = a00 * b20 + a10 * b21 + a20 * b22; - this.values[7] = a01 * b20 + a11 * b21 + a21 * b22; - this.values[8] = a02 * b20 + a12 * b21 + a22 * b22; - - return this; - } -} - - - diff --git a/lib/tsm/mat4.ts b/lib/tsm/mat4.ts deleted file mode 100644 index 0330f74..0000000 --- a/lib/tsm/mat4.ts +++ /dev/null @@ -1,640 +0,0 @@ -/** - * @fileoverview TSM - A TypeScript vector and matrix math library - * @author Matthias Ferch - * @version 0.6 - */ - -/* - * Copyright (c) 2012 Matthias Ferch - * - * Project homepage: https://github.com/matthiasferch/tsm - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * - * 2. Altered source versions must be plainly marked as such, and must not - * be misrepresented as being the original software. - * - * 3. This notice may not be removed or altered from any source - * distribution. - */ - -import { TSMMat3 } from './mat3'; -import { TSMVec3 } from './vec3'; -import { TSMVec4 } from './vec4'; - -export class TSMMat4 -{ - static identity = new TSMMat4().setIdentity(); - - private values = new Float32Array(16); - - static frustum(left: number, right: number, bottom: number, top: number, near: number, far: number): TSMMat4 - { - const rl = (right - left), - tb = (top - bottom), - fn = (far - near); - - return new TSMMat4([ - (near * 2) / rl, - 0, - 0, - 0, - - 0, - (near * 2) / tb, - 0, - 0, - - (right + left) / rl, - (top + bottom) / tb, - -(far + near) / fn, - -1, - - 0, - 0, - -(far * near * 2) / fn, - 0 - ]); - } - - static perspective(fov: number, aspect: number, near: number, far: number): TSMMat4 - { - const top = near * Math.tan(fov * Math.PI / 360.0), - right = top * aspect; - - return TSMMat4.frustum(-right, right, -top, top, near, far); - } - - static orthographic(left: number, right: number, bottom: number, top: number, near: number, far: number): TSMMat4 - { - const rl = (right - left), - tb = (top - bottom), - fn = (far - near); - - return new TSMMat4([ - 2 / rl, - 0, - 0, - 0, - - 0, - 2 / tb, - 0, - 0, - - 0, - 0, - -2 / fn, - 0, - - -(left + right) / rl, - -(top + bottom) / tb, - -(far + near) / fn, - 1 - ]); - } - - static lookAt(position: TSMVec3, target: TSMVec3, up: TSMVec3 = TSMVec3.up): TSMMat4 - { - if (position.equals(target)) - { - return this.identity; - } - - const z = TSMVec3.difference(position, target).normalize(); - - const x = TSMVec3.cross(up, z).normalize(); - const y = TSMVec3.cross(z, x).normalize(); - - return new TSMMat4([ - x.x, - y.x, - z.x, - 0, - - x.y, - y.y, - z.y, - 0, - - x.z, - y.z, - z.z, - 0, - - -TSMVec3.dot(x, position), - -TSMVec3.dot(y, position), - -TSMVec3.dot(z, position), - 1 - ]); - } - - static product(m1: TSMMat4, m2: TSMMat4, result: TSMMat4 | null = null): TSMMat4 - { - const a00 = m1.at(0), a01 = m1.at(1), a02 = m1.at(2), a03 = m1.at(3), - a10 = m1.at(4), a11 = m1.at(5), a12 = m1.at(6), a13 = m1.at(7), - a20 = m1.at(8), a21 = m1.at(9), a22 = m1.at(10), a23 = m1.at(11), - a30 = m1.at(12), a31 = m1.at(13), a32 = m1.at(14), a33 = m1.at(15); - - const b00 = m2.at(0), b01 = m2.at(1), b02 = m2.at(2), b03 = m2.at(3), - b10 = m2.at(4), b11 = m2.at(5), b12 = m2.at(6), b13 = m2.at(7), - b20 = m2.at(8), b21 = m2.at(9), b22 = m2.at(10), b23 = m2.at(11), - b30 = m2.at(12), b31 = m2.at(13), b32 = m2.at(14), b33 = m2.at(15); - - if (result) - { - result.init([ - b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30, - b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31, - b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32, - b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33, - - b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30, - b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31, - b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32, - b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33, - - b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30, - b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31, - b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32, - b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33, - - b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30, - b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31, - b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32, - b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33 - ]); - - return result; - } - else - { - return new TSMMat4([ - b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30, - b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31, - b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32, - b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33, - - b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30, - b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31, - b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32, - b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33, - - b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30, - b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31, - b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32, - b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33, - - b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30, - b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31, - b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32, - b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33 - ]); - } - } - - constructor(values: number[] | null = null) - { - if (values) - { - this.init(values); - } - } - - at(index: number): number - { - return this.values[index]; - } - - init(values: number[]): TSMMat4 - { - for (let i = 0; i < 16; i++) - { - this.values[i] = values[i]; - } - - return this; - } - - reset(): void - { - for (let i = 0; i < 16; i++) - { - this.values[i] = 0; - } - } - - copy(dest: TSMMat4 | null = null): TSMMat4 - { - if (!dest) - { - dest = new TSMMat4(); - } - - for (let i = 0; i < 16; i++) - { - dest.values[i] = this.values[i]; - } - - return dest; - } - - all(): number[] - { - const data: number[] = []; - for (let i = 0; i < 16; i++) - { - data[i] = this.values[i]; - } - - return data; - } - - row(index: number): number[] - { - return [ - this.values[index * 4 + 0], - this.values[index * 4 + 1], - this.values[index * 4 + 2], - this.values[index * 4 + 3] - ]; - } - - col(index: number): number[] - { - return [ - this.values[index], - this.values[index + 4], - this.values[index + 8], - this.values[index + 12] - ]; - } - - equals(matrix: TSMMat4, threshold = EPSILON): boolean - { - for (let i = 0; i < 16; i++) - { - if (Math.abs(this.values[i] - matrix.at(i)) > threshold) - { - return false; - } - } - - return true; - } - - determinant(): number - { - const a00 = this.values[0], a01 = this.values[1], a02 = this.values[2], a03 = this.values[3], - a10 = this.values[4], a11 = this.values[5], a12 = this.values[6], a13 = this.values[7], - a20 = this.values[8], a21 = this.values[9], a22 = this.values[10], a23 = this.values[11], - a30 = this.values[12], a31 = this.values[13], a32 = this.values[14], a33 = this.values[15]; - - const det00 = a00 * a11 - a01 * a10, - det01 = a00 * a12 - a02 * a10, - det02 = a00 * a13 - a03 * a10, - det03 = a01 * a12 - a02 * a11, - det04 = a01 * a13 - a03 * a11, - det05 = a02 * a13 - a03 * a12, - det06 = a20 * a31 - a21 * a30, - det07 = a20 * a32 - a22 * a30, - det08 = a20 * a33 - a23 * a30, - det09 = a21 * a32 - a22 * a31, - det10 = a21 * a33 - a23 * a31, - det11 = a22 * a33 - a23 * a32; - - return (det00 * det11 - det01 * det10 + det02 * det09 + det03 * det08 - det04 * det07 + det05 * det06); - } - - setIdentity(): TSMMat4 - { - this.values[0] = 1; - this.values[1] = 0; - this.values[2] = 0; - this.values[3] = 0; - this.values[4] = 0; - this.values[5] = 1; - this.values[6] = 0; - this.values[7] = 0; - this.values[8] = 0; - this.values[9] = 0; - this.values[10] = 1; - this.values[11] = 0; - this.values[12] = 0; - this.values[13] = 0; - this.values[14] = 0; - this.values[15] = 1; - - return this; - } - - transpose(): TSMMat4 | null - { - const temp01 = this.values[1], temp02 = this.values[2], - temp03 = this.values[3], temp12 = this.values[6], - temp13 = this.values[7], temp23 = this.values[11]; - - this.values[1] = this.values[4]; - this.values[2] = this.values[8]; - this.values[3] = this.values[12]; - this.values[4] = temp01; - this.values[6] = this.values[9]; - this.values[7] = this.values[13]; - this.values[8] = temp02; - this.values[9] = temp12; - this.values[11] = this.values[14]; - this.values[12] = temp03; - this.values[13] = temp13; - this.values[14] = temp23; - - return this; - } - - inverse(): TSMMat4 | null - { - const a00 = this.values[0], a01 = this.values[1], a02 = this.values[2], a03 = this.values[3], - a10 = this.values[4], a11 = this.values[5], a12 = this.values[6], a13 = this.values[7], - a20 = this.values[8], a21 = this.values[9], a22 = this.values[10], a23 = this.values[11], - a30 = this.values[12], a31 = this.values[13], a32 = this.values[14], a33 = this.values[15]; - - const det00 = a00 * a11 - a01 * a10, - det01 = a00 * a12 - a02 * a10, - det02 = a00 * a13 - a03 * a10, - det03 = a01 * a12 - a02 * a11, - det04 = a01 * a13 - a03 * a11, - det05 = a02 * a13 - a03 * a12, - det06 = a20 * a31 - a21 * a30, - det07 = a20 * a32 - a22 * a30, - det08 = a20 * a33 - a23 * a30, - det09 = a21 * a32 - a22 * a31, - det10 = a21 * a33 - a23 * a31, - det11 = a22 * a33 - a23 * a32; - - let det = (det00 * det11 - det01 * det10 + det02 * det09 + det03 * det08 - det04 * det07 + det05 * det06); - - if (!det) - { - return null; - } - - det = 1.0 / det; - - this.values[0] = (a11 * det11 - a12 * det10 + a13 * det09) * det; - this.values[1] = (-a01 * det11 + a02 * det10 - a03 * det09) * det; - this.values[2] = (a31 * det05 - a32 * det04 + a33 * det03) * det; - this.values[3] = (-a21 * det05 + a22 * det04 - a23 * det03) * det; - this.values[4] = (-a10 * det11 + a12 * det08 - a13 * det07) * det; - this.values[5] = (a00 * det11 - a02 * det08 + a03 * det07) * det; - this.values[6] = (-a30 * det05 + a32 * det02 - a33 * det01) * det; - this.values[7] = (a20 * det05 - a22 * det02 + a23 * det01) * det; - this.values[8] = (a10 * det10 - a11 * det08 + a13 * det06) * det; - this.values[9] = (-a00 * det10 + a01 * det08 - a03 * det06) * det; - this.values[10] = (a30 * det04 - a31 * det02 + a33 * det00) * det; - this.values[11] = (-a20 * det04 + a21 * det02 - a23 * det00) * det; - this.values[12] = (-a10 * det09 + a11 * det07 - a12 * det06) * det; - this.values[13] = (a00 * det09 - a01 * det07 + a02 * det06) * det; - this.values[14] = (-a30 * det03 + a31 * det01 - a32 * det00) * det; - this.values[15] = (a20 * det03 - a21 * det01 + a22 * det00) * det; - - return this; - } - - multiply(matrix: TSMMat4): TSMMat4 - { - const a00 = this.values[0], a01 = this.values[1], a02 = this.values[2], a03 = this.values[3]; - const a10 = this.values[4], a11 = this.values[5], a12 = this.values[6], a13 = this.values[7]; - const a20 = this.values[8], a21 = this.values[9], a22 = this.values[10], a23 = this.values[11]; - const a30 = this.values[12], a31 = this.values[13], a32 = this.values[14], a33 = this.values[15]; - - let b0 = matrix.at(0), - b1 = matrix.at(1), - b2 = matrix.at(2), - b3 = matrix.at(3); - - this.values[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; - this.values[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; - this.values[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; - this.values[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; - - b0 = matrix.at(4); - b1 = matrix.at(5); - b2 = matrix.at(6); - b3 = matrix.at(7); - - this.values[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; - this.values[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; - this.values[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; - this.values[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; - - b0 = matrix.at(8); - b1 = matrix.at(9); - b2 = matrix.at(10); - b3 = matrix.at(11); - - this.values[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; - this.values[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; - this.values[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; - this.values[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; - - b0 = matrix.at(12); - b1 = matrix.at(13); - b2 = matrix.at(14); - b3 = matrix.at(15); - - this.values[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; - this.values[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; - this.values[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; - this.values[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; - - return this; - } - - multiplyTSMVec3(vector: TSMVec3): TSMVec3 - { - const x = vector.x, - y = vector.y, - z = vector.z; - - return new TSMVec3([ - this.values[0] * x + this.values[4] * y + this.values[8] * z + this.values[12], - this.values[1] * x + this.values[5] * y + this.values[9] * z + this.values[13], - this.values[2] * x + this.values[6] * y + this.values[10] * z + this.values[14] - ]); - } - - multiplyTSMVec4(vector: TSMVec4, dest: TSMVec4 | null = null): TSMVec4 - { - if (!dest) - { - dest = new TSMVec4(); - } - - const x = vector.x, - y = vector.y, - z = vector.z, - w = vector.w; - - dest.x = this.values[0] * x + this.values[4] * y + this.values[8] * z + this.values[12] * w; - dest.y = this.values[1] * x + this.values[5] * y + this.values[9] * z + this.values[13] * w; - dest.z = this.values[2] * x + this.values[6] * y + this.values[10] * z + this.values[14] * w; - dest.w = this.values[3] * x + this.values[7] * y + this.values[11] * z + this.values[15] * w; - - return dest; - } - - toTSMMat3(): TSMMat3 - { - return new TSMMat3([ - this.values[0], - this.values[1], - this.values[2], - this.values[4], - this.values[5], - this.values[6], - this.values[8], - this.values[9], - this.values[10] - ]); - } - - toInverseTSMMat3(): TSMMat3 | null - { - const a00 = this.values[0], a01 = this.values[1], a02 = this.values[2], - a10 = this.values[4], a11 = this.values[5], a12 = this.values[6], - a20 = this.values[8], a21 = this.values[9], a22 = this.values[10]; - - const det01 = a22 * a11 - a12 * a21, - det11 = -a22 * a10 + a12 * a20, - det21 = a21 * a10 - a11 * a20; - - let det = a00 * det01 + a01 * det11 + a02 * det21; - - if (!det) - { - return null; - } - - det = 1.0 / det; - - return new TSMMat3([ - det01 * det, - (-a22 * a01 + a02 * a21) * det, - (a12 * a01 - a02 * a11) * det, - det11 * det, - (a22 * a00 - a02 * a20) * det, - (-a12 * a00 + a02 * a10) * det, - det21 * det, - (-a21 * a00 + a01 * a20) * det, - (a11 * a00 - a01 * a10) * det - ]); - } - - translate(vector: TSMVec3): TSMMat4 - { - const x = vector.x, - y = vector.y, - z = vector.z; - - this.values[12] += this.values[0] * x + this.values[4] * y + this.values[8] * z; - this.values[13] += this.values[1] * x + this.values[5] * y + this.values[9] * z; - this.values[14] += this.values[2] * x + this.values[6] * y + this.values[10] * z; - this.values[15] += this.values[3] * x + this.values[7] * y + this.values[11] * z; - - return this; - } - - scale(vector: TSMVec3): TSMMat4 - { - const x = vector.x, - y = vector.y, - z = vector.z; - - this.values[0] *= x; - this.values[1] *= x; - this.values[2] *= x; - this.values[3] *= x; - - this.values[4] *= y; - this.values[5] *= y; - this.values[6] *= y; - this.values[7] *= y; - - this.values[8] *= z; - this.values[9] *= z; - this.values[10] *= z; - this.values[11] *= z; - - return this; - } - - rotate(angle: number, axis: TSMVec3): TSMMat4 | null - { - let x = axis.x, - y = axis.y, - z = axis.z; - - let length = Math.sqrt(x * x + y * y + z * z); - - if (!length) - { - return null; - } - - if (length !== 1) - { - length = 1 / length; - x *= length; - y *= length; - z *= length; - } - - const s = Math.sin(angle); - const c = Math.cos(angle); - - const t = 1.0 - c; - - const a00 = this.values[0], a01 = this.values[1], a02 = this.values[2], a03 = this.values[3], - a10 = this.values[4], a11 = this.values[5], a12 = this.values[6], a13 = this.values[7], - a20 = this.values[8], a21 = this.values[9], a22 = this.values[10], a23 = this.values[11]; - - const b00 = x * x * t + c, b01 = y * x * t + z * s, b02 = z * x * t - y * s, - b10 = x * y * t - z * s, b11 = y * y * t + c, b12 = z * y * t + x * s, - b20 = x * z * t + y * s, b21 = y * z * t - x * s, b22 = z * z * t + c; - - this.values[0] = a00 * b00 + a10 * b01 + a20 * b02; - this.values[1] = a01 * b00 + a11 * b01 + a21 * b02; - this.values[2] = a02 * b00 + a12 * b01 + a22 * b02; - this.values[3] = a03 * b00 + a13 * b01 + a23 * b02; - - this.values[4] = a00 * b10 + a10 * b11 + a20 * b12; - this.values[5] = a01 * b10 + a11 * b11 + a21 * b12; - this.values[6] = a02 * b10 + a12 * b11 + a22 * b12; - this.values[7] = a03 * b10 + a13 * b11 + a23 * b12; - - this.values[8] = a00 * b20 + a10 * b21 + a20 * b22; - this.values[9] = a01 * b20 + a11 * b21 + a21 * b22; - this.values[10] = a02 * b20 + a12 * b21 + a22 * b22; - this.values[11] = a03 * b20 + a13 * b21 + a23 * b22; - - return this; - } - - toArray(): number[] - { - return Array.from(this.values); - } -} - - - diff --git a/lib/tsm/quat.ts b/lib/tsm/quat.ts deleted file mode 100644 index 0419849..0000000 --- a/lib/tsm/quat.ts +++ /dev/null @@ -1,637 +0,0 @@ -/** - * @fileoverview TSM - A TypeScript vector and matrix math library - * @author Matthias Ferch - * @version 0.6 - */ - -/* - * Copyright (c) 2012 Matthias Ferch - * - * Project homepage: https://github.com/matthiasferch/tsm - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * - * 2. Altered source versions must be plainly marked as such, and must not - * be misrepresented as being the original software. - * - * 3. This notice may not be removed or altered from any source - * distribution. - */ - -import { TSMVec3 } from './vec3'; -import { TSMMat3 } from './mat3'; -import { TSMMat4 } from './mat4'; - -export class TSMQuat -{ - static identity = new TSMQuat().setIdentity(); - - private values = new Float32Array(4); - - static dot(q1: TSMQuat, q2: TSMQuat): number - { - return q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w; - } - - static sum(q1: TSMQuat, q2: TSMQuat, dest: TSMQuat | null = null): TSMQuat - { - if (!dest) - { - dest = new TSMQuat(); - } - - dest.x = q1.x + q2.x; - dest.y = q1.y + q2.y; - dest.z = q1.z + q2.z; - dest.w = q1.w + q2.w; - - return dest; - } - - static product(q1: TSMQuat, q2: TSMQuat, dest: TSMQuat | null = null): TSMQuat - { - if (!dest) - { - dest = new TSMQuat(); - } - - const q1x = q1.x, - q1y = q1.y, - q1z = q1.z, - q1w = q1.w, - - q2x = q2.x, - q2y = q2.y, - q2z = q2.z, - q2w = q2.w; - - dest.x = q1x * q2w + q1w * q2x + q1y * q2z - q1z * q2y; - dest.y = q1y * q2w + q1w * q2y + q1z * q2x - q1x * q2z; - dest.z = q1z * q2w + q1w * q2z + q1x * q2y - q1y * q2x; - dest.w = q1w * q2w - q1x * q2x - q1y * q2y - q1z * q2z; - - return dest; - } - - static cross(q1: TSMQuat, q2: TSMQuat, dest: TSMQuat | null = null): TSMQuat - { - if (!dest) - { - dest = new TSMQuat(); - } - - const q1x = q1.x, - q1y = q1.y, - q1z = q1.z, - q1w = q1.w, - - q2x = q2.x, - q2y = q2.y, - q2z = q2.z, - q2w = q2.w; - - dest.x = q1w * q2z + q1z * q2w + q1x * q2y - q1y * q2x; - dest.y = q1w * q2w - q1x * q2x - q1y * q2y - q1z * q2z; - dest.z = q1w * q2x + q1x * q2w + q1y * q2z - q1z * q2y; - dest.w = q1w * q2y + q1y * q2w + q1z * q2x - q1x * q2z; - - return dest; - } - - static shortMix(q1: TSMQuat, q2: TSMQuat, time: number, dest: TSMQuat | null = null): TSMQuat - { - if (!dest) - { - dest = new TSMQuat(); - } - - if (time <= 0.0) - { - dest.xyzw = q1.xyzw; - - return dest; - } - else if (time >= 1.0) - { - dest.xyzw = q2.xyzw; - - return dest; - } - - let cos = TSMQuat.dot(q1, q2); - const q2a = q2.copy(); - - if (cos < 0.0) - { - q2a.inverse(); - cos = -cos; - } - - let k0: number, - k1: number; - - if (cos > 0.9999) - { - k0 = 1 - time; - k1 = time; - } - else - { - const sin: number = Math.sqrt(1 - cos * cos); - const angle: number = Math.atan2(sin, cos); - - const oneOverSin: number = 1 / sin; - - k0 = Math.sin((1 - time) * angle) * oneOverSin; - k1 = Math.sin((time) * angle) * oneOverSin; - } - - dest.x = k0 * q1.x + k1 * q2a.x; - dest.y = k0 * q1.y + k1 * q2a.y; - dest.z = k0 * q1.z + k1 * q2a.z; - dest.w = k0 * q1.w + k1 * q2a.w; - - return dest; - } - - static mix(q1: TSMQuat, q2: TSMQuat, time: number, dest: TSMQuat | null = null): TSMQuat - { - if (!dest) - { - dest = new TSMQuat(); - } - - const cosHalfTheta = q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w; - - if (Math.abs(cosHalfTheta) >= 1.0) - { - dest.xyzw = q1.xyzw; - - return dest; - } - - const halfTheta = Math.acos(cosHalfTheta), - sinHalfTheta = Math.sqrt(1.0 - cosHalfTheta * cosHalfTheta); - - if (Math.abs(sinHalfTheta) < 0.001) - { - dest.x = q1.x * 0.5 + q2.x * 0.5; - dest.y = q1.y * 0.5 + q2.y * 0.5; - dest.z = q1.z * 0.5 + q2.z * 0.5; - dest.w = q1.w * 0.5 + q2.w * 0.5; - - return dest; - } - - const ratioA = Math.sin((1 - time) * halfTheta) / sinHalfTheta, - ratioB = Math.sin(time * halfTheta) / sinHalfTheta; - - dest.x = q1.x * ratioA + q2.x * ratioB; - dest.y = q1.y * ratioA + q2.y * ratioB; - dest.z = q1.z * ratioA + q2.z * ratioB; - dest.w = q1.w * ratioA + q2.w * ratioB; - - return dest; - } - - static fromAxis(axis: TSMVec3, angle: number, dest: TSMQuat | null = null): TSMQuat - { - if (!dest) - { - dest = new TSMQuat(); - } - - angle *= 0.5; - const sin = Math.sin(angle); - - dest.x = axis.x * sin; - dest.y = axis.y * sin; - dest.z = axis.z * sin; - dest.w = Math.cos(angle); - - return dest; - } - - get x(): number - { - return this.values[0]; - } - - get y(): number - { - return this.values[1]; - } - - get z(): number - { - return this.values[2]; - } - - get w(): number - { - return this.values[3]; - } - - get xy(): number[] - { - return [ - this.values[0], - this.values[1] - ]; - } - - get xyz(): number[] - { - return [ - this.values[0], - this.values[1], - this.values[2] - ]; - } - - get xyzw(): number[] - { - return [ - this.values[0], - this.values[1], - this.values[2], - this.values[3] - ]; - } - - set x(value: number) - { - this.values[0] = value; - } - - set y(value: number) - { - this.values[1] = value; - } - - set z(value: number) - { - this.values[2] = value; - } - - set w(value: number) - { - this.values[3] = value; - } - - set xy(values: number[]) - { - this.values[0] = values[0]; - this.values[1] = values[1]; - } - - set xyz(values: number[]) - { - this.values[0] = values[0]; - this.values[1] = values[1]; - this.values[2] = values[2]; - } - - set xyzw(values: number[]) - { - this.values[0] = values[0]; - this.values[1] = values[1]; - this.values[2] = values[2]; - this.values[3] = values[3]; - } - - constructor(values: number[] | null = null) - { - if (values) - { - this.xyzw = values; - } - } - - at(index: number): number - { - return this.values[index]; - } - - reset(): void - { - for (let i = 0; i < 4; i++) - { - this.values[i] = 0; - } - } - - copy(dest: TSMQuat | null = null): TSMQuat - { - if (!dest) - { - dest = new TSMQuat(); - } - - for (let i = 0; i < 4; i++) - { - dest.values[i] = this.values[i]; - } - - return dest; - } - - roll(): number - { - const x = this.x, - y = this.y, - z = this.z, - w = this.w; - - return Math.atan2(2.0 * (x * y + w * z), w * w + x * x - y * y - z * z); - } - - pitch(): number - { - const x = this.x, - y = this.y, - z = this.z, - w = this.w; - - return Math.atan2(2.0 * (y * z + w * x), w * w - x * x - y * y + z * z); - } - - yaw(): number - { - return Math.asin(2.0 * (this.x * this.z - this.w * this.y)); - } - - equals(vector: TSMQuat, threshold = EPSILON): boolean - { - for (let i = 0; i < 4; i++) - { - if (Math.abs(this.values[i] - vector.at(i)) > threshold) - { - return false; - } - } - - return true; - } - - setIdentity(): TSMQuat - { - this.x = 0; - this.y = 0; - this.z = 0; - this.w = 1; - - return this; - } - - calculateW(): TSMQuat - { - const x = this.x, - y = this.y, - z = this.z; - - this.w = -(Math.sqrt(Math.abs(1.0 - x * x - y * y - z * z))); - - return this; - } - - inverse(): TSMQuat - { - const dot = TSMQuat.dot(this, this); - - if (!dot) - { - this.xyzw = [0, 0, 0, 0]; - - return this; - } - - const invDot = dot ? 1.0 / dot : 0; - - this.x *= -invDot; - this.y *= -invDot; - this.z *= -invDot; - this.w *= invDot; - - return this; - } - - conjugate(): TSMQuat - { - this.values[0] *= -1; - this.values[1] *= -1; - this.values[2] *= -1; - - return this; - } - - length(): number - { - const x = this.x, - y = this.y, - z = this.z, - w = this.w; - - return Math.sqrt(x * x + y * y + z * z + w * w); - } - - normalize(dest: TSMQuat | null = null): TSMQuat - { - if (!dest) - { - dest = this; - } - - const x = this.x, - y = this.y, - z = this.z, - w = this.w; - - let length = Math.sqrt(x * x + y * y + z * z + w * w); - - if (!length) - { - dest.x = 0; - dest.y = 0; - dest.z = 0; - dest.w = 0; - - return dest; - } - - length = 1 / length; - - dest.x = x * length; - dest.y = y * length; - dest.z = z * length; - dest.w = w * length; - - return dest; - } - - add(other: TSMQuat): TSMQuat - { - for (let i = 0; i < 4; i++) - { - this.values[i] += other.at(i); - } - - return this; - } - - multiply(other: TSMQuat): TSMQuat - { - const q1x = this.values[0], - q1y = this.values[1], - q1z = this.values[2], - q1w = this.values[3]; - - const q2x = other.x, - q2y = other.y, - q2z = other.z, - q2w = other.w; - - this.x = q1x * q2w + q1w * q2x + q1y * q2z - q1z * q2y; - this.y = q1y * q2w + q1w * q2y + q1z * q2x - q1x * q2z; - this.z = q1z * q2w + q1w * q2z + q1x * q2y - q1y * q2x; - this.w = q1w * q2w - q1x * q2x - q1y * q2y - q1z * q2z; - - return this; - } - - multiplyTSMVec3(vector: TSMVec3, dest: TSMVec3 | null = null): TSMVec3 - { - if (!dest) - { - dest = new TSMVec3(); - } - - const x = vector.x, - y = vector.y, - z = vector.z; - - const qx = this.x, - qy = this.y, - qz = this.z, - qw = this.w; - - const ix = qw * x + qy * z - qz * y, - iy = qw * y + qz * x - qx * z, - iz = qw * z + qx * y - qy * x, - iw = -qx * x - qy * y - qz * z; - - dest.x = ix * qw + iw * -qx + iy * -qz - iz * -qy; - dest.y = iy * qw + iw * -qy + iz * -qx - ix * -qz; - dest.z = iz * qw + iw * -qz + ix * -qy - iy * -qx; - - return dest; - } - - toTSMMat3(dest: TSMMat3 | null = null): TSMMat3 - { - if (!dest) - { - dest = new TSMMat3(); - } - - const x = this.x, - y = this.y, - z = this.z, - w = this.w; - - const x2 = x + x, - y2 = y + y, - z2 = z + z; - - const xx = x * x2, - xy = x * y2, - xz = x * z2, - yy = y * y2, - yz = y * z2, - zz = z * z2, - wx = w * x2, - wy = w * y2, - wz = w * z2; - - dest.init([ - 1 - (yy + zz), - xy + wz, - xz - wy, - - xy - wz, - 1 - (xx + zz), - yz + wx, - - xz + wy, - yz - wx, - 1 - (xx + yy) - ]); - - return dest; - } - - toTSMMat4(dest: TSMMat4 | null = null): TSMMat4 - { - if (!dest) - { - dest = new TSMMat4(); - } - - const x = this.x, - y = this.y, - z = this.z, - w = this.w, - - x2 = x + x, - y2 = y + y, - z2 = z + z, - - xx = x * x2, - xy = x * y2, - xz = x * z2, - yy = y * y2, - yz = y * z2, - zz = z * z2, - wx = w * x2, - wy = w * y2, - wz = w * z2; - - dest.init([ - 1 - (yy + zz), - xy + wz, - xz - wy, - 0, - - xy - wz, - 1 - (xx + zz), - yz + wx, - 0, - - xz + wy, - yz - wx, - 1 - (xx + yy), - 0, - - 0, - 0, - 0, - 1 - ]); - - return dest; - } -} - - - diff --git a/lib/tsm/vec2.ts b/lib/tsm/vec2.ts deleted file mode 100644 index 3609678..0000000 --- a/lib/tsm/vec2.ts +++ /dev/null @@ -1,380 +0,0 @@ -/* - * Copyright (c) 2012 Matthias Ferch - * - * Project homepage: https://github.com/matthiasferch/tsm - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * - * 2. Altered source versions must be plainly marked as such, and must not - * be misrepresented as being the original software. - * - * 3. This notice may not be removed or altered from any source - * distribution. - */ - -import { TSMVec3 } from './vec3'; -import { TSMMat2 } from './mat2'; -import { TSMMat3 } from './mat3'; - -export class TSMVec2 -{ - - static zero = new TSMVec2([0, 0]); - - private values = new Float32Array(2); - - static cross(vector: TSMVec2, vector2: TSMVec2, dest: TSMVec3 | null = null): TSMVec3 - { - if (!dest) - { - dest = new TSMVec3(); - } - - const x = vector.x, - y = vector.y; - - const x2 = vector2.x, - y2 = vector2.y; - - const z = x * y2 - y * x2; - - dest.x = 0; - dest.y = 0; - dest.z = z; - - return dest; - } - - static dot(vector: TSMVec2, vector2: TSMVec2): number - { - return (vector.x * vector2.x + vector.y * vector2.y); - } - - static distance(vector: TSMVec2, vector2: TSMVec2): number - { - return Math.sqrt(this.squaredDistance(vector, vector2)); - } - - static squaredDistance(vector: TSMVec2, vector2: TSMVec2): number - { - const x = vector2.x - vector.x, - y = vector2.y - vector.y; - - return (x * x + y * y); - } - - static direction(vector: TSMVec2, vector2: TSMVec2, dest: TSMVec2 | null = null): TSMVec2 - { - if (!dest) - { - dest = new TSMVec2(); - } - - const x = vector.x - vector2.x, - y = vector.y - vector2.y; - - let length = Math.sqrt(x * x + y * y); - - if (length === 0) - { - dest.x = 0; - dest.y = 0; - - return dest; - } - - length = 1 / length; - - dest.x = x * length; - dest.y = y * length; - - return dest; - } - - static mix(vector: TSMVec2, vector2: TSMVec2, time: number, dest: TSMVec2 | null = null): TSMVec2 - { - if (!dest) - { - dest = new TSMVec2(); - } - - const x = vector.x, - y = vector.y; - - const x2 = vector2.x, - y2 = vector2.y; - - dest.x = x + time * (x2 - x); - dest.y = y + time * (y2 - y); - - return dest; - } - - static sum(vector: TSMVec2, vector2: TSMVec2, dest: TSMVec2 | null = null): TSMVec2 - { - if (!dest) - { - dest = new TSMVec2(); - } - - dest.x = vector.x + vector2.x; - dest.y = vector.y + vector2.y; - - return dest; - } - - static difference(vector: TSMVec2, vector2: TSMVec2, dest: TSMVec2 | null = null): TSMVec2 - { - if (!dest) - { - dest = new TSMVec2(); - } - - dest.x = vector.x - vector2.x; - dest.y = vector.y - vector2.y; - - return dest; - } - - static product(vector: TSMVec2, vector2: TSMVec2, dest: TSMVec2 | null = null): TSMVec2 - { - if (!dest) - { - dest = new TSMVec2(); - } - - dest.x = vector.x * vector2.x; - dest.y = vector.y * vector2.y; - - return dest; - } - - static quotient(vector: TSMVec2, vector2: TSMVec2, dest: TSMVec2 | null = null): TSMVec2 - { - if (!dest) - { - dest = new TSMVec2(); - } - - dest.x = vector.x / vector2.x; - dest.y = vector.y / vector2.y; - - return dest; - } - - get x(): number - { - return this.values[0]; - } - - get y(): number - { - return this.values[1]; - } - - get xy(): number[] - { - return [ - this.values[0], - this.values[1] - ]; - } - - set x(value: number) - { - this.values[0] = value; - } - - set y(value: number) - { - this.values[1] = value; - } - - set xy(values: number[]) - { - this.values[0] = values[0]; - this.values[1] = values[1]; - } - - constructor(values: number[] | null = null) - { - if (values) - { - this.xy = values; - } - } - - at(index: number): number - { - return this.values[index]; - } - - reset(): void - { - this.x = 0; - this.y = 0; - } - - copy(dest: TSMVec2 | null = null): TSMVec2 - { - if (!dest) - { - dest = new TSMVec2(); - } - - dest.x = this.x; - dest.y = this.y; - - return dest; - } - - negate(dest: TSMVec2 | null = null): TSMVec2 - { - if (!dest) - { - dest = this; - } - - dest.x = -this.x; - dest.y = -this.y; - - return dest; - } - - equals(vector: TSMVec2, threshold = EPSILON): boolean - { - if (Math.abs(this.x - vector.x) > threshold) - { - return false; - } - - if (Math.abs(this.y - vector.y) > threshold) - { - return false; - } - - return true; - } - - length(): number - { - return Math.sqrt(this.squaredLength()); - } - - squaredLength(): number - { - const x = this.x, - y = this.y; - - return (x * x + y * y); - } - - add(vector: TSMVec2): TSMVec2 - { - this.x += vector.x; - this.y += vector.y; - - return this; - } - - subtract(vector: TSMVec2): TSMVec2 - { - this.x -= vector.x; - this.y -= vector.y; - - return this; - } - - multiply(vector: TSMVec2): TSMVec2 - { - this.x *= vector.x; - this.y *= vector.y; - - return this; - } - - divide(vector: TSMVec2): TSMVec2 - { - this.x /= vector.x; - this.y /= vector.y; - - return this; - } - - scale(value: number, dest: TSMVec2 | null = null): TSMVec2 - { - if (!dest) - { - dest = this; - } - - dest.x *= value; - dest.y *= value; - - return dest; - } - - normalize(dest: TSMVec2 | null = null): TSMVec2 - { - if (!dest) - { - dest = this; - } - - let length = this.length(); - - if (length === 1) - { - return this; - } - - if (length === 0) - { - dest.x = 0; - dest.y = 0; - - return dest; - } - - length = 1.0 / length; - - dest.x *= length; - dest.y *= length; - - return dest; - } - - multiplyTSMMat2(matrix: TSMMat2, dest: TSMVec2 | null = null): TSMVec2 - { - if (!dest) - { - dest = this; - } - - return matrix.multiplyTSMVec2(this, dest); - } - - multiplyTSMMat3(matrix: TSMMat3, dest: TSMVec2 | null = null): TSMVec2 - { - if (!dest) - { - dest = this; - } - - return matrix.multiplyTSMVec2(this, dest); - } -} - - - diff --git a/lib/tsm/vec3.ts b/lib/tsm/vec3.ts deleted file mode 100644 index e93f409..0000000 --- a/lib/tsm/vec3.ts +++ /dev/null @@ -1,471 +0,0 @@ -/** - * @fileoverview TSM - A TypeScript vector and matrix math library - * @author Matthias Ferch - * @version 0.6 - */ - -/* - * Copyright (c) 2012 Matthias Ferch - * - * Project homepage: https://github.com/matthiasferch/tsm - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * - * 2. Altered source versions must be plainly marked as such, and must not - * be misrepresented as being the original software. - * - * 3. This notice may not be removed or altered from any source - * distribution. - */ - -import { TSMQuat } from './quat'; -import { TSMMat3 } from './mat3'; - -export class TSMVec3 -{ - static zero = new TSMVec3([0, 0, 0]); - - static up = new TSMVec3([0, 1, 0]); - static right = new TSMVec3([1, 0, 0]); - static forward = new TSMVec3([0, 0, 1]); - - private values = new Float32Array(3); - - static cross(vector: TSMVec3, vector2: TSMVec3, dest: TSMVec3 | null = null): TSMVec3 - { - if (!dest) - { - dest = new TSMVec3(); - } - - const x = vector.x, - y = vector.y, - z = vector.z; - - const x2 = vector2.x, - y2 = vector2.y, - z2 = vector2.z; - - dest.x = y * z2 - z * y2; - dest.y = z * x2 - x * z2; - dest.z = x * y2 - y * x2; - - return dest; - } - - static dot(vector: TSMVec3, vector2: TSMVec3): number - { - const x = vector.x, - y = vector.y, - z = vector.z; - - const x2 = vector2.x, - y2 = vector2.y, - z2 = vector2.z; - - return (x * x2 + y * y2 + z * z2); - } - - static distance(vector: TSMVec3, vector2: TSMVec3): number - { - return Math.sqrt(this.squaredDistance(vector, vector2)); - } - - static squaredDistance(vector: TSMVec3, vector2: TSMVec3): number - { - const x = vector2.x - vector.x, - y = vector2.y - vector.y, - z = vector2.z - vector.z; - - return (x * x + y * y + z * z); - } - - static direction(vector: TSMVec3, vector2: TSMVec3, dest: TSMVec3 | null = null): TSMVec3 - { - if (!dest) - { - dest = new TSMVec3(); - } - - const x = vector.x - vector2.x, - y = vector.y - vector2.y, - z = vector.z - vector2.z; - - let length = Math.sqrt(x * x + y * y + z * z); - - if (length === 0) - { - dest.x = 0; - dest.y = 0; - dest.z = 0; - - return dest; - } - - length = 1 / length; - - dest.x = x * length; - dest.y = y * length; - dest.z = z * length; - - return dest; - } - - static mix(vector: TSMVec3, vector2: TSMVec3, time: number, dest: TSMVec3 | null = null): TSMVec3 - { - if (!dest) - { - dest = new TSMVec3(); - } - - dest.x = vector.x + time * (vector2.x - vector.x); - dest.y = vector.y + time * (vector2.y - vector.y); - dest.z = vector.z + time * (vector2.z - vector.z); - - return dest; - } - - static sum(vector: TSMVec3, vector2: TSMVec3, dest: TSMVec3 | null = null): TSMVec3 - { - if (!dest) - { - dest = new TSMVec3(); - } - - dest.x = vector.x + vector2.x; - dest.y = vector.y + vector2.y; - dest.z = vector.z + vector2.z; - - return dest; - } - - static difference(vector: TSMVec3, vector2: TSMVec3, dest: TSMVec3 | null = null): TSMVec3 - { - if (!dest) - { - dest = new TSMVec3(); - } - - dest.x = vector.x - vector2.x; - dest.y = vector.y - vector2.y; - dest.z = vector.z - vector2.z; - - return dest; - } - - static product(vector: TSMVec3, vector2: TSMVec3, dest: TSMVec3 | null = null): TSMVec3 - { - if (!dest) - { - dest = new TSMVec3(); - } - - dest.x = vector.x * vector2.x; - dest.y = vector.y * vector2.y; - dest.z = vector.z * vector2.z; - - return dest; - } - - static quotient(vector: TSMVec3, vector2: TSMVec3, dest: TSMVec3 | null = null): TSMVec3 - { - if (!dest) - { - dest = new TSMVec3(); - } - - dest.x = vector.x / vector2.x; - dest.y = vector.y / vector2.y; - dest.z = vector.z / vector2.z; - - return dest; - } - - - - get x(): number - { - return this.values[0]; - } - - get y(): number - { - return this.values[1]; - } - - get z(): number - { - return this.values[2]; - } - - get xy(): number[] - { - return [ - this.values[0], - this.values[1] - ]; - } - - get xyz(): number[] - { - return [ - this.values[0], - this.values[1], - this.values[2] - ]; - } - - set x(value: number) - { - this.values[0] = value; - } - - set y(value: number) - { - this.values[1] = value; - } - - set z(value: number) - { - this.values[2] = value; - } - - set xy(values: number[]) - { - this.values[0] = values[0]; - this.values[1] = values[1]; - } - - set xyz(values: number[]) - { - this.values[0] = values[0]; - this.values[1] = values[1]; - this.values[2] = values[2]; - } - - constructor(values: number[] | null = null) - { - if (values) - { - this.xyz = values; - } - } - - at(index: number): number - { - return this.values[index]; - } - - reset(): void - { - this.x = 0; - this.y = 0; - this.z = 0; - } - - copy(dest: TSMVec3 | null = null): TSMVec3 - { - if (!dest) - { - dest = new TSMVec3(); - } - - dest.x = this.x; - dest.y = this.y; - dest.z = this.z; - - return dest; - } - - negate(dest: TSMVec3 | null = null): TSMVec3 - { - if (!dest) - { - dest = this; - } - - dest.x = -this.x; - dest.y = -this.y; - dest.z = -this.z; - - return dest; - } - - equals(vector: TSMVec3, threshold = EPSILON): boolean - { - if (Math.abs(this.x - vector.x) > threshold) - { - return false; - } - - if (Math.abs(this.y - vector.y) > threshold) - { - return false; - } - - if (Math.abs(this.z - vector.z) > threshold) - { - return false; - } - - - return true; - } - - length(): number - { - return Math.sqrt(this.squaredLength()); - } - - squaredLength(): number - { - const x = this.x, - y = this.y, - z = this.z; - - return (x * x + y * y + z * z); - } - - add(vector: TSMVec3): TSMVec3 - { - this.x += vector.x; - this.y += vector.y; - this.z += vector.z; - - return this; - } - - subtract(vector: TSMVec3): TSMVec3 - { - this.x -= vector.x; - this.y -= vector.y; - this.z -= vector.z; - - return this; - } - - multiply(vector: TSMVec3): TSMVec3 - { - this.x *= vector.x; - this.y *= vector.y; - this.z *= vector.z; - - return this; - } - - divide(vector: TSMVec3): TSMVec3 - { - this.x /= vector.x; - this.y /= vector.y; - this.z /= vector.z; - - return this; - } - - scale(value: number, dest: TSMVec3 | null = null): TSMVec3 - { - if (!dest) - { - dest = this; - } - - dest.x *= value; - dest.y *= value; - dest.z *= value; - - return dest; - } - - normalize(dest: TSMVec3 | null = null): TSMVec3 - { - if (!dest) - { - dest = this; - } - - let length = this.length(); - - if (length === 1) - { - return this; - } - - if (length === 0) - { - dest.x = 0; - dest.y = 0; - dest.z = 0; - - return dest; - } - - length = 1.0 / length; - - dest.x *= length; - dest.y *= length; - dest.z *= length; - - return dest; - } - - multiplyByTSMMat3(matrix: TSMMat3, dest: TSMVec3 | null = null): TSMVec3 - { - if (!dest) - { - dest = this; - } - - return matrix.multiplyTSMVec3(this, dest); - } - - multiplyByTSMQuat(src: TSMQuat, dest: TSMVec3 | null = null): TSMVec3 - { - if (!dest) - { - dest = this; - } - - return src.multiplyTSMVec3(this, dest); - } - - toTSMQuat(dest: TSMQuat | null = null): TSMQuat - { - if (!dest) - { - dest = new TSMQuat(); - } - - const c = new TSMVec3(); - const s = new TSMVec3(); - - c.x = Math.cos(this.x * 0.5); - s.x = Math.sin(this.x * 0.5); - - c.y = Math.cos(this.y * 0.5); - s.y = Math.sin(this.y * 0.5); - - c.z = Math.cos(this.z * 0.5); - s.z = Math.sin(this.z * 0.5); - - dest.x = s.x * c.y * c.z - c.x * s.y * s.z; - dest.y = c.x * s.y * c.z + s.x * c.y * s.z; - dest.z = c.x * c.y * s.z - s.x * s.y * c.z; - dest.w = c.x * c.y * c.z + s.x * s.y * s.z; - - return dest; - } -} - - - diff --git a/lib/tsm/vec4.ts b/lib/tsm/vec4.ts deleted file mode 100644 index 8117627..0000000 --- a/lib/tsm/vec4.ts +++ /dev/null @@ -1,487 +0,0 @@ -/** - * @fileoverview TSM - A TypeScript vector and matrix math library - * @author Matthias Ferch - * @version 0.6 - */ - -/* - * Copyright (c) 2012 Matthias Ferch - * - * Project homepage: https://github.com/matthiasferch/tsm - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * - * 2. Altered source versions must be plainly marked as such, and must not - * be misrepresented as being the original software. - * - * 3. This notice may not be removed or altered from any source - * distribution. - */ - - -/// - - -import { TSMMat4 } from './mat4'; - -export class TSMVec4 -{ - static zero = new TSMVec4([0, 0, 0, 1]); - - private values = new Float32Array(4); - - static mix(vector: TSMVec4, vector2: TSMVec4, time: number, dest: TSMVec4 | null = null): TSMVec4 - { - if (!dest) - { - dest = new TSMVec4(); - } - - dest.x = vector.x + time * (vector2.x - vector.x); - dest.y = vector.y + time * (vector2.y - vector.y); - dest.z = vector.z + time * (vector2.z - vector.z); - dest.w = vector.w + time * (vector2.w - vector.w); - - return dest; - } - - static sum(vector: TSMVec4, vector2: TSMVec4, dest: TSMVec4 | null = null): TSMVec4 - { - if (!dest) - { - dest = new TSMVec4(); - } - - dest.x = vector.x + vector2.x; - dest.y = vector.y + vector2.y; - dest.z = vector.z + vector2.z; - dest.w = vector.w + vector2.w; - - return dest; - } - - static difference(vector: TSMVec4, vector2: TSMVec4, dest: TSMVec4 | null = null): TSMVec4 - { - if (!dest) - { - dest = new TSMVec4(); - } - - dest.x = vector.x - vector2.x; - dest.y = vector.y - vector2.y; - dest.z = vector.z - vector2.z; - dest.w = vector.w - vector2.w; - - return dest; - } - - static product(vector: TSMVec4, vector2: TSMVec4, dest: TSMVec4 | null = null): TSMVec4 - { - if (!dest) - { - dest = new TSMVec4(); - } - - dest.x = vector.x * vector2.x; - dest.y = vector.y * vector2.y; - dest.z = vector.z * vector2.z; - dest.w = vector.w * vector2.w; - - return dest; - } - - static quotient(vector: TSMVec4, vector2: TSMVec4, dest: TSMVec4 | null = null): TSMVec4 - { - if (!dest) - { - dest = new TSMVec4(); - } - - dest.x = vector.x / vector2.x; - dest.y = vector.y / vector2.y; - dest.z = vector.z / vector2.z; - dest.w = vector.w / vector2.w; - - return dest; - } - - get x(): number - { - return this.values[0]; - } - - get y(): number - { - return this.values[1]; - } - - get z(): number - { - return this.values[2]; - } - - get w(): number - { - return this.values[3]; - } - - get xy(): number[] - { - return [ - this.values[0], - this.values[1] - ]; - } - - get xyz(): number[] - { - return [ - this.values[0], - this.values[1], - this.values[2] - ]; - } - - get xyzw(): number[] - { - return [ - this.values[0], - this.values[1], - this.values[2], - this.values[3] - ]; - } - - set x(value: number) - { - this.values[0] = value; - } - - set y(value: number) - { - this.values[1] = value; - } - - set z(value: number) - { - this.values[2] = value; - } - - set w(value: number) - { - this.values[3] = value; - } - - set xy(values: number[]) - { - this.values[0] = values[0]; - this.values[1] = values[1]; - } - - set xyz(values: number[]) - { - this.values[0] = values[0]; - this.values[1] = values[1]; - this.values[2] = values[2]; - } - - set xyzw(values: number[]) - { - this.values[0] = values[0]; - this.values[1] = values[1]; - this.values[2] = values[2]; - this.values[3] = values[3]; - } - - get r(): number - { - return this.values[0]; - } - - get g(): number - { - return this.values[1]; - } - - get b(): number - { - return this.values[2]; - } - - get a(): number - { - return this.values[3]; - } - - get rg(): number[] - { - return [ - this.values[0], - this.values[1] - ]; - } - - get rgb(): number[] - { - return [ - this.values[0], - this.values[1], - this.values[2] - ]; - } - - get rgba(): number[] - { - return [ - this.values[0], - this.values[1], - this.values[2], - this.values[3] - ]; - } - - set r(value: number) - { - this.values[0] = value; - } - - set g(value: number) - { - this.values[1] = value; - } - - set b(value: number) - { - this.values[2] = value; - } - - set a(value: number) - { - this.values[3] = value; - } - - set rg(values: number[]) - { - this.values[0] = values[0]; - this.values[1] = values[1]; - } - - set rgb(values: number[]) - { - this.values[0] = values[0]; - this.values[1] = values[1]; - this.values[2] = values[2]; - } - - set rgba(values: number[]) - { - this.values[0] = values[0]; - this.values[1] = values[1]; - this.values[2] = values[2]; - this.values[3] = values[3]; - } - - constructor(values: number[] | null = null) - { - if (values) - { - this.xyzw = values; - } - } - - at(index: number): number - { - return this.values[index]; - } - - reset(): void - { - this.x = 0; - this.y = 0; - this.z = 0; - this.w = 0; - } - - copy(dest: TSMVec4 | null = null): TSMVec4 - { - if (!dest) - { - dest = new TSMVec4(); - } - - dest.x = this.x; - dest.y = this.y; - dest.z = this.z; - dest.w = this.w; - - return dest; - } - - negate(dest: TSMVec4 | null = null): TSMVec4 - { - if (!dest) - { - dest = this; - } - - dest.x = -this.x; - dest.y = -this.y; - dest.z = -this.z; - dest.w = -this.w; - - return dest; - } - - equals(vector: TSMVec4, threshold = EPSILON): boolean - { - if (Math.abs(this.x - vector.x) > threshold) - { - return false; - } - - if (Math.abs(this.y - vector.y) > threshold) - { - return false; - } - - if (Math.abs(this.z - vector.z) > threshold) - { - return false; - } - - if (Math.abs(this.w - vector.w) > threshold) - { - return false; - } - - return true; - } - - length(): number - { - return Math.sqrt(this.squaredLength()); - } - - squaredLength(): number - { - const x = this.x, - y = this.y, - z = this.z, - w = this.w; - - return (x * x + y * y + z * z + w * w); - } - - add(vector: TSMVec4): TSMVec4 - { - this.x += vector.x; - this.y += vector.y; - this.z += vector.z; - this.w += vector.w; - - return this; - } - - subtract(vector: TSMVec4): TSMVec4 - { - this.x -= vector.x; - this.y -= vector.y; - this.z -= vector.z; - this.w -= vector.w; - - return this; - } - - multiply(vector: TSMVec4): TSMVec4 - { - this.x *= vector.x; - this.y *= vector.y; - this.z *= vector.z; - this.w *= vector.w; - - return this; - } - - divide(vector: TSMVec4): TSMVec4 - { - this.x /= vector.x; - this.y /= vector.y; - this.z /= vector.z; - this.w /= vector.w; - - return this; - } - - scale(value: number, dest: TSMVec4 | null = null): TSMVec4 - { - if (!dest) - { - dest = this; - } - - dest.x *= value; - dest.y *= value; - dest.z *= value; - dest.w *= value; - - return dest; - } - - normalize(dest: TSMVec4 | null = null): TSMVec4 - { - if (!dest) - { - dest = this; - } - - let length = this.length(); - - if (length === 1) - { - return this; - } - - if (length === 0) - { - dest.x *= 0; - dest.y *= 0; - dest.z *= 0; - dest.w *= 0; - - return dest; - } - - length = 1.0 / length; - - dest.x *= length; - dest.y *= length; - dest.z *= length; - dest.w *= length; - - return dest; - } - - multiplyTSMMat4(matrix: TSMMat4, dest: TSMVec4 | null = null): TSMVec4 - { - if (!dest) - { - dest = this; - } - - return matrix.multiplyTSMVec4(this, dest); - } -} - - - diff --git a/package-lock.json b/package-lock.json index 8818f7f..e69de29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,3889 +0,0 @@ -{ - "name": "@caspertech/node-metaverse", - "version": "0.7.24", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@caspertech/node-metaverse", - "version": "0.7.24", - "license": "MIT", - "dependencies": { - "@caspertech/llsd": "^1.0.5", - "@types/long": "^4.0.2", - "@types/mocha": "^9.1.1", - "@types/validator": "^13.11.5", - "@types/xml": "^1.0.10", - "@types/xmlrpc": "^1.3.9", - "chalk": "^4.1.2", - "flatted": "^3.2.9", - "fs-extra": "^10.1.0", - "glob": "^7.2.3", - "got": "^11.8.6", - "ipaddr.js": "^2.1.0", - "logform": "^2.6.0", - "long": "^4.0.0", - "micromatch": "^4.0.5", - "moment": "^2.29.4", - "qs": "^6.5.3", - "rbush-3d": "0.0.4", - "rxjs": "^7.8.1", - "tiny-async-pool": "^2.1.0", - "uuid": "^8.3.2", - "validator": "^13.11.0", - "winston": "^3.11.0", - "xml": "^1.0.1", - "xml2js": "^0.5.0", - "xmlbuilder": "^15.1.1", - "xmlrpc": "github:CasperTech/node-xmlrpc" - }, - "devDependencies": { - "@angular-eslint/eslint-plugin": "^12.7.0", - "@types/micromatch": "^4.0.4", - "@types/node": "^22.4.0", - "@types/tiny-async-pool": "^2.0.2", - "@types/uuid": "^8.3.4", - "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^4.33.0", - "@typescript-eslint/eslint-plugin-tslint": "^4.33.0", - "@typescript-eslint/parser": "^4.33.0", - "eslint": "^7.32.0", - "mocha": "^9.2.2", - "source-map-support": "^0.5.21", - "ts-node": "^10.9.1", - "tslint": "^6.1.3", - "typescript": "^5.5.4" - }, - "engines": { - "node": ">=7.6.0" - } - }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@angular-eslint/bundled-angular-compiler": { - "version": "12.7.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-12.7.0.tgz", - "integrity": "sha512-n7nUSIK+bl2DQXIPRyts/xVTw94Mk0rRNd2WBCL9ni27XKOhKtTdP7tLpD+nAiuY4BTTJr7/yTzPWCCRDQgWZg==", - "dev": true - }, - "node_modules/@angular-eslint/eslint-plugin": { - "version": "12.7.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-12.7.0.tgz", - "integrity": "sha512-TTTimCddON6TdGw3NDglgWqnrP2VLFiAA+FJAg/iiCKKVI+XOddtpDXmeHmas8cHIJXJH1WNxrae394DpThiOA==", - "dev": true, - "dependencies": { - "@angular-eslint/utils": "12.7.0", - "@typescript-eslint/experimental-utils": "4.28.2" - }, - "peerDependencies": { - "eslint": "*", - "typescript": "*" - } - }, - "node_modules/@angular-eslint/utils": { - "version": "12.7.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-12.7.0.tgz", - "integrity": "sha512-1yyRxtxXg6VoyU8wUDcaZEdN7oDE0pRRCUZsQBGungPSv5PQt4nlv+9ZnjJ93rVMEoGztHD2CBWeoRtNlqvg4A==", - "dev": true, - "dependencies": { - "@angular-eslint/bundled-angular-compiler": "12.7.0", - "@typescript-eslint/experimental-utils": "4.28.2" - }, - "peerDependencies": { - "eslint": "*", - "typescript": "*" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/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/@babel/highlight/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/@babel/highlight/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/@babel/highlight/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/@babel/highlight/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/@babel/highlight/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/@babel/highlight/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/@caspertech/llsd": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@caspertech/llsd/-/llsd-1.0.5.tgz", - "integrity": "sha512-IOOGYgrL7ACMDYnA3tkN/NFJbu3z0XvddY2hmS+632nPp2kHIhtulMyw45gwRzpMwNNBtoFr9OxT8MOLZPHhiw==", - "dependencies": { - "@xmldom/xmldom": "^0.7.5", - "abab": "^2.0.5" - } - }, - "node_modules/@colors/colors": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", - "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@dabh/diagnostics": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", - "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", - "dependencies": { - "colorspace": "1.1.x", - "enabled": "2.0.x", - "kuler": "^2.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "dependencies": { - "defer-to-connect": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true - }, - "node_modules/@types/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-8pfphQ0Gtn58PJ+pWB9LsZGz8Q6FTTvc4egkqGT9K1lp4fVF/HeX3d6w/YEkHt/9Luv1r2i+HekuwhAm8UX6/A==", - "dev": true - }, - "node_modules/@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" - }, - "node_modules/@types/json-schema": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", - "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", - "dev": true - }, - "node_modules/@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" - }, - "node_modules/@types/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-ZeDgs/tFSdUqkAZmgdnu5enRwFXJ+nIF4TxK5ENw6x0bvfcgMD1H3GnTS+fIkBUcvijQNF7ZOa2tuOtOaEjt3w==", - "dev": true, - "dependencies": { - "@types/braces": "*" - } - }, - "node_modules/@types/mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==" - }, - "node_modules/@types/node": { - "version": "22.4.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.4.0.tgz", - "integrity": "sha512-49AbMDwYUz7EXxKU/r7mXOsxwFr4BYbvB7tWYxVuLdb2ibd30ijjXINSMAHiEEZk5PCRBmW1gUeisn2VMKt3cQ==", - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/@types/responselike": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", - "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/tape": { - "version": "4.13.4", - "resolved": "https://registry.npmjs.org/@types/tape/-/tape-4.13.4.tgz", - "integrity": "sha512-0Mw8/FAMheD2MvyaFYDaAix7X5GfNjl/XI+zvqJdzC6N05BmHKz6Hwn+r7+8PEXDEKrC3V/irC9z7mrl5a130g==", - "dependencies": { - "@types/node": "*", - "@types/through": "*" - } - }, - "node_modules/@types/through": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.32.tgz", - "integrity": "sha512-7XsfXIsjdfJM2wFDRAtEWp3zb2aVPk5QeyZxGlVK57q4u26DczMHhJmlhr0Jqv0THwxam/L8REXkj8M2I/lcvw==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/tiny-async-pool": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/tiny-async-pool/-/tiny-async-pool-2.0.2.tgz", - "integrity": "sha512-0dTO8/Qh4aA77NGROkocUM+kHBYVfqJLY9gAFqbz/fDw2iQCRLr75jZwzRgb7rx2kGHgPzuP2NvyY0FU0+Ja1Q==", - "dev": true - }, - "node_modules/@types/triple-beam": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.4.tgz", - "integrity": "sha512-HlJjF3wxV4R2VQkFpKe0YqJLilYNgtRtsqqZtby7RkVsSs+i+vbyzjtUwpFEdUCKcrGzCiEJE7F/0mKjh0sunA==" - }, - "node_modules/@types/uuid": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", - "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", - "dev": true - }, - "node_modules/@types/validator": { - "version": "13.11.5", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.5.tgz", - "integrity": "sha512-xW4qsT4UIYILu+7ZrBnfQdBYniZrMLYYK3wN9M/NdeIHgBN5pZI2/8Q7UfdWIcr5RLJv/OGENsx91JIpUUoC7Q==" - }, - "node_modules/@types/xml": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@types/xml/-/xml-1.0.10.tgz", - "integrity": "sha512-fNVNc7SBoHyGPPyKfUPuBeq6h9+h/pH+CJd/ehB8tdbaD07Ch4iVVI/qloC78I5i5ccC3QOKC9yZucibM2Tf0Q==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/xml2js": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz", - "integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/xmlrpc": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/@types/xmlrpc/-/xmlrpc-1.3.9.tgz", - "integrity": "sha512-VW+NEAILh1qaZph0Q49I/JC6Y09obHUWsvMdl0oMaRnitN6Y8WJFDZMCCIVPV8lsII3c0wNoEpWeFx2wnGGAXQ==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz", - "integrity": "sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==", - "dev": true, - "dependencies": { - "@typescript-eslint/experimental-utils": "4.33.0", - "@typescript-eslint/scope-manager": "4.33.0", - "debug": "^4.3.1", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.1.0", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^4.0.0", - "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin-tslint": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin-tslint/-/eslint-plugin-tslint-4.33.0.tgz", - "integrity": "sha512-o3ujMErtZJPgiNRETRJefo1bFNrloocOa5dMU49OW/G+Rq92IbXTY6FSF5MOwrdQK1X+VBEcA8y6PhUPWGlYqA==", - "dev": true, - "dependencies": { - "@typescript-eslint/experimental-utils": "4.33.0", - "lodash": "^4.17.21" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "peerDependencies": { - "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0", - "tslint": "^5.0.0 || ^6.0.0", - "typescript": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin-tslint/node_modules/@typescript-eslint/experimental-utils": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz", - "integrity": "sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.33.0", - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/typescript-estree": "4.33.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin-tslint/node_modules/@typescript-eslint/types": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz", - "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==", - "dev": true, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin-tslint/node_modules/@typescript-eslint/typescript-estree": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz", - "integrity": "sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/visitor-keys": "4.33.0", - "debug": "^4.3.1", - "globby": "^11.0.3", - "is-glob": "^4.0.1", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/experimental-utils": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz", - "integrity": "sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.33.0", - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/typescript-estree": "4.33.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz", - "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==", - "dev": true, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz", - "integrity": "sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/visitor-keys": "4.33.0", - "debug": "^4.3.1", - "globby": "^11.0.3", - "is-glob": "^4.0.1", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/experimental-utils": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.2.tgz", - "integrity": "sha512-MwHPsL6qo98RC55IoWWP8/opTykjTp4JzfPu1VfO2Z0MshNP0UZ1GEV5rYSSnZSUI8VD7iHvtIPVGW5Nfh7klQ==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.28.2", - "@typescript-eslint/types": "4.28.2", - "@typescript-eslint/typescript-estree": "4.28.2", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - } - }, - "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/scope-manager": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.2.tgz", - "integrity": "sha512-MqbypNjIkJFEFuOwPWNDjq0nqXAKZvDNNs9yNseoGBB1wYfz1G0WHC2AVOy4XD7di3KCcW3+nhZyN6zruqmp2A==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "4.28.2", - "@typescript-eslint/visitor-keys": "4.28.2" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.2.tgz", - "integrity": "sha512-aT2B4PLyyRDUVUafXzpZFoc0C9t0za4BJAKP5sgWIhG+jHECQZUEjuQSCIwZdiJJ4w4cgu5r3Kh20SOdtEBl0w==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "4.28.2", - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz", - "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "4.33.0", - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/typescript-estree": "4.33.0", - "debug": "^4.3.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz", - "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==", - "dev": true, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz", - "integrity": "sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/visitor-keys": "4.33.0", - "debug": "^4.3.1", - "globby": "^11.0.3", - "is-glob": "^4.0.1", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz", - "integrity": "sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/visitor-keys": "4.33.0" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/scope-manager/node_modules/@typescript-eslint/types": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz", - "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==", - "dev": true, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.2.tgz", - "integrity": "sha512-Gr15fuQVd93uD9zzxbApz3wf7ua3yk4ZujABZlZhaxxKY8ojo448u7XTm/+ETpy0V0dlMtj6t4VdDvdc0JmUhA==", - "dev": true, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.2.tgz", - "integrity": "sha512-86lLstLvK6QjNZjMoYUBMMsULFw0hPHJlk1fzhAVoNjDBuPVxiwvGuPQq3fsBMCxuDJwmX87tM/AXoadhHRljg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "4.28.2", - "@typescript-eslint/visitor-keys": "4.28.2", - "debug": "^4.3.1", - "globby": "^11.0.3", - "is-glob": "^4.0.1", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/visitor-keys": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.2.tgz", - "integrity": "sha512-aT2B4PLyyRDUVUafXzpZFoc0C9t0za4BJAKP5sgWIhG+jHECQZUEjuQSCIwZdiJJ4w4cgu5r3Kh20SOdtEBl0w==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "4.28.2", - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz", - "integrity": "sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "4.33.0", - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/@typescript-eslint/types": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz", - "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==", - "dev": true, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, - "node_modules/@xmldom/xmldom": { - "version": "0.7.13", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.13.tgz", - "integrity": "sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==" - }, - "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", - "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "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/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "engines": { - "node": ">=10.6.0" - } - }, - "node_modules/cacheable-request": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "dependencies": { - "mimic-response": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "dependencies": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/color/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==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color/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==" - }, - "node_modules/colorspace": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", - "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", - "dependencies": { - "color": "^3.1.3", - "text-hex": "1.0.x" - } - }, - "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", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "engines": { - "node": ">=10" - } - }, - "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/enabled": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enquirer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint/node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fecha": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", - "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/flat-cache": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", - "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", - "dev": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" - }, - "node_modules/fn.name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" - }, - "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "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", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" - }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true, - "engines": { - "node": ">=4.x" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "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", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" - }, - "node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ipaddr.js": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", - "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", - "engines": { - "node": ">= 10" - } - }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "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", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kuler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/logform": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.0.tgz", - "integrity": "sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==", - "dependencies": { - "@colors/colors": "1.6.0", - "@types/triple-beam": "^1.3.2", - "fecha": "^4.2.0", - "ms": "^2.1.1", - "safe-stable-stringify": "^2.3.1", - "triple-beam": "^1.3.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "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", - "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", - "dev": true, - "dependencies": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.3", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "4.2.1", - "ms": "2.1.3", - "nanoid": "3.3.1", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "workerpool": "6.2.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/mocha/node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/mocha/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/mocha/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mocha/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/mocha/node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", - "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/one-time": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", - "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", - "dependencies": { - "fn.name": "1.x.x" - } - }, - "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "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", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/quickselect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-1.1.1.tgz", - "integrity": "sha512-qN0Gqdw4c4KGPsBOQafj6yj/PA6c/L63f6CaZ/DCF/xF4Esu3jVmKLUDYxghFx8Kb/O7y9tI7x2RjTSXwdK1iQ==" - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/rbush-3d": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/rbush-3d/-/rbush-3d-0.0.4.tgz", - "integrity": "sha512-s02wJ4Oawn3xdfIUN1hO+dhIcI3zL4vFs+yTlFn/U1jpHh3zaIcaOhLVL6SxyhB4vR/wad77HVJ9dG/ZyLZ6tQ==", - "dependencies": { - "@types/node": "^10.5.1", - "@types/tape": "^4.2.32", - "quickselect": "^1.0.0" - } - }, - "node_modules/rbush-3d/node_modules/@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==" - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "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-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dependencies": { - "lowercase-keys": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safe-stable-stringify": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", - "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", - "engines": { - "node": ">=10" - } - }, - "node_modules/sax": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", - "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" - }, - "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", - "engines": { - "node": "*" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "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", - "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", - "dev": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/text-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/tiny-async-pool": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-2.1.0.tgz", - "integrity": "sha512-ltAHPh/9k0STRQqaoUX52NH4ZQYAJz24ZAEwf1Zm+HYg3l9OXTWeqWKyYsHu40wF/F0rxd2N2bk5sLvX2qlSvg==" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/triple-beam": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", - "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node/node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ts-node/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/tslib": { - "version": "2.6.2", - "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", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/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/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", - "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.19.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.6.tgz", - "integrity": "sha512-e/vggGopEfTKSvj4ihnOLTsqhrKRN3LeO6qSN/GxohhuRv8qH9bNQ4B8W7e/vFL+0XTnmHPB4/kegunZGA4Org==" - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", - "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", - "dev": true - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "node_modules/validator": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", - "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/winston": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.11.0.tgz", - "integrity": "sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==", - "dependencies": { - "@colors/colors": "^1.6.0", - "@dabh/diagnostics": "^2.0.2", - "async": "^3.2.3", - "is-stream": "^2.0.0", - "logform": "^2.4.0", - "one-time": "^1.0.0", - "readable-stream": "^3.4.0", - "safe-stable-stringify": "^2.3.1", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.5.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/winston-transport": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.6.0.tgz", - "integrity": "sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg==", - "dependencies": { - "logform": "^2.3.2", - "readable-stream": "^3.6.0", - "triple-beam": "^1.3.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/workerpool": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", - "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/xml": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", - "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==" - }, - "node_modules/xml2js": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", - "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xml2js/node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/xmlbuilder": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", - "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", - "engines": { - "node": ">=8.0" - } - }, - "node_modules/xmlrpc": { - "version": "1.3.2", - "resolved": "git+ssh://git@github.com/CasperTech/node-xmlrpc.git#c5d52f9aa61ccc877fd0c69b63f4793eedf366d8", - "license": "MIT", - "dependencies": { - "sax": "1.2.x", - "xmlbuilder": "8.2.x" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.0.0" - } - }, - "node_modules/xmlrpc/node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "node_modules/xmlrpc/node_modules/xmlbuilder": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", - "integrity": "sha512-eKRAFz04jghooy8muekqzo8uCSVNeyRedbuJrp0fovbLIi7wlsYtdUn3vBAAPq2Y3/0xMz2WMEUQ8yhVVO9Stw==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/package.json b/package.json index 7d9074f..1ba7195 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@caspertech/node-metaverse", - "version": "0.7.50", + "version": "0.8.0", "description": "A node.js interface for Second Life.", "main": "dist/lib/index.js", "types": "dist/lib/index.d.ts", @@ -23,16 +23,16 @@ "url": "git+https://github.com/CasperTech/node-metaverse.git" }, "devDependencies": { - "@angular-eslint/eslint-plugin": "^12.7.0", + "@eslint/eslintrc": "^3.2.0", "@types/micromatch": "^4.0.4", "@types/node": "^22.4.0", "@types/tiny-async-pool": "^2.0.2", "@types/uuid": "^8.3.4", + "@types/validator": "^13.12.2", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^4.33.0", - "@typescript-eslint/eslint-plugin-tslint": "^4.33.0", - "@typescript-eslint/parser": "^4.33.0", - "eslint": "^7.32.0", + "@typescript-eslint/eslint-plugin": "^8.15.0", + "@typescript-eslint/parser": "^8.15.0", + "eslint": "^9.15.0", "mocha": "^9.2.2", "source-map-support": "^0.5.21", "ts-node": "^10.9.1", @@ -43,15 +43,15 @@ "@caspertech/llsd": "^1.0.5", "@types/long": "^4.0.2", "@types/mocha": "^9.1.1", - "@types/validator": "^13.11.5", "@types/xml": "^1.0.10", "@types/xmlrpc": "^1.3.9", "chalk": "^4.1.2", + "fast-xml-parser": "^4.5.0", "flatted": "^3.2.9", "fs-extra": "^10.1.0", "glob": "^7.2.3", "got": "^11.8.6", - "ipaddr.js": "^2.1.0", + "ipaddr.js": "^2.2.0", "logform": "^2.6.0", "long": "^4.0.0", "micromatch": "^4.0.5", @@ -61,7 +61,8 @@ "rxjs": "^7.8.1", "tiny-async-pool": "^2.1.0", "uuid": "^8.3.2", - "validator": "^13.11.0", + "validator": "^13.12.0", + "vitest": "^2.1.6", "winston": "^3.11.0", "xml": "^1.0.1", "xml2js": "^0.5.0", diff --git a/testing/TestingUtils.util.spec.ts b/testing/TestingUtils.util.spec.ts new file mode 100644 index 0000000..8f645e6 --- /dev/null +++ b/testing/TestingUtils.util.spec.ts @@ -0,0 +1,352 @@ +import { expect } from 'vitest'; +import { Vector4 } from '../lib/classes/Vector4'; +import { Quaternion } from '../lib/classes/Quaternion'; +import { LLSDReal } from '../lib/classes/llsd/LLSDReal'; +import { LLSDURI } from '../lib/classes/llsd/LLSDURI'; +import { Vector3 } from '../lib/classes/Vector3'; +import { Vector2 } from '../lib/classes/Vector2'; +import { UUID } from '../lib'; +import { LLSDInteger } from '../lib/classes/llsd/LLSDInteger'; +import { isInt } from 'validator'; + +function compareObject(path: string, one: unknown, two: unknown, visited = new Set()) +{ + if (one === null) + { + expect(one, path + ' (null)').toBe(two); + if (two !== null) + { + console.error(path + ': Mismatch - One is NULL, two is ' + typeof two); + return; + } + return; + } + else if (typeof one === 'string') + { + expect(one, path + ' (string)').toBe(two); + return; + } + else if (typeof one === 'number') + { + if (isInt(String(one)) && isInt(String(two))) + { + expect(one, path + ' (number)').toBe(two); + } + else if (isNaN(one)) + { + expect(isNaN(Number(two)), path + ' (number)').toBeTruthy; + } + else + { + expect(one, path + ' (number)').toBeCloseTo(Number(two), 6); + } + } + else if (typeof one === 'boolean') + { + expect(typeof two, path + ' (Boolean)').toBe('boolean'); + expect(one, path + ' (Boolean)').toBe(two); + } + else if (one instanceof Buffer) + { + expect(two, path).toBeInstanceOf(Buffer); + if (two instanceof Buffer) + { + expect(one.length, path + ' (Buffer)').toBe(two.length); + expect(one.toString('base64'), path + ' (Buffer)').toBe(two.toString('base64')); + } + } + else if (one instanceof Date) + { + expect(two, path + ' (Date)').toBeInstanceOf(Date); + if (two instanceof Date) + { + if (isNaN(one.getTime())) + { + expect(one.getTime(), path + ' (Date)').toBe(two.getTime()); + } + else + { + expect(one.getTime(), path + ' (Date)').toBeGreaterThanOrEqual(two.getTime() - 1); + expect(one.getTime(), path + ' (Date)').toBeLessThanOrEqual(two.getTime() + 1); + } + } + } + else if (Array.isArray(one)) + { + expect(Array.isArray(two), path).toBeTruthy(); + if (Array.isArray(two)) + { + expect(one.length, path).toBe(two.length); + for (let x = 0; x < one.length; x++) + { + compareObject(path + '[' + String(x) + ']', one[x], two[x], visited); + } + } + } + else if (one instanceof Map) + { + expect(two).toBeInstanceOf(Map); + if (two instanceof Map) + { + for (const k of one.keys()) + { + const v = one.get(k); + const v2 = two.get(k); + compareObject(path + '<' + String(v) + '>', v, v2, visited); + } + for (const k of two.keys()) + { + expect(one.has(k)).toBeTruthy(); + } + } + } + else if (one instanceof LLSDInteger) + { + expect(two, path).toBeInstanceOf(LLSDInteger); + if (two instanceof LLSDInteger) + { + expect(one.valueOf(), path + ' (LLSDInteger)').toBe(two.valueOf()); + } + } + else if (one instanceof LLSDReal) + { + expect(two, path).toBeInstanceOf(LLSDReal); + if (two instanceof LLSDReal) + { + const oneVal = one.valueOf(); + const twoVal = two.valueOf(); + if (isNaN(oneVal)) + { + expect(isNaN(twoVal), path + ' (LLSDReal)').toBeTruthy(); + } + else + { + expect(oneVal, path + ' (LLSDReal)').toBeCloseTo(twoVal); + } + } + } + else if (one instanceof LLSDURI) + { + expect(two, path).toBeInstanceOf(LLSDURI); + if (two instanceof LLSDURI) + { + expect(one.valueOf(), path + ' (LLSDURI)').toBe(two.valueOf()); + } + } + /*else if (one instanceof Matrix4) + { + expect(two).toBeInstanceOf(Matrix4); + if (two instanceof Matrix4) + { + const arr1 = one.all(); + const arr2 = two.all(); + expect(arr1.length, path + ' (Matrix4)').toBe(arr2.length); + for(let x = 0; x < arr1.length; x++) + { + if (isNaN(arr1[x])) + { + expect(isNaN(arr2[x]), path + ' (Matrix4)').toBeTruthy(); + } + else + { + expect(arr1[x], path + ' (Matrix4)').toBeCloseTo(arr2[x], 6); + } + } + } + }*/ + else if (one instanceof Quaternion) + { + expect(two).toBeInstanceOf(Quaternion); + if (two instanceof Quaternion) + { + if (isNaN(one.x)) + { + expect(isNaN(two.x), path + ' (Quaternion)').toBeTruthy(); + } + else + { + expect(one.x, path + ' (Quaternion)').toBeCloseTo(two.x, 6); + } + if (isNaN(one.y)) + { + expect(isNaN(two.y), path + ' (Quaternion)').toBeTruthy(); + } + else + { + expect(one.y, path + ' (Quaternion)').toBeCloseTo(two.y, 6); + } + if (isNaN(one.z)) + { + expect(isNaN(two.z), path + ' (Quaternion)').toBeTruthy(); + } + else + { + expect(one.z, path + ' (Quaternion)').toBeCloseTo(two.z, 6); + } + if (isNaN(one.w)) + { + expect(isNaN(two.w), path + ' (Quaternion)').toBeTruthy(); + } + else + { + expect(one.w, path + ' (Quaternion)').toBeCloseTo(two.w, 6); + } + } + } + else if (one instanceof Vector4) + { + expect(two).toBeInstanceOf(Vector4); + if (two instanceof Vector4) + { + if (isNaN(one.x)) + { + expect(isNaN(two.x), path + ' (Vector4)').toBeTruthy(); + } + else + { + expect(one.x, path + ' (Vector4)').toBeCloseTo(two.x, 6); + } + if (isNaN(one.y)) + { + expect(isNaN(two.y), path + ' (Vector4)').toBeTruthy(); + } + else + { + expect(one.y, path + ' (Vector4)').toBeCloseTo(two.y, 6); + } + if (isNaN(one.z)) + { + expect(isNaN(two.z), path + ' (Vector4)').toBeTruthy(); + } + else + { + expect(one.z, path + ' (Vector4)').toBeCloseTo(two.z, 6); + } + if (isNaN(one.w)) + { + expect(isNaN(two.w), path + ' (Vector4)').toBeTruthy(); + } + else + { + expect(one.w, path + ' (Vector4)').toBeCloseTo(two.w, 6); + } + } + } + else if (one instanceof Vector3) + { + expect(two).toBeInstanceOf(Vector3); + if (two instanceof Vector3) + { + if (isNaN(one.x)) + { + expect(isNaN(two.x), path + ' (Vector3)').toBeTruthy(); + } + else + { + expect(one.x, path + ' (Vector3)').toBeCloseTo(two.x, 6); + } + if (isNaN(one.y)) + { + expect(isNaN(two.y), path + ' (Vector3)').toBeTruthy(); + } + else + { + expect(one.y, path + ' (Vector3)').toBeCloseTo(two.y, 6); + } + if (isNaN(one.z)) + { + expect(isNaN(two.z), path + ' (Vector3)').toBeTruthy(); + } + else + { + expect(one.z, path + ' (Vector3)').toBeCloseTo(two.z, 6); + } + } + } + else if (one instanceof Vector2) + { + expect(two).toBeInstanceOf(Vector2); + if (two instanceof Vector2) + { + if (isNaN(one.x)) + { + expect(isNaN(two.x), path + ' (Vector2)').toBeTruthy(); + } + else + { + expect(one.x, path + ' (Vector2)').toBeCloseTo(two.x, 6); + } + if (isNaN(one.y)) + { + expect(isNaN(two.y), path + ' (Vector2)').toBeTruthy(); + } + else + { + expect(one.y, path + ' (Vector2)').toBeCloseTo(two.y, 6); + } + } + } + else if (one instanceof UUID) + { + expect(two).toBeInstanceOf(UUID); + if (two instanceof UUID) + { + expect(one.toString(), path + ' (UUID)').toBe(two.toString()); + } + } + else if (typeof one === 'object') + { + if (visited.has(one)) + { + return; + } + visited.add(one); + + expect(typeof two, path + ' (object)').toBe('object'); + expect(two, path + ' (object)').not.toBeNull(); + if (one.constructor.name !== 'Object' && one.constructor.name !== 'LLMesh' && one.constructor.name !== 'LLSettings') + { + throw new Error('Unhandled object ' + one.constructor.name); + } + if (typeof two === 'object' && two !== null) + { + expect(one.constructor.name, path + ' (object)').toBe(two.constructor.name); + + const keys = Object.keys(one); + for (const k of keys) + { + const subPath = path + '[\'' + k + '\']'; + if ((one as any)[k] !== undefined) + { + expect((two as any)[k], subPath + ' (object)').toBeDefined(); + compareObject(subPath, (one as any)[k], (two as any)[k], visited); + } + } + } + } + else + { + throw new Error('Unknown object type: ' + one); + } +} + +export function toDeeplyMatch(received: unknown, expected: unknown) +{ + const visited = new Set(); + + try + { + compareObject('', received, expected, visited); + return { + pass: true, + message: () => `Expected objects to not deeply match, but they did.`, + }; + } + catch (error: any) + { + return { + pass: false, + message: () => error.message || `Objects do not deeply match.`, + }; + } +} diff --git a/tsconfig.json b/tsconfig.json index b5aabb6..30fdd55 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es6", + "target": "es2020", "module": "commonjs", "declaration": true, "outDir": "dist", @@ -15,7 +15,7 @@ "strictBindCallApply": true, "strictPropertyInitialization": false, "strictNullChecks": true, - "noFallthroughCasesInSwitch": true, + "noFallthroughCasesInSwitch": false, "types": [ "node" ] @@ -25,6 +25,7 @@ "examples/**/*.ts" ], "exclude": [ - "node_modules" + "node_modules", + "lib/**/*.spec.ts" ] }