yolkbot 0.1.0-alpha.6 → 0.1.0-alpha.60

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