yolkbot 0.1.0-alpha.5 → 0.1.0-alpha.51

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