[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:
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
53
lib/classes/Vector2.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) =>
|
||||
|
||||
311
lib/classes/commands/FriendCommands.ts
Normal file
311
lib/classes/commands/FriendCommands.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
25
lib/classes/public/Avatar.ts
Normal file
25
lib/classes/public/Avatar.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
9
lib/classes/public/Friend.ts
Normal file
9
lib/classes/public/Friend.ts
Normal 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;
|
||||
}
|
||||
10
lib/classes/public/interfaces/GlobalPosition.ts
Normal file
10
lib/classes/public/interfaces/GlobalPosition.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import * as Long from 'long';
|
||||
|
||||
export interface GlobalPosition
|
||||
{
|
||||
regionHandle: Long;
|
||||
regionX: number;
|
||||
regionY: number;
|
||||
localX: number;
|
||||
localY: number;
|
||||
}
|
||||
14
lib/classes/public/interfaces/MapLocation.ts
Normal file
14
lib/classes/public/interfaces/MapLocation.ts
Normal 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[]
|
||||
}
|
||||
Reference in New Issue
Block a user