yolkbot 0.1.0-alpha.5 → 0.1.0-alpha.50

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.
Files changed (60) hide show
  1. package/build/browser.js +6 -6
  2. package/package.json +27 -8
  3. package/src/api.js +44 -23
  4. package/src/bot/GamePlayer.js +3 -3
  5. package/src/bot.js +758 -391
  6. package/src/constants/index.js +23 -1
  7. package/src/constants/items.js +372 -173
  8. package/src/constants/maps.js +51 -12
  9. package/src/dispatches/BootPlayerDispatch.js +1 -1
  10. package/src/dispatches/ChatDispatch.js +1 -1
  11. package/src/dispatches/GameOptionsDispatch.js +1 -1
  12. package/src/dispatches/GoToAmmoDispatch.js +44 -0
  13. package/src/dispatches/GoToCoopDispatch.js +44 -0
  14. package/src/dispatches/GoToGrenadeDispatch.js +44 -0
  15. package/src/dispatches/GoToPlayerDispatch.js +5 -1
  16. package/src/dispatches/GoToSpatulaDispatch.js +5 -1
  17. package/src/dispatches/MeleeDispatch.js +1 -1
  18. package/src/dispatches/PauseDispatch.js +1 -1
  19. package/src/dispatches/ReloadDispatch.js +1 -1
  20. package/src/dispatches/ReportPlayerDispatch.js +1 -1
  21. package/src/dispatches/SaveLoadoutDispatch.js +11 -34
  22. package/src/dispatches/SpawnDispatch.js +1 -1
  23. package/src/dispatches/SwapWeaponDispatch.js +1 -1
  24. package/src/dispatches/SwitchTeamDispatch.js +1 -1
  25. package/src/dispatches/ThrowGrenadeDispatch.js +1 -1
  26. package/src/dispatches/index.js +14 -2
  27. package/src/matchmaker.js +11 -2
  28. package/src/pathing/mapnode.js +33 -4
  29. package/src/socket.js +1 -1
  30. package/src/types/api.d.ts +1 -16
  31. package/src/types/bot/GamePlayer.d.ts +87 -0
  32. package/src/types/bot.d.ts +117 -35
  33. package/src/types/constants/guns.d.ts +240 -0
  34. package/src/types/constants/index.d.ts +100 -0
  35. package/src/types/constants/items.d.ts +21 -0
  36. package/src/types/constants/maps.d.ts +15 -0
  37. package/src/types/dispatches/BootPlayerDispatch.d.ts +12 -0
  38. package/src/types/dispatches/ChatDispatch.d.ts +10 -0
  39. package/src/types/dispatches/FireDispatch.d.ts +10 -0
  40. package/src/types/dispatches/GameOptionsDispatch.d.ts +8 -0
  41. package/src/types/dispatches/GoToAmmoDispatch.d.ts +8 -0
  42. package/src/types/dispatches/GoToCoopDispatch.d.ts +8 -0
  43. package/src/types/dispatches/GoToGrenadeDispatch.d.ts +8 -0
  44. package/src/types/dispatches/GoToPlayerDispatch.d.ts +11 -0
  45. package/src/types/dispatches/GoToSpatulaDispatch.d.ts +8 -0
  46. package/src/types/dispatches/LookAtDispatch.d.ts +14 -0
  47. package/src/types/dispatches/LookAtPosDispatch.d.ts +19 -0
  48. package/src/types/dispatches/MeleeDispatch.d.ts +8 -0
  49. package/src/types/dispatches/MovementDispatch.d.ts +12 -0
  50. package/src/types/dispatches/PauseDispatch.d.ts +8 -0
  51. package/src/types/dispatches/ReloadDispatch.d.ts +8 -0
  52. package/src/types/dispatches/ReportPlayerDispatch.d.ts +22 -0
  53. package/src/types/dispatches/SaveLoadoutDispatch.d.ts +34 -0
  54. package/src/types/dispatches/SpawnDispatch.d.ts +8 -0
  55. package/src/types/dispatches/SwapWeaponDispatch.d.ts +8 -0
  56. package/src/types/dispatches/SwitchTeamDispatch.d.ts +8 -0
  57. package/src/types/dispatches/ThrowGrenadeDispatch.d.ts +10 -0
  58. package/src/types/dispatches/index.d.ts +174 -0
  59. package/src/types/matchmaker.d.ts +19 -14
  60. package/src/types/socket.d.ts +7 -0
package/src/bot.js CHANGED
@@ -1,4 +1,4 @@
1
- import { loginAnonymously, loginWithCredentials } from '#api';
1
+ import { createAccount, loginAnonymously, loginWithCredentials, queryServices } from '#api';
2
2
 
3
3
  import CommIn from './comm/CommIn.js';
4
4
  import CommOut from './comm/CommOut.js';
@@ -9,6 +9,7 @@ import Matchmaker from './matchmaker.js';
9
9
  import yolkws from './socket.js';
10
10
 
11
11
  import {
12
+ ChiknWinnerDailyLimit,
12
13
  CollectTypes,
13
14
  CoopStates,
14
15
  findItemById,
@@ -17,6 +18,7 @@ import {
17
18
  GameOptionFlags,
18
19
  GunList,
19
20
  IsBrowser,
21
+ ItemTypes,
20
22
  Movements,
21
23
  PlayTypes,
22
24
  ShellStreaks
@@ -32,30 +34,30 @@ import { Maps } from './constants/maps.js';
32
34
  const CoopStagesById = Object.fromEntries(Object.entries(CoopStates).map(([key, value]) => [value, key]));
33
35
  const GameModesById = Object.fromEntries(Object.entries(GameModes).map(([key, value]) => [value, key]));
34
36
 
37
+ const intents = {
38
+ CHALLENGES: 1,
39
+ STATS: 2,
40
+ PATHFINDING: 3,
41
+ BUFFERS: 4,
42
+ PING: 5,
43
+ COSMETIC_DATA: 6
44
+ }
45
+
35
46
  export class Bot {
36
- // params.name - the bot name
37
- // params.proxy - a socks(4|5) proxy
38
- // params.doUpdate - whether to auto update
39
- // params.updateInterval - the auto update interval
40
- // params.doPing - whether to auto ping (for bot.<ping>)
41
- // params.pingInterval - the ping interval
42
- // params.doPathing - whether to run pathfinding logic
43
- // params.instance - a custom shell URL to run requests through
47
+ static Intents = intents;
48
+ Intents = intents;
49
+
44
50
  constructor(params = {}) {
45
51
  if (params.proxy && IsBrowser)
46
52
  throw new Error('proxies do not work and hence are not supported in the browser');
47
53
 
54
+ this.intents = params.intents || [];
55
+
56
+ this.instance = params.instance || 'shellshock.io';
48
57
  this.proxy = params.proxy || '';
49
- this.name = params.name || Math.random().toString(36).substring(8);
50
58
 
51
- this.autoPing = params.doPing || true;
52
59
  this.autoUpdate = params.doUpdate || true;
53
- this.disablePathing = !params.doPathing || true;
54
-
55
- this.pingInterval = params.pingInterval || 1000;
56
- this.updateInterval = params.updateInterval || 5;
57
-
58
- this.instance = params.instance || 'shellshock.io';
60
+ this.updateInterval = params.updateInterval || 11;
59
61
 
60
62
  this._hooks = {};
61
63
  this._globalHooks = [];
@@ -63,10 +65,10 @@ export class Bot {
63
65
 
64
66
  // private information NOT FOR OTHER PLAYERS!!
65
67
  this.state = {
66
- loggedIn: false,
67
- gameFound: false,
68
+ // kept for specifying socket open sequence
69
+ name: '',
68
70
 
69
- // once we implement more packets these may be moved to "players"
71
+ // tracking for dispatch checks
70
72
  reloading: false,
71
73
  swappingGun: false,
72
74
  usingMelee: false,
@@ -81,6 +83,7 @@ export class Bot {
81
83
  this.game = {
82
84
  raw: {}, // matchmaker response
83
85
  code: '',
86
+ socket: null,
84
87
 
85
88
  // data given on sign in
86
89
  gameModeId: 0, // assume ffa
@@ -98,8 +101,10 @@ export class Bot {
98
101
  },
99
102
  availability: 'both',
100
103
  numPlayers: '18',
104
+
101
105
  raw: {},
102
- nodes: {}
106
+ nodes: {},
107
+ zones: []
103
108
  },
104
109
  playerLimit: 0,
105
110
  isGameOwner: false,
@@ -135,7 +140,8 @@ export class Bot {
135
140
 
136
141
  // data from kotc
137
142
  stage: CoopStates.capturing,
138
- activeZone: 0,
143
+ zoneNumber: 0,
144
+ activeZone: [],
139
145
  capturing: 0,
140
146
  captureProgress: 0,
141
147
  numCapturing: 0,
@@ -145,10 +151,23 @@ export class Bot {
145
151
 
146
152
  this.account = {
147
153
  // used for auth
154
+ id: 0,
148
155
  firebaseId: '',
149
156
  sessionId: '',
150
157
  session: '',
151
158
 
159
+ // raw login params
160
+ email: '',
161
+ password: '',
162
+
163
+ // chikn winner related info
164
+ cw: {
165
+ atLimit: false,
166
+ limit: 0,
167
+ secondsUntilPlay: 0, // short cooldown, in seconds
168
+ canPlayAgain: Date.now()
169
+ },
170
+
152
171
  // used for skin changing
153
172
  loadout: {
154
173
  hatId: null,
@@ -184,21 +203,17 @@ export class Bot {
184
203
  this._dispatches = [];
185
204
  this._packetQueue = [];
186
205
 
187
- this.gameSocket = null;
206
+ this.matchmaker = null;
188
207
 
189
208
  this.ping = 0;
190
209
  this.lastPingTime = -1;
191
210
 
192
211
  this.lastDeathTime = -1;
193
212
  this.lastChatTime = -1;
194
-
195
213
  this.lastUpdateTime = -1;
196
- this.nUpdates = 0;
197
214
 
198
215
  this.controlKeys = 0;
199
216
 
200
- this.initTime = Date.now();
201
-
202
217
  this.pathing = {
203
218
  nodeList: null,
204
219
  followingPath: false,
@@ -208,47 +223,11 @@ export class Bot {
208
223
  }
209
224
  }
210
225
 
211
- async login(email, pass) {
212
- // const time = Date.now();
213
-
214
- this.email = email;
215
- this.pass = pass;
216
-
217
- const loginData = await loginWithCredentials(email, pass, this.proxy, this.instance);
218
-
219
- if (typeof loginData == 'string') {
220
- this.#emit('authFail', loginData);
221
- return false;
222
- }
223
-
224
- if (loginData.banRemaining) {
225
- this.#emit('banned', loginData.banRemaining);
226
- return false;
227
- }
228
-
229
- this.state.loggedIn = true;
230
-
231
- this.account.rawLoginData = loginData;
232
-
233
- this.account.accountAge = loginData.accountAge;
234
- this.account.eggBalance = loginData.currentBalance;
235
- this.account.emailVerified = loginData.emailVerified;
236
- this.account.firebaseId = loginData.firebaseId;
237
- this.account.loadout = loginData.loadout;
238
- this.account.ownedItemIds = loginData.ownedItemIds;
239
- this.account.sessionId = loginData.sessionId;
240
- this.account.vip = loginData.upgradeProductId && !loginData.upgradeIsExpired;
241
-
242
- // console.log('Logged in successfully. Time:', Date.now() - time, 'ms');
243
-
244
- return this.account;
245
- }
246
-
247
226
  dispatch(disp) {
248
227
  this._dispatches.push(disp);
249
228
  }
250
229
 
251
- drain() {
230
+ #drain() {
252
231
  for (let i = 0; i < this._dispatches.length; i++) {
253
232
  const disp = this._dispatches[i];
254
233
  if (disp.check(this)) {
@@ -261,15 +240,40 @@ export class Bot {
261
240
  }
262
241
  }
263
242
 
264
- async #anonLogin() {
243
+ async createAccount(email, pass) {
244
+ this.account.email = email;
245
+ this.account.password = pass;
246
+
247
+ const loginData = await createAccount(email, pass, this.proxy, this.instance);
248
+ return this.#processLoginData(loginData);
249
+ }
250
+
251
+ async login(email, pass) {
252
+ this.account.email = email;
253
+ this.account.password = pass;
254
+
255
+ const loginData = await loginWithCredentials(email, pass, this.proxy, this.instance);
256
+ return this.#processLoginData(loginData);
257
+ }
258
+
259
+ async loginAnonymously() {
260
+ delete this.account.email;
261
+ delete this.account.password;
262
+
265
263
  const loginData = await loginAnonymously(this.proxy, this.instance);
264
+ return this.#processLoginData(loginData);
265
+ }
266
266
 
267
+ #processLoginData(loginData) {
267
268
  if (typeof loginData == 'string') {
268
269
  this.#emit('authFail', loginData);
269
270
  return false;
270
271
  }
271
272
 
272
- this.state.loggedIn = true;
273
+ if (loginData.banRemaining) {
274
+ this.#emit('banned', loginData.banRemaining);
275
+ return false;
276
+ }
273
277
 
274
278
  this.account.rawLoginData = loginData;
275
279
 
@@ -277,19 +281,20 @@ export class Bot {
277
281
  this.account.eggBalance = loginData.currentBalance;
278
282
  this.account.emailVerified = loginData.emailVerified;
279
283
  this.account.firebaseId = loginData.firebaseId;
284
+ this.account.id = loginData.id;
280
285
  this.account.loadout = loginData.loadout;
281
286
  this.account.ownedItemIds = loginData.ownedItemIds;
282
287
  this.account.session = loginData.session;
283
288
  this.account.sessionId = loginData.sessionId;
284
- this.account.vip = false;
289
+ this.account.vip = loginData.upgradeProductId && !loginData.upgradeIsExpired;
285
290
 
286
291
  return this.account;
287
292
  }
288
293
 
289
294
  async initMatchmaker() {
290
- if (!this.state.loggedIn) {
295
+ if (this.account.id == 0) {
291
296
  // console.log('Not logged in, attempting to create anonymous user...');
292
- const anonLogin = await this.#anonLogin();
297
+ const anonLogin = await this.loginAnonymously();
293
298
  if (!anonLogin) return false;
294
299
  }
295
300
 
@@ -312,34 +317,33 @@ export class Bot {
312
317
  async #joinGameWithCode(code) {
313
318
  if (!await this.initMatchmaker()) return false;
314
319
 
315
- const listener = (mes) => {
316
- if (mes.command == 'gameFound') {
317
- this.game.raw = mes;
318
- this.game.code = code;
319
- delete this.game.raw.command; // pissed me off
320
+ return await new Promise((resolve) => {
321
+ const listener = (mes) => {
322
+ if (mes.command == 'gameFound') {
323
+ this.matchmaker.off('msg', listener);
320
324
 
321
- this.gameFound = true;
322
- }
323
-
324
- if (mes.error && mes.error == 'gameNotFound')
325
- throw new Error(`Game ${code} not found (likely expired).`)
326
- };
325
+ this.game.raw = mes;
326
+ this.game.code = code;
327
327
 
328
- this.matchmaker.on('msg', listener);
328
+ resolve();
329
+ }
329
330
 
330
- this.matchmaker.send({
331
- command: 'joinGame',
332
- id: code,
333
- observe: false,
334
- sessionId: this.account.sessionId
335
- })
331
+ if (mes.error && mes.error == 'gameNotFound')
332
+ throw new Error(`Game ${code} not found (likely expired).`)
333
+ };
336
334
 
337
- while (!this.gameFound) await new Promise(r => setTimeout(r, 10));
335
+ this.matchmaker.on('msg', listener);
338
336
 
339
- this.matchmaker.off('msg', listener);
337
+ this.matchmaker.send({
338
+ command: 'joinGame',
339
+ id: code,
340
+ observe: false,
341
+ sessionId: this.account.sessionId
342
+ })
343
+ });
340
344
  }
341
345
 
342
- async #onGameMesssage(msg) { // to minify with vscode
346
+ async #onGameMesssage(msg) {
343
347
  CommIn.init(msg.data);
344
348
 
345
349
  let out;
@@ -350,7 +354,7 @@ export class Bot {
350
354
  out = CommOut.getBuffer();
351
355
  out.packInt8(CommCode.joinGame);
352
356
 
353
- out.packString(this.name); // name
357
+ out.packString(this.state.name); // name
354
358
  out.packString(this.game.raw.uuid); // game id
355
359
 
356
360
  out.packInt8(0); // hidebadge
@@ -360,10 +364,10 @@ export class Bot {
360
364
  out.packString(this.account.firebaseId); // firebase id
361
365
  out.packString(this.account.sessionId); // session id
362
366
 
363
- out.send(this.gameSocket);
367
+ out.send(this.game.socket);
364
368
  break;
365
369
 
366
- case CommCode.gameJoined:
370
+ case CommCode.gameJoined: {
367
371
  this.me.id = CommIn.unPackInt8U();
368
372
  // console.log("My id is:", this.me.id);
369
373
  this.me.team = CommIn.unPackInt8U();
@@ -373,9 +377,10 @@ export class Bot {
373
377
  // console.log("Gametype:", this.game.gameMode, this.game.gameModeId);
374
378
  this.game.mapIdx = CommIn.unPackInt8U();
375
379
  this.game.map = Maps[this.game.mapIdx];
376
- if (!this.disablePathing) {
380
+ if (this.intents.includes(this.Intents.PATHFINDING)) {
377
381
  this.game.map.raw = await this.#fetchMap(this.game.map.filename, this.game.map.hash);
378
382
  this.pathing.nodeList = new NodeList(this.game.map.raw);
383
+ if (this.game.gameModeId === GameModes.kotc) this.#initKotcZones();
379
384
  }
380
385
  // console.log("Map:", this.game.map);
381
386
  this.game.playerLimit = CommIn.unPackInt8U();
@@ -389,34 +394,34 @@ export class Bot {
389
394
  this.state.joinedGame = true;
390
395
  this.lastDeathTime = Date.now();
391
396
 
397
+ const out = CommOut.getBuffer();
398
+ out.packInt8(CommCode.clientReady);
399
+ out.send(this.game.socket);
400
+
401
+ this.game.socket.onmessage = (msg) => this._packetQueue.push(msg.data);
402
+
403
+ if (this.autoUpdate)
404
+ setInterval(() => this.update(), this.updateInterval);
405
+
406
+ if (this.intents.includes(this.Intents.PING)) {
407
+ const out = CommOut.getBuffer();
408
+ out.packInt8(CommCode.ping);
409
+ out.send(this.game.socket);
410
+ this.lastPingTime = Date.now();
411
+ }
392
412
  break;
413
+ }
393
414
 
394
415
  case CommCode.eventModifier:
395
416
  // console.log("Echoed eventModifier"); // why the fuck do you need to do this
396
417
  out = CommOut.getBuffer();
397
418
  out.packInt8(CommCode.eventModifier);
398
- out.send(this.gameSocket);
419
+ out.send(this.game.socket);
399
420
  break;
400
421
 
401
- case CommCode.requestGameOptions: {
402
- out = CommOut.getBuffer();
403
- out.packInt8(CommCode.gameOptions);
404
- out.packInt8(this.game.options.gravity * 4);
405
- out.packInt8(this.game.options.damage * 4);
406
- out.packInt8(this.game.options.healthRegen * 4);
407
-
408
- const flags =
409
- (this.game.options.locked ? 1 : 0) |
410
- (this.game.options.noTeamChange ? 2 : 0) |
411
- (this.game.options.noTeamShuffle ? 4 : 0);
412
-
413
- out.packInt8(flags);
414
-
415
- this.game.options.weaponsDisabled.forEach((v) => {
416
- out.packInt8(v ? 1 : 0);
417
- });
422
+ case CommCode.requestGameOptions:
423
+ this.#processGameRequestOptionsPacket();
418
424
  break;
419
- }
420
425
 
421
426
  default:
422
427
  try {
@@ -449,52 +454,50 @@ export class Bot {
449
454
 
450
455
  if (mapIdx == -1) throw new Error('invalid map, see the Maps constant for a list')
451
456
 
452
- const listener = (msg) => {
453
- if (msg.command == 'gameFound') {
454
- this.game.raw = msg;
455
- this.game.code = this.game.raw.id;
456
- delete this.game.raw.command;
457
-
458
- this.gameFound = true;
459
- }
460
- };
457
+ await new Promise((resolve) => {
458
+ const listener = (msg) => {
459
+ if (msg.command == 'gameFound') {
460
+ this.matchmaker.off('msg', listener);
461
461
 
462
- this.matchmaker.on('msg', listener);
462
+ this.game.raw = msg;
463
+ this.game.code = this.game.raw.id;
463
464
 
464
- this.matchmaker.send({
465
- command: 'findGame',
466
- region: opts.region,
467
- playType: PlayTypes.createPrivate,
468
- gameType: GameModes[opts.mode],
469
- sessionId: this.account.sessionId,
470
- noobLobby: false,
471
- map: mapIdx
472
- });
465
+ resolve();
466
+ }
467
+ };
473
468
 
474
- while (!this.gameFound) await new Promise(r => setTimeout(r, 10));
469
+ this.matchmaker.on('msg', listener);
475
470
 
476
- this.matchmaker.off('msg', listener);
471
+ this.matchmaker.send({
472
+ command: 'findGame',
473
+ region: opts.region,
474
+ playType: PlayTypes.createPrivate,
475
+ gameType: GameModes[opts.mode],
476
+ sessionId: this.account.sessionId,
477
+ noobLobby: false,
478
+ map: mapIdx
479
+ });
480
+ });
477
481
 
478
482
  return this.game.raw;
479
483
  }
480
484
 
481
- async join(data) {
485
+ async join(name, data) {
486
+ this.state.name = name || 'yolkbot';
487
+
482
488
  if (typeof data == 'string') {
483
489
  if (data.includes('#')) data = data.split('#')[1]; // stupid shell kids put in full links
484
490
  // this is a string code that we can pass and get the needed info from
485
491
  await this.#joinGameWithCode(data);
486
492
  } else if (typeof data == 'object') {
487
- if (!this.state.loggedIn) {
493
+ if (this.account.id == 0) {
488
494
  // console.log('passed an object but you still need to be logged in!!')
489
- await this.#anonLogin();
495
+ await this.loginAnonymously();
490
496
  }
491
497
 
492
498
  // this is a game object that we can pass and get the needed info from
493
499
  this.game.raw = data;
494
500
  this.game.code = this.game.raw.id;
495
- delete this.game.raw.command;
496
-
497
- this.gameFound = true;
498
501
  }
499
502
 
500
503
  if (!this.game.raw.id || !this.game.raw.subdomain)
@@ -502,113 +505,103 @@ export class Bot {
502
505
 
503
506
  // console.log(`Joining ${this.game.raw.id} using proxy ${this.proxy || 'none'}`);
504
507
 
505
- this.gameSocket = new yolkws(`wss://${this.game.raw.subdomain}.${this.instance}/game/${this.game.raw.id}`, this.proxy);
508
+ const attempt = async () => {
509
+ try {
510
+ this.game.socket = new yolkws(`wss://${this.game.raw.subdomain}.${this.instance}/game/${this.game.raw.id}`, this.proxy);
511
+ } catch {
512
+ await new Promise((resolve) => setTimeout(resolve, 100));
513
+ await attempt();
514
+ }
515
+ }
516
+
517
+ await attempt();
506
518
 
507
- this.gameSocket.binaryType = 'arraybuffer';
519
+ this.game.socket.binaryType = 'arraybuffer';
508
520
 
509
- this.gameSocket.onopen = () => {
521
+ this.game.socket.onopen = () => {
510
522
  // console.log('Successfully connected to game server.');
511
523
  }
512
524
 
513
- this.gameSocket.onmessage = this.#onGameMesssage.bind(this);
525
+ this.game.socket.onmessage = this.#onGameMesssage.bind(this);
514
526
 
515
- this.gameSocket.onclose = (e) => {
527
+ this.game.socket.onclose = (e) => {
516
528
  // console.log('Game socket closed:', e.code, Object.entries(CloseCode).filter(([, v]) => v == e.code));
517
529
  this.#emit('close', e.code);
518
530
  }
519
-
520
- while (!this.state.joinedGame) await new Promise(r => setTimeout(r, 5));
521
-
522
- const out = CommOut.getBuffer();
523
- out.packInt8(CommCode.clientReady);
524
- out.send(this.gameSocket);
525
-
526
- this.gameSocket.onmessage = (msg) => this._packetQueue.push(msg.data);
527
-
528
- // console.log(`Successfully joined ${this.game.code}. Startup to join time: ${Date.now() - this.initTime} ms`);
529
-
530
- if (this.autoUpdate)
531
- setInterval(() => this.update(), this.updateInterval);
532
-
533
- if (this.autoPing) {
534
- const out = CommOut.getBuffer();
535
- out.packInt8(CommCode.ping);
536
- out.send(this.gameSocket);
537
- this.lastPingTime = Date.now();
538
- }
539
531
  }
540
532
 
541
- update() {
542
- if (!this.state.joinedGame) { throw new Error('Not playing, can\'t update. '); }
543
-
544
- this.nUpdates++;
545
-
546
- if (this._packetQueue.length === 0 && this._dispatches.length === 0) return;
547
-
548
- let packet;
549
- while ((packet = this._packetQueue.shift()) !== undefined) { this.#handlePacket(packet); }
550
-
551
- this.drain();
533
+ #processPathfinding() {
534
+ const myPositionStr = Object.entries(this.me.position).map(entry => Math.floor(entry[1])).join(',');
552
535
 
553
- if (this.pathing.followingPath && !this.disablePathing) {
554
- const myPositionStr = Object.entries(this.me.position).map(entry => Math.floor(entry[1])).join(',');
536
+ if (myPositionStr == this.pathing.activePath[this.pathing.activePath.length - 1].positionStr()) {
537
+ // console.log('Completed path to', this.pathing.activePath[this.pathing.activePath.length - 1].position);
538
+ this.pathing.followingPath = false;
539
+ this.pathing.activePath = null;
540
+ this.pathing.activeNode = null;
541
+ this.pathing.activeNodeIdx = 0;
555
542
 
556
- if (myPositionStr == this.pathing.activePath[this.pathing.activePath.length - 1].positionStr()) {
557
- // console.log('Completed path to', this.pathing.activePath[this.pathing.activePath.length - 1].position);
558
- this.pathing.followingPath = false;
559
- this.pathing.activePath = null;
560
- this.pathing.activeNode = null;
561
- this.pathing.activeNodeIdx = 0;
562
-
563
- this.dispatch(new MovementDispatch(0));
543
+ this.dispatch(new MovementDispatch(0));
544
+ } else {
545
+ let positionTarget;
546
+ if (this.pathing.activeNodeIdx < this.pathing.activePath.length - 1) {
547
+ positionTarget = this.pathing.activePath[this.pathing.activeNodeIdx + 1].flatCenter();
548
+ this.dispatch(new LookAtPosDispatch(positionTarget));
564
549
  } else {
565
- let positionTarget;
566
- if (this.pathing.activeNodeIdx < this.pathing.activePath.length - 1) {
567
- positionTarget = this.pathing.activePath[this.pathing.activeNodeIdx + 1].flatCenter();
568
- this.dispatch(new LookAtPosDispatch(positionTarget));
569
- } else {
570
- positionTarget = this.pathing.activePath[this.pathing.activeNodeIdx].flatCenter();
571
- this.dispatch(new LookAtPosDispatch(positionTarget));
572
- }
550
+ positionTarget = this.pathing.activePath[this.pathing.activeNodeIdx].flatCenter();
551
+ this.dispatch(new LookAtPosDispatch(positionTarget));
552
+ }
573
553
 
574
- for (const node of this.pathing.activePath) {
575
- if (node.flatRadialDistance(this.me.position) < 0.1 && node.position.y == Math.floor(this.me.position.y)) {
576
- if (this.pathing.activePath.indexOf(node) >= this.pathing.activeNodeIdx) {
577
- this.pathing.activeNodeIdx = this.pathing.activePath.indexOf(node) + 1;
578
- this.pathing.activeNode = this.pathing.activePath[this.pathing.activeNodeIdx];
579
- break;
580
- } else {
581
- // console.log('Close to node that\'s before, idx:',
582
- // this.pathing.activePath.indexOf(node), 'current:', this.pathing.activeNodeIdx);
583
- }
554
+ for (const node of this.pathing.activePath) {
555
+ if (node.flatRadialDistance(this.me.position) < 0.1 && node.position.y == Math.floor(this.me.position.y)) {
556
+ if (this.pathing.activePath.indexOf(node) >= this.pathing.activeNodeIdx) {
557
+ this.pathing.activeNodeIdx = this.pathing.activePath.indexOf(node) + 1;
558
+ this.pathing.activeNode = this.pathing.activePath[this.pathing.activeNodeIdx];
559
+ break;
584
560
  } else {
585
- // console.log('Node at', node.position, 'is', node.flatRadialDistance(this.me.position), 'away.')
561
+ // console.log('Close to node that\'s before, idx:',
562
+ // this.pathing.activePath.indexOf(node), 'current:', this.pathing.activeNodeIdx);
586
563
  }
587
- // console.log('activeNode is ', this.pathing.activeNode.flatRadialDistance(this.me.position), 'away');
564
+ } else {
565
+ // console.log('Node at', node.position, 'is', node.flatRadialDistance(this.me.position), 'away.')
588
566
  }
567
+ // console.log('activeNode is ', this.pathing.activeNode.flatRadialDistance(this.me.position), 'away');
568
+ }
589
569
 
590
- if (!(this.controlKeys & Movements.FORWARD)) {
591
- this.dispatch(new MovementDispatch(Movements.FORWARD));
592
- }
570
+ if (!(this.controlKeys & Movements.FORWARD)) {
571
+ this.dispatch(new MovementDispatch(Movements.FORWARD));
593
572
  }
573
+ }
594
574
 
595
- /*let onPath = false;
596
- for (const node of this.pathing.activePath) {
597
- if (node.positionStr() == myPositionStr) {
598
- onPath = true;
599
- break;
600
- }
575
+ /*let onPath = false;
576
+ for (const node of this.pathing.activePath) {
577
+ if (node.positionStr() == myPositionStr) {
578
+ onPath = true;
579
+ break;
601
580
  }
602
- if (!onPath) {
603
- console.log('Got off-path somehow');
604
- this.dispatch(new PathfindDispatch(this.pathing.activePath[this.pathing.activePath.length - 1]));
605
- this.pathing.followingPath = false;
606
- this.pathing.activePath = null;
607
- this.pathing.activeNode = null;
608
- this.pathing.activeNodeIdx = 0;
609
- }*/
610
581
  }
582
+ if (!onPath) {
583
+ console.log('Got off-path somehow');
584
+ this.dispatch(new PathfindDispatch(this.pathing.activePath[this.pathing.activePath.length - 1]));
585
+ this.pathing.followingPath = false;
586
+ this.pathing.activePath = null;
587
+ this.pathing.activeNode = null;
588
+ this.pathing.activeNodeIdx = 0;
589
+ }*/
590
+ }
611
591
 
592
+ update() {
593
+ if (!this.state.joinedGame) throw new Error('Not playing, can\'t update. ');
594
+
595
+ // process pathfinding
596
+ if (this.pathing.followingPath && this.intents.includes(this.Intents.PATHFINDING)) this.#processPathfinding();
597
+
598
+ // process incoming packets
599
+ while (this._packetQueue.length > 0) this.#handlePacket(this._packetQueue.shift());
600
+
601
+ // process dispatches
602
+ if (this._dispatches.length > 0) this.#drain();
603
+
604
+ // process syncMe
612
605
  if (Date.now() - this.lastUpdateTime >= 50) {
613
606
  this.#emit('tick');
614
607
 
@@ -624,14 +617,16 @@ export class Bot {
624
617
  out.packRad(this.me.view.pitch); // pitch
625
618
  out.packInt8(100); // fixes commcode issues, does nothing
626
619
  }
627
- out.send(this.gameSocket);
620
+ out.send(this.game.socket);
628
621
 
629
622
  this.lastUpdateTime = Date.now();
630
623
  this.state.shotsFired = 0;
631
624
  }
632
625
 
633
- let cb;
634
- while ((cb = this._liveCallbacks.shift()) !== undefined) cb();
626
+ while (this._liveCallbacks.length > 0) {
627
+ const cb = this._liveCallbacks.shift();
628
+ cb();
629
+ }
635
630
  }
636
631
 
637
632
  on(event, cb) {
@@ -688,6 +683,64 @@ export class Bot {
688
683
  }
689
684
  }
690
685
 
686
+ #initKotcZones() {
687
+ const meshData = this.game.map.raw.data['DYNAMIC.capture-zone.none'];
688
+ if (!meshData) return delete this.game.map.zones;
689
+
690
+ let numCaptureZones = 0;
691
+ const mapData = {};
692
+ const zones = [];
693
+
694
+ for (const cell of meshData) {
695
+ if (!mapData[cell.x]) mapData[cell.x] = {};
696
+ if (!mapData[cell.x][cell.y]) mapData[cell.x][cell.y] = {};
697
+ mapData[cell.x][cell.y][cell.z] = { zone: null };
698
+ }
699
+
700
+ const offsets = [
701
+ { x: -1, z: 0 },
702
+ { x: 1, z: 0 },
703
+ { x: 0, z: -1 },
704
+ { x: 0, z: 1 }
705
+ ];
706
+
707
+ function getMapCellAt(x, y, z) {
708
+ return mapData[x] && mapData[x][y] && mapData[x][y][z] ? mapData[x][y][z] : null;
709
+ }
710
+
711
+ for (const cellA of meshData) {
712
+ if (!mapData[cellA.x][cellA.y][cellA.z].zone) {
713
+ cellA.zone = ++numCaptureZones;
714
+ mapData[cellA.x][cellA.y][cellA.z].zone = cellA.zone;
715
+
716
+ const currentZone = [cellA];
717
+ let hits;
718
+
719
+ do {
720
+ hits = 0;
721
+ for (const cellB of meshData) {
722
+ if (!mapData[cellB.x][cellB.y][cellB.z].zone) {
723
+ for (const o of offsets) {
724
+ const cell = getMapCellAt(cellB.x + o.x, cellB.y, cellB.z + o.z);
725
+ if (cell && cell.zone == cellA.zone) {
726
+ hits++;
727
+ cellB.zone = cellA.zone;
728
+ mapData[cellB.x][cellB.y][cellB.z].zone = cellA.zone;
729
+ currentZone.push(cellB);
730
+ break;
731
+ }
732
+ }
733
+ }
734
+ }
735
+ } while (hits > 0);
736
+
737
+ zones.push(currentZone);
738
+ }
739
+ }
740
+
741
+ this.game.map.zones = zones;
742
+ }
743
+
691
744
  #processChatPacket() {
692
745
  const id = CommIn.unPackInt8U();
693
746
  const msgFlags = CommIn.unPackInt8U();
@@ -704,6 +757,7 @@ export class Bot {
704
757
  const name = CommIn.unPackString();
705
758
  const safename = CommIn.unPackString(); // ??? (a)
706
759
  const charClass = CommIn.unPackInt8U();
760
+ const findCosmetics = this.intents.includes(this.Intents.COSMETIC_DATA);
707
761
  const playerData = {
708
762
  id_: id_,
709
763
  uniqueId_: uniqueId,
@@ -712,14 +766,14 @@ export class Bot {
712
766
  charClass_: charClass,
713
767
  team_: CommIn.unPackInt8U(),
714
768
  primaryWeaponItem_: findItemById(CommIn.unPackInt16U()),
715
- secondaryWeaponItem_: findItemById(CommIn.unPackInt16U()), // b
769
+ secondaryWeaponItem_: findCosmetics ? findItemById(CommIn.unPackInt16U()) : CommIn.unPackInt16U(),
716
770
  shellColor_: CommIn.unPackInt8U(),
717
- hatItem_: findItemById(CommIn.unPackInt16U()),
718
- stampItem_: findItemById(CommIn.unPackInt16U()),
771
+ hatItem_: findCosmetics ? findItemById(CommIn.unPackInt16U()) : CommIn.unPackInt16U(),
772
+ stampItem_: findCosmetics ? findItemById(CommIn.unPackInt16U()) : CommIn.unPackInt16U(),
719
773
  unknownInt8: CommIn.unPackInt8(), // c
720
774
  otherUnknownInt8: CommIn.unPackInt8(),
721
- grenadeItem_: findItemById(CommIn.unPackInt16U()),
722
- meleeItem_: findItemById(CommIn.unPackInt16U()),
775
+ grenadeItem_: findCosmetics ? findItemById(CommIn.unPackInt16U()) : CommIn.unPackInt16U(),
776
+ meleeItem_: findCosmetics ? findItemById(CommIn.unPackInt16U()) : CommIn.unPackInt16U(),
723
777
  x_: CommIn.unPackFloat(),
724
778
  y_: CommIn.unPackFloat(),
725
779
  z_: CommIn.unPackFloat(),
@@ -777,11 +831,6 @@ export class Bot {
777
831
  }
778
832
 
779
833
  this.#emit('playerJoin', this.players[playerData.id_]);
780
-
781
- const unp = CommIn.unPackInt8U();
782
- if (unp == CommCode.addPlayer) { // there is another player stacked
783
- this.#processAddPlayerPacket();
784
- }
785
834
  }
786
835
 
787
836
  #processRespawnPacket() {
@@ -838,37 +887,53 @@ export class Bot {
838
887
  const climbing = CommIn.unPackInt8U();
839
888
 
840
889
  const player = this.players[id];
841
- if (!player || !player.buffer) return;
890
+ if (!player) return;
842
891
 
843
- if (player.id == this.me.id) {
844
- for (let i2 = 0; i2 < 3 /* FramesBetweenSyncs */; i2++) {
845
- CommIn.unPackInt8U();
846
- CommIn.unPackRadU();
847
- CommIn.unPackRad();
892
+ if (player.position.x !== x) player.position.x = x;
893
+ if (player.position.z !== z) player.position.z = z;
894
+
895
+ if (!player.jumping || Math.abs(player.position.y - y) > 0.5 && player.position.y !== y)
896
+ player.position.y = y;
897
+
898
+ if (player.climbing !== climbing) player.climbing = climbing;
899
+
900
+ if (this.intents.includes(this.Intents.BUFFERS)) {
901
+ if (!player.buffer) return;
902
+
903
+ if (player.id == this.me.id) {
904
+ for (let i2 = 0; i2 < 3 /* FramesBetweenSyncs */; i2++) {
905
+ CommIn.unPackInt8U();
906
+ CommIn.unPackRadU();
907
+ CommIn.unPackRad();
908
+ CommIn.unPackInt8U();
909
+ }
910
+ return;
848
911
  }
849
- }
850
912
 
851
- let yaw, pitch;
852
- for (let i2 = 0; i2 < 3; i2++) {
853
- player.buffer[i2].controlKeys = CommIn.unPackInt8U();
854
- yaw = CommIn.unPackRadU();
855
- if (!isNaN(yaw)) { player.buffer[i2].yaw_ = yaw }
856
- pitch = CommIn.unPackRad();
857
- if (!isNaN(pitch)) { player.buffer[i2].pitch_ = pitch }
858
- }
913
+ let yaw, pitch;
914
+ for (let i2 = 0; i2 < 3; i2++) {
915
+ player.buffer[i2].controlKeys = CommIn.unPackInt8U();
859
916
 
860
- player.position.x = x;
917
+ yaw = CommIn.unPackRadU();
918
+ if (!isNaN(yaw)) player.buffer[i2].yaw_ = yaw
861
919
 
862
- if (!player.jumping || Math.abs(player.position.y - y) > 0.5) {
863
- player.position.y = y;
864
- }
920
+ pitch = CommIn.unPackRad();
921
+ if (!isNaN(pitch)) player.buffer[i2].pitch_ = pitch
865
922
 
866
- player.position.z = z;
867
- player.buffer[0].x = x;
868
- player.buffer[0].y = y;
869
- player.buffer[0].z = z;
870
- player.climbing = climbing;
871
- // console.log(`Player ${player.name} is now at ${x}, ${y}, ${z} (climbing = ${climbing})`);
923
+ CommIn.unPackInt8U();
924
+ }
925
+
926
+ player.buffer[0].x = x;
927
+ player.buffer[0].y = y;
928
+ player.buffer[0].z = z;
929
+ } else {
930
+ for (let i2 = 0; i2 < 3; i2++) {
931
+ CommIn.unPackInt8U();
932
+ CommIn.unPackRadU();
933
+ CommIn.unPackRad();
934
+ CommIn.unPackInt8U();
935
+ }
936
+ }
872
937
  }
873
938
 
874
939
  #processPausePacket() {
@@ -894,16 +959,14 @@ export class Bot {
894
959
  #processDeathPacket() {
895
960
  const killedId = CommIn.unPackInt8U();
896
961
  const byId = CommIn.unPackInt8U();
897
- // const rs = CommIn.unPackInt8U();
962
+
963
+ CommIn.unPackInt8U();
964
+ CommIn.unPackInt8U();
965
+ CommIn.unPackInt8U();
898
966
 
899
967
  const killed = this.players[killedId];
900
968
  const killer = this.players[byId];
901
969
 
902
- /*
903
- const killerLastDmg = CommIn.unPackInt8U();
904
- const killedLastDmg = CommIn.unPackInt8U();
905
- */
906
-
907
970
  if (killed) {
908
971
  killed.playing = false;
909
972
  killed.kills = 0;
@@ -920,6 +983,9 @@ export class Bot {
920
983
  #processFirePacket() {
921
984
  const id = CommIn.unPackInt8U();
922
985
 
986
+ for (let i = 0; i < 6; i++)
987
+ CommIn.unPackFloat();
988
+
923
989
  const player = this.players[id];
924
990
  const playerWeapon = player.weapons[player.activeGun];
925
991
 
@@ -960,7 +1026,9 @@ export class Bot {
960
1026
  }
961
1027
 
962
1028
  if (type == CollectTypes.GRENADE) {
963
- player.grenades >= 3 ? player.grenades = 3 : player.grenades++;
1029
+ player.grenades++;
1030
+ if (player.grenades > 3) player.grenades = 3
1031
+
964
1032
  this.#emit('collectGrenade', player);
965
1033
  }
966
1034
  }
@@ -980,8 +1048,11 @@ export class Bot {
980
1048
 
981
1049
  #processHitMePacket() {
982
1050
  const hp = CommIn.unPackInt8U();
983
- const oldHp = this.me.hp;
984
1051
 
1052
+ CommIn.unPackFloat();
1053
+ CommIn.unPackFloat();
1054
+
1055
+ const oldHp = this.me.hp;
985
1056
  this.me.hp = hp;
986
1057
 
987
1058
  this.#emit('selfDamaged', oldHp, this.me.hp);
@@ -1001,6 +1072,10 @@ export class Bot {
1001
1072
  const newY = CommIn.unPackFloat();
1002
1073
  const newZ = CommIn.unPackFloat();
1003
1074
 
1075
+ CommIn.unPackInt8U();
1076
+ CommIn.unPackInt8U();
1077
+ CommIn.unPackInt8U();
1078
+
1004
1079
  const oldX = player.position.x;
1005
1080
  const oldY = player.position.y;
1006
1081
  const oldZ = player.position.z;
@@ -1017,7 +1092,7 @@ export class Bot {
1017
1092
  #processEventModifierPacket() {
1018
1093
  const out = CommOut.getBuffer();
1019
1094
  out.packInt8(CommCode.eventModifier);
1020
- out.send(this.gameSocket);
1095
+ out.send(this.game.socket);
1021
1096
  }
1022
1097
 
1023
1098
  #processRemovePlayerPacket() {
@@ -1052,7 +1127,7 @@ export class Bot {
1052
1127
  this.#emit('gameStateChange', this.game);
1053
1128
  } else if (this.game.gameModeId == GameModes.kotc) {
1054
1129
  this.game.stage = CommIn.unPackInt8U(); // constants.CoopStates
1055
- this.game.activeZone = CommIn.unPackInt8U(); // a number to represent which 'active zone' kotc is using
1130
+ this.game.zoneNumber = CommIn.unPackInt8U(); // a number to represent which 'active zone' kotc is using
1056
1131
  this.game.capturing = CommIn.unPackInt8U(); // the team capturing, named "teams" in shell src
1057
1132
  this.game.captureProgress = CommIn.unPackInt16U(); // progress of the coop capture
1058
1133
  this.game.numCapturing = CommIn.unPackInt8U(); // number of players capturing - number/1000
@@ -1062,8 +1137,12 @@ export class Bot {
1062
1137
  // not in shell, for utility purposes =D
1063
1138
  this.game.stageName = CoopStagesById[this.game.stage]; // name of the stage ('start' / 'capturing' / 'etc')
1064
1139
  this.game.capturePercent = this.game.captureProgress / 1000; // progress of the capture as a percentage
1140
+ this.game.activeZone = this.game.map.zones ? this.game.map.zones[this.game.zoneNumber - 1] : null;
1065
1141
 
1066
1142
  this.#emit('gameStateChange', this.game);
1143
+ } else if (this.game.gameModeId == GameModes.team) {
1144
+ this.game.teamScore[1] = CommIn.unPackInt16U();
1145
+ this.game.teamScore[2] = CommIn.unPackInt16U();
1067
1146
  }
1068
1147
 
1069
1148
  if (this.game.gameModeId !== GameModes.spatula) {
@@ -1072,13 +1151,16 @@ export class Bot {
1072
1151
 
1073
1152
  if (this.game.gameModeId !== GameModes.kotc) {
1074
1153
  delete this.game.stage;
1075
- delete this.game.activeZone;
1154
+ delete this.game.zoneNumber;
1076
1155
  delete this.game.capturing;
1077
1156
  delete this.game.captureProgress;
1078
- delete this.game.numCapturing
1157
+ delete this.game.numCapturing;
1158
+ delete this.game.stageName;
1159
+ delete this.game.numCapturing;
1160
+ delete this.game.activeZone;
1079
1161
  }
1080
1162
 
1081
- if (this.game.gameModeId !== GameModes.spatula && this.game.gameModeId !== GameModes.kotc) {
1163
+ if (this.game.gameModeId == GameModes.ffa) {
1082
1164
  delete this.game.teamScore;
1083
1165
  }
1084
1166
  }
@@ -1155,6 +1237,9 @@ export class Bot {
1155
1237
  const hb = CommIn.unPackInt8U();
1156
1238
  const hp = CommIn.unPackInt8U();
1157
1239
 
1240
+ CommIn.unPackFloat();
1241
+ CommIn.unPackFloat();
1242
+
1158
1243
  this.me.hpShield = hb;
1159
1244
  this.me.hp = hp;
1160
1245
 
@@ -1208,25 +1293,33 @@ export class Bot {
1208
1293
  // console.log('owner reset game');
1209
1294
 
1210
1295
  this.me.kills = 0;
1211
- this.game.teamScore = [0, 0, 0];
1212
1296
 
1213
- this.game.spatula.controlledBy = 0;
1214
- this.game.spatula.controlledByTeam = 0;
1215
- this.game.spatula.coords = { x: 0, y: 0, z: 0 };
1297
+ if (this.game.gameModeId !== GameModes.ffa) this.game.teamScore = [0, 0, 0];
1216
1298
 
1217
- this.game.stage = CoopStates.capturing;
1218
- this.game.activeZone = 0;
1219
- this.game.capturing = 0;
1220
- this.game.captureProgress = 0;
1221
- this.game.numCapturing = 0;
1222
- this.game.stageName = CoopStagesById[CoopStates.capturing];
1223
- this.game.capturePercent = 0.0;
1299
+ if (this.game.gameModeId === GameModes.spatula) {
1300
+ this.game.spatula.controlledBy = 0;
1301
+ this.game.spatula.controlledByTeam = 0;
1302
+ this.game.spatula.coords = { x: 0, y: 0, z: 0 };
1303
+ }
1304
+
1305
+ if (this.game.gameModeId === GameModes.kotc) {
1306
+ this.game.stage = CoopStates.capturing;
1307
+ this.game.zoneNumber = 0;
1308
+ this.game.activeZone = null;
1309
+ this.game.capturing = 0;
1310
+ this.game.captureProgress = 0;
1311
+ this.game.numCapturing = 0;
1312
+ this.game.stageName = CoopStagesById[CoopStates.capturing];
1313
+ this.game.capturePercent = 0.0;
1314
+ }
1224
1315
 
1225
1316
  this.#emit('gameReset');
1226
1317
  }
1227
1318
  }
1228
1319
 
1229
1320
  #processPingPacket() {
1321
+ if (!this.intents.includes(this.Intents.PING)) return;
1322
+
1230
1323
  const oldPing = this.ping;
1231
1324
 
1232
1325
  this.ping = Date.now() - this.lastPingTime;
@@ -1236,7 +1329,7 @@ export class Bot {
1236
1329
  setTimeout(() => {
1237
1330
  const out = CommOut.getBuffer();
1238
1331
  out.packInt8(CommCode.ping);
1239
- out.send(this.gameSocket);
1332
+ out.send(this.game.socket);
1240
1333
  this.lastPingTime = Date.now();
1241
1334
  }, this.pingInterval);
1242
1335
  }
@@ -1268,12 +1361,18 @@ export class Bot {
1268
1361
  const grenadeIdx = CommIn.unPackInt16U();
1269
1362
  const meleeIdx = CommIn.unPackInt16U();
1270
1363
 
1364
+ CommIn.unPackInt8();
1365
+ CommIn.unPackInt8();
1366
+
1367
+ const findCosmetics = this.intents.includes(this.Intents.COSMETIC_DATA);
1368
+
1271
1369
  const primaryWeaponItem = findItemById(primaryWeaponIdx);
1272
- const secondaryWeaponItem = findItemById(secondaryWeaponIdx);
1273
- const hatItem = findItemById(hatIdx);
1274
- const stampItem = findItemById(stampIdx);
1275
- const grenadeItem = findItemById(grenadeIdx);
1276
- const meleeItem = findItemById(meleeIdx);
1370
+
1371
+ const secondaryWeaponItem = findCosmetics ? findItemById(secondaryWeaponIdx) : secondaryWeaponIdx;
1372
+ const hatItem = findCosmetics ? findItemById(hatIdx) : hatIdx;
1373
+ const stampItem = findCosmetics ? findItemById(stampIdx) : stampIdx;
1374
+ const grenadeItem = findCosmetics ? findItemById(grenadeIdx) : grenadeIdx;
1375
+ const meleeItem = findCosmetics ? findItemById(meleeIdx) : meleeIdx;
1277
1376
 
1278
1377
  const player = this.players[id];
1279
1378
  if (player) {
@@ -1316,7 +1415,8 @@ export class Bot {
1316
1415
  if (player) this.#emit('playerMelee', player);
1317
1416
  }
1318
1417
 
1319
- // we do this since reload doesn't get emitted to ourselves
1418
+ // we do this since reload doesn't get emitted for the bot itself
1419
+ // so we simulate it when calling a reload dispatch
1320
1420
  processReloadPacket(customPlayer, iUnderstandThisIsForInternalUseOnlyAndIShouldNotBeCallingThis) {
1321
1421
  if (!iUnderstandThisIsForInternalUseOnlyAndIShouldNotBeCallingThis)
1322
1422
  throw new Error('processReloadPacket is exposed for internal use only. do not call it.');
@@ -1341,140 +1441,407 @@ export class Bot {
1341
1441
  this.#emit('playerReload', player, playerActiveWeapon);
1342
1442
  }
1343
1443
 
1444
+ #processGameRequestOptionsPacket() {
1445
+ const out = CommOut.getBuffer();
1446
+ out.packInt8(CommCode.gameOptions);
1447
+ out.packInt8(this.game.options.gravity * 4);
1448
+ out.packInt8(this.game.options.damage * 4);
1449
+ out.packInt8(this.game.options.healthRegen * 4);
1450
+
1451
+ const flags =
1452
+ (this.game.options.locked ? 1 : 0) |
1453
+ (this.game.options.noTeamChange ? 2 : 0) |
1454
+ (this.game.options.noTeamShuffle ? 4 : 0);
1455
+
1456
+ out.packInt8(flags);
1457
+
1458
+ this.game.options.weaponsDisabled.forEach((v) => {
1459
+ out.packInt8(v ? 1 : 0);
1460
+ });
1461
+ }
1462
+
1463
+ #processExplodePacket() {
1464
+ const itemType = CommIn.unPackInt8U();
1465
+ let item = CommIn.unPackInt16U();
1466
+ const x = CommIn.unPackFloat();
1467
+ const y = CommIn.unPackFloat();
1468
+ const z = CommIn.unPackFloat();
1469
+ const damage = CommIn.unPackInt8U();
1470
+ const radius = CommIn.unPackFloat();
1471
+
1472
+ if (this.intents.includes(this.Intents.COSMETIC_DATA))
1473
+ item = findItemById(item);
1474
+
1475
+ if (itemType == ItemTypes.Grenade) this.#emit('grenadeExploded', item, { x, y, z }, damage, radius);
1476
+ else this.#emit('rocketHit', { x, y, z }, damage, radius);
1477
+ }
1478
+
1479
+ #processThrowGrenadePacket() {
1480
+ const id = CommIn.unPackInt8U();
1481
+ const x = CommIn.unPackFloat();
1482
+ const y = CommIn.unPackFloat();
1483
+ const z = CommIn.unPackFloat();
1484
+ const dx = CommIn.unPackFloat();
1485
+ const dy = CommIn.unPackFloat();
1486
+ const dz = CommIn.unPackFloat();
1487
+
1488
+ const player = this.players[id];
1489
+
1490
+ if (player) {
1491
+ player.grenades--;
1492
+ this.#emit('playerThrowGrenade', player, { x, y, z }, { x: dx, y: dy, z: dz });
1493
+ }
1494
+ }
1495
+
1344
1496
  #handlePacket(packet) {
1345
1497
  CommIn.init(packet);
1346
- this.#emit('packet', packet);
1347
- const cmd = CommIn.unPackInt8U();
1348
- switch (cmd) {
1349
- case CommCode.chat:
1350
- this.#processChatPacket(packet);
1351
- break;
1352
1498
 
1353
- case CommCode.addPlayer:
1354
- this.#processAddPlayerPacket(packet);
1355
- break;
1499
+ let lastCommand = 0;
1500
+ let abort = false;
1356
1501
 
1357
- case CommCode.respawn:
1358
- this.#processRespawnPacket(packet);
1359
- break;
1502
+ while (CommIn.isMoreDataAvailable() && !abort) {
1503
+ const cmd = CommIn.unPackInt8U();
1360
1504
 
1361
- case CommCode.swapWeapon:
1362
- this.#processSwapWeaponPacket(packet);
1363
- break;
1505
+ switch (cmd) {
1506
+ case CommCode.syncThem:
1507
+ this.#processExternalSyncPacket(packet);
1508
+ break;
1364
1509
 
1365
- case CommCode.syncThem:
1366
- this.#processExternalSyncPacket(packet);
1367
- break;
1510
+ case CommCode.fire:
1511
+ this.#processFirePacket(packet);
1512
+ break;
1368
1513
 
1369
- case CommCode.pause:
1370
- this.#processPausePacket(packet);
1371
- break;
1514
+ case CommCode.hitThem:
1515
+ this.#processHitThemPacket(packet);
1516
+ break;
1372
1517
 
1373
- case CommCode.die:
1374
- this.#processDeathPacket(packet);
1375
- break;
1518
+ case CommCode.syncMe:
1519
+ this.#processSyncMePacket(packet);
1520
+ break;
1376
1521
 
1377
- case CommCode.fire:
1378
- this.#processFirePacket(packet);
1379
- break;
1522
+ case CommCode.hitMe:
1523
+ this.#processHitMePacket(packet);
1524
+ break;
1380
1525
 
1381
- case CommCode.collectItem:
1382
- this.#processCollectPacket(packet);
1383
- break;
1526
+ case CommCode.swapWeapon:
1527
+ this.#processSwapWeaponPacket(packet);
1528
+ break;
1384
1529
 
1385
- case CommCode.hitThem:
1386
- this.#processHitThemPacket(packet);
1387
- break;
1530
+ case CommCode.collectItem:
1531
+ this.#processCollectPacket(packet);
1532
+ break;
1388
1533
 
1389
- case CommCode.hitMe:
1390
- this.#processHitMePacket(packet);
1391
- break;
1534
+ case CommCode.respawn:
1535
+ this.#processRespawnPacket(packet);
1536
+ break;
1392
1537
 
1393
- case CommCode.syncMe:
1394
- this.#processSyncMePacket(packet);
1395
- break;
1538
+ case CommCode.die:
1539
+ this.#processDeathPacket(packet);
1540
+ break;
1396
1541
 
1397
- case CommCode.eventModifier:
1398
- this.#processEventModifierPacket(packet);
1399
- break;
1542
+ case CommCode.pause:
1543
+ this.#processPausePacket(packet);
1544
+ break;
1400
1545
 
1401
- case CommCode.removePlayer:
1402
- this.#processRemovePlayerPacket(packet);
1403
- break;
1546
+ case CommCode.chat:
1547
+ this.#processChatPacket(packet);
1548
+ break;
1404
1549
 
1405
- case CommCode.metaGameState:
1406
- this.#processGameStatePacket(packet);
1407
- break;
1550
+ case CommCode.addPlayer:
1551
+ this.#processAddPlayerPacket(packet);
1552
+ break;
1408
1553
 
1409
- case CommCode.beginShellStreak:
1410
- this.#processBeginStreakPacket(packet);
1411
- break;
1554
+ case CommCode.removePlayer:
1555
+ this.#processRemovePlayerPacket(packet);
1556
+ break;
1412
1557
 
1413
- case CommCode.endShellStreak:
1414
- this.#processEndStreakPacket(packet);
1415
- break;
1558
+ case CommCode.eventModifier:
1559
+ this.#processEventModifierPacket(packet);
1560
+ break;
1416
1561
 
1417
- case CommCode.hitMeHardBoiled:
1418
- this.#processHitShieldPacket(packet);
1419
- break;
1562
+ case CommCode.metaGameState:
1563
+ this.#processGameStatePacket(packet);
1564
+ break;
1420
1565
 
1421
- case CommCode.gameOptions:
1422
- this.#processGameOptionsPacket(packet);
1423
- break;
1566
+ case CommCode.beginShellStreak:
1567
+ this.#processBeginStreakPacket(packet);
1568
+ break;
1424
1569
 
1425
- case CommCode.gameAction:
1426
- this.#processGameActionPacket(packet);
1427
- break;
1570
+ case CommCode.endShellStreak:
1571
+ this.#processEndStreakPacket(packet);
1572
+ break;
1428
1573
 
1429
- case CommCode.ping:
1430
- this.#processPingPacket(packet);
1431
- break;
1574
+ case CommCode.hitMeHardBoiled:
1575
+ this.#processHitShieldPacket(packet);
1576
+ break;
1432
1577
 
1433
- case CommCode.switchTeam:
1434
- this.#processSwitchTeamPacket(packet);
1435
- break;
1578
+ case CommCode.gameOptions:
1579
+ this.#processGameOptionsPacket(packet);
1580
+ break;
1436
1581
 
1437
- case CommCode.changeCharacter:
1438
- this.#processChangeCharacterPacket(packet);
1439
- break;
1582
+ case CommCode.ping:
1583
+ this.#processPingPacket(packet);
1584
+ break;
1440
1585
 
1441
- case CommCode.updateBalance:
1442
- this.#processUpdateBalancePacket(packet);
1443
- break;
1586
+ case CommCode.switchTeam:
1587
+ this.#processSwitchTeamPacket(packet);
1588
+ break;
1444
1589
 
1445
- case CommCode.respawnDenied:
1446
- this.#processRespawnDeniedPacket(packet);
1447
- break;
1590
+ case CommCode.changeCharacter:
1591
+ this.#processChangeCharacterPacket(packet);
1592
+ break;
1448
1593
 
1449
- case CommCode.reload:
1450
- this.processReloadPacket(null, true);
1451
- break;
1594
+ case CommCode.reload:
1595
+ this.processReloadPacket(null, true);
1596
+ break;
1452
1597
 
1453
- case CommCode.spawnItem:
1454
- this.#processSpawnItemPacket();
1455
- break;
1598
+ case CommCode.explode:
1599
+ this.#processExplodePacket();
1600
+ break;
1456
1601
 
1457
- case CommCode.melee:
1458
- this.#processMeleePacket();
1459
- break;
1602
+ case CommCode.throwGrenade:
1603
+ this.#processThrowGrenadePacket();
1604
+ break;
1605
+
1606
+ case CommCode.spawnItem:
1607
+ this.#processSpawnItemPacket();
1608
+ break;
1609
+
1610
+ case CommCode.melee:
1611
+ this.#processMeleePacket();
1612
+ break;
1613
+
1614
+ case CommCode.updateBalance:
1615
+ this.#processUpdateBalancePacket(packet);
1616
+ break;
1617
+
1618
+ case CommCode.gameAction:
1619
+ this.#processGameActionPacket(packet);
1620
+ break;
1621
+
1622
+ case CommCode.requestGameOptions:
1623
+ this.#processGameRequestOptionsPacket();
1624
+ break;
1625
+
1626
+ case CommCode.respawnDenied:
1627
+ this.#processRespawnDeniedPacket(packet);
1628
+ break;
1460
1629
 
1461
- case CommCode.clientReady:
1462
- case CommCode.expireUpgrade:
1463
- case CommCode.musicInfo:
1464
- case CommCode.challengeCompleted:
1465
1630
  // we do not plan to implement these
1466
1631
  // for more info, see comm/codes.js
1467
- break;
1632
+ case CommCode.clientReady:
1633
+ case CommCode.expireUpgrade:
1634
+ break;
1468
1635
 
1469
- case CommCode.explode:
1470
- case CommCode.throwGrenade:
1471
- // do nothing
1472
- break;
1636
+ case CommCode.musicInfo:
1637
+ CommIn.unPackLongString();
1638
+ break;
1473
1639
 
1474
- default:
1475
- console.error(`handlePacket: I got but did not handle a: ${Object.entries(CommCode).filter(([, v]) => v == cmd)[0][0]}`);
1476
- break;
1640
+ case CommCode.challengeCompleted:
1641
+ CommIn.unPackInt8U();
1642
+ CommIn.unPackInt8U();
1643
+ break;
1644
+
1645
+ default:
1646
+ console.error(`handlePacket: I got but did not handle a: ${Object.keys(CommCode).find(k => CommCode[k] === cmd)}`);
1647
+ if (lastCommand) console.error(`handlePacket: It may be a result of the ${lastCommand} command.`);
1648
+ abort = true
1649
+ break;
1650
+ }
1651
+
1652
+ lastCommand = Object.keys(CommCode).find(k => CommCode[k] === cmd);
1653
+ }
1654
+ }
1655
+
1656
+ async checkChiknWinner() {
1657
+ const response = await queryServices({
1658
+ cmd: 'chicknWinnerReady',
1659
+ id: this.account.id,
1660
+ sessionId: this.account.sessionId
1661
+ });
1662
+
1663
+ this.account.cw.limit = response.limit;
1664
+ this.account.cw.atLimit = response.limit > 3;
1665
+
1666
+ // if there is a "span", that means that it's under the daily limit and you can play again soon
1667
+ // if there is a "period", that means that the account is done for the day and must wait a long time
1668
+ this.account.cw.secondsUntilPlay = response.span || response.period || 0;
1669
+ this.account.cw.canPlayAgain = Date.now() + (this.account.cw.secondsUntilPlay * 1000);
1670
+
1671
+ return this.account.cw;
1672
+ }
1673
+
1674
+ async playChiknWinner() {
1675
+ if (this.account.cw.atLimit || this.account.cw.limit > ChiknWinnerDailyLimit) return 'hit_daily_limit';
1676
+ if (this.account.cw.canPlayAgain > Date.now()) return 'on_cooldown';
1677
+
1678
+ const response = await queryServices({
1679
+ cmd: 'incentivizedVideoReward',
1680
+ firebaseId: this.account.firebaseId,
1681
+ id: this.account.id,
1682
+ sessionId: this.account.sessionId,
1683
+ token: null
1684
+ }, this.proxy, this.instance);
1685
+
1686
+ if (response.error) {
1687
+ if (response.error == 'RATELIMITED') {
1688
+ await this.checkChiknWinner();
1689
+ return 'on_cooldown';
1690
+ } else if (response.error == 'SESSION_EXPIRED') {
1691
+ return 'session_expired';
1692
+ } else {
1693
+ console.error('Unknown Chikn Winner response', response);
1694
+ return 'unknown_error';
1695
+ }
1696
+ }
1697
+
1698
+ if (response.reward) {
1699
+ this.account.eggBalance += response.reward.eggsGiven;
1700
+ response.reward.itemIds.forEach((id) => this.account.ownedItemIds.push(id));
1701
+
1702
+ await this.checkChiknWinner();
1703
+
1704
+ return response.reward;
1477
1705
  }
1706
+
1707
+ console.error('Unknown Chikn Winner response', response);
1708
+ return 'unknown_error';
1709
+ }
1710
+
1711
+ async resetChiknWinner() {
1712
+ if (this.account.eggBalance < 200) return 'not_enough_eggs';
1713
+ if (!this.account.cw.atLimit) return 'not_at_limit';
1714
+
1715
+ const response = await queryServices({
1716
+ cmd: 'chwReset',
1717
+ sessionId: this.account.sessionId
1718
+ });
1719
+
1720
+ if (response.result !== 'SUCCESS') {
1721
+ console.error('Unknown Chikn Winner reset response', response);
1722
+ return 'unknown_error';
1723
+ }
1724
+
1725
+ this.account.eggBalance -= 200;
1726
+ await this.checkChiknWinner();
1727
+
1728
+ return this.account.cw;
1729
+ }
1730
+
1731
+ canSee(target) {
1732
+ if (!this.intents.includes(this.Intents.PATHFINDING)) throw new Error('You must have the PATHFINDING intent to use this method.');
1733
+ return this.pathing.nodeList.hasLineOfSight(this.me.position, target.position);
1734
+ }
1735
+
1736
+ getBestTarget() {
1737
+ const options = Object.values(this.players)
1738
+ .filter((player) => player)
1739
+ .filter((player) => player !== this.me)
1740
+ .filter((player) => player.playing)
1741
+ .filter((player) => player.hp > 0)
1742
+ .filter((player) => player.name !== this.me.name)
1743
+ .filter((player) => this.me.team === 0 || player.team !== this.me.team)
1744
+ .filter((player) => this.canSee(player));
1745
+
1746
+ let minDistance = 200;
1747
+ let targetPlayer = null;
1748
+
1749
+ for (const player of options) {
1750
+ const dx = player.position.x - this.me.position.x;
1751
+ const dy = player.position.y - this.me.position.y;
1752
+ const dz = player.position.z - this.me.position.z;
1753
+
1754
+ const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
1755
+
1756
+ if (distance < minDistance) {
1757
+ minDistance = distance;
1758
+ targetPlayer = player;
1759
+ }
1760
+ }
1761
+
1762
+ return targetPlayer;
1763
+ }
1764
+
1765
+ async refreshBalance() {
1766
+ const result = await queryServices({
1767
+ cmd: 'checkBalance',
1768
+ firebaseId: this.account.firebaseId,
1769
+ sessionId: this.account.sessionId
1770
+ }, this.proxy, this.instance);
1771
+
1772
+ this.account.eggBalance = result.currentBalance;
1773
+
1774
+ return result.currentBalance;
1775
+ }
1776
+
1777
+ async redeemCode(code) {
1778
+ const result = await queryServices({
1779
+ cmd: 'redeem',
1780
+ firebaseId: this.account.firebaseId,
1781
+ sessionId: this.account.sessionId,
1782
+ id: this.account.id,
1783
+ code
1784
+ }, this.proxy, this.instance);
1785
+
1786
+ if (result.result === 'SUCCESS') {
1787
+ this.account.eggBalance = result.eggs_given;
1788
+ result.item_ids.forEach((id) => this.account.ownedItemIds.push(id));
1789
+
1790
+ return {
1791
+ result,
1792
+ eggsGiven: result.eggs_given,
1793
+ itemIds: result.item_ids
1794
+ };
1795
+ } else return result;
1796
+ }
1797
+
1798
+ async claimURLReward(reward) {
1799
+ const result = await queryServices({
1800
+ cmd: 'urlRewardParams',
1801
+ firebaseId: this.account.firebaseId,
1802
+ sessionId: this.account.sessionId,
1803
+ reward
1804
+ }, this.proxy, this.instance);
1805
+
1806
+ if (result.result === 'SUCCESS') {
1807
+ this.account.eggBalance += result.eggsGiven;
1808
+ result.itemIds.forEach((id) => this.account.ownedItemIds.push(id));
1809
+ }
1810
+
1811
+ return result;
1812
+ }
1813
+
1814
+ async claimSocialReward(rewardTag) {
1815
+ const result = await queryServices({
1816
+ cmd: 'reward',
1817
+ firebaseId: this.account.firebaseId,
1818
+ sessionId: this.account.sessionId,
1819
+ rewardTag
1820
+ }, this.proxy, this.instance);
1821
+
1822
+ if (result.result === 'SUCCESS') {
1823
+ this.account.eggBalance += result.eggsGiven;
1824
+ result.itemIds.forEach((id) => this.account.ownedItemIds.push(id));
1825
+ }
1826
+
1827
+ return result;
1828
+ }
1829
+
1830
+ async buyItem(itemId) {
1831
+ const result = await queryServices({
1832
+ cmd: 'buy',
1833
+ firebaseId: this.account.firebaseId,
1834
+ sessionId: this.account.sessionId,
1835
+ itemId,
1836
+ save: true
1837
+ }, this.proxy, this.instance);
1838
+
1839
+ if (result.result === 'SUCCESS') {
1840
+ this.account.eggBalance = result.currentBalance;
1841
+ this.account.ownedItemIds.push(result.itemId);
1842
+ }
1843
+
1844
+ return result;
1478
1845
  }
1479
1846
  }
1480
1847