yolkbot 0.1.0-alpha.9 → 0.1.1-alpha.10

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