Teleport and EventQueue reliability improvements. Start plumbing for stayPut()
This commit is contained in:
@@ -79,6 +79,7 @@ export class Agent
|
||||
serialNumber: number
|
||||
};
|
||||
agentUpdateTimer: number | null = null;
|
||||
estateManager = false;
|
||||
private clientEvents: ClientEvents;
|
||||
|
||||
constructor(clientEvents: ClientEvents)
|
||||
@@ -109,6 +110,11 @@ export class Agent
|
||||
});
|
||||
}
|
||||
|
||||
setIsEstateManager(is: boolean): void
|
||||
{
|
||||
this.estateManager = is;
|
||||
}
|
||||
|
||||
getSessionAgentCount(uuid: UUID): number
|
||||
{
|
||||
const str = uuid.toString();
|
||||
|
||||
@@ -17,6 +17,7 @@ export class Caps
|
||||
private capabilities: { [key: string]: string } = {};
|
||||
private clientEvents: ClientEvents;
|
||||
private agent: Agent;
|
||||
private active = false;
|
||||
eventQueueClient: EventQueueClient | null = null;
|
||||
|
||||
constructor(agent: Agent, region: Region, seedURL: string, clientEvents: ClientEvents)
|
||||
@@ -113,7 +114,7 @@ export class Caps
|
||||
req.push('ViewerMetrics');
|
||||
req.push('ViewerStartAuction');
|
||||
req.push('ViewerStats');
|
||||
|
||||
this.active = true;
|
||||
this.request(seedURL, LLSD.LLSD.formatXML(req), 'application/llsd+xml').then((body: string) =>
|
||||
{
|
||||
this.capabilities = LLSD.LLSD.parseXML(body);
|
||||
@@ -211,6 +212,11 @@ export class Caps
|
||||
{
|
||||
return new Promise<string>((resolve, reject) =>
|
||||
{
|
||||
if (!this.active)
|
||||
{
|
||||
reject(new Error('Requesting getCapability to an inactive Caps instance'));
|
||||
return;
|
||||
}
|
||||
this.waitForSeedCapability().then(() =>
|
||||
{
|
||||
if (this.capabilities[capability])
|
||||
@@ -279,5 +285,6 @@ export class Caps
|
||||
{
|
||||
this.eventQueueClient.shutdown();
|
||||
}
|
||||
this.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ export class Circuit
|
||||
receivedPackets: {
|
||||
[key: number]: number
|
||||
} = {};
|
||||
active = false;
|
||||
private clientEvents: ClientEvents;
|
||||
|
||||
private onPacketReceived: Subject<Packet>;
|
||||
@@ -65,6 +66,10 @@ export class Circuit
|
||||
|
||||
sendMessage(message: MessageBase, flags: PacketFlags): number
|
||||
{
|
||||
if (!this.active)
|
||||
{
|
||||
throw new Error('Attempting to send a message on a closed circuit');
|
||||
}
|
||||
const packet: Packet = new Packet();
|
||||
packet.message = message;
|
||||
packet.sequenceNumber = this.sequenceNumber++;
|
||||
@@ -75,6 +80,11 @@ export class Circuit
|
||||
|
||||
resend(sequenceNumber: number)
|
||||
{
|
||||
if (!this.active)
|
||||
{
|
||||
console.log('Resend triggered, but circuit is not active!');
|
||||
return;
|
||||
}
|
||||
if (this.awaitingAck[sequenceNumber])
|
||||
{
|
||||
const toResend: Packet = this.awaitingAck[sequenceNumber].packet;
|
||||
@@ -147,6 +157,7 @@ export class Circuit
|
||||
{
|
||||
|
||||
});
|
||||
this.active = true;
|
||||
}
|
||||
|
||||
shutdown()
|
||||
@@ -169,6 +180,7 @@ export class Circuit
|
||||
this.onPacketReceived.complete();
|
||||
this.onAckReceived.complete();
|
||||
}
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
waitForMessage<T extends MessageBase>(id: Message, timeout: number, filter?: (message: T) => FilterResponse): Promise<T>
|
||||
|
||||
@@ -38,4 +38,17 @@ export class ClientCommands
|
||||
this.group = new GroupCommands(region, agent, bot);
|
||||
this.inventory = new InventoryCommands(region, agent, bot);
|
||||
}
|
||||
shutdown()
|
||||
{
|
||||
this.network.shutdown();
|
||||
this.asset.shutdown();
|
||||
this.teleport.shutdown();
|
||||
this.region.shutdown();
|
||||
this.parcel.shutdown();
|
||||
this.grid.shutdown();
|
||||
this.comms.shutdown();
|
||||
this.agent.shutdown();
|
||||
this.group.shutdown();
|
||||
this.inventory.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,14 +36,22 @@ export class EventQueueClient
|
||||
}
|
||||
shutdown()
|
||||
{
|
||||
const state = new EventQueueStateChangeEvent();
|
||||
state.active = false;
|
||||
this.clientEvents.onEventQueueStateChange.next(state);
|
||||
// We must ACK any outstanding events
|
||||
this.done = true;
|
||||
if (this.currentRequest !== null)
|
||||
{
|
||||
this.currentRequest.abort();
|
||||
}
|
||||
this.done = true;
|
||||
const req = {
|
||||
'ack': this.ack,
|
||||
'done': true
|
||||
};
|
||||
this.capsRequestXML('EventQueueGet', req).then((data) =>
|
||||
{
|
||||
const state = new EventQueueStateChangeEvent();
|
||||
state.active = false;
|
||||
this.clientEvents.onEventQueueStateChange.next(state);
|
||||
});
|
||||
}
|
||||
Get()
|
||||
{
|
||||
@@ -54,340 +62,6 @@ export class EventQueueClient
|
||||
const startTime = new Date().getTime();
|
||||
this.capsRequestXML('EventQueueGet', req).then((data) =>
|
||||
{
|
||||
if (data['events'])
|
||||
{
|
||||
data['events'].forEach((event: any) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (event['message'])
|
||||
{
|
||||
// noinspection TsLint
|
||||
switch (event['message'])
|
||||
{
|
||||
case 'EnableSimulator':
|
||||
|
||||
/*
|
||||
{
|
||||
"body": {
|
||||
"SimulatorInfo": [
|
||||
{
|
||||
"Handle": "AALoAAAECwA=",
|
||||
"IP": "2FIqRA==",
|
||||
"Port": 13029
|
||||
}
|
||||
]
|
||||
},
|
||||
"message": "EnableSimulator"
|
||||
}
|
||||
*/
|
||||
|
||||
break;
|
||||
case 'ParcelProperties':
|
||||
/*
|
||||
{
|
||||
"body": {
|
||||
"AgeVerificationBlock": [
|
||||
{
|
||||
|
||||
"RegionDenyAgeUnverified": true
|
||||
}
|
||||
],
|
||||
"MediaData": [
|
||||
{
|
||||
"MediaDesc": "",
|
||||
"MediaHeight": 0,
|
||||
"MediaLoop": 0,
|
||||
"MediaType": "text/html",
|
||||
"MediaWidth": 0,
|
||||
"ObscureMedia": 0,
|
||||
"ObscureMusic": 0
|
||||
}
|
||||
],
|
||||
"ParcelData": [
|
||||
{
|
||||
"AABBMax": [
|
||||
256,
|
||||
256,
|
||||
50
|
||||
],
|
||||
"AABBMin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"AnyAVSounds": true,
|
||||
"Area": 65536,
|
||||
"AuctionID": "AAAAAA==",
|
||||
"AuthBuyerID": "00000000-0000-0000-0000-000000000000",
|
||||
"Bitmap": "/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8=",
|
||||
"Category": 0,
|
||||
"ClaimDate": 1333505995,
|
||||
"ClaimPrice": 0,
|
||||
"Desc": "adoption parent furry parent teen twin cub neko pets adult elf vamp toddleedoo baby child panel brother sister numbers meshmerized gacha adoptions adopt family mesh skin shape camp ngi youthspot foster kids mall zoo train kid primbay\ndupli
|
||||
city onlinker",
|
||||
"GroupAVSounds": true,
|
||||
"GroupID": "f2b75b49-8ebc-2a9c-f345-aa2f91adc908",
|
||||
"GroupPrims": 18677,
|
||||
"IsGroupOwned": true,
|
||||
"LandingType": 2,
|
||||
"LocalID": 15,
|
||||
"MaxPrims": 30000,
|
||||
"MediaAutoScale": 1,
|
||||
"MediaID": "6bd35c06-2b24-a83e-03f6-f547c65c8556",
|
||||
"MediaURL": "",
|
||||
"MusicURL": "http://142.4.209.63:8071",
|
||||
"Name": "Next Gen Inc. Adoption Agency on the :::: KiD GRiD :::",
|
||||
"OtherCleanTime": 0,
|
||||
"OtherCount": 4096,
|
||||
"OtherPrims": 312,
|
||||
"OwnerID": "f2b75b49-8ebc-2a9c-f345-aa2f91adc908",
|
||||
"OwnerPrims": 3,
|
||||
"ParcelFlags": "NiAUSw==",
|
||||
"ParcelPrimBonus": 1,
|
||||
"PassHours": 10,
|
||||
"PassPrice": 10,
|
||||
"PublicCount": 0,
|
||||
"RegionDenyAnonymous": true,
|
||||
"RegionDenyIdentified": true,
|
||||
"RegionDenyTransacted": true,
|
||||
"RegionPushOverride": true,
|
||||
"RentPrice": 0,
|
||||
"RequestResult": 0,
|
||||
"SalePrice": 1,
|
||||
"SeeAVs": true,
|
||||
"SelectedPrims": 1,
|
||||
"SelfCount": 0,
|
||||
"SequenceID": 0,
|
||||
"SimWideMaxPrims": 30000,
|
||||
"SimWideTotalPrims": 18993,
|
||||
"SnapSelection": true,
|
||||
"SnapshotID": "09c4101a-9406-2501-b9b7-dbb60260fd7a",
|
||||
"Status": 0,
|
||||
"TotalPrims": 18993,
|
||||
"UserLocation": [
|
||||
131.48399353027344,
|
||||
171.41600036621094,
|
||||
21.544700622558594
|
||||
],
|
||||
"UserLookAt": [
|
||||
0.0325143001973629,
|
||||
-0.9994710087776184,
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"RegionAllowAccessBlock": [
|
||||
{
|
||||
"RegionAllowAccessOverride": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"message": "ParcelProperties"
|
||||
}
|
||||
|
||||
*/
|
||||
break;
|
||||
case 'AgentGroupDataUpdate':
|
||||
/*
|
||||
{
|
||||
"body": {
|
||||
"AgentData": [
|
||||
{
|
||||
"AgentID": "49cc9041-5c53-4c1c-8490-e6bb84cdbacd"
|
||||
}
|
||||
],
|
||||
"GroupData": [
|
||||
{
|
||||
"AcceptNotices": true,
|
||||
"Contribution": 0,
|
||||
"GroupID": "06459c46-069f-4de1-c297-c966bd55ab91",
|
||||
"GroupInsigniaID": "8dacb5c9-80bc-aae4-6a12-d792b6eb7dc4",
|
||||
"GroupName": "Jez Ember Estates",
|
||||
"GroupPowers": "AAAgAAQAAAA="
|
||||
},
|
||||
{
|
||||
"AcceptNotices": true,
|
||||
"Contribution": 0,
|
||||
"GroupID": "539b5be0-bb18-d0ef-6c07-3326e0130aaf",
|
||||
"GroupInsigniaID": "7d7d0b4a-bf5b-dc51-3869-5e0eaa6ad41d",
|
||||
"GroupName": "**BOY BEARS MALL**",
|
||||
"GroupPowers": "AAAIABgBAAA="
|
||||
}
|
||||
],
|
||||
"NewGroupData": [
|
||||
{
|
||||
"ListInProfile": true
|
||||
},
|
||||
{
|
||||
"ListInProfile": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"message": "AgentGroupDataUpdate"
|
||||
}
|
||||
|
||||
*/
|
||||
break;
|
||||
case 'AgentStateUpdate':
|
||||
/*
|
||||
|
||||
{
|
||||
"body": {
|
||||
"can_modify_navmesh": true,
|
||||
"has_modified_navmesh": true,
|
||||
"preferences": {
|
||||
"access_prefs": {
|
||||
"max": "PG"
|
||||
},
|
||||
"default_object_perm_masks": {
|
||||
"Everyone": 0,
|
||||
"Group": 0,
|
||||
"NextOwner": 532480
|
||||
},
|
||||
"god_level": 0,
|
||||
"hover_height": 0,
|
||||
"language": "",
|
||||
"language_is_public": true
|
||||
}
|
||||
},
|
||||
"message": "AgentStateUpdate"
|
||||
}
|
||||
*/
|
||||
break;
|
||||
case 'TeleportFailed':
|
||||
{
|
||||
const tpEvent = new TeleportEvent();
|
||||
tpEvent.message = event['body']['Info'][0]['Reason'];
|
||||
tpEvent.eventType = TeleportEventType.TeleportFailed;
|
||||
tpEvent.simIP = '';
|
||||
tpEvent.simPort = 0;
|
||||
tpEvent.seedCapability = '';
|
||||
|
||||
this.clientEvents.onTeleportEvent.next(tpEvent);
|
||||
break;
|
||||
}
|
||||
case "ChatterBoxSessionStartReply":
|
||||
{
|
||||
if (event['body'])
|
||||
{
|
||||
const gcsje = new GroupChatSessionJoinEvent();
|
||||
gcsje.sessionID = new UUID(event['body']['session_id'].toString());
|
||||
gcsje.success = event['body']['success'];
|
||||
if (gcsje.success)
|
||||
{
|
||||
this.agent.addChatSession(gcsje.sessionID);
|
||||
}
|
||||
this.clientEvents.onGroupChatSessionJoin.next(gcsje);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'ChatterBoxInvitation':
|
||||
{
|
||||
if (event['body'] && event['body']['instantmessage'] && event['body']['instantmessage']['message_params'] && event['body']['instantmessage']['message_params']['id'])
|
||||
{
|
||||
const messageParams = event['body']['instantmessage']['message_params'];
|
||||
const imSessionID = messageParams['id'];
|
||||
|
||||
|
||||
const groupChatEvent = new GroupChatEvent();
|
||||
groupChatEvent.from = new UUID(messageParams['from_id'].toString());
|
||||
groupChatEvent.fromName = messageParams['from_name'];
|
||||
groupChatEvent.groupID = new UUID(messageParams['id'].toString());
|
||||
groupChatEvent.message = messageParams['message'];
|
||||
|
||||
const requestedFolders = {
|
||||
'method': 'accept invitation',
|
||||
'session-id': imSessionID
|
||||
};
|
||||
this.caps.capsRequestXML('ChatSessionRequest', requestedFolders).then((ignore: any) =>
|
||||
{
|
||||
this.agent.addChatSession(groupChatEvent.groupID);
|
||||
|
||||
const gcsje = new GroupChatSessionJoinEvent();
|
||||
gcsje.sessionID = groupChatEvent.groupID;
|
||||
gcsje.success = true;
|
||||
this.clientEvents.onGroupChatSessionJoin.next(gcsje);
|
||||
this.clientEvents.onGroupChat.next(groupChatEvent);
|
||||
}).catch((err) =>
|
||||
{
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'ChatterBoxSessionAgentListUpdates':
|
||||
{
|
||||
if (event['body'])
|
||||
{
|
||||
if (event['body']['agent_updates'])
|
||||
{
|
||||
Object.keys(event['body']['agent_updates']).forEach((agentUpdate) =>
|
||||
{
|
||||
const updObj = event['body']['agent_updates'][agentUpdate];
|
||||
const gcsale = new GroupChatSessionAgentListEvent();
|
||||
gcsale.agentID = new UUID(agentUpdate);
|
||||
gcsale.groupID = new UUID(event['body']['session_id'].toString());
|
||||
gcsale.canVoiceChat = false;
|
||||
gcsale.isModerator = false;
|
||||
gcsale.entered = (updObj['transition'] === 'ENTER');
|
||||
|
||||
if (updObj['can_voice_chat'] === true)
|
||||
{
|
||||
gcsale.canVoiceChat = true;
|
||||
}
|
||||
if (updObj['is_moderator'] === true)
|
||||
{
|
||||
gcsale.isModerator = true;
|
||||
}
|
||||
this.clientEvents.onGroupChatAgentListUpdate.next(gcsale);
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'TeleportFinish':
|
||||
{
|
||||
const info = event['body']['Info'][0];
|
||||
if (info['LocationID'])
|
||||
{
|
||||
info['LocationID'] = Buffer.from(info['LocationID'].toArray()).readUInt32LE(0);
|
||||
|
||||
const regionHandleBuf = Buffer.from(info['RegionHandle'].toArray());
|
||||
info['RegionHandle'] = new Long(regionHandleBuf.readUInt32LE(0), regionHandleBuf.readUInt32LE(4), true);
|
||||
|
||||
|
||||
info['SimIP'] = new IPAddress(Buffer.from(info['SimIP'].toArray()), 0).toString();
|
||||
|
||||
info['TeleportFlags'] = Buffer.from(info['TeleportFlags'].toArray()).readUInt32LE(0);
|
||||
|
||||
const tpEvent = new TeleportEvent();
|
||||
tpEvent.message = '';
|
||||
tpEvent.eventType = TeleportEventType.TeleportCompleted;
|
||||
tpEvent.simIP = info['SimIP'];
|
||||
tpEvent.simPort = info['SimPort'];
|
||||
tpEvent.seedCapability = info['SeedCapability'];
|
||||
|
||||
this.clientEvents.onTeleportEvent.next(tpEvent);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.log('Unhandled event:');
|
||||
console.log(JSON.stringify(event, null, 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (erro)
|
||||
{
|
||||
console.error('Error handling cap');
|
||||
console.error(erro);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (data['id'])
|
||||
{
|
||||
this.ack = data['id'];
|
||||
@@ -396,6 +70,347 @@ export class EventQueueClient
|
||||
{
|
||||
this.ack = undefined;
|
||||
}
|
||||
try
|
||||
{
|
||||
if (data['events'])
|
||||
{
|
||||
data['events'].forEach((event: any) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (event['message'])
|
||||
{
|
||||
// noinspection TsLint
|
||||
switch (event['message'])
|
||||
{
|
||||
case 'EnableSimulator':
|
||||
|
||||
/*
|
||||
{
|
||||
"body": {
|
||||
"SimulatorInfo": [
|
||||
{
|
||||
"Handle": "AALoAAAECwA=",
|
||||
"IP": "2FIqRA==",
|
||||
"Port": 13029
|
||||
}
|
||||
]
|
||||
},
|
||||
"message": "EnableSimulator"
|
||||
}
|
||||
*/
|
||||
|
||||
break;
|
||||
case 'ParcelProperties':
|
||||
/*
|
||||
{
|
||||
"body": {
|
||||
"AgeVerificationBlock": [
|
||||
{
|
||||
|
||||
"RegionDenyAgeUnverified": true
|
||||
}
|
||||
],
|
||||
"MediaData": [
|
||||
{
|
||||
"MediaDesc": "",
|
||||
"MediaHeight": 0,
|
||||
"MediaLoop": 0,
|
||||
"MediaType": "text/html",
|
||||
"MediaWidth": 0,
|
||||
"ObscureMedia": 0,
|
||||
"ObscureMusic": 0
|
||||
}
|
||||
],
|
||||
"ParcelData": [
|
||||
{
|
||||
"AABBMax": [
|
||||
256,
|
||||
256,
|
||||
50
|
||||
],
|
||||
"AABBMin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"AnyAVSounds": true,
|
||||
"Area": 65536,
|
||||
"AuctionID": "AAAAAA==",
|
||||
"AuthBuyerID": "00000000-0000-0000-0000-000000000000",
|
||||
"Bitmap": "/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8=",
|
||||
"Category": 0,
|
||||
"ClaimDate": 1333505995,
|
||||
"ClaimPrice": 0,
|
||||
"Desc": "adoption parent furry parent teen twin cub neko pets adult elf vamp toddleedoo baby child panel brother sister numbers meshmerized gacha adoptions adopt family mesh skin shape camp ngi youthspot foster kids mall zoo train kid primbay\ndupli
|
||||
city onlinker",
|
||||
"GroupAVSounds": true,
|
||||
"GroupID": "f2b75b49-8ebc-2a9c-f345-aa2f91adc908",
|
||||
"GroupPrims": 18677,
|
||||
"IsGroupOwned": true,
|
||||
"LandingType": 2,
|
||||
"LocalID": 15,
|
||||
"MaxPrims": 30000,
|
||||
"MediaAutoScale": 1,
|
||||
"MediaID": "6bd35c06-2b24-a83e-03f6-f547c65c8556",
|
||||
"MediaURL": "",
|
||||
"MusicURL": "http://142.4.209.63:8071",
|
||||
"Name": "Next Gen Inc. Adoption Agency on the :::: KiD GRiD :::",
|
||||
"OtherCleanTime": 0,
|
||||
"OtherCount": 4096,
|
||||
"OtherPrims": 312,
|
||||
"OwnerID": "f2b75b49-8ebc-2a9c-f345-aa2f91adc908",
|
||||
"OwnerPrims": 3,
|
||||
"ParcelFlags": "NiAUSw==",
|
||||
"ParcelPrimBonus": 1,
|
||||
"PassHours": 10,
|
||||
"PassPrice": 10,
|
||||
"PublicCount": 0,
|
||||
"RegionDenyAnonymous": true,
|
||||
"RegionDenyIdentified": true,
|
||||
"RegionDenyTransacted": true,
|
||||
"RegionPushOverride": true,
|
||||
"RentPrice": 0,
|
||||
"RequestResult": 0,
|
||||
"SalePrice": 1,
|
||||
"SeeAVs": true,
|
||||
"SelectedPrims": 1,
|
||||
"SelfCount": 0,
|
||||
"SequenceID": 0,
|
||||
"SimWideMaxPrims": 30000,
|
||||
"SimWideTotalPrims": 18993,
|
||||
"SnapSelection": true,
|
||||
"SnapshotID": "09c4101a-9406-2501-b9b7-dbb60260fd7a",
|
||||
"Status": 0,
|
||||
"TotalPrims": 18993,
|
||||
"UserLocation": [
|
||||
131.48399353027344,
|
||||
171.41600036621094,
|
||||
21.544700622558594
|
||||
],
|
||||
"UserLookAt": [
|
||||
0.0325143001973629,
|
||||
-0.9994710087776184,
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"RegionAllowAccessBlock": [
|
||||
{
|
||||
"RegionAllowAccessOverride": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"message": "ParcelProperties"
|
||||
}
|
||||
|
||||
*/
|
||||
break;
|
||||
case 'AgentGroupDataUpdate':
|
||||
/*
|
||||
{
|
||||
"body": {
|
||||
"AgentData": [
|
||||
{
|
||||
"AgentID": "49cc9041-5c53-4c1c-8490-e6bb84cdbacd"
|
||||
}
|
||||
],
|
||||
"GroupData": [
|
||||
{
|
||||
"AcceptNotices": true,
|
||||
"Contribution": 0,
|
||||
"GroupID": "06459c46-069f-4de1-c297-c966bd55ab91",
|
||||
"GroupInsigniaID": "8dacb5c9-80bc-aae4-6a12-d792b6eb7dc4",
|
||||
"GroupName": "Jez Ember Estates",
|
||||
"GroupPowers": "AAAgAAQAAAA="
|
||||
},
|
||||
{
|
||||
"AcceptNotices": true,
|
||||
"Contribution": 0,
|
||||
"GroupID": "539b5be0-bb18-d0ef-6c07-3326e0130aaf",
|
||||
"GroupInsigniaID": "7d7d0b4a-bf5b-dc51-3869-5e0eaa6ad41d",
|
||||
"GroupName": "**BOY BEARS MALL**",
|
||||
"GroupPowers": "AAAIABgBAAA="
|
||||
}
|
||||
],
|
||||
"NewGroupData": [
|
||||
{
|
||||
"ListInProfile": true
|
||||
},
|
||||
{
|
||||
"ListInProfile": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"message": "AgentGroupDataUpdate"
|
||||
}
|
||||
|
||||
*/
|
||||
break;
|
||||
case 'AgentStateUpdate':
|
||||
/*
|
||||
|
||||
{
|
||||
"body": {
|
||||
"can_modify_navmesh": true,
|
||||
"has_modified_navmesh": true,
|
||||
"preferences": {
|
||||
"access_prefs": {
|
||||
"max": "PG"
|
||||
},
|
||||
"default_object_perm_masks": {
|
||||
"Everyone": 0,
|
||||
"Group": 0,
|
||||
"NextOwner": 532480
|
||||
},
|
||||
"god_level": 0,
|
||||
"hover_height": 0,
|
||||
"language": "",
|
||||
"language_is_public": true
|
||||
}
|
||||
},
|
||||
"message": "AgentStateUpdate"
|
||||
}
|
||||
*/
|
||||
break;
|
||||
case 'TeleportFailed':
|
||||
{
|
||||
const tpEvent = new TeleportEvent();
|
||||
tpEvent.message = event['body']['Info'][0]['Reason'];
|
||||
tpEvent.eventType = TeleportEventType.TeleportFailed;
|
||||
tpEvent.simIP = '';
|
||||
tpEvent.simPort = 0;
|
||||
tpEvent.seedCapability = '';
|
||||
|
||||
this.clientEvents.onTeleportEvent.next(tpEvent);
|
||||
break;
|
||||
}
|
||||
case "ChatterBoxSessionStartReply":
|
||||
{
|
||||
if (event['body'])
|
||||
{
|
||||
const gcsje = new GroupChatSessionJoinEvent();
|
||||
gcsje.sessionID = new UUID(event['body']['session_id'].toString());
|
||||
gcsje.success = event['body']['success'];
|
||||
if (gcsje.success)
|
||||
{
|
||||
this.agent.addChatSession(gcsje.sessionID);
|
||||
}
|
||||
this.clientEvents.onGroupChatSessionJoin.next(gcsje);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'ChatterBoxInvitation':
|
||||
{
|
||||
if (event['body'] && event['body']['instantmessage'] && event['body']['instantmessage']['message_params'] && event['body']['instantmessage']['message_params']['id'])
|
||||
{
|
||||
const messageParams = event['body']['instantmessage']['message_params'];
|
||||
const imSessionID = messageParams['id'];
|
||||
|
||||
|
||||
const groupChatEvent = new GroupChatEvent();
|
||||
groupChatEvent.from = new UUID(messageParams['from_id'].toString());
|
||||
groupChatEvent.fromName = messageParams['from_name'];
|
||||
groupChatEvent.groupID = new UUID(messageParams['id'].toString());
|
||||
groupChatEvent.message = messageParams['message'];
|
||||
|
||||
const requestedFolders = {
|
||||
'method': 'accept invitation',
|
||||
'session-id': imSessionID
|
||||
};
|
||||
this.caps.capsRequestXML('ChatSessionRequest', requestedFolders).then((ignore: any) =>
|
||||
{
|
||||
this.agent.addChatSession(groupChatEvent.groupID);
|
||||
|
||||
const gcsje = new GroupChatSessionJoinEvent();
|
||||
gcsje.sessionID = groupChatEvent.groupID;
|
||||
gcsje.success = true;
|
||||
this.clientEvents.onGroupChatSessionJoin.next(gcsje);
|
||||
this.clientEvents.onGroupChat.next(groupChatEvent);
|
||||
}).catch((err) =>
|
||||
{
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'ChatterBoxSessionAgentListUpdates':
|
||||
{
|
||||
if (event['body'])
|
||||
{
|
||||
if (event['body']['agent_updates'])
|
||||
{
|
||||
Object.keys(event['body']['agent_updates']).forEach((agentUpdate) =>
|
||||
{
|
||||
const updObj = event['body']['agent_updates'][agentUpdate];
|
||||
const gcsale = new GroupChatSessionAgentListEvent();
|
||||
gcsale.agentID = new UUID(agentUpdate);
|
||||
gcsale.groupID = new UUID(event['body']['session_id'].toString());
|
||||
gcsale.canVoiceChat = false;
|
||||
gcsale.isModerator = false;
|
||||
gcsale.entered = (updObj['transition'] === 'ENTER');
|
||||
|
||||
if (updObj['can_voice_chat'] === true)
|
||||
{
|
||||
gcsale.canVoiceChat = true;
|
||||
}
|
||||
if (updObj['is_moderator'] === true)
|
||||
{
|
||||
gcsale.isModerator = true;
|
||||
}
|
||||
this.clientEvents.onGroupChatAgentListUpdate.next(gcsale);
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'TeleportFinish':
|
||||
{
|
||||
const info = event['body']['Info'][0];
|
||||
if (info['LocationID'])
|
||||
{
|
||||
info['LocationID'] = Buffer.from(info['LocationID'].toArray()).readUInt32LE(0);
|
||||
|
||||
const regionHandleBuf = Buffer.from(info['RegionHandle'].toArray());
|
||||
info['RegionHandle'] = new Long(regionHandleBuf.readUInt32LE(0), regionHandleBuf.readUInt32LE(4), true);
|
||||
|
||||
|
||||
info['SimIP'] = new IPAddress(Buffer.from(info['SimIP'].toArray()), 0).toString();
|
||||
|
||||
info['TeleportFlags'] = Buffer.from(info['TeleportFlags'].toArray()).readUInt32LE(0);
|
||||
|
||||
const tpEvent = new TeleportEvent();
|
||||
tpEvent.message = '';
|
||||
tpEvent.eventType = TeleportEventType.TeleportCompleted;
|
||||
tpEvent.simIP = info['SimIP'];
|
||||
tpEvent.simPort = info['SimPort'];
|
||||
tpEvent.seedCapability = info['SeedCapability'];
|
||||
|
||||
this.clientEvents.onTeleportEvent.next(tpEvent);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.log('Unhandled event:');
|
||||
console.log(JSON.stringify(event, null, 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (erro)
|
||||
{
|
||||
console.error('Error handling cap');
|
||||
console.error(erro);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.error(error);
|
||||
}
|
||||
if (!this.done)
|
||||
{
|
||||
this.Get();
|
||||
@@ -413,16 +428,19 @@ export class EventQueueClient
|
||||
}
|
||||
else
|
||||
{
|
||||
console.error('Event queue aborted after ' + time + 'ms. Reconnecting in 5 seconds');
|
||||
|
||||
// Wait 5 seconds before retrying
|
||||
setTimeout(() =>
|
||||
if (!this.done)
|
||||
{
|
||||
if (!this.done)
|
||||
console.error('Event queue aborted after ' + time + 'ms. Reconnecting in 5 seconds');
|
||||
|
||||
// Wait 5 seconds before retrying
|
||||
setTimeout(() =>
|
||||
{
|
||||
this.Get();
|
||||
}
|
||||
}, 5000);
|
||||
if (!this.done)
|
||||
{
|
||||
this.Get();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -474,7 +492,7 @@ export class EventQueueClient
|
||||
else
|
||||
{
|
||||
// Retry caps request three times before giving up
|
||||
if (attempt < 3)
|
||||
if (attempt < 3 && capability !== 'EventQueueGet')
|
||||
{
|
||||
return this.capsRequestXML(capability, data, ++attempt);
|
||||
}
|
||||
|
||||
@@ -6,12 +6,71 @@ import {ClientEvents} from './ClientEvents';
|
||||
import {IObjectStore} from './interfaces/IObjectStore';
|
||||
import {ObjectStoreFull} from './ObjectStoreFull';
|
||||
import {ObjectStoreLite} from './ObjectStoreLite';
|
||||
import {BotOptionFlags} from '..';
|
||||
import {BotOptionFlags, PacketFlags, UUID} from '..';
|
||||
import {RequestRegionInfoMessage} from './messages/RequestRegionInfo';
|
||||
import {RegionInfoMessage} from './messages/RegionInfo';
|
||||
import {Message} from '../enums/Message';
|
||||
import {Utils} from './Utils';
|
||||
import {RegionFlags} from '../enums/RegionFlags';
|
||||
import {RegionHandshakeMessage} from './messages/RegionHandshake';
|
||||
import {MapNameRequestMessage} from './messages/MapNameRequest';
|
||||
import {GridLayerType} from '../enums/GridLayerType';
|
||||
import {MapBlockReplyMessage} from './messages/MapBlockReply';
|
||||
import {FilterResponse} from '../enums/FilterResponse';
|
||||
import * as Long from 'long';
|
||||
|
||||
export class Region
|
||||
{
|
||||
regionName: string;
|
||||
regionOwner: UUID;
|
||||
regionID: UUID;
|
||||
regionHandle: Long;
|
||||
xCoordinate: number;
|
||||
yCoordinate: number;
|
||||
estateID: number;
|
||||
parentEstateID: number;
|
||||
regionFlags: RegionFlags;
|
||||
mapImage: UUID;
|
||||
|
||||
simAccess: number;
|
||||
maxAgents: number;
|
||||
billableFactor: number;
|
||||
objectBonusFactor: number;
|
||||
waterHeight: number;
|
||||
terrainRaiseLimit: number;
|
||||
terrainLowerLimit: number;
|
||||
pricePerMeter: number;
|
||||
redirectGridX: number;
|
||||
redirectGridY: number;
|
||||
useEstateSun: boolean;
|
||||
sunHour: number;
|
||||
productSKU: string;
|
||||
productName: string;
|
||||
maxAgents32: number;
|
||||
hardMaxAgents: number;
|
||||
hardMaxObjects: number;
|
||||
cacheID: UUID;
|
||||
cpuClassID: number;
|
||||
cpuRatio: number;
|
||||
coloName: string;
|
||||
|
||||
terrainBase0: UUID;
|
||||
terrainBase1: UUID;
|
||||
terrainBase2: UUID;
|
||||
terrainBase3: UUID;
|
||||
terrainDetail0: UUID;
|
||||
terrainDetail1: UUID;
|
||||
terrainDetail2: UUID;
|
||||
terrainDetail3: UUID;
|
||||
terrainStartHeight00: number;
|
||||
terrainStartHeight01: number;
|
||||
terrainStartHeight10: number;
|
||||
terrainStartHeight11: number;
|
||||
terrainHeightRange00: number;
|
||||
terrainHeightRange01: number;
|
||||
terrainHeightRange10: number;
|
||||
terrainHeightRange11: number;
|
||||
|
||||
circuit: Circuit;
|
||||
objects: IObjectStore;
|
||||
caps: Caps;
|
||||
@@ -40,6 +99,93 @@ export class Region
|
||||
{
|
||||
this.caps = new Caps(this.agent, this, seedURL, this.clientEvents);
|
||||
}
|
||||
async handshake(handshake: RegionHandshakeMessage)
|
||||
{
|
||||
this.regionName = Utils.BufferToStringSimple(handshake.RegionInfo.SimName);
|
||||
this.simAccess = handshake.RegionInfo.SimAccess;
|
||||
this.regionFlags = handshake.RegionInfo.RegionFlags;
|
||||
this.regionOwner = handshake.RegionInfo.SimOwner;
|
||||
this.agent.setIsEstateManager(handshake.RegionInfo.IsEstateManager);
|
||||
this.waterHeight = handshake.RegionInfo.WaterHeight;
|
||||
this.billableFactor = handshake.RegionInfo.BillableFactor;
|
||||
this.cacheID = handshake.RegionInfo.CacheID;
|
||||
this.terrainBase0 = handshake.RegionInfo.TerrainBase0;
|
||||
this.terrainBase1 = handshake.RegionInfo.TerrainBase1;
|
||||
this.terrainBase2 = handshake.RegionInfo.TerrainBase2;
|
||||
this.terrainBase3 = handshake.RegionInfo.TerrainBase3;
|
||||
this.terrainDetail0 = handshake.RegionInfo.TerrainDetail0;
|
||||
this.terrainDetail1 = handshake.RegionInfo.TerrainDetail1;
|
||||
this.terrainDetail2 = handshake.RegionInfo.TerrainDetail2;
|
||||
this.terrainDetail3 = handshake.RegionInfo.TerrainDetail3;
|
||||
this.terrainStartHeight00 = handshake.RegionInfo.TerrainStartHeight00;
|
||||
this.terrainStartHeight01 = handshake.RegionInfo.TerrainStartHeight01;
|
||||
this.terrainStartHeight10 = handshake.RegionInfo.TerrainStartHeight10;
|
||||
this.terrainStartHeight11 = handshake.RegionInfo.TerrainStartHeight11;
|
||||
this.terrainHeightRange00 = handshake.RegionInfo.TerrainHeightRange00;
|
||||
this.terrainHeightRange01 = handshake.RegionInfo.TerrainHeightRange01;
|
||||
this.terrainHeightRange10 = handshake.RegionInfo.TerrainHeightRange10;
|
||||
this.terrainHeightRange11 = handshake.RegionInfo.TerrainHeightRange11;
|
||||
this.regionID = handshake.RegionInfo2.RegionID;
|
||||
this.cpuClassID = handshake.RegionInfo3.CPUClassID;
|
||||
this.cpuRatio = handshake.RegionInfo3.CPURatio;
|
||||
this.coloName = Utils.BufferToStringSimple(handshake.RegionInfo3.ColoName);
|
||||
this.productSKU = Utils.BufferToStringSimple(handshake.RegionInfo3.ProductSKU);
|
||||
this.productName = Utils.BufferToStringSimple(handshake.RegionInfo3.ProductName);
|
||||
|
||||
|
||||
|
||||
const request: RequestRegionInfoMessage = new RequestRegionInfoMessage();
|
||||
request.AgentData = {
|
||||
AgentID: this.agent.agentID,
|
||||
SessionID: this.circuit.sessionID
|
||||
};
|
||||
this.circuit.sendMessage(request, PacketFlags.Reliable);
|
||||
const regionInfo: RegionInfoMessage = await this.circuit.waitForMessage<RegionInfoMessage>(Message.RegionInfo, 10000);
|
||||
|
||||
this.estateID = regionInfo.RegionInfo.EstateID;
|
||||
this.parentEstateID = regionInfo.RegionInfo.ParentEstateID;
|
||||
this.maxAgents = regionInfo.RegionInfo.MaxAgents;
|
||||
this.objectBonusFactor = regionInfo.RegionInfo.ObjectBonusFactor;
|
||||
this.terrainRaiseLimit = regionInfo.RegionInfo.TerrainRaiseLimit;
|
||||
this.terrainLowerLimit = regionInfo.RegionInfo.TerrainLowerLimit;
|
||||
this.pricePerMeter = regionInfo.RegionInfo.PricePerMeter;
|
||||
this.redirectGridX = regionInfo.RegionInfo.RedirectGridX;
|
||||
this.redirectGridY = regionInfo.RegionInfo.RedirectGridY;
|
||||
this.useEstateSun = regionInfo.RegionInfo.UseEstateSun;
|
||||
this.sunHour = regionInfo.RegionInfo.SunHour;
|
||||
this.maxAgents32 = regionInfo.RegionInfo2.MaxAgents32;
|
||||
this.hardMaxAgents = regionInfo.RegionInfo2.HardMaxAgents;
|
||||
this.hardMaxObjects = regionInfo.RegionInfo2.HardMaxObjects;
|
||||
|
||||
const msg: MapNameRequestMessage = new MapNameRequestMessage();
|
||||
msg.AgentData = {
|
||||
AgentID: this.agent.agentID,
|
||||
SessionID: this.circuit.sessionID,
|
||||
Flags: GridLayerType.Objects,
|
||||
EstateID: 0,
|
||||
Godlike: false
|
||||
};
|
||||
msg.NameData = {
|
||||
Name: handshake.RegionInfo.SimName
|
||||
};
|
||||
this.circuit.sendMessage(msg, PacketFlags.Reliable);
|
||||
const reply: MapBlockReplyMessage = await this.circuit.waitForMessage<MapBlockReplyMessage>(Message.MapBlockReply, 10000, (filterMsg: MapBlockReplyMessage): FilterResponse =>
|
||||
{
|
||||
for (const region of filterMsg.Data)
|
||||
{
|
||||
const name = Utils.BufferToStringSimple(region.Name);
|
||||
if (name.trim().toLowerCase() === this.regionName.trim().toLowerCase())
|
||||
{
|
||||
this.xCoordinate = region.X;
|
||||
this.yCoordinate = region.Y;
|
||||
this.mapImage = region.MapImageID;
|
||||
this.regionHandle = Utils.RegionCoordinatesToHandle(this.xCoordinate, this.yCoordinate);
|
||||
return FilterResponse.Finish;
|
||||
}
|
||||
}
|
||||
return FilterResponse.NoMatch;
|
||||
});
|
||||
}
|
||||
shutdown()
|
||||
{
|
||||
this.comms.shutdown();
|
||||
|
||||
@@ -17,4 +17,8 @@ export class CommandsBase
|
||||
this.bot = bot;
|
||||
this.circuit = this.currentRegion.circuit;
|
||||
}
|
||||
}
|
||||
shutdown()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,57 @@ import {TeleportLureRequestMessage} from '../messages/TeleportLureRequest';
|
||||
import {Vector3} from '../Vector3';
|
||||
import {TeleportLocationRequestMessage} from '../messages/TeleportLocationRequest';
|
||||
import * as Long from 'long';
|
||||
import {LureEvent, PacketFlags, RegionInfoReplyEvent, TeleportEvent, TeleportFlags} from '../..';
|
||||
import {LureEvent, PacketFlags, RegionInfoReplyEvent, TeleportEvent, TeleportFlags, Bot} from '../..';
|
||||
import {Agent} from '../Agent';
|
||||
import {Subscription} from 'rxjs/Subscription';
|
||||
|
||||
export class TeleportCommands extends CommandsBase
|
||||
{
|
||||
private awaitTeleportEvent(): Promise<TeleportEvent>
|
||||
private expectingTeleport = false;
|
||||
private teleportSubscription: Subscription;
|
||||
constructor(region: Region, agent: Agent, bot: Bot)
|
||||
{
|
||||
super(region, agent, bot);
|
||||
this.teleportSubscription = this.bot.clientEvents.onTeleportEvent.subscribe((e: TeleportEvent) =>
|
||||
{
|
||||
if (e.eventType === TeleportEventType.TeleportCompleted)
|
||||
{
|
||||
if (!this.expectingTeleport)
|
||||
{
|
||||
if (e.simIP === 'local')
|
||||
{
|
||||
// Local TP - no need for any other shindiggery
|
||||
return;
|
||||
}
|
||||
|
||||
const newRegion: Region = new Region(this.agent, this.bot.clientEvents, this.currentRegion.options);
|
||||
newRegion.circuit.circuitCode = this.currentRegion.circuit.circuitCode;
|
||||
newRegion.circuit.secureSessionID = this.currentRegion.circuit.secureSessionID;
|
||||
newRegion.circuit.sessionID = this.currentRegion.circuit.sessionID;
|
||||
newRegion.circuit.udpBlacklist = this.currentRegion.circuit.udpBlacklist;
|
||||
newRegion.circuit.ipAddress = e.simIP;
|
||||
newRegion.circuit.port = e.simPort;
|
||||
newRegion.activateCaps(e.seedCapability);
|
||||
|
||||
this.bot.changeRegion(newRegion, false).then(() =>
|
||||
{
|
||||
// Change region successful
|
||||
}).catch((error) =>
|
||||
{
|
||||
console.log('Failed to change region');
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
shutdown()
|
||||
{
|
||||
this.teleportSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
private awaitTeleportEvent(requested: boolean): Promise<TeleportEvent>
|
||||
{
|
||||
return new Promise<TeleportEvent>((resolve, reject) =>
|
||||
{
|
||||
@@ -20,10 +66,15 @@ export class TeleportCommands extends CommandsBase
|
||||
reject(new Error('ClientEvents is null'));
|
||||
return;
|
||||
}
|
||||
this.expectingTeleport = true;
|
||||
const subscription = this.bot.clientEvents.onTeleportEvent.subscribe((e: TeleportEvent) =>
|
||||
{
|
||||
if (e.eventType === TeleportEventType.TeleportFailed || e.eventType === TeleportEventType.TeleportCompleted)
|
||||
{
|
||||
setTimeout(() =>
|
||||
{
|
||||
this.expectingTeleport = false;
|
||||
});
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
if (e.eventType === TeleportEventType.TeleportFailed)
|
||||
@@ -46,7 +97,6 @@ export class TeleportCommands extends CommandsBase
|
||||
}
|
||||
|
||||
// Successful teleport! First, rip apart circuit
|
||||
this.currentRegion.shutdown();
|
||||
const region: Region = new Region(this.agent, this.bot.clientEvents, this.currentRegion.options);
|
||||
region.circuit.circuitCode = this.currentRegion.circuit.circuitCode;
|
||||
region.circuit.secureSessionID = this.currentRegion.circuit.secureSessionID;
|
||||
@@ -54,11 +104,9 @@ export class TeleportCommands extends CommandsBase
|
||||
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);
|
||||
region.activateCaps(e.seedCapability);
|
||||
|
||||
this.bot.changeRegion(this.currentRegion).then(() =>
|
||||
this.bot.changeRegion(region, requested).then(() =>
|
||||
{
|
||||
resolve(e);
|
||||
}).catch((error) =>
|
||||
@@ -88,7 +136,7 @@ export class TeleportCommands extends CommandsBase
|
||||
TeleportFlags: TeleportFlags.ViaLure
|
||||
};
|
||||
circuit.sendMessage(tlr, PacketFlags.Reliable);
|
||||
this.awaitTeleportEvent().then((event: TeleportEvent) =>
|
||||
this.awaitTeleportEvent(true).then((event: TeleportEvent) =>
|
||||
{
|
||||
resolve(event);
|
||||
}).catch((err) =>
|
||||
@@ -113,7 +161,7 @@ export class TeleportCommands extends CommandsBase
|
||||
RegionHandle: handle
|
||||
};
|
||||
this.circuit.sendMessage(rtm, PacketFlags.Reliable);
|
||||
this.awaitTeleportEvent().then((event: TeleportEvent) =>
|
||||
this.awaitTeleportEvent(true).then((event: TeleportEvent) =>
|
||||
{
|
||||
resolve(event);
|
||||
}).catch((err) =>
|
||||
|
||||
Reference in New Issue
Block a user