1937 lines
74 KiB
TypeScript
1937 lines
74 KiB
TypeScript
import * as LLSD from '@caspertech/llsd';
|
|
|
|
import * as Long from 'long';
|
|
import * as micromatch from 'micromatch';
|
|
import { Subscription } from 'rxjs';
|
|
import { SculptType } from '../../enums/SculptType';
|
|
import { FilterResponse } from '../../enums/FilterResponse';
|
|
import { InventoryType } from '../../enums/InventoryType';
|
|
import { Message } from '../../enums/Message';
|
|
import { PacketFlags } from '../../enums/PacketFlags';
|
|
import { PCode } from '../../enums/PCode';
|
|
import { PrimFlags } from '../../enums/PrimFlags';
|
|
import { NewObjectEvent } from '../../events/NewObjectEvent';
|
|
import { AssetMap } from '../AssetMap';
|
|
import { BuildMap } from '../BuildMap';
|
|
import { EstateOwnerMessageMessage } from '../messages/EstateOwnerMessage';
|
|
import { ObjectAddMessage } from '../messages/ObjectAdd';
|
|
import { ObjectDeGrabMessage } from '../messages/ObjectDeGrab';
|
|
import { ObjectDeselectMessage } from '../messages/ObjectDeselect';
|
|
import { ObjectGrabMessage } from '../messages/ObjectGrab';
|
|
import { ObjectGrabUpdateMessage } from '../messages/ObjectGrabUpdate';
|
|
import { ObjectPropertiesMessage } from '../messages/ObjectProperties';
|
|
import { ObjectSelectMessage } from '../messages/ObjectSelect';
|
|
import { RegionHandleRequestMessage } from '../messages/RegionHandleRequest';
|
|
import { RegionIDAndHandleReplyMessage } from '../messages/RegionIDAndHandleReply';
|
|
import { Avatar } from '../public/Avatar';
|
|
import { GameObject } from '../public/GameObject';
|
|
import { Parcel } from '../public/Parcel';
|
|
import { Quaternion } from '../Quaternion';
|
|
import { Utils } from '../Utils';
|
|
import { UUID } from '../UUID';
|
|
import { Vector3 } from '../Vector3';
|
|
import { CommandsBase } from './CommandsBase';
|
|
import Timeout = NodeJS.Timeout;
|
|
|
|
import Timer = NodeJS.Timer;
|
|
|
|
export interface GetObjectsOptions
|
|
{
|
|
resolve?: boolean;
|
|
includeTempObjects?: boolean;
|
|
includeAvatars?: boolean;
|
|
}
|
|
|
|
export class RegionCommands extends CommandsBase
|
|
{
|
|
async getRegionHandle(regionID: UUID): Promise<Long>
|
|
{
|
|
const circuit = this.currentRegion.circuit;
|
|
const msg: RegionHandleRequestMessage = new RegionHandleRequestMessage();
|
|
msg.RequestBlock = {
|
|
RegionID: regionID,
|
|
};
|
|
circuit.sendMessage(msg, PacketFlags.Reliable);
|
|
const responseMsg: RegionIDAndHandleReplyMessage = await circuit.waitForMessage<RegionIDAndHandleReplyMessage>(Message.RegionIDAndHandleReply, 10000, (filterMsg: RegionIDAndHandleReplyMessage): FilterResponse =>
|
|
{
|
|
if (filterMsg.ReplyBlock.RegionID.toString() === regionID.toString())
|
|
{
|
|
return FilterResponse.Finish;
|
|
}
|
|
else
|
|
{
|
|
return FilterResponse.NoMatch;
|
|
}
|
|
});
|
|
return responseMsg.ReplyBlock.RegionHandle;
|
|
}
|
|
|
|
waitForHandshake(timeout: number = 10000): Promise<void>
|
|
{
|
|
return new Promise((resolve, reject) =>
|
|
{
|
|
if (this.currentRegion.handshakeComplete)
|
|
{
|
|
resolve();
|
|
}
|
|
else
|
|
{
|
|
let handshakeSubscription: Subscription | undefined;
|
|
let timeoutTimer: number | undefined;
|
|
handshakeSubscription = this.currentRegion.handshakeCompleteEvent.subscribe(() =>
|
|
{
|
|
if (timeoutTimer !== undefined)
|
|
{
|
|
clearTimeout(timeoutTimer);
|
|
timeoutTimer = undefined;
|
|
}
|
|
if (handshakeSubscription !== undefined)
|
|
{
|
|
handshakeSubscription.unsubscribe();
|
|
handshakeSubscription = undefined;
|
|
resolve();
|
|
}
|
|
});
|
|
timeoutTimer = setTimeout(() =>
|
|
{
|
|
if (handshakeSubscription !== undefined)
|
|
{
|
|
handshakeSubscription.unsubscribe();
|
|
handshakeSubscription = undefined;
|
|
}
|
|
if (timeoutTimer !== undefined)
|
|
{
|
|
clearTimeout(timeoutTimer);
|
|
timeoutTimer = undefined;
|
|
reject(new Error('Timeout'));
|
|
}
|
|
}, timeout) as any as number;
|
|
if (this.currentRegion.handshakeComplete)
|
|
{
|
|
if (handshakeSubscription !== undefined)
|
|
{
|
|
handshakeSubscription.unsubscribe();
|
|
handshakeSubscription = undefined;
|
|
}
|
|
if (timeoutTimer !== undefined)
|
|
{
|
|
clearTimeout(timeoutTimer);
|
|
timeoutTimer = undefined;
|
|
}
|
|
resolve();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
estateMessage(method: string, params: string[]): Promise<void>
|
|
{
|
|
const msg = new EstateOwnerMessageMessage();
|
|
msg.AgentData = {
|
|
AgentID: this.agent.agentID,
|
|
SessionID: this.circuit.sessionID,
|
|
TransactionID: UUID.zero()
|
|
};
|
|
msg.MethodData = {
|
|
Invoice: UUID.random(),
|
|
Method: Utils.StringToBuffer(method),
|
|
}
|
|
msg.ParamList = [];
|
|
for (const param of params)
|
|
{
|
|
msg.ParamList.push({
|
|
Parameter: Utils.StringToBuffer(param)
|
|
})
|
|
}
|
|
const sequenceID = this.circuit.sendMessage(msg, PacketFlags.Reliable);
|
|
return this.circuit.waitForAck(sequenceID, 10000);
|
|
}
|
|
|
|
restartRegion(secs: number): Promise<void>
|
|
{
|
|
return this.estateMessage('restart', [String(secs)]);
|
|
}
|
|
|
|
simulatorMessage(message: string): Promise<void>
|
|
{
|
|
return this.estateMessage('simulatormessage', [
|
|
'-1',
|
|
'-1',
|
|
this.agent.agentID.toString(),
|
|
this.agent.firstName + ' ' + this.agent.lastName,
|
|
message
|
|
]);
|
|
}
|
|
|
|
cancelRestart(): Promise<void>
|
|
{
|
|
return this.restartRegion(-1);
|
|
}
|
|
|
|
getAvatarsInRegion(): Avatar[]
|
|
{
|
|
return Object.values(this.currentRegion.agents);
|
|
}
|
|
|
|
async deselectObjects(objects: GameObject[]): Promise<void>
|
|
{
|
|
// Limit to 255 objects at once
|
|
const selectLimit = 255;
|
|
if (objects.length > selectLimit)
|
|
{
|
|
for (let x = 0; x < objects.length; x += selectLimit)
|
|
{
|
|
const selectList: GameObject[] = [];
|
|
for (let y = 0; y < selectLimit; y++)
|
|
{
|
|
if (y < objects.length)
|
|
{
|
|
selectList.push(objects[x + y]);
|
|
}
|
|
}
|
|
await this.deselectObjects(selectList);
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
const deselectObject = new ObjectDeselectMessage();
|
|
deselectObject.AgentData = {
|
|
AgentID: this.agent.agentID,
|
|
SessionID: this.circuit.sessionID
|
|
};
|
|
deselectObject.ObjectData = [];
|
|
const uuidMap: { [key: string]: GameObject } = {};
|
|
for (const obj of objects)
|
|
{
|
|
const uuidStr = obj.FullID.toString();
|
|
if (!uuidMap[uuidStr])
|
|
{
|
|
uuidMap[uuidStr] = obj;
|
|
deselectObject.ObjectData.push({
|
|
ObjectLocalID: obj.ID
|
|
});
|
|
}
|
|
}
|
|
|
|
// Create a map of our expected UUIDs
|
|
|
|
const sequenceID = this.circuit.sendMessage(deselectObject, PacketFlags.Reliable);
|
|
return this.circuit.waitForAck(sequenceID, 10000);
|
|
}
|
|
}
|
|
|
|
countObjects(): number
|
|
{
|
|
return this.currentRegion.objects.getNumberOfObjects();
|
|
}
|
|
|
|
getTerrainTextures(): UUID[]
|
|
{
|
|
const textures: UUID[] = [];
|
|
textures.push(this.currentRegion.terrainDetail0);
|
|
textures.push(this.currentRegion.terrainDetail1);
|
|
textures.push(this.currentRegion.terrainDetail2);
|
|
textures.push(this.currentRegion.terrainDetail3);
|
|
return textures;
|
|
}
|
|
|
|
exportSettings(): string
|
|
{
|
|
return this.currentRegion.exportXML();
|
|
}
|
|
|
|
async getTerrain(): Promise<Buffer>
|
|
{
|
|
await this.currentRegion.waitForTerrain();
|
|
const buf = Buffer.allocUnsafe(262144);
|
|
let pos = 0;
|
|
for (let x = 0; x < 256; x++)
|
|
{
|
|
for (let y = 0; y < 256; y++)
|
|
{
|
|
buf.writeFloatLE(this.currentRegion.terrain[x][y], pos);
|
|
pos = pos + 4;
|
|
}
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
async selectObjects(objects: GameObject[]): Promise<void>
|
|
{
|
|
// Limit to 255 objects at once
|
|
const selectLimit = 255;
|
|
if (objects.length > selectLimit)
|
|
{
|
|
for (let x = 0; x < objects.length; x += selectLimit)
|
|
{
|
|
const selectList: GameObject[] = [];
|
|
for (let y = 0; y < selectLimit; y++)
|
|
{
|
|
if (y < objects.length)
|
|
{
|
|
selectList.push(objects[x + y]);
|
|
}
|
|
}
|
|
await this.selectObjects(selectList);
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
const selectObject = new ObjectSelectMessage();
|
|
selectObject.AgentData = {
|
|
AgentID: this.agent.agentID,
|
|
SessionID: this.circuit.sessionID
|
|
};
|
|
selectObject.ObjectData = [];
|
|
const uuidMap: { [key: string]: GameObject } = {};
|
|
for (const obj of objects)
|
|
{
|
|
const uuidStr = obj.FullID.toString();
|
|
if (!uuidMap[uuidStr])
|
|
{
|
|
uuidMap[uuidStr] = obj;
|
|
selectObject.ObjectData.push({
|
|
ObjectLocalID: obj.ID
|
|
});
|
|
}
|
|
}
|
|
|
|
// Create a map of our expected UUIDs
|
|
this.circuit.sendMessage(selectObject, PacketFlags.Reliable);
|
|
try
|
|
{
|
|
await this.circuit.waitForMessage<ObjectPropertiesMessage>(Message.ObjectProperties, 10000, (propertiesMessage: ObjectPropertiesMessage): FilterResponse =>
|
|
{
|
|
let found = false;
|
|
for (const objData of propertiesMessage.ObjectData)
|
|
{
|
|
const objDataUUID = objData.ObjectID.toString();
|
|
if (uuidMap[objDataUUID] !== undefined)
|
|
{
|
|
const obj = uuidMap[objDataUUID];
|
|
obj.creatorID = objData.CreatorID;
|
|
obj.creationDate = objData.CreationDate;
|
|
obj.baseMask = objData.BaseMask;
|
|
obj.ownerMask = objData.OwnerMask;
|
|
obj.groupMask = objData.GroupMask;
|
|
obj.everyoneMask = objData.EveryoneMask;
|
|
obj.nextOwnerMask = objData.NextOwnerMask;
|
|
obj.ownershipCost = objData.OwnershipCost;
|
|
obj.saleType = objData.SaleType;
|
|
obj.salePrice = objData.SalePrice;
|
|
obj.aggregatePerms = objData.AggregatePerms;
|
|
obj.aggregatePermTextures = objData.AggregatePermTextures;
|
|
obj.aggregatePermTexturesOwner = objData.AggregatePermTexturesOwner;
|
|
obj.category = objData.Category;
|
|
obj.inventorySerial = objData.InventorySerial;
|
|
obj.itemID = objData.ItemID;
|
|
obj.folderID = objData.FolderID;
|
|
obj.fromTaskID = objData.FromTaskID;
|
|
obj.groupID = objData.GroupID;
|
|
obj.lastOwnerID = objData.LastOwnerID;
|
|
obj.name = Utils.BufferToStringSimple(objData.Name);
|
|
obj.description = Utils.BufferToStringSimple(objData.Description);
|
|
obj.touchName = Utils.BufferToStringSimple(objData.TouchName);
|
|
obj.sitName = Utils.BufferToStringSimple(objData.SitName);
|
|
obj.textureID = Utils.BufferToStringSimple(objData.TextureID);
|
|
obj.resolvedAt = new Date().getTime() / 1000;
|
|
delete uuidMap[objDataUUID];
|
|
found = true;
|
|
}
|
|
}
|
|
if (Object.keys(uuidMap).length === 0)
|
|
{
|
|
return FilterResponse.Finish;
|
|
}
|
|
if (!found)
|
|
{
|
|
return FilterResponse.NoMatch;
|
|
}
|
|
else
|
|
{
|
|
return FilterResponse.Match;
|
|
}
|
|
});
|
|
}
|
|
catch (error)
|
|
{
|
|
|
|
}
|
|
finally
|
|
{
|
|
|
|
for (const obj of objects)
|
|
{
|
|
if (obj.resolvedAt === undefined || obj.name === undefined)
|
|
{
|
|
obj.resolveAttempts++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
getName(): string
|
|
{
|
|
return this.currentRegion.regionName;
|
|
}
|
|
|
|
async resolveObject(object: GameObject, options: GetObjectsOptions): Promise<GameObject[]>
|
|
{
|
|
return this.currentRegion.resolver.resolveObjects([object], options);
|
|
}
|
|
|
|
async resolveObjects(objects: GameObject[], options: GetObjectsOptions): Promise<GameObject[]>
|
|
{
|
|
return this.currentRegion.resolver.resolveObjects(objects, options);
|
|
}
|
|
|
|
public async fetchObjectInventory(object: GameObject): Promise<void>
|
|
{
|
|
return this.currentRegion.resolver.getInventory(object);
|
|
}
|
|
|
|
public async fetchObjectInventories(objects: GameObject[]): Promise<void>
|
|
{
|
|
return this.currentRegion.resolver.getInventories(objects);
|
|
}
|
|
|
|
public async fetchObjectCost(object: GameObject): Promise<void>
|
|
{
|
|
return this.currentRegion.resolver.getCosts([object]);
|
|
}
|
|
|
|
public async fetchObjectCosts(objects: GameObject[]): Promise<void>
|
|
{
|
|
return this.currentRegion.resolver.getCosts(objects);
|
|
}
|
|
|
|
|
|
private waitForObjectByLocalID(localID: number, timeout: number): Promise<GameObject>
|
|
{
|
|
return new Promise<GameObject>((resolve, reject) =>
|
|
{
|
|
let tmr: Timer | null = null;
|
|
const subscription = this.currentRegion.clientEvents.onNewObjectEvent.subscribe(async(event: NewObjectEvent) =>
|
|
{
|
|
if (event.localID === localID)
|
|
{
|
|
if (tmr !== null)
|
|
{
|
|
clearTimeout(tmr);
|
|
}
|
|
subscription.unsubscribe();
|
|
resolve(event.object);
|
|
}
|
|
});
|
|
tmr = setTimeout(() =>
|
|
{
|
|
subscription.unsubscribe();
|
|
reject(new Error('Timeout'));
|
|
}, timeout)
|
|
});
|
|
}
|
|
|
|
private waitForObjectByUUID(objectID: UUID, timeout: number): Promise<GameObject>
|
|
{
|
|
return new Promise<GameObject>((resolve, reject) =>
|
|
{
|
|
let tmr: Timer | null = null;
|
|
const subscription = this.currentRegion.clientEvents.onNewObjectEvent.subscribe(async(event: NewObjectEvent) =>
|
|
{
|
|
if (event.objectID.equals(objectID))
|
|
{
|
|
if (tmr !== null)
|
|
{
|
|
clearTimeout(tmr);
|
|
}
|
|
subscription.unsubscribe();
|
|
resolve(event.object);
|
|
}
|
|
});
|
|
tmr = setTimeout(() =>
|
|
{
|
|
subscription.unsubscribe();
|
|
reject(new Error('Timeout'));
|
|
}, timeout)
|
|
});
|
|
}
|
|
|
|
private async buildPart(obj: GameObject, posOffset: Vector3, rotOffset: Quaternion, buildMap: BuildMap, markRoot = false): Promise<GameObject>
|
|
{
|
|
// Calculate geometry
|
|
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)
|
|
{
|
|
finalPos = new Vector3(objectPosition);
|
|
finalRot = new Quaternion(objectRotation);
|
|
}
|
|
else
|
|
{
|
|
const adjustedPos = new Vector3(objectPosition).multiplyByTSMQuat(new Quaternion(rotOffset));
|
|
finalPos = new Vector3(new Vector3(posOffset).add(adjustedPos));
|
|
|
|
const baseRot = new Quaternion(rotOffset);
|
|
finalRot = new Quaternion(baseRot.multiply(new Quaternion(objectRotation)));
|
|
}
|
|
|
|
// Is this a mesh part?
|
|
let object: GameObject | null = null;
|
|
let rezzedMesh = false;
|
|
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);
|
|
|
|
if (meshEntry.item !== null)
|
|
{
|
|
try
|
|
{
|
|
object = await meshEntry.item.rezInWorld(rezLocation);
|
|
}
|
|
catch (err)
|
|
{
|
|
console.error('Failed to rez object ' + obj.name + ' in-world');
|
|
console.error(err);
|
|
}
|
|
rezzedMesh = true;
|
|
}
|
|
else
|
|
{
|
|
console.error('Unable to rez mesh item from inventory - item is null');
|
|
}
|
|
}
|
|
}
|
|
else if (buildMap.primReservoir.length > 0)
|
|
{
|
|
const newPrim = buildMap.primReservoir.shift();
|
|
if (newPrim !== undefined)
|
|
{
|
|
object = newPrim;
|
|
try
|
|
{
|
|
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
|
|
);
|
|
}
|
|
catch (err)
|
|
{
|
|
console.error('Error setting shape on ' + obj.name);
|
|
console.error(err);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
console.error('Exhausted prim reservoir!!');
|
|
}
|
|
|
|
if (object === null)
|
|
{
|
|
console.error('Failed to acquire prim for build');
|
|
throw new Error('Failed to acquire prim for build');
|
|
}
|
|
|
|
if (markRoot)
|
|
{
|
|
object.isMarkedRoot = true;
|
|
}
|
|
|
|
try
|
|
{
|
|
await object.setGeometry(finalPos, finalRot, objectScale);
|
|
}
|
|
catch (err)
|
|
{
|
|
console.error('Error setting geometry on ' + obj.name);
|
|
console.error(err);
|
|
}
|
|
|
|
if (obj.extraParams.sculptData !== null)
|
|
{
|
|
if (obj.extraParams.sculptData.type !== SculptType.Mesh)
|
|
{
|
|
const oldTextureID = obj.extraParams.sculptData.texture.toString();
|
|
const item = buildMap.assetMap.textures[oldTextureID];
|
|
if (item !== null && item !== undefined && item.item !== null)
|
|
{
|
|
obj.extraParams.sculptData.texture = item.item.assetID;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rezzedMesh)
|
|
{
|
|
obj.extraParams.meshData = object.extraParams.meshData;
|
|
obj.extraParams.sculptData = object.extraParams.sculptData;
|
|
}
|
|
|
|
try
|
|
{
|
|
await object.setExtraParams(obj.extraParams);
|
|
}
|
|
catch (err: unknown)
|
|
{
|
|
if (err instanceof Error)
|
|
{
|
|
throw (err);
|
|
}
|
|
else if (typeof err === 'string')
|
|
{
|
|
throw new Error(err);
|
|
}
|
|
else
|
|
{
|
|
throw new Error('Error setting ExtraParams on ' + obj.name);
|
|
}
|
|
}
|
|
|
|
if (obj.TextureEntry !== undefined)
|
|
{
|
|
// Handle materials
|
|
const materialUpload: {
|
|
'FullMaterialsPerFace': any[]
|
|
} = {
|
|
'FullMaterialsPerFace': []
|
|
};
|
|
|
|
const materialFaces: { [key: string]: boolean } = {};
|
|
if (obj.TextureEntry.defaultTexture !== undefined && obj.TextureEntry.defaultTexture !== null)
|
|
{
|
|
const materialID = obj.TextureEntry.defaultTexture.materialID;
|
|
if (!materialID.isZero())
|
|
{
|
|
const storedMat = buildMap.assetMap.materials[materialID.toString()];
|
|
if (storedMat !== null && storedMat !== undefined)
|
|
{
|
|
materialUpload.FullMaterialsPerFace.push({
|
|
ID: object.ID,
|
|
Material: storedMat.toLLSDObject()
|
|
});
|
|
materialFaces[-1] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (let face = 0; face < obj.TextureEntry.faces.length; face++)
|
|
{
|
|
const materialID = obj.TextureEntry.faces[face].materialID;
|
|
if (!materialID.isZero())
|
|
{
|
|
const storedMat = buildMap.assetMap.materials[materialID.toString()];
|
|
if (storedMat !== null && storedMat !== undefined)
|
|
{
|
|
materialUpload.FullMaterialsPerFace.push({
|
|
Face: face,
|
|
ID: object.ID,
|
|
Material: storedMat.toLLSDObject()
|
|
});
|
|
materialFaces[face] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Object.keys(materialFaces).length > 0)
|
|
{
|
|
const zipped = await Utils.deflate(Buffer.from(LLSD.LLSD.formatBinary(materialUpload).octets));
|
|
const newMat = {
|
|
'Zipped': new LLSD.Binary(Array.from(zipped), 'BASE64')
|
|
};
|
|
this.currentRegion.caps.capsPutXML('RenderMaterials', newMat).then(() =>
|
|
{
|
|
|
|
}).catch((err) =>
|
|
{
|
|
console.error(err);
|
|
});
|
|
try
|
|
{
|
|
let complete = false;
|
|
do
|
|
{
|
|
complete = true;
|
|
await object.waitForTextureUpdate(10000);
|
|
for (const materialFace of Object.keys(materialFaces))
|
|
{
|
|
const entry = object.TextureEntry;
|
|
if (entry === undefined)
|
|
{
|
|
complete = false;
|
|
}
|
|
else if (parseInt(materialFace, 10) === -1)
|
|
{
|
|
const def = entry.defaultTexture
|
|
if (def === undefined || def === null)
|
|
{
|
|
complete = false;
|
|
}
|
|
else
|
|
{
|
|
if (def.materialID.equals(UUID.zero()))
|
|
{
|
|
complete = false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const fc = parseInt(materialFace, 10);
|
|
const thisFace = entry.faces[fc];
|
|
if (thisFace === undefined)
|
|
{
|
|
complete = false;
|
|
}
|
|
else
|
|
{
|
|
if (thisFace.materialID.equals(UUID.zero()))
|
|
{
|
|
complete = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
while (!complete);
|
|
}
|
|
catch (error)
|
|
{
|
|
console.error(obj.name + ':Timed out while waiting for RenderMaterials update');
|
|
}
|
|
if (object.TextureEntry !== undefined)
|
|
{
|
|
for (let face = 0; face < object.TextureEntry.faces.length; face++)
|
|
{
|
|
const oldFace = obj.TextureEntry.faces[face];
|
|
if (!oldFace.materialID.isZero())
|
|
{
|
|
obj.TextureEntry.faces[face].materialID = object.TextureEntry.faces[face].materialID;
|
|
if (obj.TextureEntry.defaultTexture !== null)
|
|
{
|
|
if (oldFace.materialID.equals(obj.TextureEntry.defaultTexture.materialID))
|
|
{
|
|
obj.TextureEntry.defaultTexture.materialID = obj.TextureEntry.faces[face].materialID;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (obj.TextureEntry.defaultTexture !== null && object.TextureEntry.defaultTexture !== null)
|
|
{
|
|
obj.TextureEntry.defaultTexture.materialID = object.TextureEntry.defaultTexture.materialID;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (obj.TextureEntry.defaultTexture !== null)
|
|
{
|
|
const oldTextureID = obj.TextureEntry.defaultTexture.textureID.toString();
|
|
|
|
const item = buildMap.assetMap.textures[oldTextureID];
|
|
if (item !== null && item !== undefined && item.item !== null)
|
|
{
|
|
obj.TextureEntry.defaultTexture.textureID = item.item.assetID;
|
|
}
|
|
}
|
|
for (const j of obj.TextureEntry.faces)
|
|
{
|
|
const oldTextureID = j.textureID.toString();
|
|
|
|
const item = buildMap.assetMap.textures[oldTextureID];
|
|
if (item !== null && item !== undefined && item.item !== null)
|
|
{
|
|
j.textureID = item.item.assetID;
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
await object.setTextureEntry(obj.TextureEntry);
|
|
}
|
|
catch (error)
|
|
{
|
|
console.error('Error setting TextureEntry on ' + obj.name);
|
|
console.error(error);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
if (obj.name !== undefined)
|
|
{
|
|
try
|
|
{
|
|
await object.setName(obj.name);
|
|
}
|
|
catch (error)
|
|
{
|
|
if (error instanceof Error)
|
|
{
|
|
throw error;
|
|
}
|
|
else if (typeof error === 'string')
|
|
{
|
|
throw new Error(error);
|
|
}
|
|
else
|
|
{
|
|
throw new Error('Error setting name on ' + obj.name);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (obj.description !== undefined)
|
|
{
|
|
try
|
|
{
|
|
await object.setDescription(obj.description);
|
|
}
|
|
catch (error)
|
|
{
|
|
if (error instanceof Error)
|
|
{
|
|
throw error;
|
|
}
|
|
else if (typeof error === 'string')
|
|
{
|
|
throw new Error(error);
|
|
}
|
|
else
|
|
{
|
|
throw new Error('Error setting name on ' + obj.name);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const invItem of obj.inventory)
|
|
{
|
|
try
|
|
{
|
|
if (invItem.inventoryType === InventoryType.Object && invItem.assetID.isZero())
|
|
{
|
|
invItem.assetID = invItem.itemID;
|
|
}
|
|
switch (invItem.inventoryType)
|
|
{
|
|
case InventoryType.Clothing:
|
|
{
|
|
if (buildMap.assetMap.clothing[invItem.assetID.toString()] !== undefined)
|
|
{
|
|
const item = buildMap.assetMap.clothing[invItem.assetID.toString()].item;
|
|
if (item !== null)
|
|
{
|
|
await object.dropInventoryIntoContents(item);
|
|
if (invItem.name !== item.name)
|
|
{
|
|
await object.updateInventory();
|
|
for (const taskItem of object.inventory)
|
|
{
|
|
if (taskItem.name === item.name)
|
|
{
|
|
await taskItem.renameInTask(object, invItem.name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case InventoryType.Settings:
|
|
{
|
|
if (buildMap.assetMap.settings[invItem.assetID.toString()] !== undefined)
|
|
{
|
|
const item = buildMap.assetMap.settings[invItem.assetID.toString()].item;
|
|
if (item !== null)
|
|
{
|
|
await object.dropInventoryIntoContents(item);
|
|
if (invItem.name !== item.name)
|
|
{
|
|
await object.updateInventory();
|
|
for (const taskItem of object.inventory)
|
|
{
|
|
if (taskItem.name === item.name)
|
|
{
|
|
await taskItem.renameInTask(object, invItem.name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case InventoryType.Wearable:
|
|
case InventoryType.Bodypart:
|
|
{
|
|
if (buildMap.assetMap.bodyparts[invItem.assetID.toString()] !== undefined)
|
|
{
|
|
const item = buildMap.assetMap.bodyparts[invItem.assetID.toString()].item;
|
|
if (item !== null)
|
|
{
|
|
await object.dropInventoryIntoContents(item);
|
|
if (invItem.name !== item.name)
|
|
{
|
|
await object.updateInventory();
|
|
for (const taskItem of object.inventory)
|
|
{
|
|
if (taskItem.name === item.name)
|
|
{
|
|
await taskItem.renameInTask(object, invItem.name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case InventoryType.Notecard:
|
|
{
|
|
if (buildMap.assetMap.notecards[invItem.assetID.toString()] !== undefined)
|
|
{
|
|
const item = buildMap.assetMap.notecards[invItem.assetID.toString()].item;
|
|
if (item !== null)
|
|
{
|
|
await object.dropInventoryIntoContents(item);
|
|
if (invItem.name !== item.name)
|
|
{
|
|
await object.updateInventory();
|
|
for (const taskItem of object.inventory)
|
|
{
|
|
if (taskItem.name === item.name)
|
|
{
|
|
await taskItem.renameInTask(object, invItem.name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case InventoryType.Sound:
|
|
{
|
|
if (buildMap.assetMap.sounds[invItem.assetID.toString()] !== undefined)
|
|
{
|
|
const item = buildMap.assetMap.sounds[invItem.assetID.toString()].item;
|
|
if (item !== null)
|
|
{
|
|
await object.dropInventoryIntoContents(item);
|
|
if (invItem.name !== item.name)
|
|
{
|
|
await object.updateInventory();
|
|
for (const taskItem of object.inventory)
|
|
{
|
|
if (taskItem.name === item.name)
|
|
{
|
|
await taskItem.renameInTask(object, invItem.name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case InventoryType.Gesture:
|
|
{
|
|
if (buildMap.assetMap.gestures[invItem.assetID.toString()] !== undefined)
|
|
{
|
|
const item = buildMap.assetMap.gestures[invItem.assetID.toString()].item;
|
|
if (item !== null)
|
|
{
|
|
await object.dropInventoryIntoContents(item);
|
|
if (invItem.name !== item.name)
|
|
{
|
|
await object.updateInventory();
|
|
for (const taskItem of object.inventory)
|
|
{
|
|
if (taskItem.name === item.name)
|
|
{
|
|
await taskItem.renameInTask(object, invItem.name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case InventoryType.Script:
|
|
case InventoryType.LSL:
|
|
{
|
|
if (buildMap.assetMap.scripts[invItem.assetID.toString()] !== undefined)
|
|
{
|
|
const item = buildMap.assetMap.scripts[invItem.assetID.toString()].item;
|
|
if (item !== null)
|
|
{
|
|
await object.dropInventoryIntoContents(item);
|
|
if (invItem.name !== item.name)
|
|
{
|
|
await object.updateInventory();
|
|
for (const taskItem of object.inventory)
|
|
{
|
|
if (taskItem.name === item.name)
|
|
{
|
|
await taskItem.renameInTask(object, invItem.name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case InventoryType.Animation:
|
|
{
|
|
if (buildMap.assetMap.animations[invItem.assetID.toString()] !== undefined)
|
|
{
|
|
const item = buildMap.assetMap.animations[invItem.assetID.toString()].item;
|
|
if (item !== null)
|
|
{
|
|
await object.dropInventoryIntoContents(item);
|
|
if (invItem.name !== item.name)
|
|
{
|
|
await object.updateInventory();
|
|
for (const taskItem of object.inventory)
|
|
{
|
|
if (taskItem.name === item.name)
|
|
{
|
|
await taskItem.renameInTask(object, invItem.name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case InventoryType.Object:
|
|
{
|
|
if (buildMap.assetMap.objects[invItem.itemID.toString()] !== undefined)
|
|
{
|
|
const inventoryItem = buildMap.assetMap.objects[invItem.itemID.toString()];
|
|
if (inventoryItem !== null)
|
|
{
|
|
await object.dropInventoryIntoContents(inventoryItem);
|
|
if (invItem.name !== inventoryItem.name)
|
|
{
|
|
await object.updateInventory();
|
|
for (const taskItem of object.inventory)
|
|
{
|
|
if (taskItem.name === inventoryItem.name)
|
|
{
|
|
await taskItem.renameInTask(object, invItem.name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
console.error('Unable to drop object: item is null');
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case InventoryType.Texture:
|
|
case InventoryType.Snapshot:
|
|
{
|
|
if (buildMap.assetMap.textures[invItem.assetID.toString()] !== undefined)
|
|
{
|
|
const texItem = buildMap.assetMap.textures[invItem.assetID.toString()];
|
|
if (texItem.item !== null)
|
|
{
|
|
await object.dropInventoryIntoContents(texItem.item);
|
|
if (invItem.name !== texItem.item.name)
|
|
{
|
|
await object.updateInventory();
|
|
for (const taskItem of object.inventory)
|
|
{
|
|
if (taskItem.name === texItem.item.name)
|
|
{
|
|
await taskItem.renameInTask(object, invItem.name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
console.error('Unable to drop object: item is null');
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default: // TODO: 3 - landmark
|
|
console.error('Unsupported inventory type: ' + invItem.inventoryType);
|
|
break;
|
|
}
|
|
}
|
|
catch (error)
|
|
{
|
|
console.error(error);
|
|
}
|
|
}
|
|
return object;
|
|
}
|
|
|
|
private gatherAssets(obj: GameObject, buildMap: BuildMap): void
|
|
{
|
|
if (obj.extraParams !== undefined)
|
|
{
|
|
if (obj.extraParams.meshData !== null)
|
|
{
|
|
if (buildMap.assetMap.mesh[obj.extraParams.meshData.meshData.toString()] === undefined)
|
|
{
|
|
buildMap.assetMap.mesh[obj.extraParams.meshData.meshData.toString()] = {
|
|
name: obj.name || 'Object',
|
|
description: obj.description || '(no description)',
|
|
item: null
|
|
};
|
|
}
|
|
}
|
|
else
|
|
{
|
|
buildMap.primsNeeded++;
|
|
}
|
|
if (obj.extraParams.sculptData !== null)
|
|
{
|
|
if (obj.extraParams.sculptData.type !== SculptType.Mesh)
|
|
{
|
|
if (buildMap.assetMap.textures[obj.extraParams.sculptData.texture.toString()] === undefined)
|
|
{
|
|
buildMap.assetMap.textures[obj.extraParams.sculptData.texture.toString()] = {
|
|
item: null
|
|
};
|
|
}
|
|
}
|
|
}
|
|
if (obj.TextureEntry !== undefined)
|
|
{
|
|
for (const j of obj.TextureEntry.faces)
|
|
{
|
|
const textureID = j.textureID;
|
|
if (buildMap.assetMap.textures[textureID.toString()] === undefined)
|
|
{
|
|
buildMap.assetMap.textures[textureID.toString()] = {
|
|
item: null
|
|
}
|
|
}
|
|
const materialID = j.materialID;
|
|
if (!materialID.isZero())
|
|
{
|
|
if (buildMap.assetMap.materials[materialID.toString()] === undefined)
|
|
{
|
|
buildMap.assetMap.materials[materialID.toString()] = null
|
|
}
|
|
}
|
|
}
|
|
if (obj.TextureEntry.defaultTexture !== null)
|
|
{
|
|
const textureID = obj.TextureEntry.defaultTexture.textureID;
|
|
if (buildMap.assetMap.textures[textureID.toString()] === undefined)
|
|
{
|
|
buildMap.assetMap.textures[textureID.toString()] = {
|
|
item: null
|
|
}
|
|
}
|
|
const materialID = obj.TextureEntry.defaultTexture.materialID;
|
|
if (!materialID.isZero())
|
|
{
|
|
if (buildMap.assetMap.materials[materialID.toString()] === undefined)
|
|
{
|
|
buildMap.assetMap.materials[materialID.toString()] = null
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (obj.inventory !== undefined)
|
|
{
|
|
for (const j of obj.inventory)
|
|
{
|
|
const assetID = j.assetID;
|
|
switch (j.inventoryType)
|
|
{
|
|
case InventoryType.Animation:
|
|
{
|
|
if (buildMap.assetMap.animations[assetID.toString()] === undefined)
|
|
{
|
|
buildMap.assetMap.animations[assetID.toString()] = {
|
|
name: j.name,
|
|
description: j.description,
|
|
item: null
|
|
};
|
|
}
|
|
break;
|
|
}
|
|
case InventoryType.Wearable:
|
|
case InventoryType.Bodypart:
|
|
{
|
|
if (buildMap.assetMap.bodyparts[assetID.toString()] === undefined)
|
|
{
|
|
buildMap.assetMap.bodyparts[assetID.toString()] = {
|
|
name: j.name,
|
|
description: j.description,
|
|
item: null
|
|
};
|
|
}
|
|
break;
|
|
}
|
|
case InventoryType.CallingCard:
|
|
{
|
|
if (buildMap.assetMap.callingcards[assetID.toString()] === undefined)
|
|
{
|
|
buildMap.assetMap.callingcards[assetID.toString()] = {
|
|
name: j.name,
|
|
description: j.description,
|
|
item: null
|
|
};
|
|
}
|
|
break;
|
|
}
|
|
case InventoryType.Clothing:
|
|
{
|
|
if (buildMap.assetMap.clothing[assetID.toString()] === undefined)
|
|
{
|
|
buildMap.assetMap.clothing[assetID.toString()] = {
|
|
name: j.name,
|
|
description: j.description,
|
|
item: null
|
|
};
|
|
}
|
|
break;
|
|
}
|
|
case InventoryType.Gesture:
|
|
{
|
|
if (buildMap.assetMap.gestures[assetID.toString()] === undefined)
|
|
{
|
|
buildMap.assetMap.gestures[assetID.toString()] = {
|
|
name: j.name,
|
|
description: j.description,
|
|
item: null
|
|
};
|
|
}
|
|
break;
|
|
}
|
|
case InventoryType.Script:
|
|
case InventoryType.LSL:
|
|
{
|
|
if (buildMap.assetMap.scripts[assetID.toString()] === undefined)
|
|
{
|
|
buildMap.assetMap.scripts[assetID.toString()] = {
|
|
name: j.name,
|
|
description: j.description,
|
|
item: null
|
|
};
|
|
}
|
|
break;
|
|
}
|
|
case InventoryType.Texture:
|
|
case InventoryType.Snapshot:
|
|
{
|
|
if (buildMap.assetMap.textures[assetID.toString()] === undefined)
|
|
{
|
|
buildMap.assetMap.textures[assetID.toString()] = {
|
|
name: j.name,
|
|
description: j.description,
|
|
item: null
|
|
};
|
|
}
|
|
break;
|
|
}
|
|
case InventoryType.Notecard:
|
|
{
|
|
if (buildMap.assetMap.notecards[assetID.toString()] === undefined)
|
|
{
|
|
buildMap.assetMap.notecards[assetID.toString()] = {
|
|
name: j.name,
|
|
description: j.description,
|
|
item: null
|
|
};
|
|
}
|
|
break;
|
|
}
|
|
case InventoryType.Sound:
|
|
{
|
|
if (buildMap.assetMap.sounds[assetID.toString()] === undefined)
|
|
{
|
|
buildMap.assetMap.sounds[assetID.toString()] = {
|
|
name: j.name,
|
|
description: j.description,
|
|
item: null
|
|
};
|
|
}
|
|
break;
|
|
}
|
|
case InventoryType.Object:
|
|
{
|
|
if (buildMap.assetMap.objects[assetID.toString()] === undefined)
|
|
{
|
|
buildMap.assetMap.objects[assetID.toString()] = null;
|
|
}
|
|
break;
|
|
}
|
|
case InventoryType.Settings:
|
|
{
|
|
if (buildMap.assetMap.settings[assetID.toString()] === undefined)
|
|
{
|
|
buildMap.assetMap.settings[assetID.toString()] = {
|
|
name: j.name,
|
|
description: j.description,
|
|
item: null
|
|
};
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
console.error('Unsupported inventory type: ' + j.inventoryType);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (obj.children)
|
|
{
|
|
for (const child of obj.children)
|
|
{
|
|
this.gatherAssets(child, buildMap);
|
|
}
|
|
}
|
|
}
|
|
|
|
async buildObjectNew(obj: GameObject, map: AssetMap, callback: (map: AssetMap) => void, costOnly: boolean = false, skipMove = false): Promise<GameObject | null>
|
|
{
|
|
const buildMap = new BuildMap(map, callback, costOnly);
|
|
this.gatherAssets(obj, buildMap);
|
|
const bomTextures = [
|
|
'5a9f4a74-30f2-821c-b88d-70499d3e7183',
|
|
'ae2de45c-d252-50b8-5c6e-19f39ce79317',
|
|
'24daea5f-0539-cfcf-047f-fbc40b2786ba',
|
|
'52cc6bb6-2ee5-e632-d3ad-50197b1dcb8a',
|
|
'43529ce8-7faa-ad92-165a-bc4078371687',
|
|
'09aac1fb-6bce-0bee-7d44-caac6dbb6c63',
|
|
'ff62763f-d60a-9855-890b-0c96f8f8cd98',
|
|
'8e915e25-31d1-cc95-ae08-d58a47488251',
|
|
'9742065b-19b5-297c-858a-29711d539043',
|
|
'03642e83-2bd1-4eb9-34b4-4c47ed586d2d',
|
|
'edd51b77-fc10-ce7a-4b3d-011dfc349e4f',
|
|
'c228d1cf-4b5d-4ba8-84f4-899a0796aa97' // 'non existent asset'
|
|
];
|
|
for (const bomTexture of bomTextures)
|
|
{
|
|
if (buildMap.assetMap.textures[bomTexture] !== undefined)
|
|
{
|
|
delete buildMap.assetMap.textures[bomTexture];
|
|
}
|
|
}
|
|
await callback(map);
|
|
|
|
if (costOnly)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
let agentPos = new Vector3([128, 128, 2048]);
|
|
try
|
|
{
|
|
const agentLocalID = this.currentRegion.agent.localID;
|
|
const agentObject = this.currentRegion.objects.getObjectByLocalID(agentLocalID);
|
|
if (agentObject.Position !== undefined)
|
|
{
|
|
agentPos = new Vector3(agentObject.Position);
|
|
}
|
|
else
|
|
{
|
|
throw new Error('Agent position is undefined');
|
|
}
|
|
}
|
|
catch (error)
|
|
{
|
|
console.warn('Unable to find avatar location, rezzing at ' + agentPos.toString());
|
|
}
|
|
agentPos.z += 2.0;
|
|
buildMap.rezLocation = agentPos;
|
|
// Set camera above target location for fast acquisition
|
|
const campos = new Vector3(agentPos);
|
|
campos.z += 2.0;
|
|
await this.currentRegion.clientCommands.agent.setCamera(campos, agentPos, 10, new Vector3([-1.0, 0, 0]), new Vector3([0.0, 1.0, 0]));
|
|
|
|
if (buildMap.primsNeeded > 0)
|
|
{
|
|
buildMap.primReservoir = await this.createPrims(buildMap.primsNeeded, agentPos);
|
|
}
|
|
|
|
let storedPosition: Vector3 | undefined = undefined;
|
|
if (skipMove)
|
|
{
|
|
storedPosition = obj.Position;
|
|
obj.Position = new Vector3(buildMap.rezLocation);
|
|
}
|
|
|
|
const parts = [];
|
|
parts.push(async() =>
|
|
{
|
|
return {
|
|
index: 1,
|
|
object: await this.buildPart(obj, Vector3.getZero(), Quaternion.getIdentity(), buildMap, true)
|
|
}
|
|
});
|
|
|
|
if (obj.children)
|
|
{
|
|
if (obj.Position === undefined)
|
|
{
|
|
obj.Position = Vector3.getZero();
|
|
}
|
|
if (obj.Rotation === undefined)
|
|
{
|
|
obj.Rotation = Quaternion.getIdentity();
|
|
}
|
|
let childIndex = 2;
|
|
for (const child of obj.children)
|
|
{
|
|
if (child.Position !== undefined && child.Rotation !== undefined)
|
|
{
|
|
const index = childIndex++;
|
|
parts.push(async() =>
|
|
{
|
|
return {
|
|
index,
|
|
object: await this.buildPart(child, new Vector3(obj.Position), new Quaternion(obj.Rotation), buildMap, false)
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
let results: {
|
|
results: {
|
|
index: number,
|
|
object: GameObject
|
|
}[],
|
|
errors: Error[]
|
|
} = {
|
|
results: [],
|
|
errors: []
|
|
};
|
|
results = await Utils.promiseConcurrent<{
|
|
index: number,
|
|
object: GameObject
|
|
}>(parts, 5, 0);
|
|
if (results.errors.length > 0)
|
|
{
|
|
for (const err of results.errors)
|
|
{
|
|
console.error(err);
|
|
}
|
|
}
|
|
|
|
let rootObj: GameObject | null = null;
|
|
for (const childObject of results.results)
|
|
{
|
|
if (childObject.object.isMarkedRoot)
|
|
{
|
|
rootObj = childObject.object;
|
|
break;
|
|
}
|
|
}
|
|
if (rootObj === null)
|
|
{
|
|
throw new Error('Failed to find root prim..');
|
|
}
|
|
|
|
const childPrims: GameObject[] = [];
|
|
results.results.sort((a: { index: number, object: GameObject }, b: { index: number, object: GameObject }) =>
|
|
{
|
|
return a.index - b.index;
|
|
});
|
|
for (const childObject of results.results)
|
|
{
|
|
if (childObject.object !== rootObj)
|
|
{
|
|
childPrims.push(childObject.object);
|
|
}
|
|
}
|
|
try
|
|
{
|
|
await rootObj.linkFrom(childPrims);
|
|
}
|
|
catch (err)
|
|
{
|
|
console.error('Link failed:');
|
|
console.error(err);
|
|
}
|
|
if (storedPosition !== undefined)
|
|
{
|
|
obj.Position = storedPosition;
|
|
}
|
|
return rootObj;
|
|
}
|
|
|
|
private createPrims(count: number, position: Vector3): Promise<GameObject[]>
|
|
{
|
|
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.resolveObject(evt.object, {});
|
|
}
|
|
if (evt.createSelected && !evt.object.claimedForBuild)
|
|
{
|
|
if (evt.object.itemID === undefined || evt.object.itemID.isZero())
|
|
{
|
|
if (
|
|
evt.object.PCode === PCode.Prim &&
|
|
evt.object.Material === 3 &&
|
|
evt.object.PathCurve === 16 &&
|
|
evt.object.ProfileCurve === 1 &&
|
|
evt.object.PathBegin === 0 &&
|
|
evt.object.PathEnd === 1 &&
|
|
evt.object.PathScaleX === 1 &&
|
|
evt.object.PathScaleY === 1 &&
|
|
evt.object.PathShearX === 0 &&
|
|
evt.object.PathShearY === 0 &&
|
|
evt.object.PathTwist === 0 &&
|
|
evt.object.PathTwistBegin === 0 &&
|
|
evt.object.PathRadiusOffset === 0 &&
|
|
evt.object.PathTaperX === 0 &&
|
|
evt.object.PathTaperY === 0 &&
|
|
evt.object.PathRevolutions === 1 &&
|
|
evt.object.PathSkew === 0 &&
|
|
evt.object.ProfileBegin === 0 &&
|
|
evt.object.ProfileHollow === 0
|
|
)
|
|
{
|
|
evt.object.claimedForBuild = true;
|
|
gatheredPrims.push(evt.object);
|
|
if (gatheredPrims.length === count)
|
|
{
|
|
if (objSub !== undefined)
|
|
{
|
|
objSub.unsubscribe();
|
|
objSub = undefined;
|
|
}
|
|
if (timeout !== undefined)
|
|
{
|
|
clearTimeout(timeout);
|
|
timeout = undefined;
|
|
}
|
|
resolve(gatheredPrims);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
for (let x = 0; x < count; x++)
|
|
{
|
|
const msg = new ObjectAddMessage();
|
|
msg.AgentData = {
|
|
AgentID: this.agent.agentID,
|
|
SessionID: this.circuit.sessionID,
|
|
GroupID: UUID.zero()
|
|
};
|
|
msg.ObjectData = {
|
|
PCode: PCode.Prim,
|
|
Material: 3,
|
|
AddFlags: PrimFlags.CreateSelected,
|
|
PathCurve: 16,
|
|
ProfileCurve: 1,
|
|
PathBegin: 0,
|
|
PathEnd: 0,
|
|
PathScaleX: 100,
|
|
PathScaleY: 100,
|
|
PathShearX: 0,
|
|
PathShearY: 0,
|
|
PathTwist: 0,
|
|
PathTwistBegin: 0,
|
|
PathRadiusOffset: 0,
|
|
PathTaperX: 0,
|
|
PathTaperY: 0,
|
|
PathRevolutions: 0,
|
|
PathSkew: 0,
|
|
ProfileBegin: 0,
|
|
ProfileEnd: 0,
|
|
ProfileHollow: 0,
|
|
BypassRaycast: 1,
|
|
RayStart: position,
|
|
RayEnd: position,
|
|
RayTargetID: UUID.zero(),
|
|
RayEndIsIntersection: 0,
|
|
Scale: new Vector3([0.5, 0.5, 0.5]),
|
|
Rotation: Quaternion.getIdentity(),
|
|
State: 0
|
|
};
|
|
this.circuit.sendMessage(msg, PacketFlags.Reliable);
|
|
}
|
|
});
|
|
}
|
|
|
|
async getObjectByLocalID(id: number, resolve: boolean, waitFor: number = 0): Promise<GameObject>
|
|
{
|
|
let obj = null;
|
|
try
|
|
{
|
|
obj = this.currentRegion.objects.getObjectByLocalID(id);
|
|
}
|
|
catch (error)
|
|
{
|
|
if (waitFor > 0)
|
|
{
|
|
obj = await this.waitForObjectByLocalID(id, waitFor);
|
|
}
|
|
else
|
|
{
|
|
throw (error);
|
|
}
|
|
}
|
|
if (resolve)
|
|
{
|
|
await this.currentRegion.resolver.resolveObjects([obj], {});
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
async getObjectByUUID(id: UUID, resolve: boolean, waitFor: number = 0): Promise<GameObject>
|
|
{
|
|
let obj = null;
|
|
try
|
|
{
|
|
obj = this.currentRegion.objects.getObjectByUUID(id);
|
|
}
|
|
catch (error)
|
|
{
|
|
if (waitFor > 0)
|
|
{
|
|
obj = await this.waitForObjectByUUID(id, waitFor);
|
|
}
|
|
else
|
|
{
|
|
throw (error);
|
|
}
|
|
}
|
|
if (resolve)
|
|
{
|
|
await this.currentRegion.resolver.resolveObjects([obj], {});
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
async findObjectsByName(pattern: string | RegExp, minX?: number, maxX?: number, minY?: number, maxY?: number, minZ?: number, maxZ?: number): Promise<GameObject[]>
|
|
{
|
|
let objects: GameObject[] = [];
|
|
if (minX !== undefined && maxX !== undefined && minY !== undefined && maxY !== undefined && minZ !== undefined && maxZ !== undefined)
|
|
{
|
|
objects = await this.getObjectsInArea(minX, maxX, minY, maxY, minZ, maxZ, true);
|
|
}
|
|
else
|
|
{
|
|
objects = await this.getAllObjects({ resolve: true });
|
|
}
|
|
const idCheck: { [key: string]: boolean } = {};
|
|
const matches: GameObject[] = [];
|
|
const it = function(go: GameObject): void
|
|
{
|
|
if (go.name !== undefined)
|
|
{
|
|
let match = false;
|
|
if (pattern instanceof RegExp)
|
|
{
|
|
if (pattern.test(go.name))
|
|
{
|
|
match = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
match = micromatch.isMatch(go.name, pattern, { nocase: true });
|
|
}
|
|
|
|
if (match)
|
|
{
|
|
const fullID = go.FullID.toString();
|
|
if (!idCheck[fullID])
|
|
{
|
|
matches.push(go);
|
|
idCheck[fullID] = true;
|
|
}
|
|
}
|
|
}
|
|
if (go.children && go.children.length > 0)
|
|
{
|
|
for (const child of go.children)
|
|
{
|
|
it(child);
|
|
}
|
|
}
|
|
};
|
|
for (const go of objects)
|
|
{
|
|
it(go);
|
|
}
|
|
return matches;
|
|
}
|
|
|
|
getParcelAt(x: number, y: number): Promise<Parcel>
|
|
{
|
|
return this.currentRegion.getParcelProperties(x, y);
|
|
}
|
|
|
|
getParcels(): Promise<Parcel[]>
|
|
{
|
|
return this.currentRegion.getParcels();
|
|
}
|
|
|
|
async getAllObjects(options: GetObjectsOptions): Promise<GameObject[]>
|
|
{
|
|
const objs = await this.currentRegion.objects.getAllObjects();
|
|
if (options.resolve)
|
|
{
|
|
await this.currentRegion.resolver.resolveObjects(objs, options);
|
|
}
|
|
return objs;
|
|
}
|
|
|
|
async getObjectsInArea(minX: number, maxX: number, minY: number, maxY: number, minZ: number, maxZ: number, resolve: boolean = false): Promise<GameObject[]>
|
|
{
|
|
const objs = await this.currentRegion.objects.getObjectsInArea(minX, maxX, minY, maxY, minZ, maxZ);
|
|
if (resolve)
|
|
{
|
|
await this.currentRegion.resolver.resolveObjects(objs, {});
|
|
}
|
|
return objs;
|
|
}
|
|
|
|
async pruneObjects(checkList: GameObject[]): Promise<GameObject[]>
|
|
{
|
|
let uuids = [];
|
|
let objects = [];
|
|
const stillAlive: { [key: string]: GameObject } = {};
|
|
const checkObjects = async(uuidList: any[], objectList: GameObject[]) =>
|
|
{
|
|
|
|
const objRef: { [key: string]: GameObject } = {};
|
|
for (const obj of objectList)
|
|
{
|
|
objRef[obj.FullID.toString()] = obj;
|
|
}
|
|
const result = await this.currentRegion.caps.capsPostXML('GetObjectCost', {
|
|
'object_ids': uuidList
|
|
});
|
|
for (const u of Object.keys(result))
|
|
{
|
|
stillAlive[u] = objRef[u];
|
|
}
|
|
};
|
|
|
|
for (const o of checkList)
|
|
{
|
|
if (o.FullID)
|
|
{
|
|
uuids.push(new LLSD.UUID(o.FullID));
|
|
objects.push(o);
|
|
if (uuids.length > 256)
|
|
{
|
|
await checkObjects(uuids, objects);
|
|
uuids = [];
|
|
objects = [];
|
|
}
|
|
}
|
|
}
|
|
if (uuids.length > 0)
|
|
{
|
|
await checkObjects(uuids, objects);
|
|
}
|
|
const deadObjects: GameObject[] = [];
|
|
for (const o of checkList)
|
|
{
|
|
let found = false;
|
|
if (o.FullID)
|
|
{
|
|
const fullID = o.FullID.toString();
|
|
if (stillAlive[fullID])
|
|
{
|
|
found = true;
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
deadObjects.push(o);
|
|
}
|
|
}
|
|
return deadObjects;
|
|
}
|
|
|
|
setPersist(persist: boolean): void
|
|
{
|
|
this.currentRegion.objects.setPersist(persist);
|
|
}
|
|
|
|
async grabObject(localID: number | UUID,
|
|
grabOffset: Vector3 = Vector3.getZero(),
|
|
uvCoordinate: Vector3 = Vector3.getZero(),
|
|
stCoordinate: Vector3 = Vector3.getZero(),
|
|
faceIndex: number = 0,
|
|
position: Vector3 = Vector3.getZero(),
|
|
normal: Vector3 = Vector3.getZero(),
|
|
binormal: Vector3 = Vector3.getZero()): Promise<void>
|
|
{
|
|
if (localID instanceof UUID)
|
|
{
|
|
const obj: GameObject = this.currentRegion.objects.getObjectByUUID(localID);
|
|
localID = obj.ID;
|
|
}
|
|
const msg = new ObjectGrabMessage();
|
|
msg.AgentData = {
|
|
AgentID: this.agent.agentID,
|
|
SessionID: this.circuit.sessionID
|
|
};
|
|
msg.ObjectData = {
|
|
LocalID: localID,
|
|
GrabOffset: grabOffset
|
|
};
|
|
msg.SurfaceInfo = [
|
|
{
|
|
UVCoord: uvCoordinate,
|
|
STCoord: stCoordinate,
|
|
FaceIndex: faceIndex,
|
|
Position: position,
|
|
Normal: normal,
|
|
Binormal: binormal
|
|
}
|
|
];
|
|
const seqID = this.circuit.sendMessage(msg, PacketFlags.Reliable);
|
|
return this.circuit.waitForAck(seqID, 10000);
|
|
}
|
|
|
|
async deGrabObject(localID: number | UUID,
|
|
_grabOffset: Vector3 = Vector3.getZero(),
|
|
uvCoordinate: Vector3 = Vector3.getZero(),
|
|
stCoordinate: Vector3 = Vector3.getZero(),
|
|
faceIndex: number = 0,
|
|
position: Vector3 = Vector3.getZero(),
|
|
normal: Vector3 = Vector3.getZero(),
|
|
binormal: Vector3 = Vector3.getZero()): Promise<void>
|
|
{
|
|
if (localID instanceof UUID)
|
|
{
|
|
const obj: GameObject = this.currentRegion.objects.getObjectByUUID(localID);
|
|
localID = obj.ID;
|
|
}
|
|
const msg = new ObjectDeGrabMessage();
|
|
msg.AgentData = {
|
|
AgentID: this.agent.agentID,
|
|
SessionID: this.circuit.sessionID
|
|
};
|
|
msg.ObjectData = {
|
|
LocalID: localID
|
|
};
|
|
msg.SurfaceInfo = [
|
|
{
|
|
UVCoord: uvCoordinate,
|
|
STCoord: stCoordinate,
|
|
FaceIndex: faceIndex,
|
|
Position: position,
|
|
Normal: normal,
|
|
Binormal: binormal
|
|
}
|
|
];
|
|
const seqID = this.circuit.sendMessage(msg, PacketFlags.Reliable);
|
|
return this.circuit.waitForAck(seqID, 10000);
|
|
}
|
|
|
|
async dragGrabbedObject(localID: number | UUID,
|
|
grabPosition: Vector3,
|
|
grabOffset: Vector3 = Vector3.getZero(),
|
|
uvCoordinate: Vector3 = Vector3.getZero(),
|
|
stCoordinate: Vector3 = Vector3.getZero(),
|
|
faceIndex: number = 0,
|
|
position: Vector3 = Vector3.getZero(),
|
|
normal: Vector3 = Vector3.getZero(),
|
|
binormal: Vector3 = Vector3.getZero()): Promise<void>
|
|
{
|
|
// For some reason this message takes a UUID when the others take a LocalID - wtf?
|
|
if (!(localID instanceof UUID))
|
|
{
|
|
const obj: GameObject = this.currentRegion.objects.getObjectByLocalID(localID);
|
|
localID = obj.FullID;
|
|
}
|
|
const msg = new ObjectGrabUpdateMessage();
|
|
msg.AgentData = {
|
|
AgentID: this.agent.agentID,
|
|
SessionID: this.circuit.sessionID
|
|
};
|
|
msg.ObjectData = {
|
|
ObjectID: localID,
|
|
GrabOffsetInitial: grabOffset,
|
|
GrabPosition: grabPosition,
|
|
TimeSinceLast: 0
|
|
};
|
|
msg.SurfaceInfo = [
|
|
{
|
|
UVCoord: uvCoordinate,
|
|
STCoord: stCoordinate,
|
|
FaceIndex: faceIndex,
|
|
Position: position,
|
|
Normal: normal,
|
|
Binormal: binormal
|
|
}
|
|
];
|
|
const seqID = this.circuit.sendMessage(msg, PacketFlags.Reliable);
|
|
return this.circuit.waitForAck(seqID, 10000);
|
|
}
|
|
|
|
async touchObject(localID: number | UUID,
|
|
grabOffset: Vector3 = Vector3.getZero(),
|
|
uvCoordinate: Vector3 = Vector3.getZero(),
|
|
stCoordinate: Vector3 = Vector3.getZero(),
|
|
faceIndex: number = 0,
|
|
position: Vector3 = Vector3.getZero(),
|
|
normal: Vector3 = Vector3.getZero(),
|
|
binormal: Vector3 = Vector3.getZero()): Promise<void>
|
|
{
|
|
if (localID instanceof UUID)
|
|
{
|
|
const obj: GameObject = this.currentRegion.objects.getObjectByUUID(localID);
|
|
localID = obj.ID;
|
|
}
|
|
await this.grabObject(localID, grabOffset, uvCoordinate, stCoordinate, faceIndex, position, normal, binormal);
|
|
return this.deGrabObject(localID, grabOffset, uvCoordinate, stCoordinate, faceIndex, position, normal, binormal);
|
|
}
|
|
}
|