NMV 0.8.0 - Big refactor and linting fixes
This commit is contained in:
224
.eslintrc
224
.eslintrc
@@ -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
104
eslint.config.mjs
Normal 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,
|
||||
}],
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
19
examples/Region/Settings.ts
Normal file
19
examples/Region/Settings.ts
Normal 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);
|
||||
});
|
||||
305
lib/Bot.ts
305
lib/Bot.ts
@@ -1,58 +1,61 @@
|
||||
import { LoginHandler } from './LoginHandler';
|
||||
import { LoginResponse } from './classes/LoginResponse';
|
||||
import { LoginParameters } from './classes/LoginParameters';
|
||||
import { Agent } from './classes/Agent';
|
||||
import type { LoginResponse } from './classes/LoginResponse';
|
||||
import type { LoginParameters } from './classes/LoginParameters';
|
||||
import type { Agent } from './classes/Agent';
|
||||
import { PacketFlags } from './enums/PacketFlags';
|
||||
import { UseCircuitCodeMessage } from './classes/messages/UseCircuitCode';
|
||||
import { CompleteAgentMovementMessage } from './classes/messages/CompleteAgentMovement';
|
||||
import { Message } from './enums/Message';
|
||||
import { Packet } from './classes/Packet';
|
||||
import { Region } from './classes/Region';
|
||||
import type { Packet } from './classes/Packet';
|
||||
import type { Region } from './classes/Region';
|
||||
import { LogoutRequestMessage } from './classes/messages/LogoutRequest';
|
||||
import { Utils } from './classes/Utils';
|
||||
import { RegionHandshakeReplyMessage } from './classes/messages/RegionHandshakeReply';
|
||||
import { RegionProtocolFlags } from './enums/RegionProtocolFlags';
|
||||
import { AgentDataUpdateRequestMessage } from './classes/messages/AgentDataUpdateRequest';
|
||||
import { TeleportProgressMessage } from './classes/messages/TeleportProgress';
|
||||
import type { TeleportProgressMessage } from './classes/messages/TeleportProgress';
|
||||
import { TeleportEvent } from './events/TeleportEvent';
|
||||
import { ClientEvents } from './classes/ClientEvents';
|
||||
import { TeleportEventType } from './enums/TeleportEventType';
|
||||
import { ClientCommands } from './classes/ClientCommands';
|
||||
import { DisconnectEvent } from './events/DisconnectEvent';
|
||||
import { KickUserMessage } from './classes/messages/KickUser';
|
||||
import type { KickUserMessage } from './classes/messages/KickUser';
|
||||
import { StartPingCheckMessage } from './classes/messages/StartPingCheck';
|
||||
import { CompletePingCheckMessage } from './classes/messages/CompletePingCheck';
|
||||
import { BotOptionFlags } from './enums/BotOptionFlags';
|
||||
import type { CompletePingCheckMessage } from './classes/messages/CompletePingCheck';
|
||||
import type { BotOptionFlags } from './enums/BotOptionFlags';
|
||||
import { FilterResponse } from './enums/FilterResponse';
|
||||
import { LogoutReplyMessage } from './classes/messages/LogoutReply';
|
||||
import { EventQueueStateChangeEvent } from './events/EventQueueStateChangeEvent';
|
||||
import type { LogoutReplyMessage } from './classes/messages/LogoutReply';
|
||||
import type { EventQueueStateChangeEvent } from './events/EventQueueStateChangeEvent';
|
||||
import { UUID } from './classes/UUID';
|
||||
import { Vector3 } from './classes/Vector3';
|
||||
import { RegionHandshakeMessage } from './classes/messages/RegionHandshake';
|
||||
import { AgentMovementCompleteMessage } from './classes/messages/AgentMovementComplete';
|
||||
import { Subscription } from 'rxjs';
|
||||
import Timer = NodeJS.Timer;
|
||||
import type { RegionHandshakeMessage } from './classes/messages/RegionHandshake';
|
||||
import type { AgentMovementCompleteMessage } from './classes/messages/AgentMovementComplete';
|
||||
import type { Subscription } from 'rxjs';
|
||||
|
||||
export class Bot
|
||||
{
|
||||
private loginParams: LoginParameters;
|
||||
public clientEvents: ClientEvents;
|
||||
private stayRegion = '';
|
||||
private stayPosition = new Vector3();
|
||||
|
||||
private readonly loginParams: LoginParameters;
|
||||
private ping: NodeJS.Timeout | null = null;
|
||||
private pingNumber = 0;
|
||||
private lastSuccessfulPing = 0;
|
||||
private circuitSubscription: Subscription | null = null;
|
||||
private options: BotOptionFlags;
|
||||
private readonly options: BotOptionFlags;
|
||||
private eventQueueRunning = false;
|
||||
private eventQueueWaits: any = {};
|
||||
private readonly eventQueueWaits = new Map<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
34
lib/classes/AssetRegistry.ts
Normal file
34
lib/classes/AssetRegistry.ts
Normal 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>();
|
||||
}
|
||||
102
lib/classes/AssetTypeRegistry.ts
Normal file
102
lib/classes/AssetTypeRegistry.ts
Normal 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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
327
lib/classes/BinaryReader.spec.ts
Normal file
327
lib/classes/BinaryReader.spec.ts
Normal 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
443
lib/classes/BinaryReader.ts
Normal 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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
509
lib/classes/BinaryWriter.spec.ts
Normal file
509
lib/classes/BinaryWriter.spec.ts
Normal 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
268
lib/classes/BinaryWriter.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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++);
|
||||
|
||||
@@ -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
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
88
lib/classes/InventoryTypeRegistry.ts
Normal file
88
lib/classes/InventoryTypeRegistry.ts
Normal 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
129
lib/classes/LLAnimation.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
72
lib/classes/LLAnimationJoint.ts
Normal file
72
lib/classes/LLAnimationJoint.ts
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
8
lib/classes/LLAnimationJointKeyFrame.ts
Normal file
8
lib/classes/LLAnimationJointKeyFrame.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { Vector3 } from './Vector3';
|
||||
|
||||
export class LLAnimationJointKeyFrame
|
||||
{
|
||||
public time: number;
|
||||
public transform: Vector3;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -124,9 +124,9 @@ export interface LLGLTFMaterialDataPart
|
||||
scale?: number[];
|
||||
translaction?: number[]
|
||||
} | {
|
||||
matrix?: number[]
|
||||
}
|
||||
)) & LLGLTFExtensionsAndExtras[],
|
||||
matrix?: number[]
|
||||
}
|
||||
)) & LLGLTFExtensionsAndExtras[],
|
||||
scenes?: ({
|
||||
name?: string;
|
||||
nodes?: number[];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { LLGestureStepType } from '../enums/LLGestureStepType';
|
||||
import type { LLGestureStepType } from '../enums/LLGestureStepType';
|
||||
|
||||
export class LLGestureStep
|
||||
{
|
||||
stepType: LLGestureStepType
|
||||
public stepType: LLGestureStepType
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 !== '}')
|
||||
{
|
||||
|
||||
31
lib/classes/LLSettings.spec.ts
Normal file
31
lib/classes/LLSettings.spec.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
463
lib/classes/Matrix3.ts
Normal 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
547
lib/classes/Matrix4.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
100
lib/classes/PrimFacesHelper.ts
Normal file
100
lib/classes/PrimFacesHelper.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -1,6 +1,6 @@
|
||||
import { TarFile } from './TarFile';
|
||||
import type { TarFile } from './TarFile';
|
||||
|
||||
export class TarArchive
|
||||
{
|
||||
files: TarFile[] = [];
|
||||
public files: TarFile[] = [];
|
||||
}
|
||||
|
||||
@@ -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) =>
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export class TimeoutError extends Error
|
||||
{
|
||||
timeout: true;
|
||||
waitingForMessage: number;
|
||||
public timeout: true;
|
||||
public waitingForMessage: number;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) =>
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user