diff --git a/lib/Bot.ts b/lib/Bot.ts index b0b3e05..1651da4 100644 --- a/lib/Bot.ts +++ b/lib/Bot.ts @@ -95,6 +95,7 @@ export class Bot this.currentRegion = response.region; this.agent = response.agent; this.clientCommands = new ClientCommands(response.region, response.agent, this); + this.currentRegion.clientCommands = this.clientCommands; return response; } @@ -103,6 +104,7 @@ export class Bot this.closeCircuit(); this.currentRegion = region; this.clientCommands = new ClientCommands(this.currentRegion, this.agent, this); + this.currentRegion.clientCommands = this.clientCommands; if (this.ping !== null) { clearInterval(this.ping); diff --git a/lib/classes/Color4.ts b/lib/classes/Color4.ts index 5618591..8b244e8 100644 --- a/lib/classes/Color4.ts +++ b/lib/classes/Color4.ts @@ -1,4 +1,5 @@ import {XMLElementOrXMLNode} from 'xmlbuilder'; +import {Utils} from './Utils'; export class Color4 { @@ -17,6 +18,48 @@ export class Color4 doc.ele('A', c.alpha); } + static fromXMLJS(obj: any, param: string): Color4 | false + { + if (!obj[param]) + { + return false; + } + let value = obj[param]; + if (Array.isArray(value) && value.length > 0) + { + value = value[0]; + } + if (typeof value === 'object') + { + if (value['R'] !== undefined && value['G'] !== undefined && value['B'] !== undefined && value['A'] !== undefined) + { + let red = value['R']; + let green = value['G']; + let blue = value['B']; + let alpha = value['A']; + if (Array.isArray(red) && red.length > 0) + { + red = red[0]; + } + if (Array.isArray(green) && green.length > 0) + { + green = green[0]; + } + if (Array.isArray(blue) && blue.length > 0) + { + blue = blue[0]; + } + if (Array.isArray(alpha) && alpha.length > 0) + { + alpha = alpha[0]; + } + return new Color4(red, green, blue, alpha); + } + return false; + } + return false; + } + constructor(public red: number | Buffer | number[], public green: number = 0, public blue: number | boolean = 0, public alpha: number | boolean = 0) { if (red instanceof Buffer && typeof blue === 'boolean') @@ -63,4 +106,44 @@ export class Color4 this.red = red[0]; } } + getRed(): number + { + if (typeof this.red === 'number') + { + return this.red; + } + return 0; + } + getGreen(): number + { + if (typeof this.green === 'number') + { + return this.green; + } + return 0; + } + getBlue(): number + { + if (typeof this.blue === 'number') + { + return this.blue; + } + return 0; + } + getAlpha(): number + { + if (typeof this.alpha === 'number') + { + return this.alpha; + } + return 0; + } + + writeToBuffer(buf: Buffer, pos: number) + { + buf.writeUInt8(Utils.FloatToByte(this.getRed(), 0, 1.0), pos++); + buf.writeUInt8(Utils.FloatToByte(this.getGreen(), 0, 1.0), pos++); + buf.writeUInt8(Utils.FloatToByte(this.getBlue(), 0, 1.0), pos++); + buf.writeUInt8(Utils.FloatToByte(this.getAlpha(), 0, 1.0), pos); + } } diff --git a/lib/classes/LLWearable.ts b/lib/classes/LLWearable.ts new file mode 100644 index 0000000..4a653be --- /dev/null +++ b/lib/classes/LLWearable.ts @@ -0,0 +1,178 @@ +import {UUID} from './UUID'; +import {WearableType} from '../enums/WearableType'; +import {SaleType} from '../enums/SaleType'; + +export class LLWearable +{ + name: string; + type: WearableType; + parameters: {[key: number]: number} = {}; + textures: {[key: number]: UUID} = {}; + permission: { + baseMask: number, + ownerMask: number, + groupMask: number, + everyoneMask: number, + nextOwnerMask: number, + creatorID: UUID, + ownerID: UUID, + lastOwnerID: UUID, + groupID: UUID + } = { + baseMask: 0, + ownerMask: 0, + groupMask: 0, + everyoneMask: 0, + nextOwnerMask: 0, + creatorID: UUID.zero(), + ownerID: UUID.zero(), + lastOwnerID: UUID.zero(), + groupID: UUID.zero() + }; + saleType: SaleType; + salePrice: number; + constructor(data: string) + { + const lines: string[] = data.replace(/\r\n/g, '\n').split('\n'); + for (let index = 0; index < lines.length; index++) + { + if (index === 0) + { + const header = lines[index].split(' '); + if (header[0] !== 'LLWearable') + { + return; + } + } + else if (index === 1) + { + this.name = lines[index]; + } + else + { + const parsedLine = this.parseLine(lines[index]); + if (parsedLine.key !== null) + { + switch (parsedLine.key) + { + case 'base_mask': + this.permission.baseMask = parseInt(parsedLine.value, 16); + break; + case 'owner_mask': + this.permission.ownerMask = parseInt(parsedLine.value, 16); + break; + case 'group_mask': + this.permission.groupMask = parseInt(parsedLine.value, 16); + break; + case 'everyone_mask': + this.permission.everyoneMask = parseInt(parsedLine.value, 16); + break; + case 'next_owner_mask': + this.permission.nextOwnerMask = parseInt(parsedLine.value, 16); + break; + case 'creator_id': + this.permission.creatorID = new UUID(parsedLine.value); + break; + case 'owner_id': + this.permission.ownerID = new UUID(parsedLine.value); + break; + case 'last_owner_id': + this.permission.lastOwnerID = new UUID(parsedLine.value); + break; + case 'group_id': + this.permission.groupID = new UUID(parsedLine.value); + break; + case 'sale_type': + this.saleType = parseInt(parsedLine.value, 10); + break; + case 'sale_price': + this.salePrice = parseInt(parsedLine.value, 10); + break; + case 'type': + this.type = parseInt(parsedLine.value, 10); + break; + case 'parameters': + { + const num = parseInt(parsedLine.value, 10); + const max = index + num; + for (index; index < max; index++) + { + const paramLine = this.parseLine(lines[index++]); + if (paramLine.key !== null) + { + this.parameters[parseInt(paramLine.key, 10)] = parseInt(paramLine.value, 10); + } + } + break; + } + case 'textures': + { + const num = parseInt(parsedLine.value, 10); + const max = index + num ; + for (index; index < max; index++) + { + const texLine = this.parseLine(lines[index + 1]); + if (texLine.key !== null) + { + this.textures[parseInt(texLine.key, 10)] = new UUID(texLine.value); + } + } + break; + } + case 'permissions': + case 'sale_info': + case '{': + case '}': + // ignore + break; + default: + console.log('skipping: ' + lines[index]); + break; + } + } + } + } + } + + private parseLine(line: string): { + 'key': string | null, + 'value': string + } + { + line = line.trim().replace(/[\t]/gu, ' ').trim(); + while (line.indexOf('\u0020\u0020') > 0) + { + line = line.replace(/\u0020\u0020/gu, '\u0020'); + } + let key: string | null = null; + let value = ''; + if (line.length > 2) + { + const sep = line.indexOf(' '); + if (sep > 0) + { + key = line.substr(0, sep); + value = line.substr(sep + 1); + } + } + else if (line.length === 1) + { + key = line; + } + else if (line.length > 0) + { + return { + 'key': line, + 'value': '' + } + } + if (key !== null) + { + key = key.trim(); + } + return { + 'key': key, + 'value': value + } + } +} diff --git a/lib/classes/ObjectStoreFull.ts b/lib/classes/ObjectStoreFull.ts index 2c1be36..e8525c1 100644 --- a/lib/classes/ObjectStoreFull.ts +++ b/lib/classes/ObjectStoreFull.ts @@ -19,6 +19,8 @@ import {Color4} from './Color4'; import {ParticleSystem} from './ParticleSystem'; import {GameObject} from './public/GameObject'; import {ObjectStoreLite} from './ObjectStoreLite'; +import {TextureAnim} from './public/TextureAnim'; +import {ExtraParams} from './public/ExtraParams'; export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore { @@ -71,95 +73,8 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore obj.ClickAction = objData.ClickAction; obj.Scale = objData.Scale; - obj.ObjectData = objData.ObjectData; - const data: Buffer = objData.ObjectData; - let dataPos = 0; + obj.setObjectData(objData.ObjectData); - // noinspection FallThroughInSwitchStatementJS, TsLint - switch (data.length) - { - case 76: - // Avatar collision normal; - obj.CollisionPlane = new Vector4(objData.ObjectData, dataPos); - dataPos += 16; - case 60: - // Position - obj.Position = new Vector3(objData.ObjectData, dataPos); - dataPos += 12; - obj.Velocity = new Vector3(objData.ObjectData, dataPos); - dataPos += 12; - obj.Acceleration = new Vector3(objData.ObjectData, dataPos); - dataPos += 12; - obj.Rotation = new Quaternion(objData.ObjectData, dataPos); - dataPos += 12; - obj.AngularVelocity = new Vector3(objData.ObjectData, dataPos); - dataPos += 12; - break; - case 48: - obj.CollisionPlane = new Vector4(objData.ObjectData, dataPos); - dataPos += 16; - case 32: - obj.Position = new Vector3([ - Utils.UInt16ToFloat(objData.ObjectData.readUInt16LE(dataPos), -0.5 * 256.0, 1.5 * 256.0), - Utils.UInt16ToFloat(objData.ObjectData.readUInt16LE(dataPos + 2), -0.5 * 256.0, 1.5 * 256.0), - Utils.UInt16ToFloat(objData.ObjectData.readUInt16LE(dataPos + 4), -256.0, 3.0 * 256.0) - ]); - dataPos += 6; - obj.Velocity = new Vector3([ - Utils.UInt16ToFloat(objData.ObjectData.readUInt16LE(dataPos), -256.0, 256.0), - Utils.UInt16ToFloat(objData.ObjectData.readUInt16LE(dataPos + 2), -256.0, 256.0), - Utils.UInt16ToFloat(objData.ObjectData.readUInt16LE(dataPos + 4), -256.0, 256.0) - ]); - dataPos += 6; - obj.Acceleration = new Vector3([ - Utils.UInt16ToFloat(objData.ObjectData.readUInt16LE(dataPos), -256.0, 256.0), - Utils.UInt16ToFloat(objData.ObjectData.readUInt16LE(dataPos + 2), -256.0, 256.0), - Utils.UInt16ToFloat(objData.ObjectData.readUInt16LE(dataPos + 4), -256.0, 256.0) - ]); - dataPos += 6; - obj.Rotation = new Quaternion([ - Utils.UInt16ToFloat(objData.ObjectData.readUInt16LE(dataPos), -1.0, 1.0), - Utils.UInt16ToFloat(objData.ObjectData.readUInt16LE(dataPos + 2), -1.0, 1.0), - Utils.UInt16ToFloat(objData.ObjectData.readUInt16LE(dataPos + 4), -1.0, 1.0), - Utils.UInt16ToFloat(objData.ObjectData.readUInt16LE(dataPos + 4), -1.0, 1.0) - ]); - dataPos += 8; - obj.AngularVelocity = new Vector3([ - Utils.UInt16ToFloat(objData.ObjectData.readUInt16LE(dataPos), -256.0, 256.0), - Utils.UInt16ToFloat(objData.ObjectData.readUInt16LE(dataPos + 2), -256.0, 256.0), - Utils.UInt16ToFloat(objData.ObjectData.readUInt16LE(dataPos + 4), -256.0, 256.0) - ]); - dataPos += 6; - break; - case 16: - obj.Position = new Vector3([ - Utils.ByteToFloat(objData.ObjectData.readUInt8(dataPos++), -256.0, 256.0), - Utils.ByteToFloat(objData.ObjectData.readUInt8(dataPos++), -256.0, 256.0), - Utils.ByteToFloat(objData.ObjectData.readUInt8(dataPos++), -256.0, 256.0) - ]); - obj.Velocity = new Vector3([ - Utils.ByteToFloat(objData.ObjectData.readUInt8(dataPos++), -256.0, 256.0), - Utils.ByteToFloat(objData.ObjectData.readUInt8(dataPos++), -256.0, 256.0), - Utils.ByteToFloat(objData.ObjectData.readUInt8(dataPos++), -256.0, 256.0) - ]); - obj.Acceleration = new Vector3([ - Utils.ByteToFloat(objData.ObjectData.readUInt8(dataPos++), -256.0, 256.0), - Utils.ByteToFloat(objData.ObjectData.readUInt8(dataPos++), -256.0, 256.0), - Utils.ByteToFloat(objData.ObjectData.readUInt8(dataPos++), -256.0, 256.0) - ]); - obj.Rotation = new Quaternion([ - Utils.ByteToFloat(objData.ObjectData.readUInt8(dataPos++), -1.0, 1.0), - Utils.ByteToFloat(objData.ObjectData.readUInt8(dataPos++), -1.0, 1.0), - Utils.ByteToFloat(objData.ObjectData.readUInt8(dataPos++), -1.0, 1.0), - Utils.ByteToFloat(objData.ObjectData.readUInt8(dataPos++), -1.0, 1.0) - ]); - obj.AngularVelocity = new Vector3([ - Utils.ByteToFloat(objData.ObjectData.readUInt8(dataPos++), -256.0, 256.0), - Utils.ByteToFloat(objData.ObjectData.readUInt8(dataPos++), -256.0, 256.0), - Utils.ByteToFloat(objData.ObjectData.readUInt8(dataPos++), -256.0, 256.0) - ]); - break; - } obj.ParentID = objData.ParentID; obj.Flags = objData.UpdateFlags; @@ -182,22 +97,13 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore obj.ProfileEnd = objData.ProfileEnd; obj.ProfileHollow = objData.ProfileHollow; obj.TextureEntry = new TextureEntry(objData.TextureEntry); - obj.TextureAnim = objData.TextureAnim; - - if (obj.TextureAnim.length >= 16) - { - this.readTextureAnim(obj); - } + obj.textureAnim = TextureAnim.from(objData.TextureAnim); const pcodeData = objData.Data; obj.Text = Utils.BufferToStringSimple(objData.Text); obj.TextColor = new Color4(objData.TextColor, 0, false, true); obj.MediaURL = Utils.BufferToStringSimple(objData.MediaURL); - obj.PSBlock = objData.PSBlock; - if (obj.PSBlock.length > 0) - { - obj.Particles = new ParticleSystem(obj.PSBlock, 0); - } + obj.Particles = ParticleSystem.from(objData.PSBlock); obj.Sound = objData.Sound; obj.OwnerID = objData.OwnerID; obj.SoundGain = objData.Gain; @@ -261,8 +167,7 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore }); } } - - this.readExtraParams(objData.ExtraParams, 0, this.objects[localID]); + this.objects[localID].extraParams = ExtraParams.from(objData.ExtraParams); this.objects[localID].NameValue = this.parseNameValues(Utils.BufferToStringSimple(objData.NameValue)); this.objects[localID].IsAttachment = this.objects[localID].NameValue['AttachItemID'] !== undefined; @@ -321,23 +226,6 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore } } - private readTextureAnim(obj: GameObject) - { - let animPos = 0; - if (obj.TextureAnim !== undefined) - { - obj.TextureAnimFlags = obj.TextureAnim.readUInt8(animPos++); - obj.TextureAnimFace = obj.TextureAnim.readUInt8(animPos++); - obj.TextureAnimSizeX = obj.TextureAnim.readUInt8(animPos++); - obj.TextureAnimSizeY = obj.TextureAnim.readUInt8(animPos++); - obj.TextureAnimStart = obj.TextureAnim.readFloatLE(animPos); - animPos = animPos + 4; - obj.TextureAnimLength = obj.TextureAnim.readFloatLE(animPos); - animPos = animPos + 4; - obj.TextureAnimRate = obj.TextureAnim.readFloatLE(animPos); - } - } - protected async objectUpdateCompressed(objectUpdateCompressed: ObjectUpdateCompressedMessage) { for (const obj of objectUpdateCompressed.ObjectData) @@ -464,13 +352,14 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore } if (compressedflags & CompressedFlags.HasParticles) { - o.PSBlock = buf.slice(pos, pos + 86); - o.Particles = new ParticleSystem(o.PSBlock, 0); + o.Particles = ParticleSystem.from(buf.slice(pos, pos + 86)); pos += 86; } // Extra params - pos = this.readExtraParams(buf, pos, o); + const extraParamsLength = ExtraParams.getLengthOfParams(buf, pos); + o.extraParams = ExtraParams.from(buf.slice(pos, pos + extraParamsLength)); + pos += extraParamsLength; if (compressedflags & CompressedFlags.HasSound) { @@ -520,11 +409,7 @@ export class ObjectStoreFull extends ObjectStoreLite implements IObjectStore { const textureAnimLength = buf.readUInt32LE(pos); pos = pos + 4; - o.TextureAnim = buf.slice(pos, pos + textureAnimLength); - if (o.TextureAnim.length >= 16) - { - this.readTextureAnim(o); - } + o.textureAnim = TextureAnim.from(buf.slice(pos, pos + textureAnimLength)); } o.IsAttachment = (compressedflags & CompressedFlags.HasNameValues) !== 0 && o.ParentID !== 0; diff --git a/lib/classes/ObjectStoreLite.ts b/lib/classes/ObjectStoreLite.ts index ed99c1d..b835751 100644 --- a/lib/classes/ObjectStoreLite.ts +++ b/lib/classes/ObjectStoreLite.ts @@ -8,28 +8,30 @@ import {ImprovedTerseObjectUpdateMessage} from './messages/ImprovedTerseObjectUp import {RequestMultipleObjectsMessage} from './messages/RequestMultipleObjects'; import {Agent} from './Agent'; import {UUID} from './UUID'; -import {ExtraParamType} from '../enums/ExtraParamType'; import {Utils} from './Utils'; import {ClientEvents} from './ClientEvents'; import {KillObjectMessage} from './messages/KillObject'; import {IObjectStore} from './interfaces/IObjectStore'; import {NameValue} from './NameValue'; -import {BotOptionFlags, CompressedFlags, ObjectPhysicsDataEvent, PacketFlags, PCode, Vector3} from '..'; +import { + BotOptionFlags, + CompressedFlags, + NewObjectEvent, + ObjectPhysicsDataEvent, + ObjectUpdatedEvent, + PacketFlags, + PCode, + Vector3 +} from '..'; import {GameObject} from './public/GameObject'; import {RBush3D} from 'rbush-3d/dist'; import {ITreeBoundingBox} from './interfaces/ITreeBoundingBox'; import {FilterResponse} from '../enums/FilterResponse'; import {ObjectSelectMessage} from './messages/ObjectSelect'; import {ObjectDeselectMessage} from './messages/ObjectDeselect'; -import {FlexibleData} from './public/FlexibleData'; -import {LightImageData} from './public/LightImageData'; -import {LightData} from './public/LightData'; -import {MeshData} from './public/MeshData'; -import {SculptData} from './public/SculptData'; import {Quaternion} from './Quaternion'; import {Subscription} from 'rxjs/internal/Subscription'; -import {NewObjectEvent} from '../events/NewObjectEvent'; -import {ObjectUpdatedEvent} from '../events/ObjectUpdatedEvent'; +import {ExtraParams} from './public/ExtraParams'; export class ObjectStoreLite implements IObjectStore { @@ -445,7 +447,9 @@ export class ObjectStoreLite implements IObjectStore } // Extra params - pos = this.readExtraParams(buf, pos, o); + const extraParamsLength = ExtraParams.getLengthOfParams(buf, pos); + o.extraParams = ExtraParams.from(buf.slice(pos, pos + extraParamsLength)); + pos = pos + extraParamsLength; if (compressedflags & CompressedFlags.HasSound) { @@ -553,47 +557,6 @@ export class ObjectStoreLite implements IObjectStore delete this.objects[objectID]; } } - - readExtraParams(buf: Buffer, pos: number, o: GameObject): number - { - const startPos = pos; - if (pos >= buf.length) - { - return 0; - } - const extraParamCount = buf.readUInt8(pos++); - for (let k = 0; k < extraParamCount; k++) - { - const type: ExtraParamType = buf.readUInt16LE(pos); - pos = pos + 2; - const paramLength = buf.readUInt32LE(pos); - pos = pos + 4; - - switch (type) - { - case ExtraParamType.Flexible: - o.FlexibleData = new FlexibleData(buf, pos, paramLength); - break; - case ExtraParamType.Light: - o.LightData = new LightData(buf, pos, paramLength); - break; - case ExtraParamType.LightImage: - o.LightImageData = new LightImageData(buf, pos, paramLength); - break; - case ExtraParamType.Mesh: - o.MeshData = new MeshData(buf, pos, paramLength); - break; - case ExtraParamType.Sculpt: - o.SculptData = new SculptData(buf, pos, paramLength); - break; - } - - pos += paramLength; - } - o.ExtraParams = buf.slice(startPos, pos); - return pos; - } - getObjectsByParent(parentID: number): GameObject[] { const list = this.objectsByParent[parentID]; diff --git a/lib/classes/ParticleSystem.ts b/lib/classes/ParticleSystem.ts index 3dad978..9d20c0d 100644 --- a/lib/classes/ParticleSystem.ts +++ b/lib/classes/ParticleSystem.ts @@ -1,9 +1,7 @@ -import {BlendFunc} from '../enums/BlendFunc'; -import {SourcePattern} from '../enums/SourcePattern'; import {Vector3} from './Vector3'; import {UUID} from './UUID'; -import {ParticleDataFlags} from '../enums/ParticleDataFlags'; import {Color4} from './Color4'; +import {BlendFunc, ParticleDataFlags, SourcePattern, Utils} from '..'; export class ParticleSystem { @@ -36,6 +34,88 @@ export class ParticleSystem endScaleY = 0.0; flags = 0; + static from(buf: Buffer): ParticleSystem + { + const ps = new ParticleSystem(); + let pos = 0; + const size = buf.length; + if (size === 86) // Legacy data block size + { + pos = this.unpackSystem(ps, buf, pos); + pos = this.unpackLegacyData(ps, buf, pos); + } + else if (size > 86 && size <= 98) + { + const sysSize = buf.readInt32LE(pos); + pos += 4; + if (sysSize !== 68) + { + console.error('Particle system block size ' + sysSize + ' different from expected 68 bytes'); + return ps; + } + pos = this.unpackSystem(ps, buf, pos); + const dataSize = buf.readInt32LE(pos); + pos += 4; + pos = this.unpackLegacyData(ps, buf, pos); + + if ((ps.dataFlags & ParticleDataFlags.DataGlow) === ParticleDataFlags.DataGlow) + { + let glow = buf.readUInt8(pos++); + ps.startGlow = glow / 255.0; + glow = buf.readUInt8(pos++); + ps.endGlow = glow / 255.0; + } + if ((ps.dataFlags & ParticleDataFlags.DataBlend) === ParticleDataFlags.DataBlend) + { + ps.blendFuncSource = buf.readUInt8(pos++); + ps.blendFuncDest = buf.readUInt8(pos); + } + } + return ps; + } + + static packFixed(buf: Buffer, pos: number, data: number, signed: boolean, intBits: number, fracBits: number): number + { + let totalBits = intBits + fracBits; + let min; + let max; + + if (signed) + { + totalBits++; + min = 1 << intBits; + min *= -1; + } + else + { + min = 0; + } + max = 1 << intBits; + + let fixedVal = Utils.Clamp(data, min, max); + if (signed) + { + fixedVal += max; + } + fixedVal *= 1 << fracBits; + if (totalBits <= 8) + { + buf.writeUInt8(fixedVal, pos); + return 1; + } + if (totalBits <= 16) + { + buf.writeUInt16LE(fixedVal, pos); + return 2; + } + if (totalBits <= 32) + { + buf.writeUInt32LE(fixedVal, pos); + return 4; + } + throw new Error('Total bits greater than 32'); + } + static unpackFixed(buf: Buffer, pos: number, signed: boolean, intBits: number, fracBits: number): number { let totalBits = intBits + fracBits; @@ -72,85 +152,44 @@ export class ParticleSystem return fixedVal; } - constructor(buf: Buffer, pos: number) - { - const size = buf.length - pos; - if (size === 86) // Legacy data block size - { - pos = this.unpackSystem(buf, pos); - pos = this.unpackLegacyData(buf, pos); - } - else if (size > 86 && size <= 98) - { - const sysSize = buf.readInt32LE(pos); - pos += 4; - if (sysSize !== 68) - { - console.error('Particle system block size ' + sysSize + ' different from expected 68 bytes'); - return; - } - pos = this.unpackSystem(buf, pos); - const dataSize = buf.readInt32LE(pos); - pos += 4; - pos = this.unpackLegacyData(buf, pos); - - if ((this.dataFlags & ParticleDataFlags.DataGlow) === ParticleDataFlags.DataGlow) - { - let glow = buf.readUInt8(pos++); - this.startGlow = glow / 255.0; - glow = buf.readUInt8(pos++); - this.endGlow = glow / 255.0; - } - if ((this.dataFlags & ParticleDataFlags.DataBlend) === ParticleDataFlags.DataBlend) - { - this.blendFuncSource = buf.readUInt8(pos++); - this.blendFuncDest = buf.readUInt8(pos++); - } - } - else - { - console.error('WARNING: Paricle system size of ' + size + ' bytes exceeds maximum block size of 98'); - } - } - - unpackSystem(buf: Buffer, pos: number): number + static unpackSystem(ps: ParticleSystem, buf: Buffer, pos: number): number { const startPos = pos; - this.crc = buf.readUInt32LE(pos); + ps.crc = buf.readUInt32LE(pos); pos += 4; - this.flags = buf.readUInt32LE(pos); + ps.flags = buf.readUInt32LE(pos); pos += 4; - this.pattern = buf.readUInt8(pos++); - this.maxAge = ParticleSystem.unpackFixed(buf, pos, false, 8, 8); + ps.pattern = buf.readUInt8(pos++); + ps.maxAge = ParticleSystem.unpackFixed(buf, pos, false, 8, 8); pos += 2; - this.startAge = ParticleSystem.unpackFixed(buf, pos, false, 8, 8); + ps.startAge = ParticleSystem.unpackFixed(buf, pos, false, 8, 8); pos += 2; - this.innerAngle = ParticleSystem.unpackFixed(buf, pos++, false, 3, 5); - this.outerAngle = ParticleSystem.unpackFixed(buf, pos++, false, 3, 5); - this.burstRate = ParticleSystem.unpackFixed(buf, pos, false, 8, 8); + ps.innerAngle = ParticleSystem.unpackFixed(buf, pos++, false, 3, 5); + ps.outerAngle = ParticleSystem.unpackFixed(buf, pos++, false, 3, 5); + ps.burstRate = ParticleSystem.unpackFixed(buf, pos, false, 8, 8); pos += 2; - this.burstRadius = ParticleSystem.unpackFixed(buf, pos, false, 8, 8); + ps.burstRadius = ParticleSystem.unpackFixed(buf, pos, false, 8, 8); pos += 2; - this.burstSpeedMin = ParticleSystem.unpackFixed(buf, pos, false, 8, 8); + ps.burstSpeedMin = ParticleSystem.unpackFixed(buf, pos, false, 8, 8); pos += 2; - this.burstSpeedMax = ParticleSystem.unpackFixed(buf, pos, false, 8, 8); + ps.burstSpeedMax = ParticleSystem.unpackFixed(buf, pos, false, 8, 8); pos += 2; - this.burstPartCount = buf.readUInt8(pos++); - this.angularVelocity = new Vector3([ + ps.burstPartCount = buf.readUInt8(pos++); + ps.angularVelocity = new Vector3([ ParticleSystem.unpackFixed(buf, pos, true, 8, 7), ParticleSystem.unpackFixed(buf, pos + 2, true, 8, 7), ParticleSystem.unpackFixed(buf, pos + 4, true, 8, 7), ]); pos = pos + 6; - this.acceleration = new Vector3([ + ps.acceleration = new Vector3([ ParticleSystem.unpackFixed(buf, pos, true, 8, 7), ParticleSystem.unpackFixed(buf, pos + 2, true, 8, 7), ParticleSystem.unpackFixed(buf, pos + 4, true, 8, 7), ]); pos = pos + 6; - this.texture = new UUID(buf, pos); + ps.texture = new UUID(buf, pos); pos += 16; - this.target = new UUID(buf, pos); + ps.target = new UUID(buf, pos); pos += 16; if (pos - startPos !== 68) { @@ -159,28 +198,119 @@ export class ParticleSystem return pos; } - unpackLegacyData(buf: Buffer, pos: number): number + static unpackLegacyData(ps: ParticleSystem, buf: Buffer, pos: number): number { - this.dataFlags = buf.readUInt32LE(pos); + ps.dataFlags = buf.readUInt32LE(pos); pos += 4; - this.partMaxAge = ParticleSystem.unpackFixed(buf, pos, false, 8, 8); + ps.partMaxAge = ParticleSystem.unpackFixed(buf, pos, false, 8, 8); pos += 2; - this.startColor = new Color4( + ps.startColor = new Color4( buf.readUInt8(pos++), buf.readUInt8(pos++), buf.readUInt8(pos++), buf.readUInt8(pos++), ); - this.endColor = new Color4( + ps.endColor = new Color4( buf.readUInt8(pos++), buf.readUInt8(pos++), buf.readUInt8(pos++), buf.readUInt8(pos++), ); - this.startScaleX = ParticleSystem.unpackFixed(buf, pos++, false, 3, 5); - this.startScaleY = ParticleSystem.unpackFixed(buf, pos++, false, 3, 5); - this.endScaleX = ParticleSystem.unpackFixed(buf, pos++, false, 3, 5); - this.endScaleY = ParticleSystem.unpackFixed(buf, pos++, false, 3, 5); + ps.startScaleX = ParticleSystem.unpackFixed(buf, pos++, false, 3, 5); + ps.startScaleY = ParticleSystem.unpackFixed(buf, pos++, false, 3, 5); + ps.endScaleX = ParticleSystem.unpackFixed(buf, pos++, false, 3, 5); + ps.endScaleY = ParticleSystem.unpackFixed(buf, pos++, false, 3, 5); return pos; } + + toBuffer(): Buffer + { + if (this.crc === 0) + { + return Buffer.allocUnsafe(0); + } + const systemBlock = Buffer.allocUnsafe(68); + let pos = 0; + console.log('FLAGS: ' + this.flags); + systemBlock.writeUInt32LE(this.crc, pos); pos = pos + 4; + systemBlock.writeUInt32LE(this.flags, pos); pos = pos + 4; // Flags is zero + systemBlock.writeUInt8(this.pattern, pos++); + pos = pos + ParticleSystem.packFixed(systemBlock, pos, this.maxAge, false, 8, 8); + pos = pos + ParticleSystem.packFixed(systemBlock, pos, this.startAge, false, 8, 8); + pos = pos + ParticleSystem.packFixed(systemBlock, pos, this.innerAngle, false, 3, 5); + pos = pos + ParticleSystem.packFixed(systemBlock, pos, this.outerAngle, false, 3, 5); + pos = pos + ParticleSystem.packFixed(systemBlock, pos, this.burstRate, false, 8, 8); + pos = pos + ParticleSystem.packFixed(systemBlock, pos, this.burstRadius, false, 8, 8); + pos = pos + ParticleSystem.packFixed(systemBlock, pos, this.burstSpeedMin, false, 8, 8); + pos = pos + ParticleSystem.packFixed(systemBlock, pos, this.burstSpeedMax, false, 8, 8); + systemBlock.writeUInt8(this.burstPartCount, pos++); + pos = pos + ParticleSystem.packFixed(systemBlock, pos, this.angularVelocity.x, true, 8, 7); + pos = pos + ParticleSystem.packFixed(systemBlock, pos, this.angularVelocity.y, true, 8, 7); + pos = pos + ParticleSystem.packFixed(systemBlock, pos, this.angularVelocity.z, true, 8, 7); + pos = pos + ParticleSystem.packFixed(systemBlock, pos, this.acceleration.x, true, 8, 7); + pos = pos + ParticleSystem.packFixed(systemBlock, pos, this.acceleration.y, true, 8, 7); + pos = pos + ParticleSystem.packFixed(systemBlock, pos, this.acceleration.z, true, 8, 7); + this.texture.writeToBuffer(systemBlock, pos); pos = pos + 16; + this.target.writeToBuffer(systemBlock, pos); pos = pos + 16; + pos = 0; + const legacyBlock = Buffer.allocUnsafe(18); + legacyBlock.writeUInt32LE(this.dataFlags, pos); pos = pos + 4; + pos = pos + ParticleSystem.packFixed(legacyBlock, pos, this.partMaxAge, false, 8, 8); + legacyBlock.writeUInt8(this.startColor.getRed(), pos++); + legacyBlock.writeUInt8(this.startColor.getGreen(), pos++); + legacyBlock.writeUInt8(this.startColor.getBlue(), pos++); + legacyBlock.writeUInt8(this.startColor.getAlpha(), pos++); + legacyBlock.writeUInt8(this.endColor.getRed(), pos++); + legacyBlock.writeUInt8(this.endColor.getGreen(), pos++); + legacyBlock.writeUInt8(this.endColor.getBlue(), pos++); + legacyBlock.writeUInt8(this.endColor.getAlpha(), pos++); + pos = pos + ParticleSystem.packFixed(legacyBlock, pos, this.startScaleX, false, 3, 5); + pos = pos + ParticleSystem.packFixed(legacyBlock, pos, this.startScaleY, false, 3, 5); + pos = pos + ParticleSystem.packFixed(legacyBlock, pos, this.endScaleX, false, 3, 5); + pos = pos + ParticleSystem.packFixed(legacyBlock, pos, this.endScaleY, false, 3, 5); + + if ((this.dataFlags & ParticleDataFlags.DataGlow) === ParticleDataFlags.DataGlow || (this.dataFlags & ParticleDataFlags.DataBlend) === ParticleDataFlags.DataBlend) + { + let extraBytes = 0; + if ((this.dataFlags & ParticleDataFlags.DataGlow) === ParticleDataFlags.DataGlow) + { + extraBytes += 2; + } + if ((this.dataFlags & ParticleDataFlags.DataBlend) === ParticleDataFlags.DataBlend) + { + extraBytes += 2; + } + const extraBuf = Buffer.allocUnsafe(extraBytes); + pos = 0; + if ((this.dataFlags & ParticleDataFlags.DataGlow) === ParticleDataFlags.DataGlow) + { + extraBuf.writeUInt8(this.startGlow * 255, pos++); + extraBuf.writeUInt8(this.endGlow * 255, pos++); + } + if ((this.dataFlags & ParticleDataFlags.DataBlend) === ParticleDataFlags.DataBlend) + { + extraBuf.writeUInt8(this.blendFuncSource, pos++); + extraBuf.writeUInt8(this.blendFuncDest, pos++); + } + + const totalSize = 4 + 86 + 4; + const finalBuffer = Buffer.allocUnsafe(totalSize); + pos = 0; + finalBuffer.writeInt32LE(systemBlock.length, pos); pos = pos + 4; + systemBlock.copy(finalBuffer, pos); pos = pos + systemBlock.length; + finalBuffer.writeInt32LE(legacyBlock.length, pos); pos = pos + 4; + legacyBlock.copy(finalBuffer, pos); + return Buffer.concat([finalBuffer, extraBuf]); + } + else + { + // Just return the legacy style particle block + return Buffer.concat([systemBlock, legacyBlock]); + } + } + + toBase64(): string + { + return this.toBuffer().toString('base64'); + } } diff --git a/lib/classes/Quaternion.ts b/lib/classes/Quaternion.ts index 704da45..30b4b3f 100644 --- a/lib/classes/Quaternion.ts +++ b/lib/classes/Quaternion.ts @@ -22,6 +22,48 @@ export class Quaternion extends quat doc.ele('W', v.w); } + static fromXMLJS(obj: any, param: string): Quaternion | false + { + if (!obj[param]) + { + return false; + } + let value = obj[param]; + if (Array.isArray(value) && value.length > 0) + { + value = value[0]; + } + if (typeof value === 'object') + { + if (value['X'] !== undefined && value['Y'] !== undefined && value['Z'] !== undefined && value['W'] !== undefined) + { + let x = value['X']; + let y = value['Y']; + let z = value['Z']; + let w = value['W']; + if (Array.isArray(x) && x.length > 0) + { + x = x[0]; + } + if (Array.isArray(y) && y.length > 0) + { + y = y[0]; + } + if (Array.isArray(z) && z.length > 0) + { + z = z[0]; + } + if (Array.isArray(w) && w.length > 0) + { + w = w[0]; + } + return new Quaternion([x, y, z, w]); + } + return false; + } + return false; + } + constructor(buf?: Buffer | number[] | Quaternion, pos?: number) { if (buf instanceof Quaternion) diff --git a/lib/classes/Region.ts b/lib/classes/Region.ts index 712cf20..8485661 100644 --- a/lib/classes/Region.ts +++ b/lib/classes/Region.ts @@ -34,6 +34,7 @@ import {Color4} from './Color4'; import {SkyPreset} from './public/interfaces/SkyPreset'; import {Vector4} from './Vector4'; import {WaterPreset} from './public/interfaces/WaterPreset'; +import {ClientCommands} from './ClientCommands'; export class Region { @@ -98,6 +99,7 @@ export class Region caps: Caps; comms: Comms; clientEvents: ClientEvents; + clientCommands: ClientCommands; options: BotOptionFlags; agent: Agent; messageSubscription: Subscription; diff --git a/lib/classes/UUID.ts b/lib/classes/UUID.ts index 09f63b5..e62a4fb 100644 --- a/lib/classes/UUID.ts +++ b/lib/classes/UUID.ts @@ -36,6 +36,44 @@ export class UUID doc.ele('UUID', str); } + static fromXMLJS(obj: any, param: string): false | UUID + { + if (obj[param] === undefined) + { + return false; + } + if (Array.isArray(obj[param]) && obj[param].length > 0) + { + obj[param] = obj[param][0]; + } + if (typeof obj[param] === 'string') + { + if (validator.isUUID(obj[param])) + { + return new UUID(obj[param]); + } + return false; + } + if (typeof obj[param] === 'object') + { + if (obj[param]['UUID'] !== undefined && Array.isArray(obj[param]['UUID']) && obj[param]['UUID'].length > 0) + { + const u = obj[param]['UUID'][0]; + if (typeof u === 'string') + { + if (validator.isUUID(u)) + { + return new UUID(u); + } + return false; + } + return false; + } + return false; + } + return false; + } + constructor(buf?: Buffer | string, pos?: number) { if (buf !== undefined) diff --git a/lib/classes/Utils.ts b/lib/classes/Utils.ts index 9dc60fc..ddb9b22 100644 --- a/lib/classes/Utils.ts +++ b/lib/classes/Utils.ts @@ -22,6 +22,12 @@ export class Utils return buf.toString('utf8'); } } + static Clamp(value: number, min: number, max: number) + { + value = (value > max) ? max : value; + value = (value < min) ? min : value; + return value; + } static JSONStringify(obj: object, space: number) { const cache: any[] = []; @@ -137,7 +143,13 @@ export class Utils return ''; } } - + static FloatToByte(val: number, lower: number, upper: number) + { + val = Utils.Clamp(val, lower, upper); + val -= lower; + val /= (upper - lower); + return Math.round(val * 255); + } static ByteToFloat(byte: number, lower: number, upper: number) { const ONE_OVER_BYTEMAX: number = 1.0 / 255; diff --git a/lib/classes/Vector3.ts b/lib/classes/Vector3.ts index c467dfc..5bdf9a9 100644 --- a/lib/classes/Vector3.ts +++ b/lib/classes/Vector3.ts @@ -19,6 +19,43 @@ export class Vector3 extends vec3 doc.ele('Z', v.z); } + static fromXMLJS(obj: any, param: string): Vector3 | false + { + if (!obj[param]) + { + return false; + } + let value = obj[param]; + if (Array.isArray(value) && value.length > 0) + { + value = value[0]; + } + if (typeof value === 'object') + { + if (value['X'] !== undefined && value['Y'] !== undefined && value['Z'] !== undefined) + { + let x = value['X']; + let y = value['Y']; + let z = value['Z']; + if (Array.isArray(x) && x.length > 0) + { + x = x[0]; + } + if (Array.isArray(y) && y.length > 0) + { + y = y[0]; + } + if (Array.isArray(z) && z.length > 0) + { + z = z[0]; + } + return new Vector3([x, y, z]); + } + return false; + } + return false; + } + constructor(buf?: Buffer | number[] | Vector3, pos?: number, double?: boolean) { if (buf instanceof Vector3) diff --git a/lib/classes/public/ExtraParams.ts b/lib/classes/public/ExtraParams.ts new file mode 100644 index 0000000..edb5963 --- /dev/null +++ b/lib/classes/public/ExtraParams.ts @@ -0,0 +1,179 @@ +import {GameObject} from './GameObject'; +import {ExtraParamType} from '../../enums/ExtraParamType'; +import {FlexibleData} from './FlexibleData'; +import {LightData} from './LightData'; +import {LightImageData} from './LightImageData'; +import {MeshData} from './MeshData'; +import {SculptData} from './SculptData'; +import {UUID} from '../UUID'; +import {Vector3} from '../Vector3'; +import {Color4} from '../Color4'; + +export class ExtraParams +{ + flexibleData: FlexibleData | null = null; + lightData: LightData | null = null; + lightImageData: LightImageData | null = null; + meshData: MeshData | null = null; + sculptData: SculptData | null = null; + + static getLengthOfParams(buf: Buffer, pos: number): number + { + const startPos = pos; + if (pos >= buf.length) + { + return 0; + } + const extraParamCount = buf.readUInt8(pos++); + for (let k = 0; k < extraParamCount; k++) + { + const type: ExtraParamType = buf.readUInt16LE(pos); + pos = pos + 2; + const paramLength = buf.readUInt32LE(pos); + pos = pos + 4 + paramLength; + } + return pos - startPos; + } + static from(buf: Buffer): ExtraParams + { + const ep = new ExtraParams(); + if (buf instanceof Buffer) + { + let pos = 0; + if (pos >= buf.length) + { + return ep; + } + const extraParamCount = buf.readUInt8(pos++); + for (let k = 0; k < extraParamCount; k++) + { + const type: ExtraParamType = buf.readUInt16LE(pos); + pos = pos + 2; + const paramLength = buf.readUInt32LE(pos); + pos = pos + 4; + + switch (type) + { + case ExtraParamType.Flexible: + ep.flexibleData = new FlexibleData(buf, pos, paramLength); + break; + case ExtraParamType.Light: + ep.lightData = new LightData(buf, pos, paramLength); + break; + case ExtraParamType.LightImage: + ep.lightImageData = new LightImageData(buf, pos, paramLength); + break; + case ExtraParamType.Mesh: + ep.meshData = new MeshData(buf, pos, paramLength); + break; + case ExtraParamType.Sculpt: + ep.sculptData = new SculptData(buf, pos, paramLength); + break; + } + + pos += paramLength; + } + return ep; + } + return ep; + } + setMeshData(type: number, uuid: UUID) + { + this.meshData = new MeshData(); + this.meshData.type = type; + this.meshData.meshData = uuid; + } + setSculptData(type: number, uuid: UUID) + { + this.sculptData = new SculptData(); + this.sculptData.type = type; + this.sculptData.texture = uuid; + } + setFlexiData(softness: number, tension: number, drag: number, gravity: number, wind: number, force: Vector3) + { + this.flexibleData = new FlexibleData(); + this.flexibleData.Softness = softness; + this.flexibleData.Tension = tension; + this.flexibleData.Drag = drag; + this.flexibleData.Gravity = gravity; + this.flexibleData.Wind = wind; + this.flexibleData.Force = force; + } + setLightData(color: Color4, radius: number, cutoff: number, falloff: number, intensity: number) + { + this.lightData = new LightData(); + this.lightData.Color = color; + this.lightData.Radius = radius; + this.lightData.Cutoff = cutoff; + this.lightData.Falloff = falloff; + this.lightData.Intensity = intensity; + } + toBuffer(): Buffer + { + let totalLength = 1; + let paramCount = 0; + if (this.flexibleData !== null) + { + paramCount++; + totalLength = totalLength + 2 + 4 + 16; + } + if (this.lightData !== null) + { + paramCount++; + totalLength = totalLength + 2 + 4 + 16; + } + if (this.lightImageData !== null) + { + paramCount++; + totalLength = totalLength + 2 + 4 + 28; + } + if (this.meshData !== null) + { + paramCount++; + totalLength = totalLength + 2 + 4 + 17; + } + if (this.sculptData !== null) + { + paramCount++; + totalLength = totalLength + 2 + 4 + 17; + } + const buf = Buffer.allocUnsafe(totalLength); + let pos = 0; + buf.writeUInt8(paramCount, pos++); + if (this.flexibleData !== null) + { + buf.writeUInt16LE(ExtraParamType.Flexible, pos); pos = pos + 2; + buf.writeUInt32LE(16, pos); pos = pos + 4; + this.flexibleData.writeToBuffer(buf, pos); pos = pos + 16; + } + if (this.lightData !== null) + { + buf.writeUInt16LE(ExtraParamType.Light, pos); pos = pos + 2; + buf.writeUInt32LE(16, pos); pos = pos + 4; + this.lightData.writeToBuffer(buf, pos); pos = pos + 16; + } + if (this.lightImageData !== null) + { + buf.writeUInt16LE(ExtraParamType.LightImage, pos); pos = pos + 2; + buf.writeUInt32LE(28, pos); pos = pos + 4; + this.lightImageData.writeToBuffer(buf, pos); pos = pos + 28; + } + if (this.meshData !== null) + { + buf.writeUInt16LE(ExtraParamType.Mesh, pos); pos = pos + 2; + buf.writeUInt32LE(17, pos); pos = pos + 4; + this.meshData.writeToBuffer(buf, pos); pos = pos + 17; + } + if (this.sculptData !== null) + { + buf.writeUInt16LE(ExtraParamType.Sculpt, pos); pos = pos + 2; + buf.writeUInt32LE(17, pos); pos = pos + 4; + this.sculptData.writeToBuffer(buf, pos); pos = pos + 17; + } + return buf; + } + toBase64(): string + { + return this.toBuffer().toString('base64'); + } +} diff --git a/lib/classes/public/FlexibleData.ts b/lib/classes/public/FlexibleData.ts index 231111f..e6ab66c 100644 --- a/lib/classes/public/FlexibleData.ts +++ b/lib/classes/public/FlexibleData.ts @@ -9,16 +9,29 @@ export class FlexibleData Wind = 0.0; Force = Vector3.getZero(); - constructor(buf: Buffer, pos: number, length: number) + constructor(buf?: Buffer, pos?: number, length?: number) { - if (length >= 5) + if (buf !== undefined && pos !== undefined && length !== undefined) { - this.Softness = ((buf.readUInt8(pos) & 0x80) >> 6) | ((buf.readUInt8(pos + 1) & 0x80) >> 7); - this.Tension = (buf.readUInt8(pos++) & 0x7F) / 10.0; - this.Drag = (buf.readUInt8(pos++) & 0x7F) / 10.0; - this.Gravity = (buf.readUInt8(pos++) / 10.0) - 10.0; - this.Wind = (buf.readUInt8(pos++) / 10.0); - this.Force = new Vector3(buf, pos); + if (length >= 5) + { + this.Softness = ((buf.readUInt8(pos) & 0x80) >> 6) | ((buf.readUInt8(pos + 1) & 0x80) >> 7); + this.Tension = (buf.readUInt8(pos++) & 0x7F) / 10.0; + this.Drag = (buf.readUInt8(pos++) & 0x7F) / 10.0; + this.Gravity = (buf.readUInt8(pos++) / 10.0) - 10.0; + this.Wind = (buf.readUInt8(pos++) / 10.0); + this.Force = new Vector3(buf, pos); + } } } -} \ No newline at end of file + writeToBuffer(buf: Buffer, pos: number) + { + buf[pos] = (this.Softness & 2) << 6; + buf[pos + 1] = (this.Softness & 1) << 7; + buf[pos++] |= ((this.Tension * 10) & 0x7F); + buf[pos++] |= ((this.Drag * 10) & 0x7F); + buf[pos++] = (this.Gravity + 10.0) * 10.0; + buf[pos++] = (this.Wind) * 10; + this.Force.writeToBuffer(buf, pos, false); + } +} diff --git a/lib/classes/public/GameObject.ts b/lib/classes/public/GameObject.ts index 24500e2..3444aa1 100644 --- a/lib/classes/public/GameObject.ts +++ b/lib/classes/public/GameObject.ts @@ -10,28 +10,32 @@ import {ITreeBoundingBox} from '../interfaces/ITreeBoundingBox'; import {NameValue} from '../NameValue'; import * as Long from 'long'; import {IGameObjectData} from '../interfaces/IGameObjectData'; -import {FlexibleData} from './FlexibleData'; -import {LightData} from './LightData'; -import {LightImageData} from './LightImageData'; -import {SculptData} from './SculptData'; -import {MeshData} from './MeshData'; -import {PCode, PrimFlags, SoundFlags} from '../..'; +import { + HoleType, + HTTPAssets, + PCode, + PhysicsShapeType, + PrimFlags, + ProfileShape, SculptType, + SoundFlags, + Utils +} from '../..'; import * as builder from 'xmlbuilder'; import {XMLElementOrXMLNode} from 'xmlbuilder'; +import * as xml2js from 'xml2js'; import {Region} from '../Region'; -import {TextureAnimFlags} from '../../enums/TextureAnimFlags'; -import {ProfileShape} from '../../enums/ProfileShape'; -import {HoleType} from '../../enums/HoleType'; -import {PhysicsShapeType} from '../../enums/PhysicsShapeType'; import {InventoryItem} from '../InventoryItem'; +import {InventoryType} from '../../enums/InventoryType'; +import {LLWearable} from '../LLWearable'; +import {TextureAnim} from './TextureAnim'; +import {ExtraParams} from './ExtraParams'; export class GameObject implements IGameObjectData { rtreeEntry?: ITreeBoundingBox; - TextureAnim?: Buffer; - Data?: Buffer; - ObjectData?: Buffer; - PSBlock?: Buffer; + + textureAnim: TextureAnim; + extraParams: ExtraParams; deleted = false; creatorID?: UUID; @@ -91,7 +95,6 @@ export class GameObject implements IGameObjectData PathEnd?: number; PathScaleX?: number; PathScaleY?: number; - ExtraParams?: Buffer; PathShearX?: number; PathShearY?: number; PathTwist?: number; @@ -123,18 +126,6 @@ export class GameObject implements IGameObjectData SoundFlags?: SoundFlags; SoundRadius?: number; Particles?: ParticleSystem; - FlexibleData?: FlexibleData; - LightData?: LightData; - LightImageData?: LightImageData; - SculptData?: SculptData; - MeshData?: MeshData; - TextureAnimFlags?: TextureAnimFlags; - TextureAnimFace?: number; - TextureAnimSizeX?: number; - TextureAnimSizeY?: number; - TextureAnimStart?: number; - TextureAnimLength?: number; - TextureAnimRate?: number; density?: number; friction?: number; @@ -148,6 +139,445 @@ export class GameObject implements IGameObjectData resolveAttempts = 0; + private static getFromXMLJS(obj: any, param: string): any + { + if (obj[param] === undefined) + { + return false; + } + let retParam; + if (Array.isArray(obj[param])) + { + retParam = obj[param][0]; + } + else + { + retParam = obj[param]; + } + if (typeof retParam === 'string') + { + if (retParam.toLowerCase() === 'false') + { + return false; + } + if (retParam.toLowerCase() === 'true') + { + return true; + } + const numVar = parseInt(retParam, 10); + if (numVar >= Number.MIN_SAFE_INTEGER && numVar <= Number.MAX_SAFE_INTEGER && String(numVar) === retParam) + { + return numVar + } + } + return retParam; + } + + private static partFromXMLJS(obj: any, root: boolean) + { + const go = new GameObject(); + go.Flags = 0; + let prop: any; + if (this.getFromXMLJS(obj, 'AllowedDrop')) + { + go.Flags = go.Flags | PrimFlags.AllowInventoryDrop; + } + if (prop = UUID.fromXMLJS(obj, 'CreatorID')) + { + go.creatorID = prop; + } + if (prop = UUID.fromXMLJS(obj, 'FolderID')) + { + go.folderID = prop; + } + if (prop = this.getFromXMLJS(obj, 'InventorySerial')) + { + go.inventorySerial = prop; + } + if (prop = UUID.fromXMLJS(obj, 'UUID')) + { + go.FullID = prop; + } + if (prop = this.getFromXMLJS(obj, 'LocalId')) + { + go.ID = prop; + } + if (prop = this.getFromXMLJS(obj, 'Name')) + { + go.name = prop; + } + if (prop = this.getFromXMLJS(obj, 'Material')) + { + go.Material = prop; + } + if (prop = Vector3.fromXMLJS(obj, 'GroupPosition')) + { + if (root) + { + go.Position = prop; + } + } + if (prop = Vector3.fromXMLJS(obj, 'OffsetPosition')) + { + if (!root) + { + go.Position = prop; + } + } + if (prop = Quaternion.fromXMLJS(obj, 'RotationOffset')) + { + go.Rotation = prop; + } + if (prop = Vector3.fromXMLJS(obj, 'Velocity')) + { + go.Velocity = prop; + } + if (prop = Vector3.fromXMLJS(obj, 'AngularVelocity')) + { + go.AngularVelocity = prop; + } + if (prop = Vector3.fromXMLJS(obj, 'Acceleration')) + { + go.Acceleration = prop; + } + if (prop = this.getFromXMLJS(obj, 'Description')) + { + go.description = prop; + } + if (prop = this.getFromXMLJS(obj, 'Text')) + { + go.Text = prop; + } + if (prop = Color4.fromXMLJS(obj, 'Color')) + { + go.TextColor = prop; + } + if (prop = this.getFromXMLJS(obj, 'SitName')) + { + go.sitName = prop; + } + if (prop = this.getFromXMLJS(obj, 'TouchName')) + { + go.touchName = prop; + } + if (prop = this.getFromXMLJS(obj, 'ClickAction')) + { + go.ClickAction = prop; + } + if (prop = Vector3.fromXMLJS(obj, 'Scale')) + { + go.Scale = prop; + } + if (prop = this.getFromXMLJS(obj, 'ParentID')) + { + go.ParentID = prop; + } + if (prop = this.getFromXMLJS(obj, 'Category')) + { + go.category = prop; + } + if (prop = this.getFromXMLJS(obj, 'SalePrice')) + { + go.salePrice = prop; + } + if (prop = this.getFromXMLJS(obj, 'ObjectSaleType')) + { + go.saleType = prop; + } + if (prop = this.getFromXMLJS(obj, 'OwnershipCost')) + { + go.ownershipCost = prop; + } + if (prop = UUID.fromXMLJS(obj, 'GroupID')) + { + go.groupID = prop; + } + if (prop = UUID.fromXMLJS(obj, 'OwnerID')) + { + go.OwnerID = prop; + } + if (prop = UUID.fromXMLJS(obj, 'LastOwnerID')) + { + go.lastOwnerID = prop; + } + if (prop = this.getFromXMLJS(obj, 'BaseMask')) + { + go.baseMask = prop; + } + if (prop = this.getFromXMLJS(obj, 'OwnerMask')) + { + go.ownerMask = prop; + } + if (prop = this.getFromXMLJS(obj, 'GroupMask')) + { + go.groupMask = prop; + } + if (prop = this.getFromXMLJS(obj, 'EveryoneMask')) + { + go.everyoneMask = prop; + } + if (prop = this.getFromXMLJS(obj, 'NextOwnerMask')) + { + go.nextOwnerMask = prop; + } + if (prop = this.getFromXMLJS(obj, 'Flags')) + { + let flags = 0; + if (typeof prop === 'string') + { + const flagList = prop.split(' '); + for (const flag of flagList) + { + const f: any = String(flag); + if (PrimFlags[f]) + { + flags = flags | parseInt(PrimFlags[f], 10); + } + } + } + go.Flags = flags; + } + if (prop = this.getFromXMLJS(obj, 'TextureAnimation')) + { + const buf = Buffer.from(prop, 'base64'); + go.textureAnim = TextureAnim.from(buf); + } + if (prop = this.getFromXMLJS(obj, 'ParticleSystem')) + { + const buf = Buffer.from(prop, 'base64'); + go.Particles = ParticleSystem.from(buf); + } + if (prop = this.getFromXMLJS(obj, 'PhysicsShapeType')) + { + go.physicsShapeType = prop; + } + if (prop = UUID.fromXMLJS(obj, 'SoundID')) + { + go.Sound = prop; + } + if (prop = UUID.fromXMLJS(obj, 'SoundGain')) + { + go.SoundGain = prop; + } + if (prop = UUID.fromXMLJS(obj, 'SoundFlags')) + { + go.SoundFlags = prop; + } + if (prop = UUID.fromXMLJS(obj, 'SoundRadius')) + { + go.SoundRadius = prop; + } + if (prop = this.getFromXMLJS(obj, 'Shape')) + { + const shape = prop; + if (prop = this.getFromXMLJS(shape, 'ProfileCurve')) + { + go.ProfileCurve = prop; + } + if (prop = this.getFromXMLJS(shape, 'TextureEntry')) + { + const buf = Buffer.from(prop, 'base64'); + go.TextureEntry = new TextureEntry(buf); + } + if (prop = this.getFromXMLJS(shape, 'PathBegin')) + { + go.PathBegin = prop; + } + if (prop = this.getFromXMLJS(shape, 'PathCurve')) + { + go.PathCurve = prop; + } + if (prop = this.getFromXMLJS(shape, 'PathEnd')) + { + go.PathEnd = prop; + } + if (prop = this.getFromXMLJS(shape, 'PathRadiusOffset')) + { + go.PathRadiusOffset = prop; + } + if (prop = this.getFromXMLJS(shape, 'PathRevolutions')) + { + go.PathRevolutions = prop; + } + if (prop = this.getFromXMLJS(shape, 'PathScaleX')) + { + go.PathScaleX = prop; + } + if (prop = this.getFromXMLJS(shape, 'PathScaleY')) + { + go.PathScaleY = prop; + } + if (prop = this.getFromXMLJS(shape, 'PathShearX')) + { + go.PathShearX = prop; + } + if (prop = this.getFromXMLJS(shape, 'PathSkew')) + { + go.PathSkew = prop; + } + if (prop = this.getFromXMLJS(shape, 'PathTaperX')) + { + go.PathTaperX = prop; + } + if (prop = this.getFromXMLJS(shape, 'PathTaperY')) + { + go.PathTaperY = prop; + } + if (prop = this.getFromXMLJS(shape, 'PathTwist')) + { + go.PathTwist = prop; + } + if (prop = this.getFromXMLJS(shape, 'PathTwistBegin')) + { + go.PathTwistBegin = prop; + } + if (prop = this.getFromXMLJS(shape, 'PCode')) + { + go.PCode = prop; + } + if (prop = this.getFromXMLJS(shape, 'ProfileBegin')) + { + go.ProfileBegin = prop; + } + if (prop = this.getFromXMLJS(shape, 'ProfileEnd')) + { + go.ProfileEnd = prop; + } + if (prop = this.getFromXMLJS(shape, 'ProfileHollow')) + { + go.ProfileHollow = prop; + } + if (prop = this.getFromXMLJS(shape, 'State')) + { + go.State = prop; + } + if (prop = this.getFromXMLJS(shape, 'ProfileShape')) + { + if (!go.ProfileCurve) + { + go.ProfileCurve = 0; + } + go.ProfileCurve = go.ProfileCurve | parseInt(ProfileShape[prop], 10); + } + if (prop = this.getFromXMLJS(shape, 'HollowShape')) + { + if (!go.ProfileCurve) + { + go.ProfileCurve = 0; + } + go.ProfileCurve = go.ProfileCurve | parseInt(HoleType[prop], 10); + } + if (this.getFromXMLJS(shape, 'SculptEntry')) + { + const type = this.getFromXMLJS(shape, 'SculptType'); + if (type !== false && type !== undefined) + { + const id = UUID.fromXMLJS(shape, 'SculptTexture'); + if (id instanceof UUID) + { + if (type & SculptType.Mesh) + { + go.extraParams.setMeshData(type, id); + } + else + { + go.extraParams.setSculptData(type, id); + } + } + } + } + if (this.getFromXMLJS(shape, 'FlexiEntry')) + { + const flexiSoftness = this.getFromXMLJS(shape, 'FlexiSoftness'); + const flexiTension = this.getFromXMLJS(shape, 'FlexiTension'); + const flexiDrag = this.getFromXMLJS(shape, 'FlexiDrag'); + const flexiGravity = this.getFromXMLJS(shape, 'FlexiGravity'); + const flexiWind = this.getFromXMLJS(shape, 'FlexiWind'); + const flexiForceX = this.getFromXMLJS(shape, 'FlexiForceX'); + const flexiForceY = this.getFromXMLJS(shape, 'FlexiForceY'); + const flexiForceZ = this.getFromXMLJS(shape, 'FlexiForceZ'); + if (flexiSoftness !== false && + flexiTension !== false && + flexiDrag && false && + flexiGravity !== false && + flexiWind !== false && + flexiForceX !== false && + flexiForceY !== false && + flexiForceZ !== false) + { + go.extraParams.setFlexiData(flexiSoftness, flexiTension, flexiDrag, flexiGravity, flexiWind, new Vector3([flexiForceX, flexiForceY, flexiForceZ])); + } + } + if (this.getFromXMLJS(shape, 'LightEntry')) + { + const lightColorR = this.getFromXMLJS(shape, 'LightColorR'); + const lightColorG = this.getFromXMLJS(shape, 'LightColorG'); + const lightColorB = this.getFromXMLJS(shape, 'LightColorB'); + const lightColorA = this.getFromXMLJS(shape, 'LightColorA'); + const lightRadius = this.getFromXMLJS(shape, 'LightRadius'); + const lightCutoff = this.getFromXMLJS(shape, 'LightCutoff'); + const lightFalloff = this.getFromXMLJS(shape, 'LightFalloff'); + const lightIntensity = this.getFromXMLJS(shape, 'LightIntensity'); + if (lightColorR !== false && + lightColorG !== false && + lightColorB !== false && + lightColorA !== false && + lightRadius !== false && + lightCutoff !== false && + lightFalloff !== false && + lightIntensity !== false) + { + go.extraParams.setLightData( + new Color4(lightColorR, lightColorG, lightColorB, lightColorA), + lightRadius, + lightCutoff, + lightFalloff, + lightIntensity + ); + } + } + if (prop = this.getFromXMLJS(shape, 'ExtraParams')) + { + const buf = Buffer.from(prop, 'base64'); + go.extraParams = ExtraParams.from(buf); + } + } + // TODO: TaskInventory + + + + console.log('BURP'); + process.exit(0); + } + + static fromXML(xml: string) + { + return new Promise((resolve, reject) => + { + xml2js.parseString(xml, (err, result) => + { + if (err) + { + reject(err); + } + else + { + if (!result['SceneObjectGroup']) + { + throw new Error('SceneObjectGroup not found'); + } + result = result['SceneObjectGroup']; + if (!result['SceneObjectPart']) + { + throw new Error('Root part not found'); + } + const rootPart = GameObject.partFromXMLJS(result['SceneObjectPart'][0], true); + + } + }); + }); + } + constructor() { this.Position = Vector3.getZero(); @@ -173,7 +603,7 @@ export class GameObject implements IGameObjectData return ''; } - private getInventoryXML(xml: XMLElementOrXMLNode, inv: InventoryItem) + private async getInventoryXML(xml: XMLElementOrXMLNode, inv: InventoryItem) { if (!inv.assetID.equals(UUID.zero())) { @@ -196,8 +626,25 @@ export class GameObject implements IGameObjectData } item.ele('CreationDate', inv.created.getTime() / 1000); item.ele('Description', inv.description); - item.ele('Flags', inv.flags); item.ele('InvType', inv.inventoryType); + + // For wearables, OpenSim expects flags to include the wearable type + if (inv.inventoryType === InventoryType.Wearable && !inv.assetID.equals(UUID.zero())) + { + try + { + const type = (inv.type === 5 ? HTTPAssets.ASSET_CLOTHING : HTTPAssets.ASSET_BODYPART); + const data = await this.region.clientCommands.asset.downloadAsset(type, inv.assetID); + const wearable: LLWearable = new LLWearable(data.toString('utf-8')); + inv.flags = inv.flags | wearable.type; + } + catch (error) + { + console.error(error); + } + } + + item.ele('Flags', inv.flags); UUID.getXML(item.ele('ParentID'), this.FullID); UUID.getXML(item.ele('ParentPartID'), this.FullID); item.ele('Type', inv.type); @@ -205,13 +652,12 @@ export class GameObject implements IGameObjectData } } - private getXML(xml: XMLElementOrXMLNode, rootPrim: GameObject, linkNum: number) + private async getXML(xml: XMLElementOrXMLNode, rootPrim: GameObject, linkNum: number) { const sceneObjectPart = xml.ele('SceneObjectPart').att('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance').att('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema'); sceneObjectPart.ele('AllowedDrop', (this.Flags !== undefined && (this.Flags & PrimFlags.AllowInventoryDrop) !== 0) ? 'true' : 'false'); UUID.getXML(sceneObjectPart.ele('CreatorID'), this.creatorID); - sceneObjectPart.ele('CreatorData', 'node-metaverse'); - UUID.getXML(sceneObjectPart.ele('CreatorID'), this.folderID); + UUID.getXML(sceneObjectPart.ele('FolderID'), this.folderID); sceneObjectPart.ele('InventorySerial', this.inventorySerial); UUID.getXML(sceneObjectPart.ele('UUID'), this.FullID); sceneObjectPart.ele('LocalId', this.ID); @@ -251,9 +697,9 @@ export class GameObject implements IGameObjectData { shape.ele('TextureEntry', this.TextureEntry.binary.toString('base64')); } - if (this.ExtraParams) + if (this.extraParams) { - shape.ele('ExtraParams', this.ExtraParams.toString('base64')); + shape.ele('ExtraParams', this.extraParams.toBase64()); } shape.ele('PathBegin', this.PathBegin); shape.ele('PathCurve', this.PathCurve); @@ -283,48 +729,48 @@ export class GameObject implements IGameObjectData shape.ele('ProfileShape', ProfileShape[profileShape]); shape.ele('HollowShape', HoleType[holeType]); } - if (this.MeshData !== undefined) + if (this.extraParams !== undefined && this.extraParams.meshData !== null) { - shape.ele('SculptType', this.MeshData.type); - UUID.getXML(shape.ele('SculptTexture'), this.MeshData.meshData); + shape.ele('SculptType', this.extraParams.meshData.type); + UUID.getXML(shape.ele('SculptTexture'), this.extraParams.meshData.meshData); shape.ele('SculptEntry', true); } - else if (this.SculptData !== undefined) + else if (this.extraParams !== undefined && this.extraParams.sculptData !== null) { - shape.ele('SculptType', this.SculptData.type); - UUID.getXML(shape.ele('SculptTexture'), this.SculptData.texture); + shape.ele('SculptType', this.extraParams.sculptData.type); + UUID.getXML(shape.ele('SculptTexture'), this.extraParams.sculptData.texture); shape.ele('SculptEntry', true); } else { shape.ele('SculptEntry', false); } - if (this.FlexibleData !== undefined) + if (this.extraParams !== undefined && this.extraParams.flexibleData !== null) { - shape.ele('FlexiSoftness', this.FlexibleData.Softness); - shape.ele('FlexiTension', this.FlexibleData.Tension); - shape.ele('FlexiDrag', this.FlexibleData.Drag); - shape.ele('FlexiGravity', this.FlexibleData.Gravity); - shape.ele('FlexiWind', this.FlexibleData.Wind); - shape.ele('FlexiForceX', this.FlexibleData.Force.x); - shape.ele('FlexiForceY', this.FlexibleData.Force.y); - shape.ele('FlexiForceZ', this.FlexibleData.Force.z); + shape.ele('FlexiSoftness', this.extraParams.flexibleData.Softness); + shape.ele('FlexiTension', this.extraParams.flexibleData.Tension); + shape.ele('FlexiDrag', this.extraParams.flexibleData.Drag); + shape.ele('FlexiGravity', this.extraParams.flexibleData.Gravity); + shape.ele('FlexiWind', this.extraParams.flexibleData.Wind); + shape.ele('FlexiForceX', this.extraParams.flexibleData.Force.x); + shape.ele('FlexiForceY', this.extraParams.flexibleData.Force.y); + shape.ele('FlexiForceZ', this.extraParams.flexibleData.Force.z); shape.ele('FlexiEntry', true); } else { shape.ele('FlexiEntry', false); } - if (this.LightData !== undefined) + if (this.extraParams !== undefined && this.extraParams.lightData !== null) { - shape.ele('LightColorR', this.LightData.Color.red); - shape.ele('LightColorG', this.LightData.Color.green); - shape.ele('LightColorB', this.LightData.Color.blue); - shape.ele('LightColorA', this.LightData.Color.alpha); - shape.ele('LightRadius', this.LightData.Radius); - shape.ele('LightCutoff', this.LightData.Cutoff); - shape.ele('LightFalloff', this.LightData.Falloff); - shape.ele('LightIntensity', this.LightData.Intensity); + shape.ele('LightColorR', this.extraParams.lightData.Color.red); + shape.ele('LightColorG', this.extraParams.lightData.Color.green); + shape.ele('LightColorB', this.extraParams.lightData.Color.blue); + shape.ele('LightColorA', this.extraParams.lightData.Color.alpha); + shape.ele('LightRadius', this.extraParams.lightData.Radius); + shape.ele('LightCutoff', this.extraParams.lightData.Cutoff); + shape.ele('LightFalloff', this.extraParams.lightData.Falloff); + shape.ele('LightIntensity', this.extraParams.lightData.Intensity); shape.ele('LightEntry', true); } else @@ -366,13 +812,13 @@ export class GameObject implements IGameObjectData } } sceneObjectPart.ele('Flags', flags.join(' ')); - if (this.TextureAnim) + if (this.textureAnim) { - sceneObjectPart.ele('TextureAnimation', this.TextureAnim.toString('base64')); + sceneObjectPart.ele('TextureAnimation', this.textureAnim.toBase64()); } - if (this.Particles && this.PSBlock) + if (this.Particles) { - sceneObjectPart.ele('ParticleSystem', this.PSBlock.toString('base64')); + sceneObjectPart.ele('ParticleSystem', this.Particles.toBase64()); } if (this.physicsShapeType) { @@ -391,22 +837,22 @@ export class GameObject implements IGameObjectData const inventory = sceneObjectPart.ele('TaskInventory'); for (const inv of this.inventory) { - this.getInventoryXML(inventory, inv); + await this.getInventoryXML(inventory, inv); } } } - exportXML(): string + async exportXML(): Promise { const document = builder.create('SceneObjectGroup'); let linkNum = 1; - this.getXML(document, this, linkNum); + await this.getXML(document, this, linkNum); if (this.children && this.children.length > 0) { const otherParts = document.ele('OtherParts'); for (const child of this.children) { - child.getXML(otherParts, this, ++linkNum); + await child.getXML(otherParts, this, ++linkNum); } } return document.end({pretty: true, allowEmpty: true}); @@ -499,11 +945,6 @@ export class GameObject implements IGameObjectData SoundFlags: this.SoundFlags, SoundRadius: this.SoundRadius, Particles: this.Particles, - FlexibleData: this.FlexibleData, - LightData: this.LightData, - LightImageData: this.LightImageData, - SculptData: this.SculptData, - MeshData: this.MeshData, density: this.density, friction: this.friction, gravityMultiplier: this.gravityMultiplier, @@ -511,4 +952,94 @@ export class GameObject implements IGameObjectData restitution: this.restitution } } + setObjectData(data: Buffer) + { + let dataPos = 0; + + // noinspection FallThroughInSwitchStatementJS, TsLint + switch (data.length) + { + case 76: + // Avatar collision normal; + this.CollisionPlane = new Vector4(data, dataPos); + dataPos += 16; + case 60: + // Position + this.Position = new Vector3(data, dataPos); + dataPos += 12; + this.Velocity = new Vector3(data, dataPos); + dataPos += 12; + this.Acceleration = new Vector3(data, dataPos); + dataPos += 12; + this.Rotation = new Quaternion(data, dataPos); + dataPos += 12; + this.AngularVelocity = new Vector3(data, dataPos); + dataPos += 12; + break; + case 48: + this.CollisionPlane = new Vector4(data, dataPos); + dataPos += 16; + case 32: + this.Position = new Vector3([ + Utils.UInt16ToFloat(data.readUInt16LE(dataPos), -0.5 * 256.0, 1.5 * 256.0), + Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 2), -0.5 * 256.0, 1.5 * 256.0), + Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 4), -256.0, 3.0 * 256.0) + ]); + dataPos += 6; + this.Velocity = new Vector3([ + Utils.UInt16ToFloat(data.readUInt16LE(dataPos), -256.0, 256.0), + Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 2), -256.0, 256.0), + Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 4), -256.0, 256.0) + ]); + dataPos += 6; + this.Acceleration = new Vector3([ + Utils.UInt16ToFloat(data.readUInt16LE(dataPos), -256.0, 256.0), + Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 2), -256.0, 256.0), + Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 4), -256.0, 256.0) + ]); + dataPos += 6; + this.Rotation = new Quaternion([ + Utils.UInt16ToFloat(data.readUInt16LE(dataPos), -1.0, 1.0), + Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 2), -1.0, 1.0), + Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 4), -1.0, 1.0), + Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 4), -1.0, 1.0) + ]); + dataPos += 8; + this.AngularVelocity = new Vector3([ + Utils.UInt16ToFloat(data.readUInt16LE(dataPos), -256.0, 256.0), + Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 2), -256.0, 256.0), + Utils.UInt16ToFloat(data.readUInt16LE(dataPos + 4), -256.0, 256.0) + ]); + dataPos += 6; + break; + case 16: + this.Position = new Vector3([ + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0) + ]); + this.Velocity = new Vector3([ + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0) + ]); + this.Acceleration = new Vector3([ + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0) + ]); + this.Rotation = new Quaternion([ + Utils.ByteToFloat(data.readUInt8(dataPos++), -1.0, 1.0), + Utils.ByteToFloat(data.readUInt8(dataPos++), -1.0, 1.0), + Utils.ByteToFloat(data.readUInt8(dataPos++), -1.0, 1.0), + Utils.ByteToFloat(data.readUInt8(dataPos++), -1.0, 1.0) + ]); + this.AngularVelocity = new Vector3([ + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0), + Utils.ByteToFloat(data.readUInt8(dataPos++), -256.0, 256.0) + ]); + break; + } + } } diff --git a/lib/classes/public/LightData.ts b/lib/classes/public/LightData.ts index 04c79eb..6ad9682 100644 --- a/lib/classes/public/LightData.ts +++ b/lib/classes/public/LightData.ts @@ -9,22 +9,34 @@ export class LightData Falloff = 0.0; Intensity = 0.0; - constructor(buf: Buffer, pos: number, length: number) + constructor(buf?: Buffer, pos?: number, length?: number) { - if (length >= 16) + if (buf !== undefined && pos !== undefined && length !== undefined) { - this.Color = new Color4(buf, pos, false); - pos += 4; - this.Radius = buf.readFloatLE(pos); - pos += 4; - this.Cutoff = buf.readFloatLE(pos); - pos += 4; - this.Falloff = buf.readFloatLE(pos); - if (typeof this.Color.alpha === 'number') + if (length >= 16) { - this.Intensity = this.Color.alpha; + this.Color = new Color4(buf, pos, false); + pos += 4; + this.Radius = buf.readFloatLE(pos); + pos += 4; + this.Cutoff = buf.readFloatLE(pos); + pos += 4; + this.Falloff = buf.readFloatLE(pos); + if (typeof this.Color.alpha === 'number') + { + this.Intensity = this.Color.alpha; + } + this.Color.alpha = 1.0; } - this.Color.alpha = 1.0; } } + writeToBuffer(buf: Buffer, pos: number) + { + const tmpColour = new Color4(this.Color.getRed(), this.Color.getGreen(), this.Color.getBlue(), this.Color.getAlpha()); + tmpColour.alpha = this.Intensity; + tmpColour.writeToBuffer(buf, pos); pos = pos + 4; + buf.writeFloatLE(this.Radius, pos); pos = pos + 4; + buf.writeFloatLE(this.Cutoff, pos); pos = pos + 4; + buf.writeFloatLE(this.Falloff, pos); + } } diff --git a/lib/classes/public/LightImageData.ts b/lib/classes/public/LightImageData.ts index 288c06b..22e0bab 100644 --- a/lib/classes/public/LightImageData.ts +++ b/lib/classes/public/LightImageData.ts @@ -15,4 +15,9 @@ export class LightImageData this.params = new Vector3(buf, pos); } } -} \ No newline at end of file + writeToBuffer(buf: Buffer, pos: number) + { + this.texture.writeToBuffer(buf, pos); pos = pos + 16; + this.params.writeToBuffer(buf, pos, false); + } +} diff --git a/lib/classes/public/MeshData.ts b/lib/classes/public/MeshData.ts index fdea06f..b32eae5 100644 --- a/lib/classes/public/MeshData.ts +++ b/lib/classes/public/MeshData.ts @@ -1,18 +1,26 @@ import {UUID} from '../UUID'; -import {SculptType} from '../../enums/SculptType'; +import {SculptType} from '../..'; export class MeshData { meshData: UUID = UUID.zero(); type: SculptType = SculptType.None; - constructor(buf: Buffer, pos: number, length: number) + constructor(buf?: Buffer, pos?: number, length?: number) { - if (length >= 17) + if (buf !== undefined && pos !== undefined && length !== undefined) { - this.meshData = new UUID(buf, pos); - pos += 16; - this.type = buf.readUInt8(pos); + if (length >= 17) + { + this.meshData = new UUID(buf, pos); + pos += 16; + this.type = buf.readUInt8(pos); + } } } + writeToBuffer(buf: Buffer, pos: number) + { + this.meshData.writeToBuffer(buf, pos); pos = pos + 16; + buf.writeUInt8(this.type, pos); + } } diff --git a/lib/classes/public/SculptData.ts b/lib/classes/public/SculptData.ts index fb76ad2..fadc70b 100644 --- a/lib/classes/public/SculptData.ts +++ b/lib/classes/public/SculptData.ts @@ -1,18 +1,26 @@ import {UUID} from '../UUID'; -import {SculptType} from '../../enums/SculptType'; +import {SculptType} from '../..'; export class SculptData { texture: UUID = UUID.zero(); type: SculptType = SculptType.None; - constructor(buf: Buffer, pos: number, length: number) + constructor(buf?: Buffer, pos?: number, length?: number) { - if (length >= 17) + if (buf !== undefined && pos !== undefined && length !== undefined) { - this.texture = new UUID(buf, pos); - pos += 16; - this.type = buf.readUInt8(pos); + if (length >= 17) + { + this.texture = new UUID(buf, pos); + pos += 16; + this.type = buf.readUInt8(pos); + } } } + writeToBuffer(buf: Buffer, pos: number) + { + this.texture.writeToBuffer(buf, pos); pos = pos + 16; + buf.writeUInt8(this.type, pos); + } } diff --git a/lib/classes/public/TextureAnim.ts b/lib/classes/public/TextureAnim.ts new file mode 100644 index 0000000..9dc083a --- /dev/null +++ b/lib/classes/public/TextureAnim.ts @@ -0,0 +1,54 @@ +import {TextureAnimFlags, Vector2} from '../..'; + +export class TextureAnim +{ + textureAnimFlags: TextureAnimFlags = 0; + textureAnimFace = 0; + textureAnimSize = Vector2.getZero(); + textureAnimStart = 0; + textureAnimLength = 0; + textureAnimRate = 0; + + static from(buf: Buffer): TextureAnim + { + const obj = new TextureAnim(); + let animPos = 0; + if (buf.length >= 16) + { + obj.textureAnimFlags = buf.readUInt8(animPos++); + obj.textureAnimFace = buf.readUInt8(animPos++); + obj.textureAnimSize = new Vector2([ + buf.readUInt8(animPos++), + buf.readUInt8(animPos++) + ]); + obj.textureAnimStart = buf.readFloatLE(animPos); + animPos = animPos + 4; + obj.textureAnimLength = buf.readFloatLE(animPos); + animPos = animPos + 4; + obj.textureAnimRate = buf.readFloatLE(animPos); + } + return obj; + } + toBuffer(): Buffer + { + if (this.textureAnimFlags === 0 && this.textureAnimFace === 0 && this.textureAnimStart === 0 && this.textureAnimLength === 0 && this.textureAnimRate === 0) + { + return Buffer.allocUnsafe(0); + } + const buf = Buffer.allocUnsafe(16); + let animPos = 0; + buf.writeUInt8(this.textureAnimFlags, animPos++); + buf.writeUInt8(this.textureAnimFace, animPos++); + buf.writeUInt8(this.textureAnimSize.x, animPos++); + buf.writeUInt8(this.textureAnimSize.y, animPos++); + buf.writeFloatLE(this.textureAnimStart, animPos); animPos = animPos + 4; + buf.writeFloatLE(this.textureAnimLength, animPos); animPos = animPos + 4; + buf.writeFloatLE(this.textureAnimRate, animPos); + return buf; + } + toBase64(): string + { + const bin = this.toBuffer(); + return bin.toString('base64'); + } +} diff --git a/lib/enums/SaleType.ts b/lib/enums/SaleType.ts new file mode 100644 index 0000000..a11a71a --- /dev/null +++ b/lib/enums/SaleType.ts @@ -0,0 +1,7 @@ +export enum SaleType +{ + Not = 0, + Original = 1, + Copy = 2, + Contents = 3 +} diff --git a/lib/enums/WearableType.ts b/lib/enums/WearableType.ts new file mode 100644 index 0000000..7cbd260 --- /dev/null +++ b/lib/enums/WearableType.ts @@ -0,0 +1,20 @@ +export enum WearableType +{ + Shape = 0, + Skin, + Hair, + Eyes, + Shirt, + Pants, + Shoes, + Socks, + Jacket, + Gloves, + Undershirt, + Underpants, + Skirt, + Alpha, + Tattoo, + Physics, + Invalid = 255 +} diff --git a/lib/index.ts b/lib/index.ts index 8e863d9..3334b6f 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -86,6 +86,9 @@ import {Shininess} from './enums/Shininess'; import {SimAccessFlags} from './enums/SimAccessFlags'; import {TextureAnimFlags} from './enums/TextureAnimFlags'; import {TransferStatus} from './enums/TransferStatus'; +import {LLWearable} from './classes/LLWearable'; +import {ParticleSystem} from './classes/ParticleSystem'; +import {ExtraParams} from './classes/public/ExtraParams'; export { Bot, @@ -101,6 +104,9 @@ export { Vector2, Utils, TextureEntry, + LLWearable, + ParticleSystem, + ExtraParams, // Flags AgentFlags, diff --git a/package-lock.json b/package-lock.json index 36121fa..be3851a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -122,6 +122,16 @@ "@types/node": "*" } }, + "@types/xml2js": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.3.tgz", + "integrity": "sha512-Pv2HGRE4gWLs31In7nsyXEH4uVVsd0HNV9i2dyASvtDIlOtSTr1eczPLDpdEuyv5LWH5LT20GIXwPjkshKWI1g==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/node": "*" + } + }, "@types/xmlbuilder": { "version": "0.0.34", "resolved": "https://registry.npmjs.org/@types/xmlbuilder/-/xmlbuilder-0.0.34.tgz", @@ -2071,6 +2081,22 @@ "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=" }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + }, + "dependencies": { + "xmlbuilder": { + "version": "9.0.7", + "resolved": "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + } + } + }, "xmlbuilder": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.0.tgz", diff --git a/package.json b/package.json index aa3dc27..d756dd8 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@types/uuid": "^3.4.4", "@types/validator": "^9.4.2", "@types/xml": "^1.0.2", + "@types/xml2js": "^0.4.3", "@types/xmlbuilder": "0.0.34", "@types/xmlrpc": "^1.3.5", "mocha": "^5.2.0", @@ -55,6 +56,7 @@ "uuid": "^3.3.2", "validator": "^10.8.0", "xml": "^1.0.1", + "xml2js": "^0.4.19", "xmlbuilder": "^10.1.0", "xmlrpc": "^1.3.2" }