Ping / circuit latency, break out commands, add typing function for IM, add thinkingTime and charactersPerSecond parameters to typing functions

This commit is contained in:
Casper Warden
2017-12-13 19:55:08 +00:00
parent af71aa597e
commit 4e8feb181f
96 changed files with 2151 additions and 935 deletions

View File

@@ -150,39 +150,6 @@ export class Agent
}
}
}
private animate(anim: UUID[], run: boolean): Promise<void>
{
const circuit = this.currentRegion.circuit;
const animPacket = new AgentAnimationMessage();
animPacket.AgentData = {
AgentID: this.agentID,
SessionID: circuit.sessionID
};
animPacket.PhysicalAvatarEventList = [];
animPacket.AnimationList = [];
anim.forEach((a) =>
{
animPacket.AnimationList.push({
AnimID: a,
StartAnim: run
});
});
return circuit.waitForAck(circuit.sendMessage(animPacket, PacketFlags.Reliable), 10000);
}
startAnimations(anim: UUID[]): Promise<void>
{
return this.animate(anim, true);
}
stopAnimations(anim: UUID[]): Promise<void>
{
return this.animate(anim, false);
}
setInitialAppearance()
{
const circuit = this.currentRegion.circuit;
@@ -280,5 +247,4 @@ export class Agent
});
});
}
}

View File

@@ -1,4 +0,0 @@
export class Assets
{
}

View File

@@ -29,7 +29,8 @@ export class Circuit
awaitingAck: {
[key: number]: {
packet: Packet,
timeout: number
timeout: number,
sent: number
}
} = {};
receivedPackets: {
@@ -216,7 +217,8 @@ export class Circuit
this.awaitingAck[packet.sequenceNumber] =
{
packet: packet,
timeout: setTimeout(this.resend.bind(this, packet.sequenceNumber), 1000)
timeout: setTimeout(this.resend.bind(this, packet.sequenceNumber), 1000),
sent: new Date().getTime()
};
}
let dataToSend: Buffer = Buffer.allocUnsafe(packet.getSize());
@@ -261,6 +263,26 @@ export class Circuit
this.sendMessage(msg, 0);
}
getOldestUnacked(): number
{
let result = 0;
let oldest = -1;
const keys: string[] = Object.keys(this.awaitingAck);
keys.forEach((seqID: string) =>
{
const nSeq = parseInt(seqID, 10);
if (oldest === -1 || this.awaitingAck[nSeq].sent < oldest)
{
result = nSeq;
oldest = this.awaitingAck[nSeq].sent;
}
});
return result;
}
expireReceivedPacket(sequenceNumber: number)
{
// Enough time has elapsed that we can forget about this packet

View File

@@ -0,0 +1,35 @@
import {Region} from './Region';
import {Agent} from './Agent';
import {Bot} from '../Bot';
import {NetworkCommands} from './commands/NetworkCommands';
import {AssetCommands} from './commands/AssetCommands';
import {TeleportCommands} from './commands/TeleportCommands';
import {RegionCommands} from './commands/RegionCommands';
import {GridCommands} from './commands/GridCommands';
import {CommunicationsCommands} from './commands/CommunicationsCommands';
import {AgentCommands} from './commands/AgentCommands';
import {GroupCommands} from './commands/GroupCommands';
export class ClientCommands
{
public network: NetworkCommands;
public asset: AssetCommands;
public teleport: TeleportCommands;
public region: RegionCommands;
public grid: GridCommands;
public comms: CommunicationsCommands;
public agent: AgentCommands;
public group: GroupCommands;
constructor(region: Region, agent: Agent, bot: Bot)
{
this.network = new NetworkCommands(region, agent, bot);
this.asset = new AssetCommands(region, agent, bot);
this.teleport = new TeleportCommands(region, agent, bot);
this.region = new RegionCommands(region, agent, bot);
this.grid = new GridCommands(region, agent, bot);
this.comms = new CommunicationsCommands(region, agent, bot);
this.agent = new AgentCommands(region, agent, bot);
this.group = new GroupCommands(region, agent, bot);
}
}

View File

@@ -2,10 +2,19 @@ import {LureEvent} from '../events/LureEvent';
import {ChatEvent} from '../events/ChatEvent';
import {TeleportEvent} from '../events/TeleportEvent';
import {Subject} from 'rxjs/Subject';
import {InstantMessageEvent} from '../events/InstantMessageEvent';
import {GroupInviteEvent} from '../events/GroupInviteEvent';
import {FriendRequestEvent} from '../events/FriendRequestEvent';
import {DisconnectEvent} from '../events/DisconnectEvent';
export class ClientEvents
{
onNearbyChat: Subject<ChatEvent> = new Subject<ChatEvent>();
onInstantMessage: Subject<InstantMessageEvent> = new Subject<InstantMessageEvent>();
onGroupInvite: Subject<GroupInviteEvent> = new Subject<GroupInviteEvent>();
onFriendRequest: Subject<FriendRequestEvent> = new Subject<FriendRequestEvent>();
onLure: Subject<LureEvent> = new Subject<LureEvent>();
onTeleportEvent: Subject<TeleportEvent> = new Subject<TeleportEvent>();
onDisconnected: Subject<DisconnectEvent> = new Subject<DisconnectEvent>();
onCircuitLatency: Subject<number> = new Subject<number>();
}

View File

@@ -4,17 +4,15 @@ import {Packet} from './Packet';
import {Message} from '../enums/Message';
import {ChatFromSimulatorMessage} from './messages/ChatFromSimulator';
import {ImprovedInstantMessageMessage} from './messages/ImprovedInstantMessage';
import {ChatType} from '../enums/ChatType';
import {Utils} from './Utils';
import {ChatFromViewerMessage} from './messages/ChatFromViewer';
import {PacketFlags} from '../enums/PacketFlags';
import {ChatEvent} from '../events/ChatEvent';
import {UUID} from './UUID';
import {InstantMessageDialog} from '../enums/InstantMessageDialog';
import {LureEvent} from '../events/LureEvent';
import {AlertMessageMessage} from './messages/AlertMessage';
import {ClientEvents} from './ClientEvents';
import {Vector3} from './Vector3';
import {InstantMessageEvent} from '../events/InstantMessageEvent';
import {ChatSourceType} from '../enums/ChatSourceType';
import {InstantMessageEventFlags} from '../enums/InstantMessageEventFlags';
export class Comms
{
@@ -29,10 +27,10 @@ export class Comms
this.agent = agent;
this.circuit.subscribeToMessages([
Message.ImprovedInstantMessage,
Message.ChatFromSimulator,
Message.AlertMessage
], (packet: Packet) =>
Message.ImprovedInstantMessage,
Message.ChatFromSimulator,
Message.AlertMessage
], (packet: Packet) =>
{
switch (packet.message.id)
{
@@ -41,7 +39,18 @@ export class Comms
switch (im.MessageBlock.Dialog)
{
case InstantMessageDialog.MessageFromAgent:
{
console.log(im);
const imEvent = new InstantMessageEvent();
imEvent.source = ChatSourceType.Agent;
imEvent.from = im.AgentData.AgentID;
imEvent.owner = im.AgentData.AgentID;
imEvent.fromName = Utils.BufferToStringSimple(im.MessageBlock.FromAgentName);
imEvent.message = Utils.BufferToStringSimple(im.MessageBlock.Message);
imEvent.flags = InstantMessageEventFlags.normal;
this.clientEvents.onInstantMessage.next(imEvent);
break;
}
case InstantMessageDialog.MessageBox:
break;
case InstantMessageDialog.GroupInvitation:
@@ -59,15 +68,37 @@ export class Comms
case InstantMessageDialog.TaskInventoryDeclined:
break;
case InstantMessageDialog.MessageFromObject:
{
console.log(im);
const imEvent = new InstantMessageEvent();
imEvent.source = ChatSourceType.Object;
imEvent.owner = im.AgentData.AgentID;
imEvent.from = im.MessageBlock.ID;
imEvent.fromName = Utils.BufferToStringSimple(im.MessageBlock.FromAgentName);
imEvent.message = Utils.BufferToStringSimple(im.MessageBlock.Message);
imEvent.flags = InstantMessageEventFlags.normal;
this.clientEvents.onInstantMessage.next(imEvent);
break;
}
case InstantMessageDialog.BusyAutoResponse:
{
const imEvent = new InstantMessageEvent();
imEvent.source = ChatSourceType.Agent;
imEvent.from = im.AgentData.AgentID;
imEvent.owner = im.AgentData.AgentID;
imEvent.fromName = Utils.BufferToStringSimple(im.MessageBlock.FromAgentName);
imEvent.message = Utils.BufferToStringSimple(im.MessageBlock.Message);
imEvent.flags = InstantMessageEventFlags.busyResponse;
this.clientEvents.onInstantMessage.next(imEvent);
break;
}
case InstantMessageDialog.ConsoleAndChatHistory:
break;
case InstantMessageDialog.RequestTeleport:
const lureEvent = new LureEvent();
const extraData = Utils.BufferToStringSimple(im.MessageBlock.BinaryBucket).split('|');
lureEvent.fromName = Utils.BufferToStringSimple(im.MessageBlock.FromAgentName);
lureEvent.from = im.AgentData.AgentID;
lureEvent.fromName = Utils.BufferToStringSimple(im.MessageBlock.FromAgentName);
lureEvent.lureMessage = Utils.BufferToStringSimple(im.MessageBlock.Message);
lureEvent.regionID = im.MessageBlock.RegionID;
lureEvent.position = im.MessageBlock.Position;
@@ -105,9 +136,29 @@ export class Comms
case InstantMessageDialog.FriendshipDeclined:
break;
case InstantMessageDialog.StartTyping:
{
const imEvent = new InstantMessageEvent();
imEvent.source = ChatSourceType.Agent;
imEvent.from = im.AgentData.AgentID;
imEvent.owner = im.AgentData.AgentID;
imEvent.fromName = Utils.BufferToStringSimple(im.MessageBlock.FromAgentName);
imEvent.message = '';
imEvent.flags = InstantMessageEventFlags.startTyping;
this.clientEvents.onInstantMessage.next(imEvent);
break;
}
case InstantMessageDialog.StopTyping:
{
const imEvent = new InstantMessageEvent();
imEvent.source = ChatSourceType.Agent;
imEvent.from = im.AgentData.AgentID;
imEvent.owner = im.AgentData.AgentID;
imEvent.fromName = Utils.BufferToStringSimple(im.MessageBlock.FromAgentName);
imEvent.message = '';
imEvent.flags = InstantMessageEventFlags.finishTyping;
this.clientEvents.onInstantMessage.next(imEvent);
break;
}
}
break;
@@ -130,128 +181,21 @@ export class Comms
case Message.AlertMessage:
const alertm = packet.message as AlertMessageMessage;
let alertMessage = Utils.BufferToStringSimple(alertm.AlertData.Message);
const alertMessage = Utils.BufferToStringSimple(alertm.AlertData.Message);
console.log('Alert message: ' + alertMessage);
alertm.AlertInfo.forEach((info) => {
let alertInfoMessage = Utils.BufferToStringSimple(info.Message);
alertm.AlertInfo.forEach((info) =>
{
const alertInfoMessage = Utils.BufferToStringSimple(info.Message);
console.log('Alert info message: ' + alertInfoMessage);
});
break;
}
});
}
nearbyChat(message: string, type: ChatType, channel?: number)
{
if (channel === undefined)
{
channel = 0;
}
const cfv = new ChatFromViewerMessage();
cfv.AgentData = {
AgentID: this.agent.agentID,
SessionID: this.circuit.sessionID
};
cfv.ChatData = {
Message: Utils.StringToBuffer(message),
Type: type,
Channel: channel
};
this.circuit.sendMessage(cfv, PacketFlags.Reliable);
}
say(message: string, channel?: number)
{
this.nearbyChat(message, ChatType.Normal, channel);
}
whisper(message: string, channel?: number)
{
this.nearbyChat(message, ChatType.Whisper, channel);
}
shout(message: string, channel?: number)
{
this.nearbyChat(message, ChatType.Shout, channel);
}
startTypingLocal()
{
const cfv = new ChatFromViewerMessage();
cfv.AgentData = {
AgentID: this.agent.agentID,
SessionID: this.circuit.sessionID
};
cfv.ChatData = {
Message: Buffer.allocUnsafe(0),
Type: ChatType.StartTyping,
Channel: 0
};
this.circuit.sendMessage(cfv, PacketFlags.Reliable);
}
stopTypingLocal()
{
const cfv = new ChatFromViewerMessage();
cfv.AgentData = {
AgentID: this.agent.agentID,
SessionID: this.circuit.sessionID
};
cfv.ChatData = {
Message: Buffer.allocUnsafe(0),
Type: ChatType.StopTyping,
Channel: 0
};
this.circuit.sendMessage(cfv, PacketFlags.Reliable);
}
typeMessage(message: string)
{
this.startTypingLocal();
this.agent.startAnimations([new UUID('c541c47f-e0c0-058b-ad1a-d6ae3a4584d9')]).then(() =>
{
// Average four characters per second i guess?
const timeToWait = (message.length / 5) * 1000;
setTimeout(() =>
{
this.stopTypingLocal();
this.agent.stopAnimations([new UUID('c541c47f-e0c0-058b-ad1a-d6ae3a4584d9')]).then(() =>
{
this.say(message);
});
}, timeToWait);
});
}
shutdown()
{
}
sendInstantMessage(to: UUID | string, message: string): Promise<void>
{
const circuit = this.circuit;
if (typeof to === 'string')
{
to = new UUID(to);
}
message += '\0';
const agentName = this.agent.firstName + ' ' + this.agent.lastName;
const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage();
im.AgentData = {
AgentID: this.agent.agentID,
SessionID: circuit.sessionID
};
im.MessageBlock = {
FromGroup: false,
ToAgentID: to,
ParentEstateID: 0,
RegionID: UUID.zero(),
Position: Vector3.getZero(),
Offline: 0,
Dialog: 0,
ID: UUID.zero(),
Timestamp: 0,
FromAgentName: Utils.StringToBuffer(agentName),
Message: Utils.StringToBuffer(message),
BinaryBucket: Buffer.allocUnsafe(0)
};
im.EstateBlock = {
EstateID: 0
};
const sequenceNo = circuit.sendMessage(im, PacketFlags.Reliable);
return circuit.waitForAck(sequenceNo, 10000);
}
}
}

View File

@@ -10,25 +10,39 @@ export class IPAddress
}
public toString = (): string =>
{
return this.ip.toString();
try
{
return this.ip.toString();
}
catch (ignore)
{
return '';
}
};
constructor(buf?: Buffer | string, pos?: number)
{
if (buf !== undefined && buf instanceof Buffer)
try
{
if (pos !== undefined)
if (buf !== undefined && buf instanceof Buffer)
{
const bytes = buf.slice(pos, 4);
this.ip = ipaddr.fromByteArray(bytes);
}
else
{
if (ipaddr.isValid(buf))
if (pos !== undefined)
{
this.ip = ipaddr.parse(buf);
const bytes = buf.slice(pos, 4);
this.ip = ipaddr.fromByteArray(bytes);
}
else
{
if (ipaddr.isValid(buf))
{
this.ip = ipaddr.parse(buf);
}
}
}
}
catch (ignore)
{
this.ip = ipaddr.parse('0.0.0.0');
}
}
writeToBuffer(buf: Buffer, pos: number)
{

View File

@@ -1,5 +1,5 @@
import * as Long from 'long';
import {HTTPAssets} from "../enums/HTTPAssets";
import {HTTPAssets} from '../enums/HTTPAssets';
export class Utils
{

View File

@@ -0,0 +1,39 @@
import {UUID} from '../UUID';
import {AgentAnimationMessage} from '../messages/AgentAnimation';
import {PacketFlags} from '../../enums/PacketFlags';
import {CommandsBase} from './CommandsBase';
export class AgentCommands extends CommandsBase
{
private animate(anim: UUID[], run: boolean): Promise<void>
{
const circuit = this.currentRegion.circuit;
const animPacket = new AgentAnimationMessage();
animPacket.AgentData = {
AgentID: this.agent.agentID,
SessionID: circuit.sessionID
};
animPacket.PhysicalAvatarEventList = [];
animPacket.AnimationList = [];
anim.forEach((a) =>
{
animPacket.AnimationList.push({
AnimID: a,
StartAnim: run
});
});
return circuit.waitForAck(circuit.sendMessage(animPacket, PacketFlags.Reliable), 10000);
}
startAnimations(anim: UUID[]): Promise<void>
{
return this.animate(anim, true);
}
stopAnimations(anim: UUID[]): Promise<void>
{
return this.animate(anim, false);
}
}

View File

@@ -0,0 +1,50 @@
import {CommandsBase} from './CommandsBase';
import {HTTPAssets} from '../../enums/HTTPAssets';
import {UUID} from '../UUID';
import * as LLSD from 'llsd';
import {Utils} from '../Utils';
export class AssetCommands extends CommandsBase
{
downloadAsset(type: HTTPAssets, uuid: UUID)
{
return this.currentRegion.caps.downloadAsset(uuid, type);
}
uploadAsset(type: HTTPAssets, data: Buffer, name: string, description: string): Promise<UUID>
{
return new Promise<UUID>((resolve, reject) =>
{
if (this.agent && this.agent.inventory && this.agent.inventory.main && this.agent.inventory.main.root)
{
this.currentRegion.caps.capsRequestXML('NewFileAgentInventory', {
'folder_id': new LLSD.UUID(this.agent.inventory.main.root.toString()),
'asset_type': type,
'inventory_type': Utils.HTTPAssetTypeToInventoryType(type),
'name': name,
'description': description,
'everyone_mask': (1 << 13) | (1 << 14) | (1 << 15) | (1 << 19),
'group_mask': (1 << 13) | (1 << 14) | (1 << 15) | (1 << 19),
'next_owner_mask': (1 << 13) | (1 << 14) | (1 << 15) | (1 << 19),
'expected_upload_cost': 0
}).then((response: any) =>
{
if (response['state'] === 'upload')
{
const uploadURL = response['uploader'];
this.currentRegion.caps.capsRequestUpload(uploadURL, data).then((responseUpload: any) =>
{
resolve(new UUID(responseUpload['new_asset'].toString()));
}).catch((err) =>
{
reject(err);
});
}
}).catch((err) =>
{
console.log(err);
})
}
});
}
}

View File

@@ -0,0 +1,20 @@
import {Region} from '../Region';
import {Bot} from '../../Bot';
import {Agent} from '../Agent';
import {Circuit} from '../Circuit';
export class CommandsBase
{
protected currentRegion: Region;
protected agent: Agent;
protected bot: Bot;
protected circuit: Circuit;
constructor(region: Region, agent: Agent, bot: Bot)
{
this.currentRegion = region;
this.agent = agent;
this.bot = bot;
this.circuit = this.currentRegion.circuit;
}
}

View File

@@ -0,0 +1,296 @@
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;
export class CommunicationsCommands extends CommandsBase
{
sendInstantMessage(to: UUID | string, message: string): Promise<void>
{
const circuit = this.circuit;
if (typeof to === 'string')
{
to = new UUID(to);
}
const agentName = this.agent.firstName + ' ' + this.agent.lastName;
const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage();
im.AgentData = {
AgentID: this.agent.agentID,
SessionID: circuit.sessionID
};
im.MessageBlock = {
FromGroup: false,
ToAgentID: to,
ParentEstateID: 0,
RegionID: UUID.zero(),
Position: Vector3.getZero(),
Offline: 1,
Dialog: 0,
ID: UUID.zero(),
Timestamp: 0,
FromAgentName: Utils.StringToBuffer(agentName),
Message: Utils.StringToBuffer(message),
BinaryBucket: Buffer.allocUnsafe(0)
};
im.EstateBlock = {
EstateID: 0
};
const sequenceNo = circuit.sendMessage(im, PacketFlags.Reliable);
return circuit.waitForAck(sequenceNo, 10000);
}
nearbyChat(message: string, type: ChatType, channel?: number): Promise<void>
{
if (channel === undefined)
{
channel = 0;
}
const cfv = new ChatFromViewerMessage();
cfv.AgentData = {
AgentID: this.agent.agentID,
SessionID: this.circuit.sessionID
};
cfv.ChatData = {
Message: Utils.StringToBuffer(message),
Type: type,
Channel: channel
};
const sequenceNo = this.circuit.sendMessage(cfv, PacketFlags.Reliable);
return this.circuit.waitForAck(sequenceNo, 10000);
}
say(message: string, channel?: number): Promise<void>
{
return this.nearbyChat(message, ChatType.Normal, channel);
}
whisper(message: string, channel?: number): Promise<void>
{
return this.nearbyChat(message, ChatType.Whisper, channel);
}
shout(message: string, channel?: number): Promise<void>
{
return this.nearbyChat(message, ChatType.Shout, channel);
}
startTypingLocal(): Promise<void>
{
const cfv = new ChatFromViewerMessage();
cfv.AgentData = {
AgentID: this.agent.agentID,
SessionID: this.circuit.sessionID
};
cfv.ChatData = {
Message: Buffer.allocUnsafe(0),
Type: ChatType.StartTyping,
Channel: 0
};
const sequenceNo = this.circuit.sendMessage(cfv, PacketFlags.Reliable);
return this.circuit.waitForAck(sequenceNo, 10000);
}
stopTypingLocal(): Promise<void>
{
const cfv = new ChatFromViewerMessage();
cfv.AgentData = {
AgentID: this.agent.agentID,
SessionID: this.circuit.sessionID
};
cfv.ChatData = {
Message: Buffer.allocUnsafe(0),
Type: ChatType.StopTyping,
Channel: 0
};
const sequenceNo = this.circuit.sendMessage(cfv, PacketFlags.Reliable);
return this.circuit.waitForAck(sequenceNo, 10000);
}
startTypingIM(to: UUID | string): Promise<void>
{
if (typeof to === 'string')
{
to = new UUID(to);
}
const circuit = this.circuit;
const agentName = this.agent.firstName + ' ' + this.agent.lastName;
const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage();
im.AgentData = {
AgentID: this.agent.agentID,
SessionID: circuit.sessionID
};
im.MessageBlock = {
FromGroup: false,
ToAgentID: to,
ParentEstateID: 0,
RegionID: UUID.zero(),
Position: Vector3.getZero(),
Offline: 0,
Dialog: InstantMessageDialog.StartTyping,
ID: UUID.zero(),
Timestamp: 0,
FromAgentName: Utils.StringToBuffer(agentName),
Message: Utils.StringToBuffer(''),
BinaryBucket: Buffer.allocUnsafe(0)
};
im.EstateBlock = {
EstateID: 0
};
const sequenceNo = circuit.sendMessage(im, PacketFlags.Reliable);
return circuit.waitForAck(sequenceNo, 10000);
}
stopTypingIM(to: UUID | string): Promise<void>
{
if (typeof to === 'string')
{
to = new UUID(to);
}
const circuit = this.circuit;
const agentName = this.agent.firstName + ' ' + this.agent.lastName;
const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage();
im.AgentData = {
AgentID: this.agent.agentID,
SessionID: circuit.sessionID
};
im.MessageBlock = {
FromGroup: false,
ToAgentID: to,
ParentEstateID: 0,
RegionID: UUID.zero(),
Position: Vector3.getZero(),
Offline: 0,
Dialog: InstantMessageDialog.StopTyping,
ID: UUID.zero(),
Timestamp: 0,
FromAgentName: Utils.StringToBuffer(agentName),
Message: Utils.StringToBuffer(''),
BinaryBucket: Buffer.allocUnsafe(0)
};
im.EstateBlock = {
EstateID: 0
};
const sequenceNo = circuit.sendMessage(im, PacketFlags.Reliable);
return circuit.waitForAck(sequenceNo, 10000);
}
typeInstantMessage(to: UUID | string, message: string, thinkingTime?: number, charactersPerSecond?: number): Promise<void>
{
return new Promise<void>((resolve, reject) =>
{
if (thinkingTime === undefined)
{
thinkingTime = 2000;
}
setTimeout(() =>
{
if (typeof to === 'string')
{
to = new UUID(to);
}
let typeTimer: Timer | null = null;
this.startTypingIM(to).then(() =>
{
typeTimer = setInterval(() =>
{
this.startTypingIM(to).catch(() =>
{
// ignore
});
}, 5000);
if (charactersPerSecond === undefined)
{
charactersPerSecond = 5;
}
const timeToWait = (message.length / charactersPerSecond) * 1000;
setTimeout(() =>
{
if (typeTimer !== null)
{
clearInterval(typeTimer);
typeTimer = null;
}
this.stopTypingIM(to).then(() =>
{
this.sendInstantMessage(to, message).then(() =>
{
resolve();
}).catch((err) =>
{
reject(err);
});
}).catch((err) =>
{
reject(err);
});
}, timeToWait);
}).catch((err) =>
{
if (typeTimer !== null)
{
clearInterval(typeTimer);
typeTimer = null;
}
reject(err);
});
}, thinkingTime);
});
}
typeLocalMessage(message: string, thinkingTime?: number, charactersPerSecond?: number): Promise<void>
{
return new Promise<void>((resolve, reject) =>
{
if (thinkingTime === undefined)
{
thinkingTime = 0;
}
setTimeout(() =>
{
this.startTypingLocal().then(() =>
{
this.bot.clientCommands.agent.startAnimations([new UUID('c541c47f-e0c0-058b-ad1a-d6ae3a4584d9')]).then(() =>
{
if (charactersPerSecond === undefined)
{
charactersPerSecond = 5;
}
const timeToWait = (message.length / charactersPerSecond) * 1000;
setTimeout(() =>
{
this.stopTypingLocal().then(() =>
{
this.bot.clientCommands.agent.stopAnimations([new UUID('c541c47f-e0c0-058b-ad1a-d6ae3a4584d9')]).then(() =>
{
this.say(message).then(() =>
{
resolve();
}).catch((err) =>
{
reject(err);
});
}).catch((err) => {
reject(err);
});
}).catch((err) => {
reject(err);
});
}, timeToWait);
}).catch((err) => {
reject(err);
});
}).catch((err) => {
reject(err);
});
}, thinkingTime);
});
}
}

View File

@@ -0,0 +1,141 @@
import {MapInfoReply} from '../../events/MapInfoReply';
import {Packet} from '../Packet';
import * as Long from 'long';
import {RegionHandleRequestMessage} from '../messages/RegionHandleRequest';
import {MapItemReplyMessage} from '../messages/MapItemReply';
import {Message} from '../../enums/Message';
import {MapBlockReplyMessage} from '../messages/MapBlockReply';
import {MapBlockRequestMessage} from '../messages/MapBlockRequest';
import {UUID} from '../UUID';
import {MapItemRequestMessage} from '../messages/MapItemRequest';
import {Utils} from '../Utils';
import {PacketFlags} from '../../enums/PacketFlags';
import {GridItemType} from '../../enums/GridItemType';
import {RegionIDAndHandleReplyMessage} from '../messages/RegionIDAndHandleReply';
import {CommandsBase} from './CommandsBase';
export class GridCommands extends CommandsBase
{
getRegionHandle(regionID: UUID): Promise<Long>
{
return new Promise<Long>((resolve, reject) =>
{
const circuit = this.currentRegion.circuit;
const msg: RegionHandleRequestMessage = new RegionHandleRequestMessage();
msg.RequestBlock = {
RegionID: regionID,
};
circuit.sendMessage(msg, PacketFlags.Reliable);
circuit.waitForMessage(Message.RegionIDAndHandleReply, 10000, (packet: Packet) =>
{
const filterMsg = packet.message as RegionIDAndHandleReplyMessage;
return (filterMsg.ReplyBlock.RegionID.toString() === regionID.toString());
}).then((packet: Packet) =>
{
const responseMsg = packet.message as RegionIDAndHandleReplyMessage;
resolve(responseMsg.ReplyBlock.RegionHandle);
});
});
}
getRegionMapInfo(gridX: number, gridY: number): Promise<MapInfoReply>
{
return new Promise<MapInfoReply>((resolve, reject) =>
{
const circuit = this.currentRegion.circuit;
const response = new MapInfoReply();
const msg: MapBlockRequestMessage = new MapBlockRequestMessage();
msg.AgentData = {
AgentID: this.agent.agentID,
SessionID: circuit.sessionID,
Flags: 65536,
EstateID: 0,
Godlike: true
};
msg.PositionData = {
MinX: (gridX / 256),
MaxX: (gridX / 256),
MinY: (gridY / 256),
MaxY: (gridY / 256)
};
circuit.sendMessage(msg, PacketFlags.Reliable);
circuit.waitForMessage(Message.MapBlockReply, 10000, (packet: Packet) =>
{
const filterMsg = packet.message as MapBlockReplyMessage;
let found = false;
filterMsg.Data.forEach((data) =>
{
if (data.X === (gridX / 256) && data.Y === (gridY / 256))
{
found = true;
}
});
return found;
}).then((packet: Packet) =>
{
const responseMsg = packet.message as MapBlockReplyMessage;
responseMsg.Data.forEach((data) =>
{
if (data.X === (gridX / 256) && data.Y === (gridY / 256))
{
response.name = Utils.BufferToStringSimple(data.Name);
response.accessFlags = data.Access;
response.mapImage = data.MapImageID;
}
});
// Now get the region handle
const regionHandle: Long = Utils.RegionCoordinatesToHandle(gridX, gridY);
const mi = new MapItemRequestMessage();
mi.AgentData = {
AgentID: this.agent.agentID,
SessionID: circuit.sessionID,
Flags: 2,
EstateID: 0,
Godlike: false
};
mi.RequestData = {
ItemType: GridItemType.AgentLocations,
RegionHandle: regionHandle
};
circuit.sendMessage(mi, PacketFlags.Reliable);
const minX = Math.floor(gridX / 256) * 256;
const maxX = minX + 256;
const minY = Math.floor(gridY / 256) * 256;
const maxY = minY + 256;
response.avatars = [];
circuit.waitForMessage(Message.MapItemReply, 10000, (packet: Packet) =>
{
const filterMsg = packet.message as MapItemReplyMessage;
let found = false;
filterMsg.Data.forEach((data) =>
{
// Check if avatar is within our bounds
if (data.X >= minX && data.X <= maxX && data.Y >= minY && data.Y <= maxY)
{
found = true;
}
});
return found;
}).then((packet2: Packet) =>
{
const responseMsg2 = packet2.message as MapItemReplyMessage;
responseMsg2.Data.forEach((data) =>
{
response.avatars.push({
X: data.X,
Y: data.Y
});
});
resolve(response);
}).catch((err) =>
{
reject(err);
});
}).catch((err) =>
{
reject(err);
});
});
}
}

View File

@@ -0,0 +1,44 @@
import {CommandsBase} from './CommandsBase';
import {UUID} from '../UUID';
import {InstantMessageDialog} from '../../enums/InstantMessageDialog';
import {Utils} from '../Utils';
import {PacketFlags} from '../../enums/PacketFlags';
import {ImprovedInstantMessageMessage} from '../messages/ImprovedInstantMessage';
import {Vector3} from '../Vector3';
export class GroupCommands extends CommandsBase
{
sendGroupNotice(group: UUID | string, subject: string, message: string)
{
if (typeof group === 'string')
{
group = new UUID(group);
}
const circuit = this.circuit;
const agentName = this.agent.firstName + ' ' + this.agent.lastName;
const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage();
im.AgentData = {
AgentID: this.agent.agentID,
SessionID: circuit.sessionID
};
im.MessageBlock = {
FromGroup: false,
ToAgentID: group,
ParentEstateID: 0,
RegionID: UUID.zero(),
Position: Vector3.getZero(),
Offline: 0,
Dialog: InstantMessageDialog.GroupNotice,
ID: UUID.zero(),
Timestamp: 0,
FromAgentName: Utils.StringToBuffer(agentName),
Message: Utils.StringToBuffer(subject + '|' + message),
BinaryBucket: Buffer.allocUnsafe(0)
};
im.EstateBlock = {
EstateID: 0
};
const sequenceNo = circuit.sendMessage(im, PacketFlags.Reliable);
return circuit.waitForAck(sequenceNo, 10000);
}
}

View File

@@ -0,0 +1,50 @@
import {CommandsBase} from './CommandsBase';
import {PacketFlags} from '../../enums/PacketFlags';
import {AgentThrottleMessage} from '../messages/AgentThrottle';
export class NetworkCommands extends CommandsBase
{
private throttleGenCounter = 0;
setBandwidth(total: number)
{
const agentThrottle: AgentThrottleMessage = new AgentThrottleMessage();
agentThrottle.AgentData = {
AgentID: this.agent.agentID,
SessionID: this.circuit.sessionID,
CircuitCode: this.circuit.circuitCode
};
const throttleData = Buffer.allocUnsafe(28);
let pos = 0;
const resendThrottle = total * 0.1;
const landThrottle = total * 0.172;
const windThrottle = total * 0.05;
const cloudThrottle = total * 0.05;
const taskThrottle = total * 0.234;
const textureThrottle = total * 0.234;
const assetThrottle = total * 0.160;
throttleData.writeFloatLE(resendThrottle, pos);
pos += 4;
throttleData.writeFloatLE(landThrottle, pos);
pos += 4;
throttleData.writeFloatLE(windThrottle, pos);
pos += 4;
throttleData.writeFloatLE(cloudThrottle, pos);
pos += 4;
throttleData.writeFloatLE(taskThrottle, pos);
pos += 4;
throttleData.writeFloatLE(textureThrottle, pos);
pos += 4;
throttleData.writeFloatLE(assetThrottle, pos);
agentThrottle.Throttle = {
GenCounter: this.throttleGenCounter++,
Throttles: throttleData
};
this.circuit.sendMessage(agentThrottle, PacketFlags.Reliable);
}
}

View File

@@ -0,0 +1,6 @@
import {CommandsBase} from './CommandsBase';
export class RegionCommands extends CommandsBase
{
}

View File

@@ -0,0 +1,82 @@
import {CommandsBase} from './CommandsBase';
import {Region} from '../Region';
import {LureEvent} from '../../events/LureEvent';
import {TeleportEventType} from '../../enums/TeleportEventType';
import {TeleportEvent} from '../../events/TeleportEvent';
import {PacketFlags} from '../../enums/PacketFlags';
import {TeleportLureRequestMessage} from '../messages/TeleportLureRequest';
import {TeleportFlags} from '../../enums/TeleportFlags';
export class TeleportCommands extends CommandsBase
{
acceptTeleport(lure: LureEvent): Promise<TeleportEvent>
{
return new Promise<TeleportEvent>((resolve, reject) =>
{
const circuit = this.currentRegion.circuit;
const tlr = new TeleportLureRequestMessage();
tlr.Info = {
AgentID: this.agent.agentID,
SessionID: circuit.sessionID,
LureID: lure.lureID,
TeleportFlags: TeleportFlags.ViaLure
};
circuit.sendMessage(tlr, PacketFlags.Reliable);
if (this.currentRegion.caps.eventQueueClient)
{
if (this.bot.clientEvents === null)
{
reject(new Error('ClientEvents is null'));
return;
}
const subscription = this.bot.clientEvents.onTeleportEvent.subscribe((e: TeleportEvent) =>
{
if (e.eventType === TeleportEventType.TeleportFailed || e.eventType === TeleportEventType.TeleportCompleted)
{
subscription.unsubscribe();
}
if (e.eventType === TeleportEventType.TeleportFailed)
{
reject(e);
}
else if (e.eventType === TeleportEventType.TeleportCompleted)
{
if (e.simIP === 'local')
{
// Local TP - no need for any other shindiggery
resolve(e);
return;
}
if (this.bot.clientEvents === null)
{
reject(new Error('ClientEvents is null'));
return;
}
// Successful teleport! First, rip apart circuit
this.currentRegion.shutdown();
const region: Region = new Region(this.agent, this.bot.clientEvents);
region.circuit.circuitCode = this.currentRegion.circuit.circuitCode;
region.circuit.secureSessionID = this.currentRegion.circuit.secureSessionID;
region.circuit.sessionID = this.currentRegion.circuit.sessionID;
region.circuit.udpBlacklist = this.currentRegion.circuit.udpBlacklist;
region.circuit.ipAddress = e.simIP;
region.circuit.port = e.simPort;
this.agent.setCurrentRegion(region);
this.currentRegion = region;
this.currentRegion.activateCaps(e.seedCapability);
this.bot.changeRegion(this.currentRegion).then(() =>
{
resolve(e);
}).catch((error) =>
{
reject(error);
});
}
});
}
});
}
}