import { UUID } from './UUID'; import { Vector3 } from './Vector3'; import { Inventory } from './Inventory'; import { Wearable } from './Wearable'; import { Region } from './Region'; import { Message } from '../enums/Message'; import { Packet } from './Packet'; import { AvatarAnimationMessage } from './messages/AvatarAnimation'; import { AgentUpdateMessage } from './messages/AgentUpdate'; import { Quaternion } from './Quaternion'; import { AgentState } from '../enums/AgentState'; import { BuiltInAnimations } from '../enums/BuiltInAnimations'; import { AgentWearablesRequestMessage } from './messages/AgentWearablesRequest'; import { AgentWearablesUpdateMessage } from './messages/AgentWearablesUpdate'; import { RezSingleAttachmentFromInvMessage } from './messages/RezSingleAttachmentFromInv'; import { AttachmentPoint } from '../enums/AttachmentPoint'; import { Utils } from './Utils'; import { ClientEvents } from './ClientEvents'; import * as Long from 'long'; import { GroupChatSessionAgentListEvent } from '../events/GroupChatSessionAgentListEvent'; import { AgentFlags } from '../enums/AgentFlags'; import { ControlFlags } from '../enums/ControlFlags'; import { PacketFlags } from '../enums/PacketFlags'; import { FolderType } from '../enums/FolderType'; import { Subject, Subscription } from 'rxjs'; import { InventoryFolder } from './InventoryFolder'; import { BulkUpdateInventoryEvent } from '../events/BulkUpdateInventoryEvent'; import { BulkUpdateInventoryMessage } from './messages/BulkUpdateInventory'; import { InventoryItem } from './InventoryItem'; import { AgentDataUpdateMessage } from './messages/AgentDataUpdate'; import { InventoryLibrary } from '../enums/InventoryLibrary'; import Timer = NodeJS.Timer; export class Agent { firstName: string; lastName: string; localID = 0; agentID: UUID; activeGroupID: UUID = UUID.zero(); accessMax: string; regionAccess: string; agentAccess: string; currentRegion: Region; chatSessions = new Map, timeout?: Timer }>(); controlFlags: ControlFlags = 0; openID: { 'token'?: string, 'url'?: string } = {}; AOTransition: boolean; buddyList: { 'buddyRightsGiven': boolean, 'buddyID': UUID, 'buddyRightsHas': boolean }[] = []; uiFlags: { 'allowFirstLife'?: boolean } = {}; headRotation = Quaternion.getIdentity(); bodyRotation = Quaternion.getIdentity(); 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; cofVersion: number; home: { 'regionHandle'?: Long, 'position'?: Vector3, 'lookAt'?: Vector3 } = {}; snapshotConfigURL: string; inventory: Inventory; gestures: { assetID: UUID, itemID: UUID }[] = []; agentAppearanceService: string; wearables?: { attachments: Wearable[]; serialNumber: number }; agentUpdateTimer: Timer | null = null; estateManager = false; appearanceComplete = false; appearanceCompleteEvent: Subject = new Subject(); private clientEvents: ClientEvents; private animSubscription?: Subscription; public onGroupChatExpired = new Subject(); constructor(clientEvents: ClientEvents) { this.inventory = new Inventory(clientEvents, this); this.clientEvents = clientEvents; this.clientEvents.onGroupChatAgentListUpdate.subscribe((event: GroupChatSessionAgentListEvent) => { const str = event.groupID.toString(); const agent = event.agentID.toString(); const session = this.chatSessions.get(str); if (session === undefined) { return; } if (event.entered) { if (session.agents === undefined) { session.agents = new Map(); } session.agents.set(agent, { hasVoice: event.canVoiceChat, isModerator: event.isModerator }); } else { session.agents.delete(agent); } }); } public updateLastMessage(groupID: UUID): void { const str = groupID.toString(); const entry = this.chatSessions.get(str); if (entry === undefined) { return; } if (entry.timeout !== undefined) { clearInterval(entry.timeout); entry.timeout = setTimeout(this.groupChatExpired.bind(this, groupID), 900000); } } setIsEstateManager(is: boolean): void { this.estateManager = is; } getSessionAgentCount(uuid: UUID): number { const str = uuid.toString(); const session = this.chatSessions.get(str); if (session === undefined) { return 0; } else { return Object.keys(session.agents).length; } } addChatSession(uuid: UUID, timeout: boolean): boolean { const str = uuid.toString(); if (this.chatSessions.has(str)) { return false; } this.chatSessions.set(str, { agents: new Map(), timeout: timeout ? setTimeout(this.groupChatExpired.bind(this, uuid), 900000) : undefined }); return true; } private groupChatExpired(groupID: UUID): void { this.onGroupChatExpired.next(groupID); } hasChatSession(uuid: UUID): boolean { const str = uuid.toString(); return this.chatSessions.has(str); } deleteChatSession(uuid: UUID): boolean { const str = uuid.toString(); if (!this.chatSessions.has(str)) { return false; } this.chatSessions.delete(str); return true; } setCurrentRegion(region: Region): void { if (this.animSubscription !== undefined) { this.animSubscription.unsubscribe(); } this.currentRegion = region; this.animSubscription = this.currentRegion.circuit.subscribeToMessages([ Message.AvatarAnimation, Message.AgentDataUpdate, Message.BulkUpdateInventory ], this.onMessage.bind(this)); } circuitActive(): void { this.agentUpdateTimer = setInterval(this.sendAgentUpdate.bind(this), 1000); } sendAgentUpdate(): void { if (!this.currentRegion) { return; } const circuit = this.currentRegion.circuit; const agentUpdate: AgentUpdateMessage = new AgentUpdateMessage(); agentUpdate.AgentData = { AgentID: this.agentID, SessionID: circuit.sessionID, HeadRotation: this.headRotation, BodyRotation: this.bodyRotation, State: AgentState.None, CameraCenter: this.cameraCenter, CameraAtAxis: this.cameraLookAt, CameraLeftAxis: this.cameraLeftAxis, CameraUpAxis: this.cameraUpAxis, Far: this.cameraFar, ControlFlags: this.controlFlags, Flags: AgentFlags.None }; circuit.sendMessage(agentUpdate, 0); } shutdown(): void { if (this.agentUpdateTimer !== null) { clearInterval(this.agentUpdateTimer); this.agentUpdateTimer = null; } } onMessage(packet: Packet): void { if (packet.message.id === Message.AgentDataUpdate) { const msg = packet.message as AgentDataUpdateMessage; this.activeGroupID = msg.AgentData.ActiveGroupID; } else if (packet.message.id === Message.BulkUpdateInventory) { const msg = packet.message as BulkUpdateInventoryMessage; const evt = new BulkUpdateInventoryEvent(); for (const newItem of msg.ItemData) { const folder = this.inventory.findFolder(newItem.FolderID); const item = new InventoryItem(folder || undefined, this); item.assetID = newItem.AssetID; item.inventoryType = newItem.InvType; item.name = Utils.BufferToStringSimple(newItem.Name); item.salePrice = newItem.SalePrice; item.saleType = newItem.SaleType; item.created = new Date(newItem.CreationDate * 1000); item.parentID = newItem.FolderID; item.flags = newItem.Flags; item.itemID = newItem.ItemID; item.description = Utils.BufferToStringSimple(newItem.Description); item.type = newItem.Type; item.callbackID = newItem.CallbackID; item.permissions.baseMask = newItem.BaseMask; item.permissions.groupMask = newItem.GroupMask; item.permissions.nextOwnerMask = newItem.NextOwnerMask; item.permissions.ownerMask = newItem.OwnerMask; item.permissions.everyoneMask = newItem.EveryoneMask; item.permissions.owner = newItem.OwnerID; item.permissions.creator = newItem.CreatorID; item.permissions.group = newItem.GroupID; item.permissions.groupOwned = newItem.GroupOwned; evt.itemData.push(item); } for (const newFolder of msg.FolderData) { const fld = new InventoryFolder(InventoryLibrary.Main, this.inventory.main, this); fld.typeDefault = newFolder.Type; fld.name = Utils.BufferToStringSimple(newFolder.Name); fld.folderID = newFolder.FolderID; fld.parentID = newFolder.ParentID; evt.folderData.push(fld); } this.clientEvents.onBulkUpdateInventoryEvent.next(evt); } else if (packet.message.id === Message.AvatarAnimation) { const animMsg = packet.message as AvatarAnimationMessage; if (animMsg.Sender.ID.toString() === this.agentID.toString()) { for (const anim of animMsg.AnimationList) { const a = anim.AnimID.toString(); if (a === BuiltInAnimations.STANDUP || a === BuiltInAnimations.PRE_JUMP || a === BuiltInAnimations.LAND || a === BuiltInAnimations.MEDIUM_LAND || a === BuiltInAnimations.WALK || a === BuiltInAnimations.RUN) { // TODO: Pretty sure this isn't the best way to do this this.controlFlags = ControlFlags.AGENT_CONTROL_FINISH_ANIM; this.sendAgentUpdate(); this.controlFlags = 0; } } } } } async getWearables(): Promise { for (const uuid of Object.keys(this.inventory.main.skeleton)) { const folder = this.inventory.main.skeleton[uuid]; if (folder.typeDefault === FolderType.CurrentOutfit) { await folder.populate(false); return folder; } } throw new Error('Unable to get wearables from inventory') } async setInitialAppearance(): Promise { const circuit = this.currentRegion.circuit; const wearablesRequest: AgentWearablesRequestMessage = new AgentWearablesRequestMessage(); wearablesRequest.AgentData = { AgentID: this.agentID, SessionID: circuit.sessionID }; circuit.sendMessage(wearablesRequest, PacketFlags.Reliable); const wearables: AgentWearablesUpdateMessage = await circuit.waitForMessage(Message.AgentWearablesUpdate, 10000); if (!this.wearables || wearables.AgentData.SerialNum > this.wearables.serialNumber) { this.wearables = { serialNumber: wearables.AgentData.SerialNum, attachments: [] }; for (const wearable of wearables.WearableData) { if (this.wearables && this.wearables.attachments) { this.wearables.attachments.push({ itemID: wearable.ItemID, assetID: wearable.AssetID, wearableType: wearable.WearableType }); } } } const currentOutfitFolder = await this.getWearables(); const wornObjects = this.currentRegion.objects.getObjectsByParent(this.localID); for (const item of currentOutfitFolder.items) { if (item.type === 6) { let found = false; for (const obj of wornObjects) { if (obj.hasNameValueEntry('AttachItemID')) { if (item.itemID.toString() === obj.getNameValueEntry('AttachItemID')) { found = true; } } } if (!found) { const rsafi = new RezSingleAttachmentFromInvMessage(); rsafi.AgentData = { AgentID: this.agentID, SessionID: circuit.sessionID }; rsafi.ObjectData = { ItemID: new UUID(item.itemID.toString()), OwnerID: this.agentID, AttachmentPt: 0x80 | AttachmentPoint.Default, ItemFlags: item.flags, GroupMask: item.permissions.groupMask, EveryoneMask: item.permissions.everyoneMask, NextOwnerMask: item.permissions.nextOwnerMask, Name: Utils.StringToBuffer(item.name), Description: Utils.StringToBuffer(item.description) }; circuit.sendMessage(rsafi, PacketFlags.Reliable); } } } this.appearanceComplete = true; this.appearanceCompleteEvent.next(); } setControlFlag(flag: ControlFlags): void { this.controlFlags = this.controlFlags | flag; } clearControlFlag(flag: ControlFlags): void { this.controlFlags = this.controlFlags & ~flag; } }