yolkbot 0.1.0-alpha.9 → 0.1.1-alpha.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.
Files changed (49) hide show
  1. package/build/browser.js +6 -6
  2. package/package.json +17 -5
  3. package/src/api.js +97 -23
  4. package/src/bot/GamePlayer.js +2 -2
  5. package/src/bot.js +892 -483
  6. package/src/comm/Codes.js +73 -0
  7. package/src/constants/challenges.js +1822 -0
  8. package/src/constants/index.js +23 -1
  9. package/src/constants/items.js +663 -264
  10. package/src/constants/maps.js +57 -18
  11. package/src/dispatches/BootPlayerDispatch.js +1 -1
  12. package/src/dispatches/ChatDispatch.js +1 -1
  13. package/src/dispatches/GameOptionsDispatch.js +1 -1
  14. package/src/dispatches/GoToAmmoDispatch.js +44 -0
  15. package/src/dispatches/GoToCoopDispatch.js +44 -0
  16. package/src/dispatches/GoToGrenadeDispatch.js +44 -0
  17. package/src/dispatches/GoToPlayerDispatch.js +5 -1
  18. package/src/dispatches/GoToSpatulaDispatch.js +5 -1
  19. package/src/dispatches/LookAtPosDispatch.js +0 -2
  20. package/src/dispatches/LookDispatch2.js +18 -0
  21. package/src/dispatches/MeleeDispatch.js +1 -1
  22. package/src/dispatches/PauseDispatch.js +1 -1
  23. package/src/dispatches/ReloadDispatch.js +14 -2
  24. package/src/dispatches/ReportPlayerDispatch.js +1 -1
  25. package/src/dispatches/SaveLoadoutDispatch.js +11 -34
  26. package/src/dispatches/SpawnDispatch.js +1 -1
  27. package/src/dispatches/SwapWeaponDispatch.js +1 -1
  28. package/src/dispatches/SwitchTeamDispatch.js +1 -1
  29. package/src/dispatches/ThrowGrenadeDispatch.js +1 -1
  30. package/src/dispatches/index.js +9 -0
  31. package/src/matchmaker.js +11 -2
  32. package/src/pathing/mapnode.js +83 -250
  33. package/src/socket.js +1 -1
  34. package/src/types/api.d.ts +2 -16
  35. package/src/types/bot/GamePlayer.d.ts +25 -21
  36. package/src/types/bot.d.ts +122 -54
  37. package/src/types/constants/challenges.d.ts +19 -0
  38. package/src/types/constants/guns.d.ts +240 -0
  39. package/src/types/constants/index.d.ts +100 -0
  40. package/src/types/constants/items.d.ts +21 -0
  41. package/src/types/constants/maps.d.ts +15 -0
  42. package/src/types/dispatches/GoToAmmoDispatch.d.ts +8 -0
  43. package/src/types/dispatches/GoToCoopDispatch.d.ts +8 -0
  44. package/src/types/dispatches/GoToGrenadeDispatch.d.ts +8 -0
  45. package/src/types/dispatches/MovementDispatch.d.ts +2 -0
  46. package/src/types/dispatches/index.d.ts +45 -1
  47. package/src/types/matchmaker.d.ts +19 -14
  48. package/src/types/socket.d.ts +7 -0
  49. package/src/types/gun.d.ts +0 -131
package/src/bot.js CHANGED
@@ -1,4 +1,4 @@
1
- import { loginAnonymously, loginWithCredentials } from '#api';
1
+ import { createAccount, loginAnonymously, loginWithCredentials, loginWithRefreshToken, 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,32 @@ 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
+ PLAYER_HEALTH: 7,
45
+ PACKET_HOOK: 8
46
+ }
47
+
35
48
  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
49
+ static Intents = intents;
50
+ Intents = intents;
51
+
44
52
  constructor(params = {}) {
45
53
  if (params.proxy && IsBrowser)
46
54
  throw new Error('proxies do not work and hence are not supported in the browser');
47
55
 
56
+ this.intents = params.intents || [];
57
+
58
+ this.instance = params.instance || 'shellshock.io';
48
59
  this.proxy = params.proxy || '';
49
- this.name = params.name || Math.random().toString(36).substring(8);
50
60
 
51
- this.autoPing = params.doPing || true;
52
61
  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';
62
+ this.updateInterval = params.updateInterval || 16.5;
59
63
 
60
64
  this._hooks = {};
61
65
  this._globalHooks = [];
@@ -63,10 +67,10 @@ export class Bot {
63
67
 
64
68
  // private information NOT FOR OTHER PLAYERS!!
65
69
  this.state = {
66
- loggedIn: false,
67
- gameFound: false,
70
+ // kept for specifying socket open sequence
71
+ name: '',
68
72
 
69
- // once we implement more packets these may be moved to "players"
73
+ // tracking for dispatch checks
70
74
  reloading: false,
71
75
  swappingGun: false,
72
76
  usingMelee: false,
@@ -81,6 +85,7 @@ export class Bot {
81
85
  this.game = {
82
86
  raw: {}, // matchmaker response
83
87
  code: '',
88
+ socket: null,
84
89
 
85
90
  // data given on sign in
86
91
  gameModeId: 0, // assume ffa
@@ -98,8 +103,10 @@ export class Bot {
98
103
  },
99
104
  availability: 'both',
100
105
  numPlayers: '18',
106
+
101
107
  raw: {},
102
- nodes: {}
108
+ nodes: {},
109
+ zones: []
103
110
  },
104
111
  playerLimit: 0,
105
112
  isGameOwner: false,
@@ -135,7 +142,8 @@ export class Bot {
135
142
 
136
143
  // data from kotc
137
144
  stage: CoopStates.capturing,
138
- activeZone: 0,
145
+ zoneNumber: 0,
146
+ activeZone: [],
139
147
  capturing: 0,
140
148
  captureProgress: 0,
141
149
  numCapturing: 0,
@@ -145,10 +153,23 @@ export class Bot {
145
153
 
146
154
  this.account = {
147
155
  // used for auth
156
+ id: 0,
148
157
  firebaseId: '',
149
158
  sessionId: '',
150
159
  session: '',
151
160
 
161
+ // raw login params
162
+ email: '',
163
+ password: '',
164
+
165
+ // chikn winner related info
166
+ cw: {
167
+ atLimit: false,
168
+ limit: 0,
169
+ secondsUntilPlay: 0, // short cooldown, in seconds
170
+ canPlayAgain: Date.now()
171
+ },
172
+
152
173
  // used for skin changing
153
174
  loadout: {
154
175
  hatId: null,
@@ -184,21 +205,17 @@ export class Bot {
184
205
  this._dispatches = [];
185
206
  this._packetQueue = [];
186
207
 
187
- this.gameSocket = null;
208
+ this.matchmaker = null;
188
209
 
189
210
  this.ping = 0;
190
211
  this.lastPingTime = -1;
191
212
 
192
213
  this.lastDeathTime = -1;
193
214
  this.lastChatTime = -1;
194
-
195
215
  this.lastUpdateTime = -1;
196
- this.nUpdates = 0;
197
216
 
198
217
  this.controlKeys = 0;
199
218
 
200
- this.initTime = Date.now();
201
-
202
219
  this.pathing = {
203
220
  nodeList: null,
204
221
  followingPath: false,
@@ -206,90 +223,111 @@ export class Bot {
206
223
  activeNode: null,
207
224
  activeNodeIdx: 0
208
225
  }
226
+
227
+ if (this.intents.includes(this.Intents.PLAYER_HEALTH)) this.healthIntervalId = setInterval(() => {
228
+ if (!this.players) return;
229
+
230
+ for (const player of Object.values(this.players)) {
231
+ if (player.playing && player.hp > 0) {
232
+ const regenSpeed = 0.1 * (this.game.isPrivate ? this.game.options.healthRegen : 1);
233
+
234
+ if (player.streakRewards.includes(ShellStreaks.OverHeal)) {
235
+ player.hp = Math.max(100, player.hp - regenSpeed);
236
+ } else {
237
+ player.hp = Math.min(100, player.hp + regenSpeed);
238
+ }
239
+ }
240
+ }
241
+ }, 33);
209
242
  }
210
243
 
211
- async login(email, pass) {
212
- // const time = Date.now();
244
+ dispatch(disp) {
245
+ this._dispatches.push(disp);
246
+ }
247
+
248
+ async createAccount(email, pass) {
249
+ this.account.email = email;
250
+ this.account.password = pass;
213
251
 
214
- this.email = email;
215
- this.pass = pass;
252
+ const loginData = await createAccount(email, pass, this.proxy, this.instance);
253
+ return await this.#processLoginData(loginData);
254
+ }
255
+
256
+ async login(email, pass) {
257
+ this.account.email = email;
258
+ this.account.password = pass;
216
259
 
217
260
  const loginData = await loginWithCredentials(email, pass, this.proxy, this.instance);
261
+ return await this.#processLoginData(loginData);
262
+ }
263
+
264
+ async loginWithRefreshToken(refreshToken) {
265
+ const loginData = await loginWithRefreshToken(refreshToken, this.proxy, this.instance);
266
+ return await this.#processLoginData(loginData);
267
+ }
218
268
 
269
+ async loginAnonymously() {
270
+ delete this.account.email;
271
+ delete this.account.password;
272
+
273
+ const loginData = await loginAnonymously(this.proxy, this.instance);
274
+ return await this.#processLoginData(loginData);
275
+ }
276
+
277
+ async #processLoginData(loginData) {
219
278
  if (typeof loginData == 'string') {
220
- this.#emit('authFail', loginData);
279
+ this.emit('authFail', loginData);
221
280
  return false;
222
281
  }
223
282
 
224
283
  if (loginData.banRemaining) {
225
- this.#emit('banned', loginData.banRemaining);
284
+ this.emit('banned', loginData.banRemaining);
226
285
  return false;
227
286
  }
228
287
 
229
- this.state.loggedIn = true;
230
-
231
288
  this.account.rawLoginData = loginData;
232
289
 
233
290
  this.account.accountAge = loginData.accountAge;
234
291
  this.account.eggBalance = loginData.currentBalance;
235
292
  this.account.emailVerified = loginData.emailVerified;
236
293
  this.account.firebaseId = loginData.firebaseId;
294
+ this.account.id = loginData.id;
237
295
  this.account.loadout = loginData.loadout;
238
296
  this.account.ownedItemIds = loginData.ownedItemIds;
297
+ this.account.session = loginData.session;
239
298
  this.account.sessionId = loginData.sessionId;
240
299
  this.account.vip = loginData.upgradeProductId && !loginData.upgradeIsExpired;
241
300
 
242
- // console.log('Logged in successfully. Time:', Date.now() - time, 'ms');
301
+ if (this.intents.includes(this.Intents.STATS)) this.account.stats = {
302
+ lifetime: loginData.statsLifetime,
303
+ monthly: loginData.statsCurrent
304
+ };
243
305
 
244
- return this.account;
245
- }
306
+ if (this.intents.includes(this.Intents.CHALLENGES)) {
307
+ this.account.challenges = [];
246
308
 
247
- dispatch(disp) {
248
- this._dispatches.push(disp);
249
- }
309
+ const { Challenges } = await import('./constants/challenges.js');
250
310
 
251
- drain() {
252
- for (let i = 0; i < this._dispatches.length; i++) {
253
- const disp = this._dispatches[i];
254
- if (disp.check(this)) {
255
- disp.execute(this);
256
- this._dispatches.splice(i, 1);
257
- return; // only 1 dispatch per update
258
- } else {
259
- // console.log("Dispatch failed", this.state.joinedGame, this.lastChatTime)
260
- }
261
- }
262
- }
311
+ for (const challenge of loginData.challenges) {
312
+ const challengeData = Challenges.find(c => c.id == challenge.challengeId);
313
+ if (!challengeData) continue;
263
314
 
264
- async #anonLogin() {
265
- const loginData = await loginAnonymously(this.proxy, this.instance);
315
+ delete challenge.playerId;
266
316
 
267
- if (typeof loginData == 'string') {
268
- this.#emit('authFail', loginData);
269
- return false;
317
+ this.account.challenges.push({
318
+ ...challengeData,
319
+ ...challenge
320
+ });
321
+ }
270
322
  }
271
323
 
272
- this.state.loggedIn = true;
273
-
274
- this.account.rawLoginData = loginData;
275
-
276
- this.account.accountAge = loginData.accountAge;
277
- this.account.eggBalance = loginData.currentBalance;
278
- this.account.emailVerified = loginData.emailVerified;
279
- this.account.firebaseId = loginData.firebaseId;
280
- this.account.loadout = loginData.loadout;
281
- this.account.ownedItemIds = loginData.ownedItemIds;
282
- this.account.session = loginData.session;
283
- this.account.sessionId = loginData.sessionId;
284
- this.account.vip = false;
285
-
286
324
  return this.account;
287
325
  }
288
326
 
289
327
  async initMatchmaker() {
290
- if (!this.state.loggedIn) {
328
+ if (this.account.id == 0) {
291
329
  // console.log('Not logged in, attempting to create anonymous user...');
292
- const anonLogin = await this.#anonLogin();
330
+ const anonLogin = await this.loginAnonymously();
293
331
  if (!anonLogin) return false;
294
332
  }
295
333
 
@@ -301,7 +339,7 @@ export class Bot {
301
339
  instance: this.instance
302
340
  });
303
341
 
304
- this.matchmaker.on('authFail', (data) => this.#emit('authFail', data));
342
+ this.matchmaker.on('authFail', (data) => this.emit('authFail', data));
305
343
 
306
344
  await this.matchmaker.getRegions();
307
345
  }
@@ -312,34 +350,33 @@ export class Bot {
312
350
  async #joinGameWithCode(code) {
313
351
  if (!await this.initMatchmaker()) return false;
314
352
 
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
353
+ return await new Promise((resolve) => {
354
+ const listener = (mes) => {
355
+ if (mes.command == 'gameFound') {
356
+ this.matchmaker.off('msg', listener);
320
357
 
321
- this.gameFound = true;
322
- }
358
+ this.game.raw = mes;
359
+ this.game.code = code;
323
360
 
324
- if (mes.error && mes.error == 'gameNotFound')
325
- throw new Error(`Game ${code} not found (likely expired).`)
326
- };
327
-
328
- this.matchmaker.on('msg', listener);
361
+ resolve();
362
+ }
329
363
 
330
- this.matchmaker.send({
331
- command: 'joinGame',
332
- id: code,
333
- observe: false,
334
- sessionId: this.account.sessionId
335
- })
364
+ if (mes.error && mes.error == 'gameNotFound')
365
+ throw new Error(`Game ${code} not found (likely expired).`)
366
+ };
336
367
 
337
- while (!this.gameFound) await new Promise(r => setTimeout(r, 10));
368
+ this.matchmaker.on('msg', listener);
338
369
 
339
- this.matchmaker.off('msg', listener);
370
+ this.matchmaker.send({
371
+ command: 'joinGame',
372
+ id: code,
373
+ observe: false,
374
+ sessionId: this.account.sessionId
375
+ })
376
+ });
340
377
  }
341
378
 
342
- async #onGameMesssage(msg) { // to minify with vscode
379
+ async #onGameMesssage(msg) {
343
380
  CommIn.init(msg.data);
344
381
 
345
382
  let out;
@@ -350,7 +387,7 @@ export class Bot {
350
387
  out = CommOut.getBuffer();
351
388
  out.packInt8(CommCode.joinGame);
352
389
 
353
- out.packString(this.name); // name
390
+ out.packString(this.state.name); // name
354
391
  out.packString(this.game.raw.uuid); // game id
355
392
 
356
393
  out.packInt8(0); // hidebadge
@@ -360,10 +397,10 @@ export class Bot {
360
397
  out.packString(this.account.firebaseId); // firebase id
361
398
  out.packString(this.account.sessionId); // session id
362
399
 
363
- out.send(this.gameSocket);
400
+ out.send(this.game.socket);
364
401
  break;
365
402
 
366
- case CommCode.gameJoined:
403
+ case CommCode.gameJoined: {
367
404
  this.me.id = CommIn.unPackInt8U();
368
405
  // console.log("My id is:", this.me.id);
369
406
  this.me.team = CommIn.unPackInt8U();
@@ -373,9 +410,10 @@ export class Bot {
373
410
  // console.log("Gametype:", this.game.gameMode, this.game.gameModeId);
374
411
  this.game.mapIdx = CommIn.unPackInt8U();
375
412
  this.game.map = Maps[this.game.mapIdx];
376
- if (!this.disablePathing) {
413
+ if (this.intents.includes(this.Intents.PATHFINDING)) {
377
414
  this.game.map.raw = await this.#fetchMap(this.game.map.filename, this.game.map.hash);
378
415
  this.pathing.nodeList = new NodeList(this.game.map.raw);
416
+ if (this.game.gameModeId === GameModes.kotc) this.#initKotcZones();
379
417
  }
380
418
  // console.log("Map:", this.game.map);
381
419
  this.game.playerLimit = CommIn.unPackInt8U();
@@ -389,34 +427,34 @@ export class Bot {
389
427
  this.state.joinedGame = true;
390
428
  this.lastDeathTime = Date.now();
391
429
 
430
+ const out = CommOut.getBuffer();
431
+ out.packInt8(CommCode.clientReady);
432
+ out.send(this.game.socket);
433
+
434
+ this.game.socket.onmessage = (msg) => this._packetQueue.push(msg.data);
435
+
436
+ if (this.autoUpdate)
437
+ setInterval(() => this.update(), this.updateInterval);
438
+
439
+ if (this.intents.includes(this.Intents.PING)) {
440
+ const out = CommOut.getBuffer();
441
+ out.packInt8(CommCode.ping);
442
+ out.send(this.game.socket);
443
+ this.lastPingTime = Date.now();
444
+ }
392
445
  break;
446
+ }
393
447
 
394
448
  case CommCode.eventModifier:
395
449
  // console.log("Echoed eventModifier"); // why the fuck do you need to do this
396
450
  out = CommOut.getBuffer();
397
451
  out.packInt8(CommCode.eventModifier);
398
- out.send(this.gameSocket);
452
+ out.send(this.game.socket);
399
453
  break;
400
454
 
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
- });
455
+ case CommCode.requestGameOptions:
456
+ this.#processGameRequestOptionsPacket();
418
457
  break;
419
- }
420
458
 
421
459
  default:
422
460
  try {
@@ -449,52 +487,50 @@ export class Bot {
449
487
 
450
488
  if (mapIdx == -1) throw new Error('invalid map, see the Maps constant for a list')
451
489
 
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
- };
490
+ await new Promise((resolve) => {
491
+ const listener = (msg) => {
492
+ if (msg.command == 'gameFound') {
493
+ this.matchmaker.off('msg', listener);
461
494
 
462
- this.matchmaker.on('msg', listener);
495
+ this.game.raw = msg;
496
+ this.game.code = this.game.raw.id;
463
497
 
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
- });
498
+ resolve();
499
+ }
500
+ };
473
501
 
474
- while (!this.gameFound) await new Promise(r => setTimeout(r, 10));
502
+ this.matchmaker.on('msg', listener);
475
503
 
476
- this.matchmaker.off('msg', listener);
504
+ this.matchmaker.send({
505
+ command: 'findGame',
506
+ region: opts.region,
507
+ playType: PlayTypes.createPrivate,
508
+ gameType: GameModes[opts.mode],
509
+ sessionId: this.account.sessionId,
510
+ noobLobby: false,
511
+ map: mapIdx
512
+ });
513
+ });
477
514
 
478
515
  return this.game.raw;
479
516
  }
480
517
 
481
- async join(data) {
518
+ async join(name, data) {
519
+ this.state.name = name || 'yolkbot';
520
+
482
521
  if (typeof data == 'string') {
483
522
  if (data.includes('#')) data = data.split('#')[1]; // stupid shell kids put in full links
484
523
  // this is a string code that we can pass and get the needed info from
485
524
  await this.#joinGameWithCode(data);
486
525
  } else if (typeof data == 'object') {
487
- if (!this.state.loggedIn) {
526
+ if (this.account.id == 0) {
488
527
  // console.log('passed an object but you still need to be logged in!!')
489
- await this.#anonLogin();
528
+ await this.loginAnonymously();
490
529
  }
491
530
 
492
531
  // this is a game object that we can pass and get the needed info from
493
532
  this.game.raw = data;
494
533
  this.game.code = this.game.raw.id;
495
- delete this.game.raw.command;
496
-
497
- this.gameFound = true;
498
534
  }
499
535
 
500
536
  if (!this.game.raw.id || !this.game.raw.subdomain)
@@ -502,115 +538,115 @@ export class Bot {
502
538
 
503
539
  // console.log(`Joining ${this.game.raw.id} using proxy ${this.proxy || 'none'}`);
504
540
 
505
- this.gameSocket = new yolkws(`wss://${this.game.raw.subdomain}.${this.instance}/game/${this.game.raw.id}`, this.proxy);
541
+ const attempt = async () => {
542
+ try {
543
+ this.game.socket = new yolkws(`wss://${this.game.raw.subdomain}.${this.instance}/game/${this.game.raw.id}`, this.proxy);
544
+ } catch {
545
+ await new Promise((resolve) => setTimeout(resolve, 100));
546
+ await attempt();
547
+ }
548
+ }
549
+
550
+ await attempt();
506
551
 
507
- this.gameSocket.binaryType = 'arraybuffer';
552
+ this.game.socket.binaryType = 'arraybuffer';
508
553
 
509
- this.gameSocket.onopen = () => {
554
+ this.game.socket.onopen = () => {
510
555
  // console.log('Successfully connected to game server.');
511
556
  }
512
557
 
513
- this.gameSocket.onmessage = this.#onGameMesssage.bind(this);
558
+ this.game.socket.onmessage = this.#onGameMesssage.bind(this);
514
559
 
515
- this.gameSocket.onclose = (e) => {
560
+ this.game.socket.onclose = (e) => {
516
561
  // console.log('Game socket closed:', e.code, Object.entries(CloseCode).filter(([, v]) => v == e.code));
517
- this.#emit('close', e.code);
518
- }
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();
562
+ this.emit('close', e.code);
538
563
  }
539
564
  }
540
565
 
541
- update() {
542
- if (!this.state.joinedGame) { throw new Error('Not playing, can\'t update. '); }
543
-
544
- this.nUpdates++;
566
+ #processPathfinding() {
567
+ const myPositionStr = Object.entries(this.me.position).map(entry => Math.floor(entry[1])).join(',');
545
568
 
546
- if (this._packetQueue.length === 0 && this._dispatches.length === 0) return;
569
+ if (myPositionStr == this.pathing.activePath[this.pathing.activePath.length - 1].positionStr()) {
570
+ // console.log('Completed path to', this.pathing.activePath[this.pathing.activePath.length - 1].position);
571
+ this.pathing.followingPath = false;
572
+ this.pathing.activePath = null;
573
+ this.pathing.activeNode = null;
574
+ this.pathing.activeNodeIdx = 0;
547
575
 
548
- let packet;
549
- while ((packet = this._packetQueue.shift()) !== undefined) { this.#handlePacket(packet); }
550
-
551
- this.drain();
552
-
553
- if (this.pathing.followingPath && !this.disablePathing) {
554
- const myPositionStr = Object.entries(this.me.position).map(entry => Math.floor(entry[1])).join(',');
555
-
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));
576
+ this.dispatch(new MovementDispatch(0));
577
+ } else {
578
+ let positionTarget;
579
+ if (this.pathing.activeNodeIdx < this.pathing.activePath.length - 1) {
580
+ positionTarget = this.pathing.activePath[this.pathing.activeNodeIdx + 1].flatCenter();
581
+ this.dispatch(new LookAtPosDispatch(positionTarget));
564
582
  } 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
- }
583
+ positionTarget = this.pathing.activePath[this.pathing.activeNodeIdx].flatCenter();
584
+ this.dispatch(new LookAtPosDispatch(positionTarget));
585
+ }
573
586
 
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
- }
587
+ for (const node of this.pathing.activePath) {
588
+ if (node.flatRadialDistance(this.me.position) < 0.1 && node.position.y == Math.floor(this.me.position.y)) {
589
+ if (this.pathing.activePath.indexOf(node) >= this.pathing.activeNodeIdx) {
590
+ this.pathing.activeNodeIdx = this.pathing.activePath.indexOf(node) + 1;
591
+ this.pathing.activeNode = this.pathing.activePath[this.pathing.activeNodeIdx];
592
+ break;
584
593
  } else {
585
- // console.log('Node at', node.position, 'is', node.flatRadialDistance(this.me.position), 'away.')
594
+ // console.log('Close to node that\'s before, idx:',
595
+ // this.pathing.activePath.indexOf(node), 'current:', this.pathing.activeNodeIdx);
586
596
  }
587
- // console.log('activeNode is ', this.pathing.activeNode.flatRadialDistance(this.me.position), 'away');
597
+ } else {
598
+ // console.log('Node at', node.position, 'is', node.flatRadialDistance(this.me.position), 'away.')
588
599
  }
600
+ // console.log('activeNode is ', this.pathing.activeNode.flatRadialDistance(this.me.position), 'away');
601
+ }
589
602
 
590
- if (!(this.controlKeys & Movements.FORWARD)) {
591
- this.dispatch(new MovementDispatch(Movements.FORWARD));
592
- }
603
+ if (!(this.controlKeys & Movements.FORWARD)) {
604
+ this.dispatch(new MovementDispatch(Movements.FORWARD));
593
605
  }
606
+ }
594
607
 
595
- /*let onPath = false;
596
- for (const node of this.pathing.activePath) {
597
- if (node.positionStr() == myPositionStr) {
598
- onPath = true;
599
- break;
608
+ /*let onPath = false;
609
+ for (const node of this.pathing.activePath) {
610
+ if (node.positionStr() == myPositionStr) {
611
+ onPath = true;
612
+ break;
613
+ }
614
+ }
615
+ if (!onPath) {
616
+ console.log('Got off-path somehow');
617
+ this.dispatch(new PathfindDispatch(this.pathing.activePath[this.pathing.activePath.length - 1]));
618
+ this.pathing.followingPath = false;
619
+ this.pathing.activePath = null;
620
+ this.pathing.activeNode = null;
621
+ this.pathing.activeNodeIdx = 0;
622
+ }*/
623
+ }
624
+
625
+ update() {
626
+ if (!this.state.joinedGame) throw new Error('Not playing, can\'t update. ');
627
+
628
+ // process pathfinding
629
+ if (this.pathing.followingPath && this.intents.includes(this.Intents.PATHFINDING)) this.#processPathfinding();
630
+
631
+ // process incoming packets
632
+ while (this._packetQueue.length > 0) this.#handlePacket(this._packetQueue.shift());
633
+
634
+ // process dispatches
635
+ if (this._dispatches.length > 0) {
636
+ for (let i = 0; i < this._dispatches.length; i++) {
637
+ const disp = this._dispatches[i];
638
+ if (disp.check(this)) {
639
+ disp.execute(this);
640
+ this._dispatches.splice(i, 1);
641
+ break; // only 1 dispatch per update
600
642
  }
601
643
  }
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
644
  }
611
645
 
612
- if (Date.now() - this.lastUpdateTime >= 50) {
613
- this.#emit('tick');
646
+ // process syncMe
647
+ const now = Date.now();
648
+ if (now - this.lastUpdateTime >= 50) {
649
+ this.emit('tick');
614
650
 
615
651
  // Send out update packet
616
652
  const out = CommOut.getBuffer();
@@ -624,14 +660,16 @@ export class Bot {
624
660
  out.packRad(this.me.view.pitch); // pitch
625
661
  out.packInt8(100); // fixes commcode issues, does nothing
626
662
  }
627
- out.send(this.gameSocket);
663
+ out.send(this.game.socket);
628
664
 
629
- this.lastUpdateTime = Date.now();
665
+ this.lastUpdateTime = now;
630
666
  this.state.shotsFired = 0;
631
667
  }
632
668
 
633
- let cb;
634
- while ((cb = this._liveCallbacks.shift()) !== undefined) cb();
669
+ while (this._liveCallbacks.length > 0) {
670
+ const cb = this._liveCallbacks.shift();
671
+ cb();
672
+ }
635
673
  }
636
674
 
637
675
  on(event, cb) {
@@ -646,7 +684,7 @@ export class Bot {
646
684
  // these are auth-related codes (liveCallbacks doesn't run during auth)
647
685
  #mustBeInstant = ['authFail', 'banned'];
648
686
 
649
- #emit(event, ...args) {
687
+ emit(event, ...args) {
650
688
  if (this._hooks[event]) {
651
689
  for (const cb of this._hooks[event]) {
652
690
  if (this.#mustBeInstant.includes(event)) cb(...args);
@@ -664,23 +702,25 @@ export class Bot {
664
702
  if (!IsBrowser) {
665
703
  const { existsSync, mkdirSync, readFileSync, writeFileSync } = await import('node:fs');
666
704
  const { join } = await import('node:path');
705
+ const { homedir } = await import('node:os');
667
706
 
668
- if (existsSync(join(import.meta.dirname, '..', 'data', 'cache', 'maps', `${name}-${hash}.json`))) {
669
- return JSON.parse(readFileSync(join(import.meta.dirname, '..', 'data', 'cache', 'maps', `${name}-${hash}.json`), 'utf-8'));
670
- }
707
+ const yolkbotCache = join(homedir(), '.yolkbot');
708
+ const mapCache = join(yolkbotCache, 'maps');
709
+
710
+ if (!existsSync(yolkbotCache)) mkdirSync(yolkbotCache);
711
+ if (!existsSync(mapCache)) mkdirSync(mapCache);
712
+
713
+ const mapFile = join(mapCache, `${name}-${hash}.json`);
671
714
 
672
- console.warn(`Map "${name}" not found in cache, fetching...`);
715
+ if (existsSync(mapFile))
716
+ return JSON.parse(readFileSync(mapFile, 'utf-8'));
717
+
718
+ console.log('map not in cache, IMPORT!!', name, hash);
673
719
 
674
720
  const data = await (await fetch(`https://${this.instance}/maps/${name}.json?${hash}`)).json();
675
721
 
676
- const dir = join(import.meta.dirname, '..', 'data', 'cache', 'maps');
677
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
722
+ writeFileSync(mapFile, JSON.stringify(data, null, 4), { flag: 'w+' });
678
723
 
679
- writeFileSync(
680
- join(dir, `${name}-${hash}.json`),
681
- JSON.stringify(data, null, 4),
682
- { flag: 'w+' }
683
- );
684
724
  return data;
685
725
  } else {
686
726
  const data = await (await fetch(`https://${this.instance}/maps/${name}.json?${hash}`)).json();
@@ -688,6 +728,64 @@ export class Bot {
688
728
  }
689
729
  }
690
730
 
731
+ #initKotcZones() {
732
+ const meshData = this.game.map.raw.data['DYNAMIC.capture-zone.none'];
733
+ if (!meshData) return delete this.game.map.zones;
734
+
735
+ let numCaptureZones = 0;
736
+ const mapData = {};
737
+ const zones = [];
738
+
739
+ for (const cell of meshData) {
740
+ if (!mapData[cell.x]) mapData[cell.x] = {};
741
+ if (!mapData[cell.x][cell.y]) mapData[cell.x][cell.y] = {};
742
+ mapData[cell.x][cell.y][cell.z] = { zone: null };
743
+ }
744
+
745
+ const offsets = [
746
+ { x: -1, z: 0 },
747
+ { x: 1, z: 0 },
748
+ { x: 0, z: -1 },
749
+ { x: 0, z: 1 }
750
+ ];
751
+
752
+ function getMapCellAt(x, y, z) {
753
+ return mapData[x] && mapData[x][y] && mapData[x][y][z] ? mapData[x][y][z] : null;
754
+ }
755
+
756
+ for (const cellA of meshData) {
757
+ if (!mapData[cellA.x][cellA.y][cellA.z].zone) {
758
+ cellA.zone = ++numCaptureZones;
759
+ mapData[cellA.x][cellA.y][cellA.z].zone = cellA.zone;
760
+
761
+ const currentZone = [cellA];
762
+ let hits;
763
+
764
+ do {
765
+ hits = 0;
766
+ for (const cellB of meshData) {
767
+ if (!mapData[cellB.x][cellB.y][cellB.z].zone) {
768
+ for (const o of offsets) {
769
+ const cell = getMapCellAt(cellB.x + o.x, cellB.y, cellB.z + o.z);
770
+ if (cell && cell.zone == cellA.zone) {
771
+ hits++;
772
+ cellB.zone = cellA.zone;
773
+ mapData[cellB.x][cellB.y][cellB.z].zone = cellA.zone;
774
+ currentZone.push(cellB);
775
+ break;
776
+ }
777
+ }
778
+ }
779
+ }
780
+ } while (hits > 0);
781
+
782
+ zones.push(currentZone);
783
+ }
784
+ }
785
+
786
+ this.game.map.zones = zones;
787
+ }
788
+
691
789
  #processChatPacket() {
692
790
  const id = CommIn.unPackInt8U();
693
791
  const msgFlags = CommIn.unPackInt8U();
@@ -695,7 +793,7 @@ export class Bot {
695
793
  const player = this.players[Object.keys(this.players).find(p => this.players[p].id == id)];
696
794
  // console.log(`Player ${player.name}: ${text} (flags: ${msgFlags})`);
697
795
  // console.log(`Their position: ${player.position.x}, ${player.position.y}, ${player.position.z}`);
698
- this.#emit('chat', player, text, msgFlags);
796
+ this.emit('chat', player, text, msgFlags);
699
797
  }
700
798
 
701
799
  #processAddPlayerPacket() {
@@ -704,6 +802,7 @@ export class Bot {
704
802
  const name = CommIn.unPackString();
705
803
  const safename = CommIn.unPackString(); // ??? (a)
706
804
  const charClass = CommIn.unPackInt8U();
805
+ const findCosmetics = this.intents.includes(this.Intents.COSMETIC_DATA);
707
806
  const playerData = {
708
807
  id_: id_,
709
808
  uniqueId_: uniqueId,
@@ -712,14 +811,14 @@ export class Bot {
712
811
  charClass_: charClass,
713
812
  team_: CommIn.unPackInt8U(),
714
813
  primaryWeaponItem_: findItemById(CommIn.unPackInt16U()),
715
- secondaryWeaponItem_: findItemById(CommIn.unPackInt16U()), // b
814
+ secondaryWeaponItem_: findCosmetics ? findItemById(CommIn.unPackInt16U()) : CommIn.unPackInt16U(),
716
815
  shellColor_: CommIn.unPackInt8U(),
717
- hatItem_: findItemById(CommIn.unPackInt16U()),
718
- stampItem_: findItemById(CommIn.unPackInt16U()),
816
+ hatItem_: findCosmetics ? findItemById(CommIn.unPackInt16U()) : CommIn.unPackInt16U(),
817
+ stampItem_: findCosmetics ? findItemById(CommIn.unPackInt16U()) : CommIn.unPackInt16U(),
719
818
  unknownInt8: CommIn.unPackInt8(), // c
720
819
  otherUnknownInt8: CommIn.unPackInt8(),
721
- grenadeItem_: findItemById(CommIn.unPackInt16U()),
722
- meleeItem_: findItemById(CommIn.unPackInt16U()),
820
+ grenadeItem_: findCosmetics ? findItemById(CommIn.unPackInt16U()) : CommIn.unPackInt16U(),
821
+ meleeItem_: findCosmetics ? findItemById(CommIn.unPackInt16U()) : CommIn.unPackInt16U(),
723
822
  x_: CommIn.unPackFloat(),
724
823
  y_: CommIn.unPackFloat(),
725
824
  z_: CommIn.unPackFloat(),
@@ -752,36 +851,14 @@ export class Bot {
752
851
  playerData.gameData_.private = CommIn.unPackInt8U();
753
852
  playerData.gameData_.gameType = CommIn.unPackInt8U();
754
853
 
755
- if (!this.players[playerData.id_]) {
854
+ if (!this.players[playerData.id_])
756
855
  this.players[playerData.id_] = new GamePlayer(playerData.id_, playerData.team_, playerData);
757
856
 
758
- const player = this.players[playerData.id_];
759
-
760
- if (player.playing) {
761
- player.healthInterval = setInterval(() => {
762
- if (player.hp < 1) return;
763
-
764
- const regenSpeed = 0.1 * (this.game.isPrivate ? this.game.options.healthRegen : 1);
765
-
766
- if (player.streakRewards.includes(ShellStreaks.OverHeal)) {
767
- player.hp = Math.max(100, player.hp - regenSpeed);
768
- } else {
769
- player.hp = Math.min(100, player.hp + regenSpeed);
770
- }
771
- }, 33);
772
- }
773
- }
774
-
775
857
  if (this.me.id == playerData.id_) {
776
858
  this.me = this.players[playerData.id_];
777
859
  }
778
860
 
779
- 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
- }
861
+ this.emit('playerJoin', this.players[playerData.id_]);
785
862
  }
786
863
 
787
864
  #processRespawnPacket() {
@@ -808,29 +885,13 @@ export class Bot {
808
885
  player.grenades = grenades;
809
886
  player.position = { x: x, y: y, z: z };
810
887
  // console.log(`Player ${player.name} respawned at ${x}, ${y}, ${z}`);
811
- this.#emit('playerRespawn', player);
812
-
813
- if (player.healthInterval) {
814
- clearInterval(player.healthInterval);
815
- }
816
-
817
- player.healthInterval = setInterval(() => {
818
- if (player.hp < 1) return;
819
-
820
- const regenSpeed = 0.1 * (this.game.isPrivate ? this.game.options[GameOptionFlags.healthRegen] : 1);
821
-
822
- if (player.streakRewards.includes(ShellStreaks.OverHeal)) {
823
- player.hp = Math.max(100, player.hp - regenSpeed);
824
- } else {
825
- player.hp = Math.min(100, player.hp + regenSpeed);
826
- }
827
- }, 33);
888
+ this.emit('playerRespawn', player);
828
889
  } else {
829
890
  // console.log(`Player ${id} not found. (me: ${this.me.id}) (respawn)`);
830
891
  }
831
892
  }
832
893
 
833
- #processExternalSyncPacket() {
894
+ #processSyncThemPacket() {
834
895
  const id = CommIn.unPackInt8U();
835
896
  const x = CommIn.unPackFloat();
836
897
  const y = CommIn.unPackFloat();
@@ -838,37 +899,53 @@ export class Bot {
838
899
  const climbing = CommIn.unPackInt8U();
839
900
 
840
901
  const player = this.players[id];
841
- if (!player || !player.buffer) return;
902
+ if (!player) return;
842
903
 
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();
904
+ if (player.position.x !== x) player.position.x = x;
905
+ if (player.position.z !== z) player.position.z = z;
906
+
907
+ if (!player.jumping || Math.abs(player.position.y - y) > 0.5 && player.position.y !== y)
908
+ player.position.y = y;
909
+
910
+ if (player.climbing !== climbing) player.climbing = climbing;
911
+
912
+ if (this.intents.includes(this.Intents.BUFFERS)) {
913
+ if (!player.buffer) return;
914
+
915
+ if (player.id == this.me.id) {
916
+ for (let i2 = 0; i2 < 3 /* FramesBetweenSyncs */; i2++) {
917
+ CommIn.unPackInt8U();
918
+ CommIn.unPackRadU();
919
+ CommIn.unPackRad();
920
+ CommIn.unPackInt8U();
921
+ }
922
+ return;
848
923
  }
849
- }
850
924
 
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
- }
925
+ let yaw, pitch;
926
+ for (let i2 = 0; i2 < 3; i2++) {
927
+ player.buffer[i2].controlKeys = CommIn.unPackInt8U();
859
928
 
860
- player.position.x = x;
929
+ yaw = CommIn.unPackRadU();
930
+ if (!isNaN(yaw)) player.buffer[i2].yaw_ = yaw
861
931
 
862
- if (!player.jumping || Math.abs(player.position.y - y) > 0.5) {
863
- player.position.y = y;
864
- }
932
+ pitch = CommIn.unPackRad();
933
+ if (!isNaN(pitch)) player.buffer[i2].pitch_ = pitch
934
+
935
+ CommIn.unPackInt8U();
936
+ }
865
937
 
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})`);
938
+ player.buffer[0].x = x;
939
+ player.buffer[0].y = y;
940
+ player.buffer[0].z = z;
941
+ } else {
942
+ for (let i2 = 0; i2 < 3; i2++) {
943
+ CommIn.unPackInt8U();
944
+ CommIn.unPackRadU();
945
+ CommIn.unPackRad();
946
+ CommIn.unPackInt8U();
947
+ }
948
+ }
872
949
  }
873
950
 
874
951
  #processPausePacket() {
@@ -876,7 +953,7 @@ export class Bot {
876
953
  const player = this.players[id];
877
954
  if (player) {
878
955
  player.playing = false;
879
- this.#emit('playerPause', player);
956
+ this.emit('playerPause', player);
880
957
  }
881
958
  }
882
959
 
@@ -887,23 +964,21 @@ export class Bot {
887
964
  const player = this.players[id];
888
965
  if (player) {
889
966
  player.activeGun = newWeaponId;
890
- this.#emit('playerSwapWeapon', player, newWeaponId);
967
+ this.emit('playerSwapWeapon', player, newWeaponId);
891
968
  }
892
969
  }
893
970
 
894
971
  #processDeathPacket() {
895
972
  const killedId = CommIn.unPackInt8U();
896
973
  const byId = CommIn.unPackInt8U();
897
- // const rs = CommIn.unPackInt8U();
974
+
975
+ CommIn.unPackInt8U();
976
+ CommIn.unPackInt8U();
977
+ CommIn.unPackInt8U();
898
978
 
899
979
  const killed = this.players[killedId];
900
980
  const killer = this.players[byId];
901
981
 
902
- /*
903
- const killerLastDmg = CommIn.unPackInt8U();
904
- const killedLastDmg = CommIn.unPackInt8U();
905
- */
906
-
907
982
  if (killed) {
908
983
  killed.playing = false;
909
984
  killed.kills = 0;
@@ -914,18 +989,21 @@ export class Bot {
914
989
  if (killer) { killer.kills++; }
915
990
  // console.log(`Player ${killer.name} is on a streak of ${killer.kills} kills.`);
916
991
 
917
- this.#emit('playerDeath', killed, killer); // killed, killer
992
+ this.emit('playerDeath', killed, killer); // killed, killer
918
993
  }
919
994
 
920
995
  #processFirePacket() {
921
996
  const id = CommIn.unPackInt8U();
922
997
 
998
+ for (let i = 0; i < 6; i++)
999
+ CommIn.unPackFloat();
1000
+
923
1001
  const player = this.players[id];
924
1002
  const playerWeapon = player.weapons[player.activeGun];
925
1003
 
926
1004
  if (playerWeapon && playerWeapon.ammo) {
927
1005
  playerWeapon.ammo.rounds--;
928
- this.#emit('playerFire', player, playerWeapon);
1006
+ this.emit('playerFire', player, playerWeapon);
929
1007
  }
930
1008
  }
931
1009
 
@@ -938,7 +1016,7 @@ export class Bot {
938
1016
 
939
1017
  this.game.collectables[type].push({ id, x, y, z });
940
1018
 
941
- this.#emit('spawnItem', type, id, { x, y, z });
1019
+ this.emit('spawnItem', type, id, { x, y, z });
942
1020
  }
943
1021
 
944
1022
  #processCollectPacket() {
@@ -955,13 +1033,15 @@ export class Bot {
955
1033
  const playerWeapon = player.weapons[applyToWeaponIdx];
956
1034
  if (playerWeapon && playerWeapon.ammo) {
957
1035
  playerWeapon.ammo.store = Math.min(playerWeapon.ammo.storeMax, playerWeapon.ammo.store + playerWeapon.ammo.pickup);
958
- this.#emit('collectAmmo', player, playerWeapon);
1036
+ this.emit('collectAmmo', player, playerWeapon);
959
1037
  }
960
1038
  }
961
1039
 
962
1040
  if (type == CollectTypes.GRENADE) {
963
- player.grenades >= 3 ? player.grenades = 3 : player.grenades++;
964
- this.#emit('collectGrenade', player);
1041
+ player.grenades++;
1042
+ if (player.grenades > 3) player.grenades = 3
1043
+
1044
+ this.emit('collectGrenade', player);
965
1045
  }
966
1046
  }
967
1047
 
@@ -975,16 +1055,19 @@ export class Bot {
975
1055
  const oldHP = player.hp;
976
1056
  player.hp = hp;
977
1057
 
978
- this.#emit('playerDamaged', player, oldHP, player.hp);
1058
+ this.emit('playerDamaged', player, oldHP, player.hp);
979
1059
  }
980
1060
 
981
1061
  #processHitMePacket() {
982
1062
  const hp = CommIn.unPackInt8U();
983
- const oldHp = this.me.hp;
984
1063
 
1064
+ CommIn.unPackFloat();
1065
+ CommIn.unPackFloat();
1066
+
1067
+ const oldHp = this.me.hp;
985
1068
  this.me.hp = hp;
986
1069
 
987
- this.#emit('selfDamaged', oldHp, this.me.hp);
1070
+ this.emit('selfDamaged', oldHp, this.me.hp);
988
1071
  }
989
1072
 
990
1073
  #processSyncMePacket() {
@@ -1001,6 +1084,10 @@ export class Bot {
1001
1084
  const newY = CommIn.unPackFloat();
1002
1085
  const newZ = CommIn.unPackFloat();
1003
1086
 
1087
+ CommIn.unPackInt8U();
1088
+ CommIn.unPackInt8U();
1089
+ CommIn.unPackInt8U();
1090
+
1004
1091
  const oldX = player.position.x;
1005
1092
  const oldY = player.position.y;
1006
1093
  const oldZ = player.position.z;
@@ -1010,14 +1097,14 @@ export class Bot {
1010
1097
  player.position.z = newZ;
1011
1098
 
1012
1099
  if (oldX != newX || oldY != newY || oldZ != newZ) {
1013
- this.#emit('selfMoved', player, { x: oldX, y: oldY, z: oldZ }, { x: newX, y: newY, z: newZ });
1100
+ this.emit('selfMoved', player, { x: oldX, y: oldY, z: oldZ }, { x: newX, y: newY, z: newZ });
1014
1101
  }
1015
1102
  }
1016
1103
 
1017
1104
  #processEventModifierPacket() {
1018
1105
  const out = CommOut.getBuffer();
1019
1106
  out.packInt8(CommCode.eventModifier);
1020
- out.send(this.gameSocket);
1107
+ out.send(this.game.socket);
1021
1108
  }
1022
1109
 
1023
1110
  #processRemovePlayerPacket() {
@@ -1026,7 +1113,7 @@ export class Bot {
1026
1113
 
1027
1114
  delete this.players[id.toString()];
1028
1115
 
1029
- this.#emit('playerLeave', removedPlayer);
1116
+ this.emit('playerLeave', removedPlayer);
1030
1117
  }
1031
1118
 
1032
1119
  #processGameStatePacket() {
@@ -1049,10 +1136,10 @@ export class Bot {
1049
1136
  controlledByTeam: controlledByTeam
1050
1137
  };
1051
1138
 
1052
- this.#emit('gameStateChange', this.game);
1139
+ this.emit('gameStateChange', this.game);
1053
1140
  } else if (this.game.gameModeId == GameModes.kotc) {
1054
1141
  this.game.stage = CommIn.unPackInt8U(); // constants.CoopStates
1055
- this.game.activeZone = CommIn.unPackInt8U(); // a number to represent which 'active zone' kotc is using
1142
+ this.game.zoneNumber = CommIn.unPackInt8U(); // a number to represent which 'active zone' kotc is using
1056
1143
  this.game.capturing = CommIn.unPackInt8U(); // the team capturing, named "teams" in shell src
1057
1144
  this.game.captureProgress = CommIn.unPackInt16U(); // progress of the coop capture
1058
1145
  this.game.numCapturing = CommIn.unPackInt8U(); // number of players capturing - number/1000
@@ -1062,8 +1149,12 @@ export class Bot {
1062
1149
  // not in shell, for utility purposes =D
1063
1150
  this.game.stageName = CoopStagesById[this.game.stage]; // name of the stage ('start' / 'capturing' / 'etc')
1064
1151
  this.game.capturePercent = this.game.captureProgress / 1000; // progress of the capture as a percentage
1152
+ this.game.activeZone = this.game.map.zones ? this.game.map.zones[this.game.zoneNumber - 1] : null;
1065
1153
 
1066
- this.#emit('gameStateChange', this.game);
1154
+ this.emit('gameStateChange', this.game);
1155
+ } else if (this.game.gameModeId == GameModes.team) {
1156
+ this.game.teamScore[1] = CommIn.unPackInt16U();
1157
+ this.game.teamScore[2] = CommIn.unPackInt16U();
1067
1158
  }
1068
1159
 
1069
1160
  if (this.game.gameModeId !== GameModes.spatula) {
@@ -1072,13 +1163,16 @@ export class Bot {
1072
1163
 
1073
1164
  if (this.game.gameModeId !== GameModes.kotc) {
1074
1165
  delete this.game.stage;
1075
- delete this.game.activeZone;
1166
+ delete this.game.zoneNumber;
1076
1167
  delete this.game.capturing;
1077
1168
  delete this.game.captureProgress;
1078
- delete this.game.numCapturing
1169
+ delete this.game.numCapturing;
1170
+ delete this.game.stageName;
1171
+ delete this.game.numCapturing;
1172
+ delete this.game.activeZone;
1079
1173
  }
1080
1174
 
1081
- if (this.game.gameModeId !== GameModes.spatula && this.game.gameModeId !== GameModes.kotc) {
1175
+ if (this.game.gameModeId == GameModes.ffa) {
1082
1176
  delete this.game.teamScore;
1083
1177
  }
1084
1178
  }
@@ -1129,7 +1223,7 @@ export class Bot {
1129
1223
  break;
1130
1224
  }
1131
1225
 
1132
- this.#emit('playerBeginStreak', player, ksType);
1226
+ this.emit('playerBeginStreak', player, ksType);
1133
1227
  }
1134
1228
 
1135
1229
  #processEndStreakPacket() {
@@ -1148,21 +1242,24 @@ export class Bot {
1148
1242
  player.streakRewards = player.streakRewards.filter((r) => r != ksType);
1149
1243
  }
1150
1244
 
1151
- this.#emit('playerEndStreak', ksType, player);
1245
+ this.emit('playerEndStreak', ksType, player);
1152
1246
  }
1153
1247
 
1154
1248
  #processHitShieldPacket() {
1155
1249
  const hb = CommIn.unPackInt8U();
1156
1250
  const hp = CommIn.unPackInt8U();
1157
1251
 
1252
+ CommIn.unPackFloat();
1253
+ CommIn.unPackFloat();
1254
+
1158
1255
  this.me.hpShield = hb;
1159
1256
  this.me.hp = hp;
1160
1257
 
1161
1258
  if (this.me.hpShield <= 0) {
1162
1259
  this.me.streakRewards = this.me.streakRewards.filter((r) => r != ShellStreaks.HardBoiled);
1163
- this.#emit('selfShieldLost');
1260
+ this.emit('selfShieldLost');
1164
1261
  } else {
1165
- this.#emit('selfShieldHit', this.me.hpShield);
1262
+ this.emit('selfShieldHit', this.me.hpShield);
1166
1263
  }
1167
1264
  }
1168
1265
 
@@ -1191,7 +1288,7 @@ export class Bot {
1191
1288
  this.game.options.weaponsDisabled = Array.from({ length: 7 }, () => CommIn.unPackInt8U() === 1);
1192
1289
  this.game.options.mustUseSecondary = this.game.options.weaponsDisabled.every((v) => v);
1193
1290
 
1194
- this.#emit('gameOptionsChange', oldOptions, this.game.options);
1291
+ this.emit('gameOptionsChange', oldOptions, this.game.options);
1195
1292
  return false;
1196
1293
  }
1197
1294
 
@@ -1200,7 +1297,7 @@ export class Bot {
1200
1297
 
1201
1298
  if (action == GameActions.pause) {
1202
1299
  // console.log('settings changed, gameOwner changed game settings, force paused');
1203
- this.#emit('gameForcePause');
1300
+ this.emit('gameForcePause');
1204
1301
  setTimeout(() => this.me.playing = false, 3000);
1205
1302
  }
1206
1303
 
@@ -1208,35 +1305,43 @@ export class Bot {
1208
1305
  // console.log('owner reset game');
1209
1306
 
1210
1307
  this.me.kills = 0;
1211
- this.game.teamScore = [0, 0, 0];
1212
1308
 
1213
- this.game.spatula.controlledBy = 0;
1214
- this.game.spatula.controlledByTeam = 0;
1215
- this.game.spatula.coords = { x: 0, y: 0, z: 0 };
1309
+ if (this.game.gameModeId !== GameModes.ffa) this.game.teamScore = [0, 0, 0];
1216
1310
 
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;
1311
+ if (this.game.gameModeId === GameModes.spatula) {
1312
+ this.game.spatula.controlledBy = 0;
1313
+ this.game.spatula.controlledByTeam = 0;
1314
+ this.game.spatula.coords = { x: 0, y: 0, z: 0 };
1315
+ }
1316
+
1317
+ if (this.game.gameModeId === GameModes.kotc) {
1318
+ this.game.stage = CoopStates.capturing;
1319
+ this.game.zoneNumber = 0;
1320
+ this.game.activeZone = null;
1321
+ this.game.capturing = 0;
1322
+ this.game.captureProgress = 0;
1323
+ this.game.numCapturing = 0;
1324
+ this.game.stageName = CoopStagesById[CoopStates.capturing];
1325
+ this.game.capturePercent = 0.0;
1326
+ }
1224
1327
 
1225
- this.#emit('gameReset');
1328
+ this.emit('gameReset');
1226
1329
  }
1227
1330
  }
1228
1331
 
1229
1332
  #processPingPacket() {
1333
+ if (!this.intents.includes(this.Intents.PING)) return;
1334
+
1230
1335
  const oldPing = this.ping;
1231
1336
 
1232
1337
  this.ping = Date.now() - this.lastPingTime;
1233
1338
 
1234
- this.#emit('pingUpdate', oldPing, this.ping);
1339
+ this.emit('pingUpdate', oldPing, this.ping);
1235
1340
 
1236
1341
  setTimeout(() => {
1237
1342
  const out = CommOut.getBuffer();
1238
1343
  out.packInt8(CommCode.ping);
1239
- out.send(this.gameSocket);
1344
+ out.send(this.game.socket);
1240
1345
  this.lastPingTime = Date.now();
1241
1346
  }, this.pingInterval);
1242
1347
  }
@@ -1253,7 +1358,7 @@ export class Bot {
1253
1358
  player.team = toTeam;
1254
1359
  player.kills = 0;
1255
1360
 
1256
- this.#emit('playerSwitchTeam', player, oldTeam, toTeam);
1361
+ this.emit('playerSwitchTeam', player, oldTeam, toTeam);
1257
1362
  }
1258
1363
 
1259
1364
  #processChangeCharacterPacket() {
@@ -1268,12 +1373,18 @@ export class Bot {
1268
1373
  const grenadeIdx = CommIn.unPackInt16U();
1269
1374
  const meleeIdx = CommIn.unPackInt16U();
1270
1375
 
1376
+ CommIn.unPackInt8();
1377
+ CommIn.unPackInt8();
1378
+
1379
+ const findCosmetics = this.intents.includes(this.Intents.COSMETIC_DATA);
1380
+
1271
1381
  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);
1382
+
1383
+ const secondaryWeaponItem = findCosmetics ? findItemById(secondaryWeaponIdx) : secondaryWeaponIdx;
1384
+ const hatItem = findCosmetics ? findItemById(hatIdx) : hatIdx;
1385
+ const stampItem = findCosmetics ? findItemById(stampIdx) : stampIdx;
1386
+ const grenadeItem = findCosmetics ? findItemById(grenadeIdx) : grenadeIdx;
1387
+ const meleeItem = findCosmetics ? findItemById(meleeIdx) : meleeIdx;
1277
1388
 
1278
1389
  const player = this.players[id];
1279
1390
  if (player) {
@@ -1291,8 +1402,8 @@ export class Bot {
1291
1402
  player.selectedGun = weaponIndex;
1292
1403
  player.weapons[0] = new GunList[weaponIndex]();
1293
1404
 
1294
- if (oldWeaponIdx !== player.selectedGun) this.#emit('playerChangeGun', player, oldWeaponIdx, player.selectedGun);
1295
- if (oldCharacter !== player.character) this.#emit('playerChangeCharacter', player, oldCharacter, player.character);
1405
+ if (oldWeaponIdx !== player.selectedGun) this.emit('playerChangeGun', player, oldWeaponIdx, player.selectedGun);
1406
+ if (oldCharacter !== player.character) this.emit('playerChangeCharacter', player, oldCharacter, player.character);
1296
1407
  }
1297
1408
  }
1298
1409
 
@@ -1301,27 +1412,23 @@ export class Bot {
1301
1412
  const oldBalance = this.account.eggBalance;
1302
1413
 
1303
1414
  this.account.eggBalance = newBalance;
1304
- this.#emit('balanceUpdate', newBalance - oldBalance, newBalance);
1415
+ this.emit('balanceUpdate', newBalance - oldBalance, newBalance);
1305
1416
  }
1306
1417
 
1307
1418
  #processRespawnDeniedPacket() {
1308
1419
  this.me.playing = false;
1309
- this.#emit('selfRespawnFail');
1420
+ this.emit('selfRespawnFail');
1310
1421
  }
1311
1422
 
1312
1423
  #processMeleePacket() {
1313
1424
  const id = CommIn.unPackInt8U();
1314
1425
  const player = this.players[id];
1315
1426
 
1316
- if (player) this.#emit('playerMelee', player);
1427
+ if (player) this.emit('playerMelee', player);
1317
1428
  }
1318
1429
 
1319
- // we do this since reload doesn't get emitted to ourselves
1320
- processReloadPacket(customPlayer, iUnderstandThisIsForInternalUseOnlyAndIShouldNotBeCallingThis) {
1321
- if (!iUnderstandThisIsForInternalUseOnlyAndIShouldNotBeCallingThis)
1322
- throw new Error('processReloadPacket is exposed for internal use only. do not call it.');
1323
-
1324
- const id = customPlayer || CommIn.unPackInt8U();
1430
+ #processReloadPacket() {
1431
+ const id = CommIn.unPackInt8U();
1325
1432
  const player = this.players[id];
1326
1433
 
1327
1434
  if (!player) return;
@@ -1338,142 +1445,444 @@ export class Bot {
1338
1445
  playerActiveWeapon.ammo.store -= newRounds;
1339
1446
  }
1340
1447
 
1341
- this.#emit('playerReload', player, playerActiveWeapon);
1448
+ this.emit('playerReload', player, playerActiveWeapon);
1449
+ }
1450
+
1451
+ #processGameRequestOptionsPacket() {
1452
+ const out = CommOut.getBuffer();
1453
+ out.packInt8(CommCode.gameOptions);
1454
+ out.packInt8(this.game.options.gravity * 4);
1455
+ out.packInt8(this.game.options.damage * 4);
1456
+ out.packInt8(this.game.options.healthRegen * 4);
1457
+
1458
+ const flags =
1459
+ (this.game.options.locked ? 1 : 0) |
1460
+ (this.game.options.noTeamChange ? 2 : 0) |
1461
+ (this.game.options.noTeamShuffle ? 4 : 0);
1462
+
1463
+ out.packInt8(flags);
1464
+
1465
+ this.game.options.weaponsDisabled.forEach((v) => {
1466
+ out.packInt8(v ? 1 : 0);
1467
+ });
1468
+ }
1469
+
1470
+ #processExplodePacket() {
1471
+ const itemType = CommIn.unPackInt8U();
1472
+ let item = CommIn.unPackInt16U();
1473
+ const x = CommIn.unPackFloat();
1474
+ const y = CommIn.unPackFloat();
1475
+ const z = CommIn.unPackFloat();
1476
+ const damage = CommIn.unPackInt8U();
1477
+ const radius = CommIn.unPackFloat();
1478
+
1479
+ if (this.intents.includes(this.Intents.COSMETIC_DATA))
1480
+ item = findItemById(item);
1481
+
1482
+ if (itemType == ItemTypes.Grenade) this.emit('grenadeExploded', item, { x, y, z }, damage, radius);
1483
+ else this.emit('rocketHit', { x, y, z }, damage, radius);
1484
+ }
1485
+
1486
+ #processThrowGrenadePacket() {
1487
+ const id = CommIn.unPackInt8U();
1488
+ const x = CommIn.unPackFloat();
1489
+ const y = CommIn.unPackFloat();
1490
+ const z = CommIn.unPackFloat();
1491
+ const dx = CommIn.unPackFloat();
1492
+ const dy = CommIn.unPackFloat();
1493
+ const dz = CommIn.unPackFloat();
1494
+
1495
+ const player = this.players[id];
1496
+
1497
+ if (player) {
1498
+ player.grenades--;
1499
+ this.emit('playerThrowGrenade', player, { x, y, z }, { x: dx, y: dy, z: dz });
1500
+ }
1501
+ }
1502
+
1503
+ #processChallengeCompletePacket() {
1504
+ const id = CommIn.unPackInt8U();
1505
+ const challengeId = CommIn.unPackInt8U();
1506
+
1507
+ const player = this.players[id];
1508
+ if (!player) return;
1509
+
1510
+ if (!this.intents.includes(this.Intents.CHALLENGES))
1511
+ return this.emit('challengeComplete', player, challengeId);
1512
+
1513
+ const challenge = this.account.challenges.find(c => c.id == challengeId);
1514
+ this.emit('challengeComplete', player, challenge);
1515
+
1516
+ if (player.id == this.me.id) this.refreshChallenges();
1342
1517
  }
1343
1518
 
1344
1519
  #handlePacket(packet) {
1345
1520
  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
1521
 
1353
- case CommCode.addPlayer:
1354
- this.#processAddPlayerPacket(packet);
1355
- break;
1522
+ if (this.intents.includes(this.Intents.PACKET_HOOK))
1523
+ this.emit('packet', packet);
1356
1524
 
1357
- case CommCode.respawn:
1358
- this.#processRespawnPacket(packet);
1359
- break;
1525
+ let lastCommand = 0;
1526
+ let abort = false;
1360
1527
 
1361
- case CommCode.swapWeapon:
1362
- this.#processSwapWeaponPacket(packet);
1363
- break;
1528
+ while (CommIn.isMoreDataAvailable() && !abort) {
1529
+ const cmd = CommIn.unPackInt8U();
1364
1530
 
1365
- case CommCode.syncThem:
1366
- this.#processExternalSyncPacket(packet);
1367
- break;
1531
+ switch (cmd) {
1532
+ case CommCode.syncThem:
1533
+ this.#processSyncThemPacket();
1534
+ break;
1368
1535
 
1369
- case CommCode.pause:
1370
- this.#processPausePacket(packet);
1371
- break;
1536
+ case CommCode.fire:
1537
+ this.#processFirePacket();
1538
+ break;
1372
1539
 
1373
- case CommCode.die:
1374
- this.#processDeathPacket(packet);
1375
- break;
1540
+ case CommCode.hitThem:
1541
+ this.#processHitThemPacket();
1542
+ break;
1376
1543
 
1377
- case CommCode.fire:
1378
- this.#processFirePacket(packet);
1379
- break;
1544
+ case CommCode.syncMe:
1545
+ this.#processSyncMePacket();
1546
+ break;
1380
1547
 
1381
- case CommCode.collectItem:
1382
- this.#processCollectPacket(packet);
1383
- break;
1548
+ case CommCode.hitMe:
1549
+ this.#processHitMePacket();
1550
+ break;
1384
1551
 
1385
- case CommCode.hitThem:
1386
- this.#processHitThemPacket(packet);
1387
- break;
1552
+ case CommCode.swapWeapon:
1553
+ this.#processSwapWeaponPacket();
1554
+ break;
1388
1555
 
1389
- case CommCode.hitMe:
1390
- this.#processHitMePacket(packet);
1391
- break;
1556
+ case CommCode.collectItem:
1557
+ this.#processCollectPacket();
1558
+ break;
1392
1559
 
1393
- case CommCode.syncMe:
1394
- this.#processSyncMePacket(packet);
1395
- break;
1560
+ case CommCode.respawn:
1561
+ this.#processRespawnPacket();
1562
+ break;
1396
1563
 
1397
- case CommCode.eventModifier:
1398
- this.#processEventModifierPacket(packet);
1399
- break;
1564
+ case CommCode.die:
1565
+ this.#processDeathPacket();
1566
+ break;
1400
1567
 
1401
- case CommCode.removePlayer:
1402
- this.#processRemovePlayerPacket(packet);
1403
- break;
1568
+ case CommCode.pause:
1569
+ this.#processPausePacket();
1570
+ break;
1404
1571
 
1405
- case CommCode.metaGameState:
1406
- this.#processGameStatePacket(packet);
1407
- break;
1572
+ case CommCode.chat:
1573
+ this.#processChatPacket();
1574
+ break;
1408
1575
 
1409
- case CommCode.beginShellStreak:
1410
- this.#processBeginStreakPacket(packet);
1411
- break;
1576
+ case CommCode.addPlayer:
1577
+ this.#processAddPlayerPacket();
1578
+ break;
1412
1579
 
1413
- case CommCode.endShellStreak:
1414
- this.#processEndStreakPacket(packet);
1415
- break;
1580
+ case CommCode.removePlayer:
1581
+ this.#processRemovePlayerPacket();
1582
+ break;
1416
1583
 
1417
- case CommCode.hitMeHardBoiled:
1418
- this.#processHitShieldPacket(packet);
1419
- break;
1584
+ case CommCode.eventModifier:
1585
+ this.#processEventModifierPacket();
1586
+ break;
1420
1587
 
1421
- case CommCode.gameOptions:
1422
- this.#processGameOptionsPacket(packet);
1423
- break;
1588
+ case CommCode.metaGameState:
1589
+ this.#processGameStatePacket();
1590
+ break;
1424
1591
 
1425
- case CommCode.gameAction:
1426
- this.#processGameActionPacket(packet);
1427
- break;
1592
+ case CommCode.beginShellStreak:
1593
+ this.#processBeginStreakPacket();
1594
+ break;
1428
1595
 
1429
- case CommCode.ping:
1430
- this.#processPingPacket(packet);
1431
- break;
1596
+ case CommCode.endShellStreak:
1597
+ this.#processEndStreakPacket();
1598
+ break;
1432
1599
 
1433
- case CommCode.switchTeam:
1434
- this.#processSwitchTeamPacket(packet);
1435
- break;
1600
+ case CommCode.hitMeHardBoiled:
1601
+ this.#processHitShieldPacket();
1602
+ break;
1436
1603
 
1437
- case CommCode.changeCharacter:
1438
- this.#processChangeCharacterPacket(packet);
1439
- break;
1604
+ case CommCode.gameOptions:
1605
+ this.#processGameOptionsPacket();
1606
+ break;
1440
1607
 
1441
- case CommCode.updateBalance:
1442
- this.#processUpdateBalancePacket(packet);
1443
- break;
1608
+ case CommCode.ping:
1609
+ this.#processPingPacket();
1610
+ break;
1444
1611
 
1445
- case CommCode.respawnDenied:
1446
- this.#processRespawnDeniedPacket(packet);
1447
- break;
1612
+ case CommCode.switchTeam:
1613
+ this.#processSwitchTeamPacket();
1614
+ break;
1448
1615
 
1449
- case CommCode.reload:
1450
- this.processReloadPacket(null, true);
1451
- break;
1616
+ case CommCode.changeCharacter:
1617
+ this.#processChangeCharacterPacket();
1618
+ break;
1452
1619
 
1453
- case CommCode.spawnItem:
1454
- this.#processSpawnItemPacket();
1455
- break;
1620
+ case CommCode.reload:
1621
+ this.#processReloadPacket();
1622
+ break;
1456
1623
 
1457
- case CommCode.melee:
1458
- this.#processMeleePacket();
1459
- break;
1624
+ case CommCode.explode:
1625
+ this.#processExplodePacket();
1626
+ break;
1627
+
1628
+ case CommCode.throwGrenade:
1629
+ this.#processThrowGrenadePacket();
1630
+ break;
1631
+
1632
+ case CommCode.spawnItem:
1633
+ this.#processSpawnItemPacket();
1634
+ break;
1635
+
1636
+ case CommCode.melee:
1637
+ this.#processMeleePacket();
1638
+ break;
1639
+
1640
+ case CommCode.updateBalance:
1641
+ this.#processUpdateBalancePacket();
1642
+ break;
1643
+
1644
+ case CommCode.challengeCompleted:
1645
+ this.#processChallengeCompletePacket();
1646
+ break;
1647
+
1648
+ case CommCode.gameAction:
1649
+ this.#processGameActionPacket();
1650
+ break;
1651
+
1652
+ case CommCode.requestGameOptions:
1653
+ this.#processGameRequestOptionsPacket();
1654
+ break;
1655
+
1656
+ case CommCode.respawnDenied:
1657
+ this.#processRespawnDeniedPacket();
1658
+ break;
1460
1659
 
1461
- case CommCode.clientReady:
1462
- case CommCode.expireUpgrade:
1463
- case CommCode.musicInfo:
1464
- case CommCode.challengeCompleted:
1465
1660
  // we do not plan to implement these
1466
1661
  // for more info, see comm/codes.js
1467
- break;
1662
+ case CommCode.clientReady:
1663
+ case CommCode.expireUpgrade:
1664
+ break;
1468
1665
 
1469
- case CommCode.explode:
1470
- case CommCode.throwGrenade:
1471
- // do nothing
1472
- break;
1666
+ case CommCode.musicInfo:
1667
+ CommIn.unPackLongString();
1668
+ break;
1473
1669
 
1474
- default:
1475
- console.error(`handlePacket: I got but did not handle a: ${Object.entries(CommCode).filter(([, v]) => v == cmd)[0][0]}`);
1476
- break;
1670
+ default:
1671
+ console.error(`handlePacket: I got but did not handle a: ${Object.keys(CommCode).find(k => CommCode[k] === cmd)}`);
1672
+ if (lastCommand) console.error(`handlePacket: It may be a result of the ${lastCommand} command.`);
1673
+ abort = true
1674
+ break;
1675
+ }
1676
+
1677
+ lastCommand = Object.keys(CommCode).find(k => CommCode[k] === cmd);
1678
+ }
1679
+ }
1680
+
1681
+ async checkChiknWinner() {
1682
+ const response = await queryServices({
1683
+ cmd: 'chicknWinnerReady',
1684
+ id: this.account.id,
1685
+ sessionId: this.account.sessionId
1686
+ });
1687
+
1688
+ this.account.cw.limit = response.limit;
1689
+ this.account.cw.atLimit = response.limit > 3;
1690
+
1691
+ // if there is a "span", that means that it's under the daily limit and you can play again soon
1692
+ // if there is a "period", that means that the account is done for the day and must wait a long time
1693
+ this.account.cw.secondsUntilPlay = response.span || response.period || 0;
1694
+ this.account.cw.canPlayAgain = Date.now() + (this.account.cw.secondsUntilPlay * 1000);
1695
+
1696
+ return this.account.cw;
1697
+ }
1698
+
1699
+ async playChiknWinner() {
1700
+ if (this.account.cw.atLimit || this.account.cw.limit > ChiknWinnerDailyLimit) return 'hit_daily_limit';
1701
+ if (this.account.cw.canPlayAgain > Date.now()) return 'on_cooldown';
1702
+
1703
+ const response = await queryServices({
1704
+ cmd: 'incentivizedVideoReward',
1705
+ firebaseId: this.account.firebaseId,
1706
+ id: this.account.id,
1707
+ sessionId: this.account.sessionId,
1708
+ token: null
1709
+ }, this.proxy, this.instance);
1710
+
1711
+ if (response.error) {
1712
+ if (response.error == 'RATELIMITED') {
1713
+ await this.checkChiknWinner();
1714
+ return 'on_cooldown';
1715
+ } else if (response.error == 'SESSION_EXPIRED') {
1716
+ return 'session_expired';
1717
+ } else {
1718
+ console.error('Unknown Chikn Winner response', response);
1719
+ return 'unknown_error';
1720
+ }
1721
+ }
1722
+
1723
+ if (response.reward) {
1724
+ this.account.eggBalance += response.reward.eggsGiven;
1725
+ response.reward.itemIds.forEach((id) => this.account.ownedItemIds.push(id));
1726
+
1727
+ await this.checkChiknWinner();
1728
+
1729
+ return response.reward;
1730
+ }
1731
+
1732
+ console.error('Unknown Chikn Winner response', response);
1733
+ return 'unknown_error';
1734
+ }
1735
+
1736
+ async resetChiknWinner() {
1737
+ if (this.account.eggBalance < 200) return 'not_enough_eggs';
1738
+ if (!this.account.cw.atLimit) return 'not_at_limit';
1739
+
1740
+ const response = await queryServices({
1741
+ cmd: 'chwReset',
1742
+ sessionId: this.account.sessionId
1743
+ });
1744
+
1745
+ if (response.result !== 'SUCCESS') {
1746
+ console.error('Unknown Chikn Winner reset response', response);
1747
+ return 'unknown_error';
1748
+ }
1749
+
1750
+ this.account.eggBalance -= 200;
1751
+ await this.checkChiknWinner();
1752
+
1753
+ return this.account.cw;
1754
+ }
1755
+
1756
+ canSee(target) {
1757
+ if (!this.intents.includes(this.Intents.PATHFINDING)) throw new Error('You must have the PATHFINDING intent to use this method.');
1758
+ return this.pathing.nodeList.hasLineOfSight(this.me.position, target.position);
1759
+ }
1760
+
1761
+ getBestTarget() {
1762
+ const options = Object.values(this.players)
1763
+ .filter((player) => player)
1764
+ .filter((player) => player !== this.me)
1765
+ .filter((player) => player.playing)
1766
+ .filter((player) => player.hp > 0)
1767
+ .filter((player) => player.name !== this.me.name)
1768
+ .filter((player) => this.me.team === 0 || player.team !== this.me.team)
1769
+ .filter((player) => this.canSee(player));
1770
+
1771
+ let minDistance = 200;
1772
+ let targetPlayer = null;
1773
+
1774
+ for (const player of options) {
1775
+ const dx = player.position.x - this.me.position.x;
1776
+ const dy = player.position.y - this.me.position.y;
1777
+ const dz = player.position.z - this.me.position.z;
1778
+
1779
+ const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
1780
+
1781
+ if (distance < minDistance) {
1782
+ minDistance = distance;
1783
+ targetPlayer = player;
1784
+ }
1785
+ }
1786
+
1787
+ return targetPlayer;
1788
+ }
1789
+
1790
+ async refreshBalance() {
1791
+ const result = await queryServices({
1792
+ cmd: 'checkBalance',
1793
+ firebaseId: this.account.firebaseId,
1794
+ sessionId: this.account.sessionId
1795
+ }, this.proxy, this.instance);
1796
+
1797
+ this.account.eggBalance = result.currentBalance;
1798
+
1799
+ return result.currentBalance;
1800
+ }
1801
+
1802
+ async redeemCode(code) {
1803
+ const result = await queryServices({
1804
+ cmd: 'redeem',
1805
+ firebaseId: this.account.firebaseId,
1806
+ sessionId: this.account.sessionId,
1807
+ id: this.account.id,
1808
+ code
1809
+ }, this.proxy, this.instance);
1810
+
1811
+ if (result.result === 'SUCCESS') {
1812
+ this.account.eggBalance = result.eggs_given;
1813
+ result.item_ids.forEach((id) => this.account.ownedItemIds.push(id));
1814
+
1815
+ return {
1816
+ result,
1817
+ eggsGiven: result.eggs_given,
1818
+ itemIds: result.item_ids
1819
+ };
1820
+ } else return result;
1821
+ }
1822
+
1823
+ async claimURLReward(reward) {
1824
+ const result = await queryServices({
1825
+ cmd: 'urlRewardParams',
1826
+ firebaseId: this.account.firebaseId,
1827
+ sessionId: this.account.sessionId,
1828
+ reward
1829
+ }, this.proxy, this.instance);
1830
+
1831
+ if (result.result === 'SUCCESS') {
1832
+ this.account.eggBalance += result.eggsGiven;
1833
+ result.itemIds.forEach((id) => this.account.ownedItemIds.push(id));
1834
+ }
1835
+
1836
+ return result;
1837
+ }
1838
+
1839
+ async claimSocialReward(rewardTag) {
1840
+ const result = await queryServices({
1841
+ cmd: 'reward',
1842
+ firebaseId: this.account.firebaseId,
1843
+ sessionId: this.account.sessionId,
1844
+ rewardTag
1845
+ }, this.proxy, this.instance);
1846
+
1847
+ if (result.result === 'SUCCESS') {
1848
+ this.account.eggBalance += result.eggsGiven;
1849
+ result.itemIds.forEach((id) => this.account.ownedItemIds.push(id));
1850
+ }
1851
+
1852
+ return result;
1853
+ }
1854
+
1855
+ async buyItem(itemId) {
1856
+ const result = await queryServices({
1857
+ cmd: 'buy',
1858
+ firebaseId: this.account.firebaseId,
1859
+ sessionId: this.account.sessionId,
1860
+ itemId,
1861
+ save: true
1862
+ }, this.proxy, this.instance);
1863
+
1864
+ if (result.result === 'SUCCESS') {
1865
+ this.account.eggBalance = result.currentBalance;
1866
+ this.account.ownedItemIds.push(result.itemId);
1867
+ }
1868
+
1869
+ return result;
1870
+ }
1871
+
1872
+ quit(noCleanup = false) {
1873
+ if (this.intents.includes(this.Intents.PLAYER_HEALTH))
1874
+ clearInterval(this.healthIntervalId);
1875
+
1876
+ clearInterval(this.updateInterval);
1877
+
1878
+ this.game.socket.close();
1879
+ this.matchmaker.close();
1880
+
1881
+ if (!noCleanup) {
1882
+ delete this.account;
1883
+ delete this.game;
1884
+ delete this.me;
1885
+ delete this.players;
1477
1886
  }
1478
1887
  }
1479
1888
  }