- Implement camera controls
- Option to resolve object properties when fetching from object store (names, descriptions etc). Can be more efficient - TODO: use FamilyProperties for child prims. - Refactored objectstore to reduce code duplication
This commit is contained in:
@@ -19,9 +19,9 @@ import {RezSingleAttachmentFromInvMessage} from './messages/RezSingleAttachmentF
|
||||
import {AttachmentPoint} from '../enums/AttachmentPoint';
|
||||
import {Utils} from './Utils';
|
||||
import {ClientEvents} from './ClientEvents';
|
||||
import {IGameObject} from './interfaces/IGameObject';
|
||||
import Timer = NodeJS.Timer;
|
||||
import {ControlFlags, GroupChatSessionAgentListEvent, AgentFlags, PacketFlags, AssetType} from '..';
|
||||
import {GameObject} from './GameObject';
|
||||
|
||||
export class Agent
|
||||
{
|
||||
@@ -53,7 +53,11 @@ export class Agent
|
||||
uiFlags: {
|
||||
'allowFirstLife'?: boolean
|
||||
} = {};
|
||||
lookAt: Vector3;
|
||||
cameraLookAt: Vector3 = new Vector3([0.979546, 0.105575, -0.171303]);
|
||||
cameraCenter: Vector3 = new Vector3([199.58, 203.95, 24.304]);
|
||||
cameraLeftAxis: Vector3 = new Vector3([-1.0, 0.0, 0]);
|
||||
cameraUpAxis: Vector3 = new Vector3([0.0, 0.0, 1.0]);
|
||||
cameraFar = 1;
|
||||
maxGroups: number;
|
||||
agentFlags: number;
|
||||
startLocation: string;
|
||||
@@ -164,11 +168,11 @@ export class Agent
|
||||
HeadRotation: Quaternion.getIdentity(),
|
||||
BodyRotation: Quaternion.getIdentity(),
|
||||
State: AgentState.None,
|
||||
CameraCenter: new Vector3([199.58, 203.95, 24.304]),
|
||||
CameraAtAxis: new Vector3([0.979546, 0.105575, -0.171303]),
|
||||
CameraLeftAxis: new Vector3([-0.107158, 0.994242, 0]),
|
||||
CameraUpAxis: new Vector3([0.170316, 0.018357, 0.985218]),
|
||||
Far: 128,
|
||||
CameraCenter: this.cameraCenter,
|
||||
CameraAtAxis: this.cameraLookAt,
|
||||
CameraLeftAxis: this.cameraLeftAxis,
|
||||
CameraUpAxis: this.cameraUpAxis,
|
||||
Far: this.cameraFar,
|
||||
ControlFlags: this.controlFlags,
|
||||
Flags: AgentFlags.None
|
||||
};
|
||||
@@ -266,7 +270,7 @@ export class Agent
|
||||
if (item.type === 6)
|
||||
{
|
||||
let found = false;
|
||||
wornObjects.forEach((obj: IGameObject) =>
|
||||
wornObjects.forEach((obj: GameObject) =>
|
||||
{
|
||||
if (obj.hasNameValueEntry('AttachItemID'))
|
||||
{
|
||||
|
||||
@@ -366,6 +366,10 @@ export class EventQueueClient
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'ObjectPhysicsProperties':
|
||||
{
|
||||
break;
|
||||
}
|
||||
case 'TeleportFinish':
|
||||
{
|
||||
const info = event['body']['Info'][0];
|
||||
|
||||
130
lib/classes/GameObject.ts
Normal file
130
lib/classes/GameObject.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import {Vector3} from './Vector3';
|
||||
import {UUID} from './UUID';
|
||||
import {Quaternion} from './Quaternion';
|
||||
import {Tree} from '../enums/Tree';
|
||||
import {SoundFlags} from '..';
|
||||
import {Vector4} from './Vector4';
|
||||
import {TextureEntry} from './TextureEntry';
|
||||
import {Color4} from './Color4';
|
||||
import {ParticleSystem} from './ParticleSystem';
|
||||
import {ITreeBoundingBox} from './interfaces/ITreeBoundingBox';
|
||||
import {NameValue} from './NameValue';
|
||||
import {PCode} from '../enums/PCode';
|
||||
import {Utils} from './Utils';
|
||||
import * as Long from 'long';
|
||||
|
||||
export class GameObject
|
||||
{
|
||||
creatorID?: UUID;
|
||||
creationDate?: Long;
|
||||
baseMask?: number;
|
||||
ownerMask?: number;
|
||||
groupMask?: number;
|
||||
everyoneMask?: number;
|
||||
nextOwnerMask?: number;
|
||||
ownershipCost?: number;
|
||||
saleType?: number;
|
||||
salePrice?: number;
|
||||
aggregatePerms?: number;
|
||||
aggregatePermTextures?: number;
|
||||
aggregatePermTexturesOwner?: number;
|
||||
category: number;
|
||||
inventorySerial: number;
|
||||
itemID: UUID;
|
||||
folderID: UUID;
|
||||
fromTaskID: UUID;
|
||||
lastOwnerID: UUID;
|
||||
name?: string;
|
||||
description?: string;
|
||||
touchName?: string;
|
||||
sitName?: string;
|
||||
textureID?: string;
|
||||
resolvedAt?: number;
|
||||
totalChildren?: number;
|
||||
|
||||
|
||||
children?: GameObject[];
|
||||
rtreeEntry?: ITreeBoundingBox;
|
||||
ID = 0;
|
||||
FullID = UUID.random();
|
||||
ParentID = 0;
|
||||
OwnerID = UUID.zero();
|
||||
IsAttachment = false;
|
||||
NameValue: {[key: string]: NameValue} = {};
|
||||
PCode: PCode = PCode.None;
|
||||
|
||||
State?: number;
|
||||
CRC?: number;
|
||||
Material?: number;
|
||||
ClickAction?: number;
|
||||
Scale?: Vector3;
|
||||
ObjectData?: Buffer;
|
||||
UpdateFlags?: number;
|
||||
Flags?: number;
|
||||
PathCurve?: number;
|
||||
ProfileCurve?: number;
|
||||
PathBegin?: number;
|
||||
PathEnd?: number;
|
||||
PathScaleX?: number;
|
||||
PathScaleY?: number;
|
||||
PathShearX?: number;
|
||||
PathShearY?: number;
|
||||
PathTwist?: number;
|
||||
PathTwistBegin?: number;
|
||||
PathRadiusOffset?: number;
|
||||
PathTaperX?: number;
|
||||
PathTaperY?: number;
|
||||
PathRevolutions?: number;
|
||||
PathSkew?: number;
|
||||
ProfileBegin?: number;
|
||||
ProfileEnd?: number;
|
||||
ProfileHollow?: number;
|
||||
TextureEntry?: TextureEntry;
|
||||
TextureAnim?: Buffer;
|
||||
Data?: Buffer;
|
||||
Text?: string;
|
||||
TextColor?: Color4;
|
||||
MediaURL?: string;
|
||||
PSBlock?: Buffer;
|
||||
JointType?: number;
|
||||
JointPivot?: Vector3;
|
||||
JointAxisOrAnchor?: Vector3;
|
||||
Position?: Vector3;
|
||||
Rotation?: Quaternion;
|
||||
CollisionPlane?: Vector4;
|
||||
Velocity?: Vector3;
|
||||
Acceleration?: Vector3;
|
||||
AngularVelocity?: Vector3;
|
||||
TreeSpecies?: Tree;
|
||||
Sound?: UUID;
|
||||
SoundGain?: number;
|
||||
SoundFlags?: SoundFlags;
|
||||
SoundRadius?: number;
|
||||
Particles?: ParticleSystem;
|
||||
|
||||
constructor()
|
||||
{
|
||||
this.Position = Vector3.getZero();
|
||||
this.Rotation = Quaternion.getIdentity();
|
||||
this.AngularVelocity = Vector3.getZero();
|
||||
this.TreeSpecies = 0;
|
||||
this.SoundFlags = 0;
|
||||
this.SoundRadius = 1.0;
|
||||
this.SoundGain = 1.0;
|
||||
this.ParentID = 0;
|
||||
}
|
||||
|
||||
hasNameValueEntry(key: string): boolean
|
||||
{
|
||||
return this.NameValue[key] !== undefined;
|
||||
}
|
||||
|
||||
getNameValueEntry(key: string): string
|
||||
{
|
||||
if (this.NameValue[key])
|
||||
{
|
||||
return this.NameValue[key].value;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
import {Vector3} from './Vector3';
|
||||
import {UUID} from './UUID';
|
||||
import {PCode} from '../enums/PCode';
|
||||
import {Quaternion} from './Quaternion';
|
||||
import {Tree} from '../enums/Tree';
|
||||
import {NameValue} from './NameValue';
|
||||
import {IGameObject} from './interfaces/IGameObject';
|
||||
import {SoundFlags} from '..';
|
||||
import {ITreeBoundingBox} from './interfaces/ITreeBoundingBox';
|
||||
import {Vector4} from './Vector4';
|
||||
import {TextureEntry} from './TextureEntry';
|
||||
import {Color4} from './Color4';
|
||||
import {ParticleSystem} from './ParticleSystem';
|
||||
|
||||
export class GameObjectFull implements IGameObject
|
||||
{
|
||||
rtreeEntry?: ITreeBoundingBox;
|
||||
ID: number;
|
||||
State: number;
|
||||
FullID: UUID;
|
||||
CRC: number;
|
||||
PCode: PCode;
|
||||
Material: number;
|
||||
ClickAction: number;
|
||||
Scale: Vector3;
|
||||
ObjectData: Buffer;
|
||||
ParentID: number;
|
||||
UpdateFlags: number;
|
||||
Flags: number;
|
||||
PathCurve: number;
|
||||
ProfileCurve: number;
|
||||
PathBegin: number;
|
||||
PathEnd: number;
|
||||
PathScaleX: number;
|
||||
PathScaleY: number;
|
||||
PathShearX: number;
|
||||
PathShearY: number;
|
||||
PathTwist: number;
|
||||
PathTwistBegin: number;
|
||||
PathRadiusOffset: number;
|
||||
PathTaperX: number;
|
||||
PathTaperY: number;
|
||||
PathRevolutions: number;
|
||||
PathSkew: number;
|
||||
ProfileBegin: number;
|
||||
ProfileEnd: number;
|
||||
ProfileHollow: number;
|
||||
TextureEntry: TextureEntry;
|
||||
TextureAnim: Buffer;
|
||||
Data: Buffer;
|
||||
Text: string;
|
||||
TextColor: Color4;
|
||||
MediaURL: string;
|
||||
PSBlock: Buffer;
|
||||
OwnerID: UUID;
|
||||
JointType: number;
|
||||
JointPivot: Vector3;
|
||||
JointAxisOrAnchor: Vector3;
|
||||
Position: Vector3;
|
||||
Rotation: Quaternion;
|
||||
CollisionPlane: Vector4;
|
||||
Velocity: Vector3;
|
||||
Acceleration: Vector3;
|
||||
AngularVelocity: Vector3;
|
||||
TreeSpecies: Tree;
|
||||
Sound: UUID;
|
||||
SoundGain: number;
|
||||
SoundFlags: SoundFlags;
|
||||
SoundRadius: number;
|
||||
IsAttachment: boolean;
|
||||
NameValue: {[key: string]: NameValue};
|
||||
Particles: ParticleSystem;
|
||||
constructor()
|
||||
{
|
||||
this.Position = Vector3.getZero();
|
||||
this.Rotation = Quaternion.getIdentity();
|
||||
this.IsAttachment = false;
|
||||
this.NameValue = {};
|
||||
this.AngularVelocity = Vector3.getZero();
|
||||
this.TreeSpecies = 0;
|
||||
this.SoundFlags = 0;
|
||||
this.SoundRadius = 1.0;
|
||||
this.SoundGain = 1.0;
|
||||
this.ParentID = 0;
|
||||
}
|
||||
|
||||
hasNameValueEntry(key: string): boolean
|
||||
{
|
||||
if (this.NameValue['AttachItemID'])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getNameValueEntry(key: string): string
|
||||
{
|
||||
if (this.NameValue['AttachItemID'])
|
||||
{
|
||||
return this.NameValue['AttachItemID'].value;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import {UUID} from './UUID';
|
||||
import {IGameObject} from './interfaces/IGameObject';
|
||||
import {NameValue} from './NameValue';
|
||||
import {PCode} from '../enums/PCode';
|
||||
import {ITreeBoundingBox} from './interfaces/ITreeBoundingBox';
|
||||
|
||||
export class GameObjectLite implements IGameObject
|
||||
{
|
||||
rtreeEntry?: ITreeBoundingBox;
|
||||
ID: number;
|
||||
FullID: UUID;
|
||||
ParentID: number;
|
||||
OwnerID: UUID;
|
||||
IsAttachment: boolean;
|
||||
NameValue: {[key: string]: NameValue};
|
||||
PCode: PCode;
|
||||
constructor()
|
||||
{
|
||||
this.IsAttachment = false;
|
||||
}
|
||||
|
||||
hasNameValueEntry(key: string): boolean
|
||||
{
|
||||
return this.NameValue['AttachItemID'] !== undefined;
|
||||
}
|
||||
|
||||
getNameValueEntry(key: string): string
|
||||
{
|
||||
if (this.NameValue['AttachItemID'])
|
||||
{
|
||||
return this.NameValue['AttachItemID'].value;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -246,7 +246,7 @@ export class LoginResponse
|
||||
});
|
||||
break;
|
||||
case 'look_at':
|
||||
this.agent.lookAt = LoginResponse.parseVector3(val);
|
||||
this.agent.cameraLookAt = LoginResponse.parseVector3(val);
|
||||
break;
|
||||
case 'openid_url':
|
||||
this.agent.openID.url = String(val);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,21 +15,23 @@ import {PCode} from '../enums/PCode';
|
||||
import {ClientEvents} from './ClientEvents';
|
||||
import {KillObjectMessage} from './messages/KillObject';
|
||||
import {IObjectStore} from './interfaces/IObjectStore';
|
||||
import {GameObjectLite} from './GameObjectLite';
|
||||
import {NameValue} from './NameValue';
|
||||
import {BotOptionFlags, CompressedFlags} from '..';
|
||||
import {IGameObject} from './interfaces/IGameObject';
|
||||
import {GameObjectFull} from './GameObjectFull';
|
||||
import {GameObject} from './GameObject';
|
||||
import {RBush3D} from 'rbush-3d/dist';
|
||||
import {ITreeBoundingBox} from './interfaces/ITreeBoundingBox';
|
||||
|
||||
export class ObjectStoreLite implements IObjectStore
|
||||
{
|
||||
private circuit: Circuit;
|
||||
private agent: Agent;
|
||||
private objects: { [key: number]: GameObjectLite } = {};
|
||||
private objectsByUUID: { [key: string]: number } = {};
|
||||
private objectsByParent: { [key: number]: number[] } = {};
|
||||
private clientEvents: ClientEvents;
|
||||
private options: BotOptionFlags;
|
||||
protected circuit: Circuit;
|
||||
protected agent: Agent;
|
||||
protected objects: { [key: number]: GameObject } = {};
|
||||
protected objectsByUUID: { [key: string]: number } = {};
|
||||
protected objectsByParent: { [key: number]: number[] } = {};
|
||||
protected clientEvents: ClientEvents;
|
||||
protected options: BotOptionFlags;
|
||||
|
||||
rtree?: RBush3D;
|
||||
|
||||
constructor(circuit: Circuit, agent: Agent, clientEvents: ClientEvents, options: BotOptionFlags)
|
||||
{
|
||||
@@ -51,295 +53,319 @@ export class ObjectStoreLite implements IObjectStore
|
||||
{
|
||||
case Message.ObjectUpdate:
|
||||
const objectUpdate = packet.message as ObjectUpdateMessage;
|
||||
objectUpdate.ObjectData.forEach((objData) =>
|
||||
{
|
||||
const localID = objData.ID;
|
||||
const parentID = objData.ParentID;
|
||||
let addToParentList = true;
|
||||
|
||||
if (this.objects[localID])
|
||||
{
|
||||
if (this.objects[localID].ParentID !== parentID && this.objectsByParent[parentID])
|
||||
{
|
||||
const ind = this.objectsByParent[parentID].indexOf(localID);
|
||||
if (ind !== -1)
|
||||
{
|
||||
this.objectsByParent[parentID].splice(ind, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
addToParentList = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.objects[localID] = new GameObjectLite();
|
||||
}
|
||||
|
||||
const obj = this.objects[localID];
|
||||
obj.ID = objData.ID;
|
||||
obj.FullID = objData.FullID;
|
||||
obj.ParentID = objData.ParentID;
|
||||
obj.OwnerID = objData.OwnerID;
|
||||
obj.PCode = objData.PCode;
|
||||
|
||||
this.objects[localID].NameValue = this.parseNameValues(Utils.BufferToStringSimple(objData.NameValue));
|
||||
|
||||
if (objData.PCode === PCode.Avatar && this.objects[localID].FullID.toString() === this.agent.agentID.toString())
|
||||
{
|
||||
this.agent.localID = localID;
|
||||
|
||||
if (this.options & BotOptionFlags.StoreMyAttachmentsOnly)
|
||||
{
|
||||
Object.keys(this.objectsByParent).forEach((objParentID: string) =>
|
||||
{
|
||||
const parent = parseInt(objParentID, 10);
|
||||
if (parent !== this.agent.localID)
|
||||
{
|
||||
let foundAvatars = false;
|
||||
this.objectsByParent[parent].forEach((objID) =>
|
||||
{
|
||||
if (this.objects[objID])
|
||||
{
|
||||
const o = this.objects[objID];
|
||||
if (o.PCode === PCode.Avatar)
|
||||
{
|
||||
foundAvatars = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (this.objects[parent])
|
||||
{
|
||||
const o = this.objects[parent];
|
||||
if (o.PCode === PCode.Avatar)
|
||||
{
|
||||
foundAvatars = true;
|
||||
}
|
||||
}
|
||||
if (!foundAvatars)
|
||||
{
|
||||
this.deleteObject(parent);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.objectsByUUID[objData.FullID.toString()] = localID;
|
||||
if (!this.objectsByParent[parentID])
|
||||
{
|
||||
this.objectsByParent[parentID] = [];
|
||||
}
|
||||
if (addToParentList)
|
||||
{
|
||||
this.objectsByParent[parentID].push(localID);
|
||||
}
|
||||
|
||||
if (objData.PCode !== PCode.Avatar && this.options & BotOptionFlags.StoreMyAttachmentsOnly)
|
||||
{
|
||||
if (this.agent.localID !== 0 && obj.ParentID !== this.agent.localID)
|
||||
{
|
||||
// Drop object
|
||||
this.deleteObject(localID);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.objectUpdate(objectUpdate);
|
||||
break;
|
||||
case Message.ObjectUpdateCached:
|
||||
const objectUpdateCached = packet.message as ObjectUpdateCachedMessage;
|
||||
const rmo = new RequestMultipleObjectsMessage();
|
||||
rmo.AgentData = {
|
||||
AgentID: this.agent.agentID,
|
||||
SessionID: this.circuit.sessionID
|
||||
};
|
||||
rmo.ObjectData = [];
|
||||
objectUpdateCached.ObjectData.forEach((obj) =>
|
||||
{
|
||||
rmo.ObjectData.push({
|
||||
CacheMissType: 0,
|
||||
ID: obj.ID
|
||||
});
|
||||
});
|
||||
circuit.sendMessage(rmo, 0);
|
||||
this.objectUpdateCached(objectUpdateCached);
|
||||
break;
|
||||
case Message.ObjectUpdateCompressed:
|
||||
{
|
||||
const objectUpdateCompressed = packet.message as ObjectUpdateCompressedMessage;
|
||||
for (const obj of objectUpdateCompressed.ObjectData)
|
||||
{
|
||||
const flags = obj.UpdateFlags;
|
||||
const buf = obj.Data;
|
||||
let pos = 0;
|
||||
|
||||
const fullID = new UUID(buf, pos);
|
||||
pos += 16;
|
||||
const localID = buf.readUInt32LE(pos);
|
||||
pos += 4;
|
||||
const pcode = buf.readUInt8(pos++);
|
||||
let newObj = false;
|
||||
if (!this.objects[localID])
|
||||
{
|
||||
newObj = true;
|
||||
this.objects[localID] = new GameObjectLite();
|
||||
}
|
||||
const o = this.objects[localID];
|
||||
o.ID = localID;
|
||||
o.PCode = pcode;
|
||||
this.objectsByUUID[fullID.toString()] = localID;
|
||||
o.FullID = fullID;
|
||||
|
||||
|
||||
pos++;
|
||||
|
||||
pos = pos + 4;
|
||||
pos++;
|
||||
pos++;
|
||||
|
||||
pos = pos + 12;
|
||||
|
||||
pos = pos + 12;
|
||||
|
||||
pos = pos + 12;
|
||||
const compressedflags: CompressedFlags = buf.readUInt32LE(pos);
|
||||
pos = pos + 4;
|
||||
o.OwnerID = new UUID(buf, pos);
|
||||
pos += 16;
|
||||
|
||||
if (compressedflags & CompressedFlags.HasAngularVelocity)
|
||||
{
|
||||
pos = pos + 12;
|
||||
}
|
||||
if (compressedflags & CompressedFlags.HasParent)
|
||||
{
|
||||
const newParentID = buf.readUInt32LE(pos);
|
||||
pos += 4;
|
||||
let add = true;
|
||||
if (!newObj)
|
||||
{
|
||||
if (newParentID !== o.ParentID)
|
||||
{
|
||||
const index = this.objectsByParent[o.ParentID].indexOf(localID);
|
||||
if (index !== -1)
|
||||
{
|
||||
this.objectsByParent[o.ParentID].splice(index, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
add = false;
|
||||
}
|
||||
}
|
||||
if (add)
|
||||
{
|
||||
if (!this.objectsByParent[newParentID])
|
||||
{
|
||||
this.objectsByParent[newParentID] = [];
|
||||
}
|
||||
this.objectsByParent[newParentID].push(localID);
|
||||
}
|
||||
o.ParentID = newParentID;
|
||||
}
|
||||
if (pcode !== PCode.Avatar && newObj && this.options & BotOptionFlags.StoreMyAttachmentsOnly)
|
||||
{
|
||||
if (this.agent.localID !== 0 && o.ParentID !== this.agent.localID)
|
||||
{
|
||||
// Drop object
|
||||
this.deleteObject(localID);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (compressedflags & CompressedFlags.Tree)
|
||||
{
|
||||
pos++;
|
||||
}
|
||||
else if (compressedflags & CompressedFlags.ScratchPad)
|
||||
{
|
||||
const scratchPadSize = buf.readUInt8(pos++);
|
||||
// Ignore this data
|
||||
pos = pos + scratchPadSize;
|
||||
}
|
||||
if (compressedflags & CompressedFlags.HasText)
|
||||
{
|
||||
// Read null terminated string
|
||||
const result = Utils.BufferToString(buf, pos);
|
||||
|
||||
pos += result.readLength;
|
||||
pos = pos + 4;
|
||||
}
|
||||
if (compressedflags & CompressedFlags.MediaURL)
|
||||
{
|
||||
const result = Utils.BufferToString(buf, pos);
|
||||
|
||||
pos += result.readLength;
|
||||
}
|
||||
if (compressedflags & CompressedFlags.HasParticles)
|
||||
{
|
||||
// TODO: Particle system block
|
||||
pos += 86;
|
||||
}
|
||||
|
||||
// Extra params
|
||||
pos = this.readExtraParams(buf, pos, o);
|
||||
|
||||
if (compressedflags & CompressedFlags.HasSound)
|
||||
{
|
||||
pos = pos + 16;
|
||||
pos += 4;
|
||||
pos++;
|
||||
pos = pos + 4;
|
||||
}
|
||||
if (compressedflags & CompressedFlags.HasNameValues)
|
||||
{
|
||||
const result = Utils.BufferToString(buf, pos);
|
||||
o.NameValue = this.parseNameValues(result.result);
|
||||
pos += result.readLength;
|
||||
}
|
||||
pos++;
|
||||
pos = pos + 2;
|
||||
pos = pos + 2;
|
||||
pos = pos + 12;
|
||||
pos = pos + 2;
|
||||
pos = pos + 2;
|
||||
pos = pos + 2;
|
||||
const textureEntryLength = buf.readUInt32LE(pos);
|
||||
pos = pos + 4;
|
||||
// TODO: Properly parse textureentry;
|
||||
pos = pos + textureEntryLength;
|
||||
|
||||
if (compressedflags & CompressedFlags.TextureAnimation)
|
||||
{
|
||||
// TODO: Properly parse textureAnim
|
||||
pos = pos + 4;
|
||||
}
|
||||
|
||||
o.IsAttachment = (compressedflags & CompressedFlags.HasNameValues) !== 0 && o.ParentID !== 0;
|
||||
};
|
||||
|
||||
this.objectUpdateCompressed(objectUpdateCompressed);
|
||||
break;
|
||||
}
|
||||
case Message.ImprovedTerseObjectUpdate:
|
||||
const objectUpdateTerse = packet.message as ImprovedTerseObjectUpdateMessage;
|
||||
// TODO: ImprovedTerseObjectUPdate
|
||||
this.objectUpdateTerse(objectUpdateTerse);
|
||||
break;
|
||||
case Message.MultipleObjectUpdate:
|
||||
const multipleObjectUpdate = packet.message as MultipleObjectUpdateMessage;
|
||||
// TODO: multipleObjectUpdate
|
||||
console.error('TODO: MultipleObjectUpdate');
|
||||
this.objectUpdateMultiple(multipleObjectUpdate);
|
||||
break;
|
||||
case Message.KillObject:
|
||||
const killObj = packet.message as KillObjectMessage;
|
||||
killObj.ObjectData.forEach((obj) =>
|
||||
{
|
||||
const objectID = obj.ID;
|
||||
this.deleteObject(objectID);
|
||||
});
|
||||
this.killObject(killObj);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected objectUpdate(objectUpdate: ObjectUpdateMessage)
|
||||
{
|
||||
objectUpdate.ObjectData.forEach((objData) =>
|
||||
{
|
||||
const localID = objData.ID;
|
||||
const parentID = objData.ParentID;
|
||||
let addToParentList = true;
|
||||
|
||||
if (this.objects[localID])
|
||||
{
|
||||
if (this.objects[localID].ParentID !== parentID && this.objectsByParent[parentID])
|
||||
{
|
||||
const ind = this.objectsByParent[parentID].indexOf(localID);
|
||||
if (ind !== -1)
|
||||
{
|
||||
this.objectsByParent[parentID].splice(ind, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
addToParentList = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.objects[localID] = new GameObject();
|
||||
}
|
||||
|
||||
const obj = this.objects[localID];
|
||||
obj.ID = objData.ID;
|
||||
obj.FullID = objData.FullID;
|
||||
obj.ParentID = objData.ParentID;
|
||||
obj.OwnerID = objData.OwnerID;
|
||||
obj.PCode = objData.PCode;
|
||||
|
||||
this.objects[localID].NameValue = this.parseNameValues(Utils.BufferToStringSimple(objData.NameValue));
|
||||
|
||||
if (objData.PCode === PCode.Avatar && this.objects[localID].FullID.toString() === this.agent.agentID.toString())
|
||||
{
|
||||
this.agent.localID = localID;
|
||||
|
||||
if (this.options & BotOptionFlags.StoreMyAttachmentsOnly)
|
||||
{
|
||||
Object.keys(this.objectsByParent).forEach((objParentID: string) =>
|
||||
{
|
||||
const parent = parseInt(objParentID, 10);
|
||||
if (parent !== this.agent.localID)
|
||||
{
|
||||
let foundAvatars = false;
|
||||
this.objectsByParent[parent].forEach((objID) =>
|
||||
{
|
||||
if (this.objects[objID])
|
||||
{
|
||||
const o = this.objects[objID];
|
||||
if (o.PCode === PCode.Avatar)
|
||||
{
|
||||
foundAvatars = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (this.objects[parent])
|
||||
{
|
||||
const o = this.objects[parent];
|
||||
if (o.PCode === PCode.Avatar)
|
||||
{
|
||||
foundAvatars = true;
|
||||
}
|
||||
}
|
||||
if (!foundAvatars)
|
||||
{
|
||||
this.deleteObject(parent);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.objectsByUUID[objData.FullID.toString()] = localID;
|
||||
if (!this.objectsByParent[parentID])
|
||||
{
|
||||
this.objectsByParent[parentID] = [];
|
||||
}
|
||||
if (addToParentList)
|
||||
{
|
||||
this.objectsByParent[parentID].push(localID);
|
||||
}
|
||||
|
||||
if (objData.PCode !== PCode.Avatar && this.options & BotOptionFlags.StoreMyAttachmentsOnly)
|
||||
{
|
||||
if (this.agent.localID !== 0 && obj.ParentID !== this.agent.localID)
|
||||
{
|
||||
// Drop object
|
||||
this.deleteObject(localID);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected objectUpdateCached(objectUpdateCached: ObjectUpdateCachedMessage)
|
||||
{
|
||||
const rmo = new RequestMultipleObjectsMessage();
|
||||
rmo.AgentData = {
|
||||
AgentID: this.agent.agentID,
|
||||
SessionID: this.circuit.sessionID
|
||||
};
|
||||
rmo.ObjectData = [];
|
||||
objectUpdateCached.ObjectData.forEach((obj) =>
|
||||
{
|
||||
rmo.ObjectData.push({
|
||||
CacheMissType: 0,
|
||||
ID: obj.ID
|
||||
});
|
||||
});
|
||||
this.circuit.sendMessage(rmo, 0);
|
||||
}
|
||||
|
||||
protected objectUpdateCompressed(objectUpdateCompressed: ObjectUpdateCompressedMessage)
|
||||
{
|
||||
for (const obj of objectUpdateCompressed.ObjectData)
|
||||
{
|
||||
const flags = obj.UpdateFlags;
|
||||
const buf = obj.Data;
|
||||
let pos = 0;
|
||||
|
||||
const fullID = new UUID(buf, pos);
|
||||
pos += 16;
|
||||
const localID = buf.readUInt32LE(pos);
|
||||
pos += 4;
|
||||
const pcode = buf.readUInt8(pos++);
|
||||
let newObj = false;
|
||||
if (!this.objects[localID])
|
||||
{
|
||||
newObj = true;
|
||||
this.objects[localID] = new GameObject();
|
||||
}
|
||||
const o = this.objects[localID];
|
||||
o.ID = localID;
|
||||
o.PCode = pcode;
|
||||
this.objectsByUUID[fullID.toString()] = localID;
|
||||
o.FullID = fullID;
|
||||
|
||||
|
||||
pos++;
|
||||
|
||||
pos = pos + 4;
|
||||
pos++;
|
||||
pos++;
|
||||
|
||||
pos = pos + 12;
|
||||
|
||||
pos = pos + 12;
|
||||
|
||||
pos = pos + 12;
|
||||
const compressedflags: CompressedFlags = buf.readUInt32LE(pos);
|
||||
pos = pos + 4;
|
||||
o.OwnerID = new UUID(buf, pos);
|
||||
pos += 16;
|
||||
|
||||
if (compressedflags & CompressedFlags.HasAngularVelocity)
|
||||
{
|
||||
pos = pos + 12;
|
||||
}
|
||||
if (compressedflags & CompressedFlags.HasParent)
|
||||
{
|
||||
const newParentID = buf.readUInt32LE(pos);
|
||||
pos += 4;
|
||||
let add = true;
|
||||
if (!newObj)
|
||||
{
|
||||
if (newParentID !== o.ParentID)
|
||||
{
|
||||
const index = this.objectsByParent[o.ParentID].indexOf(localID);
|
||||
if (index !== -1)
|
||||
{
|
||||
this.objectsByParent[o.ParentID].splice(index, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
add = false;
|
||||
}
|
||||
}
|
||||
if (add)
|
||||
{
|
||||
if (!this.objectsByParent[newParentID])
|
||||
{
|
||||
this.objectsByParent[newParentID] = [];
|
||||
}
|
||||
this.objectsByParent[newParentID].push(localID);
|
||||
}
|
||||
o.ParentID = newParentID;
|
||||
}
|
||||
if (pcode !== PCode.Avatar && newObj && this.options & BotOptionFlags.StoreMyAttachmentsOnly)
|
||||
{
|
||||
if (this.agent.localID !== 0 && o.ParentID !== this.agent.localID)
|
||||
{
|
||||
// Drop object
|
||||
this.deleteObject(localID);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (compressedflags & CompressedFlags.Tree)
|
||||
{
|
||||
pos++;
|
||||
}
|
||||
else if (compressedflags & CompressedFlags.ScratchPad)
|
||||
{
|
||||
const scratchPadSize = buf.readUInt8(pos++);
|
||||
// Ignore this data
|
||||
pos = pos + scratchPadSize;
|
||||
}
|
||||
if (compressedflags & CompressedFlags.HasText)
|
||||
{
|
||||
// Read null terminated string
|
||||
const result = Utils.BufferToString(buf, pos);
|
||||
|
||||
pos += result.readLength;
|
||||
pos = pos + 4;
|
||||
}
|
||||
if (compressedflags & CompressedFlags.MediaURL)
|
||||
{
|
||||
const result = Utils.BufferToString(buf, pos);
|
||||
|
||||
pos += result.readLength;
|
||||
}
|
||||
if (compressedflags & CompressedFlags.HasParticles)
|
||||
{
|
||||
// TODO: Particle system block
|
||||
pos += 86;
|
||||
}
|
||||
|
||||
// Extra params
|
||||
pos = this.readExtraParams(buf, pos, o);
|
||||
|
||||
if (compressedflags & CompressedFlags.HasSound)
|
||||
{
|
||||
pos = pos + 16;
|
||||
pos += 4;
|
||||
pos++;
|
||||
pos = pos + 4;
|
||||
}
|
||||
if (compressedflags & CompressedFlags.HasNameValues)
|
||||
{
|
||||
const result = Utils.BufferToString(buf, pos);
|
||||
o.NameValue = this.parseNameValues(result.result);
|
||||
pos += result.readLength;
|
||||
}
|
||||
pos++;
|
||||
pos = pos + 2;
|
||||
pos = pos + 2;
|
||||
pos = pos + 12;
|
||||
pos = pos + 2;
|
||||
pos = pos + 2;
|
||||
pos = pos + 2;
|
||||
const textureEntryLength = buf.readUInt32LE(pos);
|
||||
pos = pos + 4;
|
||||
// TODO: Properly parse textureentry;
|
||||
pos = pos + textureEntryLength;
|
||||
|
||||
if (compressedflags & CompressedFlags.TextureAnimation)
|
||||
{
|
||||
// TODO: Properly parse textureAnim
|
||||
pos = pos + 4;
|
||||
}
|
||||
|
||||
o.IsAttachment = (compressedflags & CompressedFlags.HasNameValues) !== 0 && o.ParentID !== 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected objectUpdateTerse(objectUpdateTerse: ImprovedTerseObjectUpdateMessage)
|
||||
{ }
|
||||
|
||||
protected objectUpdateMultiple(objectUpdateMultiple: MultipleObjectUpdateMessage)
|
||||
{ }
|
||||
|
||||
protected killObject(killObj: KillObjectMessage)
|
||||
{
|
||||
killObj.ObjectData.forEach((obj) =>
|
||||
{
|
||||
const objectID = obj.ID;
|
||||
this.deleteObject(objectID);
|
||||
});
|
||||
}
|
||||
|
||||
deleteObject(objectID: number)
|
||||
{
|
||||
if (this.objects[objectID])
|
||||
@@ -371,11 +397,15 @@ export class ObjectStoreLite implements IObjectStore
|
||||
this.objectsByParent[parentID].splice(ind, 1);
|
||||
}
|
||||
}
|
||||
if (this.rtree && this.objects[objectID].rtreeEntry !== undefined)
|
||||
{
|
||||
this.rtree.remove(this.objects[objectID].rtreeEntry);
|
||||
}
|
||||
delete this.objects[objectID];
|
||||
}
|
||||
}
|
||||
|
||||
readExtraParams(buf: Buffer, pos: number, o: GameObjectLite): number
|
||||
readExtraParams(buf: Buffer, pos: number, o: GameObject): number
|
||||
{
|
||||
if (pos >= buf.length)
|
||||
{
|
||||
@@ -395,14 +425,14 @@ export class ObjectStoreLite implements IObjectStore
|
||||
return pos;
|
||||
}
|
||||
|
||||
getObjectsByParent(parentID: number): GameObjectLite[]
|
||||
getObjectsByParent(parentID: number): GameObject[]
|
||||
{
|
||||
const list = this.objectsByParent[parentID];
|
||||
if (list === undefined)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
const result: GameObjectLite[] = [];
|
||||
const result: GameObject[] = [];
|
||||
list.forEach((localID) =>
|
||||
{
|
||||
result.push(this.objects[localID]);
|
||||
@@ -449,16 +479,102 @@ export class ObjectStoreLite implements IObjectStore
|
||||
shutdown()
|
||||
{
|
||||
this.objects = {};
|
||||
if (this.rtree)
|
||||
{
|
||||
this.rtree.clear();
|
||||
}
|
||||
this.objectsByUUID = {};
|
||||
this.objectsByParent = {};
|
||||
}
|
||||
|
||||
getObjectsInArea(minX: number, maxX: number, minY: number, maxY: number, minZ: number, maxZ: number): GameObjectFull[]
|
||||
protected findParent(go: GameObject): GameObject
|
||||
{
|
||||
throw new Error('GetObjectsInArea not available with the Lite object store.');
|
||||
if (go.ParentID !== 0 && this.objects[go.ParentID])
|
||||
{
|
||||
return this.findParent(this.objects[go.ParentID]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return go;
|
||||
}
|
||||
}
|
||||
|
||||
getObjectByUUID(fullID: UUID | string): IGameObject
|
||||
private populateChildren(obj: GameObject)
|
||||
{
|
||||
obj.children = [];
|
||||
obj.totalChildren = 0;
|
||||
for (const child of this.getObjectsByParent(obj.ID))
|
||||
{
|
||||
obj.totalChildren++;
|
||||
this.populateChildren(child);
|
||||
if (child.totalChildren !== undefined)
|
||||
{
|
||||
obj.totalChildren += child.totalChildren;
|
||||
}
|
||||
obj.children.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
getNumberOfObjects()
|
||||
{
|
||||
return Object.keys(this.objects).length;
|
||||
}
|
||||
|
||||
getObjectsInArea(minX: number, maxX: number, minY: number, maxY: number, minZ: number, maxZ: number): GameObject[]
|
||||
{
|
||||
if (!this.rtree)
|
||||
{
|
||||
throw new Error('GetObjectsInArea not available with the Lite object store');
|
||||
}
|
||||
const result = this.rtree.search({
|
||||
minX: minX,
|
||||
maxX: maxX,
|
||||
minY: minY,
|
||||
maxY: maxY,
|
||||
minZ: minZ,
|
||||
maxZ: maxZ
|
||||
});
|
||||
const found: {[key: string]: GameObject} = {};
|
||||
const objs: GameObject[] = [];
|
||||
for (const obj of result)
|
||||
{
|
||||
const o = obj as ITreeBoundingBox;
|
||||
const go = o.gameObject as GameObject;
|
||||
if (go.PCode !== PCode.Avatar && (go.IsAttachment === undefined || go.IsAttachment === false))
|
||||
{
|
||||
try
|
||||
{
|
||||
const parent = this.findParent(go);
|
||||
if (parent.PCode !== PCode.Avatar && (parent.IsAttachment === undefined || parent.IsAttachment === false))
|
||||
{
|
||||
const uuid = parent.FullID.toString();
|
||||
|
||||
if (found[uuid] === undefined)
|
||||
{
|
||||
found[uuid] = parent;
|
||||
objs.push(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.log('Failed to find parent for ' + go.FullID.toString());
|
||||
console.error(error);
|
||||
// Unable to find parent, full object probably not fully loaded yet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now populate children of each found object
|
||||
for (const obj of objs)
|
||||
{
|
||||
this.populateChildren(obj);
|
||||
}
|
||||
|
||||
return objs;
|
||||
}
|
||||
|
||||
getObjectByUUID(fullID: UUID | string): GameObject
|
||||
{
|
||||
if (fullID instanceof UUID)
|
||||
{
|
||||
@@ -472,7 +588,7 @@ export class ObjectStoreLite implements IObjectStore
|
||||
return this.objects[localID];
|
||||
}
|
||||
|
||||
getObjectByLocalID(localID: number): IGameObject
|
||||
getObjectByLocalID(localID: number): GameObject
|
||||
{
|
||||
if (!this.objects[localID])
|
||||
{
|
||||
@@ -480,4 +596,33 @@ export class ObjectStoreLite implements IObjectStore
|
||||
}
|
||||
return this.objects[localID];
|
||||
}
|
||||
|
||||
insertIntoRtree(obj: GameObject)
|
||||
{
|
||||
if (!this.rtree)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (obj.rtreeEntry !== undefined)
|
||||
{
|
||||
this.rtree.remove(obj.rtreeEntry);
|
||||
}
|
||||
if (!obj.Scale || !obj.Position || !obj.Rotation)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const normalizedScale = obj.Scale.multiplyByQuat(obj.Rotation);
|
||||
const bounds: ITreeBoundingBox = {
|
||||
minX: obj.Position.x - (normalizedScale.x / 2),
|
||||
maxX: obj.Position.x + (normalizedScale.x / 2),
|
||||
minY: obj.Position.y - (normalizedScale.y / 2),
|
||||
maxY: obj.Position.y + (normalizedScale.y / 2),
|
||||
minZ: obj.Position.z - (normalizedScale.z / 2),
|
||||
maxZ: obj.Position.z + (normalizedScale.z / 2),
|
||||
gameObject: obj
|
||||
};
|
||||
|
||||
obj.rtreeEntry = bounds;
|
||||
this.rtree.insert(bounds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import {UUID} from '../UUID';
|
||||
import {AgentAnimationMessage} from '../messages/AgentAnimation';
|
||||
import {PacketFlags} from '../../enums/PacketFlags';
|
||||
import {CommandsBase} from './CommandsBase';
|
||||
import {Vector3} from '../Vector3';
|
||||
|
||||
export class AgentCommands extends CommandsBase
|
||||
{
|
||||
@@ -36,4 +37,27 @@ export class AgentCommands extends CommandsBase
|
||||
{
|
||||
return await this.animate(anim, false);
|
||||
}
|
||||
|
||||
setCamera(position: Vector3, lookAt: Vector3, viewDistance?: number, leftAxis?: Vector3, upAxis?: Vector3)
|
||||
{
|
||||
this.agent.cameraCenter = position;
|
||||
this.agent.cameraLookAt = lookAt;
|
||||
if (viewDistance !== undefined)
|
||||
{
|
||||
this.agent.cameraFar = viewDistance;
|
||||
}
|
||||
if (leftAxis !== undefined)
|
||||
{
|
||||
this.agent.cameraLeftAxis = leftAxis;
|
||||
}
|
||||
if (upAxis !== undefined)
|
||||
{
|
||||
this.agent.cameraUpAxis = upAxis;
|
||||
}
|
||||
}
|
||||
|
||||
setViewDistance(viewDistance: number)
|
||||
{
|
||||
this.agent.cameraFar = viewDistance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,15 @@ import {Message} from '../../enums/Message';
|
||||
import {FilterResponse} from '../../enums/FilterResponse';
|
||||
import {RegionIDAndHandleReplyMessage} from '../messages/RegionIDAndHandleReply';
|
||||
import {PacketFlags, Vector3} from '../..';
|
||||
import {IGameObject} from '../interfaces/IGameObject';
|
||||
import {ObjectGrabMessage} from '../messages/ObjectGrab';
|
||||
import {ObjectDeGrabMessage} from '../messages/ObjectDeGrab';
|
||||
import {ObjectGrabUpdateMessage} from '../messages/ObjectGrabUpdate';
|
||||
import {GameObject} from '../GameObject';
|
||||
import {ObjectSelectMessage} from '../messages/ObjectSelect';
|
||||
import {ObjectPropertiesMessage} from '../messages/ObjectProperties';
|
||||
import {Utils} from '../Utils';
|
||||
import {ObjectDeselectMessage} from '../messages/ObjectDeselect';
|
||||
import {PCode} from '../../enums/PCode';
|
||||
|
||||
export class RegionCommands extends CommandsBase
|
||||
{
|
||||
@@ -35,9 +40,277 @@ export class RegionCommands extends CommandsBase
|
||||
return responseMsg.ReplyBlock.RegionHandle;
|
||||
}
|
||||
|
||||
getObjectsInArea(minX: number, maxX: number, minY: number, maxY: number, minZ: number, maxZ: number): IGameObject[]
|
||||
async deselectObjects(objects: GameObject[])
|
||||
{
|
||||
return this.currentRegion.objects.getObjectsInArea(minX, maxX, minY, maxY, minZ, maxZ);
|
||||
// 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 await this.circuit.waitForAck(sequenceID, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
countObjects(): number
|
||||
{
|
||||
return this.currentRegion.objects.getNumberOfObjects();
|
||||
}
|
||||
|
||||
async selectObjects(objects: GameObject[])
|
||||
{
|
||||
// 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
|
||||
let resolved = 0;
|
||||
|
||||
this.circuit.sendMessage(selectObject, PacketFlags.Reliable);
|
||||
return 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)
|
||||
{
|
||||
resolved++;
|
||||
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.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;
|
||||
|
||||
// console.log(obj.name + ' (' + resolved + ' of ' + objects.length + ')');
|
||||
}
|
||||
}
|
||||
if (Object.keys(uuidMap).length === 0)
|
||||
{
|
||||
return FilterResponse.Finish;
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
return FilterResponse.NoMatch;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FilterResponse.Match;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async resolveObjects(objects: GameObject[])
|
||||
{
|
||||
// First, create a map of all object IDs
|
||||
const objs: {[key: number]: GameObject} = {};
|
||||
const scanObject = function(obj: GameObject)
|
||||
{
|
||||
const localID = obj.ID;
|
||||
if (!objs[localID])
|
||||
{
|
||||
objs[localID] = obj;
|
||||
if (obj.children)
|
||||
{
|
||||
for (const child of obj.children)
|
||||
{
|
||||
scanObject(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
for (const obj of objects)
|
||||
{
|
||||
scanObject(obj);
|
||||
}
|
||||
|
||||
const resolveTime = new Date().getTime() / 1000;
|
||||
let objectList = [];
|
||||
let totalRemaining = 0;
|
||||
try
|
||||
{
|
||||
for (const k of Object.keys(objs))
|
||||
{
|
||||
const ky = parseInt(k, 10);
|
||||
if (objs[ky] !== undefined)
|
||||
{
|
||||
const o = objs[ky];
|
||||
if (o.resolvedAt === undefined)
|
||||
{
|
||||
o.resolvedAt = 0;
|
||||
}
|
||||
if (o.resolvedAt !== undefined && o.resolvedAt < resolveTime && o.PCode !== PCode.Avatar)
|
||||
{
|
||||
objs[ky].name = undefined;
|
||||
totalRemaining++;
|
||||
objectList.push(objs[ky]);
|
||||
if (objectList.length > 254)
|
||||
{
|
||||
try
|
||||
{
|
||||
await this.selectObjects(objectList);
|
||||
await this.deselectObjects(objectList);
|
||||
for (const chk of objectList)
|
||||
{
|
||||
if (chk.resolvedAt !== undefined && chk.resolvedAt >= resolveTime)
|
||||
{
|
||||
totalRemaining--;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ignore)
|
||||
{
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
objectList = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (objectList.length > 0)
|
||||
{
|
||||
await this.selectObjects(objectList);
|
||||
await this.deselectObjects(objectList);
|
||||
for (const chk of objectList)
|
||||
{
|
||||
if (chk.resolvedAt !== undefined && chk.resolvedAt >= resolveTime)
|
||||
{
|
||||
totalRemaining --;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ignore)
|
||||
{
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (totalRemaining < 1)
|
||||
{
|
||||
totalRemaining = 0;
|
||||
for (const obj of objectList)
|
||||
{
|
||||
if (obj.resolvedAt === undefined || obj.resolvedAt < resolveTime)
|
||||
{
|
||||
totalRemaining++;
|
||||
}
|
||||
}
|
||||
if (totalRemaining > 0)
|
||||
{
|
||||
console.error(totalRemaining + ' objects could not be resolved');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getObjectsInArea(minX: number, maxX: number, minY: number, maxY: number, minZ: number, maxZ: number, resolve: boolean = false): Promise<GameObject[]>
|
||||
{
|
||||
const objs = this.currentRegion.objects.getObjectsInArea(minX, maxX, minY, maxY, minZ, maxZ);
|
||||
if (resolve)
|
||||
{
|
||||
console.log('Resolving ' + objs.length + ' objects');
|
||||
await this.resolveObjects(objs);
|
||||
}
|
||||
return objs;
|
||||
}
|
||||
|
||||
async grabObject(localID: number | UUID,
|
||||
@@ -51,7 +324,7 @@ export class RegionCommands extends CommandsBase
|
||||
{
|
||||
if (localID instanceof UUID)
|
||||
{
|
||||
const obj: IGameObject = this.currentRegion.objects.getObjectByUUID(localID);
|
||||
const obj: GameObject = this.currentRegion.objects.getObjectByUUID(localID);
|
||||
localID = obj.ID;
|
||||
}
|
||||
const msg = new ObjectGrabMessage();
|
||||
@@ -88,7 +361,7 @@ export class RegionCommands extends CommandsBase
|
||||
{
|
||||
if (localID instanceof UUID)
|
||||
{
|
||||
const obj: IGameObject = this.currentRegion.objects.getObjectByUUID(localID);
|
||||
const obj: GameObject = this.currentRegion.objects.getObjectByUUID(localID);
|
||||
localID = obj.ID;
|
||||
}
|
||||
const msg = new ObjectDeGrabMessage();
|
||||
@@ -126,7 +399,7 @@ export class RegionCommands extends CommandsBase
|
||||
// For some reason this message takes a UUID when the others take a LocalID - wtf?
|
||||
if (!(localID instanceof UUID))
|
||||
{
|
||||
const obj: IGameObject = this.currentRegion.objects.getObjectByLocalID(localID);
|
||||
const obj: GameObject = this.currentRegion.objects.getObjectByLocalID(localID);
|
||||
localID = obj.FullID;
|
||||
}
|
||||
const msg = new ObjectGrabUpdateMessage();
|
||||
@@ -165,7 +438,7 @@ export class RegionCommands extends CommandsBase
|
||||
{
|
||||
if (localID instanceof UUID)
|
||||
{
|
||||
const obj: IGameObject = this.currentRegion.objects.getObjectByUUID(localID);
|
||||
const obj: GameObject = this.currentRegion.objects.getObjectByUUID(localID);
|
||||
localID = obj.ID;
|
||||
}
|
||||
await this.grabObject(localID, grabOffset, uvCoordinate, stCoordinate, faceIndex, position, normal, binormal);
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import {ITreeBoundingBox} from './ITreeBoundingBox';
|
||||
import {UUID} from '../UUID';
|
||||
import {PCode} from '../../enums/PCode';
|
||||
|
||||
export interface IGameObject
|
||||
{
|
||||
ID: number;
|
||||
FullID: UUID;
|
||||
ParentID: number;
|
||||
OwnerID: UUID;
|
||||
IsAttachment: boolean;
|
||||
PCode: PCode;
|
||||
rtreeEntry?: ITreeBoundingBox;
|
||||
hasNameValueEntry(key: string): boolean;
|
||||
getNameValueEntry(key: string): string;
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
import {IGameObject} from './IGameObject';
|
||||
import {RBush3D} from 'rbush-3d/dist';
|
||||
import {GameObjectFull} from '../GameObjectFull';
|
||||
import {UUID} from '../UUID';
|
||||
import {GameObject} from '../GameObject';
|
||||
|
||||
export interface IObjectStore
|
||||
{
|
||||
rtree?: RBush3D;
|
||||
getObjectsByParent(parentID: number): IGameObject[];
|
||||
getObjectsByParent(parentID: number): GameObject[];
|
||||
shutdown(): void;
|
||||
getObjectsInArea(minX: number, maxX: number, minY: number, maxY: number, minZ: number, maxZ: number): GameObjectFull[];
|
||||
getObjectByUUID(fullID: UUID): IGameObject;
|
||||
getObjectByLocalID(ID: number): IGameObject;
|
||||
getObjectsInArea(minX: number, maxX: number, minY: number, maxY: number, minZ: number, maxZ: number): GameObject[];
|
||||
getObjectByUUID(fullID: UUID): GameObject;
|
||||
getObjectByLocalID(ID: number): GameObject;
|
||||
getNumberOfObjects(): number;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {BBox} from 'rbush-3d/dist';
|
||||
import {IGameObject} from './IGameObject';
|
||||
import {GameObject} from '../GameObject';
|
||||
|
||||
export interface ITreeBoundingBox extends BBox
|
||||
{
|
||||
gameObject: IGameObject;
|
||||
gameObject: GameObject;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user