bonktools 3.2.0 → 4.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -247
- package/dependencies/CondensedInjector.js +18 -0
- package/dependencies/sgrAPI.user.js +526 -0
- package/dist/bot.js +15 -0
- package/dist/browser/browserManager.js +156 -0
- package/dist/browser/roomMaker.js +459 -0
- package/dist/config/room.js +23 -0
- package/dist/index.js +96 -0
- package/dist/lib/index.js +15 -0
- package/dist/messages.js +87 -0
- package/dist/types/constants.types.js +59 -0
- package/dist/types/joinTeam.types.js +10 -0
- package/dist/utils/botExtensions.js +75 -0
- package/dist/utils/investigationTriggerStart.js +21 -0
- package/dist/utils/logger.js +48 -0
- package/package.json +39 -33
- package/bonk_fullchain.pem +0 -37
- package/examples/host-bot.js +0 -63
- package/examples/simple-bot.js +0 -127
- package/old/bonkbot.js +0 -1266
- package/old/examplebot.js +0 -64
- package/scripts/download-cert.js +0 -52
- package/src/bot.js +0 -1470
- package/src/index.js +0 -23
- package/src/packet.js +0 -374
- package/src/utils/constants.js +0 -153
- package/src/utils/errors.js +0 -137
- package/src/utils/logger.js +0 -109
- package/src/utils/validation.js +0 -130
package/src/bot.js
DELETED
|
@@ -1,1470 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Main Bot class for BonkBot
|
|
3
|
-
*/
|
|
4
|
-
const EventEmitter = require('events');
|
|
5
|
-
const io = require('socket.io-client');
|
|
6
|
-
const axios = require('axios');
|
|
7
|
-
const https = require('https');
|
|
8
|
-
const {
|
|
9
|
-
createLogger
|
|
10
|
-
} = require('./utils/logger');
|
|
11
|
-
const {
|
|
12
|
-
DEFAULT_SERVER,
|
|
13
|
-
DEFAULT_AVATAR,
|
|
14
|
-
GAMEMODE_NAMES,
|
|
15
|
-
ENGINE_NAMES,
|
|
16
|
-
TEAM_NAMES,
|
|
17
|
-
CLIENT_MESSAGE_TYPES,
|
|
18
|
-
API,
|
|
19
|
-
SERVER_MESSAGE_TYPES
|
|
20
|
-
} = require('./utils/constants');
|
|
21
|
-
const {
|
|
22
|
-
parsePacket
|
|
23
|
-
} = require('./packet');
|
|
24
|
-
const { LOG_LEVELS } = require("./utils/logger");
|
|
25
|
-
|
|
26
|
-
// Create logger
|
|
27
|
-
const logger = createLogger('BonkBot');
|
|
28
|
-
|
|
29
|
-
const httpsAgent = new https.Agent({
|
|
30
|
-
rejectUnauthorized: false
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Main BonkBot class
|
|
35
|
-
*/
|
|
36
|
-
class BonkBot {
|
|
37
|
-
/**
|
|
38
|
-
* Create a new BonkBot instance
|
|
39
|
-
* @param {Object} options - Bot options
|
|
40
|
-
* @param {Object} options.account - Account information
|
|
41
|
-
* @param {string} [options.account.username] - Username
|
|
42
|
-
* @param {string} [options.account.password] - Password
|
|
43
|
-
* @param {boolean} [options.account.guest=true] - Whether the account is a guest
|
|
44
|
-
* @param {string} [options.avatar] - Bot avatar
|
|
45
|
-
* @param {string} [options.server=b2ny1] - Server to connect to
|
|
46
|
-
* @param {string} [options.bypass] - Pass bypass
|
|
47
|
-
* @param {string} [options.token] - Authentication token
|
|
48
|
-
* @param {string} [options.peerID] - Peer ID
|
|
49
|
-
* @param {number} [options.logLevel=LOG_LEVELS.INFO] - Log level
|
|
50
|
-
*/
|
|
51
|
-
constructor(options = {}) {
|
|
52
|
-
// Set log level
|
|
53
|
-
if (options.logLevel !== undefined) {
|
|
54
|
-
logger.setLevel(options.logLevel);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
logger.info('Creating new BonkBot instance');
|
|
58
|
-
|
|
59
|
-
// Initialize properties
|
|
60
|
-
this.PROTOCOL_VERSION = options.PROTOCOL_VERSION
|
|
61
|
-
this.HARDCODED_PROTOCOL_VERSION = 49; // dont change this, pass the property to the function
|
|
62
|
-
|
|
63
|
-
if(this.PROTOCOL_VERSION == undefined){
|
|
64
|
-
logger.warn("You should really set the PROTOCOL_VERSION to the correct one in the createBot() options object, without it the bot may fail to start")
|
|
65
|
-
logger.warn("Defaulting PROTOCOL_VERSION to: " + this.HARDCODED_PROTOCOL_VERSION)
|
|
66
|
-
this.PROTOCOL_VERSION = this.HARDCODED_PROTOCOL_VERSION;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
this.account = this.validateAccount(options.account || {});
|
|
70
|
-
this.avatar = options.avatar || DEFAULT_AVATAR;
|
|
71
|
-
this.server = options.server || DEFAULT_SERVER;
|
|
72
|
-
this.bypass = options.bypass;
|
|
73
|
-
this.token = options.token;
|
|
74
|
-
this.peerID = options.peerID || this.generatePeerId();
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
// Create event emitter
|
|
78
|
-
this.events = new EventEmitter();
|
|
79
|
-
|
|
80
|
-
// Socket connection
|
|
81
|
-
this.socket = null;
|
|
82
|
-
this.connected = false;
|
|
83
|
-
this.keepAliveTimer = null;
|
|
84
|
-
this.timeSyncCount = 1;
|
|
85
|
-
|
|
86
|
-
this.location = {
|
|
87
|
-
lat: 0,
|
|
88
|
-
long: 0,
|
|
89
|
-
country: "US",
|
|
90
|
-
server: "b2ny1"
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
this.timeSync = {
|
|
94
|
-
count: 0,
|
|
95
|
-
last_sync: 0,
|
|
96
|
-
last_sync_id: 0,
|
|
97
|
-
latency: 0
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Room information
|
|
101
|
-
this.room = {
|
|
102
|
-
id: null,
|
|
103
|
-
name: null,
|
|
104
|
-
address: null,
|
|
105
|
-
server: this.server,
|
|
106
|
-
bypass: this.bypass,
|
|
107
|
-
teamsLocked: false,
|
|
108
|
-
password: null,
|
|
109
|
-
countdown: false,
|
|
110
|
-
|
|
111
|
-
state: false,
|
|
112
|
-
map: false,
|
|
113
|
-
inGame: false,
|
|
114
|
-
roundStartTime: 0,
|
|
115
|
-
rounds: 3,
|
|
116
|
-
|
|
117
|
-
gt: false, // ?
|
|
118
|
-
|
|
119
|
-
quickplay: false,
|
|
120
|
-
teams: false,
|
|
121
|
-
mode: false,
|
|
122
|
-
balance: false,
|
|
123
|
-
inputs: false,
|
|
124
|
-
framecount: false,
|
|
125
|
-
stateID: false,
|
|
126
|
-
admin: false,
|
|
127
|
-
random: false,
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
// Game state
|
|
131
|
-
this.game = {
|
|
132
|
-
id: null,
|
|
133
|
-
host: null,
|
|
134
|
-
banned: false
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
// Player tracking
|
|
138
|
-
this.players = new Map();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Initialize the bot
|
|
143
|
-
* @returns {Promise<BonkBot>} This bot instance
|
|
144
|
-
*/
|
|
145
|
-
async init() {
|
|
146
|
-
logger.info('Initializing BonkBot');
|
|
147
|
-
|
|
148
|
-
try {
|
|
149
|
-
// Get authentication token if needed
|
|
150
|
-
if (!this.account.guest && !this.token) {
|
|
151
|
-
this.token = await this.getToken(this.account.username, this.account.password);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Get server information if using default
|
|
155
|
-
if (!this.server || this.server === DEFAULT_SERVER) {
|
|
156
|
-
logger.info('Getting server information');
|
|
157
|
-
const serverInfo = await this.getServerInfo();
|
|
158
|
-
this.server = serverInfo.server;
|
|
159
|
-
|
|
160
|
-
this.location = serverInfo;
|
|
161
|
-
|
|
162
|
-
this.room.server = this.server;
|
|
163
|
-
logger.info(`Using server: ${this.server}`);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
logger.info('BonkBot initialized');
|
|
167
|
-
this.events.emit('ready');
|
|
168
|
-
|
|
169
|
-
return this;
|
|
170
|
-
} catch (error) {
|
|
171
|
-
logger.error('Failed to initialize BonkBot', error);
|
|
172
|
-
throw error;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Start the keep alive timer
|
|
178
|
-
* @private
|
|
179
|
-
*/
|
|
180
|
-
startKeepAlive() {
|
|
181
|
-
this.keepAliveTimer = setInterval(() => {
|
|
182
|
-
if (this.connected) {
|
|
183
|
-
// Verify the socket is still connected before sending keep-alive
|
|
184
|
-
if (this.socket && this.socket.connected) {
|
|
185
|
-
this.sendTimesync();
|
|
186
|
-
} else {
|
|
187
|
-
logger.warn('Keep-alive detected disconnected socket');
|
|
188
|
-
|
|
189
|
-
this.stopBot();
|
|
190
|
-
this.events.emit('disconnect');
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}, 5000);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Set the room address
|
|
198
|
-
* @param {Object} addressInfo - Room address information
|
|
199
|
-
*/
|
|
200
|
-
setAddress(addressInfo) {
|
|
201
|
-
logger.info('Setting room address');
|
|
202
|
-
|
|
203
|
-
if (!addressInfo.address || !addressInfo.roomname || !addressInfo.server) {
|
|
204
|
-
throw new Error('Invalid room address information');
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
this.room.address = addressInfo.address;
|
|
208
|
-
this.room.name = addressInfo.roomname;
|
|
209
|
-
this.room.server = addressInfo.server;
|
|
210
|
-
this.room.bypass = addressInfo.bypass || '';
|
|
211
|
-
|
|
212
|
-
// Update server if different
|
|
213
|
-
if (this.server !== addressInfo.server) {
|
|
214
|
-
this.server = addressInfo.server;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
logger.info(`Set room address: ${this.room.name} (${this.room.address})`);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Connect to the server
|
|
222
|
-
* @returns {Promise<BonkBot>} This bot instance
|
|
223
|
-
*/
|
|
224
|
-
async connect() {
|
|
225
|
-
if (this.connected) {
|
|
226
|
-
logger.warn('Already connected, disconnecting first');
|
|
227
|
-
this.disconnect();
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Disable TLS certificate verification for bonk.io servers
|
|
231
|
-
// NOTE: This is required because bonk.io uses Sectigo certificates with incomplete chains
|
|
232
|
-
// and socket.io-client v2.x with engine.io-client v3.x doesn't properly pass
|
|
233
|
-
// rejectUnauthorized option to the underlying ws library
|
|
234
|
-
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
|
235
|
-
|
|
236
|
-
logger.info(`Connecting to server: ${this.server}`);
|
|
237
|
-
|
|
238
|
-
const socketAddr = `https://${this.server}.bonk.io`;
|
|
239
|
-
|
|
240
|
-
return new Promise((resolve, reject) => {
|
|
241
|
-
try {
|
|
242
|
-
// Configure socket.io options
|
|
243
|
-
const socketOptions = {
|
|
244
|
-
transports: ['websocket'],
|
|
245
|
-
reconnection: false,
|
|
246
|
-
timeout: 10000,
|
|
247
|
-
forceNew: true,
|
|
248
|
-
path: '/socket.io',
|
|
249
|
-
// NOTE: socket.io-client v2.x with engine.io-client v3.x has a bug where
|
|
250
|
-
// custom CA certificates cannot be properly passed to the websocket transport.
|
|
251
|
-
// As a workaround, we disable certificate verification for bonk.io servers
|
|
252
|
-
// which use Sectigo certificates with incomplete chains.
|
|
253
|
-
rejectUnauthorized: false
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
logger.info('Creating Socket.IO connection with certificate verification disabled');
|
|
257
|
-
this.socket = io(socketAddr, socketOptions);
|
|
258
|
-
|
|
259
|
-
// Set up connection timeout
|
|
260
|
-
const timeout = setTimeout(() => {
|
|
261
|
-
if (!this.connected) {
|
|
262
|
-
reject(new Error(`Connection timeout to server: ${this.server}`));
|
|
263
|
-
this.stopBot();
|
|
264
|
-
}
|
|
265
|
-
}, 10000);
|
|
266
|
-
|
|
267
|
-
// Connection opened
|
|
268
|
-
this.socket.on('connect', () => {
|
|
269
|
-
logger.info('Socket.IO connection established');
|
|
270
|
-
|
|
271
|
-
clearTimeout(timeout);
|
|
272
|
-
this.connected = true;
|
|
273
|
-
|
|
274
|
-
// Set up event handlers
|
|
275
|
-
this.setupSocketEvents();
|
|
276
|
-
|
|
277
|
-
// Start keep alive timer
|
|
278
|
-
this.startKeepAlive();
|
|
279
|
-
|
|
280
|
-
// Emit connect event
|
|
281
|
-
this.events.emit('connect');
|
|
282
|
-
|
|
283
|
-
resolve(this);
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
// Connection error
|
|
287
|
-
this.socket.on('connect_error', (error) => {
|
|
288
|
-
logger.error('Socket.IO connection error:', error.message || error);
|
|
289
|
-
|
|
290
|
-
if (!this.connected) {
|
|
291
|
-
clearTimeout(timeout);
|
|
292
|
-
const errorMsg = error.message || error.description?.message || 'Unknown error';
|
|
293
|
-
reject(new Error(`Failed to connect to server: ${errorMsg}`));
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
this.events.emit('error', error);
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
// Connection error (fallback handler)
|
|
300
|
-
this.socket.on('error', (error) => {
|
|
301
|
-
logger.error('Socket.IO error:', error.message || error);
|
|
302
|
-
|
|
303
|
-
if (!this.connected) {
|
|
304
|
-
clearTimeout(timeout);
|
|
305
|
-
const errorMsg = error.message || error.description?.message || 'Unknown error';
|
|
306
|
-
reject(new Error(`Failed to connect to server: ${errorMsg}`));
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
this.events.emit('error', error);
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
// Connection closed
|
|
313
|
-
this.socket.on('disconnect', (reason) => {
|
|
314
|
-
logger.info(`Socket.IO connection closed: ${reason}`);
|
|
315
|
-
|
|
316
|
-
if (!this.connected) {
|
|
317
|
-
clearTimeout(timeout);
|
|
318
|
-
reject(new Error(`Connection closed before fully established: ${reason}`));
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
this.stopBot();
|
|
322
|
-
this.events.emit('disconnect');
|
|
323
|
-
});
|
|
324
|
-
} catch (error) {
|
|
325
|
-
reject(new Error(`Failed to create Socket.IO connection: ${error.message}`));
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/**
|
|
331
|
-
* Disconnect from the server
|
|
332
|
-
*/
|
|
333
|
-
disconnect() {
|
|
334
|
-
if (!this.connected) {
|
|
335
|
-
logger.warn('Not connected, nothing to disconnect');
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
logger.info('Disconnecting from server');
|
|
340
|
-
|
|
341
|
-
this.stopBot();
|
|
342
|
-
|
|
343
|
-
if (this.socket) {
|
|
344
|
-
this.socket.disconnect();
|
|
345
|
-
this.socket = null;
|
|
346
|
-
}
|
|
347
|
-
return true;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
async getAddressFromUrl(url) {
|
|
352
|
-
const regex = /\/(\d{6})([a-zA-Z0-9]{5})?$/;
|
|
353
|
-
const match = url.match(regex);
|
|
354
|
-
|
|
355
|
-
if (!match) {
|
|
356
|
-
return null;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
const id = match[1];
|
|
360
|
-
const bypass = match[2] || "";
|
|
361
|
-
|
|
362
|
-
const data = new URLSearchParams();
|
|
363
|
-
data.append('joinID', id);
|
|
364
|
-
|
|
365
|
-
try {
|
|
366
|
-
const response = await axios.post(API.AUTOJOIN, data.toString(), {
|
|
367
|
-
headers: {
|
|
368
|
-
'Content-Type': 'application/x-www-form-urlencoded'
|
|
369
|
-
},
|
|
370
|
-
httpsAgent: httpsAgent
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
const result = response.data;
|
|
374
|
-
|
|
375
|
-
if (result.r == "success") {
|
|
376
|
-
result.bypass = bypass;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
return result;
|
|
380
|
-
} catch (error) {
|
|
381
|
-
console.error('Error getting join link:', error);
|
|
382
|
-
throw error; // Re-throw to allow caller to handle the error
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Join a room
|
|
388
|
-
* @param {Object} [options] - Join options
|
|
389
|
-
* @returns {Promise<void>} Resolves when joined
|
|
390
|
-
*/
|
|
391
|
-
async joinRoom(options = {}) {
|
|
392
|
-
if (!this.connected) {
|
|
393
|
-
throw new Error('Not connected to server');
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
if (!this.room.address) {
|
|
397
|
-
throw new Error('Room address not set');
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
logger.info(`Joining room: ${this.room.name} (${this.room.address})`);
|
|
401
|
-
|
|
402
|
-
// Prepare join data
|
|
403
|
-
const joinData = {
|
|
404
|
-
joinID: this.room.address,
|
|
405
|
-
roomPassword: options.password ? options.password.toString() : '',
|
|
406
|
-
guest: this.account.guest,
|
|
407
|
-
dbid: 2,
|
|
408
|
-
version: this.PROTOCOL_VERSION,
|
|
409
|
-
peerID: options.peerID || this.peerID,
|
|
410
|
-
bypass: this.room.bypass || '',
|
|
411
|
-
avatar: this.avatar
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
// Add account-specific data
|
|
415
|
-
if (this.account.guest) {
|
|
416
|
-
joinData.guestName = this.account.username;
|
|
417
|
-
} else {
|
|
418
|
-
joinData.token = this.token;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Send join message
|
|
422
|
-
await this.sendMessage(CLIENT_MESSAGE_TYPES.JOIN_ROOM, joinData);
|
|
423
|
-
|
|
424
|
-
logger.info(`Join request sent for room: ${this.room.name}`);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
/**
|
|
428
|
-
* Create a new room
|
|
429
|
-
* @param {Object} [options] - Room options
|
|
430
|
-
* @returns {Promise<Object>} Room address information
|
|
431
|
-
*/
|
|
432
|
-
async createRoom(options = {}) {
|
|
433
|
-
if (!this.connected) {
|
|
434
|
-
throw new Error('Not connected to server');
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
logger.info('Creating new room');
|
|
438
|
-
|
|
439
|
-
// Set room info
|
|
440
|
-
this.room.name = options.roomname || `BonkBot Room ${Math.floor(Math.random() * 1000)}`;
|
|
441
|
-
this.room.maxPlayers = options.maxplayers || 8;
|
|
442
|
-
this.room.password = options.roompassword || '';
|
|
443
|
-
|
|
444
|
-
// Prepare create data
|
|
445
|
-
const createData = {
|
|
446
|
-
peerID: options.peerID ?? this.peerID,
|
|
447
|
-
roomName: options.roomName ?? this.room.name,
|
|
448
|
-
maxPlayers: options.maxPlayers ?? this.room.maxPlayers,
|
|
449
|
-
password: options.password ?? this.room.password,
|
|
450
|
-
dbid: options.dbid ?? 11822936,
|
|
451
|
-
guest: options.guest ?? this.account.guest,
|
|
452
|
-
minLevel: options.minLevel ?? 0,
|
|
453
|
-
maxLevel: options.maxLevel ?? 999,
|
|
454
|
-
latitude: options.latitude ?? this.location.lat,
|
|
455
|
-
longitude: options.longitude ?? this.location.long,
|
|
456
|
-
country: options.country ?? this.location.country,
|
|
457
|
-
version: this.PROTOCOL_VERSION,
|
|
458
|
-
hidden: options.hidden ? 1 : 0,
|
|
459
|
-
quick: options.quick ?? false,
|
|
460
|
-
mode: options.mode ?? 'custom',
|
|
461
|
-
token: options.token ?? (this.token || ''),
|
|
462
|
-
avatar: options.avatar ?? this.avatar,
|
|
463
|
-
};
|
|
464
|
-
if(createData.guest){
|
|
465
|
-
createData.guestName = this.account.username;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Send create message
|
|
469
|
-
await this.sendMessage(CLIENT_MESSAGE_TYPES.CREATE_ROOM, createData);
|
|
470
|
-
|
|
471
|
-
// set the only player to yorself
|
|
472
|
-
this.players.set(0, {
|
|
473
|
-
peerID: createData.peerID,
|
|
474
|
-
guest: createData.guest,
|
|
475
|
-
team: 1,
|
|
476
|
-
teamName: TEAM_NAMES[1],
|
|
477
|
-
level: 0,
|
|
478
|
-
ready: false,
|
|
479
|
-
tabbed: false,
|
|
480
|
-
avatar: createData.avatar,
|
|
481
|
-
id: 0,
|
|
482
|
-
username: createData.guestName,
|
|
483
|
-
xp: 0,
|
|
484
|
-
ping: 0,
|
|
485
|
-
balance: 0,
|
|
486
|
-
host: true,
|
|
487
|
-
movement: {
|
|
488
|
-
input: 0,
|
|
489
|
-
frame: 0,
|
|
490
|
-
sequence: 0
|
|
491
|
-
}
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
this.game.id = 0
|
|
495
|
-
this.game.host = 0
|
|
496
|
-
|
|
497
|
-
logger.info(`Room creation request sent: ${this.room.name}`);
|
|
498
|
-
|
|
499
|
-
this.events.emit('CREATE_ROOM');
|
|
500
|
-
|
|
501
|
-
// Return room info
|
|
502
|
-
return {
|
|
503
|
-
address: this.room.address,
|
|
504
|
-
roomname: this.room.name,
|
|
505
|
-
server: this.room.server,
|
|
506
|
-
bypass: this.room.bypass
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
/**
|
|
511
|
-
* Get a player by ID
|
|
512
|
-
* @param {string|number} id - Player ID
|
|
513
|
-
* @returns {Object|null} Player object or null if not found
|
|
514
|
-
*/
|
|
515
|
-
getPlayerByID(id) {
|
|
516
|
-
return this.players.get(id) || null;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
/**
|
|
520
|
-
* Get a players id by username
|
|
521
|
-
* @param {string} username - Player username
|
|
522
|
-
* @param {boolean} guest - Whether the player is a guest
|
|
523
|
-
* @returns {number} Player ID
|
|
524
|
-
*/
|
|
525
|
-
getPlayerIDByUsername(username, guest = false) {
|
|
526
|
-
for (const player of this.players.values()) {
|
|
527
|
-
console.log(player)
|
|
528
|
-
if (player.username == username && player.guest == guest) {
|
|
529
|
-
return player.id;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
return -1;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
/**
|
|
537
|
-
* Get all players
|
|
538
|
-
* @returns {Array<Object>} Array of player objects
|
|
539
|
-
*/
|
|
540
|
-
getAllPlayers() {
|
|
541
|
-
const players = [];
|
|
542
|
-
|
|
543
|
-
for (const player of this.players.values()) {
|
|
544
|
-
players.push(player);
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
return players;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* Automatically handle a packet
|
|
554
|
-
* @param {Object} packet - Packet to handle
|
|
555
|
-
*/
|
|
556
|
-
async autoHandlePacket(packet) {
|
|
557
|
-
switch (packet.type) {
|
|
558
|
-
|
|
559
|
-
case 'ROOM_SHARE_LINK':
|
|
560
|
-
this.room.dbid = packet.roomId;
|
|
561
|
-
this.room.bypass = packet.roomBypass;
|
|
562
|
-
|
|
563
|
-
this.events.emit('ROOM_SHARE_LINK', { url: this.getShareLink() });
|
|
564
|
-
break;
|
|
565
|
-
|
|
566
|
-
case 'MAP_SWITCH':
|
|
567
|
-
this.room.map = packet.mapdata;
|
|
568
|
-
this.events.emit('MAP_SWITCH', { map: this.room.map });
|
|
569
|
-
break;
|
|
570
|
-
|
|
571
|
-
case 'MAP_SUGGEST':
|
|
572
|
-
const mapSuggestion = {
|
|
573
|
-
title: packet.maptitle,
|
|
574
|
-
author: packet.mapauthor,
|
|
575
|
-
player: this.players.get(packet.id)
|
|
576
|
-
}
|
|
577
|
-
this.events.emit('MAP_SUGGEST', mapSuggestion);
|
|
578
|
-
break;
|
|
579
|
-
|
|
580
|
-
case 'CHANGE_ROUNDS':
|
|
581
|
-
this.room.rounds = packet.rounds;
|
|
582
|
-
this.events.emit('CHANGE_ROUNDS', { rounds: packet.rounds });
|
|
583
|
-
break;
|
|
584
|
-
|
|
585
|
-
case 'COUNTDOWN':
|
|
586
|
-
this.room.countdown = packet.countdown;
|
|
587
|
-
this.events.emit('COUNTDOWN', { countdown: packet.countdown });
|
|
588
|
-
break;
|
|
589
|
-
|
|
590
|
-
case 'GAME_START':
|
|
591
|
-
// THIS NEEDS WORK
|
|
592
|
-
// COULD CAUSE DESYNC IF PLAYERS ARE FKING WITH THE STATE OBJ WITHOUT TELLING OTHERS
|
|
593
|
-
|
|
594
|
-
this.room.inGame = true;
|
|
595
|
-
this.room.roundStartTime = packet.timestamp;
|
|
596
|
-
this.room.state = packet.state;
|
|
597
|
-
|
|
598
|
-
this.room.map = packet.state.map;
|
|
599
|
-
this.room.gt = packet.state.gt;
|
|
600
|
-
this.room.rounds = packet.state.wl;
|
|
601
|
-
this.room.quickplay = packet.state.q;
|
|
602
|
-
this.room.teamsLocked = packet.state.tl;
|
|
603
|
-
this.room.teams = packet.state.tea;
|
|
604
|
-
this.room.engine = ENGINE_NAMES[packet.state.ga];
|
|
605
|
-
this.room.mode = GAMEMODE_NAMES[packet.state.mo];
|
|
606
|
-
|
|
607
|
-
// apply balances to all the players
|
|
608
|
-
// bal[playerid] = num
|
|
609
|
-
this.players.forEach((player) => {
|
|
610
|
-
player.balance = packet.state.bal[player.id] || 0;
|
|
611
|
-
this.players.set(player.id, player);
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
this.events.emit('GAME_START');
|
|
615
|
-
break;
|
|
616
|
-
|
|
617
|
-
case 'GAME_END':
|
|
618
|
-
this.room.inGame = false;
|
|
619
|
-
this.events.emit('GAME_END');
|
|
620
|
-
break;
|
|
621
|
-
|
|
622
|
-
case 'GAMEMODE_CHANGE':
|
|
623
|
-
this.room.mode = GAMEMODE_NAMES[packet.mode];
|
|
624
|
-
this.room.engine = ENGINE_NAMES[packet.engine];
|
|
625
|
-
|
|
626
|
-
this.events.emit('GAMEMODE_CHANGE', { mode: this.room.mode, engine: this.room.engine });
|
|
627
|
-
break;
|
|
628
|
-
|
|
629
|
-
case 'BALANCE_SET':
|
|
630
|
-
const playerBalance = this.players.get(packet.id);
|
|
631
|
-
|
|
632
|
-
if (playerBalance) {
|
|
633
|
-
playerBalance.balance = packet.balance;
|
|
634
|
-
this.players.set(packet.id, playerBalance);
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
this.events.emit('BALANCE_SET', { player: playerBalance, balance: packet.balance });
|
|
638
|
-
break;
|
|
639
|
-
|
|
640
|
-
case 'TIMESYNC':
|
|
641
|
-
this.timeSync.last_sync = packet.time;
|
|
642
|
-
this.timeSync.last_sync_id = packet.id;
|
|
643
|
-
this.timeSync.latency = Date.now() - this.timeSync.last_sync;
|
|
644
|
-
break;
|
|
645
|
-
|
|
646
|
-
case 'TEAM_CHANGE':
|
|
647
|
-
// Update player team
|
|
648
|
-
const playerTeam = this.players.get(packet.id);
|
|
649
|
-
|
|
650
|
-
if (playerTeam) {
|
|
651
|
-
playerTeam.team = packet.team;
|
|
652
|
-
playerTeam.teamName = TEAM_NAMES[packet.team];
|
|
653
|
-
this.players.set(packet.id, playerTeam);
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
// Emit team change event
|
|
657
|
-
this.events.emit('TEAM_CHANGE', { player: playerTeam, team: packet.team });
|
|
658
|
-
break;
|
|
659
|
-
|
|
660
|
-
case 'PLAYER_PINGS':
|
|
661
|
-
// Update player pings
|
|
662
|
-
// pings: { '0': 14 }
|
|
663
|
-
for (let [id, ping] of Object.entries(packet.pings)) {
|
|
664
|
-
id = parseInt(id)
|
|
665
|
-
const playerPing = this.players.get(id);
|
|
666
|
-
playerPing.ping = ping;
|
|
667
|
-
this.players.set(id, playerPing);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
await this.sendMessage(CLIENT_MESSAGE_TYPES.PING_RESPONSE, { id: packet.pingId })
|
|
671
|
-
break;
|
|
672
|
-
|
|
673
|
-
case 'CHAT_MESSAGE':
|
|
674
|
-
// Handle chat message
|
|
675
|
-
const player = this.players.get(packet.id);
|
|
676
|
-
|
|
677
|
-
this.events.emit('CHAT_MESSAGE', { player: player, message: packet.message });
|
|
678
|
-
break;
|
|
679
|
-
|
|
680
|
-
case 'JOIN_ROOM':
|
|
681
|
-
// Set game info
|
|
682
|
-
this.game.id = packet.myid;
|
|
683
|
-
this.game.host = packet.hostid;
|
|
684
|
-
// Set room info
|
|
685
|
-
this.room.id = packet.roomid;
|
|
686
|
-
this.room.bypass = packet.roombypass;
|
|
687
|
-
this.room.teamsLocked = packet.teamsLocked;
|
|
688
|
-
|
|
689
|
-
// Add players
|
|
690
|
-
if (packet.playerdata && Array.isArray(packet.playerdata)) {
|
|
691
|
-
for (let i = 0; i < packet.playerdata.length; i++) {
|
|
692
|
-
const playerData = packet.playerdata[i];
|
|
693
|
-
if (playerData) {
|
|
694
|
-
playerData.id = i;
|
|
695
|
-
|
|
696
|
-
playerData.username = playerData.userName;
|
|
697
|
-
delete playerData.userName;
|
|
698
|
-
|
|
699
|
-
playerData.xp = this.levelToXP(playerData.level)
|
|
700
|
-
playerData.ping = 0;
|
|
701
|
-
playerData.balance = 0;
|
|
702
|
-
playerData.movement = {
|
|
703
|
-
input: 0,
|
|
704
|
-
frame: 0,
|
|
705
|
-
sequence: 0
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
this.players.set(i, playerData);
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
packet.players = this.players;
|
|
714
|
-
delete packet.playerdata;
|
|
715
|
-
|
|
716
|
-
// Emit join event
|
|
717
|
-
this.events.emit('JOIN', { game: this.game, room: this.room, players: this.players });
|
|
718
|
-
break;
|
|
719
|
-
|
|
720
|
-
case 'INITIAL_DATA':
|
|
721
|
-
|
|
722
|
-
this.room.engine = ENGINE_NAMES[packet.ga];
|
|
723
|
-
this.room.mode = GAMEMODE_NAMES[packet.mo];
|
|
724
|
-
|
|
725
|
-
this.room.gt = packet.gt;
|
|
726
|
-
this.room.rounds = packet.wl;
|
|
727
|
-
this.room.quickplay = packet.q;
|
|
728
|
-
this.room.teamsLocked = packet.tl;
|
|
729
|
-
this.room.teams = packet.tea;
|
|
730
|
-
this.room.framecount = packet.fc;
|
|
731
|
-
this.room.stateID = packet.stateID;
|
|
732
|
-
this.room.admin = packet.admin;
|
|
733
|
-
this.room.map = packet.gs ? packet.gs.map : null;
|
|
734
|
-
this.room.state = packet.state;
|
|
735
|
-
this.room.random = packet.random;
|
|
736
|
-
|
|
737
|
-
// apply balances to all the players
|
|
738
|
-
// bal[playerid] = num
|
|
739
|
-
this.players.forEach((player) => {
|
|
740
|
-
player.balance = packet.bal[player.id] || 0;
|
|
741
|
-
this.players.set(player.id, player);
|
|
742
|
-
});
|
|
743
|
-
break;
|
|
744
|
-
|
|
745
|
-
case 'PLAYER_JOIN':
|
|
746
|
-
// Add player
|
|
747
|
-
this.players.set(packet.id, {
|
|
748
|
-
peerID: packet.peerID,
|
|
749
|
-
guest: packet.guest,
|
|
750
|
-
team: 1,
|
|
751
|
-
teamName: TEAM_NAMES[1],
|
|
752
|
-
level: parseInt(packet.level),
|
|
753
|
-
ready: false,
|
|
754
|
-
tabbed: false,
|
|
755
|
-
avatar: packet.avatar,
|
|
756
|
-
id: packet.id,
|
|
757
|
-
username: packet.username,
|
|
758
|
-
xp: this.levelToXP(packet.level),
|
|
759
|
-
ping: 0,
|
|
760
|
-
balance: 0,
|
|
761
|
-
host: false,
|
|
762
|
-
movement: {
|
|
763
|
-
input: 0,
|
|
764
|
-
frame: 0,
|
|
765
|
-
sequence: 0
|
|
766
|
-
}
|
|
767
|
-
});
|
|
768
|
-
|
|
769
|
-
// check if the bot is host
|
|
770
|
-
const botPlayer = this.players.get(this.game.id);
|
|
771
|
-
if (botPlayer.host) {
|
|
772
|
-
await this.sendMessage(CLIENT_MESSAGE_TYPES.INFORM_IN_LOBBY, {
|
|
773
|
-
sid: packet.id,
|
|
774
|
-
gs: {
|
|
775
|
-
map: this.room.map || {
|
|
776
|
-
v: 13,
|
|
777
|
-
s: {
|
|
778
|
-
re: false,
|
|
779
|
-
nc: false,
|
|
780
|
-
pq: 1,
|
|
781
|
-
gd: 25,
|
|
782
|
-
fl: false
|
|
783
|
-
},
|
|
784
|
-
physics: {
|
|
785
|
-
shapes: [],
|
|
786
|
-
fixtures: [],
|
|
787
|
-
bodies: [],
|
|
788
|
-
bro: [],
|
|
789
|
-
joints: [],
|
|
790
|
-
ppm: 12
|
|
791
|
-
},
|
|
792
|
-
spawns: [],
|
|
793
|
-
capZones: [],
|
|
794
|
-
m: {
|
|
795
|
-
a: "BonkBot",
|
|
796
|
-
n: "Empty Map",
|
|
797
|
-
dbv: 2,
|
|
798
|
-
dbid: 767645,
|
|
799
|
-
authid: -1,
|
|
800
|
-
date: "",
|
|
801
|
-
rxid: 0,
|
|
802
|
-
rxn: "",
|
|
803
|
-
rxa: "",
|
|
804
|
-
rxdb: 1,
|
|
805
|
-
cr: ["uint32"],
|
|
806
|
-
pub: true,
|
|
807
|
-
mo: ""
|
|
808
|
-
}
|
|
809
|
-
},
|
|
810
|
-
gt: this.room.gt || 2,
|
|
811
|
-
wl: this.room.rounds || 3,
|
|
812
|
-
q: this.room.quickplay || false,
|
|
813
|
-
tl: this.room.teamsLocked || false,
|
|
814
|
-
tea: this.room.teams || false,
|
|
815
|
-
ga: "b",
|
|
816
|
-
mo: "b",
|
|
817
|
-
bal: this.getAllPlayers().reduce((balances, player) => {
|
|
818
|
-
if (player.balance) {
|
|
819
|
-
balances[player.id] = player.balance;
|
|
820
|
-
}
|
|
821
|
-
return balances;
|
|
822
|
-
}, {})
|
|
823
|
-
}
|
|
824
|
-
});
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
// Emit join event
|
|
828
|
-
this.events.emit('PLAYER_JOIN', { player: this.players.get(packet.id), id: packet.id });
|
|
829
|
-
break;
|
|
830
|
-
|
|
831
|
-
case 'PLAYER_LEAVE':
|
|
832
|
-
const deletedPlayer = this.players.get(packet.id);
|
|
833
|
-
|
|
834
|
-
// delete player
|
|
835
|
-
this.players.delete(packet.id);
|
|
836
|
-
|
|
837
|
-
// Emit leave event
|
|
838
|
-
this.events.emit('PLAYER_LEAVE', { player: deletedPlayer, id: packet.id });
|
|
839
|
-
break;
|
|
840
|
-
|
|
841
|
-
case 'HOST_TRANSFER':
|
|
842
|
-
// Update host
|
|
843
|
-
this.game.host = packet.newHost;
|
|
844
|
-
|
|
845
|
-
// find the player that is now host
|
|
846
|
-
this.players.forEach((player) => {
|
|
847
|
-
if (player.id === packet.newHost) {
|
|
848
|
-
player.host = true;
|
|
849
|
-
} else {
|
|
850
|
-
player.host = false;
|
|
851
|
-
}
|
|
852
|
-
this.players.set(player.id, player);
|
|
853
|
-
});
|
|
854
|
-
|
|
855
|
-
// Emit host transfer event
|
|
856
|
-
this.events.emit('HOST_TRANSFER', { oldHost: packet.oldHost, newHost: packet.newHost });
|
|
857
|
-
break;
|
|
858
|
-
|
|
859
|
-
case 'READY_CHANGE':
|
|
860
|
-
// Update player ready status
|
|
861
|
-
const playerReady = this.players.get(packet.id);
|
|
862
|
-
|
|
863
|
-
if (playerReady) {
|
|
864
|
-
playerReady.ready = packet.ready;
|
|
865
|
-
|
|
866
|
-
this.players.set(packet.id, playerReady);
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
// Emit ready change event
|
|
870
|
-
this.events.emit('READY_CHANGE', { player: playerReady, ready: packet.ready });
|
|
871
|
-
break;
|
|
872
|
-
|
|
873
|
-
case 'TEAMLOCK_TOGGLE':
|
|
874
|
-
// Update teams lock
|
|
875
|
-
this.room.teamsLocked = packet.teamsLocked;
|
|
876
|
-
|
|
877
|
-
// Emit teams lock event
|
|
878
|
-
this.events.emit('TEAMLOCK_TOGGLE', { teamsLocked: packet.teamsLocked });
|
|
879
|
-
break;
|
|
880
|
-
|
|
881
|
-
case 'PLAYER_TABBED':
|
|
882
|
-
// Update player tabbed status
|
|
883
|
-
const playerTabbed = this.players.get(packet.id);
|
|
884
|
-
|
|
885
|
-
if (playerTabbed) {
|
|
886
|
-
playerTabbed.tabbed = packet.tabbed;
|
|
887
|
-
|
|
888
|
-
this.players.set(packet.id, playerTabbed);
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
// Emit tabbed event
|
|
892
|
-
this.events.emit('PLAYER_TABBED', { player: playerTabbed, tabbed: packet.tabbed });
|
|
893
|
-
break;
|
|
894
|
-
|
|
895
|
-
case 'ROOM_NAME_UPDATE':
|
|
896
|
-
// Update room name
|
|
897
|
-
this.room.name = packet.name;
|
|
898
|
-
|
|
899
|
-
// Emit room name change event
|
|
900
|
-
this.events.emit('ROOM_NAME_UPDATE', { name: packet.name });
|
|
901
|
-
break;
|
|
902
|
-
|
|
903
|
-
case 'ROOM_ADDRESS':
|
|
904
|
-
// Update room address
|
|
905
|
-
this.room.address = packet.address;
|
|
906
|
-
|
|
907
|
-
// Emit room address event
|
|
908
|
-
this.events.emit('ROOM_ADDRESS', { address: packet.address });
|
|
909
|
-
break;
|
|
910
|
-
|
|
911
|
-
case 'PLAYER_KICK':
|
|
912
|
-
// Check if we were kicked
|
|
913
|
-
if (packet.id === this.game.id) {
|
|
914
|
-
this.game.banned = true; // might not act be banned
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
// Remove player
|
|
918
|
-
const kickedPlayer = this.players.get(packet.id);
|
|
919
|
-
this.players.delete(packet.id);
|
|
920
|
-
|
|
921
|
-
// Emit kick event
|
|
922
|
-
this.events.emit('PLAYER_KICK', kickedPlayer);
|
|
923
|
-
break;
|
|
924
|
-
|
|
925
|
-
case 'PLAYER_INPUT':
|
|
926
|
-
// Handle player input
|
|
927
|
-
const playerInput = this.players.get(packet.id);
|
|
928
|
-
|
|
929
|
-
if (playerInput) {
|
|
930
|
-
playerInput.movement = {
|
|
931
|
-
input: packet.input,
|
|
932
|
-
frame: packet.frame,
|
|
933
|
-
sequence: packet.sequence
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
this.players.set(packet.id, playerInput);
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
// Emit player input event
|
|
940
|
-
this.events.emit('PLAYER_INPUT', { player: playerInput, movement: playerInput.movement });
|
|
941
|
-
break;
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
default:
|
|
945
|
-
logger.debug(`No handler for packet type: ${packet.type}. Tell the developer about this for a fix!\nhttps://github.com/PixelMelt/BonkBot`, packet);
|
|
946
|
-
break;
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
/**
|
|
951
|
-
* Validate account information
|
|
952
|
-
* @private
|
|
953
|
-
* @param {Object} account - Account information
|
|
954
|
-
* @returns {Object} Validated account information
|
|
955
|
-
*/
|
|
956
|
-
validateAccount(account) {
|
|
957
|
-
// Default to guest account
|
|
958
|
-
const validatedAccount = {
|
|
959
|
-
guest: true,
|
|
960
|
-
username: `BonkBot${Math.floor(Math.random() * 10000)}`
|
|
961
|
-
};
|
|
962
|
-
|
|
963
|
-
// Override with provided values
|
|
964
|
-
if (account.guest !== undefined) {
|
|
965
|
-
validatedAccount.guest = !!account.guest;
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
if (account.username) {
|
|
969
|
-
validatedAccount.username = account.username;
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
if (account.password) {
|
|
973
|
-
validatedAccount.password = account.password;
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
// Non-guest accounts must have a password
|
|
977
|
-
if (!validatedAccount.guest && !validatedAccount.password) {
|
|
978
|
-
logger.warn('Non-guest account must have a password, defaulting to guest');
|
|
979
|
-
validatedAccount.guest = true;
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
return validatedAccount;
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
/**
|
|
986
|
-
* Generate a random peer ID
|
|
987
|
-
* @private
|
|
988
|
-
* @returns {string} Random peer ID
|
|
989
|
-
*/
|
|
990
|
-
generatePeerId() {
|
|
991
|
-
return Math.random().toString(36).substr(2, 10) + 'v00000';
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
/**
|
|
995
|
-
* Get authentication token
|
|
996
|
-
* @private
|
|
997
|
-
* @param {string} username - Username
|
|
998
|
-
* @param {string} password - Password
|
|
999
|
-
* @returns {Promise<string>} Authentication token
|
|
1000
|
-
*/
|
|
1001
|
-
async getToken(username, password) {
|
|
1002
|
-
try {
|
|
1003
|
-
logger.info(`Getting token for user: ${username}`);
|
|
1004
|
-
|
|
1005
|
-
const response = await axios.post(API.LOGIN,
|
|
1006
|
-
`username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}&remember=true`, {
|
|
1007
|
-
headers: {
|
|
1008
|
-
'Content-Type': 'application/x-www-form-urlencoded'
|
|
1009
|
-
},
|
|
1010
|
-
httpsAgent: httpsAgent
|
|
1011
|
-
}
|
|
1012
|
-
);
|
|
1013
|
-
|
|
1014
|
-
if (!response.data || !response.data.token) {
|
|
1015
|
-
throw new Error('Failed to get authentication token');
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
logger.info('Successfully obtained authentication token');
|
|
1019
|
-
return response.data.token;
|
|
1020
|
-
} catch (error) {
|
|
1021
|
-
logger.error('Failed to authenticate', error);
|
|
1022
|
-
throw new Error(`Failed to authenticate: ${error.message}`);
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
/**
|
|
1027
|
-
* Get a room address from a room name
|
|
1028
|
-
* @param {string} roomName - Room name
|
|
1029
|
-
* @returns {Promise<Object>} Room address information
|
|
1030
|
-
*/
|
|
1031
|
-
async getAddressFromRoomName(roomName) {
|
|
1032
|
-
logger.info(`Getting address for room: ${roomName}`);
|
|
1033
|
-
|
|
1034
|
-
try {
|
|
1035
|
-
// Find room by name
|
|
1036
|
-
const room = await this.getRoomByName(roomName, this.token);
|
|
1037
|
-
|
|
1038
|
-
if (!room) {
|
|
1039
|
-
throw new Error(`Room not found: ${roomName}`);
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
// Get room address
|
|
1043
|
-
const address = await this.getRoomAddress(room.id);
|
|
1044
|
-
|
|
1045
|
-
const result = {
|
|
1046
|
-
roomname: room.roomname,
|
|
1047
|
-
address: address.address,
|
|
1048
|
-
server: address.server,
|
|
1049
|
-
bypass: ''
|
|
1050
|
-
};
|
|
1051
|
-
|
|
1052
|
-
logger.info(`Got address for room: ${roomName}`);
|
|
1053
|
-
|
|
1054
|
-
return result;
|
|
1055
|
-
} catch (error) {
|
|
1056
|
-
logger.error(`Failed to get address for room: ${roomName}`, error);
|
|
1057
|
-
throw error;
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
/**
|
|
1062
|
-
* Get server information
|
|
1063
|
-
* @private
|
|
1064
|
-
* @param {string} [token] - Authentication token
|
|
1065
|
-
* @returns {Promise<Object>} Server information
|
|
1066
|
-
*/
|
|
1067
|
-
async getServerInfo() {
|
|
1068
|
-
try {
|
|
1069
|
-
const response = await axios.post(API.GET_ROOMS,
|
|
1070
|
-
`version=${this.PROTOCOL_VERSION}&gl=y&token=${this.token ?? ""}`, {
|
|
1071
|
-
headers: {
|
|
1072
|
-
'Content-Type': 'application/x-www-form-urlencoded'
|
|
1073
|
-
},
|
|
1074
|
-
httpsAgent: httpsAgent
|
|
1075
|
-
}
|
|
1076
|
-
);
|
|
1077
|
-
|
|
1078
|
-
if (!response.data || !response.data.createserver) {
|
|
1079
|
-
throw new Error('Failed to get server information');
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
return {
|
|
1083
|
-
server: response.data.createserver,
|
|
1084
|
-
lat: response.data.lat,
|
|
1085
|
-
long: response.data.long,
|
|
1086
|
-
country: response.data.country
|
|
1087
|
-
};
|
|
1088
|
-
} catch (error) {
|
|
1089
|
-
logger.error(error);
|
|
1090
|
-
throw new Error(`Failed to get server information: ${error.message}`);
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
/**
|
|
1095
|
-
* Get list of rooms
|
|
1096
|
-
* @private
|
|
1097
|
-
* @returns {Promise<Array>} List of rooms
|
|
1098
|
-
*/
|
|
1099
|
-
async getRooms() {
|
|
1100
|
-
try {
|
|
1101
|
-
logger.info('Getting list of rooms');
|
|
1102
|
-
|
|
1103
|
-
const response = await axios.post(API.GET_ROOMS,
|
|
1104
|
-
`version=${this.PROTOCOL_VERSION}&gl=y&token=${this.token || ""}`, {
|
|
1105
|
-
headers: {
|
|
1106
|
-
'Content-Type': 'application/x-www-form-urlencoded'
|
|
1107
|
-
},
|
|
1108
|
-
httpsAgent: httpsAgent
|
|
1109
|
-
}
|
|
1110
|
-
);
|
|
1111
|
-
|
|
1112
|
-
if (!response.data || !response.data.rooms) {
|
|
1113
|
-
throw new Error('Failed to get rooms');
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
logger.info(`Retrieved ${response.data.rooms.length} rooms`);
|
|
1117
|
-
return response.data.rooms;
|
|
1118
|
-
} catch (error) {
|
|
1119
|
-
logger.error('Failed to get rooms', error);
|
|
1120
|
-
throw new Error(`Failed to get rooms: ${error.message}`);
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
/**
|
|
1125
|
-
* Change game mode
|
|
1126
|
-
* @param {string} mode - Game mode ('b' = classic, 'ar' = arrows, 'ard' = death arrows, 'sp' = grapple, 'v' = vtol, 'f' = football)
|
|
1127
|
-
* @param {string} engine - Game engine ('b' = bonk, 'f' = football)
|
|
1128
|
-
*/
|
|
1129
|
-
async setGameMode(mode, engine = 'b') {
|
|
1130
|
-
this.checkConnection();
|
|
1131
|
-
|
|
1132
|
-
// Validar modo
|
|
1133
|
-
const validModes = ['b', 'ar', 'ard', 'sp', 'v', 'f'];
|
|
1134
|
-
const validEngines = ['b', 'f'];
|
|
1135
|
-
|
|
1136
|
-
if (!validModes.includes(mode)) {
|
|
1137
|
-
throw new Error(`Invalid mode. Valid modes: ${validModes.join(', ')}`);
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
if (!validEngines.includes(engine)) {
|
|
1141
|
-
throw new Error(`Invalid engine. Valid engines: ${validEngines.join(', ')}`);
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
await this.sendMessage(CLIENT_MESSAGE_TYPES.SEND_MODE, {
|
|
1145
|
-
ga: engine,
|
|
1146
|
-
mo: mode
|
|
1147
|
-
});
|
|
1148
|
-
|
|
1149
|
-
logger.info(`Game mode changed to: ${GAMEMODE_NAMES[mode]} (${ENGINE_NAMES[engine]})`);
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
/**
|
|
1153
|
-
* Set Football mode
|
|
1154
|
-
*/
|
|
1155
|
-
async setFootballMode() {
|
|
1156
|
-
await this.setGameMode('f', 'f');
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
/**
|
|
1160
|
-
* Set Classic mode
|
|
1161
|
-
*/
|
|
1162
|
-
async setClassicMode() {
|
|
1163
|
-
await this.setGameMode('b', 'b');
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
/**
|
|
1167
|
-
* Set Arrows mode
|
|
1168
|
-
*/
|
|
1169
|
-
async setArrowsMode() {
|
|
1170
|
-
await this.setGameMode('ar', 'b');
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
/**
|
|
1174
|
-
* Set Death Arrows mode
|
|
1175
|
-
*/
|
|
1176
|
-
async setDeathArrowsMode() {
|
|
1177
|
-
await this.setGameMode('ard', 'b');
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
/**
|
|
1181
|
-
* Set Grapple mode
|
|
1182
|
-
*/
|
|
1183
|
-
async setGrappleMode() {
|
|
1184
|
-
await this.setGameMode('sp', 'b');
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
/**
|
|
1188
|
-
* Set VTOL mode
|
|
1189
|
-
*/
|
|
1190
|
-
async setVTOLMode() {
|
|
1191
|
-
await this.setGameMode('v', 'b');
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
/**
|
|
1195
|
-
* Get room by name
|
|
1196
|
-
* @private
|
|
1197
|
-
* @param {string} roomName - Room name
|
|
1198
|
-
* @returns {Promise<Object|null>} Room object or null if not found
|
|
1199
|
-
*/
|
|
1200
|
-
async getRoomByName(roomName) {
|
|
1201
|
-
try {
|
|
1202
|
-
logger.info(`Searching for room: ${roomName}`);
|
|
1203
|
-
|
|
1204
|
-
const rooms = await this.getRooms(this.token);
|
|
1205
|
-
|
|
1206
|
-
for (const room of rooms) {
|
|
1207
|
-
if (room.roomname === roomName) {
|
|
1208
|
-
logger.info(`Found room: ${roomName}`);
|
|
1209
|
-
return room;
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
logger.info(`Room not found: ${roomName}`);
|
|
1214
|
-
return null;
|
|
1215
|
-
} catch (error) {
|
|
1216
|
-
logger.error(`Failed to find room: ${roomName}`, error);
|
|
1217
|
-
throw new Error(`Failed to find room: ${error.message}`);
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
/**
|
|
1222
|
-
* Get room address
|
|
1223
|
-
* @private
|
|
1224
|
-
* @param {string} id - Room ID
|
|
1225
|
-
* @returns {Promise<Object>} Room address
|
|
1226
|
-
*/
|
|
1227
|
-
async getRoomAddress(id) {
|
|
1228
|
-
try {
|
|
1229
|
-
logger.info(`Getting address for room ID: ${id}`);
|
|
1230
|
-
|
|
1231
|
-
const response = await axios.post(API.GET_ROOM_ADDRESS,
|
|
1232
|
-
`id=${id}`, {
|
|
1233
|
-
headers: {
|
|
1234
|
-
'Content-Type': 'application/x-www-form-urlencoded'
|
|
1235
|
-
},
|
|
1236
|
-
httpsAgent: httpsAgent
|
|
1237
|
-
}
|
|
1238
|
-
);
|
|
1239
|
-
|
|
1240
|
-
if (!response.data || !response.data.address) {
|
|
1241
|
-
throw new Error('Failed to get room address');
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
logger.info(`Retrieved address for room ID: ${id}`);
|
|
1245
|
-
return response.data;
|
|
1246
|
-
} catch (error) {
|
|
1247
|
-
logger.error(`Failed to get address for room ID: ${id}`, error);
|
|
1248
|
-
throw new Error(`Failed to get room address: ${error.message}`);
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
/**
|
|
1253
|
-
* Set up socket event handlers
|
|
1254
|
-
* @private
|
|
1255
|
-
*/
|
|
1256
|
-
setupSocketEvents() {
|
|
1257
|
-
// Register listeners for all message types we care about
|
|
1258
|
-
Object.values(SERVER_MESSAGE_TYPES).forEach(eventId => {
|
|
1259
|
-
this.socket.on(eventId, (...args) => {
|
|
1260
|
-
// Create a packet array with event ID as first element and args as the rest
|
|
1261
|
-
const packet = [eventId, ...args];
|
|
1262
|
-
|
|
1263
|
-
// Process the packet
|
|
1264
|
-
const parsedPacket = parsePacket(packet);
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
// Emit events
|
|
1268
|
-
this.events.emit('PACKET', parsedPacket);
|
|
1269
|
-
|
|
1270
|
-
// logger.debug(`Received event ${eventId}`, parsedPacket);
|
|
1271
|
-
});
|
|
1272
|
-
});
|
|
1273
|
-
|
|
1274
|
-
// Handle standard Socket.IO events
|
|
1275
|
-
this.socket.on('connect', () => {
|
|
1276
|
-
this.events.emit('connect');
|
|
1277
|
-
});
|
|
1278
|
-
|
|
1279
|
-
this.socket.on('disconnect', (reason) => {
|
|
1280
|
-
this.events.emit('disconnect', reason);
|
|
1281
|
-
});
|
|
1282
|
-
|
|
1283
|
-
this.socket.on('error', (error) => {
|
|
1284
|
-
this.events.emit('error', error);
|
|
1285
|
-
});
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
/**
|
|
1290
|
-
* Send a message to the server using Socket.IO v2 protocol
|
|
1291
|
-
* @private
|
|
1292
|
-
* @param {number} eventId - Event ID from CLIENT_MESSAGE_TYPES
|
|
1293
|
-
* @param {any} data - Message data
|
|
1294
|
-
*/
|
|
1295
|
-
async sendMessage(eventId, data) {
|
|
1296
|
-
this.checkConnection()
|
|
1297
|
-
|
|
1298
|
-
// if(eventId != 18 && eventId != 1){ // just for pix to not get annoyed by things
|
|
1299
|
-
// }
|
|
1300
|
-
logger.debug(`Sending message type ${eventId}`, data);
|
|
1301
|
-
|
|
1302
|
-
// For Socket.IO v2, we need to emit to the 'message' event with the event ID and data
|
|
1303
|
-
// The server expects a message in the format: [eventId, data]
|
|
1304
|
-
await this.socket.emit(eventId, data);
|
|
1305
|
-
return true
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
stopBot() {
|
|
1309
|
-
this.connected = false;
|
|
1310
|
-
|
|
1311
|
-
if (this.keepAliveTimer) {
|
|
1312
|
-
clearInterval(this.keepAliveTimer);
|
|
1313
|
-
this.keepAliveTimer = null;
|
|
1314
|
-
}
|
|
1315
|
-
return true;
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
getShareLink(){
|
|
1319
|
-
return "https://bonk.io/" + this.room.dbid + this.room.bypass;
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
async chat(message) {
|
|
1324
|
-
this.checkConnection();
|
|
1325
|
-
await this.sendMessage(CLIENT_MESSAGE_TYPES.CHAT_MESSAGE, {
|
|
1326
|
-
message
|
|
1327
|
-
});
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
/**
|
|
1331
|
-
* Set player ready status
|
|
1332
|
-
* @param {boolean} ready - Ready status
|
|
1333
|
-
*/
|
|
1334
|
-
async ready(ready) {
|
|
1335
|
-
this.checkConnection();
|
|
1336
|
-
await this.sendMessage(CLIENT_MESSAGE_TYPES.SET_READY, {
|
|
1337
|
-
ready
|
|
1338
|
-
});
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
/**
|
|
1342
|
-
* Join a team
|
|
1343
|
-
* @param {number} team - Team to join
|
|
1344
|
-
*/
|
|
1345
|
-
async joinTeam(team) {
|
|
1346
|
-
this.checkConnection();
|
|
1347
|
-
await this.sendMessage(CLIENT_MESSAGE_TYPES.CHANGE_OWN_TEAM, {
|
|
1348
|
-
targetTeam: team
|
|
1349
|
-
});
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
/**
|
|
1353
|
-
* Toggle teams lock
|
|
1354
|
-
* @param {boolean} locked - Whether teams are locked
|
|
1355
|
-
*/
|
|
1356
|
-
async toggleTeams(locked) {
|
|
1357
|
-
this.checkConnection();
|
|
1358
|
-
await this.sendMessage(CLIENT_MESSAGE_TYPES.TEAM_LOCK, {
|
|
1359
|
-
teamLock: locked
|
|
1360
|
-
});
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
/**
|
|
1364
|
-
* Ban a player
|
|
1365
|
-
* @param {string|number} playerId - Player ID to ban
|
|
1366
|
-
*/
|
|
1367
|
-
async banPlayer(playerId) {
|
|
1368
|
-
this.checkConnection();
|
|
1369
|
-
await this.sendMessage(CLIENT_MESSAGE_TYPES.KICK_BAN_PLAYER, {
|
|
1370
|
-
banshortid: playerId
|
|
1371
|
-
});
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
/**
|
|
1375
|
-
* Leave the game
|
|
1376
|
-
*/
|
|
1377
|
-
async leaveGame() {
|
|
1378
|
-
this.checkConnection();
|
|
1379
|
-
await this.sendMessage(CLIENT_MESSAGE_TYPES.RETURN_TO_LOBBY);
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
/**
|
|
1383
|
-
* Give host to another player
|
|
1384
|
-
* @param {string|number} playerId - Player ID to give host to
|
|
1385
|
-
*/
|
|
1386
|
-
async giveHost(playerId) {
|
|
1387
|
-
this.checkConnection();
|
|
1388
|
-
await this.sendMessage(CLIENT_MESSAGE_TYPES.SEND_HOST_CHANGE, {
|
|
1389
|
-
id: playerId
|
|
1390
|
-
});
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
/**
|
|
1394
|
-
* Get the host player
|
|
1395
|
-
* @returns {Object} Host player object
|
|
1396
|
-
*/
|
|
1397
|
-
getHost(){
|
|
1398
|
-
return this.players.get(this.game.host);
|
|
1399
|
-
}
|
|
1400
|
-
|
|
1401
|
-
/**
|
|
1402
|
-
* Set number of rounds
|
|
1403
|
-
* @param {number} rounds - Number of rounds
|
|
1404
|
-
*/
|
|
1405
|
-
async setRounds(rounds) {
|
|
1406
|
-
this.checkConnection();
|
|
1407
|
-
await this.sendMessage(CLIENT_MESSAGE_TYPES.SEND_ROUNDS, {
|
|
1408
|
-
w: rounds
|
|
1409
|
-
});
|
|
1410
|
-
}
|
|
1411
|
-
|
|
1412
|
-
/**
|
|
1413
|
-
* Send an input to the game
|
|
1414
|
-
* @param {Object} input - Input data
|
|
1415
|
-
*/
|
|
1416
|
-
async sendInput(input) {
|
|
1417
|
-
this.checkConnection();
|
|
1418
|
-
await this.sendMessage(CLIENT_MESSAGE_TYPES.SEND_INPUTS, {
|
|
1419
|
-
i: input.input,
|
|
1420
|
-
f: input.frame,
|
|
1421
|
-
c: input.sequence
|
|
1422
|
-
});
|
|
1423
|
-
}
|
|
1424
|
-
|
|
1425
|
-
/**
|
|
1426
|
-
* Send a timesync message to the server
|
|
1427
|
-
* @private
|
|
1428
|
-
*/
|
|
1429
|
-
async sendTimesync() {
|
|
1430
|
-
await this.sendMessage(CLIENT_MESSAGE_TYPES.TIMESYNC, {
|
|
1431
|
-
jsonrpc: "2.0",
|
|
1432
|
-
id: this.timeSync.count++,
|
|
1433
|
-
method: "timesync"
|
|
1434
|
-
});
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
/**
|
|
1438
|
-
* Check if connected to server
|
|
1439
|
-
* @private
|
|
1440
|
-
* @throws {Error} If not connected
|
|
1441
|
-
*/
|
|
1442
|
-
checkConnection() {
|
|
1443
|
-
if (!this.connected || !this.socket) {
|
|
1444
|
-
throw new Error('Not connected to server');
|
|
1445
|
-
}
|
|
1446
|
-
return true;
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
/**
|
|
1450
|
-
* Converts level to XP total
|
|
1451
|
-
* @param {Number} level - The level to convert
|
|
1452
|
-
* @returns {Number} The total XP required for this level
|
|
1453
|
-
*/
|
|
1454
|
-
levelToXP(level) {
|
|
1455
|
-
if (level < 1) return 0;
|
|
1456
|
-
return 100 * Math.pow(level - 1, 2);
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
|
-
/**
|
|
1460
|
-
* Converts XP total to level
|
|
1461
|
-
* @param {Number} xp - The XP amount to convert
|
|
1462
|
-
* @returns {Number} The level for this XP amount
|
|
1463
|
-
*/
|
|
1464
|
-
xpToLevel(xp) {
|
|
1465
|
-
if (xp < 0) return 1;
|
|
1466
|
-
return Math.floor(Math.sqrt(xp / 100) + 1);
|
|
1467
|
-
}
|
|
1468
|
-
}
|
|
1469
|
-
|
|
1470
|
-
module.exports = BonkBot;
|