Files
node-metaverse/lib/classes/Agent.ts

446 lines
16 KiB
TypeScript
Raw Normal View History

import { UUID } from './UUID';
import { Vector3 } from './Vector3';
import { Inventory } from './Inventory';
import type { Wearable } from './Wearable';
import type { Region } from './Region';
import { Message } from '../enums/Message';
import type { Packet } from './Packet';
import type { 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 type { AgentWearablesUpdateMessage } from './messages/AgentWearablesUpdate';
import { RezSingleAttachmentFromInvMessage } from './messages/RezSingleAttachmentFromInv';
import { AttachmentPoint } from '../enums/AttachmentPoint';
import { Utils } from './Utils';
import type { ClientEvents } from './ClientEvents';
import type * as Long from 'long';
import type { 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 type { Subscription } from 'rxjs';
import { Subject } from 'rxjs';
import { InventoryFolder } from './InventoryFolder';
import { BulkUpdateInventoryEvent } from '../events/BulkUpdateInventoryEvent';
import type { BulkUpdateInventoryMessage } from './messages/BulkUpdateInventory';
import { InventoryItem } from './InventoryItem';
import type { AgentDataUpdateMessage } from './messages/AgentDataUpdate';
2020-11-23 10:32:32 +00:00
import { InventoryLibrary } from '../enums/InventoryLibrary';
import { AssetType } from '../enums/AssetType';
2017-11-24 01:00:56 +00:00
export class Agent
{
public firstName: string;
public lastName: string;
public localID = 0;
public agentID: UUID;
public activeGroupID: UUID = UUID.zero();
public accessMax: string;
public regionAccess: string;
public agentAccess: string;
public currentRegion: Region;
public openID: {
2017-11-24 01:00:56 +00:00
'token'?: string,
'url'?: string
} = {};
public AOTransition: boolean;
public buddyList: {
2017-11-24 01:00:56 +00:00
'buddyRightsGiven': boolean,
'buddyID': UUID,
'buddyRightsHas': boolean
}[] = [];
public uiFlags: {
2017-11-24 01:00:56 +00:00
'allowFirstLife'?: boolean
} = {};
public maxGroups: number;
public agentFlags: number;
public startLocation: string;
public cofVersion: number;
public home: {
2017-11-24 01:00:56 +00:00
'regionHandle'?: Long,
'position'?: Vector3,
'lookAt'?: Vector3
} = {};
public snapshotConfigURL: string;
public readonly inventory: Inventory;
public gestures: {
2017-11-24 01:00:56 +00:00
assetID: UUID,
itemID: UUID
}[] = [];
public estateManager = false;
public appearanceComplete = false;
public agentAppearanceService: string;
public onGroupChatExpired = new Subject<UUID>();
public cameraLookAt: Vector3 = new Vector3([0.979546, 0.105575, -0.171303]);
public cameraCenter: Vector3 = new Vector3([199.58, 203.95, 24.304]);
public cameraLeftAxis: Vector3 = new Vector3([-1.0, 0.0, 0]);
public cameraUpAxis: Vector3 = new Vector3([0.0, 0.0, 1.0]);
public cameraFar = 1;
public readonly appearanceCompleteEvent: Subject<void> = new Subject<void>();
private readonly headRotation = Quaternion.getIdentity();
private readonly bodyRotation = Quaternion.getIdentity();
private wearables?: {
attachments: Wearable[];
serialNumber: number
};
private agentUpdateTimer: NodeJS.Timeout | null = null;
private controlFlags: ControlFlags = 0;
private readonly clientEvents: ClientEvents;
private animSubscription?: Subscription;
private readonly chatSessions = new Map<string, {
agents: Map<string, {
hasVoice: boolean;
isModerator: boolean
}>,
timeout?: NodeJS.Timeout
}>();
2017-11-24 01:00:56 +00:00
public constructor(clientEvents: ClientEvents)
2017-11-24 01:00:56 +00:00
{
this.inventory = new Inventory(this);
this.clientEvents = clientEvents;
2017-12-19 17:58:25 +00:00
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;
}
2017-12-19 17:58:25 +00:00
if (event.entered)
{
if (session.agents === undefined)
{
session.agents = new Map<string, {
hasVoice: boolean;
isModerator: boolean
}>();
}
session.agents.set(agent, {
2017-12-19 17:58:25 +00:00
hasVoice: event.canVoiceChat,
isModerator: event.isModerator
});
2017-12-19 17:58:25 +00:00
}
else
{
session.agents.delete(agent);
2017-12-19 17:58:25 +00:00
}
});
}
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);
}
}
public setIsEstateManager(is: boolean): void
{
this.estateManager = is;
}
public getSessionAgentCount(uuid: UUID): number
2017-12-19 17:58:25 +00:00
{
const str = uuid.toString();
const session = this.chatSessions.get(str);
if (session === undefined)
2017-12-19 17:58:25 +00:00
{
return 0;
}
else
{
return session.agents.size;
2017-12-19 17:58:25 +00:00
}
2017-11-24 01:00:56 +00:00
}
public addChatSession(uuid: UUID, timeout: boolean): boolean
{
const str = uuid.toString();
if (this.chatSessions.has(str))
{
return false;
}
this.chatSessions.set(str, {
agents: new Map<string, {
hasVoice: boolean,
isModerator: boolean
}>(),
timeout: timeout ? setTimeout(this.groupChatExpired.bind(this, uuid), 900000) : undefined
});
return true;
}
public groupChatExpired(groupID: UUID): void
{
this.onGroupChatExpired.next(groupID);
}
public hasChatSession(uuid: UUID): boolean
{
const str = uuid.toString();
return this.chatSessions.has(str);
}
public deleteChatSession(uuid: UUID): boolean
{
const str = uuid.toString();
if (!this.chatSessions.has(str))
{
return false;
}
this.chatSessions.delete(str);
return true;
}
public 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));
}
public circuitActive(): void
{
this.agentUpdateTimer = setInterval(this.sendAgentUpdate.bind(this), 1000);
}
public shutdown(): void
{
if (this.agentUpdateTimer !== null)
{
clearInterval(this.agentUpdateTimer);
this.agentUpdateTimer = null;
}
}
public async setInitialAppearance(): Promise<void>
{
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<AgentWearablesUpdateMessage>(Message.AgentWearablesUpdate, 30000);
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.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 === AssetType.Notecard)
{
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();
}
public setControlFlag(flag: ControlFlags): void
{
this.controlFlags = this.controlFlags | flag;
}
public clearControlFlag(flag: ControlFlags): void
{
this.controlFlags = this.controlFlags & ~flag;
}
public async getWearables(): Promise<InventoryFolder>
{
for (const uuid of this.inventory.main.skeleton.keys())
{
const folder = this.inventory.main.skeleton.get(uuid);
if (folder && folder.typeDefault === FolderType.CurrentOutfit)
{
await folder.populate(false);
return folder;
}
}
throw new Error('Unable to get wearables from inventory')
}
public sendAgentUpdate(): void
{
if (!this.currentRegion)
{
return;
}
const circuit = this.currentRegion.circuit;
const agentUpdate: AgentUpdateMessage = new AgentUpdateMessage();
agentUpdate.AgentData = {
AgentID: this.agentID,
SessionID: circuit.sessionID,
2023-03-18 00:06:49 +00:00
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
};
2023-11-09 18:06:49 +00:00
circuit.sendMessage(agentUpdate, 0 as PacketFlags);
}
private 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)
{
2020-11-23 10:32:32 +00:00
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())
{
2020-11-19 16:27:29 +00:00
for (const anim of animMsg.AnimationList)
{
const a = anim.AnimID.toString() as BuiltInAnimations;
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;
}
2020-11-19 16:27:29 +00:00
}
}
}
}
2017-11-24 01:00:56 +00:00
}