Extensive work on building, wearables, assets, inventory, attachments, serialization, etc.
Resolves #36
This commit is contained in:
@@ -10,6 +10,7 @@ import { AvatarPropertiesReplyMessage } from '../messages/AvatarPropertiesReply'
|
||||
import { AvatarPropertiesRequestMessage } from '../messages/AvatarPropertiesRequest';
|
||||
import { AvatarPropertiesReplyEvent } from '../../events/AvatarPropertiesReplyEvent';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Avatar } from '../public/Avatar';
|
||||
|
||||
export class AgentCommands extends CommandsBase
|
||||
{
|
||||
@@ -70,11 +71,16 @@ export class AgentCommands extends CommandsBase
|
||||
this.agent.sendAgentUpdate();
|
||||
}
|
||||
|
||||
waitForAppearanceSet(timeout: number = 10000): Promise<void>
|
||||
async getWearables()
|
||||
{
|
||||
return this.agent.getWearables();
|
||||
}
|
||||
|
||||
waitForAppearanceComplete(timeout: number = 30000): Promise<void>
|
||||
{
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
if (this.agent.appearanceSet)
|
||||
if (this.agent.appearanceComplete)
|
||||
{
|
||||
resolve();
|
||||
}
|
||||
@@ -82,7 +88,7 @@ export class AgentCommands extends CommandsBase
|
||||
{
|
||||
let appearanceSubscription: Subscription | undefined;
|
||||
let timeoutTimer: number | undefined;
|
||||
appearanceSubscription = this.agent.appearanceSetEvent.subscribe(() =>
|
||||
appearanceSubscription = this.agent.appearanceCompleteEvent.subscribe(() =>
|
||||
{
|
||||
if (timeoutTimer !== undefined)
|
||||
{
|
||||
@@ -110,7 +116,7 @@ export class AgentCommands extends CommandsBase
|
||||
reject(new Error('Timeout'));
|
||||
}
|
||||
}, timeout) as any as number;
|
||||
if (this.agent.appearanceSet)
|
||||
if (this.agent.appearanceComplete)
|
||||
{
|
||||
if (appearanceSubscription !== undefined)
|
||||
{
|
||||
@@ -128,6 +134,19 @@ export class AgentCommands extends CommandsBase
|
||||
});
|
||||
}
|
||||
|
||||
getAvatar(avatarID: UUID | string = UUID.zero()): Avatar
|
||||
{
|
||||
if (typeof avatarID === 'string')
|
||||
{
|
||||
avatarID = new UUID(avatarID);
|
||||
}
|
||||
else if (avatarID.isZero())
|
||||
{
|
||||
avatarID = this.agent.agentID;
|
||||
}
|
||||
return this.currentRegion.objects.getAvatar(avatarID);
|
||||
}
|
||||
|
||||
async getAvatarProperties(avatarID: UUID | string): Promise<AvatarPropertiesReplyEvent>
|
||||
{
|
||||
if (typeof avatarID === 'string')
|
||||
|
||||
@@ -2,10 +2,8 @@ import { CommandsBase } from './CommandsBase';
|
||||
import { UUID } from '../UUID';
|
||||
import * as LLSD from '@caspertech/llsd';
|
||||
import { Utils } from '../Utils';
|
||||
import { PermissionMask } from '../../enums/PermissionMask';
|
||||
import * as zlib from 'zlib';
|
||||
import { ZlibOptions } from 'zlib';
|
||||
import { Color4 } from '../Color4';
|
||||
import { TransferRequestMessage } from '../messages/TransferRequest';
|
||||
import { TransferChannelType } from '../../enums/TransferChannelType';
|
||||
import { TransferSourceType } from '../../enums/TransferSourceTypes';
|
||||
@@ -18,18 +16,19 @@ import { AssetType } from '../../enums/AssetType';
|
||||
import { PacketFlags } from '../../enums/PacketFlags';
|
||||
import { TransferStatus } from '../../enums/TransferStatus';
|
||||
import { Material } from '../public/Material';
|
||||
import { LLMesh } from '../public/LLMesh';
|
||||
import { FolderType } from '../../enums/FolderType';
|
||||
import { HTTPAssets } from '../../enums/HTTPAssets';
|
||||
import { InventoryFolder } from '../InventoryFolder';
|
||||
import { InventoryItem } from '../InventoryItem';
|
||||
import { CreateInventoryItemMessage } from '../messages/CreateInventoryItem';
|
||||
import { WearableType } from '../../enums/WearableType';
|
||||
import { UpdateCreateInventoryItemMessage } from '../messages/UpdateCreateInventoryItem';
|
||||
import { BulkUpdateInventoryEvent } from '../../events/BulkUpdateInventoryEvent';
|
||||
import { FilterResponse } from '../../enums/FilterResponse';
|
||||
import { LLLindenText } from '../LLLindenText';
|
||||
import { Logger } from '../Logger';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
export class AssetCommands extends CommandsBase
|
||||
{
|
||||
private callbackID: number = 1;
|
||||
private callbackID = 0;
|
||||
|
||||
async downloadAsset(type: HTTPAssets, uuid: UUID | string): Promise<Buffer>
|
||||
{
|
||||
if (typeof uuid === 'string')
|
||||
@@ -59,6 +58,49 @@ export class AssetCommands extends CommandsBase
|
||||
}
|
||||
}
|
||||
|
||||
async copyInventoryFromNotecard(notecardID: UUID, folder: InventoryFolder, itemID: UUID, objectID: UUID = UUID.zero()): Promise<InventoryItem>
|
||||
{
|
||||
const gotCap = await this.currentRegion.caps.isCapAvailable('CopyInventoryFromNotecard');
|
||||
if (gotCap)
|
||||
{
|
||||
const callbackID = Math.floor(Math.random() * 2147483647);
|
||||
const request = {
|
||||
'callback-id': callbackID,
|
||||
'folder-id': new LLSD.UUID(folder.folderID.toString()),
|
||||
'item-id': new LLSD.UUID(itemID.toString()),
|
||||
'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;
|
||||
});
|
||||
const evt: BulkUpdateInventoryEvent = await Utils.waitOrTimeOut<BulkUpdateInventoryEvent>(this.currentRegion.clientEvents.onBulkUpdateInventoryEvent, 10000, (event: BulkUpdateInventoryEvent) =>
|
||||
{
|
||||
for (const item of event.itemData)
|
||||
{
|
||||
if (item.callbackID === callbackID)
|
||||
{
|
||||
return FilterResponse.Finish;
|
||||
}
|
||||
}
|
||||
return FilterResponse.NoMatch;
|
||||
});
|
||||
for (const item of evt.itemData)
|
||||
{
|
||||
if (item.callbackID === callbackID)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
throw new Error('No match');
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Error('CopyInventoryFromNotecard cap not available');
|
||||
}
|
||||
}
|
||||
|
||||
transfer(channelType: TransferChannelType, sourceType: TransferSourceType, priority: boolean, transferParams: Buffer, outAssetID?: { assetID: UUID }): Promise<Buffer>
|
||||
{
|
||||
return new Promise<Buffer>((resolve, reject) =>
|
||||
@@ -72,12 +114,45 @@ export class AssetCommands extends CommandsBase
|
||||
Priority: 100.0 + (priority ? 1.0 : 0.0),
|
||||
Params: transferParams
|
||||
};
|
||||
|
||||
this.circuit.sendMessage(msg, PacketFlags.Reliable);
|
||||
let gotInfo = true;
|
||||
let expectedSize = 0;
|
||||
const packets: { [key: number]: Buffer } = {};
|
||||
const subscription = this.circuit.subscribeToMessages([
|
||||
let subscription: Subscription | undefined = undefined;
|
||||
let timeout: number | undefined;
|
||||
|
||||
function cleanup()
|
||||
{
|
||||
if (subscription !== undefined)
|
||||
{
|
||||
subscription.unsubscribe();
|
||||
subscription = undefined;
|
||||
}
|
||||
if (timeout !== undefined)
|
||||
{
|
||||
clearTimeout(timeout);
|
||||
timeout = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function placeTimeout()
|
||||
{
|
||||
timeout = setTimeout(() =>
|
||||
{
|
||||
cleanup();
|
||||
reject(new Error('Timeout'));
|
||||
}, 10000) as any as number;
|
||||
}
|
||||
|
||||
function resetTimeout()
|
||||
{
|
||||
if (timeout !== undefined)
|
||||
{
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
placeTimeout();
|
||||
}
|
||||
|
||||
subscription = this.circuit.subscribeToMessages([
|
||||
Message.TransferInfo,
|
||||
Message.TransferAbort,
|
||||
Message.TransferPacket
|
||||
@@ -90,26 +165,31 @@ export class AssetCommands extends CommandsBase
|
||||
case Message.TransferPacket:
|
||||
{
|
||||
const messg = packet.message as TransferPacketMessage;
|
||||
if (!messg.TransferData.TransferID.equals(transferID))
|
||||
{
|
||||
return;
|
||||
}
|
||||
resetTimeout();
|
||||
packets[messg.TransferData.Packet] = messg.TransferData.Data;
|
||||
switch (messg.TransferData.Status)
|
||||
{
|
||||
case TransferStatus.Abort:
|
||||
subscription.unsubscribe();
|
||||
cleanup();
|
||||
reject(new Error('Transfer Aborted'));
|
||||
break;
|
||||
case TransferStatus.Error:
|
||||
subscription.unsubscribe();
|
||||
cleanup();
|
||||
reject(new Error('Error'));
|
||||
break;
|
||||
case TransferStatus.Skip:
|
||||
console.error('TransferPacket: Skip! not sure what this means');
|
||||
break;
|
||||
case TransferStatus.InsufficientPermissions:
|
||||
subscription.unsubscribe();
|
||||
cleanup();
|
||||
reject(new Error('Insufficient Permissions'));
|
||||
break;
|
||||
case TransferStatus.NotFound:
|
||||
subscription.unsubscribe();
|
||||
cleanup();
|
||||
reject(new Error('Not Found'));
|
||||
break;
|
||||
}
|
||||
@@ -122,6 +202,7 @@ export class AssetCommands extends CommandsBase
|
||||
{
|
||||
return;
|
||||
}
|
||||
resetTimeout();
|
||||
const status = messg.TransferInfo.Status as TransferStatus;
|
||||
switch (status)
|
||||
{
|
||||
@@ -134,23 +215,23 @@ export class AssetCommands extends CommandsBase
|
||||
}
|
||||
break;
|
||||
case TransferStatus.Abort:
|
||||
subscription.unsubscribe();
|
||||
cleanup();
|
||||
reject(new Error('Transfer Aborted'));
|
||||
break;
|
||||
case TransferStatus.Error:
|
||||
subscription.unsubscribe();
|
||||
reject(new Error('Error'));
|
||||
cleanup();
|
||||
reject(new Error('Error downloading asset'));
|
||||
// See if we get anything else
|
||||
break;
|
||||
case TransferStatus.Skip:
|
||||
console.error('TransferInfo: Skip! not sure what this means');
|
||||
break;
|
||||
case TransferStatus.InsufficientPermissions:
|
||||
subscription.unsubscribe();
|
||||
cleanup();
|
||||
reject(new Error('Insufficient Permissions'));
|
||||
break;
|
||||
case TransferStatus.NotFound:
|
||||
subscription.unsubscribe();
|
||||
cleanup();
|
||||
reject(new Error('Not Found'));
|
||||
break;
|
||||
}
|
||||
@@ -164,7 +245,8 @@ export class AssetCommands extends CommandsBase
|
||||
{
|
||||
return;
|
||||
}
|
||||
subscription.unsubscribe();
|
||||
resetTimeout();
|
||||
cleanup();
|
||||
reject(new Error('Transfer Aborted'));
|
||||
return;
|
||||
}
|
||||
@@ -188,24 +270,33 @@ export class AssetCommands extends CommandsBase
|
||||
{
|
||||
buffers.push(packets[parseInt(pn, 10)]);
|
||||
}
|
||||
subscription.unsubscribe();
|
||||
cleanup();
|
||||
resolve(Buffer.concat(buffers));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
subscription.unsubscribe();
|
||||
cleanup();
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
placeTimeout();
|
||||
this.circuit.sendMessage(msg, PacketFlags.Reliable);
|
||||
});
|
||||
}
|
||||
|
||||
downloadInventoryAsset(itemID: UUID, ownerID: UUID, type: AssetType, priority: boolean, objectID: UUID = UUID.zero(), assetID: UUID = UUID.zero(), outAssetID?: { assetID: UUID }): Promise<Buffer>
|
||||
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);
|
||||
@@ -222,7 +313,7 @@ export class AssetCommands extends CommandsBase
|
||||
pos = pos + 16;
|
||||
transferParams.writeInt32LE(type, pos);
|
||||
|
||||
this.transfer(TransferChannelType.Asset, TransferSourceType.SimInventoryItem, priority, transferParams, outAssetID).then((result) =>
|
||||
this.transfer(channelType, sourceType, priority, transferParams, outAssetID).then((result) =>
|
||||
{
|
||||
resolve(result);
|
||||
}).catch((err) =>
|
||||
@@ -233,85 +324,45 @@ export class AssetCommands extends CommandsBase
|
||||
});
|
||||
}
|
||||
|
||||
private getMaterialsLimited(uuidArray: any[], uuids: {[key: string]: Material | null}): Promise<void>
|
||||
private async getMaterialsLimited(uuidArray: any[], uuids: {[key: string]: Material | null})
|
||||
{
|
||||
return new Promise<void>((resolve, reject) =>
|
||||
{
|
||||
const binary = LLSD.LLSD.formatBinary(uuidArray);
|
||||
const options: ZlibOptions = {
|
||||
level: 9
|
||||
};
|
||||
zlib.deflate(Buffer.from(binary.toArray()), options, async (error: Error | null, res: Buffer) =>
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
const result = await this.currentRegion.caps.capsPostXML('RenderMaterials', {
|
||||
'Zipped': new LLSD.LLSD.asBinary(res.toString('base64'))
|
||||
});
|
||||
|
||||
const resultZipped = Buffer.from(result['Zipped'].octets);
|
||||
zlib.inflate(resultZipped, async (err: Error | null, reslt: Buffer) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
const binData = new LLSD.Binary(Array.from(reslt), 'BASE64');
|
||||
const llsdResult = LLSD.LLSD.parseBinary(binData);
|
||||
let obj = [];
|
||||
if (llsdResult.result)
|
||||
{
|
||||
obj = llsdResult.result;
|
||||
}
|
||||
if (obj.length > 0)
|
||||
{
|
||||
for (const mat of obj)
|
||||
{
|
||||
if (mat['ID'])
|
||||
{
|
||||
const nbuf = Buffer.from(mat['ID'].toArray());
|
||||
const nuuid = new UUID(nbuf, 0).toString();
|
||||
if (uuids[nuuid] !== undefined)
|
||||
{
|
||||
if (mat['Material'])
|
||||
{
|
||||
const material = new Material();
|
||||
material.alphaMaskCutoff = mat['Material']['AlphaMaskCutoff'];
|
||||
material.diffuseAlphaMode = mat['Material']['DiffuseAlphaMode'];
|
||||
material.envIntensity = mat['Material']['EnvIntensity'];
|
||||
material.normMap = new UUID(mat['Material']['NormMap'].toString());
|
||||
material.normOffsetX = mat['Material']['NormOffsetX'];
|
||||
material.normOffsetY = mat['Material']['NormOffsetY'];
|
||||
material.normRepeatX = mat['Material']['NormRepeatX'];
|
||||
material.normRepeatY = mat['Material']['NormRepeatY'];
|
||||
material.normRotation = mat['Material']['NormRotation'];
|
||||
material.specColor = new Color4(mat['Material']['SpecColor'][0], mat['Material']['SpecColor'][1], mat['Material']['SpecColor'][2], mat['Material']['SpecColor'][3]);
|
||||
material.specExp = mat['Material']['SpecExp'];
|
||||
material.specMap = new UUID(mat['Material']['SpecMap'].toString());
|
||||
material.specOffsetX = mat['Material']['SpecOffsetX'];
|
||||
material.specOffsetY = mat['Material']['SpecOffsetY'];
|
||||
material.specRepeatX = mat['Material']['SpecRepeatX'];
|
||||
material.specRepeatY = mat['Material']['SpecRepeatY'];
|
||||
material.specRotation = mat['Material']['SpecRotation'];
|
||||
material.llsd = LLSD.LLSD.formatXML(mat['Material']);
|
||||
uuids[nuuid] = material;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
else
|
||||
{
|
||||
reject(new Error('Material data not found'));
|
||||
}
|
||||
});
|
||||
});
|
||||
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 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)
|
||||
{
|
||||
obj = llsdResult.result;
|
||||
}
|
||||
if (obj.length > 0)
|
||||
{
|
||||
for (const mat of obj)
|
||||
{
|
||||
if (mat['ID'])
|
||||
{
|
||||
const nbuf = Buffer.from(mat['ID'].toArray());
|
||||
const nuuid = new UUID(nbuf, 0).toString();
|
||||
if (uuids[nuuid] !== undefined)
|
||||
{
|
||||
if (mat['Material'])
|
||||
{
|
||||
uuids[nuuid] = Material.fromLLSDObject(mat['Material']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Error('Material data not found');
|
||||
}
|
||||
}
|
||||
|
||||
async getMaterials(uuids: {[key: string]: Material | null}): Promise<void>
|
||||
@@ -336,8 +387,6 @@ export class AssetCommands extends CommandsBase
|
||||
}
|
||||
totalCount++;
|
||||
}
|
||||
console.log('Resolved ' + resolvedCount + ' of ' + totalCount + ' materials');
|
||||
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
@@ -366,367 +415,10 @@ export class AssetCommands extends CommandsBase
|
||||
}
|
||||
totalCount++;
|
||||
}
|
||||
console.log('Resolved ' + resolvedCount + ' of ' + totalCount + ' materials (end)');
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async uploadMesh(name: string, description: string, mesh: Buffer, confirmCostCallback: (cost: number) => boolean): Promise<UUID>
|
||||
{
|
||||
const decodedMesh = await LLMesh.from(mesh);
|
||||
if (!decodedMesh.creatorID.equals(this.agent.agentID) && !decodedMesh.creatorID.equals(UUID.zero()))
|
||||
{
|
||||
throw new Error('Unable to upload - copyright violation');
|
||||
}
|
||||
const faces = [];
|
||||
const faceCount = decodedMesh.lodLevels['high_lod'].length;
|
||||
for (let x = 0; x < faceCount; x++)
|
||||
{
|
||||
faces.push({
|
||||
'diffuse_color': [1.000000000000001, 1.000000000000001, 1.000000000000001, 1.000000000000001],
|
||||
'fullbright': false
|
||||
});
|
||||
}
|
||||
const prim = {
|
||||
'face_list': faces,
|
||||
'position': [0.000000000000001, 0.000000000000001, 0.000000000000001],
|
||||
'rotation': [0.000000000000001, 0.000000000000001, 0.000000000000001, 1.000000000000001],
|
||||
'scale': [2.000000000000001, 2.000000000000001, 2.000000000000001],
|
||||
'material': 3,
|
||||
'physics_shape_type': 2,
|
||||
'mesh': 0
|
||||
};
|
||||
const assetResources = {
|
||||
'instance_list': [prim],
|
||||
'mesh_list': [new LLSD.Binary(Array.from(mesh))],
|
||||
'texture_list': [],
|
||||
'metric': 'MUT_Unspecified'
|
||||
};
|
||||
const uploadMap = {
|
||||
'name': String(name),
|
||||
'description': String(description),
|
||||
'asset_resources': assetResources,
|
||||
'asset_type': 'mesh',
|
||||
'inventory_type': 'object',
|
||||
'folder_id': new LLSD.UUID(await this.agent.inventory.findFolderForType(FolderType.Object)),
|
||||
'texture_folder_id': new LLSD.UUID(await this.agent.inventory.findFolderForType(FolderType.Texture)),
|
||||
'everyone_mask': PermissionMask.All,
|
||||
'group_mask': PermissionMask.All,
|
||||
'next_owner_mask': PermissionMask.All
|
||||
};
|
||||
let result;
|
||||
try
|
||||
{
|
||||
result = await this.currentRegion.caps.capsPostXML('NewFileAgentInventory', uploadMap);
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.error(error);
|
||||
}
|
||||
if (result['state'] === 'upload' && result['upload_price'] !== undefined)
|
||||
{
|
||||
const cost = result['upload_price'];
|
||||
if (await confirmCostCallback(cost))
|
||||
{
|
||||
const uploader = result['uploader'];
|
||||
const uploadResult = await this.currentRegion.caps.capsPerformXMLPost(uploader, assetResources);
|
||||
if (uploadResult['new_inventory_item'] && uploadResult['new_asset'])
|
||||
{
|
||||
const inventoryItem = new UUID(uploadResult['new_inventory_item'].toString());
|
||||
const item = await this.agent.inventory.fetchInventoryItem(inventoryItem);
|
||||
if (item !== null)
|
||||
{
|
||||
item.assetID = new UUID(uploadResult['new_asset'].toString());
|
||||
}
|
||||
return inventoryItem;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
throw new Error('Upload failed - no new inventory item returned');
|
||||
}
|
||||
}
|
||||
throw new Error('Upload cost declined')
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log(result);
|
||||
console.log(JSON.stringify(result.error));
|
||||
throw new Error('Upload failed');
|
||||
}
|
||||
}
|
||||
|
||||
uploadInventoryItem(type: HTTPAssets, data: Buffer, name: string, description: string): Promise<UUID>
|
||||
{
|
||||
return new Promise<UUID>((resolve, reject) =>
|
||||
{
|
||||
if (type === HTTPAssets.ASSET_SCRIPT)
|
||||
{
|
||||
type = HTTPAssets.ASSET_LSL_TEXT;
|
||||
}
|
||||
const transactionID = UUID.random();
|
||||
const callbackID = ++this.callbackID;
|
||||
const msg = new CreateInventoryItemMessage();
|
||||
msg.AgentData = {
|
||||
AgentID: this.agent.agentID,
|
||||
SessionID: this.circuit.sessionID
|
||||
};
|
||||
msg.InventoryBlock = {
|
||||
CallbackID: callbackID,
|
||||
FolderID: this.agent.inventory.main.root || UUID.zero(),
|
||||
TransactionID: transactionID,
|
||||
NextOwnerMask: (1 << 13) | (1 << 14) | (1 << 15) | (1 << 19),
|
||||
Type: Utils.HTTPAssetTypeToAssetType(type),
|
||||
InvType: Utils.HTTPAssetTypeToInventoryType(type),
|
||||
WearableType: WearableType.Shape,
|
||||
Name: Utils.StringToBuffer(name),
|
||||
Description: Utils.StringToBuffer(description)
|
||||
};
|
||||
this.currentRegion.circuit.waitForMessage<UpdateCreateInventoryItemMessage>(Message.UpdateCreateInventoryItem, 10000, (message: UpdateCreateInventoryItemMessage) =>
|
||||
{
|
||||
if (message.InventoryData[0].CallbackID === callbackID)
|
||||
{
|
||||
return FilterResponse.Finish;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FilterResponse.NoMatch;
|
||||
}
|
||||
}).then((createInventoryMsg: UpdateCreateInventoryItemMessage) =>
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case HTTPAssets.ASSET_NOTECARD:
|
||||
{
|
||||
this.currentRegion.caps.capsPostXML('UpdateNotecardAgentInventory', {
|
||||
'item_id': new LLSD.UUID(createInventoryMsg.InventoryData[0].ItemID.toString()),
|
||||
}).then((result: any) =>
|
||||
{
|
||||
if (result['uploader'])
|
||||
{
|
||||
const uploader = result['uploader'];
|
||||
this.currentRegion.caps.capsRequestUpload(uploader, data).then((uploadResult: any) =>
|
||||
{
|
||||
if (uploadResult['state'] && uploadResult['state'] === 'complete')
|
||||
{
|
||||
const itemID: UUID = createInventoryMsg.InventoryData[0].ItemID;
|
||||
resolve(itemID);
|
||||
}
|
||||
else
|
||||
{
|
||||
reject(new Error('Asset upload failed'))
|
||||
}
|
||||
}).catch((err) =>
|
||||
{
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
reject(new Error('Invalid response when attempting to request upload URL for notecard'));
|
||||
}
|
||||
}).catch((err) =>
|
||||
{
|
||||
reject(err);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case HTTPAssets.ASSET_GESTURE:
|
||||
{
|
||||
this.currentRegion.caps.capsPostXML('UpdateGestureAgentInventory', {
|
||||
'item_id': new LLSD.UUID(createInventoryMsg.InventoryData[0].ItemID.toString()),
|
||||
}).then((result: any) =>
|
||||
{
|
||||
if (result['uploader'])
|
||||
{
|
||||
const uploader = result['uploader'];
|
||||
this.currentRegion.caps.capsRequestUpload(uploader, data).then((uploadResult: any) =>
|
||||
{
|
||||
if (uploadResult['state'] && uploadResult['state'] === 'complete')
|
||||
{
|
||||
const itemID: UUID = createInventoryMsg.InventoryData[0].ItemID;
|
||||
resolve(itemID);
|
||||
}
|
||||
else
|
||||
{
|
||||
reject(new Error('Asset upload failed'))
|
||||
}
|
||||
}).catch((err) =>
|
||||
{
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
reject(new Error('Invalid response when attempting to request upload URL for notecard'));
|
||||
}
|
||||
}).catch((err) =>
|
||||
{
|
||||
reject(err);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case HTTPAssets.ASSET_LSL_TEXT:
|
||||
{
|
||||
this.currentRegion.caps.capsPostXML('UpdateScriptAgent', {
|
||||
'item_id': new LLSD.UUID(createInventoryMsg.InventoryData[0].ItemID.toString()),
|
||||
'target': 'mono'
|
||||
}).then((result: any) =>
|
||||
{
|
||||
if (result['uploader'])
|
||||
{
|
||||
const uploader = result['uploader'];
|
||||
this.currentRegion.caps.capsRequestUpload(uploader, data).then((uploadResult: any) =>
|
||||
{
|
||||
if (uploadResult['state'] && uploadResult['state'] === 'complete')
|
||||
{
|
||||
const itemID: UUID = createInventoryMsg.InventoryData[0].ItemID;
|
||||
resolve(itemID);
|
||||
}
|
||||
else
|
||||
{
|
||||
reject(new Error('Asset upload failed'))
|
||||
}
|
||||
}).catch((err) =>
|
||||
{
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
reject(new Error('Invalid response when attempting to request upload URL for notecard'));
|
||||
}
|
||||
}).catch((err) =>
|
||||
{
|
||||
reject(err);
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
reject(new Error('Currently unsupported CreateInventoryType: ' + type));
|
||||
}
|
||||
}
|
||||
}).catch(() =>
|
||||
{
|
||||
reject(new Error('Timed out waiting for UpdateCreateInventoryItem'));
|
||||
});
|
||||
this.circuit.sendMessage(msg, PacketFlags.Reliable);
|
||||
});
|
||||
}
|
||||
|
||||
uploadAsset(type: HTTPAssets, data: Buffer, name: string, description: string): Promise<InventoryItem>
|
||||
{
|
||||
return new Promise<InventoryItem>((resolve, reject) =>
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case HTTPAssets.ASSET_LANDMARK:
|
||||
case HTTPAssets.ASSET_NOTECARD:
|
||||
case HTTPAssets.ASSET_GESTURE:
|
||||
case HTTPAssets.ASSET_SCRIPT:
|
||||
// These types of assets use an different process
|
||||
const inventoryItem = this.uploadInventoryItem(type, data, name, description).then((invItemID: UUID) =>
|
||||
{
|
||||
this.agent.inventory.fetchInventoryItem(invItemID).then((item: InventoryItem | null) =>
|
||||
{
|
||||
if (item === null)
|
||||
{
|
||||
reject(new Error('Unable to get inventory item'));
|
||||
}
|
||||
else
|
||||
{
|
||||
resolve(item);
|
||||
}
|
||||
}).catch((err) =>
|
||||
{
|
||||
reject(err);
|
||||
});
|
||||
}).catch((err) =>
|
||||
{
|
||||
reject(err);
|
||||
});
|
||||
return ;
|
||||
}
|
||||
if (this.agent && this.agent.inventory && this.agent.inventory.main && this.agent.inventory.main.root)
|
||||
{
|
||||
this.currentRegion.caps.capsPostXML('NewFileAgentInventory', {
|
||||
'folder_id': new LLSD.UUID(this.agent.inventory.main.root.toString()),
|
||||
'asset_type': type,
|
||||
'inventory_type': Utils.HTTPAssetTypeToCapInventoryType(type),
|
||||
'name': name,
|
||||
'description': description,
|
||||
'everyone_mask': PermissionMask.All,
|
||||
'group_mask': PermissionMask.All,
|
||||
'next_owner_mask': PermissionMask.All,
|
||||
'expected_upload_cost': 0
|
||||
}).then((response: any) =>
|
||||
{
|
||||
if (response['state'] === 'upload')
|
||||
{
|
||||
const uploadURL = response['uploader'];
|
||||
this.currentRegion.caps.capsRequestUpload(uploadURL, data).then((responseUpload: any) =>
|
||||
{
|
||||
if (responseUpload['new_inventory_item'] !== undefined)
|
||||
{
|
||||
const invItemID = new UUID(responseUpload['new_inventory_item'].toString());
|
||||
this.agent.inventory.fetchInventoryItem(invItemID).then((item: InventoryItem | null) =>
|
||||
{
|
||||
if (item === null)
|
||||
{
|
||||
reject(new Error('Unable to get inventory item'));
|
||||
}
|
||||
else
|
||||
{
|
||||
resolve(item);
|
||||
}
|
||||
}).catch((err) =>
|
||||
{
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
}).catch((err) =>
|
||||
{
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
else if (response['error'])
|
||||
{
|
||||
reject(response['error']['message']);
|
||||
}
|
||||
else
|
||||
{
|
||||
reject('Unable to upload asset');
|
||||
}
|
||||
}).catch((err) =>
|
||||
{
|
||||
console.log('Got err');
|
||||
console.log(err);
|
||||
reject(err);
|
||||
})
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!this.agent)
|
||||
{
|
||||
throw(new Error('Missing agent'));
|
||||
}
|
||||
else if (!this.agent.inventory)
|
||||
{
|
||||
throw(new Error('Missing agent inventory'));
|
||||
}
|
||||
else if (!this.agent.inventory.main)
|
||||
{
|
||||
throw new Error('Missing agent inventory main skeleton');
|
||||
}
|
||||
else if (!this.agent.inventory.main.root)
|
||||
{
|
||||
throw new Error('Missing agent inventory main skeleton root');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import { MapInfoReplyEvent } from '../../events/MapInfoReplyEvent';
|
||||
import { PacketFlags } from '../../enums/PacketFlags';
|
||||
import { Vector2 } from '../Vector2';
|
||||
import { MapInfoRangeReplyEvent } from '../../events/MapInfoRangeReplyEvent';
|
||||
import { Avatar } from '../public/Avatar';
|
||||
import { AvatarQueryResult } from '../public/AvatarQueryResult';
|
||||
|
||||
export class GridCommands extends CommandsBase
|
||||
{
|
||||
@@ -389,9 +389,9 @@ export class GridCommands extends CommandsBase
|
||||
});
|
||||
}
|
||||
|
||||
avatarKey2Name(uuid: UUID | UUID[]): Promise<Avatar | Avatar[]>
|
||||
avatarKey2Name(uuid: UUID | UUID[]): Promise<AvatarQueryResult | AvatarQueryResult[]>
|
||||
{
|
||||
return new Promise<Avatar | Avatar[]>(async (resolve, reject) =>
|
||||
return new Promise<AvatarQueryResult | AvatarQueryResult[]>(async (resolve, reject) =>
|
||||
{
|
||||
const req = new UUIDNameRequestMessage();
|
||||
req.UUIDNameBlock = [];
|
||||
@@ -446,16 +446,16 @@ export class GridCommands extends CommandsBase
|
||||
if (!arr)
|
||||
{
|
||||
const result = waitingFor[uuid[0].toString()];
|
||||
const av = new Avatar(uuid[0], result.firstName, result.lastName);
|
||||
const av = new AvatarQueryResult(uuid[0], result.firstName, result.lastName);
|
||||
resolve(av);
|
||||
}
|
||||
else
|
||||
{
|
||||
const response: Avatar[] = [];
|
||||
const response: AvatarQueryResult[] = [];
|
||||
for (const k of uuid)
|
||||
{
|
||||
const result = waitingFor[k.toString()];
|
||||
const av = new Avatar(k, result.firstName, result.lastName);
|
||||
const av = new AvatarQueryResult(k, result.firstName, result.lastName);
|
||||
response.push(av);
|
||||
}
|
||||
resolve(response);
|
||||
|
||||
@@ -9,6 +9,7 @@ import { UUID } from '../UUID';
|
||||
import { Vector3 } from '../Vector3';
|
||||
import { PacketFlags } from '../../enums/PacketFlags';
|
||||
import { ChatSourceType } from '../../enums/ChatSourceType';
|
||||
import { InventoryItem } from '../InventoryItem';
|
||||
|
||||
export class InventoryCommands extends CommandsBase
|
||||
{
|
||||
@@ -54,6 +55,23 @@ export class InventoryCommands extends CommandsBase
|
||||
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)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user