[BREAKING CHANGES] - Add new commands module for the Friends list, add new events for friends online/offline, friend rights management, friend map lookup

This commit is contained in:
Casper Warden
2018-10-12 14:34:43 +01:00
parent 375abc433e
commit 2a0c4dc3e8
86 changed files with 1406 additions and 330 deletions

View File

@@ -201,7 +201,7 @@ export class Circuit
if (handleObj.subscription !== null)
{
handleObj.subscription.unsubscribe();
const err = new TimeoutError('Timeout waiting for message of type ' + id);
const err = new TimeoutError('Timeout waiting for message of type ' + Message[id]);
err.timeout = true;
err.waitingForMessage = id;
reject(err);

View File

@@ -11,6 +11,7 @@ import {AgentCommands} from './commands/AgentCommands';
import {GroupCommands} from './commands/GroupCommands';
import {InventoryCommands} from './commands/InventoryCommands';
import {ParcelCommands} from './commands/ParcelCommands';
import {FriendCommands} from './commands/FriendCommands';
export class ClientCommands
{
@@ -19,6 +20,7 @@ export class ClientCommands
public teleport: TeleportCommands;
public region: RegionCommands;
public parcel: ParcelCommands;
public friends: FriendCommands;
public grid: GridCommands;
public comms: CommunicationsCommands;
public agent: AgentCommands;
@@ -33,6 +35,7 @@ export class ClientCommands
this.region = new RegionCommands(region, agent, bot);
this.parcel = new ParcelCommands(region, agent, bot);
this.grid = new GridCommands(region, agent, bot);
this.friends = new FriendCommands(region, agent, bot);
this.comms = new CommunicationsCommands(region, agent, bot);
this.agent = new AgentCommands(region, agent, bot);
this.group = new GroupCommands(region, agent, bot);
@@ -50,5 +53,6 @@ export class ClientCommands
this.agent.shutdown();
this.group.shutdown();
this.inventory.shutdown();
this.friends.shutdown();
}
}

View File

@@ -12,7 +12,9 @@ import {
LureEvent,
TeleportEvent,
ScriptDialogEvent,
EventQueueStateChangeEvent
EventQueueStateChangeEvent,
FriendOnlineEvent,
FriendRightsEvent, FriendRemovedEvent
} from '..';
import {Subject} from 'rxjs/internal/Subject';
@@ -34,4 +36,7 @@ export class ClientEvents
onFriendResponse: Subject<FriendResponseEvent> = new Subject<FriendResponseEvent>();
onScriptDialog: Subject<ScriptDialogEvent> = new Subject<ScriptDialogEvent>();
onEventQueueStateChange: Subject<EventQueueStateChangeEvent> = new Subject<EventQueueStateChangeEvent>();
onFriendOnline: Subject<FriendOnlineEvent> = new Subject<FriendOnlineEvent>();
onFriendRights: Subject<FriendRightsEvent> = new Subject<FriendRightsEvent>();
onFriendRemoved: Subject<FriendRemovedEvent> = new Subject<FriendRemovedEvent>();
}

View File

@@ -36,6 +36,21 @@ export class IPAddress
{
this.ip = ipaddr.parse(buf);
}
else
{
throw new Error('Invalid IP address');
}
}
}
else
{
if (ipaddr.isValid(buf))
{
this.ip = ipaddr.parse(buf);
}
else
{
throw new Error('Invalid IP address');
}
}
}

View File

@@ -178,7 +178,8 @@ export class Region
this.xCoordinate = region.X;
this.yCoordinate = region.Y;
this.mapImage = region.MapImageID;
this.regionHandle = Utils.RegionCoordinatesToHandle(this.xCoordinate, this.yCoordinate);
const globalPos = Utils.RegionCoordinatesToHandle(this.xCoordinate, this.yCoordinate);
this.regionHandle = globalPos.regionHandle;
return FilterResponse.Finish;
}
}

View File

@@ -1,5 +1,5 @@
import * as Long from 'long';
import {HTTPAssets} from '..';
import {GlobalPosition, HTTPAssets} from '..';
export class Utils
{
@@ -60,11 +60,20 @@ export class Utils
}
}
static RegionCoordinatesToHandle(regionX: number, regionY: number): Long
static RegionCoordinatesToHandle(regionX: number, regionY: number): GlobalPosition
{
regionX = Math.floor(regionX / 256) * 256;
regionY = Math.floor( regionY / 256) * 256;
return new Long(regionY, regionX);
const realRegionX = Math.floor(regionX / 256) * 256;
const realRegionY = Math.floor(regionY / 256) * 256;
const localX = regionX - realRegionX;
const localY = regionY - realRegionY;
const handle = new Long(realRegionY, realRegionX);
return {
'regionHandle': handle,
'regionX': realRegionX / 256,
'regionY': realRegionY / 256,
'localX': localX,
'localY': localY
};
}
static HTTPAssetTypeToInventoryType(HTTPAssetType: string)

53
lib/classes/Vector2.ts Normal file
View File

@@ -0,0 +1,53 @@
import {vec2} from '../tsm/vec2';
export class Vector2 extends vec2
{
static getZero(): Vector2
{
return new Vector2();
}
constructor(buf?: Buffer | number[], pos?: number, double?: boolean)
{
if (double === undefined)
{
double = false;
}
if (buf !== undefined && pos !== undefined && buf instanceof Buffer)
{
if (!double)
{
const x = buf.readFloatLE(pos);
const y = buf.readFloatLE(pos + 4);
super([x, y]);
}
else
{
const x = buf.readDoubleLE(pos);
const y = buf.readDoubleLE(pos + 8);
super([x, y]);
}
}
else if (buf !== undefined && Array.isArray(buf))
{
super(buf);
}
else
{
super();
}
}
writeToBuffer(buf: Buffer, pos: number, double: boolean)
{
if (double)
{
buf.writeDoubleLE(this.x, pos);
buf.writeDoubleLE(this.y, pos + 8);
}
else
{
buf.writeFloatLE(this.x, pos);
buf.writeFloatLE(this.y, pos + 4);
}
}
}

View File

@@ -1,17 +1,13 @@
import {CommandsBase} from './CommandsBase';
import {UUID} from '../UUID';
import {Utils} from '../Utils';
import {PacketFlags} from '../../enums/PacketFlags';
import {ImprovedInstantMessageMessage} from '../messages/ImprovedInstantMessage';
import {Vector3} from '../Vector3';
import {ChatFromViewerMessage} from '../messages/ChatFromViewer';
import {ChatType} from '../../enums/ChatType';
import {InstantMessageDialog} from '../../enums/InstantMessageDialog';
import Timer = NodeJS.Timer;
import {AcceptFriendshipMessage} from '../messages/AcceptFriendship';
import {DeclineFriendshipMessage} from '../messages/DeclineFriendship';
import {InventoryOfferedEvent} from '../../events/InventoryOfferedEvent';
import {AssetType, ChatSourceType, FriendRequestEvent, GroupChatSessionJoinEvent} from '../..';
import {GroupChatSessionJoinEvent, PacketFlags} from '../..';
export class CommunicationsCommands extends CommandsBase
{
@@ -361,132 +357,6 @@ export class CommunicationsCommands extends CommandsBase
});
}
async acceptFriendRequest(event: FriendRequestEvent): Promise<void>
{
const accept: AcceptFriendshipMessage = new AcceptFriendshipMessage();
accept.AgentData = {
AgentID: this.agent.agentID,
SessionID: this.circuit.sessionID
};
accept.TransactionBlock = {
TransactionID: event.requestID
};
accept.FolderData = [];
accept.FolderData.push(
{
'FolderID': this.agent.inventory.findFolderForType(AssetType.CallingCard)
}
);
const sequenceNo = this.circuit.sendMessage(accept, PacketFlags.Reliable);
return await this.circuit.waitForAck(sequenceNo, 10000);
}
async sendFriendRequest(to: UUID | string, message: string): Promise<void>
{
if (typeof to === 'string')
{
to = new UUID(to);
}
const requestID = UUID.random();
const agentName = this.agent.firstName + ' ' + this.agent.lastName;
const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage();
im.AgentData = {
AgentID: this.agent.agentID,
SessionID: this.circuit.sessionID
};
im.MessageBlock = {
FromGroup: false,
ToAgentID: to,
ParentEstateID: 0,
RegionID: UUID.zero(),
Position: Vector3.getZero(),
Offline: 0,
Dialog: InstantMessageDialog.FriendshipOffered,
ID: requestID,
Timestamp: Math.floor(new Date().getTime() / 1000),
FromAgentName: Utils.StringToBuffer(agentName),
Message: Utils.StringToBuffer(message),
BinaryBucket: Utils.StringToBuffer('')
};
im.EstateBlock = {
EstateID: 0
};
const sequenceNo = this.circuit.sendMessage(im, PacketFlags.Reliable);
return await this.circuit.waitForAck(sequenceNo, 10000);
}
private async respondToInventoryOffer(event: InventoryOfferedEvent, response: InstantMessageDialog): Promise<void>
{
const agentName = this.agent.firstName + ' ' + this.agent.lastName;
const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage();
const folder = this.agent.inventory.findFolderForType(event.type);
const binary = Buffer.allocUnsafe(16);
folder.writeToBuffer(binary, 0);
im.AgentData = {
AgentID: this.agent.agentID,
SessionID: this.circuit.sessionID
};
im.MessageBlock = {
FromGroup: false,
ToAgentID: event.from,
ParentEstateID: 0,
RegionID: UUID.zero(),
Position: Vector3.getZero(),
Offline: 0,
Dialog: response,
ID: event.requestID,
Timestamp: Math.floor(new Date().getTime() / 1000),
FromAgentName: Utils.StringToBuffer(agentName),
Message: Utils.StringToBuffer(''),
BinaryBucket: binary
};
im.EstateBlock = {
EstateID: 0
};
const sequenceNo = this.circuit.sendMessage(im, PacketFlags.Reliable);
return await this.circuit.waitForAck(sequenceNo, 10000);
}
async acceptInventoryOffer(event: InventoryOfferedEvent): Promise<void>
{
if (event.source === ChatSourceType.Object)
{
return await this.respondToInventoryOffer(event, InstantMessageDialog.TaskInventoryAccepted);
}
else
{
return await this.respondToInventoryOffer(event, InstantMessageDialog.InventoryAccepted);
}
}
async rejectInventoryOffer(event: InventoryOfferedEvent): Promise<void>
{
if (event.source === ChatSourceType.Object)
{
return await this.respondToInventoryOffer(event, InstantMessageDialog.TaskInventoryDeclined);
}
else
{
return await this.respondToInventoryOffer(event, InstantMessageDialog.InventoryDeclined);
}
}
async rejectFriendRequest(event: FriendRequestEvent): Promise<void>
{
const reject: DeclineFriendshipMessage = new DeclineFriendshipMessage();
reject.AgentData = {
AgentID: this.agent.agentID,
SessionID: this.circuit.sessionID
};
reject.TransactionBlock = {
TransactionID: event.requestID
};
const sequenceNo = this.circuit.sendMessage(reject, PacketFlags.Reliable);
return await this.circuit.waitForAck(sequenceNo, 10000);
}
sendGroupMessage(groupID: UUID | string, message: string): Promise<number>
{
return new Promise<number>((resolve, reject) =>

View File

@@ -0,0 +1,311 @@
import {CommandsBase} from './CommandsBase';
import {Region} from '../Region';
import {Agent} from '../Agent';
import {Bot} from '../../Bot';
import {Subscription} from 'rxjs/internal/Subscription';
import {Message} from '../../enums/Message';
import {Packet} from '../Packet';
import {OnlineNotificationMessage} from '../messages/OnlineNotification';
import {OfflineNotificationMessage} from '../messages/OfflineNotification';
import {TerminateFriendshipMessage} from '../messages/TerminateFriendship';
import {AssetType, Friend, FriendOnlineEvent, FriendRemovedEvent, FriendRequestEvent, FriendRightsEvent, MapInfoReplyEvent, MapLocation, PacketFlags, RightsFlags, UUID, Vector3} from '../..';
import {AcceptFriendshipMessage} from '../messages/AcceptFriendship';
import {ImprovedInstantMessageMessage} from '../messages/ImprovedInstantMessage';
import {InstantMessageDialog} from '../../enums/InstantMessageDialog';
import {Utils} from '../Utils';
import {DeclineFriendshipMessage} from '../messages/DeclineFriendship';
import {ChangeUserRightsMessage} from '../messages/ChangeUserRights';
import {FindAgentMessage} from '../messages/FindAgent';
import {IPAddress} from '../IPAddress';
import {FilterResponse} from '../../enums/FilterResponse';
import {GrantUserRightsMessage} from '../messages/GrantUserRights';
export class FriendCommands extends CommandsBase
{
friendMessages: Subscription;
friendsList: {
[key: string]: Friend
} = {};
constructor(region: Region, agent: Agent, bot: Bot)
{
super(region, agent, bot);
// FriendResponse is handled by Comms because it's part of the InstantMessageImproved module.
// We don't handle it here because it's always accompanied by an OnlineNotificationMessage.
this.friendMessages = this.circuit.subscribeToMessages([
Message.OnlineNotification,
Message.OfflineNotification,
Message.TerminateFriendship,
Message.ChangeUserRights
], async (packet: Packet) =>
{
switch (packet.message.id)
{
case Message.OnlineNotification:
{
const msg = packet.message as OnlineNotificationMessage;
for (const agentEntry of msg.AgentBlock)
{
const uuidStr = agentEntry.AgentID.toString();
if (this.friendsList[uuidStr] === undefined)
{
this.friendsList[uuidStr] = await this.bot.clientCommands.grid.avatarKey2Name(agentEntry.AgentID) as Friend;
this.friendsList[uuidStr].online = false;
this.friendsList[uuidStr].myRights = RightsFlags.None;
this.friendsList[uuidStr].theirRights = RightsFlags.None;
}
if (this.friendsList[uuidStr].online !== true)
{
this.friendsList[uuidStr].online = true;
const friendOnlineEvent = new FriendOnlineEvent();
friendOnlineEvent.friend = this.friendsList[uuidStr];
friendOnlineEvent.online = true;
this.bot.clientEvents.onFriendOnline.next(friendOnlineEvent);
}
}
break;
}
case Message.OfflineNotification:
{
const msg = packet.message as OfflineNotificationMessage;
for (const agentEntry of msg.AgentBlock)
{
const uuidStr = agentEntry.AgentID.toString();
if (this.friendsList[uuidStr] === undefined)
{
this.friendsList[uuidStr] = await this.bot.clientCommands.grid.avatarKey2Name(agentEntry.AgentID) as Friend;
this.friendsList[uuidStr].online = false;
this.friendsList[uuidStr].myRights = RightsFlags.None;
this.friendsList[uuidStr].theirRights = RightsFlags.None;
}
if (this.friendsList[uuidStr].online !== false)
{
this.friendsList[uuidStr].online = false;
const friendOnlineEvent = new FriendOnlineEvent();
friendOnlineEvent.friend = this.friendsList[uuidStr];
friendOnlineEvent.online = false;
this.bot.clientEvents.onFriendOnline.next(friendOnlineEvent);
}
}
break;
}
case Message.TerminateFriendship:
{
const msg = packet.message as TerminateFriendshipMessage;
const friendID = msg.ExBlock.OtherID;
const uuidStr = friendID.toString();
if (this.friendsList[uuidStr] !== undefined)
{
const event = new FriendRemovedEvent();
event.friend = this.friendsList[uuidStr];
this.bot.clientEvents.onFriendRemoved.next(event);
delete this.friendsList[uuidStr];
}
break;
}
case Message.ChangeUserRights:
{
const msg = packet.message as ChangeUserRightsMessage;
for (const rightsEntry of msg.Rights)
{
let uuidStr = '';
if (rightsEntry.AgentRelated.equals(this.agent.agentID))
{
// My rights
uuidStr = msg.AgentData.AgentID.toString();
if (this.friendsList[uuidStr] === undefined)
{
this.friendsList[uuidStr] = await this.bot.clientCommands.grid.avatarKey2Name(rightsEntry.AgentRelated) as Friend;
this.friendsList[uuidStr].online = false;
this.friendsList[uuidStr].myRights = RightsFlags.None;
this.friendsList[uuidStr].theirRights = RightsFlags.None;
}
this.friendsList[uuidStr].myRights = rightsEntry.RelatedRights;
}
else
{
uuidStr = rightsEntry.AgentRelated.toString();
if (this.friendsList[uuidStr] === undefined)
{
this.friendsList[uuidStr] = await this.bot.clientCommands.grid.avatarKey2Name(rightsEntry.AgentRelated) as Friend;
this.friendsList[uuidStr].online = false;
this.friendsList[uuidStr].myRights = RightsFlags.None;
this.friendsList[uuidStr].theirRights = RightsFlags.None;
}
this.friendsList[uuidStr].theirRights = rightsEntry.RelatedRights;
}
const friendRightsEvent = new FriendRightsEvent();
friendRightsEvent.friend = this.friendsList[uuidStr];
friendRightsEvent.theirRights = this.friendsList[uuidStr].theirRights;
friendRightsEvent.myRights = this.friendsList[uuidStr].myRights;
this.bot.clientEvents.onFriendRights.next(friendRightsEvent);
}
break;
}
}
});
}
async grantFriendRights(friend: Friend | UUID | string, rights: RightsFlags)
{
let friendKey = UUID.zero();
if (friend instanceof UUID)
{
friendKey = friend;
}
else if (friend instanceof Friend)
{
friendKey = friend.getKey();
}
else if (typeof friend === 'string')
{
friendKey = new UUID(friend);
}
else
{
throw new Error('"Friend" parameter must be Friend, UUID or string');
}
const request: GrantUserRightsMessage = new GrantUserRightsMessage();
request.AgentData = {
AgentID: this.agent.agentID,
SessionID: this.circuit.sessionID
};
request.Rights = [
{
'AgentRelated': friendKey,
'RelatedRights': rights
}
];
const sequenceNo = this.circuit.sendMessage(request, PacketFlags.Reliable);
return await this.circuit.waitForAck(sequenceNo, 10000);
}
async getFriendMapLocation(friend: Friend | UUID | string): Promise<MapLocation>
{
let friendKey = UUID.zero();
if (friend instanceof UUID)
{
friendKey = friend;
}
else if (friend instanceof Friend)
{
friendKey = friend.getKey();
}
else if (typeof friend === 'string')
{
friendKey = new UUID(friend);
}
else
{
throw new Error('"Friend" parameter must be Friend, UUID or string');
}
const request: FindAgentMessage = new FindAgentMessage();
request.AgentBlock = {
'Hunter': this.agent.agentID,
'Prey': friendKey,
'SpaceIP': IPAddress.zero()
};
request.LocationBlock = [
{
GlobalX: 0.0,
GlobalY: 0.0
}
];
this.circuit.sendMessage(request, PacketFlags.Reliable);
const response: FindAgentMessage = await this.circuit.waitForMessage<FindAgentMessage>(Message.FindAgent, 10000, (filterMsg: FindAgentMessage) =>
{
if (filterMsg.AgentBlock.Hunter.equals(this.agent.agentID) && filterMsg.AgentBlock.Prey.equals(friendKey))
{
return FilterResponse.Finish;
}
return FilterResponse.NoMatch;
});
const globalPos = Utils.RegionCoordinatesToHandle(response.LocationBlock[0].GlobalX, response.LocationBlock[0].GlobalY);
const mapInfo = await this.bot.clientCommands.grid.getRegionMapInfo(globalPos.regionX, globalPos.regionY);
return {
'regionName': mapInfo.block.name,
'mapImage': mapInfo.block.mapImage,
'regionHandle': globalPos.regionHandle,
'regionX': globalPos.regionX,
'regionY': globalPos.regionY,
'localX': Math.floor(globalPos.localX),
'localY': Math.floor(globalPos.localY),
'avatars': mapInfo.avatars
};
}
async acceptFriendRequest(event: FriendRequestEvent): Promise<void>
{
const accept: AcceptFriendshipMessage = new AcceptFriendshipMessage();
accept.AgentData = {
AgentID: this.agent.agentID,
SessionID: this.circuit.sessionID
};
accept.TransactionBlock = {
TransactionID: event.requestID
};
accept.FolderData = [];
accept.FolderData.push(
{
'FolderID': this.agent.inventory.findFolderForType(AssetType.CallingCard)
}
);
const sequenceNo = this.circuit.sendMessage(accept, PacketFlags.Reliable);
return await this.circuit.waitForAck(sequenceNo, 10000);
}
async rejectFriendRequest(event: FriendRequestEvent): Promise<void>
{
const reject: DeclineFriendshipMessage = new DeclineFriendshipMessage();
reject.AgentData = {
AgentID: this.agent.agentID,
SessionID: this.circuit.sessionID
};
reject.TransactionBlock = {
TransactionID: event.requestID
};
const sequenceNo = this.circuit.sendMessage(reject, PacketFlags.Reliable);
return await this.circuit.waitForAck(sequenceNo, 10000);
}
async sendFriendRequest(to: UUID | string, message: string): Promise<void>
{
if (typeof to === 'string')
{
to = new UUID(to);
}
const requestID = UUID.random();
const agentName = this.agent.firstName + ' ' + this.agent.lastName;
const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage();
im.AgentData = {
AgentID: this.agent.agentID,
SessionID: this.circuit.sessionID
};
im.MessageBlock = {
FromGroup: false,
ToAgentID: to,
ParentEstateID: 0,
RegionID: UUID.zero(),
Position: Vector3.getZero(),
Offline: 0,
Dialog: InstantMessageDialog.FriendshipOffered,
ID: requestID,
Timestamp: Math.floor(new Date().getTime() / 1000),
FromAgentName: Utils.StringToBuffer(agentName),
Message: Utils.StringToBuffer(message),
BinaryBucket: Utils.StringToBuffer('')
};
im.EstateBlock = {
EstateID: 0
};
const sequenceNo = this.circuit.sendMessage(im, PacketFlags.Reliable);
return await this.circuit.waitForAck(sequenceNo, 10000);
}
shutdown()
{
this.friendMessages.unsubscribe();
}
}

View File

@@ -1,4 +1,3 @@
import {Packet} from '../Packet';
import * as Long from 'long';
import {MapItemReplyMessage} from '../messages/MapItemReply';
import {Message} from '../../enums/Message';
@@ -15,8 +14,11 @@ import {FilterResponse} from '../../enums/FilterResponse';
import {MapNameRequestMessage} from '../messages/MapNameRequest';
import {GridLayerType} from '../../enums/GridLayerType';
import {MapBlock} from '../MapBlock';
import {MapInfoRangeReplyEvent, MapInfoReplyEvent, PacketFlags, RegionInfoReplyEvent} from '../..';
import {Avatar, MapInfoRangeReplyEvent, MapInfoReplyEvent, PacketFlags, RegionInfoReplyEvent, Vector2} from '../..';
import {TimeoutError} from '../TimeoutError';
import {UUIDNameRequestMessage} from '../messages/UUIDNameRequest';
import {UUIDNameReplyMessage} from '../messages/UUIDNameReply';
export class GridCommands extends CommandsBase
{
getRegionByName(regionName: string)
@@ -69,7 +71,7 @@ export class GridCommands extends CommandsBase
waterHeight = region.WaterHeight;
agents = region.Agents;
mapImageID = region.MapImageID;
handle = Utils.RegionCoordinatesToHandle(region.X * 256, region.Y * 256)
handle = Utils.RegionCoordinatesToHandle(region.X * 256, region.Y * 256).regionHandle;
};
resolve(reply);
}
@@ -130,7 +132,7 @@ export class GridCommands extends CommandsBase
});
// Now get the region handle
const regionHandle: Long = Utils.RegionCoordinatesToHandle(gridX * 256, gridY * 256);
const regionHandle: Long = Utils.RegionCoordinatesToHandle(gridX * 256, gridY * 256).regionHandle;
const mi = new MapItemRequestMessage();
mi.AgentData = {
@@ -173,10 +175,10 @@ export class GridCommands extends CommandsBase
{
responseMsg2.Data.forEach((data) =>
{
response.avatars.push({
X: data.X,
Y: data.Y
});
response.avatars.push(new Vector2([
data.X,
data.Y
]));
});
resolve(response);
}).catch((err) =>
@@ -249,7 +251,7 @@ export class GridCommands extends CommandsBase
});
}
name2Key(name: string): Promise<UUID>
avatarName2Key(name: string): Promise<UUID>
{
const check = name.split('.');
if (check.length > 1)
@@ -313,4 +315,83 @@ export class GridCommands extends CommandsBase
});
});
}
avatarKey2Name(uuid: UUID | UUID[]): Promise<Avatar | Avatar[]>
{
return new Promise<Avatar | Avatar[]>(async (resolve, reject) =>
{
const req = new UUIDNameRequestMessage();
req.UUIDNameBlock = [];
let arr = true;
if (!Array.isArray(uuid))
{
arr = false;
uuid = [uuid];
}
const waitingFor: any = {};
let remaining = 0;
for (const id of uuid)
{
waitingFor[id.toString()] = null;
req.UUIDNameBlock.push({'ID': id});
remaining++;
}
this.circuit.sendMessage(req, PacketFlags.Reliable);
try
{
await this.circuit.waitForMessage<UUIDNameReplyMessage>(Message.UUIDNameReply, 10000, (reply: UUIDNameReplyMessage): FilterResponse =>
{
let found = false;
for (const name of reply.UUIDNameBlock)
{
if (waitingFor[name.ID.toString()] !== undefined)
{
found = true;
if (waitingFor[name.ID.toString()] === null)
{
waitingFor[name.ID.toString()] = {
'firstName': Utils.BufferToStringSimple(name.FirstName),
'lastName': Utils.BufferToStringSimple(name.LastName)
};
remaining--;
}
}
}
if (remaining < 1)
{
return FilterResponse.Finish;
}
else if (found)
{
return FilterResponse.Match;
}
return FilterResponse.NoMatch;
});
if (!arr)
{
const result = waitingFor[uuid[0].toString()];
const av = new Avatar(uuid[0], result.firstName, result.lastName);
resolve(av);
}
else
{
const response: Avatar[] = [];
for (const k of uuid)
{
const result = waitingFor[k.toString()];
const av = new Avatar(k, result.firstName, result.lastName);
response.push(av);
}
resolve(response);
}
}
catch (e)
{
reject(e);
}
});
}
}

View File

@@ -1,5 +1,9 @@
import {CommandsBase} from './CommandsBase';
import {InventoryFolder} from '../InventoryFolder';
import {ChatSourceType, InventoryOfferedEvent, PacketFlags, UUID, Vector3} from '../..';
import {InstantMessageDialog} from '../../enums/InstantMessageDialog';
import {ImprovedInstantMessageMessage} from '../messages/ImprovedInstantMessage';
import {Utils} from '../Utils';
export class InventoryCommands extends CommandsBase
{
@@ -11,4 +15,61 @@ export class InventoryCommands extends CommandsBase
{
return this.agent.inventory.getRootFolderLibrary();
}
}
private async respondToInventoryOffer(event: InventoryOfferedEvent, response: InstantMessageDialog): Promise<void>
{
const agentName = this.agent.firstName + ' ' + this.agent.lastName;
const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage();
const folder = this.agent.inventory.findFolderForType(event.type);
const binary = Buffer.allocUnsafe(16);
folder.writeToBuffer(binary, 0);
im.AgentData = {
AgentID: this.agent.agentID,
SessionID: this.circuit.sessionID
};
im.MessageBlock = {
FromGroup: false,
ToAgentID: event.from,
ParentEstateID: 0,
RegionID: UUID.zero(),
Position: Vector3.getZero(),
Offline: 0,
Dialog: response,
ID: event.requestID,
Timestamp: Math.floor(new Date().getTime() / 1000),
FromAgentName: Utils.StringToBuffer(agentName),
Message: Utils.StringToBuffer(''),
BinaryBucket: binary
};
im.EstateBlock = {
EstateID: 0
};
const sequenceNo = this.circuit.sendMessage(im, PacketFlags.Reliable);
return await this.circuit.waitForAck(sequenceNo, 10000);
}
async acceptInventoryOffer(event: InventoryOfferedEvent): Promise<void>
{
if (event.source === ChatSourceType.Object)
{
return await this.respondToInventoryOffer(event, InstantMessageDialog.TaskInventoryAccepted);
}
else
{
return await this.respondToInventoryOffer(event, InstantMessageDialog.InventoryAccepted);
}
}
async rejectInventoryOffer(event: InventoryOfferedEvent): Promise<void>
{
if (event.source === ChatSourceType.Object)
{
return await this.respondToInventoryOffer(event, InstantMessageDialog.TaskInventoryDeclined);
}
else
{
return await this.respondToInventoryOffer(event, InstantMessageDialog.InventoryDeclined);
}
}
}

View File

@@ -0,0 +1,25 @@
import {UUID} from '../UUID';
export class Avatar
{
constructor(private avatarKey: UUID, private firstName: string, private lastName: string)
{
}
getName(): string
{
return this.firstName + ' ' + this.lastName;
}
getFirstName(): string
{
return this.firstName;
}
getLastName(): string
{
return this.lastName;
}
getKey(): UUID
{
return this.avatarKey;
}
}

View File

@@ -0,0 +1,9 @@
import {Avatar} from './Avatar';
import {RightsFlags} from '../..';
export class Friend extends Avatar
{
online: boolean;
theirRights: RightsFlags = RightsFlags.None;
myRights: RightsFlags = RightsFlags.None;
}

View File

@@ -0,0 +1,10 @@
import * as Long from 'long';
export interface GlobalPosition
{
regionHandle: Long;
regionX: number;
regionY: number;
localX: number;
localY: number;
}

View File

@@ -0,0 +1,14 @@
import {UUID} from '../../UUID';
import * as Long from 'long';
import {Vector2} from '../../Vector2';
export interface MapLocation {
'regionName': string;
'mapImage': UUID;
'regionHandle': Long,
'regionX': number,
'regionY': number,
'localX': number,
'localY': number,
'avatars': Vector2[]
}