- Bump to 0.5.13
- Add building support for TaskInventory and nested objects (from XML) - Add support for taking objects into inventory - Add waitForAppearanceSet utility - Add new event for when object is fully resolved (ObjectProperties received) - Fixed InventoryItem CRC method - Fixed quaternion bug - Support for uploading Script, Notecard and Gesture assets - Significantly improved build process
This commit is contained in:
@@ -5,6 +5,7 @@ lib/
|
||||
.idea/
|
||||
tools/
|
||||
example/
|
||||
exampleMine/
|
||||
docs/
|
||||
dist/tests/
|
||||
testing/
|
||||
|
||||
@@ -26,6 +26,7 @@ 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';
|
||||
|
||||
export class Agent
|
||||
{
|
||||
@@ -84,6 +85,9 @@ export class Agent
|
||||
};
|
||||
agentUpdateTimer: Timer | null = null;
|
||||
estateManager = false;
|
||||
appearanceSet = false;
|
||||
appearanceSetEvent: Subject<void> = new Subject<void>();
|
||||
|
||||
private clientEvents: ClientEvents;
|
||||
|
||||
constructor(clientEvents: ClientEvents)
|
||||
@@ -311,6 +315,9 @@ export class Agent
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
this.appearanceSet = true;
|
||||
this.appearanceSetEvent.next();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
21
lib/classes/AssetMap.ts
Normal file
21
lib/classes/AssetMap.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export class AssetMap
|
||||
{
|
||||
mesh: {
|
||||
[key: string]: {
|
||||
objectName: string,
|
||||
objectDescription: string,
|
||||
assetID: string
|
||||
}
|
||||
} = {};
|
||||
textures: { [key: string]: string } = {};
|
||||
animations: { [key: string]: string } = {};
|
||||
sounds: { [key: string]: string } = {};
|
||||
gestures: { [key: string]: string } = {};
|
||||
landmarks: { [key: string]: string } = {};
|
||||
callingcards: { [key: string]: string } = {};
|
||||
scripts: { [key: string]: string } = {};
|
||||
clothing: { [key: string]: string } = {};
|
||||
notecards: { [key: string]: string } = {};
|
||||
bodyparts: { [key: string]: string } = {};
|
||||
objects: { [key: string]: Buffer | null } = {};
|
||||
}
|
||||
15
lib/classes/BuildMap.ts
Normal file
15
lib/classes/BuildMap.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { AssetMap } from './AssetMap';
|
||||
import { GameObject } from './public/GameObject';
|
||||
import { Vector3 } from './Vector3';
|
||||
|
||||
export class BuildMap
|
||||
{
|
||||
public primsNeeded = 0;
|
||||
public primReservoir: GameObject[] = [];
|
||||
public rezLocation: Vector3 = Vector3.getZero();
|
||||
|
||||
constructor(public assetMap: AssetMap, public callback: (map: AssetMap) => void, public costOnly = false)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -484,7 +484,7 @@ export class Circuit
|
||||
{
|
||||
clearTimeout(this.receivedPackets[packet.sequenceNumber]);
|
||||
this.receivedPackets[packet.sequenceNumber] = setTimeout(this.expireReceivedPacket.bind(this, packet.sequenceNumber), 10000);
|
||||
console.log('Ignoring duplicate packet: ' + packet.message.name + ' sequenceID: ' + packet.sequenceNumber);
|
||||
this.sendAck(packet.sequenceNumber);
|
||||
return;
|
||||
}
|
||||
this.receivedPackets[packet.sequenceNumber] = setTimeout(this.expireReceivedPacket.bind(this, packet.sequenceNumber), 10000);
|
||||
|
||||
@@ -23,6 +23,7 @@ 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';
|
||||
|
||||
|
||||
export class ClientEvents
|
||||
@@ -52,4 +53,5 @@ export class ClientEvents
|
||||
onObjectUpdatedEvent: Subject<ObjectUpdatedEvent> = new Subject<ObjectUpdatedEvent>();
|
||||
onObjectKilledEvent: Subject<ObjectKilledEvent> = new Subject<ObjectKilledEvent>();
|
||||
onSelectedObjectEvent: Subject<SelectedObjectEvent> = new Subject<SelectedObjectEvent>();
|
||||
onObjectResolvedEvent: Subject<ObjectResolvedEvent> = new Subject<ObjectResolvedEvent>();
|
||||
}
|
||||
|
||||
@@ -141,7 +141,6 @@ export class InventoryFolder
|
||||
delete this.agent.inventory.itemsByID[itemID.toString()];
|
||||
this.items = this.items.filter((item) =>
|
||||
{
|
||||
console.log(item.itemID + ' vs ' + JSON.stringify(itemID));
|
||||
return !item.itemID.equals(itemID);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@ export class InventoryItem
|
||||
parentID: UUID;
|
||||
flags: InventoryItemFlags;
|
||||
itemID: UUID;
|
||||
oldItemID?: UUID;
|
||||
parentPartID?: UUID;
|
||||
permsGranter?: UUID;
|
||||
description: string;
|
||||
type: AssetType;
|
||||
permissions: {
|
||||
@@ -44,26 +47,24 @@ export class InventoryItem
|
||||
getCRC(): number
|
||||
{
|
||||
let crc = 0;
|
||||
crc += this.assetID.CRC();
|
||||
crc += this.parentID.CRC();
|
||||
crc += this.itemID.CRC();
|
||||
crc += this.permissions.creator.CRC();
|
||||
crc += this.permissions.owner.CRC();
|
||||
crc += this.permissions.group.CRC();
|
||||
crc += this.permissions.ownerMask;
|
||||
crc += this.permissions.nextOwnerMask;
|
||||
crc += this.permissions.everyoneMask;
|
||||
crc += this.permissions.groupMask;
|
||||
crc += this.flags;
|
||||
crc += this.inventoryType;
|
||||
crc += this.type;
|
||||
crc += Math.round(this.created.getTime() / 1000);
|
||||
crc += this.salePrice;
|
||||
crc += this.saleType * 0x07073096;
|
||||
while (crc > 4294967295)
|
||||
{
|
||||
crc -= 4294967295;
|
||||
}
|
||||
crc = crc + this.itemID.CRC() >>> 0;
|
||||
crc = crc + this.parentID.CRC() >>> 0;
|
||||
crc = crc + this.permissions.creator.CRC() >>> 0;
|
||||
crc = crc + this.permissions.owner.CRC() >>> 0;
|
||||
crc = crc + this.permissions.group.CRC() >>> 0;
|
||||
crc = crc + this.permissions.baseMask >>> 0;
|
||||
crc = crc + this.permissions.ownerMask >>> 0;
|
||||
crc = crc + this.permissions.everyoneMask >>> 0;
|
||||
crc = crc + this.permissions.groupMask >>> 0;
|
||||
|
||||
crc = crc + this.assetID.CRC() >>> 0;
|
||||
crc = crc + this.type >>> 0;
|
||||
crc = crc + this.inventoryType >>> 0;
|
||||
|
||||
crc = crc + this.flags >>> 0;
|
||||
crc = crc + this.salePrice >>> 0;
|
||||
crc = crc + (this.saleType * 0x07073096 >>> 0) >>> 0;
|
||||
crc = crc + Math.round(this.created.getTime() / 1000) >>> 0;
|
||||
return crc;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import { ObjectUpdatedEvent } from '../events/ObjectUpdatedEvent';
|
||||
import { CompressedFlags } from '../enums/CompressedFlags';
|
||||
import { Vector3 } from './Vector3';
|
||||
import { ObjectPhysicsDataEvent } from '../events/ObjectPhysicsDataEvent';
|
||||
import { ObjectResolvedEvent } from '../events/ObjectResolvedEvent';
|
||||
|
||||
export class ObjectStoreLite implements IObjectStore
|
||||
{
|
||||
@@ -207,8 +208,13 @@ export class ObjectStoreLite implements IObjectStore
|
||||
o.touchName = Utils.BufferToStringSimple(obj.TouchName);
|
||||
o.sitName = Utils.BufferToStringSimple(obj.SitName);
|
||||
o.textureID = Utils.BufferToStringSimple(obj.TextureID);
|
||||
o.resolvedAt = new Date().getTime() / 1000;
|
||||
|
||||
if (!o.resolvedAt)
|
||||
{
|
||||
o.resolvedAt = new Date().getTime() / 1000;
|
||||
const evt = new ObjectResolvedEvent();
|
||||
evt.object = o;
|
||||
this.clientEvents.onObjectResolvedEvent.next(evt);
|
||||
}
|
||||
if (o.Flags !== undefined)
|
||||
{
|
||||
if (o.Flags & PrimFlags.CreateSelected)
|
||||
@@ -419,6 +425,7 @@ export class ObjectStoreLite implements IObjectStore
|
||||
newObj.objectID = obj.FullID;
|
||||
newObj.object = obj;
|
||||
newObj.createSelected = obj.Flags !== undefined && (obj.Flags & PrimFlags.CreateSelected) !== 0;
|
||||
obj.createdSelected = newObj.createSelected;
|
||||
if (obj.Flags !== undefined && obj.Flags & PrimFlags.CreateSelected && !this.pendingObjectProperties[obj.FullID.toString()])
|
||||
{
|
||||
this.selectedPrimsWithoutUpdate[obj.ID] = true;
|
||||
|
||||
@@ -72,7 +72,7 @@ export class Quaternion extends quat
|
||||
this.x = buf.x;
|
||||
this.y = buf.y;
|
||||
this.z = buf.z;
|
||||
this.w = buf.z;
|
||||
this.w = buf.w;
|
||||
}
|
||||
else if (buf instanceof Quaternion)
|
||||
{
|
||||
|
||||
@@ -164,14 +164,11 @@ export class UUID
|
||||
|
||||
public CRC(): number
|
||||
{
|
||||
let retval = 0;
|
||||
const bytes: Buffer = this.getBuffer();
|
||||
|
||||
retval += ((bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0]);
|
||||
retval += ((bytes[7] << 24) + (bytes[6] << 16) + (bytes[5] << 8) + bytes[4]);
|
||||
retval += ((bytes[11] << 24) + (bytes[10] << 16) + (bytes[9] << 8) + bytes[8]);
|
||||
retval += ((bytes[15] << 24) + (bytes[14] << 16) + (bytes[13] << 8) + bytes[12]);
|
||||
|
||||
return retval;
|
||||
const crcOne = ((bytes[3] << 24 >>> 0) + (bytes[2] << 16 >>> 0) + (bytes[1] << 8 >>> 0) + bytes[0]);
|
||||
const crcTwo = ((bytes[7] << 24 >>> 0) + (bytes[6] << 16 >>> 0) + (bytes[5] << 8 >>> 0) + bytes[4]);
|
||||
const crcThree = ((bytes[11] << 24 >>> 0) + (bytes[10] << 16 >>> 0) + (bytes[9] << 8 >>> 0) + bytes[8]);
|
||||
const crcFour = ((bytes[15] << 24 >>> 0) + (bytes[14] << 16 >>> 0) + (bytes[13] << 8 >>> 0) + bytes[12]);
|
||||
return crcOne + crcTwo + crcThree + crcFour >>> 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ import { Quaternion } from './Quaternion';
|
||||
import { GlobalPosition } from './public/interfaces/GlobalPosition';
|
||||
import { HTTPAssets } from '../enums/HTTPAssets';
|
||||
import { Vector3 } from './Vector3';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
import { Subject } from 'rxjs';
|
||||
import { AssetType } from '../enums/AssetType';
|
||||
import { InventoryTypeLL } from '../enums/InventoryTypeLL';
|
||||
import Timeout = NodeJS.Timeout;
|
||||
|
||||
export class Utils
|
||||
@@ -132,7 +134,81 @@ export class Utils
|
||||
};
|
||||
}
|
||||
|
||||
static HTTPAssetTypeToInventoryType(HTTPAssetType: string)
|
||||
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;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static HTTPAssetTypeToInventoryType(HTTPAssetType: string): InventoryTypeLL
|
||||
{
|
||||
switch (HTTPAssetType)
|
||||
{
|
||||
case HTTPAssets.ASSET_TEXTURE:
|
||||
return InventoryTypeLL.texture;
|
||||
case HTTPAssets.ASSET_SOUND:
|
||||
return InventoryTypeLL.sound;
|
||||
case HTTPAssets.ASSET_ANIMATION:
|
||||
return InventoryTypeLL.animation;
|
||||
case HTTPAssets.ASSET_GESTURE:
|
||||
return InventoryTypeLL.gesture;
|
||||
case HTTPAssets.ASSET_LANDMARK:
|
||||
return InventoryTypeLL.landmark;
|
||||
case HTTPAssets.ASSET_CALLINGCARD:
|
||||
return InventoryTypeLL.callcard;
|
||||
case HTTPAssets.ASSET_SCRIPT:
|
||||
return InventoryTypeLL.script;
|
||||
case HTTPAssets.ASSET_CLOTHING:
|
||||
return InventoryTypeLL.wearable;
|
||||
case HTTPAssets.ASSET_OBJECT:
|
||||
return InventoryTypeLL.object;
|
||||
case HTTPAssets.ASSET_NOTECARD:
|
||||
return InventoryTypeLL.notecard;
|
||||
case HTTPAssets.ASSET_LSL_TEXT:
|
||||
return InventoryTypeLL.script;
|
||||
case HTTPAssets.ASSET_LSL_BYTECODE:
|
||||
return InventoryTypeLL.script;
|
||||
case HTTPAssets.ASSET_BODYPART:
|
||||
return InventoryTypeLL.wearable;
|
||||
case HTTPAssets.ASSET_MESH:
|
||||
return InventoryTypeLL.mesh;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static HTTPAssetTypeToCapInventoryType(HTTPAssetType: string): String
|
||||
{
|
||||
switch (HTTPAssetType)
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ import { FilterResponse } from '../../enums/FilterResponse';
|
||||
import { AvatarPropertiesReplyMessage } from '../messages/AvatarPropertiesReply';
|
||||
import { AvatarPropertiesRequestMessage } from '../messages/AvatarPropertiesRequest';
|
||||
import { AvatarPropertiesReplyEvent } from '../../events/AvatarPropertiesReplyEvent';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
export class AgentCommands extends CommandsBase
|
||||
{
|
||||
@@ -69,6 +70,64 @@ export class AgentCommands extends CommandsBase
|
||||
this.agent.sendAgentUpdate();
|
||||
}
|
||||
|
||||
waitForAppearanceSet(timeout: number = 10000): Promise<void>
|
||||
{
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
if (this.agent.appearanceSet)
|
||||
{
|
||||
resolve();
|
||||
}
|
||||
else
|
||||
{
|
||||
let appearanceSubscription: Subscription | undefined;
|
||||
let timeoutTimer: number | undefined;
|
||||
appearanceSubscription = this.agent.appearanceSetEvent.subscribe(() =>
|
||||
{
|
||||
if (timeoutTimer !== undefined)
|
||||
{
|
||||
clearTimeout(timeoutTimer);
|
||||
timeoutTimer = undefined;
|
||||
}
|
||||
if (appearanceSubscription !== undefined)
|
||||
{
|
||||
appearanceSubscription.unsubscribe();
|
||||
appearanceSubscription = undefined;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
timeoutTimer = setTimeout(() =>
|
||||
{
|
||||
if (appearanceSubscription !== undefined)
|
||||
{
|
||||
appearanceSubscription.unsubscribe();
|
||||
appearanceSubscription = undefined;
|
||||
}
|
||||
if (timeoutTimer !== undefined)
|
||||
{
|
||||
clearTimeout(timeoutTimer);
|
||||
timeoutTimer = undefined;
|
||||
reject(new Error('Timeout'));
|
||||
}
|
||||
}, timeout) as any as number;
|
||||
if (this.agent.appearanceSet)
|
||||
{
|
||||
if (appearanceSubscription !== undefined)
|
||||
{
|
||||
appearanceSubscription.unsubscribe();
|
||||
appearanceSubscription = undefined;
|
||||
}
|
||||
if (timeoutTimer !== undefined)
|
||||
{
|
||||
clearTimeout(timeoutTimer);
|
||||
timeoutTimer = undefined;
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getAvatarProperties(avatarID: UUID | string): Promise<AvatarPropertiesReplyEvent>
|
||||
{
|
||||
if (typeof avatarID === 'string')
|
||||
|
||||
@@ -21,9 +21,15 @@ import { Material } from '../public/Material';
|
||||
import { LLMesh } from '../public/LLMesh';
|
||||
import { FolderType } from '../../enums/FolderType';
|
||||
import { HTTPAssets } from '../../enums/HTTPAssets';
|
||||
import { InventoryItem } from '../InventoryItem';
|
||||
import { CreateInventoryItemMessage } from '../messages/CreateInventoryItem';
|
||||
import { WearableType } from '../../enums/WearableType';
|
||||
import { UpdateCreateInventoryItemMessage } from '../messages/UpdateCreateInventoryItem';
|
||||
import { FilterResponse } from '../../enums/FilterResponse';
|
||||
|
||||
export class AssetCommands extends CommandsBase
|
||||
{
|
||||
private callbackID: number = 1;
|
||||
async downloadAsset(type: HTTPAssets, uuid: UUID | string): Promise<Buffer>
|
||||
{
|
||||
if (typeof uuid === 'string')
|
||||
@@ -400,8 +406,8 @@ export class AssetCommands extends CommandsBase
|
||||
'metric': 'MUT_Unspecified'
|
||||
};
|
||||
const uploadMap = {
|
||||
'name': name,
|
||||
'description': description,
|
||||
'name': String(name),
|
||||
'description': String(description),
|
||||
'asset_resources': assetResources,
|
||||
'asset_type': 'mesh',
|
||||
'inventory_type': 'object',
|
||||
@@ -411,7 +417,15 @@ export class AssetCommands extends CommandsBase
|
||||
'group_mask': PermissionMask.All,
|
||||
'next_owner_mask': PermissionMask.All
|
||||
};
|
||||
const result = await this.currentRegion.caps.capsPostXML('NewFileAgentInventory', uploadMap);
|
||||
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'];
|
||||
@@ -445,16 +459,204 @@ export class AssetCommands extends CommandsBase
|
||||
}
|
||||
}
|
||||
|
||||
uploadAsset(type: HTTPAssets, data: Buffer, name: string, description: string): Promise<UUID>
|
||||
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.HTTPAssetTypeToInventoryType(type),
|
||||
'inventory_type': Utils.HTTPAssetTypeToCapInventoryType(type),
|
||||
'name': name,
|
||||
'description': description,
|
||||
'everyone_mask': PermissionMask.All,
|
||||
@@ -468,7 +670,24 @@ export class AssetCommands extends CommandsBase
|
||||
const uploadURL = response['uploader'];
|
||||
this.currentRegion.caps.capsRequestUpload(uploadURL, data).then((responseUpload: any) =>
|
||||
{
|
||||
resolve(new UUID(responseUpload['new_asset'].toString()));
|
||||
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);
|
||||
|
||||
@@ -21,8 +21,6 @@ import { ObjectAddMessage } from '../messages/ObjectAdd';
|
||||
import { Quaternion } from '../Quaternion';
|
||||
import { RezObjectMessage } from '../messages/RezObject';
|
||||
import { PermissionMask } from '../../enums/PermissionMask';
|
||||
import { SelectedObjectEvent } from '../../events/SelectedObjectEvent';
|
||||
import Timer = NodeJS.Timer;
|
||||
import { PacketFlags } from '../../enums/PacketFlags';
|
||||
import { GameObject } from '../public/GameObject';
|
||||
import { PCode } from '../../enums/PCode';
|
||||
@@ -35,12 +33,19 @@ import { Parcel } from '../public/Parcel';
|
||||
import * as Long from 'long';
|
||||
import * as micromatch from 'micromatch';
|
||||
import * as LLSD from '@caspertech/llsd';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
import { SculptType } from '../..';
|
||||
import { ObjectResolvedEvent } from '../../events/ObjectResolvedEvent';
|
||||
import { AssetMap } from '../AssetMap';
|
||||
import { InventoryType } from '../../enums/InventoryType';
|
||||
import { BuildMap } from '../BuildMap';
|
||||
import Timer = NodeJS.Timer;
|
||||
import Timeout = NodeJS.Timeout;
|
||||
import { ObjectUpdatedEvent } from '../..';
|
||||
|
||||
export class RegionCommands extends CommandsBase
|
||||
{
|
||||
private resolveQueue: {[key: number]: GameObject} = {};
|
||||
|
||||
async getRegionHandle(regionID: UUID): Promise<Long>
|
||||
{
|
||||
const circuit = this.currentRegion.circuit;
|
||||
@@ -372,6 +377,102 @@ export class RegionCommands extends CommandsBase
|
||||
return this.currentRegion.regionName;
|
||||
}
|
||||
|
||||
private waitForObjectResolve(localID: number)
|
||||
{
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
let timeout: Timeout | undefined = undefined;
|
||||
let subs: Subscription | undefined = undefined;
|
||||
try
|
||||
{
|
||||
const ourObject = this.currentRegion.objects.getObjectByLocalID(localID);
|
||||
if (ourObject.resolvedAt)
|
||||
{
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (ignore)
|
||||
{
|
||||
|
||||
}
|
||||
subs = this.currentRegion.clientEvents.onObjectResolvedEvent.subscribe((evt: ObjectResolvedEvent) =>
|
||||
{
|
||||
if (evt.object.ID === localID)
|
||||
{
|
||||
if (timeout !== undefined)
|
||||
{
|
||||
clearTimeout(timeout);
|
||||
timeout = undefined;
|
||||
}
|
||||
if (subs !== undefined)
|
||||
{
|
||||
subs.unsubscribe();
|
||||
subs = undefined;
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
timeout = setTimeout(() =>
|
||||
{
|
||||
if (timeout !== undefined)
|
||||
{
|
||||
clearTimeout(timeout);
|
||||
timeout = undefined;
|
||||
}
|
||||
if (subs !== undefined)
|
||||
{
|
||||
subs.unsubscribe();
|
||||
subs = undefined;
|
||||
}
|
||||
const object = this.currentRegion.objects.getObjectByLocalID(localID);
|
||||
if (object.resolvedAt)
|
||||
{
|
||||
try
|
||||
{
|
||||
const ourObject = this.currentRegion.objects.getObjectByLocalID(localID);
|
||||
if (ourObject.resolvedAt)
|
||||
{
|
||||
console.warn('Resolve timed out but object ' + localID + ' HAS been resolved!');
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (ignore)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
reject(new Error('Timeout'));
|
||||
}, 10000);
|
||||
});
|
||||
}
|
||||
|
||||
private async queueResolveObject(object: GameObject, skipInventory = false)
|
||||
{
|
||||
if (object.resolvedAt)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (this.resolveQueue[object.ID] === undefined)
|
||||
{
|
||||
this.resolveQueue[object.ID] = object;
|
||||
try
|
||||
{
|
||||
await this.resolveObjects([object], true, true);
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.error('Failed to resolve ' + object.ID);
|
||||
}
|
||||
delete this.resolveQueue[object.ID];
|
||||
}
|
||||
else
|
||||
{
|
||||
return this.waitForObjectResolve(object.ID);
|
||||
}
|
||||
}
|
||||
|
||||
private async resolveObjects(objects: GameObject[], onlyUnresolved: boolean = false, skipInventory = false)
|
||||
{
|
||||
// First, create a map of all object IDs
|
||||
@@ -468,7 +569,6 @@ export class RegionCommands extends CommandsBase
|
||||
const o = objs[ky];
|
||||
if ((o.resolveAttempts === undefined || o.resolveAttempts < 3) && o.FullID !== undefined && o.name !== undefined && o.Flags !== undefined && !(o.Flags & PrimFlags.InventoryEmpty) && (!o.inventory || o.inventory.length === 0))
|
||||
{
|
||||
console.log(' ... Downloading task inventory for object ' + o.FullID.toString() + ' (' + o.name + '), done ' + count + ' of ' + objectSet.length);
|
||||
const req = new RequestTaskInventoryMessage();
|
||||
req.AgentData = {
|
||||
AgentID: this.agent.agentID,
|
||||
@@ -886,237 +986,608 @@ export class RegionCommands extends CommandsBase
|
||||
});
|
||||
}
|
||||
|
||||
private async createPrimWithRetry(retries: number, obj: GameObject, posOffset: Vector3, rotOffset: Quaternion, inventoryID?: UUID)
|
||||
private async buildPart(obj: GameObject, posOffset: Vector3, rotOffset: Quaternion, buildMap: BuildMap)
|
||||
{
|
||||
for (retries--; retries > -1; retries--)
|
||||
{
|
||||
try
|
||||
{
|
||||
const newObject = await this.createPrim(obj, new Vector3(posOffset), new Quaternion(rotOffset), inventoryID);
|
||||
return newObject;
|
||||
}
|
||||
catch (ignore)
|
||||
{
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
throw new Error('Failed to create prim with ' + retries + ' tries');
|
||||
}
|
||||
// Calculate geometry
|
||||
const objectPosition = new Vector3(obj.Position);
|
||||
const objectRotation = new Quaternion(obj.Rotation);
|
||||
const objectScale = new Vector3(obj.Scale);
|
||||
|
||||
private async buildPart(obj: GameObject, posOffset: Vector3, rotOffset: Quaternion, meshCallback: (object: GameObject, meshData: UUID) => UUID | null)
|
||||
{
|
||||
// Rez a prim
|
||||
let newObject: GameObject;
|
||||
if (obj.extraParams !== undefined && obj.extraParams.meshData !== null)
|
||||
let finalPos = Vector3.getZero();
|
||||
let finalRot = Quaternion.getIdentity();
|
||||
if (posOffset.x === 0.0 && posOffset.y === 0.0 && posOffset.z === 0.0 && objectPosition !== undefined)
|
||||
{
|
||||
const inventoryID: UUID | null = await meshCallback(obj, obj.extraParams.meshData.meshData);
|
||||
if (inventoryID !== null)
|
||||
{
|
||||
newObject = await this.createPrimWithRetry(3, obj, posOffset, rotOffset, inventoryID);
|
||||
}
|
||||
else
|
||||
{
|
||||
newObject = await this.createPrimWithRetry(3, obj, posOffset, rotOffset);
|
||||
}
|
||||
finalPos = new Vector3(objectPosition);
|
||||
finalRot = new Quaternion(objectRotation);
|
||||
}
|
||||
else
|
||||
{
|
||||
newObject = await this.createPrimWithRetry(3, obj, posOffset, rotOffset);
|
||||
const adjustedPos = new Vector3(objectPosition).multiplyByQuat(new Quaternion(rotOffset));
|
||||
finalPos = new Vector3(new Vector3(posOffset).add(adjustedPos));
|
||||
|
||||
const baseRot = new Quaternion(rotOffset);
|
||||
finalRot = new Quaternion(baseRot.multiply(new Quaternion(objectRotation)));
|
||||
}
|
||||
await newObject.setExtraParams(obj.extraParams);
|
||||
|
||||
// Is this a mesh part?
|
||||
let object: GameObject | null = null;
|
||||
if (obj.extraParams !== undefined && obj.extraParams.meshData !== null)
|
||||
{
|
||||
if (buildMap.assetMap.mesh[obj.extraParams.meshData.meshData.toString()] !== undefined)
|
||||
{
|
||||
const meshEntry = buildMap.assetMap.mesh[obj.extraParams.meshData.meshData.toString()];
|
||||
const rezLocation = new Vector3(buildMap.rezLocation);
|
||||
rezLocation.z += (objectScale.z / 2);
|
||||
|
||||
object = await this.rezFromInventory(obj, rezLocation, new UUID(meshEntry.assetID));
|
||||
}
|
||||
}
|
||||
else if (buildMap.primReservoir.length > 0)
|
||||
{
|
||||
const newPrim = buildMap.primReservoir.shift();
|
||||
if (newPrim !== undefined)
|
||||
{
|
||||
object = newPrim;
|
||||
await object.setShape(
|
||||
obj.PathCurve,
|
||||
obj.ProfileCurve,
|
||||
obj.PathBegin,
|
||||
obj.PathEnd,
|
||||
obj.PathScaleX,
|
||||
obj.PathScaleY,
|
||||
obj.PathShearX,
|
||||
obj.PathShearY,
|
||||
obj.PathTwist,
|
||||
obj.PathTwistBegin,
|
||||
obj.PathRadiusOffset,
|
||||
obj.PathTaperX,
|
||||
obj.PathTaperY,
|
||||
obj.PathRevolutions,
|
||||
obj.PathSkew,
|
||||
obj.ProfileBegin,
|
||||
obj.ProfileEnd,
|
||||
obj.ProfileHollow
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (object === null)
|
||||
{
|
||||
throw new Error('Failed to acquire prim for build');
|
||||
}
|
||||
|
||||
await object.setGeometry(finalPos, finalRot, objectScale);
|
||||
|
||||
if (obj.extraParams.sculptData !== null)
|
||||
{
|
||||
if (obj.extraParams.sculptData.type !== SculptType.Mesh)
|
||||
{
|
||||
const oldTextureID = obj.extraParams.sculptData.texture.toString();
|
||||
if (buildMap.assetMap.textures[oldTextureID] !== undefined)
|
||||
{
|
||||
obj.extraParams.sculptData.texture = new UUID(buildMap.assetMap.textures[oldTextureID]);
|
||||
}
|
||||
}
|
||||
}
|
||||
await object.setExtraParams(obj.extraParams);
|
||||
|
||||
if (obj.TextureEntry !== undefined)
|
||||
{
|
||||
await newObject.setTextureEntry(obj.TextureEntry);
|
||||
if (obj.TextureEntry.defaultTexture !== null)
|
||||
{
|
||||
const oldTextureID = obj.TextureEntry.defaultTexture.textureID.toString();
|
||||
if (buildMap.assetMap.textures[oldTextureID] !== undefined)
|
||||
{
|
||||
obj.TextureEntry.defaultTexture.textureID = new UUID(buildMap.assetMap.textures[oldTextureID]);
|
||||
}
|
||||
}
|
||||
for (const j of obj.TextureEntry.faces)
|
||||
{
|
||||
const oldTextureID = j.textureID.toString();
|
||||
if (buildMap.assetMap.textures[oldTextureID] !== undefined)
|
||||
{
|
||||
j.textureID = new UUID(buildMap.assetMap.textures[oldTextureID]);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await object.setTextureEntry(obj.TextureEntry);
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.name !== undefined)
|
||||
{
|
||||
await newObject.setName(obj.name);
|
||||
await object.setName(obj.name);
|
||||
}
|
||||
|
||||
if (obj.description !== undefined)
|
||||
{
|
||||
await newObject.setDescription(obj.description);
|
||||
await object.setDescription(obj.description);
|
||||
}
|
||||
return newObject;
|
||||
}
|
||||
|
||||
buildObject(obj: GameObject, meshCallback: (object: GameObject, meshData: UUID) => UUID | null): Promise<GameObject>
|
||||
{
|
||||
return new Promise<GameObject>(async (resolve, reject) =>
|
||||
for (const invItem of obj.inventory)
|
||||
{
|
||||
const parts: (Promise<GameObject>)[] = [];
|
||||
console.log('Rezzing prims');
|
||||
parts.push(this.buildPart(obj, Vector3.getZero(), Quaternion.getIdentity(), meshCallback));
|
||||
|
||||
if (obj.children)
|
||||
try
|
||||
{
|
||||
if (obj.Position === undefined)
|
||||
switch (invItem.inventoryType)
|
||||
{
|
||||
obj.Position = Vector3.getZero();
|
||||
}
|
||||
if (obj.Rotation === undefined)
|
||||
{
|
||||
obj.Rotation = Quaternion.getIdentity();
|
||||
}
|
||||
for (const child of obj.children)
|
||||
{
|
||||
if (child.Position !== undefined && child.Rotation !== undefined)
|
||||
case InventoryType.Clothing:
|
||||
{
|
||||
const objPos = new Vector3(obj.Position);
|
||||
const objRot = new Quaternion(obj.Rotation);
|
||||
parts.push(this.buildPart(child, objPos, objRot, meshCallback));
|
||||
if (buildMap.assetMap.clothing[invItem.assetID.toString()] !== undefined)
|
||||
{
|
||||
const invItemID = buildMap.assetMap.clothing[invItem.assetID.toString()];
|
||||
await object.dropInventoryIntoContents(new UUID(invItemID));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InventoryType.Bodypart:
|
||||
{
|
||||
if (buildMap.assetMap.bodyparts[invItem.assetID.toString()] !== undefined)
|
||||
{
|
||||
const invItemID = buildMap.assetMap.bodyparts[invItem.assetID.toString()];
|
||||
await object.dropInventoryIntoContents(new UUID(invItemID));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InventoryType.Notecard:
|
||||
{
|
||||
if (buildMap.assetMap.notecards[invItem.assetID.toString()] !== undefined)
|
||||
{
|
||||
const invItemID = buildMap.assetMap.notecards[invItem.assetID.toString()];
|
||||
await object.dropInventoryIntoContents(new UUID(invItemID));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InventoryType.Sound:
|
||||
{
|
||||
if (buildMap.assetMap.sounds[invItem.assetID.toString()] !== undefined)
|
||||
{
|
||||
const invItemID = buildMap.assetMap.sounds[invItem.assetID.toString()];
|
||||
await object.dropInventoryIntoContents(new UUID(invItemID));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InventoryType.Gesture:
|
||||
{
|
||||
if (buildMap.assetMap.gestures[invItem.assetID.toString()] !== undefined)
|
||||
{
|
||||
const invItemID = buildMap.assetMap.gestures[invItem.assetID.toString()];
|
||||
await object.dropInventoryIntoContents(new UUID(invItemID));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InventoryType.Landmark:
|
||||
{
|
||||
if (buildMap.assetMap.landmarks[invItem.assetID.toString()] !== undefined)
|
||||
{
|
||||
const invItemID = buildMap.assetMap.landmarks[invItem.assetID.toString()];
|
||||
await object.dropInventoryIntoContents(new UUID(invItemID));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InventoryType.LSL:
|
||||
{
|
||||
if (buildMap.assetMap.scripts[invItem.assetID.toString()] !== undefined)
|
||||
{
|
||||
const invItemID = buildMap.assetMap.scripts[invItem.assetID.toString()];
|
||||
await object.dropInventoryIntoContents(new UUID(invItemID));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InventoryType.Animation:
|
||||
{
|
||||
if (buildMap.assetMap.animations[invItem.assetID.toString()] !== undefined)
|
||||
{
|
||||
const invItemID = buildMap.assetMap.animations[invItem.assetID.toString()];
|
||||
await object.dropInventoryIntoContents(new UUID(invItemID));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Promise.all(parts).then(async (results) =>
|
||||
catch (error)
|
||||
{
|
||||
console.log('Linking prims');
|
||||
const rootObj = results[0];
|
||||
const childPrims: GameObject[] = [];
|
||||
for (const childObject of results)
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Do nested objects last
|
||||
for (const invItem of obj.inventory)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (invItem.inventoryType)
|
||||
{
|
||||
if (childObject !== rootObj)
|
||||
case InventoryType.Object:
|
||||
{
|
||||
childPrims.push(childObject);
|
||||
if (buildMap.assetMap.objects[invItem.assetID.toString()] !== undefined)
|
||||
{
|
||||
const objectXML = buildMap.assetMap.objects[invItem.assetID.toString()];
|
||||
if (objectXML !== null)
|
||||
{
|
||||
const taskObjectXML = await GameObject.fromXML(objectXML.toString('utf-8'));
|
||||
const taskObject = await this.buildObjectNew(taskObjectXML, buildMap.callback, buildMap.costOnly);
|
||||
if (taskObject !== null)
|
||||
{
|
||||
const invItemUUID = await taskObject.takeToInventory();
|
||||
await object.dropInventoryIntoContents(invItemUUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
await rootObj.linkFrom(childPrims);
|
||||
console.log('All done');
|
||||
resolve(rootObj);
|
||||
}).catch((err) =>
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
reject(err);
|
||||
});
|
||||
/*
|
||||
Utils.promiseConcurrent<GameObject>(parts, 1000, 10000).then(async (results) =>
|
||||
{
|
||||
console.log('Linking prims');
|
||||
const rootObj = results.results[0];
|
||||
await rootObj.linkFrom(results.results);
|
||||
console.log('All done');
|
||||
resolve(rootObj);
|
||||
}).catch((err) =>
|
||||
{
|
||||
reject(err);
|
||||
});
|
||||
*/
|
||||
});
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
createPrim(obj: GameObject, posOffset: Vector3, rotOffset: Quaternion, inventoryID?: UUID): Promise<GameObject>
|
||||
private gatherAssets(obj: GameObject, buildMap: BuildMap)
|
||||
{
|
||||
return new Promise(async (resolve, reject) =>
|
||||
if (obj.extraParams !== undefined)
|
||||
{
|
||||
const timeRequested = (new Date().getTime() / 1000) - this.currentRegion.timeOffset;
|
||||
|
||||
const objectPosition = new Vector3(obj.Position);
|
||||
const objectRotation = new Quaternion(obj.Rotation);
|
||||
const objectScale = new Vector3(obj.Scale);
|
||||
|
||||
let finalPos = Vector3.getZero();
|
||||
let finalRot = Quaternion.getIdentity();
|
||||
if (posOffset.x === 0.0 && posOffset.y === 0.0 && posOffset.z === 0.0 && objectPosition !== undefined)
|
||||
if (obj.extraParams.meshData !== null)
|
||||
{
|
||||
finalPos = new Vector3(objectPosition);
|
||||
finalRot = new Quaternion(objectRotation);
|
||||
buildMap.assetMap.mesh[obj.extraParams.meshData.meshData.toString()] = {
|
||||
objectName: obj.name || 'Object',
|
||||
objectDescription: obj.description || '(no description)',
|
||||
assetID: obj.extraParams.meshData.meshData.toString()
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
const adjustedPos = new Vector3(new Vector3(objectPosition).multiplyByQuat(new Quaternion(rotOffset).inverse()));
|
||||
finalPos = new Vector3(new Vector3(adjustedPos).add(new Vector3(posOffset)));
|
||||
finalRot = new Quaternion(new Quaternion(objectRotation).add(new Quaternion(rotOffset)));
|
||||
buildMap.primsNeeded++;
|
||||
}
|
||||
let msg: ObjectAddMessage | RezObjectMessage | null = null;
|
||||
let fromInventory = false;
|
||||
if (inventoryID === undefined || this.agent.inventory.itemsByID[inventoryID.toString()] === undefined)
|
||||
if (obj.extraParams.sculptData !== null)
|
||||
{
|
||||
// First, rez object in scene
|
||||
msg = new ObjectAddMessage();
|
||||
if (obj.extraParams.sculptData.type !== SculptType.Mesh)
|
||||
{
|
||||
buildMap.assetMap.textures[obj.extraParams.sculptData.texture.toString()] = obj.extraParams.sculptData.texture.toString();
|
||||
}
|
||||
}
|
||||
if (obj.TextureEntry !== undefined)
|
||||
{
|
||||
for (const j of obj.TextureEntry.faces)
|
||||
{
|
||||
const textureID = j.textureID;
|
||||
buildMap.assetMap.textures[textureID.toString()] = textureID.toString();
|
||||
}
|
||||
if (obj.TextureEntry.defaultTexture !== null)
|
||||
{
|
||||
const textureID = obj.TextureEntry.defaultTexture.textureID;
|
||||
buildMap.assetMap.textures[textureID.toString()] = textureID.toString();
|
||||
}
|
||||
}
|
||||
if (obj.inventory !== undefined)
|
||||
{
|
||||
for (const j of obj.inventory)
|
||||
{
|
||||
switch (j.inventoryType)
|
||||
{
|
||||
case InventoryType.Animation:
|
||||
{
|
||||
buildMap.assetMap.animations[j.assetID.toString()] = j.assetID.toString();
|
||||
break;
|
||||
}
|
||||
case InventoryType.Bodypart:
|
||||
{
|
||||
buildMap.assetMap.bodyparts[j.assetID.toString()] = j.assetID.toString();
|
||||
break;
|
||||
}
|
||||
case InventoryType.CallingCard:
|
||||
{
|
||||
buildMap.assetMap.callingcards[j.assetID.toString()] = j.assetID.toString();
|
||||
break;
|
||||
}
|
||||
case InventoryType.Clothing:
|
||||
{
|
||||
buildMap.assetMap.clothing[j.assetID.toString()] = j.assetID.toString();
|
||||
break;
|
||||
}
|
||||
case InventoryType.Gesture:
|
||||
{
|
||||
buildMap.assetMap.gestures[j.assetID.toString()] = j.assetID.toString();
|
||||
break;
|
||||
}
|
||||
case InventoryType.Landmark:
|
||||
{
|
||||
buildMap.assetMap.landmarks[j.assetID.toString()] = j.assetID.toString();
|
||||
break;
|
||||
}
|
||||
case InventoryType.LSL:
|
||||
{
|
||||
buildMap.assetMap.scripts[j.assetID.toString()] = j.assetID.toString();
|
||||
break;
|
||||
}
|
||||
case InventoryType.Snapshot:
|
||||
{
|
||||
buildMap.assetMap.textures[j.assetID.toString()] = j.assetID.toString();
|
||||
break;
|
||||
}
|
||||
case InventoryType.Notecard:
|
||||
{
|
||||
buildMap.assetMap.notecards[j.assetID.toString()] = j.assetID.toString();
|
||||
break;
|
||||
}
|
||||
case InventoryType.Sound:
|
||||
{
|
||||
buildMap.assetMap.sounds[j.assetID.toString()] = j.assetID.toString();
|
||||
break;
|
||||
}
|
||||
case InventoryType.Object:
|
||||
{
|
||||
buildMap.assetMap.objects[j.assetID.toString()] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (obj.children)
|
||||
{
|
||||
for (const child of obj.children)
|
||||
{
|
||||
this.gatherAssets(child, buildMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async buildObjectNew(obj: GameObject, callback: (map: AssetMap) => void, costOnly: boolean = false): Promise<GameObject | null>
|
||||
{
|
||||
const map: AssetMap = new AssetMap();
|
||||
const buildMap = new BuildMap(map, callback, costOnly);
|
||||
this.gatherAssets(obj, buildMap);
|
||||
await callback(map);
|
||||
|
||||
if (costOnly)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
let agentPos = new Vector3([128, 128, 2048]);
|
||||
try
|
||||
{
|
||||
const agentLocalID = this.currentRegion.agent.localID;
|
||||
const agentObject = this.currentRegion.objects.getObjectByLocalID(agentLocalID);
|
||||
if (agentObject.Position !== undefined)
|
||||
{
|
||||
agentPos = new Vector3(agentObject.Position);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Error('Agent position is undefined');
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.warn('Unable to find avatar location, rezzing at ' + agentPos.toString());
|
||||
}
|
||||
agentPos.z += 2.0;
|
||||
buildMap.rezLocation = agentPos;
|
||||
// Set camera above target location for fast acquisition
|
||||
const campos = new Vector3(agentPos);
|
||||
campos.z += 2.0;
|
||||
await this.currentRegion.clientCommands.agent.setCamera(campos, agentPos, 10, new Vector3([-1.0, 0, 0]), new Vector3([0.0, 1.0, 0]));
|
||||
|
||||
if (buildMap.primsNeeded > 0)
|
||||
{
|
||||
buildMap.primReservoir = await this.createPrims(buildMap.primsNeeded, agentPos);
|
||||
}
|
||||
|
||||
const parts = [];
|
||||
parts.push(this.buildPart(obj, Vector3.getZero(), Quaternion.getIdentity(), buildMap));
|
||||
|
||||
if (obj.children)
|
||||
{
|
||||
if (obj.Position === undefined)
|
||||
{
|
||||
obj.Position = Vector3.getZero();
|
||||
}
|
||||
if (obj.Rotation === undefined)
|
||||
{
|
||||
obj.Rotation = Quaternion.getIdentity();
|
||||
}
|
||||
for (const child of obj.children)
|
||||
{
|
||||
if (child.Position !== undefined && child.Rotation !== undefined)
|
||||
{
|
||||
const objPos = new Vector3(obj.Position);
|
||||
const objRot = new Quaternion(obj.Rotation);
|
||||
parts.push(this.buildPart(child, objPos, objRot, buildMap));
|
||||
}
|
||||
}
|
||||
}
|
||||
const results: GameObject[] = await Promise.all(parts);
|
||||
|
||||
const rootObj = results[0];
|
||||
const childPrims: GameObject[] = [];
|
||||
for (const childObject of results)
|
||||
{
|
||||
if (childObject !== rootObj)
|
||||
{
|
||||
childPrims.push(childObject);
|
||||
}
|
||||
}
|
||||
await rootObj.linkFrom(childPrims);
|
||||
return rootObj;
|
||||
}
|
||||
|
||||
private createPrims(count: number, position: Vector3)
|
||||
{
|
||||
return new Promise<GameObject[]>((resolve, reject) =>
|
||||
{
|
||||
const gatheredPrims: GameObject[] = [];
|
||||
let objSub: Subscription | undefined = undefined;
|
||||
let timeout: Timeout | undefined = setTimeout(() =>
|
||||
{
|
||||
if (objSub !== undefined)
|
||||
{
|
||||
objSub.unsubscribe();
|
||||
objSub = undefined;
|
||||
}
|
||||
if (timeout !== undefined)
|
||||
{
|
||||
clearTimeout(timeout);
|
||||
timeout = undefined;
|
||||
}
|
||||
reject(new Error('Failed to gather ' + count + ' prims - only gathered ' + gatheredPrims.length));
|
||||
}, 30000);
|
||||
objSub = this.currentRegion.clientEvents.onNewObjectEvent.subscribe(async (evt: NewObjectEvent) =>
|
||||
{
|
||||
if (!evt.object.resolvedAt)
|
||||
{
|
||||
// We need to get the full ObjectProperties so we can be sure this is or isn't a rez from inventory
|
||||
await this.queueResolveObject(evt.object, true);
|
||||
}
|
||||
if (evt.createSelected && !evt.object.claimedForBuild)
|
||||
{
|
||||
if (evt.object.itemID === undefined || evt.object.itemID.equals(UUID.zero()))
|
||||
{
|
||||
if (
|
||||
evt.object.PCode === PCode.Prim &&
|
||||
evt.object.Material === 3 &&
|
||||
evt.object.PathCurve === 16 &&
|
||||
evt.object.ProfileCurve === 1 &&
|
||||
evt.object.PathBegin === 0 &&
|
||||
evt.object.PathEnd === 1 &&
|
||||
evt.object.PathScaleX === 1 &&
|
||||
evt.object.PathScaleY === 1 &&
|
||||
evt.object.PathShearX === 0 &&
|
||||
evt.object.PathShearY === 0 &&
|
||||
evt.object.PathTwist === 0 &&
|
||||
evt.object.PathTwistBegin === 0 &&
|
||||
evt.object.PathRadiusOffset === 0 &&
|
||||
evt.object.PathTaperX === 0 &&
|
||||
evt.object.PathTaperY === 0 &&
|
||||
evt.object.PathRevolutions === 1 &&
|
||||
evt.object.PathSkew === 0 &&
|
||||
evt.object.ProfileBegin === 0 &&
|
||||
evt.object.ProfileHollow === 0
|
||||
)
|
||||
{
|
||||
evt.object.claimedForBuild = true;
|
||||
gatheredPrims.push(evt.object);
|
||||
if (gatheredPrims.length === count)
|
||||
{
|
||||
if (objSub !== undefined)
|
||||
{
|
||||
objSub.unsubscribe();
|
||||
objSub = undefined;
|
||||
}
|
||||
if (timeout !== undefined)
|
||||
{
|
||||
clearTimeout(timeout);
|
||||
timeout = undefined;
|
||||
}
|
||||
resolve(gatheredPrims);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (let x = 0; x < count; x++)
|
||||
{
|
||||
const msg = new ObjectAddMessage();
|
||||
msg.AgentData = {
|
||||
AgentID: this.agent.agentID,
|
||||
SessionID: this.circuit.sessionID,
|
||||
GroupID: UUID.zero()
|
||||
};
|
||||
msg.ObjectData = {
|
||||
PCode: Utils.numberOrZero(obj.PCode),
|
||||
Material: Utils.numberOrZero(obj.Material),
|
||||
PCode: PCode.Prim,
|
||||
Material: 3,
|
||||
AddFlags: PrimFlags.CreateSelected,
|
||||
PathCurve: Utils.numberOrZero(obj.PathCurve),
|
||||
ProfileCurve: Utils.numberOrZero(obj.ProfileCurve),
|
||||
PathBegin: Utils.packBeginCut(Utils.numberOrZero(obj.PathBegin)),
|
||||
PathEnd: Utils.packEndCut(Utils.numberOrZero(obj.PathEnd)),
|
||||
PathScaleX: Utils.packPathScale(Utils.numberOrZero(obj.PathScaleX)),
|
||||
PathScaleY: Utils.packPathScale(Utils.numberOrZero(obj.PathScaleY)),
|
||||
PathShearX: Utils.packPathShear(Utils.numberOrZero(obj.PathShearX)),
|
||||
PathShearY: Utils.packPathShear(Utils.numberOrZero(obj.PathShearY)),
|
||||
PathTwist: Utils.packPathTwist(Utils.numberOrZero(obj.PathTwist)),
|
||||
PathTwistBegin: Utils.packPathTwist(Utils.numberOrZero(obj.PathTwistBegin)),
|
||||
PathRadiusOffset: Utils.packPathTwist(Utils.numberOrZero(obj.PathRadiusOffset)),
|
||||
PathTaperX: Utils.packPathTaper(Utils.numberOrZero(obj.PathTaperX)),
|
||||
PathTaperY: Utils.packPathTaper(Utils.numberOrZero(obj.PathTaperY)),
|
||||
PathRevolutions: Utils.packPathRevolutions(Utils.numberOrZero(obj.PathRevolutions)),
|
||||
PathSkew: Utils.packPathTwist(Utils.numberOrZero(obj.PathSkew)),
|
||||
ProfileBegin: Utils.packBeginCut(Utils.numberOrZero(obj.ProfileBegin)),
|
||||
ProfileEnd: Utils.packEndCut(Utils.numberOrZero(obj.ProfileEnd)),
|
||||
ProfileHollow: Utils.packProfileHollow(Utils.numberOrZero(obj.ProfileHollow)),
|
||||
PathCurve: 16,
|
||||
ProfileCurve: 1,
|
||||
PathBegin: 0,
|
||||
PathEnd: 0,
|
||||
PathScaleX: 100,
|
||||
PathScaleY: 100,
|
||||
PathShearX: 0,
|
||||
PathShearY: 0,
|
||||
PathTwist: 0,
|
||||
PathTwistBegin: 0,
|
||||
PathRadiusOffset: 0,
|
||||
PathTaperX: 0,
|
||||
PathTaperY: 0,
|
||||
PathRevolutions: 0,
|
||||
PathSkew: 0,
|
||||
ProfileBegin: 0,
|
||||
ProfileEnd: 0,
|
||||
ProfileHollow: 0,
|
||||
BypassRaycast: 1,
|
||||
RayStart: finalPos,
|
||||
RayEnd: finalPos,
|
||||
RayStart: position,
|
||||
RayEnd: position,
|
||||
RayTargetID: UUID.zero(),
|
||||
RayEndIsIntersection: 0,
|
||||
Scale: Utils.vector3OrZero(obj.Scale),
|
||||
Rotation: finalRot,
|
||||
State: Utils.numberOrZero(obj.State)
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
fromInventory = true;
|
||||
const invItem = this.agent.inventory.itemsByID[inventoryID.toString()];
|
||||
const queryID = UUID.random();
|
||||
msg = new RezObjectMessage();
|
||||
msg.AgentData = {
|
||||
AgentID: this.agent.agentID,
|
||||
SessionID: this.circuit.sessionID,
|
||||
GroupID: UUID.zero()
|
||||
};
|
||||
msg.RezData = {
|
||||
FromTaskID: UUID.zero(),
|
||||
BypassRaycast: 1,
|
||||
RayStart: finalPos,
|
||||
RayEnd: finalPos,
|
||||
RayTargetID: UUID.zero(),
|
||||
RayEndIsIntersection: false,
|
||||
RezSelected: true,
|
||||
RemoveItem: false,
|
||||
ItemFlags: invItem.flags,
|
||||
GroupMask: PermissionMask.All,
|
||||
EveryoneMask: PermissionMask.All,
|
||||
NextOwnerMask: PermissionMask.All,
|
||||
};
|
||||
msg.InventoryData = {
|
||||
ItemID: invItem.itemID,
|
||||
FolderID: invItem.parentID,
|
||||
CreatorID: invItem.permissions.creator,
|
||||
OwnerID: invItem.permissions.owner,
|
||||
GroupID: invItem.permissions.group,
|
||||
BaseMask: invItem.permissions.baseMask,
|
||||
OwnerMask: invItem.permissions.ownerMask,
|
||||
GroupMask: invItem.permissions.groupMask,
|
||||
EveryoneMask: invItem.permissions.everyoneMask,
|
||||
NextOwnerMask: invItem.permissions.nextOwnerMask,
|
||||
GroupOwned: false,
|
||||
TransactionID: queryID,
|
||||
Type: invItem.type,
|
||||
InvType: invItem.inventoryType,
|
||||
Flags: invItem.flags,
|
||||
SaleType: invItem.saleType,
|
||||
SalePrice: invItem.salePrice,
|
||||
Name: Utils.StringToBuffer(invItem.name),
|
||||
Description: Utils.StringToBuffer(invItem.description),
|
||||
CreationDate: Math.round(invItem.created.getTime() / 1000),
|
||||
CRC: 0,
|
||||
Scale: new Vector3([0.5, 0.5, 0.5]),
|
||||
Rotation: Quaternion.getIdentity(),
|
||||
State: 0
|
||||
};
|
||||
this.circuit.sendMessage(msg, PacketFlags.Reliable);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
rezFromInventory(obj: GameObject, position: Vector3, inventoryID: UUID): Promise<GameObject>
|
||||
{
|
||||
return new Promise(async (resolve, reject) =>
|
||||
{
|
||||
const invItem = this.agent.inventory.itemsByID[inventoryID.toString()];
|
||||
const queryID = UUID.random();
|
||||
const msg = new RezObjectMessage();
|
||||
msg.AgentData = {
|
||||
AgentID: this.agent.agentID,
|
||||
SessionID: this.circuit.sessionID,
|
||||
GroupID: UUID.zero()
|
||||
};
|
||||
msg.RezData = {
|
||||
FromTaskID: UUID.zero(),
|
||||
BypassRaycast: 1,
|
||||
RayStart: position,
|
||||
RayEnd: position,
|
||||
RayTargetID: UUID.zero(),
|
||||
RayEndIsIntersection: false,
|
||||
RezSelected: true,
|
||||
RemoveItem: false,
|
||||
ItemFlags: invItem.flags,
|
||||
GroupMask: PermissionMask.All,
|
||||
EveryoneMask: PermissionMask.All,
|
||||
NextOwnerMask: PermissionMask.All,
|
||||
};
|
||||
msg.InventoryData = {
|
||||
ItemID: invItem.itemID,
|
||||
FolderID: invItem.parentID,
|
||||
CreatorID: invItem.permissions.creator,
|
||||
OwnerID: invItem.permissions.owner,
|
||||
GroupID: invItem.permissions.group,
|
||||
BaseMask: invItem.permissions.baseMask,
|
||||
OwnerMask: invItem.permissions.ownerMask,
|
||||
GroupMask: invItem.permissions.groupMask,
|
||||
EveryoneMask: invItem.permissions.everyoneMask,
|
||||
NextOwnerMask: invItem.permissions.nextOwnerMask,
|
||||
GroupOwned: false,
|
||||
TransactionID: queryID,
|
||||
Type: invItem.type,
|
||||
InvType: invItem.inventoryType,
|
||||
Flags: invItem.flags,
|
||||
SaleType: invItem.saleType,
|
||||
SalePrice: invItem.salePrice,
|
||||
Name: Utils.StringToBuffer(invItem.name),
|
||||
Description: Utils.StringToBuffer(invItem.description),
|
||||
CreationDate: Math.round(invItem.created.getTime() / 1000),
|
||||
CRC: 0,
|
||||
};
|
||||
|
||||
let objSub: Subscription | undefined = undefined;
|
||||
let timeout: Timeout | undefined = setTimeout(() =>
|
||||
{
|
||||
@@ -1132,11 +1603,17 @@ export class RegionCommands extends CommandsBase
|
||||
}
|
||||
reject(new Error('Prim never arrived'));
|
||||
}, 10000);
|
||||
let claimedPrim = false;
|
||||
objSub = this.currentRegion.clientEvents.onNewObjectEvent.subscribe(async (evt: NewObjectEvent) =>
|
||||
{
|
||||
if (evt.createSelected && !evt.object.claimedForBuild)
|
||||
if (evt.createSelected && !evt.object.resolvedAt)
|
||||
{
|
||||
if (!fromInventory || (inventoryID !== undefined && evt.object.itemID.equals(inventoryID)))
|
||||
// We need to get the full ObjectProperties so we can be sure this is or isn't a rez from inventory
|
||||
await this.queueResolveObject(evt.object, true);
|
||||
}
|
||||
if (evt.createSelected && !evt.object.claimedForBuild && !claimedPrim)
|
||||
{
|
||||
if (inventoryID !== undefined && evt.object.itemID !== undefined && evt.object.itemID.equals(inventoryID))
|
||||
{
|
||||
if (objSub !== undefined)
|
||||
{
|
||||
@@ -1149,36 +1626,20 @@ export class RegionCommands extends CommandsBase
|
||||
timeout = undefined;
|
||||
}
|
||||
evt.object.claimedForBuild = true;
|
||||
|
||||
if (!fromInventory)
|
||||
{
|
||||
await evt.object.setShape(obj.PathCurve, obj.ProfileCurve, obj.PathBegin, obj.PathEnd, obj.PathScaleX, obj.PathScaleY, obj.PathShearX, obj.PathShearY, obj.PathTwist, obj.PathTwistBegin, obj.PathRadiusOffset, obj.PathTaperX, obj.PathTaperY, obj.PathRevolutions, obj.PathSkew, obj.ProfileBegin, obj.ProfileEnd, obj.ProfileHollow);
|
||||
}
|
||||
|
||||
|
||||
// Set the object's position properly
|
||||
try
|
||||
{
|
||||
await evt.object.setGeometry(finalPos, finalRot, obj.Scale);
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.log('Failed to set object position :/');
|
||||
}
|
||||
claimedPrim = true;
|
||||
resolve(evt.object);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Move the camera to look directly at prim for faster capture
|
||||
const campos = new Vector3(finalPos);
|
||||
campos.z += 128.0 + objectScale.z;
|
||||
await this.currentRegion.clientCommands.agent.setCamera(campos, finalPos, 4096, new Vector3([-1.0, 0, 0]), new Vector3([0.0, 1.0, 0]));
|
||||
|
||||
if (msg !== null)
|
||||
if (obj.Scale !== undefined)
|
||||
{
|
||||
this.circuit.sendMessage(msg, PacketFlags.Reliable);
|
||||
const camLocation = new Vector3(position);
|
||||
camLocation.z += (obj.Scale.z / 2) + 1;
|
||||
await this.currentRegion.clientCommands.agent.setCamera(camLocation, position, obj.Scale.z, new Vector3([-1.0, 0, 0]), new Vector3([0.0, 1.0, 0]));
|
||||
}
|
||||
this.circuit.sendMessage(msg, PacketFlags.Reliable);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1357,7 +1818,6 @@ export class RegionCommands extends CommandsBase
|
||||
{
|
||||
await checkObjects(uuids, objects);
|
||||
}
|
||||
console.log('Found ' + Object.keys(stillAlive).length + ' objects still present out of ' + checkList.length + ' objects');
|
||||
const deadObjects: GameObject[] = [];
|
||||
for (const o of checkList)
|
||||
{
|
||||
|
||||
@@ -25,14 +25,10 @@ export class MapBlockReplyMessage implements MessageBase
|
||||
Agents: number;
|
||||
MapImageID: UUID;
|
||||
}[];
|
||||
Size: {
|
||||
SizeX: number;
|
||||
SizeY: number;
|
||||
}[];
|
||||
|
||||
getSize(): number
|
||||
{
|
||||
return this.calculateVarVarSize(this.Data, 'Name', 1) + ((27) * this.Data.length) + ((4) * this.Size.length) + 22;
|
||||
return this.calculateVarVarSize(this.Data, 'Name', 1) + ((27) * this.Data.length) + 21;
|
||||
}
|
||||
|
||||
calculateVarVarSize(block: object[], paramName: string, extraPerVar: number): number
|
||||
@@ -52,7 +48,7 @@ export class MapBlockReplyMessage implements MessageBase
|
||||
pos += 16;
|
||||
buf.writeUInt32LE(this.AgentData['Flags'], pos);
|
||||
pos += 4;
|
||||
let count = this.Data.length;
|
||||
const count = this.Data.length;
|
||||
buf.writeUInt8(this.Data.length, pos++);
|
||||
for (let i = 0; i < count; i++)
|
||||
{
|
||||
@@ -71,15 +67,6 @@ export class MapBlockReplyMessage implements MessageBase
|
||||
this.Data[i]['MapImageID'].writeToBuffer(buf, pos);
|
||||
pos += 16;
|
||||
}
|
||||
count = this.Size.length;
|
||||
buf.writeUInt8(this.Size.length, pos++);
|
||||
for (let i = 0; i < count; i++)
|
||||
{
|
||||
buf.writeUInt16LE(this.Size[i]['SizeX'], pos);
|
||||
pos += 2;
|
||||
buf.writeUInt16LE(this.Size[i]['SizeY'], pos);
|
||||
pos += 2;
|
||||
}
|
||||
return pos - startPos;
|
||||
}
|
||||
|
||||
@@ -103,7 +90,7 @@ export class MapBlockReplyMessage implements MessageBase
|
||||
{
|
||||
return pos - startPos;
|
||||
}
|
||||
let count = buf.readUInt8(pos++);
|
||||
const count = buf.readUInt8(pos++);
|
||||
this.Data = [];
|
||||
for (let i = 0; i < count; i++)
|
||||
{
|
||||
@@ -142,27 +129,6 @@ export class MapBlockReplyMessage implements MessageBase
|
||||
pos += 16;
|
||||
this.Data.push(newObjData);
|
||||
}
|
||||
if (pos >= buf.length)
|
||||
{
|
||||
return pos - startPos;
|
||||
}
|
||||
count = buf.readUInt8(pos++);
|
||||
this.Size = [];
|
||||
for (let i = 0; i < count; i++)
|
||||
{
|
||||
const newObjSize: {
|
||||
SizeX: number,
|
||||
SizeY: number
|
||||
} = {
|
||||
SizeX: 0,
|
||||
SizeY: 0
|
||||
};
|
||||
newObjSize['SizeX'] = buf.readUInt16LE(pos);
|
||||
pos += 2;
|
||||
newObjSize['SizeY'] = buf.readUInt16LE(pos);
|
||||
pos += 2;
|
||||
this.Size.push(newObjSize);
|
||||
}
|
||||
return pos - startPos;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,15 @@ import { HTTPAssets } from '../../enums/HTTPAssets';
|
||||
import { PhysicsShapeType } from '../../enums/PhysicsShapeType';
|
||||
import { PCode } from '../../enums/PCode';
|
||||
import { SoundFlags } from '../../enums/SoundFlags';
|
||||
import { DeRezObjectMessage } from '../messages/DeRezObject';
|
||||
import { DeRezDestination } from '../../enums/DeRezDestination';
|
||||
import { Message } from '../../enums/Message';
|
||||
import { UpdateCreateInventoryItemMessage } from '../messages/UpdateCreateInventoryItem';
|
||||
import { FilterResponse } from '../../enums/FilterResponse';
|
||||
import { UpdateTaskInventoryMessage } from '../messages/UpdateTaskInventory';
|
||||
import { ObjectPropertiesMessage } from '../messages/ObjectProperties';
|
||||
import { ObjectSelectMessage } from '../messages/ObjectSelect';
|
||||
import { ObjectDeselectMessage } from '../messages/ObjectDeselect';
|
||||
|
||||
export class GameObject implements IGameObjectData
|
||||
{
|
||||
@@ -149,6 +158,7 @@ export class GameObject implements IGameObjectData
|
||||
resolveAttempts = 0;
|
||||
|
||||
claimedForBuild = false;
|
||||
createdSelected = false;
|
||||
|
||||
private static getFromXMLJS(obj: any, param: string): any
|
||||
{
|
||||
@@ -569,7 +579,98 @@ export class GameObject implements IGameObjectData
|
||||
go.extraParams = ExtraParams.from(buf);
|
||||
}
|
||||
}
|
||||
// TODO: TaskInventory
|
||||
if ((prop = this.getFromXMLJS(obj, 'TaskInventory')) !== undefined)
|
||||
{
|
||||
if (prop.TaskInventoryItem)
|
||||
{
|
||||
for (const invItemXML of prop.TaskInventoryItem)
|
||||
{
|
||||
const invItem = new InventoryItem();
|
||||
let subProp: any;
|
||||
if ((subProp = UUID.fromXMLJS(invItemXML, 'AssetID')) !== undefined)
|
||||
{
|
||||
invItem.assetID = subProp;
|
||||
}
|
||||
if ((subProp = this.getFromXMLJS(invItemXML, 'BasePermissions')) !== undefined)
|
||||
{
|
||||
invItem.permissions.baseMask = subProp;
|
||||
}
|
||||
if ((subProp = this.getFromXMLJS(invItemXML, 'EveryonePermissions')) !== undefined)
|
||||
{
|
||||
invItem.permissions.everyoneMask = subProp;
|
||||
}
|
||||
if ((subProp = this.getFromXMLJS(invItemXML, 'GroupPermissions')) !== undefined)
|
||||
{
|
||||
invItem.permissions.groupMask = subProp;
|
||||
}
|
||||
if ((subProp = this.getFromXMLJS(invItemXML, 'NextPermissions')) !== undefined)
|
||||
{
|
||||
invItem.permissions.nextOwnerMask = subProp;
|
||||
}
|
||||
if ((subProp = this.getFromXMLJS(invItemXML, 'CurrentPermissions')) !== undefined)
|
||||
{
|
||||
invItem.permissions.ownerMask = subProp;
|
||||
}
|
||||
if ((subProp = this.getFromXMLJS(invItemXML, 'CreationDate')) !== undefined)
|
||||
{
|
||||
invItem.created = new Date(parseInt(subProp, 10) * 1000);
|
||||
}
|
||||
if ((subProp = UUID.fromXMLJS(invItemXML, 'CreatorID')) !== undefined)
|
||||
{
|
||||
invItem.permissions.creator = subProp;
|
||||
}
|
||||
if ((subProp = this.getFromXMLJS(invItemXML, 'Description')) !== undefined)
|
||||
{
|
||||
invItem.description = subProp;
|
||||
}
|
||||
if ((subProp = this.getFromXMLJS(invItemXML, 'Flags')) !== undefined)
|
||||
{
|
||||
invItem.flags = subProp;
|
||||
}
|
||||
if ((subProp = UUID.fromXMLJS(invItemXML, 'GroupID')) !== undefined)
|
||||
{
|
||||
invItem.permissions.group = subProp;
|
||||
}
|
||||
if ((subProp = this.getFromXMLJS(invItemXML, 'InvType')) !== undefined)
|
||||
{
|
||||
invItem.inventoryType = subProp;
|
||||
}
|
||||
if ((subProp = UUID.fromXMLJS(invItemXML, 'ItemID')) !== undefined)
|
||||
{
|
||||
invItem.itemID = subProp;
|
||||
}
|
||||
if ((subProp = UUID.fromXMLJS(invItemXML, 'OldItemID')) !== undefined)
|
||||
{
|
||||
invItem.oldItemID = subProp;
|
||||
}
|
||||
if ((subProp = UUID.fromXMLJS(invItemXML, 'LastOwnerID')) !== undefined)
|
||||
{
|
||||
invItem.permissions.lastOwner = subProp;
|
||||
}
|
||||
if ((subProp = this.getFromXMLJS(invItemXML, 'Name')) !== undefined)
|
||||
{
|
||||
invItem.name = subProp;
|
||||
}
|
||||
if ((subProp = UUID.fromXMLJS(invItemXML, 'OwnerID')) !== undefined)
|
||||
{
|
||||
invItem.permissions.owner = subProp;
|
||||
}
|
||||
if ((subProp = UUID.fromXMLJS(invItemXML, 'ParentID')) !== undefined)
|
||||
{
|
||||
invItem.parentID = subProp;
|
||||
}
|
||||
if ((subProp = UUID.fromXMLJS(invItemXML, 'ParentPartID')) !== undefined)
|
||||
{
|
||||
invItem.parentPartID = subProp;
|
||||
}
|
||||
if ((subProp = UUID.fromXMLJS(invItemXML, 'PermsGranter')) !== undefined)
|
||||
{
|
||||
invItem.permsGranter = subProp;
|
||||
}
|
||||
go.inventory.push(invItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
return go;
|
||||
}
|
||||
|
||||
@@ -590,11 +691,22 @@ export class GameObject implements IGameObjectData
|
||||
throw new Error('SceneObjectGroup not found');
|
||||
}
|
||||
result = result['SceneObjectGroup'];
|
||||
if (!result['SceneObjectPart'])
|
||||
|
||||
let rootPartXML;
|
||||
if (result['SceneObjectPart'])
|
||||
{
|
||||
rootPartXML = result['SceneObjectPart'];
|
||||
}
|
||||
else if (result['RootPart'] && result['RootPart'][0] && result['RootPart'][0]['SceneObjectPart'])
|
||||
{
|
||||
rootPartXML = result['RootPart'][0]['SceneObjectPart'];
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Error('Root part not found');
|
||||
}
|
||||
const rootPart = GameObject.partFromXMLJS(result['SceneObjectPart'][0], true);
|
||||
|
||||
const rootPart = GameObject.partFromXMLJS(rootPartXML[0], true);
|
||||
rootPart.children = [];
|
||||
rootPart.totalChildren = 0;
|
||||
if (result['OtherParts'] && Array.isArray(result['OtherParts']) && result['OtherParts'].length > 0)
|
||||
@@ -749,49 +861,6 @@ export class GameObject implements IGameObjectData
|
||||
await this.region.circuit.waitForAck(this.region.circuit.sendMessage(msg, PacketFlags.Reliable), 10000);
|
||||
}
|
||||
|
||||
private compareParam(name: string, param1: number | undefined, param2: number | undefined): boolean
|
||||
{
|
||||
if (param1 === undefined)
|
||||
{
|
||||
param1 = 0;
|
||||
}
|
||||
if (param2 === undefined)
|
||||
{
|
||||
param2 = 0;
|
||||
}
|
||||
if (Math.abs(param1 - param2) < 0.0001)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log('Failed ' + name + ' - ' + param1 + ' vs ' + param2);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
compareShape(obj: GameObject): boolean
|
||||
{
|
||||
return this.compareParam('PathCurve', this.PathCurve, obj.PathCurve) &&
|
||||
this.compareParam('ProfileCurve', this.ProfileCurve, obj.ProfileCurve) &&
|
||||
this.compareParam('PathBegin', this.PathBegin, obj.PathBegin) &&
|
||||
this.compareParam('PathEnd', this.PathEnd, obj.PathEnd) &&
|
||||
this.compareParam('PathScaleX', this.PathScaleX, obj.PathScaleX) &&
|
||||
this.compareParam('PathScaleY', this.PathScaleY, obj.PathScaleY) &&
|
||||
this.compareParam('PathShearX', this.PathShearX, obj.PathShearX) &&
|
||||
this.compareParam('PathShearY', this.PathShearY, obj.PathShearY) &&
|
||||
this.compareParam('PathTwist', this.PathTwist, obj.PathTwist) &&
|
||||
this.compareParam('PathTwistBegin', this.PathTwistBegin, obj.PathTwistBegin) &&
|
||||
this.compareParam('PathRadiusOffset', this.PathRadiusOffset, obj.PathRadiusOffset) &&
|
||||
this.compareParam('PathTaperX', this.PathTaperX, obj.PathTaperX) &&
|
||||
this.compareParam('PathTaperY', this.PathTaperY, obj.PathTaperY) &&
|
||||
this.compareParam('PathRevolutions', this.PathRevolutions, obj.PathRevolutions) &&
|
||||
this.compareParam('PathSkew', this.PathSkew, obj.PathSkew) &&
|
||||
this.compareParam('ProfileBegin', this.ProfileBegin, obj.ProfileBegin) &&
|
||||
this.compareParam('ProfileEnd', this.ProfileEnd, obj.ProfileEnd) &&
|
||||
this.compareParam('PRofileHollow', this.ProfileHollow, obj.ProfileHollow);
|
||||
}
|
||||
|
||||
async setGeometry(pos?: Vector3, rot?: Quaternion, scale?: Vector3)
|
||||
{
|
||||
const data = [];
|
||||
@@ -1455,4 +1524,162 @@ export class GameObject implements IGameObjectData
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async deRezObject(destination: DeRezDestination, transactionID: UUID, destFolder: UUID): Promise<void>
|
||||
{
|
||||
const msg = new DeRezObjectMessage();
|
||||
|
||||
msg.AgentData = {
|
||||
AgentID: this.region.agent.agentID,
|
||||
SessionID: this.region.circuit.sessionID
|
||||
};
|
||||
msg.AgentBlock = {
|
||||
GroupID: UUID.zero(),
|
||||
Destination: destination,
|
||||
DestinationID: destFolder,
|
||||
TransactionID: transactionID,
|
||||
PacketCount: 1,
|
||||
PacketNumber: 1
|
||||
};
|
||||
msg.ObjectData = [{
|
||||
ObjectLocalID: this.ID
|
||||
}];
|
||||
const ack = this.region.circuit.sendMessage(msg, PacketFlags.Reliable);
|
||||
return this.region.circuit.waitForAck(ack, 10000);
|
||||
}
|
||||
|
||||
takeToInventory(): Promise<UUID>
|
||||
{
|
||||
const transactionID = UUID.random();
|
||||
const rootFolder = this.region.agent.inventory.getRootFolderMain();
|
||||
return new Promise<UUID>((resolve, reject) =>
|
||||
{
|
||||
|
||||
this.region.circuit.waitForMessage<UpdateCreateInventoryItemMessage>(Message.UpdateCreateInventoryItem, 10000, (message: UpdateCreateInventoryItemMessage) =>
|
||||
{
|
||||
if (Utils.BufferToStringSimple(message.InventoryData[0].Name, 0) === this.name)
|
||||
{
|
||||
return FilterResponse.Finish;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FilterResponse.NoMatch;
|
||||
}
|
||||
}).then((createInventoryMsg: UpdateCreateInventoryItemMessage) =>
|
||||
{
|
||||
resolve(createInventoryMsg.InventoryData[0].ItemID);
|
||||
}).catch(() =>
|
||||
{
|
||||
reject(new Error('Timed out waiting for UpdateCreateInventoryItem'));
|
||||
});
|
||||
this.deRezObject(DeRezDestination.AgentInventoryTake, transactionID, rootFolder.folderID).then(() => {}).catch((err) =>
|
||||
{
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
dropInventoryIntoContents(inventoryID: UUID): Promise<void>
|
||||
{
|
||||
return new Promise<void>(async (resolve, reject) =>
|
||||
{
|
||||
const transactionID = UUID.random();
|
||||
const item: InventoryItem | null = await this.region.agent.inventory.fetchInventoryItem(inventoryID);
|
||||
if (item === null)
|
||||
{
|
||||
reject(new Error('Failed to drop inventory into object contents - Inventory item ' + inventoryID.toString() + ' not found'));
|
||||
return;
|
||||
}
|
||||
|
||||
const msg = new UpdateTaskInventoryMessage();
|
||||
msg.AgentData = {
|
||||
AgentID: this.region.agent.agentID,
|
||||
SessionID: this.region.circuit.sessionID
|
||||
};
|
||||
msg.UpdateData = {
|
||||
Key: 0,
|
||||
LocalID: this.ID
|
||||
};
|
||||
msg.InventoryData = {
|
||||
ItemID: item.itemID,
|
||||
FolderID: item.parentID,
|
||||
CreatorID: item.permissions.creator,
|
||||
OwnerID: item.permissions.owner,
|
||||
GroupID: item.permissions.group,
|
||||
BaseMask: item.permissions.baseMask,
|
||||
OwnerMask: item.permissions.ownerMask,
|
||||
GroupMask: item.permissions.groupMask,
|
||||
EveryoneMask: item.permissions.everyoneMask,
|
||||
NextOwnerMask: item.permissions.nextOwnerMask,
|
||||
GroupOwned: item.permissions.groupOwned || false,
|
||||
TransactionID: transactionID,
|
||||
Type: item.type,
|
||||
InvType: item.inventoryType,
|
||||
Flags: item.flags,
|
||||
SaleType: item.saleType,
|
||||
SalePrice: item.salePrice,
|
||||
Name: Utils.StringToBuffer(item.name),
|
||||
Description: Utils.StringToBuffer(item.description),
|
||||
CreationDate: item.created.getTime() / 1000,
|
||||
CRC: item.getCRC()
|
||||
};
|
||||
|
||||
const inventorySerial = this.inventorySerial;
|
||||
this.region.circuit.waitForMessage<ObjectPropertiesMessage>(Message.ObjectProperties, 10000, (message: ObjectPropertiesMessage) =>
|
||||
{
|
||||
const n = 5;
|
||||
for (const obj of message.ObjectData)
|
||||
{
|
||||
if (obj.ObjectID.equals(this.FullID))
|
||||
{
|
||||
if (obj.InventorySerial > inventorySerial)
|
||||
{
|
||||
return FilterResponse.Finish;
|
||||
}
|
||||
}
|
||||
}
|
||||
return FilterResponse.NoMatch;
|
||||
}).then((message: ObjectPropertiesMessage) =>
|
||||
{
|
||||
this.deselect().then(() => {}).catch(() => {});
|
||||
resolve();
|
||||
}).catch(() =>
|
||||
{
|
||||
reject(new Error('Timed out waiting for task inventory drop'));
|
||||
});
|
||||
|
||||
// We need to select the object or we won't get the objectProperties message
|
||||
await this.select();
|
||||
|
||||
this.region.circuit.sendMessage(msg, PacketFlags.Reliable)
|
||||
});
|
||||
}
|
||||
|
||||
async select()
|
||||
{
|
||||
const selectObject = new ObjectSelectMessage();
|
||||
selectObject.AgentData = {
|
||||
AgentID: this.region.agent.agentID,
|
||||
SessionID: this.region.circuit.sessionID
|
||||
};
|
||||
selectObject.ObjectData = [{
|
||||
ObjectLocalID: this.ID
|
||||
}];
|
||||
const ack = this.region.circuit.sendMessage(selectObject, PacketFlags.Reliable);
|
||||
await this.region.circuit.waitForAck(ack, 10000);
|
||||
}
|
||||
|
||||
async deselect()
|
||||
{
|
||||
const deselectObject = new ObjectDeselectMessage();
|
||||
deselectObject.AgentData = {
|
||||
AgentID: this.region.agent.agentID,
|
||||
SessionID: this.region.circuit.sessionID
|
||||
};
|
||||
deselectObject.ObjectData = [{
|
||||
ObjectLocalID: this.ID
|
||||
}];
|
||||
const ack = this.region.circuit.sendMessage(deselectObject, PacketFlags.Reliable);
|
||||
await this.region.circuit.waitForAck(ack, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
14
lib/enums/DeRezDestination.ts
Normal file
14
lib/enums/DeRezDestination.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export enum DeRezDestination
|
||||
{
|
||||
AgentInventorySave = 0,
|
||||
AgentInventoryCopy = 1,
|
||||
TaskInventory = 2,
|
||||
Attachment = 3,
|
||||
AgentInventoryTake = 4,
|
||||
ForceToGodInventory = 5,
|
||||
TrashFolder = 6,
|
||||
AttachmentToInventory = 7,
|
||||
AttachmentExists = 8,
|
||||
ReturnToOwner = 9,
|
||||
ReturnToLastOwner = 10
|
||||
}
|
||||
6
lib/events/ObjectResolvedEvent.ts
Normal file
6
lib/events/ObjectResolvedEvent.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { GameObject } from '../classes/public/GameObject';
|
||||
|
||||
export class ObjectResolvedEvent
|
||||
{
|
||||
object: GameObject
|
||||
}
|
||||
@@ -94,6 +94,7 @@ import { ParticleSystem } from './classes/ParticleSystem';
|
||||
import { ExtraParams } from './classes/public/ExtraParams';
|
||||
import { LLMesh } from './classes/public/LLMesh';
|
||||
import { FolderType } from './enums/FolderType';
|
||||
import { InventoryItem } from './classes/InventoryItem';
|
||||
|
||||
export {
|
||||
Bot,
|
||||
@@ -193,6 +194,7 @@ export {
|
||||
SculptData,
|
||||
MeshData,
|
||||
LLMesh,
|
||||
InventoryItem,
|
||||
|
||||
// Public Interfaces
|
||||
GlobalPosition,
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@caspertech/node-metaverse",
|
||||
"version": "0.5.12",
|
||||
"version": "0.5.13",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@caspertech/node-metaverse",
|
||||
"version": "0.5.12",
|
||||
"version": "0.5.13",
|
||||
"description": "A node.js interface for Second Life.",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -25039,23 +25039,6 @@
|
||||
"size": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Size",
|
||||
"type": "Variable",
|
||||
"count": 1,
|
||||
"params": [
|
||||
{
|
||||
"name": "SizeX",
|
||||
"type": "U16",
|
||||
"size": 1
|
||||
},
|
||||
{
|
||||
"name": "SizeY",
|
||||
"type": "U16",
|
||||
"size": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -8766,11 +8766,6 @@ version 2.0
|
||||
{ Agents U8 }
|
||||
{ MapImageID LLUUID }
|
||||
}
|
||||
{
|
||||
Size Variable
|
||||
{ SizeX U16 }
|
||||
{ SizeY U16 }
|
||||
}
|
||||
}
|
||||
|
||||
// viewer -> sim
|
||||
|
||||
Reference in New Issue
Block a user