NMV 0.8.0 - Big refactor and linting fixes

This commit is contained in:
Casper Warden
2025-01-17 23:37:54 +00:00
parent 3870861b0a
commit 53659008ac
210 changed files with 17588 additions and 18300 deletions

224
.eslintrc
View File

@@ -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"
]
}
}
]
}
}

104
eslint.config.mjs Normal file
View File

@@ -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,
}],
},
},
];

View File

@@ -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<void>
{
console.log(prefix + ' [' + folder.name + ']');
await folder.populate(false);
// Set to false to skip cache
await folder.populate();
for (const subFolder of folder.folders)
{

View File

@@ -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<void>
public async onConnected(): Promise<void>
{
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<void>
public async onBalanceUpdated(evt: BalanceUpdatedEvent): Promise<void>
{
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);
});

View File

@@ -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<void>
public async onConnected(): Promise<void>
{
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);
});

View File

@@ -0,0 +1,19 @@
import { ExampleBot } from '../ExampleBot';
class Region extends ExampleBot
{
async onConnected(): Promise<void>
{
console.log('Exporting:');
console.log(this.bot.currentRegion.exportXML());
console.log('done');
}
}
new Region().run().then(() =>
{
}).catch((err) =>
{
console.error(err);
});

View File

@@ -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<string, {
timer?: NodeJS.Timeout,
resolve: (value: (void | PromiseLike<void>)) => 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<LoginResponse>
public async login(): Promise<LoginResponse>
{
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<void>
public async changeRegion(region: Region, requested: boolean): Promise<void>
{
this.closeCircuit();
this._currentRegion = region;
@@ -153,7 +160,7 @@ export class Bot
await this.connectToSim(requested);
}
waitForEventQueue(timeout: number = 1000): Promise<void>
public async waitForEventQueue(timeout = 1000): Promise<void>
{
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>)) => 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<void>
public async close(): Promise<void>
{
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<void>
public async connectToSim(requested = false): Promise<void>
{
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<void> =>
{
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<CompletePingCheckMessage>(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<CompletePingCheckMessage>(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);
}
}
}

View File

@@ -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));
}

View File

@@ -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<string, {
public firstName: string;
public lastName: string;
public localID = 0;
public agentID: UUID;
public activeGroupID: UUID = UUID.zero();
public accessMax: string;
public regionAccess: string;
public agentAccess: string;
public currentRegion: Region;
public openID: {
'token'?: string,
'url'?: string
} = {};
public AOTransition: boolean;
public buddyList: {
'buddyRightsGiven': boolean,
'buddyID': UUID,
'buddyRightsHas': boolean
}[] = [];
public uiFlags: {
'allowFirstLife'?: boolean
} = {};
public maxGroups: number;
public agentFlags: number;
public startLocation: string;
public cofVersion: number;
public home: {
'regionHandle'?: Long,
'position'?: Vector3,
'lookAt'?: Vector3
} = {};
public snapshotConfigURL: string;
public readonly inventory: Inventory;
public gestures: {
assetID: UUID,
itemID: UUID
}[] = [];
public estateManager = false;
public appearanceComplete = false;
public agentAppearanceService: string;
public onGroupChatExpired = new Subject<UUID>();
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<void> = new Subject<void>();
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<string, {
agents: Map<string, {
hasVoice: boolean;
isModerator: boolean
}>,
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<void> = new Subject<void>();
private clientEvents: ClientEvents;
private animSubscription?: Subscription;
public onGroupChatExpired = new Subject<UUID>();
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<void>
{
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<AgentWearablesUpdateMessage>(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<InventoryFolder>
{
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<InventoryFolder>
{
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<void>
{
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<AgentWearablesUpdateMessage>(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;
}
}

View File

@@ -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<T = InventoryItem>
{
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<T = InventoryItem>
{
private readonly map = new Map<string, AssetData<T>>();
private readonly pending = new Map<string, number>();
public get(uuid: UUID | string): AssetData<T> | undefined
{
if (uuid instanceof UUID)
{
uuid = uuid.toString();
}
return this.map.get(uuid);
}
public request(uuid: UUID | string, metadata?: AssetData<T>): 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);
}
}
}
}

View File

@@ -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<Material>();
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<string, {
gameObject: GameObject,
scripts: {
item: InventoryItem,
oldAssetID: UUID,
mono: boolean,
shouldStart: boolean
}[]
}>;
public temporaryInventory = new Map<string, InventoryItem>();
public byUUID = new Map<string, InventoryItem>();
}

View File

@@ -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<AssetType, RegisteredAssetType>();
private static readonly assetTypeByName = new Map<string, RegisteredAssetType>();
private static readonly assetTypeByHumanName = new Map<string, RegisteredAssetType>();
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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -3,8 +3,8 @@ import { Subject } from 'rxjs';
export class BatchQueue<T>
{
private running = false;
private pending: Set<T> = new Set<T>();
private onResult = new Subject<{
private readonly pending: Set<T> = new Set<T>();
private readonly onResult = new Subject<{
batch: Set<T>,
failed?: Set<T>,
exception?: unknown
@@ -26,7 +26,7 @@ export class BatchQueue<T>
if (!this.running)
{
this.processBatch().catch((_e) =>
this.processBatch().catch((_e: unknown) =>
{
// ignore
});
@@ -61,7 +61,7 @@ export class BatchQueue<T>
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<T>
this.running = false;
if (this.pending.size > 0)
{
this.processBatch().catch((_e) =>
this.processBatch().catch((_e: unknown) =>
{
// ignore
});

View File

@@ -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 = <T>(
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<string>(
(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);
});
});
});

443
lib/classes/BinaryReader.ts Normal file
View File

@@ -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}`);
}
}
}

View File

@@ -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 = <T>(
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<number>(
(bw, val) => bw.writeUInt8(val),
(br) => br.readUInt8(),
value
);
});
it('should write and read UInt16LE correctly', () =>
{
const value = 65535;
testRoundTrip<number>(
(bw, val) => bw.writeUInt16LE(val),
(br) => br.readUInt16LE(),
value
);
});
it('should write and read UInt16BE correctly', () =>
{
const value = 65535;
testRoundTrip<number>(
(bw, val) => bw.writeUInt16BE(val),
(br) => br.readUInt16BE(),
value
);
});
it('should write and read UInt32LE correctly', () =>
{
const value = 4294967295;
testRoundTrip<number>(
(bw, val) => bw.writeUInt32LE(val),
(br) => br.readUInt32LE(),
value
);
});
it('should write and read UInt32BE correctly', () =>
{
const value = 4294967295;
testRoundTrip<number>(
(bw, val) => bw.writeUInt32BE(val),
(br) => br.readUInt32BE(),
value
);
});
it('should write and read UInt64LE correctly', () =>
{
const value = 18446744073709551615n; // Max UInt64
testRoundTrip<bigint>(
(bw, val) => bw.writeUInt64LE(val),
(br) => br.readUInt64LE(),
value
);
});
it('should write and read UInt64BE correctly', () =>
{
const value = 18446744073709551615n; // Max UInt64
testRoundTrip<bigint>(
(bw, val) => bw.writeUInt64BE(val),
(br) => br.readUInt64BE(),
value
);
});
});
describe('Signed Integers', () =>
{
it('should write and read Int8 correctly', () =>
{
const value = -128;
testRoundTrip<number>(
(bw, val) => bw.writeInt8(val),
(br) => br.readInt8(),
value
);
});
it('should write and read Int16LE correctly', () =>
{
const value = -32768;
testRoundTrip<number>(
(bw, val) => bw.writeInt16LE(val),
(br) => br.readInt16LE(),
value
);
});
it('should write and read Int16BE correctly', () =>
{
const value = -32768;
testRoundTrip<number>(
(bw, val) => bw.writeInt16BE(val),
(br) => br.readInt16BE(),
value
);
});
it('should write and read Int32LE correctly', () =>
{
const value = -2147483648;
testRoundTrip<number>(
(bw, val) => bw.writeInt32LE(val),
(br) => br.readInt32LE(),
value
);
});
it('should write and read Int32BE correctly', () =>
{
const value = -2147483648;
testRoundTrip<number>(
(bw, val) => bw.writeInt32BE(val),
(br) => br.readInt32BE(),
value
);
});
it('should write and read Int64LE correctly', () =>
{
const value = -9223372036854775808n; // Min Int64
testRoundTrip<bigint>(
(bw, val) => bw.writeInt64LE(val),
(br) => br.readInt64LE(),
value
);
});
it('should write and read Int64BE correctly', () =>
{
const value = -9223372036854775808n; // Min Int64
testRoundTrip<bigint>(
(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<number>(
(bw, val) => bw.writeFloatLE(val),
(br) => br.readFloatLE(),
value
);
});
it('should write and read FloatBE correctly', () =>
{
const value = 12345.6789;
testRoundTrip<number>(
(bw, val) => bw.writeFloatBE(val),
(br) => br.readFloatBE(),
value
);
});
it('should write and read DoubleLE correctly', () =>
{
const value = 123456789.123456789;
testRoundTrip<number>(
(bw, val) => bw.writeDoubleLE(val),
(br) => br.readDoubleLE(),
value
);
});
it('should write and read DoubleBE correctly', () =>
{
const value = 123456789.123456789;
testRoundTrip<number>(
(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]);
}
});
});
});

268
lib/classes/BinaryWriter.ts Normal file
View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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<void>, public readonly costOnly = false)
{
}

View File

@@ -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<string, number> = {
'NewFileAgentInventory': 2000,
'FetchInventory2': 200
};
private onGotSeedCap: Subject<void> = new Subject<void>();
private readonly onGotSeedCap: Subject<void> = new Subject<void>();
private gotSeedCap = false;
private capabilities: { [key: string]: string } = {};
private clientEvents: ClientEvents;
private agent: Agent;
private capabilities: Record<string, string> = {};
private readonly clientEvents: ClientEvents;
private readonly agent: Agent;
private active = false;
private timeLastCapExecuted: { [key: string]: number } = {};
eventQueueClient: EventQueueClient | null = null;
private timeLastCapExecuted: Record<string, number> = {};
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<Buffer>
public async downloadAsset(uuid: UUID, type: AssetType): Promise<Buffer>
{
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<void>
{
return new Promise((resolve) =>
{
if (this.gotSeedCap)
{
resolve();
}
else
{
const sub: Subscription = this.onGotSeedCap.subscribe(() =>
{
sub.unsubscribe();
resolve();
});
}
});
}
async isCapAvailable(capability: string): Promise<boolean>
public async isCapAvailable(capability: string): Promise<boolean>
{
await this.waitForSeedCapability();
return (this.capabilities[capability] !== undefined);
}
getCapability(capability: string): Promise<string>
public async getCapability(capability: string): Promise<string>
{
return new Promise<string>((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<any>
{
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<any>
public async capsPerformXMLPost(capURL: string, data: any): Promise<any>
{
return new Promise<any>((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<void>
{
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<any>
public async capsPerformXMLPut(capURL: string, data: any): Promise<any>
{
return new Promise<any>(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<any>
public async capsPerformXMLGet(capURL: string): Promise<any>
{
return new Promise<any>(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<any>
public async capsPerformGet(capURL: string): Promise<string>
{
return new Promise<any>(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<any>
public async capsGetXML(capability: string | [string, Record<string, string>]): Promise<any>
{
let capName = '';
let queryParams: { [key: string]: string } = {};
let queryParams: Record<string, string> = {};
if (typeof capability === 'string')
{
capName = capability;
@@ -508,10 +429,48 @@ export class Caps
}
}
async capsPostXML(capability: string | [string, { [key: string]: string }], data: any): Promise<any>
public async capsGetString(capability: string | [string, Record<string, string>]): Promise<string>
{
let capName = '';
let queryParams: { [key: string]: string } = {};
let queryParams: Record<string, string> = {};
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<string, string>], data: any): Promise<any>
{
let capName = '';
let queryParams: Record<string, string> = {};
if (typeof capability === 'string')
{
capName = capability;
@@ -546,10 +505,10 @@ export class Caps
}
}
async capsPutXML(capability: string | [string, { [key: string]: string }], data: any): Promise<any>
public async capsPutXML(capability: string | [string, Record<string, string>], data: any): Promise<any>
{
let capName = '';
let queryParams: { [key: string]: string } = {};
let queryParams: Record<string, string> = {};
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<void>
{
return new Promise((resolve) =>
{
if (this.gotSeedCap)
{
resolve();
}
else
{
const sub: Subscription = this.onGotSeedCap.subscribe(() =>
{
sub.unsubscribe();
resolve();
});
}
});
}
private async waitForCapTimeout(capName: string): Promise<void>
{
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();
}
}
});
}
}

View File

@@ -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<number, {
packet: Packet,
timeout: NodeJS.Timeout,
sent: number
}>();
private onPacketReceived: Subject<Packet>;
private onAckReceived: Subject<number>;
private readonly receivedPackets = new Map<number, NodeJS.Timeout>();
private active = false;
constructor()
private readonly onPacketReceived: Subject<Packet>;
private readonly onAckReceived: Subject<number>;
public constructor()
{
this.onPacketReceived = new Subject<Packet>();
this.onAckReceived = new Subject<number>();
}
subscribeToMessages(ids: number[], callback: (packet: Packet) => void): Subscription
public subscribeToMessages(ids: number[], callback: (packet: Packet) => Promise<void> | void): Subscription
{
const lookupObject: { [key: number]: boolean } = {};
const lookupObject: Record<number, boolean> = {};
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<void>
public async XferFileUp(xferID: Long, data: Buffer): Promise<void>
{
return new Promise<void>((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<Buffer>
public async XferFileDown(fileName: string, deleteOnCompletion: boolean, useBigPackets: boolean, vFileID: UUID, vFileType: AssetType, fromCache: boolean): Promise<Buffer>
{
return new Promise<Buffer>((resolve, reject) =>
{
let subscription: null | Subscription = null;
let timeout: NodeJS.Timeout | null = null;
const receivedChunks: { [key: number]: Buffer } = {};
const receivedChunks: Record<number, Buffer> = {};
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<void>
public async waitForAck(ack: number, timeout: number): Promise<void>
{
return new Promise<void>((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<T extends MessageBase>(id: Message, timeout: number, messageFilter?: (message: T) => FilterResponse): Promise<T>
public async sendAndWaitForMessage<T extends MessageBase>(message: MessageBase, flags: PacketFlags, id: Message, timeout: number, messageFilter?: (message: T) => FilterResponse): Promise<T>
{
const awaiter = this.waitForMessage(id, timeout, messageFilter);
this.sendMessage(message, flags);
return awaiter;
}
public async waitForMessage<T extends MessageBase>(id: Message, timeout: number, messageFilter?: (message: T) => FilterResponse): Promise<T>
{
return new Promise<T>((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);

View File

@@ -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();

View File

@@ -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<ChatEvent> = new Subject<ChatEvent>();
onInstantMessage: Subject<InstantMessageEvent> = new Subject<InstantMessageEvent>();
onGroupInvite: Subject<GroupInviteEvent> = new Subject<GroupInviteEvent>();
onFriendRequest: Subject<FriendRequestEvent> = new Subject<FriendRequestEvent>();
onInventoryOffered: Subject<InventoryOfferedEvent> = new Subject<InventoryOfferedEvent>();
onLure: Subject<LureEvent> = new Subject<LureEvent>();
onTeleportEvent: Subject<TeleportEvent> = new Subject<TeleportEvent>();
onDisconnected: Subject<DisconnectEvent> = new Subject<DisconnectEvent>();
onCircuitLatency: Subject<number> = new Subject<number>();
onGroupChat: Subject<GroupChatEvent> = new Subject<GroupChatEvent>();
onGroupChatClosed: Subject<GroupChatClosedEvent> = new Subject<GroupChatClosedEvent>();
onGroupNotice: Subject<GroupNoticeEvent> = new Subject<GroupNoticeEvent>();
onGroupChatSessionJoin: Subject<GroupChatSessionJoinEvent> = new Subject<GroupChatSessionJoinEvent>();
onGroupChatAgentListUpdate: Subject<GroupChatSessionAgentListEvent> = new Subject<GroupChatSessionAgentListEvent>();
onFriendResponse: Subject<FriendResponseEvent> = new Subject<FriendResponseEvent>();
onInventoryResponse: Subject<InventoryResponseEvent> = new Subject<InventoryResponseEvent>();
onScriptDialog: Subject<ScriptDialogEvent> = new Subject<ScriptDialogEvent>();
onEventQueueStateChange: Subject<EventQueueStateChangeEvent> = new Subject<EventQueueStateChangeEvent>();
onFriendOnline: Subject<FriendOnlineEvent> = new Subject<FriendOnlineEvent>();
onFriendRights: Subject<FriendRightsEvent> = new Subject<FriendRightsEvent>();
onFriendRemoved: Subject<FriendRemovedEvent> = new Subject<FriendRemovedEvent>();
onPhysicsDataEvent: Subject<ObjectPhysicsDataEvent> = new Subject<ObjectPhysicsDataEvent>();
onParcelPropertiesEvent: Subject<ParcelPropertiesEvent> = new Subject<ParcelPropertiesEvent>();
onNewObjectEvent: Subject<NewObjectEvent> = new Subject<NewObjectEvent>();
onObjectUpdatedEvent: Subject<ObjectUpdatedEvent> = new Subject<ObjectUpdatedEvent>();
onObjectUpdatedTerseEvent: Subject<ObjectUpdatedEvent> = new Subject<ObjectUpdatedEvent>();
onObjectKilledEvent: Subject<ObjectKilledEvent> = new Subject<ObjectKilledEvent>();
onSelectedObjectEvent: Subject<SelectedObjectEvent> = new Subject<SelectedObjectEvent>();
onObjectResolvedEvent: Subject<ObjectResolvedEvent> = new Subject<ObjectResolvedEvent>();
onAvatarEnteredRegion: Subject<Avatar> = new Subject<Avatar>();
onRegionTimeDilation: Subject<number> = new Subject<number>();
onBulkUpdateInventoryEvent: Subject<BulkUpdateInventoryEvent> = new Subject<BulkUpdateInventoryEvent>();
onLandStatReplyEvent: Subject<LandStatsEvent> = new Subject<LandStatsEvent>();
onSimStats: Subject<SimStatsEvent> = new Subject<SimStatsEvent>();
onBalanceUpdated: Subject<BalanceUpdatedEvent> = new Subject<BalanceUpdatedEvent>();
public onNearbyChat: Subject<ChatEvent> = new Subject<ChatEvent>();
public onInstantMessage: Subject<InstantMessageEvent> = new Subject<InstantMessageEvent>();
public onGroupInvite: Subject<GroupInviteEvent> = new Subject<GroupInviteEvent>();
public onFriendRequest: Subject<FriendRequestEvent> = new Subject<FriendRequestEvent>();
public onInventoryOffered: Subject<InventoryOfferedEvent> = new Subject<InventoryOfferedEvent>();
public onLure: Subject<LureEvent> = new Subject<LureEvent>();
public onTeleportEvent: Subject<TeleportEvent> = new Subject<TeleportEvent>();
public onDisconnected: Subject<DisconnectEvent> = new Subject<DisconnectEvent>();
public onCircuitLatency: Subject<number> = new Subject<number>();
public onGroupChat: Subject<GroupChatEvent> = new Subject<GroupChatEvent>();
public onGroupChatClosed: Subject<GroupChatClosedEvent> = new Subject<GroupChatClosedEvent>();
public onGroupNotice: Subject<GroupNoticeEvent> = new Subject<GroupNoticeEvent>();
public onGroupChatSessionJoin: Subject<GroupChatSessionJoinEvent> = new Subject<GroupChatSessionJoinEvent>();
public onGroupChatAgentListUpdate: Subject<GroupChatSessionAgentListEvent> = new Subject<GroupChatSessionAgentListEvent>();
public onFriendResponse: Subject<FriendResponseEvent> = new Subject<FriendResponseEvent>();
public onInventoryResponse: Subject<InventoryResponseEvent> = new Subject<InventoryResponseEvent>();
public onScriptDialog: Subject<ScriptDialogEvent> = new Subject<ScriptDialogEvent>();
public onEventQueueStateChange: Subject<EventQueueStateChangeEvent> = new Subject<EventQueueStateChangeEvent>();
public onFriendOnline: Subject<FriendOnlineEvent> = new Subject<FriendOnlineEvent>();
public onFriendRights: Subject<FriendRightsEvent> = new Subject<FriendRightsEvent>();
public onFriendRemoved: Subject<FriendRemovedEvent> = new Subject<FriendRemovedEvent>();
public onPhysicsDataEvent: Subject<ObjectPhysicsDataEvent> = new Subject<ObjectPhysicsDataEvent>();
public onParcelPropertiesEvent: Subject<ParcelPropertiesEvent> = new Subject<ParcelPropertiesEvent>();
public onNewObjectEvent: Subject<NewObjectEvent> = new Subject<NewObjectEvent>();
public onObjectUpdatedEvent: Subject<ObjectUpdatedEvent> = new Subject<ObjectUpdatedEvent>();
public onObjectUpdatedTerseEvent: Subject<ObjectUpdatedEvent> = new Subject<ObjectUpdatedEvent>();
public onObjectKilledEvent: Subject<ObjectKilledEvent> = new Subject<ObjectKilledEvent>();
public onSelectedObjectEvent: Subject<SelectedObjectEvent> = new Subject<SelectedObjectEvent>();
public onObjectResolvedEvent: Subject<ObjectResolvedEvent> = new Subject<ObjectResolvedEvent>();
public onAvatarEnteredRegion: Subject<Avatar> = new Subject<Avatar>();
public onRegionTimeDilation: Subject<number> = new Subject<number>();
public onBulkUpdateInventoryEvent: Subject<BulkUpdateInventoryEvent> = new Subject<BulkUpdateInventoryEvent>();
public onLandStatReplyEvent: Subject<LandStatsEvent> = new Subject<LandStatsEvent>();
public onSimStats: Subject<SimStatsEvent> = new Subject<SimStatsEvent>();
public onBalanceUpdated: Subject<BalanceUpdatedEvent> = new Subject<BalanceUpdatedEvent>();
public onScriptRunningReply = new Subject<{
ItemID: UUID,
Mono: boolean,
ObjectID: UUID,
Running: boolean
}>();
public async waitForEvent<T>(subj: Subject<T>, messageFilter?: (message: T) => FilterResponse, timeout = 10000): Promise<T>
{
return new Promise<T>((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);
}
});
});
}
}

View File

@@ -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<CoalescedGameObject>
public static async fromXML(xml: string): Promise<CoalescedGameObject>
{
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<string>): Promise<XMLElement>
public async exportXMLElement(rootNode?: string, skipResolve?: Set<string>): Promise<XMLElement>
{
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<string>): Promise<string>
public async exportXML(rootNode?: string, skipResolve?: Set<string>): Promise<string>
{
return (await this.exportXMLElement(rootNode, skipResolve)).end({ pretty: true, allowEmpty: true });
}

View File

@@ -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);
}

View File

@@ -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)
{

View File

@@ -1,16 +1,31 @@
export class ConcurrentQueue
{
private concurrency: number;
private readonly concurrency: number;
private runningCount;
private jobQueue: { job: () => Promise<void>, resolve: (value: void | PromiseLike<void>) => void, reject: (reason?: unknown) => void }[];
private readonly jobQueue: { job: () => Promise<void>, resolve: (value: void | PromiseLike<void>) => 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<void>): Promise<void>
{
return new Promise<void>((resolve, reject) =>
{
if (this.runningCount < this.concurrency)
{
this.executeJob(job, resolve, reject);
}
else
{
this.jobQueue.push({ job, resolve, reject });
}
});
}
private executeJob(job: () => Promise<void>, resolve: (value: void | PromiseLike<void>) => 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<void>): Promise<void>
{
return new Promise<void>((resolve, reject) =>
{
if (this.runningCount < this.concurrency)
{
this.executeJob(job, resolve, reject);
}
else
{
this.jobQueue.push({ job, resolve, reject });
}
});
}
}

View File

@@ -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<Response<string>> = 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<Response<string>> = 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<void>
{
// 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<any>
public async capsPostXML(capability: string, data: any, attempt = 0): Promise<any>
{
return new Promise<any>((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('<llsd>'))
{
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('<llsd>') !== -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);
}
}
}
}

View File

@@ -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)
{
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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++);

View File

@@ -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<string, InventoryFolder>,
root?: UUID
} = {
skeleton: {}
skeleton: new Map<string, InventoryFolder>()
};
library: {
public library: {
owner?: UUID,
skeleton: { [key: string]: InventoryFolder },
skeleton: Map<string, InventoryFolder>,
root?: UUID
} = {
skeleton: {}
skeleton: new Map<string, InventoryFolder>()
};
itemsByID: { [key: string]: InventoryItem } = {};
public itemsByID = new Map<string, InventoryItem>();
// @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<InventoryItem | null>
public async fetchInventoryItem(item: UUID): Promise<InventoryItem | null>
{
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;
}

File diff suppressed because it is too large Load Diff

View File

@@ -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<InventoryItem>
public static async fromXML(xml: string): Promise<InventoryItem>
{
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<void>
public async update(): Promise<void>
{
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<InventoryItem>
public async moveToFolder(targetFolder: InventoryFolder): Promise<InventoryItem>
{
if (this.agent !== undefined)
{
@@ -729,7 +686,7 @@ export class InventoryItem
}
}
async delete(): Promise<void>
public async delete(): Promise<void>
{
if (this.agent !== undefined)
{
@@ -752,7 +709,8 @@ export class InventoryItem
}
}
async exportXML(): Promise<string>
// 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<void>
// noinspection JSUnusedGlobalSymbols
public async detachFromAvatar(): Promise<void>
{
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<GameObject>
// noinspection JSUnusedGlobalSymbols
public async attachToAvatar(attachPoint: AttachmentPoint, timeout = 10000): Promise<GameObject>
{
return new Promise<GameObject>((resolve, reject) =>
{
@@ -859,9 +824,10 @@ export class InventoryItem
});
}
rezGroupInWorld(position: Vector3): Promise<GameObject[]>
// noinspection JSUnusedGlobalSymbols
public async rezGroupInWorld(position: Vector3): Promise<GameObject[]>
{
return new Promise<GameObject[]>(async(resolve, reject) =>
return new Promise<GameObject[]>((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<void> =>
{
// 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<GameObject>
public async rezInWorld(position: Vector3, objectScale?: Vector3): Promise<GameObject>
{
return new Promise<GameObject>(async(resolve, reject) =>
return new Promise<GameObject>((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<void> =>
{
// 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<void>
// noinspection JSUnusedGlobalSymbols
public async rename(newName: string): Promise<void>
{
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<BulkUpdateInventoryEvent>
// noinspection JSUnusedGlobalSymbols
public async isScriptRunning(): Promise<boolean>
{
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<BulkUpdateInventoryEvent>(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<ScriptRunningReplyMessage>(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<InventoryItem>
@@ -1174,7 +1200,8 @@ export class InventoryItem
throw new Error('Unable to locate inventory item after copy');
}
async updateScript(scriptAsset: Buffer): Promise<UUID>
// noinspection JSUnusedGlobalSymbols
public async updateScript(scriptAsset: Buffer, running = true, target: 'mono' | 'lsl2' = 'mono'): Promise<UUID>
{
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<void>
{
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<BulkUpdateInventoryEvent>
{
if (!this.agent)
{
throw new Error('No active agent');
}
return Utils.waitOrTimeOut<BulkUpdateInventoryEvent>(this.agent.currentRegion.clientEvents.onBulkUpdateInventoryEvent, 10000, (event: BulkUpdateInventoryEvent) =>
{
for (const item of event.itemData)
{
if (item.callbackID === callbackID)
{
return FilterResponse.Finish;
}
}
return FilterResponse.NoMatch;
});
}
}

View File

@@ -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<InventoryType, RegisteredInventoryType>();
private static readonly invTypeByName = new Map<string, RegisteredInventoryType>();
private static readonly invTypeByHumanName = new Map<string, RegisteredInventoryType>();
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]);

129
lib/classes/LLAnimation.ts Normal file
View File

@@ -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();
}
}

View File

@@ -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));
}
}
}

View File

@@ -0,0 +1,8 @@
import type { Vector3 } from './Vector3';
export class LLAnimationJointKeyFrame
{
public time: number;
public transform: Vector3;
}

View File

@@ -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 !== '<? LLSD/Binary ?>\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)
{

View File

@@ -124,9 +124,9 @@ export interface LLGLTFMaterialDataPart
scale?: number[];
translaction?: number[]
} | {
matrix?: number[]
}
)) & LLGLTFExtensionsAndExtras[],
matrix?: number[]
}
)) & LLGLTFExtensionsAndExtras[],
scenes?: ({
name?: string;
nodes?: number[];

View File

@@ -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;
}
}

View File

@@ -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),

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -1,6 +1,6 @@
import { LLGestureStepType } from '../enums/LLGestureStepType';
import type { LLGestureStepType } from '../enums/LLGestureStepType';
export class LLGestureStep
{
stepType: LLGestureStepType
public stepType: LLGestureStepType
}

View File

@@ -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;
}

View File

@@ -3,9 +3,12 @@ import { Utils } from './Utils';
export class LLLindenText
{
version = 2;
public version = 2;
private lineObj: {
public body = '';
public embeddedItems = new Map<number, InventoryItem>();
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 !== '}')
{

View File

@@ -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);
});
}
});

View File

@@ -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<string, SettingsConfigLLSD>,
tracks?: {
key_keyframe: number,
frames?: LLSDMap<SettingsConfigLLSD>,
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<string, LLSettings>;
@@ -167,117 +171,127 @@ export class LLSettings
public wave1Direction?: Vector2;
public wave2Direction?: Vector2;
public constructor(data?: string | SettingsConfigLLSD)
public constructor(data?: string | LLSDMap<SettingsConfigLLSD>)
{
if (data)
if (data !== undefined)
{
let settings: SettingsConfigLLSD | null = null;
let settings: LLSDMap<SettingsConfigLLSD> & SettingsConfigLLSD | null = null;
if (typeof data === 'string')
{
const result = LLSDNotationParser.parse(data);
if (!(result instanceof LLSDMap))
if (data.startsWith('<?llsd/binary?>'))
{
return;
settings = LLSD.parseBinary(Buffer.from(data, 'utf-8')) as LLSDMap<SettingsConfigLLSD>;
}
else
{
settings = LLSD.parseNotation(data) as LLSDMap<SettingsConfigLLSD>;
}
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<string, LLSettings>();
for (const keyFrame of Object.keys(settings.frames))
{
const frame = settings.frames[keyFrame];
const frame = settings.frames[keyFrame] as LLSDMap<SettingsConfigLLSD>;
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<string, LLSettings>): LLSDMap<SettingsConfigLLSD> | undefined
{
if (fr === undefined)
{
return undefined;
}
const frames = new LLSDMap<SettingsConfigLLSD>();
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');
}
}

View File

@@ -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<number, number> = {};
public textures: Record<number, UUID> = {};
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';
}

View File

@@ -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')
{

View File

@@ -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)
{

View File

@@ -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);
}
}

View File

@@ -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;
}

463
lib/classes/Matrix3.ts Normal file
View File

@@ -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);
}
}

547
lib/classes/Matrix4.ts Normal file
View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -480,485 +480,485 @@ export * from './messages/RezRestoreToWorld';
export * from './messages/LinkInventoryItem';
import { Message } from '../enums/Message';
const messages: { [index: number]: string } = {};
messages[<number>Message.TestMessage] = 'TestMessageMessage';
messages[<number>Message.PacketAck] = 'PacketAckMessage';
messages[<number>Message.OpenCircuit] = 'OpenCircuitMessage';
messages[<number>Message.CloseCircuit] = 'CloseCircuitMessage';
messages[<number>Message.StartPingCheck] = 'StartPingCheckMessage';
messages[<number>Message.CompletePingCheck] = 'CompletePingCheckMessage';
messages[<number>Message.AddCircuitCode] = 'AddCircuitCodeMessage';
messages[<number>Message.UseCircuitCode] = 'UseCircuitCodeMessage';
messages[<number>Message.NeighborList] = 'NeighborListMessage';
messages[<number>Message.AvatarTextureUpdate] = 'AvatarTextureUpdateMessage';
messages[<number>Message.SimulatorMapUpdate] = 'SimulatorMapUpdateMessage';
messages[<number>Message.SimulatorSetMap] = 'SimulatorSetMapMessage';
messages[<number>Message.SubscribeLoad] = 'SubscribeLoadMessage';
messages[<number>Message.UnsubscribeLoad] = 'UnsubscribeLoadMessage';
messages[<number>Message.SimulatorReady] = 'SimulatorReadyMessage';
messages[<number>Message.TelehubInfo] = 'TelehubInfoMessage';
messages[<number>Message.SimulatorPresentAtLocation] = 'SimulatorPresentAtLocationMessage';
messages[<number>Message.SimulatorLoad] = 'SimulatorLoadMessage';
messages[<number>Message.SimulatorShutdownRequest] = 'SimulatorShutdownRequestMessage';
messages[<number>Message.RegionPresenceRequestByRegionID] = 'RegionPresenceRequestByRegionIDMessage';
messages[<number>Message.RegionPresenceRequestByHandle] = 'RegionPresenceRequestByHandleMessage';
messages[<number>Message.RegionPresenceResponse] = 'RegionPresenceResponseMessage';
messages[<number>Message.UpdateSimulator] = 'UpdateSimulatorMessage';
messages[<number>Message.LogDwellTime] = 'LogDwellTimeMessage';
messages[<number>Message.FeatureDisabled] = 'FeatureDisabledMessage';
messages[<number>Message.LogFailedMoneyTransaction] = 'LogFailedMoneyTransactionMessage';
messages[<number>Message.UserReportInternal] = 'UserReportInternalMessage';
messages[<number>Message.SetSimStatusInDatabase] = 'SetSimStatusInDatabaseMessage';
messages[<number>Message.SetSimPresenceInDatabase] = 'SetSimPresenceInDatabaseMessage';
messages[<number>Message.EconomyDataRequest] = 'EconomyDataRequestMessage';
messages[<number>Message.EconomyData] = 'EconomyDataMessage';
messages[<number>Message.AvatarPickerRequest] = 'AvatarPickerRequestMessage';
messages[<number>Message.AvatarPickerRequestBackend] = 'AvatarPickerRequestBackendMessage';
messages[<number>Message.AvatarPickerReply] = 'AvatarPickerReplyMessage';
messages[<number>Message.PlacesQuery] = 'PlacesQueryMessage';
messages[<number>Message.PlacesReply] = 'PlacesReplyMessage';
messages[<number>Message.DirFindQuery] = 'DirFindQueryMessage';
messages[<number>Message.DirFindQueryBackend] = 'DirFindQueryBackendMessage';
messages[<number>Message.DirPlacesQuery] = 'DirPlacesQueryMessage';
messages[<number>Message.DirPlacesQueryBackend] = 'DirPlacesQueryBackendMessage';
messages[<number>Message.DirPlacesReply] = 'DirPlacesReplyMessage';
messages[<number>Message.DirPeopleReply] = 'DirPeopleReplyMessage';
messages[<number>Message.DirEventsReply] = 'DirEventsReplyMessage';
messages[<number>Message.DirGroupsReply] = 'DirGroupsReplyMessage';
messages[<number>Message.DirClassifiedQuery] = 'DirClassifiedQueryMessage';
messages[<number>Message.DirClassifiedQueryBackend] = 'DirClassifiedQueryBackendMessage';
messages[<number>Message.DirClassifiedReply] = 'DirClassifiedReplyMessage';
messages[<number>Message.AvatarClassifiedReply] = 'AvatarClassifiedReplyMessage';
messages[<number>Message.ClassifiedInfoRequest] = 'ClassifiedInfoRequestMessage';
messages[<number>Message.ClassifiedInfoReply] = 'ClassifiedInfoReplyMessage';
messages[<number>Message.ClassifiedInfoUpdate] = 'ClassifiedInfoUpdateMessage';
messages[<number>Message.ClassifiedDelete] = 'ClassifiedDeleteMessage';
messages[<number>Message.ClassifiedGodDelete] = 'ClassifiedGodDeleteMessage';
messages[<number>Message.DirLandQuery] = 'DirLandQueryMessage';
messages[<number>Message.DirLandQueryBackend] = 'DirLandQueryBackendMessage';
messages[<number>Message.DirLandReply] = 'DirLandReplyMessage';
messages[<number>Message.DirPopularQuery] = 'DirPopularQueryMessage';
messages[<number>Message.DirPopularQueryBackend] = 'DirPopularQueryBackendMessage';
messages[<number>Message.DirPopularReply] = 'DirPopularReplyMessage';
messages[<number>Message.ParcelInfoRequest] = 'ParcelInfoRequestMessage';
messages[<number>Message.ParcelInfoReply] = 'ParcelInfoReplyMessage';
messages[<number>Message.ParcelObjectOwnersRequest] = 'ParcelObjectOwnersRequestMessage';
messages[<number>Message.ParcelObjectOwnersReply] = 'ParcelObjectOwnersReplyMessage';
messages[<number>Message.GroupNoticesListRequest] = 'GroupNoticesListRequestMessage';
messages[<number>Message.GroupNoticesListReply] = 'GroupNoticesListReplyMessage';
messages[<number>Message.GroupNoticeRequest] = 'GroupNoticeRequestMessage';
messages[<number>Message.GroupNoticeAdd] = 'GroupNoticeAddMessage';
messages[<number>Message.TeleportRequest] = 'TeleportRequestMessage';
messages[<number>Message.TeleportLocationRequest] = 'TeleportLocationRequestMessage';
messages[<number>Message.TeleportLocal] = 'TeleportLocalMessage';
messages[<number>Message.TeleportLandmarkRequest] = 'TeleportLandmarkRequestMessage';
messages[<number>Message.TeleportProgress] = 'TeleportProgressMessage';
messages[<number>Message.DataHomeLocationRequest] = 'DataHomeLocationRequestMessage';
messages[<number>Message.DataHomeLocationReply] = 'DataHomeLocationReplyMessage';
messages[<number>Message.TeleportFinish] = 'TeleportFinishMessage';
messages[<number>Message.StartLure] = 'StartLureMessage';
messages[<number>Message.TeleportLureRequest] = 'TeleportLureRequestMessage';
messages[<number>Message.TeleportCancel] = 'TeleportCancelMessage';
messages[<number>Message.TeleportStart] = 'TeleportStartMessage';
messages[<number>Message.TeleportFailed] = 'TeleportFailedMessage';
messages[<number>Message.Undo] = 'UndoMessage';
messages[<number>Message.Redo] = 'RedoMessage';
messages[<number>Message.UndoLand] = 'UndoLandMessage';
messages[<number>Message.AgentPause] = 'AgentPauseMessage';
messages[<number>Message.AgentResume] = 'AgentResumeMessage';
messages[<number>Message.AgentUpdate] = 'AgentUpdateMessage';
messages[<number>Message.ChatFromViewer] = 'ChatFromViewerMessage';
messages[<number>Message.AgentThrottle] = 'AgentThrottleMessage';
messages[<number>Message.AgentFOV] = 'AgentFOVMessage';
messages[<number>Message.AgentHeightWidth] = 'AgentHeightWidthMessage';
messages[<number>Message.AgentSetAppearance] = 'AgentSetAppearanceMessage';
messages[<number>Message.AgentAnimation] = 'AgentAnimationMessage';
messages[<number>Message.AgentRequestSit] = 'AgentRequestSitMessage';
messages[<number>Message.AgentSit] = 'AgentSitMessage';
messages[<number>Message.AgentQuitCopy] = 'AgentQuitCopyMessage';
messages[<number>Message.RequestImage] = 'RequestImageMessage';
messages[<number>Message.ImageNotInDatabase] = 'ImageNotInDatabaseMessage';
messages[<number>Message.RebakeAvatarTextures] = 'RebakeAvatarTexturesMessage';
messages[<number>Message.SetAlwaysRun] = 'SetAlwaysRunMessage';
messages[<number>Message.ObjectAdd] = 'ObjectAddMessage';
messages[<number>Message.ObjectDelete] = 'ObjectDeleteMessage';
messages[<number>Message.ObjectDuplicate] = 'ObjectDuplicateMessage';
messages[<number>Message.ObjectDuplicateOnRay] = 'ObjectDuplicateOnRayMessage';
messages[<number>Message.MultipleObjectUpdate] = 'MultipleObjectUpdateMessage';
messages[<number>Message.RequestMultipleObjects] = 'RequestMultipleObjectsMessage';
messages[<number>Message.ObjectPosition] = 'ObjectPositionMessage';
messages[<number>Message.ObjectScale] = 'ObjectScaleMessage';
messages[<number>Message.ObjectRotation] = 'ObjectRotationMessage';
messages[<number>Message.ObjectFlagUpdate] = 'ObjectFlagUpdateMessage';
messages[<number>Message.ObjectClickAction] = 'ObjectClickActionMessage';
messages[<number>Message.ObjectImage] = 'ObjectImageMessage';
messages[<number>Message.ObjectMaterial] = 'ObjectMaterialMessage';
messages[<number>Message.ObjectShape] = 'ObjectShapeMessage';
messages[<number>Message.ObjectExtraParams] = 'ObjectExtraParamsMessage';
messages[<number>Message.ObjectOwner] = 'ObjectOwnerMessage';
messages[<number>Message.ObjectGroup] = 'ObjectGroupMessage';
messages[<number>Message.ObjectBuy] = 'ObjectBuyMessage';
messages[<number>Message.BuyObjectInventory] = 'BuyObjectInventoryMessage';
messages[<number>Message.DerezContainer] = 'DerezContainerMessage';
messages[<number>Message.ObjectPermissions] = 'ObjectPermissionsMessage';
messages[<number>Message.ObjectSaleInfo] = 'ObjectSaleInfoMessage';
messages[<number>Message.ObjectName] = 'ObjectNameMessage';
messages[<number>Message.ObjectDescription] = 'ObjectDescriptionMessage';
messages[<number>Message.ObjectCategory] = 'ObjectCategoryMessage';
messages[<number>Message.ObjectSelect] = 'ObjectSelectMessage';
messages[<number>Message.ObjectDeselect] = 'ObjectDeselectMessage';
messages[<number>Message.ObjectAttach] = 'ObjectAttachMessage';
messages[<number>Message.ObjectDetach] = 'ObjectDetachMessage';
messages[<number>Message.ObjectDrop] = 'ObjectDropMessage';
messages[<number>Message.ObjectLink] = 'ObjectLinkMessage';
messages[<number>Message.ObjectDelink] = 'ObjectDelinkMessage';
messages[<number>Message.ObjectGrab] = 'ObjectGrabMessage';
messages[<number>Message.ObjectGrabUpdate] = 'ObjectGrabUpdateMessage';
messages[<number>Message.ObjectDeGrab] = 'ObjectDeGrabMessage';
messages[<number>Message.ObjectSpinStart] = 'ObjectSpinStartMessage';
messages[<number>Message.ObjectSpinUpdate] = 'ObjectSpinUpdateMessage';
messages[<number>Message.ObjectSpinStop] = 'ObjectSpinStopMessage';
messages[<number>Message.ObjectExportSelected] = 'ObjectExportSelectedMessage';
messages[<number>Message.ModifyLand] = 'ModifyLandMessage';
messages[<number>Message.VelocityInterpolateOn] = 'VelocityInterpolateOnMessage';
messages[<number>Message.VelocityInterpolateOff] = 'VelocityInterpolateOffMessage';
messages[<number>Message.StateSave] = 'StateSaveMessage';
messages[<number>Message.ReportAutosaveCrash] = 'ReportAutosaveCrashMessage';
messages[<number>Message.SimWideDeletes] = 'SimWideDeletesMessage';
messages[<number>Message.RequestObjectPropertiesFamily] = 'RequestObjectPropertiesFamilyMessage';
messages[<number>Message.TrackAgent] = 'TrackAgentMessage';
messages[<number>Message.ViewerStats] = 'ViewerStatsMessage';
messages[<number>Message.ScriptAnswerYes] = 'ScriptAnswerYesMessage';
messages[<number>Message.UserReport] = 'UserReportMessage';
messages[<number>Message.AlertMessage] = 'AlertMessageMessage';
messages[<number>Message.AgentAlertMessage] = 'AgentAlertMessageMessage';
messages[<number>Message.MeanCollisionAlert] = 'MeanCollisionAlertMessage';
messages[<number>Message.ViewerFrozenMessage] = 'ViewerFrozenMessageMessage';
messages[<number>Message.HealthMessage] = 'HealthMessageMessage';
messages[<number>Message.ChatFromSimulator] = 'ChatFromSimulatorMessage';
messages[<number>Message.SimStats] = 'SimStatsMessage';
messages[<number>Message.RequestRegionInfo] = 'RequestRegionInfoMessage';
messages[<number>Message.RegionInfo] = 'RegionInfoMessage';
messages[<number>Message.GodUpdateRegionInfo] = 'GodUpdateRegionInfoMessage';
messages[<number>Message.NearestLandingRegionRequest] = 'NearestLandingRegionRequestMessage';
messages[<number>Message.NearestLandingRegionReply] = 'NearestLandingRegionReplyMessage';
messages[<number>Message.NearestLandingRegionUpdated] = 'NearestLandingRegionUpdatedMessage';
messages[<number>Message.TeleportLandingStatusChanged] = 'TeleportLandingStatusChangedMessage';
messages[<number>Message.RegionHandshake] = 'RegionHandshakeMessage';
messages[<number>Message.RegionHandshakeReply] = 'RegionHandshakeReplyMessage';
messages[<number>Message.CoarseLocationUpdate] = 'CoarseLocationUpdateMessage';
messages[<number>Message.ImageData] = 'ImageDataMessage';
messages[<number>Message.ImagePacket] = 'ImagePacketMessage';
messages[<number>Message.LayerData] = 'LayerDataMessage';
messages[<number>Message.ObjectUpdate] = 'ObjectUpdateMessage';
messages[<number>Message.ObjectUpdateCompressed] = 'ObjectUpdateCompressedMessage';
messages[<number>Message.ObjectUpdateCached] = 'ObjectUpdateCachedMessage';
messages[<number>Message.ImprovedTerseObjectUpdate] = 'ImprovedTerseObjectUpdateMessage';
messages[<number>Message.KillObject] = 'KillObjectMessage';
messages[<number>Message.CrossedRegion] = 'CrossedRegionMessage';
messages[<number>Message.SimulatorViewerTimeMessage] = 'SimulatorViewerTimeMessageMessage';
messages[<number>Message.EnableSimulator] = 'EnableSimulatorMessage';
messages[<number>Message.DisableSimulator] = 'DisableSimulatorMessage';
messages[<number>Message.ConfirmEnableSimulator] = 'ConfirmEnableSimulatorMessage';
messages[<number>Message.TransferRequest] = 'TransferRequestMessage';
messages[<number>Message.TransferInfo] = 'TransferInfoMessage';
messages[<number>Message.TransferPacket] = 'TransferPacketMessage';
messages[<number>Message.TransferAbort] = 'TransferAbortMessage';
messages[<number>Message.RequestXfer] = 'RequestXferMessage';
messages[<number>Message.SendXferPacket] = 'SendXferPacketMessage';
messages[<number>Message.ConfirmXferPacket] = 'ConfirmXferPacketMessage';
messages[<number>Message.AbortXfer] = 'AbortXferMessage';
messages[<number>Message.AvatarAnimation] = 'AvatarAnimationMessage';
messages[<number>Message.AvatarAppearance] = 'AvatarAppearanceMessage';
messages[<number>Message.AvatarSitResponse] = 'AvatarSitResponseMessage';
messages[<number>Message.SetFollowCamProperties] = 'SetFollowCamPropertiesMessage';
messages[<number>Message.ClearFollowCamProperties] = 'ClearFollowCamPropertiesMessage';
messages[<number>Message.CameraConstraint] = 'CameraConstraintMessage';
messages[<number>Message.ObjectProperties] = 'ObjectPropertiesMessage';
messages[<number>Message.ObjectPropertiesFamily] = 'ObjectPropertiesFamilyMessage';
messages[<number>Message.RequestPayPrice] = 'RequestPayPriceMessage';
messages[<number>Message.PayPriceReply] = 'PayPriceReplyMessage';
messages[<number>Message.KickUser] = 'KickUserMessage';
messages[<number>Message.KickUserAck] = 'KickUserAckMessage';
messages[<number>Message.GodKickUser] = 'GodKickUserMessage';
messages[<number>Message.SystemKickUser] = 'SystemKickUserMessage';
messages[<number>Message.EjectUser] = 'EjectUserMessage';
messages[<number>Message.FreezeUser] = 'FreezeUserMessage';
messages[<number>Message.AvatarPropertiesRequest] = 'AvatarPropertiesRequestMessage';
messages[<number>Message.AvatarPropertiesRequestBackend] = 'AvatarPropertiesRequestBackendMessage';
messages[<number>Message.AvatarPropertiesReply] = 'AvatarPropertiesReplyMessage';
messages[<number>Message.AvatarInterestsReply] = 'AvatarInterestsReplyMessage';
messages[<number>Message.AvatarGroupsReply] = 'AvatarGroupsReplyMessage';
messages[<number>Message.AvatarPropertiesUpdate] = 'AvatarPropertiesUpdateMessage';
messages[<number>Message.AvatarInterestsUpdate] = 'AvatarInterestsUpdateMessage';
messages[<number>Message.AvatarNotesReply] = 'AvatarNotesReplyMessage';
messages[<number>Message.AvatarNotesUpdate] = 'AvatarNotesUpdateMessage';
messages[<number>Message.AvatarPicksReply] = 'AvatarPicksReplyMessage';
messages[<number>Message.EventInfoRequest] = 'EventInfoRequestMessage';
messages[<number>Message.EventInfoReply] = 'EventInfoReplyMessage';
messages[<number>Message.EventNotificationAddRequest] = 'EventNotificationAddRequestMessage';
messages[<number>Message.EventNotificationRemoveRequest] = 'EventNotificationRemoveRequestMessage';
messages[<number>Message.EventGodDelete] = 'EventGodDeleteMessage';
messages[<number>Message.PickInfoReply] = 'PickInfoReplyMessage';
messages[<number>Message.PickInfoUpdate] = 'PickInfoUpdateMessage';
messages[<number>Message.PickDelete] = 'PickDeleteMessage';
messages[<number>Message.PickGodDelete] = 'PickGodDeleteMessage';
messages[<number>Message.ScriptQuestion] = 'ScriptQuestionMessage';
messages[<number>Message.ScriptControlChange] = 'ScriptControlChangeMessage';
messages[<number>Message.ScriptDialog] = 'ScriptDialogMessage';
messages[<number>Message.ScriptDialogReply] = 'ScriptDialogReplyMessage';
messages[<number>Message.ForceScriptControlRelease] = 'ForceScriptControlReleaseMessage';
messages[<number>Message.RevokePermissions] = 'RevokePermissionsMessage';
messages[<number>Message.LoadURL] = 'LoadURLMessage';
messages[<number>Message.ScriptTeleportRequest] = 'ScriptTeleportRequestMessage';
messages[<number>Message.ParcelOverlay] = 'ParcelOverlayMessage';
messages[<number>Message.ParcelPropertiesRequest] = 'ParcelPropertiesRequestMessage';
messages[<number>Message.ParcelPropertiesRequestByID] = 'ParcelPropertiesRequestByIDMessage';
messages[<number>Message.ParcelProperties] = 'ParcelPropertiesMessage';
messages[<number>Message.ParcelPropertiesUpdate] = 'ParcelPropertiesUpdateMessage';
messages[<number>Message.ParcelReturnObjects] = 'ParcelReturnObjectsMessage';
messages[<number>Message.ParcelSetOtherCleanTime] = 'ParcelSetOtherCleanTimeMessage';
messages[<number>Message.ParcelDisableObjects] = 'ParcelDisableObjectsMessage';
messages[<number>Message.ParcelSelectObjects] = 'ParcelSelectObjectsMessage';
messages[<number>Message.EstateCovenantRequest] = 'EstateCovenantRequestMessage';
messages[<number>Message.EstateCovenantReply] = 'EstateCovenantReplyMessage';
messages[<number>Message.ForceObjectSelect] = 'ForceObjectSelectMessage';
messages[<number>Message.ParcelBuyPass] = 'ParcelBuyPassMessage';
messages[<number>Message.ParcelDeedToGroup] = 'ParcelDeedToGroupMessage';
messages[<number>Message.ParcelReclaim] = 'ParcelReclaimMessage';
messages[<number>Message.ParcelClaim] = 'ParcelClaimMessage';
messages[<number>Message.ParcelJoin] = 'ParcelJoinMessage';
messages[<number>Message.ParcelDivide] = 'ParcelDivideMessage';
messages[<number>Message.ParcelRelease] = 'ParcelReleaseMessage';
messages[<number>Message.ParcelBuy] = 'ParcelBuyMessage';
messages[<number>Message.ParcelGodForceOwner] = 'ParcelGodForceOwnerMessage';
messages[<number>Message.ParcelAccessListRequest] = 'ParcelAccessListRequestMessage';
messages[<number>Message.ParcelAccessListReply] = 'ParcelAccessListReplyMessage';
messages[<number>Message.ParcelAccessListUpdate] = 'ParcelAccessListUpdateMessage';
messages[<number>Message.ParcelDwellRequest] = 'ParcelDwellRequestMessage';
messages[<number>Message.ParcelDwellReply] = 'ParcelDwellReplyMessage';
messages[<number>Message.RequestParcelTransfer] = 'RequestParcelTransferMessage';
messages[<number>Message.UpdateParcel] = 'UpdateParcelMessage';
messages[<number>Message.RemoveParcel] = 'RemoveParcelMessage';
messages[<number>Message.MergeParcel] = 'MergeParcelMessage';
messages[<number>Message.LogParcelChanges] = 'LogParcelChangesMessage';
messages[<number>Message.CheckParcelSales] = 'CheckParcelSalesMessage';
messages[<number>Message.ParcelSales] = 'ParcelSalesMessage';
messages[<number>Message.ParcelGodMarkAsContent] = 'ParcelGodMarkAsContentMessage';
messages[<number>Message.ViewerStartAuction] = 'ViewerStartAuctionMessage';
messages[<number>Message.StartAuction] = 'StartAuctionMessage';
messages[<number>Message.ConfirmAuctionStart] = 'ConfirmAuctionStartMessage';
messages[<number>Message.CompleteAuction] = 'CompleteAuctionMessage';
messages[<number>Message.CancelAuction] = 'CancelAuctionMessage';
messages[<number>Message.CheckParcelAuctions] = 'CheckParcelAuctionsMessage';
messages[<number>Message.ParcelAuctions] = 'ParcelAuctionsMessage';
messages[<number>Message.UUIDNameRequest] = 'UUIDNameRequestMessage';
messages[<number>Message.UUIDNameReply] = 'UUIDNameReplyMessage';
messages[<number>Message.UUIDGroupNameRequest] = 'UUIDGroupNameRequestMessage';
messages[<number>Message.UUIDGroupNameReply] = 'UUIDGroupNameReplyMessage';
messages[<number>Message.ChatPass] = 'ChatPassMessage';
messages[<number>Message.EdgeDataPacket] = 'EdgeDataPacketMessage';
messages[<number>Message.SimStatus] = 'SimStatusMessage';
messages[<number>Message.ChildAgentUpdate] = 'ChildAgentUpdateMessage';
messages[<number>Message.ChildAgentAlive] = 'ChildAgentAliveMessage';
messages[<number>Message.ChildAgentPositionUpdate] = 'ChildAgentPositionUpdateMessage';
messages[<number>Message.ChildAgentDying] = 'ChildAgentDyingMessage';
messages[<number>Message.ChildAgentUnknown] = 'ChildAgentUnknownMessage';
messages[<number>Message.AtomicPassObject] = 'AtomicPassObjectMessage';
messages[<number>Message.KillChildAgents] = 'KillChildAgentsMessage';
messages[<number>Message.GetScriptRunning] = 'GetScriptRunningMessage';
messages[<number>Message.ScriptRunningReply] = 'ScriptRunningReplyMessage';
messages[<number>Message.SetScriptRunning] = 'SetScriptRunningMessage';
messages[<number>Message.ScriptReset] = 'ScriptResetMessage';
messages[<number>Message.ScriptSensorRequest] = 'ScriptSensorRequestMessage';
messages[<number>Message.ScriptSensorReply] = 'ScriptSensorReplyMessage';
messages[<number>Message.CompleteAgentMovement] = 'CompleteAgentMovementMessage';
messages[<number>Message.AgentMovementComplete] = 'AgentMovementCompleteMessage';
messages[<number>Message.DataServerLogout] = 'DataServerLogoutMessage';
messages[<number>Message.LogoutRequest] = 'LogoutRequestMessage';
messages[<number>Message.LogoutReply] = 'LogoutReplyMessage';
messages[<number>Message.ImprovedInstantMessage] = 'ImprovedInstantMessageMessage';
messages[<number>Message.RetrieveInstantMessages] = 'RetrieveInstantMessagesMessage';
messages[<number>Message.FindAgent] = 'FindAgentMessage';
messages[<number>Message.RequestGodlikePowers] = 'RequestGodlikePowersMessage';
messages[<number>Message.GrantGodlikePowers] = 'GrantGodlikePowersMessage';
messages[<number>Message.GodlikeMessage] = 'GodlikeMessageMessage';
messages[<number>Message.EstateOwnerMessage] = 'EstateOwnerMessageMessage';
messages[<number>Message.GenericMessage] = 'GenericMessageMessage';
messages[<number>Message.GenericStreamingMessage] = 'GenericStreamingMessageMessage';
messages[<number>Message.LargeGenericMessage] = 'LargeGenericMessageMessage';
messages[<number>Message.MuteListRequest] = 'MuteListRequestMessage';
messages[<number>Message.UpdateMuteListEntry] = 'UpdateMuteListEntryMessage';
messages[<number>Message.RemoveMuteListEntry] = 'RemoveMuteListEntryMessage';
messages[<number>Message.CopyInventoryFromNotecard] = 'CopyInventoryFromNotecardMessage';
messages[<number>Message.UpdateInventoryItem] = 'UpdateInventoryItemMessage';
messages[<number>Message.UpdateCreateInventoryItem] = 'UpdateCreateInventoryItemMessage';
messages[<number>Message.MoveInventoryItem] = 'MoveInventoryItemMessage';
messages[<number>Message.CopyInventoryItem] = 'CopyInventoryItemMessage';
messages[<number>Message.RemoveInventoryItem] = 'RemoveInventoryItemMessage';
messages[<number>Message.ChangeInventoryItemFlags] = 'ChangeInventoryItemFlagsMessage';
messages[<number>Message.SaveAssetIntoInventory] = 'SaveAssetIntoInventoryMessage';
messages[<number>Message.CreateInventoryFolder] = 'CreateInventoryFolderMessage';
messages[<number>Message.UpdateInventoryFolder] = 'UpdateInventoryFolderMessage';
messages[<number>Message.MoveInventoryFolder] = 'MoveInventoryFolderMessage';
messages[<number>Message.RemoveInventoryFolder] = 'RemoveInventoryFolderMessage';
messages[<number>Message.FetchInventoryDescendents] = 'FetchInventoryDescendentsMessage';
messages[<number>Message.InventoryDescendents] = 'InventoryDescendentsMessage';
messages[<number>Message.FetchInventory] = 'FetchInventoryMessage';
messages[<number>Message.FetchInventoryReply] = 'FetchInventoryReplyMessage';
messages[<number>Message.BulkUpdateInventory] = 'BulkUpdateInventoryMessage';
messages[<number>Message.RequestInventoryAsset] = 'RequestInventoryAssetMessage';
messages[<number>Message.InventoryAssetResponse] = 'InventoryAssetResponseMessage';
messages[<number>Message.RemoveInventoryObjects] = 'RemoveInventoryObjectsMessage';
messages[<number>Message.PurgeInventoryDescendents] = 'PurgeInventoryDescendentsMessage';
messages[<number>Message.UpdateTaskInventory] = 'UpdateTaskInventoryMessage';
messages[<number>Message.RemoveTaskInventory] = 'RemoveTaskInventoryMessage';
messages[<number>Message.MoveTaskInventory] = 'MoveTaskInventoryMessage';
messages[<number>Message.RequestTaskInventory] = 'RequestTaskInventoryMessage';
messages[<number>Message.ReplyTaskInventory] = 'ReplyTaskInventoryMessage';
messages[<number>Message.DeRezObject] = 'DeRezObjectMessage';
messages[<number>Message.DeRezAck] = 'DeRezAckMessage';
messages[<number>Message.RezObject] = 'RezObjectMessage';
messages[<number>Message.RezObjectFromNotecard] = 'RezObjectFromNotecardMessage';
messages[<number>Message.TransferInventory] = 'TransferInventoryMessage';
messages[<number>Message.TransferInventoryAck] = 'TransferInventoryAckMessage';
messages[<number>Message.AcceptFriendship] = 'AcceptFriendshipMessage';
messages[<number>Message.DeclineFriendship] = 'DeclineFriendshipMessage';
messages[<number>Message.FormFriendship] = 'FormFriendshipMessage';
messages[<number>Message.TerminateFriendship] = 'TerminateFriendshipMessage';
messages[<number>Message.OfferCallingCard] = 'OfferCallingCardMessage';
messages[<number>Message.AcceptCallingCard] = 'AcceptCallingCardMessage';
messages[<number>Message.DeclineCallingCard] = 'DeclineCallingCardMessage';
messages[<number>Message.RezScript] = 'RezScriptMessage';
messages[<number>Message.CreateInventoryItem] = 'CreateInventoryItemMessage';
messages[<number>Message.CreateLandmarkForEvent] = 'CreateLandmarkForEventMessage';
messages[<number>Message.EventLocationRequest] = 'EventLocationRequestMessage';
messages[<number>Message.EventLocationReply] = 'EventLocationReplyMessage';
messages[<number>Message.RegionHandleRequest] = 'RegionHandleRequestMessage';
messages[<number>Message.RegionIDAndHandleReply] = 'RegionIDAndHandleReplyMessage';
messages[<number>Message.MoneyTransferRequest] = 'MoneyTransferRequestMessage';
messages[<number>Message.MoneyTransferBackend] = 'MoneyTransferBackendMessage';
messages[<number>Message.MoneyBalanceRequest] = 'MoneyBalanceRequestMessage';
messages[<number>Message.MoneyBalanceReply] = 'MoneyBalanceReplyMessage';
messages[<number>Message.RoutedMoneyBalanceReply] = 'RoutedMoneyBalanceReplyMessage';
messages[<number>Message.ActivateGestures] = 'ActivateGesturesMessage';
messages[<number>Message.DeactivateGestures] = 'DeactivateGesturesMessage';
messages[<number>Message.MuteListUpdate] = 'MuteListUpdateMessage';
messages[<number>Message.UseCachedMuteList] = 'UseCachedMuteListMessage';
messages[<number>Message.GrantUserRights] = 'GrantUserRightsMessage';
messages[<number>Message.ChangeUserRights] = 'ChangeUserRightsMessage';
messages[<number>Message.OnlineNotification] = 'OnlineNotificationMessage';
messages[<number>Message.OfflineNotification] = 'OfflineNotificationMessage';
messages[<number>Message.SetStartLocationRequest] = 'SetStartLocationRequestMessage';
messages[<number>Message.SetStartLocation] = 'SetStartLocationMessage';
messages[<number>Message.NetTest] = 'NetTestMessage';
messages[<number>Message.SetCPURatio] = 'SetCPURatioMessage';
messages[<number>Message.SimCrashed] = 'SimCrashedMessage';
messages[<number>Message.NameValuePair] = 'NameValuePairMessage';
messages[<number>Message.RemoveNameValuePair] = 'RemoveNameValuePairMessage';
messages[<number>Message.UpdateAttachment] = 'UpdateAttachmentMessage';
messages[<number>Message.RemoveAttachment] = 'RemoveAttachmentMessage';
messages[<number>Message.SoundTrigger] = 'SoundTriggerMessage';
messages[<number>Message.AttachedSound] = 'AttachedSoundMessage';
messages[<number>Message.AttachedSoundGainChange] = 'AttachedSoundGainChangeMessage';
messages[<number>Message.PreloadSound] = 'PreloadSoundMessage';
messages[<number>Message.ObjectAnimation] = 'ObjectAnimationMessage';
messages[<number>Message.AssetUploadRequest] = 'AssetUploadRequestMessage';
messages[<number>Message.AssetUploadComplete] = 'AssetUploadCompleteMessage';
messages[<number>Message.EmailMessageRequest] = 'EmailMessageRequestMessage';
messages[<number>Message.EmailMessageReply] = 'EmailMessageReplyMessage';
messages[<number>Message.InternalScriptMail] = 'InternalScriptMailMessage';
messages[<number>Message.ScriptDataRequest] = 'ScriptDataRequestMessage';
messages[<number>Message.ScriptDataReply] = 'ScriptDataReplyMessage';
messages[<number>Message.CreateGroupRequest] = 'CreateGroupRequestMessage';
messages[<number>Message.CreateGroupReply] = 'CreateGroupReplyMessage';
messages[<number>Message.UpdateGroupInfo] = 'UpdateGroupInfoMessage';
messages[<number>Message.GroupRoleChanges] = 'GroupRoleChangesMessage';
messages[<number>Message.JoinGroupRequest] = 'JoinGroupRequestMessage';
messages[<number>Message.JoinGroupReply] = 'JoinGroupReplyMessage';
messages[<number>Message.EjectGroupMemberRequest] = 'EjectGroupMemberRequestMessage';
messages[<number>Message.EjectGroupMemberReply] = 'EjectGroupMemberReplyMessage';
messages[<number>Message.LeaveGroupRequest] = 'LeaveGroupRequestMessage';
messages[<number>Message.LeaveGroupReply] = 'LeaveGroupReplyMessage';
messages[<number>Message.InviteGroupRequest] = 'InviteGroupRequestMessage';
messages[<number>Message.InviteGroupResponse] = 'InviteGroupResponseMessage';
messages[<number>Message.GroupProfileRequest] = 'GroupProfileRequestMessage';
messages[<number>Message.GroupProfileReply] = 'GroupProfileReplyMessage';
messages[<number>Message.GroupAccountSummaryRequest] = 'GroupAccountSummaryRequestMessage';
messages[<number>Message.GroupAccountSummaryReply] = 'GroupAccountSummaryReplyMessage';
messages[<number>Message.GroupAccountDetailsRequest] = 'GroupAccountDetailsRequestMessage';
messages[<number>Message.GroupAccountDetailsReply] = 'GroupAccountDetailsReplyMessage';
messages[<number>Message.GroupAccountTransactionsRequest] = 'GroupAccountTransactionsRequestMessage';
messages[<number>Message.GroupAccountTransactionsReply] = 'GroupAccountTransactionsReplyMessage';
messages[<number>Message.GroupActiveProposalsRequest] = 'GroupActiveProposalsRequestMessage';
messages[<number>Message.GroupActiveProposalItemReply] = 'GroupActiveProposalItemReplyMessage';
messages[<number>Message.GroupVoteHistoryRequest] = 'GroupVoteHistoryRequestMessage';
messages[<number>Message.GroupVoteHistoryItemReply] = 'GroupVoteHistoryItemReplyMessage';
messages[<number>Message.StartGroupProposal] = 'StartGroupProposalMessage';
messages[<number>Message.GroupProposalBallot] = 'GroupProposalBallotMessage';
messages[<number>Message.TallyVotes] = 'TallyVotesMessage';
messages[<number>Message.GroupMembersRequest] = 'GroupMembersRequestMessage';
messages[<number>Message.GroupMembersReply] = 'GroupMembersReplyMessage';
messages[<number>Message.ActivateGroup] = 'ActivateGroupMessage';
messages[<number>Message.SetGroupContribution] = 'SetGroupContributionMessage';
messages[<number>Message.SetGroupAcceptNotices] = 'SetGroupAcceptNoticesMessage';
messages[<number>Message.GroupRoleDataRequest] = 'GroupRoleDataRequestMessage';
messages[<number>Message.GroupRoleDataReply] = 'GroupRoleDataReplyMessage';
messages[<number>Message.GroupRoleMembersRequest] = 'GroupRoleMembersRequestMessage';
messages[<number>Message.GroupRoleMembersReply] = 'GroupRoleMembersReplyMessage';
messages[<number>Message.GroupTitlesRequest] = 'GroupTitlesRequestMessage';
messages[<number>Message.GroupTitlesReply] = 'GroupTitlesReplyMessage';
messages[<number>Message.GroupTitleUpdate] = 'GroupTitleUpdateMessage';
messages[<number>Message.GroupRoleUpdate] = 'GroupRoleUpdateMessage';
messages[<number>Message.LiveHelpGroupRequest] = 'LiveHelpGroupRequestMessage';
messages[<number>Message.LiveHelpGroupReply] = 'LiveHelpGroupReplyMessage';
messages[<number>Message.AgentWearablesRequest] = 'AgentWearablesRequestMessage';
messages[<number>Message.AgentWearablesUpdate] = 'AgentWearablesUpdateMessage';
messages[<number>Message.AgentIsNowWearing] = 'AgentIsNowWearingMessage';
messages[<number>Message.AgentCachedTexture] = 'AgentCachedTextureMessage';
messages[<number>Message.AgentCachedTextureResponse] = 'AgentCachedTextureResponseMessage';
messages[<number>Message.AgentDataUpdateRequest] = 'AgentDataUpdateRequestMessage';
messages[<number>Message.AgentDataUpdate] = 'AgentDataUpdateMessage';
messages[<number>Message.GroupDataUpdate] = 'GroupDataUpdateMessage';
messages[<number>Message.AgentGroupDataUpdate] = 'AgentGroupDataUpdateMessage';
messages[<number>Message.AgentDropGroup] = 'AgentDropGroupMessage';
messages[<number>Message.LogTextMessage] = 'LogTextMessageMessage';
messages[<number>Message.ViewerEffect] = 'ViewerEffectMessage';
messages[<number>Message.CreateTrustedCircuit] = 'CreateTrustedCircuitMessage';
messages[<number>Message.DenyTrustedCircuit] = 'DenyTrustedCircuitMessage';
messages[<number>Message.RequestTrustedCircuit] = 'RequestTrustedCircuitMessage';
messages[<number>Message.RezSingleAttachmentFromInv] = 'RezSingleAttachmentFromInvMessage';
messages[<number>Message.RezMultipleAttachmentsFromInv] = 'RezMultipleAttachmentsFromInvMessage';
messages[<number>Message.DetachAttachmentIntoInv] = 'DetachAttachmentIntoInvMessage';
messages[<number>Message.CreateNewOutfitAttachments] = 'CreateNewOutfitAttachmentsMessage';
messages[<number>Message.UserInfoRequest] = 'UserInfoRequestMessage';
messages[<number>Message.UserInfoReply] = 'UserInfoReplyMessage';
messages[<number>Message.UpdateUserInfo] = 'UpdateUserInfoMessage';
messages[<number>Message.ParcelRename] = 'ParcelRenameMessage';
messages[<number>Message.InitiateDownload] = 'InitiateDownloadMessage';
messages[<number>Message.SystemMessage] = 'SystemMessageMessage';
messages[<number>Message.MapLayerRequest] = 'MapLayerRequestMessage';
messages[<number>Message.MapLayerReply] = 'MapLayerReplyMessage';
messages[<number>Message.MapBlockRequest] = 'MapBlockRequestMessage';
messages[<number>Message.MapNameRequest] = 'MapNameRequestMessage';
messages[<number>Message.MapBlockReply] = 'MapBlockReplyMessage';
messages[<number>Message.MapItemRequest] = 'MapItemRequestMessage';
messages[<number>Message.MapItemReply] = 'MapItemReplyMessage';
messages[<number>Message.SendPostcard] = 'SendPostcardMessage';
messages[<number>Message.RpcChannelRequest] = 'RpcChannelRequestMessage';
messages[<number>Message.RpcChannelReply] = 'RpcChannelReplyMessage';
messages[<number>Message.RpcScriptRequestInbound] = 'RpcScriptRequestInboundMessage';
messages[<number>Message.RpcScriptRequestInboundForward] = 'RpcScriptRequestInboundForwardMessage';
messages[<number>Message.RpcScriptReplyInbound] = 'RpcScriptReplyInboundMessage';
messages[<number>Message.ScriptMailRegistration] = 'ScriptMailRegistrationMessage';
messages[<number>Message.ParcelMediaCommandMessage] = 'ParcelMediaCommandMessageMessage';
messages[<number>Message.ParcelMediaUpdate] = 'ParcelMediaUpdateMessage';
messages[<number>Message.LandStatRequest] = 'LandStatRequestMessage';
messages[<number>Message.LandStatReply] = 'LandStatReplyMessage';
messages[<number>Message.Error] = 'ErrorMessage';
messages[<number>Message.ObjectIncludeInSearch] = 'ObjectIncludeInSearchMessage';
messages[<number>Message.RezRestoreToWorld] = 'RezRestoreToWorldMessage';
messages[<number>Message.LinkInventoryItem] = 'LinkInventoryItemMessage';
const messages: Record<number, string> = {};
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
{

View File

@@ -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;
}

View File

@@ -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<GameObject>(256, this.resolveInternal.bind(this));
private getCostsQueue = new BatchQueue<GameObject>(64, this.getCostsInternal.bind(this));
private readonly resolveQueue = new BatchQueue<GameObject>(256, this.resolveInternal.bind(this));
private readonly getCostsQueue = new BatchQueue<GameObject>(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<GameObject[]>
@@ -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<void>
@@ -147,6 +165,10 @@ export class ObjectResolver
private async getCostsInternal(objs: Set<GameObject>): Promise<Set<GameObject>>
{
const failed = new Set<GameObject>();
if (objs.size === 0)
{
return failed;
}
const submitted: Map<string, GameObject> = new Map<string, GameObject>();
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<number, GameObject>, timeout: number = 10000): Promise<void>
private async waitForResolve(objs: Map<number, GameObject>, timeout = 10000): Promise<void>
{
const entries = objs.entries();
for (const [localID, entry] of entries)

View File

@@ -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<void>
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);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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 (<any>MessageClass)[nameFromID(messageID)]() as MessageBase;
this.message = new (MessageClass as any)[nameFromID(messageID)]() as MessageBase;
pos += this.message.readFromBuffer(buf, pos);

View File

@@ -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');
}

View File

@@ -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<void>();
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<number>
{
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<number>
{
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);
});
});
}
}

View File

@@ -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]);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
import { TarFile } from './TarFile';
import type { TarFile } from './TarFile';
export class TarArchive
{
files: TarFile[] = [];
public files: TarFile[] = [];
}

View File

@@ -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<Buffer>
public async read(): Promise<Buffer>
{
return new Promise<Buffer>((resolve, reject) =>
{

View File

@@ -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<TarArchive>
public async parse(stream: Readable): Promise<TarArchive>
{
return new Promise<TarArchive>((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

View File

@@ -7,16 +7,16 @@ export class TarWriter extends Transform
private fileActive = false;
async newFile(archivePath: string, realPath: string): Promise<void>
public async newFile(archivePath: string, realPath: string): Promise<void>
{
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<void>
public async pipeFromBuffer(buf: Buffer): Promise<void>
{
const readableInstanceStream = new Readable({
read(): void
@@ -55,7 +55,7 @@ export class TarWriter extends Transform
return this.pipeFrom(readableInstanceStream);
}
pipeFrom(str: Readable): Promise<void>
public async pipeFrom(str: Readable): Promise<void>
{
return new Promise((resolve, reject) =>
{
@@ -71,6 +71,21 @@ export class TarWriter extends Transform
});
}
public async endFile(): Promise<void>
{
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<void>
{
const header = Buffer.alloc(512);
@@ -104,21 +119,6 @@ export class TarWriter extends Transform
return this.pipeFromBuffer(header);
}
async endFile(): Promise<void>
{
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);

View File

@@ -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<number, LLGLTFMaterialOverride>()
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));
}
}
}

View File

@@ -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;
}

View File

@@ -1,5 +1,5 @@
export class TimeoutError extends Error
{
timeout: true;
waitingForMessage: number;
public timeout: true;
public waitingForMessage: number;
}

View File

@@ -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();

View File

@@ -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<T>(value: T, count: number): T[]
public static fillArray<T>(value: T, count: number): T[]
{
const arr: T[] = new Array<T>(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<T>(promises: (() => Promise<T>)[], concurrency: number, timeout: number): Promise<{ results: T[], errors: Error[] }>
public static async promiseConcurrent<T>(promises: (() => Promise<T>)[], concurrency: number, timeout: number): Promise<{ results: T[], errors: unknown[] }>
{
return new Promise<{ results: T[], errors: Error[] }>(async(resolve) =>
const originalConcurrency = concurrency;
const promiseQueue: (() => Promise<T>)[] = [];
for (const promise of promises)
{
const originalConcurrency = concurrency;
const promiseQueue: (() => Promise<T>)[] = [];
Logger.Info('PromiseConcurrent: ' + promiseQueue.length + ' in queue. Concurrency: ' + concurrency);
for (const promise of promises)
{
promiseQueue.push(promise);
}
const slotAvailable: Subject<void> = new Subject<void>();
const errors: Error[] = [];
const results: T[] = [];
promiseQueue.push(promise);
}
const slotAvailable: Subject<void> = new Subject<void>();
const errors: unknown[] = [];
const results: T[] = [];
function waitForAvailable(): Promise<void>
async function waitForAvailable(): Promise<void>
{
return new Promise<void>((resolve1) =>
{
return new Promise<void>((resolve1) =>
const subs = slotAvailable.subscribe(() =>
{
const subs = slotAvailable.subscribe(() =>
{
subs.unsubscribe();
resolve1();
});
subs.unsubscribe();
resolve1();
});
}
});
}
function runPromise(promise: () => Promise<T>): void
function runPromise(promise: () => Promise<T>): 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<void>
public static async waitFor(timeout: number): Promise<void>
{
return new Promise<void>((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<Buffer>
public static async inflate(buf: Buffer): Promise<Buffer>
{
return new Promise<Buffer>((resolve, reject) =>
{
@@ -892,7 +640,7 @@ export class Utils
})
});
}
static deflate(buf: Buffer): Promise<Buffer>
public static async deflate(buf: Buffer): Promise<Buffer>
{
return new Promise<Buffer>((resolve, reject) =>
{
@@ -909,7 +657,7 @@ export class Utils
})
});
}
static waitOrTimeOut<T>(subject: Subject<T>, timeout?: number, callback?: (msg: T) => FilterResponse): Promise<T>
public static async waitOrTimeOut<T>(subject: Subject<T>, timeout?: number, callback?: (msg: T) => FilterResponse): Promise<T>
{
return new Promise<T>((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<any>
public static async parseXML(input: string): Promise<any>
{
return new Promise<any>((resolve, reject) =>
{
@@ -1033,7 +781,7 @@ export class Utils
return line.replace(/\r/, '').trim().replace(/[\t ]+/g, ' ');
}
public static sleep(ms: number): Promise<void>
public static async sleep(ms: number): Promise<void>
{
return new Promise((resolve) =>
{

View File

@@ -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);
}
}

View File

@@ -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]);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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<void>
public async startAnimations(anim: UUID[]): Promise<void>
{
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<void>
public async stopAnimations(anim: UUID[]): Promise<void>
{
return await this.animate(anim, true);
return this.animate(anim, false);
}
async stopAnimations(anim: UUID[]): Promise<void>
{
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<InventoryFolder>
// noinspection JSUnusedGlobalSymbols
public getGameObject(): GameObject
{
const agentLocalID = this.currentRegion.agent.localID;
return this.currentRegion.objects.getObjectByLocalID(agentLocalID);
}
// noinspection JSUnusedGlobalSymbols
public async getWearables(): Promise<InventoryFolder>
{
return this.agent.getWearables();
}
waitForAppearanceComplete(timeout: number = 30000): Promise<void>
// noinspection JSUnusedGlobalSymbols
public async waitForAppearanceComplete(timeout = 30000): Promise<void>
{
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<AvatarPropertiesReplyEvent>
// noinspection JSUnusedGlobalSymbols
public async getAvatarProperties(avatarID: UUID | string): Promise<AvatarPropertiesReplyEvent>
{
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<void>
{
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);
}
}

View File

@@ -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<Buffer>
public async downloadAsset(type: AssetType, uuid: UUID | string): Promise<Buffer>
{
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<InventoryItem>
public async copyInventoryFromNotecard(notecardID: UUID, folder: InventoryFolder, itemID: UUID, objectID: UUID = UUID.zero()): Promise<InventoryItem>
{
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<BulkUpdateInventoryEvent>(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<Buffer>
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<Buffer>
{
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<string, Material | null>): Promise<void>
{
let uuidArray: any[] = [];
let submittedUUIDS: Record<string, Material | null> = {};
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<Buffer>
{
return new Promise<Buffer>((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<number, Buffer> = {};
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<Buffer>
{
return new Promise<Buffer>((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<void>
private async getMaterialsLimited(uuidArray: unknown[], uuids: Record<string, Material | null>): Promise<void>
{
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<void>
{
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);
}
}
}

View File

@@ -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
}
}

View File

@@ -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<void>
public async giveInventory(to: UUID | string, itemOrFolder: InventoryItem | InventoryFolder): Promise<void>
{
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<void>
public async sendInstantMessage(to: UUID | string, message: string): Promise<void>
{
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<void>
public async nearbyChat(message: string, type: ChatType, channel?: number): Promise<void>
{
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<void>
public async say(message: string, channel?: number): Promise<void>
{
return await this.nearbyChat(message, ChatType.Normal, channel);
await this.nearbyChat(message, ChatType.Normal, channel);
}
async whisper(message: string, channel?: number): Promise<void>
public async whisper(message: string, channel?: number): Promise<void>
{
return await this.nearbyChat(message, ChatType.Whisper, channel);
await this.nearbyChat(message, ChatType.Whisper, channel);
}
async shout(message: string, channel?: number): Promise<void>
public async shout(message: string, channel?: number): Promise<void>
{
return await this.nearbyChat(message, ChatType.Shout, channel);
await this.nearbyChat(message, ChatType.Shout, channel);
}
async startTypingLocal(): Promise<void>
public async startTypingLocal(): Promise<void>
{
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<void>
public async sendTeleport(target: UUID | string, message?: string): Promise<void>
{
if (typeof target === 'string')
{
@@ -185,7 +185,7 @@ export class CommunicationsCommands extends CommandsBase
return this.circuit.waitForAck(sequenceNo, 10000);
}
async stopTypingLocal(): Promise<void>
public async stopTypingLocal(): Promise<void>
{
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<void>
public async startTypingIM(to: UUID | string): Promise<void>
{
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<void>
public async stopTypingIM(to: UUID | string): Promise<void>
{
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<void>
public async typeInstantMessage(to: UUID | string, message: string, thinkingTime?: number, charactersPerSecond?: number): Promise<void>
{
return new Promise<void>((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<void>
public async typeLocalMessage(message: string, thinkingTime?: number, charactersPerSecond?: number): Promise<void>
{
return new Promise<void>((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<void>
@@ -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<void>
public async moderateGroupChat(groupID: UUID | string, memberID: UUID | string, muteText: boolean, muteVoice: boolean): Promise<any>
{
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<number>
public async sendGroupMessage(groupID: UUID | string, message: string): Promise<number>
{
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<void>
public async respondToScriptDialog(event: ScriptDialogEvent, buttonIndex: number): Promise<void>
{
const dialog: ScriptDialogReplyMessage = new ScriptDialogReplyMessage();
dialog.AgentData = {

View File

@@ -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<string, Friend>();
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<void>
// noinspection JSUnusedGlobalSymbols
public async grantFriendRights(friend: Friend | UUID | string, rights: RightsFlags): Promise<void>
{
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<MapLocation>
public async getFriendMapLocation(friend: Friend | UUID | string): Promise<MapLocation>
{
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<void>
// noinspection JSUnusedGlobalSymbols
public getFriend(key: UUID): Friend | undefined
{
return this.friendsList.get(key.toString());
}
public async acceptFriendRequest(event: FriendRequestEvent): Promise<void>
{
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<void>
public async rejectFriendRequest(event: FriendRequestEvent): Promise<void>
{
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<void>
public async sendFriendRequest(to: UUID | string, message: string): Promise<void>
{
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<void>
{
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;
}
}
}

View File

@@ -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<RegionInfoReplyEvent>
public async getRegionByName(regionName: string): Promise<RegionInfoReplyEvent>
{
return new Promise<RegionInfoReplyEvent>((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<MapBlockReplyMessage>(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<MapBlockReplyMessage>(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<MapInfoReplyEvent>
{
return new Promise<MapInfoReplyEvent>((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<MapBlockReplyMessage>(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<MapItemReplyMessage>(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<MapInfoRangeReplyEvent>
public async getRegionMapInfo(gridX: number, gridY: number): Promise<MapInfoReplyEvent>
{
return new Promise<MapInfoRangeReplyEvent>((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<MapBlockReplyMessage>(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<MapItemReplyMessage>(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<MapInfoRangeReplyEvent>
{
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<MapBlockReplyMessage>(Message.MapBlockReply, 30000, (filterMsg: MapBlockReplyMessage): FilterResponse =>
await circuit.waitForMessage<MapBlockReplyMessage>(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<UUID>
public async avatarName2Key(name: string, useCap = true): Promise<UUID>
{
const result = await this.avatarName2KeyAndName(name, useCap);
return result.avatarKey;
}
async getBalance(): Promise<number>
public async getBalance(): Promise<number>
{
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<void>
public async payObject(target: GameObject, amount: number): Promise<void>
{
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<void>
public async payGroup(target: UUID | string, amount: number, description: string): Promise<void>
{
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<void>
public async payAvatar(target: UUID | string, amount: number, description: string): Promise<void>
{
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<AvatarQueryResult | AvatarQueryResult[]>
{
const req = new UUIDNameRequestMessage();
req.UUIDNameBlock = [];
let arr = true;
if (!Array.isArray(uuid))
{
arr = false;
uuid = [uuid];
}
const waitingFor: Record<string, {
firstName: string,
lastName: string
} | null> = {};
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<UUIDNameReplyMessage>(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<void>
{
if (amount % 1 !== 0)
@@ -439,83 +504,4 @@ export class GridCommands extends CommandsBase
throw new Error('Payment failed');
}
}
avatarKey2Name(uuid: UUID | UUID[]): Promise<AvatarQueryResult | AvatarQueryResult[]>
{
return new Promise<AvatarQueryResult | AvatarQueryResult[]>(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<UUIDNameReplyMessage>(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);
}
});
}
}

View File

@@ -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<void>
public async sendGroupNotice(groupID: UUID | string, subject: string, message: string): Promise<void>
{
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<void>
@@ -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<void>
public async sendGroupInvite(groupID: UUID | string, to: UUID | string, role: UUID | string | undefined): Promise<void>
{
const sendTo = [{
avatarID: to,
roleID: role
}];
return await this.sendGroupInviteBulk(groupID, sendTo);
await this.sendGroupInviteBulk(groupID, sendTo);
}
async acceptGroupInvite(event: GroupInviteEvent): Promise<void>
public async acceptGroupInvite(event: GroupInviteEvent): Promise<void>
{
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<void>
public async rejectGroupInvite(event: GroupInviteEvent): Promise<void>
{
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<void>
public async unbanMembers(groupID: UUID | string, avatars: UUID | string | string[] | UUID[]): Promise<void>
{
return this.banMembers(groupID, avatars, GroupBanAction.Unban);
}
async banMembers(groupID: UUID | string, avatars: UUID | string | string[] | UUID[], groupAction: GroupBanAction = GroupBanAction.Ban): Promise<void>
public async banMembers(groupID: UUID | string, avatars: UUID | string | string[] | UUID[], groupAction: GroupBanAction = GroupBanAction.Ban): Promise<void>
{
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<GroupBan[]>
public async getBanList(groupID: UUID | string): Promise<GroupBan[]>
{
if (typeof groupID === 'string')
{
@@ -242,7 +245,7 @@ export class GroupCommands extends CommandsBase
return bans;
}
async getMemberList(groupID: UUID | string): Promise<GroupMember[]>
public async getMemberList(groupID: UUID | string): Promise<GroupMember[]>
{
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<string, {
last_login: string,
owner: string,
title: number,
powers: string,
}>,
titles: Record<string, string>,
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<GroupRole[]>
public async getGroupRoles(groupID: UUID | string): Promise<GroupRole[]>
{
return new Promise<GroupRole[]>((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<GroupRoleDataReplyMessage>(Message.GroupRoleDataReply, 10000, (gmr: GroupRoleDataReplyMessage): FilterResponse =>
this.circuit.sendMessage(grdr, PacketFlags.Reliable);
await this.circuit.waitForMessage<GroupRoleDataReplyMessage>(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<void>
public async ejectFromGroupBulk(groupID: UUID | string, sendTo: UUID[] | string[]): Promise<void>
{
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<void>
public async ejectFromGroup(groupID: UUID | string, ejecteeID: UUID | string): Promise<void>
{
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<GroupProfileReplyEvent>
public async getGroupProfile(groupID: UUID | string): Promise<GroupProfileReplyEvent>
{
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;
};
}
}

View File

@@ -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<InventoryItem>
{
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<void>
{
if (event.source === ChatSourceType.Object)
{
return this.respondToInventoryOffer(event, InstantMessageDialog.TaskInventoryAccepted);
}
else
{
return this.respondToInventoryOffer(event, InstantMessageDialog.InventoryAccepted);
}
}
public async rejectInventoryOffer(event: InventoryOfferedEvent): Promise<void>
{
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<void>
{
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<InventoryItem>
{
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<void>
{
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<void>
{
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);
}
}

View File

@@ -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<void>
public async sitOnObject(targetID: UUID, offset: Vector3): Promise<void>
{
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);

View File

@@ -6,7 +6,7 @@ export class NetworkCommands extends CommandsBase
{
private throttleGenCounter = 0;
async setBandwidth(total: number): Promise<void>
public async setBandwidth(total: number): Promise<void>
{
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);
}
}

View File

@@ -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<ParcelInfoReplyEvent>
public async getParcelInfo(parcelID: UUID | string): Promise<ParcelInfoReplyEvent>
{
// 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<ParcelInfoReplyMessage>(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<LandStatsEvent>
public async getLandStats(parcelID: string | UUID | number, reportType: LandStatReportType, flags: LandStatFlags, filter?: string): Promise<LandStatsEvent>
{
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)
{

File diff suppressed because it is too large Load Diff

View File

@@ -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<TeleportEvent>
public async acceptTeleport(lure: LureEvent): Promise<TeleportEvent>
{
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<TeleportEvent>
{
const globalPos = Utils.RegionCoordinatesToHandle(x, y);
return this.teleportToHandle(globalPos.regionHandle, position, lookAt);
}
public async teleportToHandle(handle: Long, position: Vector3, lookAt: Vector3): Promise<TeleportEvent>
{
return new Promise<TeleportEvent>((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<TeleportEvent>
{
const region: RegionInfoReplyEvent = await this.bot.clientCommands.grid.getRegionByName(regionName);
return this.teleportToHandle(region.handle, position, lookAt);
}
private async awaitTeleportEvent(requested: boolean): Promise<TeleportEvent>
{
return new Promise<TeleportEvent>((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<TeleportEvent>
{
return new Promise<TeleportEvent>((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<TeleportEvent>
{
const globalPos = Utils.RegionCoordinatesToHandle(x, y);
return this.teleportToHandle(globalPos.regionHandle, position, lookAt);
}
teleportToHandle(handle: Long, position: Vector3, lookAt: Vector3): Promise<TeleportEvent>
{
return new Promise<TeleportEvent>((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<TeleportEvent>
{
return new Promise<TeleportEvent>((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);
});
});
}
}

View File

@@ -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<string, NameValue>;
PCode: PCode;
State?: number;
CRC?: number;

Some files were not shown because too many files have changed in this diff Show More