yolkbot 1.4.8 → 1.5.0

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 (129) hide show
  1. package/README.md +1 -1
  2. package/browser/build/global.js +1 -1
  3. package/browser/build/module.js +1 -1
  4. package/dist/api.d.ts +34 -16
  5. package/dist/api.js +164 -121
  6. package/dist/bot/GamePlayer.d.ts +2 -2
  7. package/dist/bot/GamePlayer.js +15 -5
  8. package/dist/bot.d.ts +121 -83
  9. package/dist/bot.js +624 -1077
  10. package/dist/comm/CommIn.js +14 -17
  11. package/dist/comm/CommOut.js +15 -15
  12. package/dist/constants/CommCode.js +1 -1
  13. package/dist/constants/findItemById.js +2 -1
  14. package/dist/constants/guns.d.ts +16 -41
  15. package/dist/constants/guns.js +137 -259
  16. package/dist/constants/index.d.ts +7 -9
  17. package/dist/constants/index.js +15 -13
  18. package/dist/dispatches/BanPlayerDispatch.d.ts +3 -1
  19. package/dist/dispatches/BootPlayerDispatch.d.ts +3 -1
  20. package/dist/dispatches/ChatDispatch.d.ts +3 -1
  21. package/dist/dispatches/ChatDispatch.js +3 -3
  22. package/dist/dispatches/FireDispatch.d.ts +3 -1
  23. package/dist/dispatches/GameOptionsDispatch.d.ts +3 -1
  24. package/dist/dispatches/GoToAmmoDispatch.d.ts +2 -0
  25. package/dist/dispatches/GoToAmmoDispatch.js +15 -16
  26. package/dist/dispatches/GoToCoopDispatch.d.ts +2 -0
  27. package/dist/dispatches/GoToCoopDispatch.js +19 -20
  28. package/dist/dispatches/GoToGrenadeDispatch.d.ts +2 -0
  29. package/dist/dispatches/GoToGrenadeDispatch.js +15 -16
  30. package/dist/dispatches/GoToPlayerDispatch.d.ts +3 -1
  31. package/dist/dispatches/GoToPlayerDispatch.js +16 -21
  32. package/dist/dispatches/GoToSpatulaDispatch.d.ts +2 -0
  33. package/dist/dispatches/GoToSpatulaDispatch.js +11 -14
  34. package/dist/dispatches/LookAtDispatch.d.ts +3 -1
  35. package/dist/dispatches/LookAtDispatch.js +8 -6
  36. package/dist/dispatches/LookAtPosDispatch.d.ts +3 -1
  37. package/dist/dispatches/LookAtPosDispatch.js +1 -1
  38. package/dist/dispatches/MeleeDispatch.d.ts +2 -0
  39. package/dist/dispatches/MeleeDispatch.js +4 -4
  40. package/dist/dispatches/MovementDispatch.d.ts +3 -1
  41. package/dist/dispatches/MovementDispatch.js +1 -1
  42. package/dist/dispatches/PauseDispatch.d.ts +2 -0
  43. package/dist/dispatches/ReloadDispatch.d.ts +2 -0
  44. package/dist/dispatches/ReloadDispatch.js +17 -10
  45. package/dist/dispatches/ReportPlayerDispatch.d.ts +4 -2
  46. package/dist/dispatches/ReportPlayerDispatch.js +8 -6
  47. package/dist/dispatches/ResetGameDispatch.d.ts +2 -0
  48. package/dist/dispatches/SaveLoadoutDispatch.d.ts +4 -2
  49. package/dist/dispatches/SaveLoadoutDispatch.js +9 -8
  50. package/dist/dispatches/SpawnDispatch.d.ts +2 -0
  51. package/dist/dispatches/SpawnDispatch.js +5 -1
  52. package/dist/dispatches/SwapWeaponDispatch.d.ts +3 -1
  53. package/dist/dispatches/SwapWeaponDispatch.js +1 -1
  54. package/dist/dispatches/SwitchTeamDispatch.d.ts +2 -0
  55. package/dist/dispatches/SwitchTeamDispatch.js +2 -1
  56. package/dist/dispatches/ThrowGrenadeDispatch.d.ts +3 -1
  57. package/dist/dispatches/index.d.ts +105 -182
  58. package/dist/dispatches/index.js +24 -25
  59. package/dist/enums.d.ts +154 -0
  60. package/dist/enums.js +114 -0
  61. package/dist/env/fetch.d.ts +15 -0
  62. package/dist/env/fetch.js +113 -79
  63. package/dist/env/globals.d.ts +9 -0
  64. package/dist/env/globals.js +11 -9
  65. package/dist/index.d.ts +31 -0
  66. package/dist/index.js +24 -14
  67. package/dist/packets/addPlayer.js +63 -0
  68. package/dist/packets/beginShellStreak.js +44 -0
  69. package/dist/packets/challengeCompleted.js +16 -0
  70. package/dist/packets/changeCharacter.js +46 -0
  71. package/dist/packets/chat.js +10 -0
  72. package/dist/packets/collectItem.js +26 -0
  73. package/dist/packets/die.js +40 -0
  74. package/dist/packets/endShellStreak.js +21 -0
  75. package/dist/packets/eventModifier.js +8 -0
  76. package/dist/packets/explode.js +19 -0
  77. package/dist/packets/fire.js +21 -0
  78. package/dist/packets/gameAction.js +33 -0
  79. package/dist/packets/gameJoined.js +64 -0
  80. package/dist/packets/gameOptions.js +27 -0
  81. package/dist/packets/hitMe.js +12 -0
  82. package/dist/packets/hitMeHardBoiled.js +18 -0
  83. package/dist/packets/hitThem.js +12 -0
  84. package/dist/packets/melee.js +8 -0
  85. package/dist/packets/metaGameState.js +62 -0
  86. package/dist/packets/pause.js +17 -0
  87. package/dist/packets/ping.js +19 -0
  88. package/dist/packets/playerInfo.js +15 -0
  89. package/dist/packets/reload.js +17 -0
  90. package/dist/packets/removePlayer.js +10 -0
  91. package/dist/packets/respawn.js +31 -0
  92. package/dist/packets/socketReady.js +16 -0
  93. package/dist/packets/spawnItem.js +11 -0
  94. package/dist/packets/swapWeapon.js +11 -0
  95. package/dist/packets/switchTeam.js +13 -0
  96. package/dist/packets/syncMe.js +25 -0
  97. package/dist/packets/syncThem.js +63 -0
  98. package/dist/packets/throwGrenade.js +17 -0
  99. package/dist/packets/updateBalance.js +8 -0
  100. package/dist/pathing/astar.js +33 -20
  101. package/dist/pathing/mapnode.d.ts +3 -1
  102. package/dist/pathing/mapnode.js +170 -65
  103. package/dist/socket.d.ts +21 -6
  104. package/dist/socket.js +48 -38
  105. package/dist/util.d.ts +4 -1
  106. package/dist/util.js +102 -44
  107. package/dist/wasm/bytes.d.ts +1 -1
  108. package/dist/wasm/bytes.js +1 -1
  109. package/dist/wasm/direct.d.ts +1 -1
  110. package/dist/wasm/direct.js +13 -13
  111. package/dist/wasm/legacy.d.ts +7 -6
  112. package/dist/wasm/legacy.js +14 -8
  113. package/package.json +43 -30
  114. package/dist/comm/index.d.ts +0 -12
  115. package/dist/comm/index.js +0 -11
  116. package/dist/constants/changelog.d.ts +0 -7
  117. package/dist/constants/housePromo.d.ts +0 -40
  118. package/dist/constants/language.d.ts +0 -3
  119. package/dist/constants/notices.d.ts +0 -4
  120. package/dist/constants/shellNews.d.ts +0 -12
  121. package/dist/constants/shellYoutube.d.ts +0 -12
  122. package/dist/constants/shopItems.d.ts +0 -15
  123. package/dist/constants/sounds.d.ts +0 -10
  124. package/dist/matchmaker.d.ts +0 -50
  125. package/dist/matchmaker.js +0 -141
  126. package/dist/pathing/binaryheap.d.ts +0 -18
  127. package/dist/pathing/binaryheap.js +0 -79
  128. package/dist/wasm/util.d.ts +0 -9
  129. package/dist/wasm/util.js +0 -19
package/dist/bot.js CHANGED
@@ -4,19 +4,13 @@ import CommOut from "./comm/CommOut.js";
4
4
  import CloseCode from "./constants/CloseCode.js";
5
5
  import CommCode from "./constants/CommCode.js";
6
6
  import GamePlayer from "./bot/GamePlayer.js";
7
- import Matchmaker from "./matchmaker.js";
8
7
  import yolkws from "./socket.js";
9
8
  import {
10
9
  ChiknWinnerDailyLimit,
11
- CollectType,
12
10
  CoopState,
13
11
  findItemById,
14
12
  FramesBetweenSyncs,
15
- GameAction,
16
13
  GameMode,
17
- GameOptionFlag,
18
- GunList,
19
- ItemType,
20
14
  Movement,
21
15
  PlayType,
22
16
  ShellStreak,
@@ -24,57 +18,89 @@ import {
24
18
  } from "./constants/index.js";
25
19
  import LookAtPosDispatch from "./dispatches/LookAtPosDispatch.js";
26
20
  import MovementDispatch from "./dispatches/MovementDispatch.js";
27
- import globals from "./env/globals.js";
28
- import { NodeList } from "./pathing/mapnode.js";
29
- import { coords } from "./wasm/direct.js";
30
- import { fetchMap, initKotcZones } from "./util.js";
21
+ import { DispatchIndex } from "./dispatches/index.js";
22
+ import { coords, validate } from "./wasm/direct.js";
23
+ import { createError } from "./util.js";
31
24
  import { Challenges } from "./constants/challenges.js";
32
25
  import { Maps } from "./constants/maps.js";
33
26
  import { Regions } from "./constants/regions.js";
34
- const GameModeById = Object.fromEntries(Object.entries(GameMode).map(([key, value]) => [value, key]));
35
- const CCGameOptionFlag = Object.fromEntries(Object.entries(GameOptionFlag).map(([k, v]) => [k[0].toLowerCase() + k.slice(1), v]));
36
- const intents = {
37
- CHALLENGES: 1,
38
- BOT_STATS: 2,
39
- PATHFINDING: 3,
40
- PING: 5,
41
- COSMETIC_DATA: 6,
42
- PLAYER_HEALTH: 7,
43
- PACKET_HOOK: 8,
44
- LOG_PACKETS: 10,
45
- NO_LOGIN: 11,
46
- DEBUG_BUFFER: 12,
47
- NO_AFK_KICK: 16,
48
- LOAD_MAP: 17,
49
- OBSERVE_GAME: 18,
50
- NO_REGION_CHECK: 19,
51
- NO_EXIT_ON_ERROR: 20,
52
- RENEW_SESSION: 21,
53
- VIP_HIDE_BADGE: 22
54
- };
27
+ import {
28
+ BuyItemError,
29
+ ChallengeClaimError,
30
+ ChallengeRerollError,
31
+ ChicknWinnerError,
32
+ ClaimSocialError,
33
+ ClaimURLError,
34
+ CleanupLevel,
35
+ GameFindError,
36
+ GameJoinError,
37
+ Intents,
38
+ LoginError,
39
+ MatchmakerError,
40
+ RedeemCodeError
41
+ } from "./enums.js";
42
+ import processAddPlayerPacket from "./packets/addPlayer.js";
43
+ import processBeginShellStreakPacket from "./packets/beginShellStreak.js";
44
+ import processChallengeCompletedPacket from "./packets/challengeCompleted.js";
45
+ import processChangeCharacterPacket from "./packets/changeCharacter.js";
46
+ import processChatPacket from "./packets/chat.js";
47
+ import processCollectItemPacket from "./packets/collectItem.js";
48
+ import processDiePacket from "./packets/die.js";
49
+ import processEndShellStreakPacket from "./packets/endShellStreak.js";
50
+ import processEventModifierPacket from "./packets/eventModifier.js";
51
+ import processExplodePacket from "./packets/explode.js";
52
+ import processFirePacket from "./packets/fire.js";
53
+ import processGameActionPacket from "./packets/gameAction.js";
54
+ import processGameJoinedPacket from "./packets/gameJoined.js";
55
+ import processGameOptionsPacket from "./packets/gameOptions.js";
56
+ import processHitMeHardBoiledPacket from "./packets/hitMeHardBoiled.js";
57
+ import processHitMePacket from "./packets/hitMe.js";
58
+ import processHitThemPacket from "./packets/hitThem.js";
59
+ import processMeleePacket from "./packets/melee.js";
60
+ import processMetaGameStatePacket from "./packets/metaGameState.js";
61
+ import processPausePacket from "./packets/pause.js";
62
+ import processPingPacket from "./packets/ping.js";
63
+ import processPlayerInfoPacket from "./packets/playerInfo.js";
64
+ import processReloadPacket from "./packets/reload.js";
65
+ import processRemovePlayerPacket from "./packets/removePlayer.js";
66
+ import processRespawnPacket from "./packets/respawn.js";
67
+ import processSocketReadyPacket from "./packets/socketReady.js";
68
+ import processSpawnItemPacket from "./packets/spawnItem.js";
69
+ import processSwitchTeamPacket from "./packets/switchTeam.js";
70
+ import processSyncMePacket from "./packets/syncMe.js";
71
+ import processSyncThemPacket from "./packets/syncThem.js";
72
+ import processThrowGrenadePacket from "./packets/throwGrenade.js";
73
+ import processUpdateBalancePacket from "./packets/updateBalance.js";
74
+ import processSwapWeaponPacket from "./packets/swapWeapon.js";
55
75
  const mod = (n, m) => (n % m + m) % m;
56
76
 
57
77
  export class Bot {
58
- static Intents = intents;
59
- Intents = intents;
78
+ intents = [];
79
+ regionList = [];
80
+ matchmakerListeners = [];
60
81
  #dispatches = [];
61
82
  #hooks = {};
62
83
  #globalHooks = [];
63
84
  #initialAccount;
64
85
  #initialGame;
86
+ errorLogger = (...args) => console.error(...args);
65
87
  constructor(params = {}) {
66
- if (params.proxy && globals.isBrowser)
67
- this.processError("proxies do not work and hence are not supported in the browser");
88
+ if (params.proxy && typeof process === "undefined")
89
+ throw new Error("proxies do not work in this environment");
68
90
  this.intents = params.intents || [];
69
- if (this.intents.includes(this.Intents.COSMETIC_DATA)) {
91
+ if (this.intents.includes(Intents.COSMETIC_DATA)) {
70
92
  const ballCap = findItemById(1001);
71
93
  if (!ballCap)
72
- this.processError("you cannot use the COSMETIC_DATA intent inside of the singlefile browser bundles");
94
+ throw new Error("you cannot use the COSMETIC_DATA intent inside of the browser bundles");
73
95
  }
96
+ if (this.intents.includes(Intents.SIMULATION) && !this.intents.includes(Intents.PATHFINDING))
97
+ throw new Error("the SIMULATION intent requires the PATHFINDING intent");
74
98
  this.instance = params.instance || "shellshock.io";
75
99
  this.protocol = params.protocol || "wss";
76
100
  this.proxy = params.proxy || "";
77
101
  this.connectionTimeout = params.connectionTimeout || 5000;
102
+ if (typeof params.errorLogger === "function")
103
+ this.errorLogger = params.errorLogger;
78
104
  this.state = {
79
105
  name: "yolkbot",
80
106
  weaponIdx: 0,
@@ -84,23 +110,27 @@ export class Bot {
84
110
  yaw: 0,
85
111
  pitch: 0,
86
112
  controlKeys: 0,
113
+ onGround: 4,
114
+ dx: 0,
115
+ dy: 0,
116
+ dz: 0,
87
117
  reloading: false,
88
118
  swappingGun: false,
89
119
  usingMelee: false,
90
120
  stateIdx: 0,
91
121
  serverStateIdx: 0,
92
122
  shotsFired: 0,
93
- buffer: [],
94
- left: false
123
+ buffer: []
95
124
  };
96
125
  this.players = {};
97
126
  this.me = new GamePlayer({});
98
127
  this.game = {
99
128
  raw: {},
100
129
  code: "",
130
+ region: "",
101
131
  socket: null,
102
132
  gameModeId: 0,
103
- gameMode: GameModeById[0],
133
+ gameMode: "ffa",
104
134
  mapIdx: 0,
105
135
  map: {
106
136
  filename: "",
@@ -115,7 +145,6 @@ export class Bot {
115
145
  availability: "both",
116
146
  numPlayers: "18",
117
147
  raw: {},
118
- nodes: {},
119
148
  zones: []
120
149
  },
121
150
  playerLimit: 0,
@@ -131,22 +160,24 @@ export class Bot {
131
160
  weaponsDisabled: Array(7).fill(false),
132
161
  mustUseSecondary: false
133
162
  },
134
- collectables: [[], []],
163
+ collectibles: [[], []],
135
164
  teamScore: [0, 0, 0],
136
165
  spatula: {
137
166
  coords: { x: 0, y: 0, z: 0 },
138
167
  controlledBy: 0,
139
168
  controlledByTeam: 0
140
169
  },
141
- stage: CoopState.Capturing,
142
- zoneNumber: 0,
143
- activeZone: [],
144
- capturing: 0,
145
- captureProgress: 0,
146
- numCapturing: 0,
147
- capturePercent: 0
170
+ kotc: {
171
+ stage: CoopState.Capturing,
172
+ zoneIdx: 0,
173
+ activeZone: [],
174
+ capturing: 0,
175
+ captureProgress: 0,
176
+ numCapturing: 0,
177
+ capturePercent: 0
178
+ }
148
179
  };
149
- this.#initialGame = this.game;
180
+ this.#initialGame = JSON.parse(JSON.stringify(this.game));
150
181
  this.account = {
151
182
  id: 0,
152
183
  firebase: {
@@ -173,15 +204,7 @@ export class Bot {
173
204
  classIdx: 0,
174
205
  colorIdx: 0,
175
206
  grenadeId: 0,
176
- primaryId: [
177
- 3100,
178
- 3600,
179
- 3400,
180
- 3800,
181
- 4000,
182
- 4200,
183
- 4500
184
- ],
207
+ primaryId: [3100, 3600, 3400, 3800, 4000, 4200, 4500],
185
208
  secondaryId: new Array(7).fill(3000),
186
209
  stampPositionX: 0,
187
210
  stampPositionY: 0
@@ -190,42 +213,34 @@ export class Bot {
190
213
  vip: false,
191
214
  emailVerified: false,
192
215
  isAged: false,
193
- eggBalance: 0,
216
+ isCG: false,
194
217
  adminRoles: 0,
195
218
  rawLoginData: {},
196
- isDoubleEggWeeknd: () => {
197
- const day = new Date().getUTCDay();
198
- const hours = new Date().getUTCHours();
199
- return day >= 5 && hours >= 20 || day === 6 || day === 0;
200
- }
219
+ eggBalance: 0
201
220
  };
202
- this.#initialAccount = this.account;
203
- this.matchmaker = null;
221
+ this.#initialAccount = JSON.parse(JSON.stringify(this.account));
204
222
  this.api = new API({
205
223
  proxy: this.proxy,
206
224
  protocol: this.protocol,
207
225
  instance: this.instance,
208
- maxRetries: params?.apiMaxRetries,
209
226
  connectionTimeout: this.connectionTimeout
210
227
  });
211
228
  this.ping = 0;
212
229
  this.lastPingTime = -1;
213
230
  this.lastDeathTime = -1;
214
- this.lastUpdateTick = 0;
215
231
  this.pathing = {
216
232
  nodeList: null,
217
- followingPath: false,
218
233
  activePath: null,
219
234
  activeNode: null,
220
235
  activeNodeIdx: 0
221
236
  };
222
237
  this.hasQuit = false;
223
- if (this.intents.includes(this.Intents.NO_AFK_KICK))
238
+ if (this.intents.includes(Intents.NO_AFK_KICK))
224
239
  this.afkKickInterval = 0;
225
- if (this.intents.includes(this.Intents.RENEW_SESSION))
240
+ if (this.intents.includes(Intents.RENEW_SESSION))
226
241
  this.renewSessionInterval = 0;
227
242
  }
228
- dispatch(dispatch) {
243
+ dispatch(dispatch, isEmit) {
229
244
  if (dispatch.validate(this)) {
230
245
  if (dispatch.check(this))
231
246
  dispatch.execute(this);
@@ -233,6 +248,9 @@ export class Bot {
233
248
  this.#dispatches.push(dispatch);
234
249
  return true;
235
250
  }
251
+ this.errorLogger(`${isEmit ? "emit" : "dispatch"}: validation failed for dispatch ${dispatch.constructor.name}`);
252
+ this.errorLogger("this means the dispatch will NEVER RUN!");
253
+ this.errorLogger("make sure all parameters are valid and that any player IDs are in the game");
236
254
  return false;
237
255
  }
238
256
  async createAccount(email, pass) {
@@ -260,53 +278,49 @@ export class Bot {
260
278
  return this.processLoginData(loginData);
261
279
  }
262
280
  processLoginData(loginData) {
263
- if (typeof loginData !== "object") {
264
- this.emit("authFail", loginData);
265
- return loginData;
281
+ if (!loginData.ok || !loginData.playerOutput) {
282
+ this.$emit("authFail", loginData);
283
+ if (loginData.ok && !loginData.playerOutput)
284
+ this.errorLogger("processLoginData: missing playerOutput but is ok", loginData);
285
+ return { ...loginData, error: LoginError.InternalError, ok: false };
266
286
  }
267
287
  if (loginData.banRemaining) {
268
- this.emit("banned", loginData.banRemaining);
269
- return "account_banned";
288
+ this.$emit("banned", loginData.banRemaining);
289
+ return createError(LoginError.AccountBanned);
270
290
  }
271
- if (!loginData.playerOutput) {
272
- this.emit("authFail", loginData);
273
- return loginData;
274
- }
275
- this.account.firebase = loginData.firebase || {};
276
- loginData = loginData.playerOutput;
277
- this.account.rawLoginData = loginData;
278
- this.account.adminRoles = loginData.adminRoles || 0;
279
- this.account.eggBalance = loginData.currentBalance;
280
- this.account.emailVerified = loginData.emailVerified;
281
- this.account.firebaseId = loginData.firebaseId;
282
- this.account.id = loginData.id;
283
- this.account.isAged = new Date(loginData.dateCreated).getTime() < 1714546800000;
284
- this.account.loadout = loginData.loadout;
285
- this.account.ownedItemIds = loginData.ownedItemIds;
286
- this.account.session = loginData.session;
287
- this.account.sessionId = loginData.sessionId;
288
- this.account.vip = loginData.active_sub === "IsVIP";
289
- if (this.intents.includes(this.Intents.BOT_STATS))
291
+ this.account.firebase = loginData.firebase;
292
+ const output = loginData.playerOutput;
293
+ this.account.rawLoginData = output;
294
+ this.account.adminRoles = output.adminRoles || 0;
295
+ this.account.eggBalance = output.currentBalance;
296
+ this.account.emailVerified = output.emailVerified;
297
+ this.account.firebaseId = output.firebaseId;
298
+ this.account.id = output.id;
299
+ this.account.isAged = new Date(output.dateCreated).getTime() < 1714546800000;
300
+ this.account.isCG = output.cgAccountStatus?.hasAccount;
301
+ this.account.loadout = output.loadout;
302
+ this.account.ownedItemIds = output.ownedItemIds;
303
+ this.account.session = output.session;
304
+ this.account.sessionId = output.sessionId;
305
+ this.account.vip = output.active_sub === "IsVIP";
306
+ if (this.intents.includes(Intents.BOT_STATS))
290
307
  this.account.stats = {
291
- lifetime: loginData.statsLifetime,
292
- monthly: loginData.statsCurrent
308
+ lifetime: output.statsLifetime,
309
+ monthly: output.statsCurrent
293
310
  };
294
- if (this.intents.includes(this.Intents.CHALLENGES))
295
- this.#importChallenges(loginData.challenges);
296
- this.emit("authSuccess", this.account);
297
- if (this.intents.includes(this.Intents.RENEW_SESSION)) {
311
+ if (this.intents.includes(Intents.CHALLENGES))
312
+ this.#importChallenges(output.challenges);
313
+ this.$emit("authSuccess", this.account);
314
+ if (this.intents.includes(Intents.RENEW_SESSION)) {
298
315
  this.renewSessionInterval = setInterval(async () => {
299
316
  if (!this.account?.sessionId)
300
317
  return clearInterval(this.renewSessionInterval);
301
- const res = await this.api.queryServices({
302
- cmd: "renewSession",
303
- sessionId: this.account.sessionId
304
- });
318
+ const res = await this.api.queryServices({ cmd: "renewSession", sessionId: this.account.sessionId });
305
319
  if (res.data !== "renewed")
306
- this.emit("sessionExpired");
307
- }, 600000);
320
+ this.$emit("sessionExpired");
321
+ }, 10 * 60 * 1000);
308
322
  }
309
- return this.account;
323
+ return { ok: true, account: this.account };
310
324
  }
311
325
  #importChallenges(challengeArray) {
312
326
  this.account.challenges = [];
@@ -329,213 +343,333 @@ export class Bot {
329
343
  });
330
344
  }
331
345
  }
332
- async initMatchmaker() {
333
- if (!this.account.sessionId && !this.intents.includes(this.Intents.NO_LOGIN)) {
346
+ async createMatchmaker() {
347
+ const matchmaker = new yolkws(`${this.protocol}://${this.instance}/matchmaker/`, { proxy: this.proxy, errorLogger: this.errorLogger });
348
+ matchmaker.autoReconnect = true;
349
+ const didConnect = await matchmaker.tryConnect();
350
+ if (!didConnect)
351
+ return createError(MatchmakerError.WebSocketConnectFail);
352
+ this.matchmaker = matchmaker;
353
+ const uuidTimeouts = [];
354
+ this.matchmaker.onmessage = async (e) => {
355
+ const data = JSON.parse(e.data);
356
+ if (data.command === "validateUUID") {
357
+ const timeout = setTimeout(() => {
358
+ console.error("createMatchmaker: the matchmaker did not respond to our validateUUID");
359
+ console.error("createMatchmaker: this means yolkbot is broken, please report this on Github");
360
+ console.error("createMatchmaker: https://github.com/yolkorg/yolkbot (or join the Discord)");
361
+ }, 5000);
362
+ uuidTimeouts.push(timeout);
363
+ return this.matchmaker.send(JSON.stringify({ command: "validateUUID", hash: await validate(data.uuid) }));
364
+ }
365
+ if (uuidTimeouts.length) {
366
+ uuidTimeouts.forEach((t) => clearTimeout(t));
367
+ uuidTimeouts.length = 0;
368
+ }
369
+ this.matchmakerListeners.forEach((listener) => listener(data));
370
+ };
371
+ return { ok: true };
372
+ }
373
+ async getRegions() {
374
+ if (!this.matchmaker) {
375
+ const mmConnection = await this.createMatchmaker();
376
+ if (!mmConnection.ok)
377
+ return mmConnection;
378
+ }
379
+ return new Promise((res) => {
380
+ const listener = (data) => {
381
+ if (data.command === "regionList") {
382
+ this.matchmakerListeners.splice(this.matchmakerListeners.indexOf(listener), 1);
383
+ this.regionList = data.regionList;
384
+ res({ ok: true, regionList: this.regionList });
385
+ }
386
+ };
387
+ this.matchmakerListeners.push(listener);
388
+ this.matchmaker.send(JSON.stringify({ command: "regionList" }));
389
+ });
390
+ }
391
+ async initSession() {
392
+ if (!this.account.sessionId && !this.intents.includes(Intents.SKIP_LOGIN)) {
334
393
  const anonLogin = await this.loginAnonymously();
335
- if (typeof anonLogin !== "object")
394
+ if (!anonLogin.ok)
336
395
  return anonLogin;
337
396
  }
338
397
  if (!this.matchmaker) {
339
- this.matchmaker = new Matchmaker({
340
- api: this.api,
341
- proxy: this.proxy,
342
- protocol: this.protocol,
343
- instance: this.instance,
344
- sessionId: this.account.sessionId,
345
- connectionTimeout: this.connectionTimeout,
346
- noLogin: this.intents.includes(this.Intents.NO_LOGIN)
347
- });
348
- const didConnect = await this.matchmaker.ws.tryConnect();
349
- if (!didConnect)
350
- return "matchmaker_tryconnect_failed";
351
- this.matchmaker.on("authFail", (data) => this.emit("authFail", data));
352
- this.matchmaker.on("error", (data) => this.processError(data));
398
+ const mmConnection = await this.createMatchmaker();
399
+ if (!mmConnection.ok)
400
+ return mmConnection;
353
401
  }
354
- return true;
402
+ return { ok: true };
355
403
  }
356
- async findPublicGame(region, modeId) {
404
+ async findPublicGame(region, mode) {
357
405
  if (typeof region !== "string")
358
- return "no_region_passed";
359
- if (!Regions.find((r) => r.id === region) && !this.intents.includes(this.Intents.NO_REGION_CHECK))
360
- return "invalid_region_passed";
361
- if (typeof modeId !== "number")
362
- return "no_mode_passed";
363
- if (Object.values(GameMode).indexOf(modeId) === -1)
364
- return "invalid_mode_passed";
365
- if (!await this.initMatchmaker())
366
- return "matchmaker_init_fail";
406
+ return createError(GameFindError.MissingParams);
407
+ const regions = this.regionList.length ? this.regionList : Regions;
408
+ if (!regions.find((r) => r.id === region))
409
+ return createError(GameFindError.InvalidRegion);
410
+ let computedModeId;
411
+ if (typeof mode === "number") {
412
+ if (Object.values(GameMode).indexOf(mode) > -1)
413
+ computedModeId = mode;
414
+ else
415
+ return createError(GameFindError.InvalidMode);
416
+ } else if (typeof mode === "string") {
417
+ const modeEntry = Object.keys(GameMode).find((key) => key.toLowerCase() === mode.toLowerCase());
418
+ if (modeEntry)
419
+ computedModeId = GameMode[modeEntry];
420
+ else
421
+ return createError(GameFindError.InvalidMode);
422
+ } else
423
+ return createError(GameFindError.InvalidMode);
424
+ const initInfo = await this.initSession();
425
+ if (!initInfo.ok)
426
+ return initInfo;
367
427
  const game = await new Promise((resolve) => {
368
428
  const listener = (msg) => {
369
429
  if (msg.command === "notice")
370
430
  return;
371
- this.matchmaker.off("msg", listener);
431
+ this.matchmakerListeners.splice(this.matchmakerListeners.indexOf(listener), 1);
372
432
  if (msg.command === "gameFound") {
373
433
  if (msg.useAltGameURL)
374
434
  this.state.useAltGameURL = true;
375
435
  return resolve(msg);
376
436
  }
377
437
  if (msg.error === "sessionNotFound")
378
- return resolve("internal_session_error");
379
- this.processError("unknown matchmaker response", JSON.stringify(msg));
438
+ return resolve(createError(GameFindError.SessionExpired));
439
+ this.errorLogger("findPublicGame: unknown matchmaker response", JSON.stringify(msg));
440
+ resolve(createError(GameFindError.InternalError));
380
441
  };
381
- this.matchmaker.on("msg", listener);
382
- this.matchmaker.send({
442
+ this.matchmakerListeners.push(listener);
443
+ this.matchmaker.send(JSON.stringify({
383
444
  command: "findGame",
384
445
  region,
385
446
  playType: PlayType.JoinPublic,
386
- gameType: modeId,
447
+ gameType: computedModeId,
387
448
  sessionId: this.account.sessionId
388
- });
449
+ }));
389
450
  });
390
- return game;
451
+ if (!game.ok)
452
+ return game;
453
+ return {
454
+ ok: true,
455
+ raw: game,
456
+ id: game.id,
457
+ uuid: game.uuid,
458
+ region: game.region,
459
+ private: game.private,
460
+ subdomain: game.subdomain
461
+ };
391
462
  }
392
- async createPrivateGame(region, modeId, map) {
463
+ async createPrivateGame(region, mode, map) {
393
464
  if (typeof region !== "string")
394
- return "no_region_passed";
395
- if (!Regions.find((r) => r.id === region) && !this.intents.includes(this.Intents.NO_REGION_CHECK))
396
- return "invalid_region_passed";
397
- if (typeof modeId !== "number")
398
- return "no_mode_passed";
399
- if (Object.values(GameMode).indexOf(modeId) === -1)
400
- return "invalid_mode_passed";
401
- if (typeof map !== "string")
402
- return "no_map_passed";
465
+ return createError(GameFindError.MissingParams);
466
+ const regions = this.regionList.length ? this.regionList : Regions;
467
+ if (!regions.find((r) => r.id === region))
468
+ return createError(GameFindError.InvalidRegion);
469
+ let computedModeId;
470
+ if (typeof mode === "number") {
471
+ if (Object.values(GameMode).indexOf(mode) > -1)
472
+ computedModeId = mode;
473
+ else
474
+ return createError(GameFindError.InvalidMode);
475
+ } else if (typeof mode === "string") {
476
+ const modeEntry = Object.keys(GameMode).find((key) => key.toLowerCase() === mode.toLowerCase());
477
+ if (modeEntry)
478
+ computedModeId = GameMode[modeEntry];
479
+ else
480
+ return createError(GameFindError.InvalidMode);
481
+ } else
482
+ return createError(GameFindError.InvalidMode);
403
483
  const mapIdx = Maps.findIndex((m) => m.name.toLowerCase() === map.toLowerCase());
404
484
  if (mapIdx === -1)
405
- return "invalid_map_passed";
406
- if (!await this.initMatchmaker())
407
- return "matchmaker_init_fail";
485
+ return createError(GameFindError.InvalidMap);
486
+ const initInfo = await this.initSession();
487
+ if (!initInfo.ok)
488
+ return initInfo;
408
489
  const game = await new Promise((resolve) => {
409
490
  const listener = (msg) => {
410
491
  if (msg.command === "notice")
411
492
  return;
412
- this.matchmaker.off("msg", listener);
493
+ this.matchmakerListeners.splice(this.matchmakerListeners.indexOf(listener), 1);
413
494
  if (msg.command === "gameFound") {
414
495
  if (msg.useAltGameURL)
415
496
  this.state.useAltGameURL = true;
416
497
  return resolve(msg);
417
498
  }
418
499
  if (msg.error === "sessionNotFound")
419
- return resolve("internal_session_error");
420
- this.processError("unknown matchmaker response", JSON.stringify(msg));
500
+ return resolve(createError(GameFindError.SessionExpired));
501
+ this.errorLogger("createPrivateGame: unknown matchmaker response", JSON.stringify(msg));
502
+ resolve(createError(GameFindError.InternalError));
421
503
  };
422
- this.matchmaker.on("msg", listener);
423
- this.matchmaker.send({
504
+ this.matchmakerListeners.push(listener);
505
+ this.matchmaker.send(JSON.stringify({
424
506
  command: "findGame",
425
507
  region,
426
508
  playType: PlayType.CreatePrivate,
427
- gameType: modeId,
509
+ gameType: computedModeId,
428
510
  sessionId: this.account.sessionId,
429
511
  noobLobby: false,
430
512
  map: mapIdx
431
- });
513
+ }));
432
514
  });
433
- return game;
515
+ if (!game.ok)
516
+ return game;
517
+ return {
518
+ ok: true,
519
+ raw: game,
520
+ id: game.id,
521
+ uuid: game.uuid,
522
+ region: game.region,
523
+ private: game.private,
524
+ subdomain: game.subdomain
525
+ };
434
526
  }
435
527
  async join(name, data) {
436
528
  this.state.name = name || "yolkbot";
437
529
  if (typeof data === "string") {
438
530
  if (data.includes("#"))
439
531
  data = data.split("#")[1];
440
- if (!await this.initMatchmaker())
441
- return "matchmaker_init_fail";
532
+ const initInfo = await this.initSession();
533
+ if (!initInfo.ok)
534
+ return initInfo;
442
535
  const joinResult = await new Promise((resolve) => {
443
536
  const listener = (message) => {
444
537
  if (message.command === "gameFound") {
445
- this.matchmaker.off("msg", listener);
538
+ this.matchmakerListeners.splice(this.matchmakerListeners.indexOf(listener), 1);
446
539
  this.game.raw = message;
447
540
  this.game.code = message.id;
541
+ this.game.region = message.region;
448
542
  resolve(message.id);
449
543
  }
450
544
  if (message.error && message.error === "gameNotFound") {
451
- this.processError(`game "${data}" not found, it may have expired.`);
452
- this.leave();
453
- return "gameNotFound";
545
+ this.matchmakerListeners.splice(this.matchmakerListeners.indexOf(listener), 1);
546
+ resolve("gameNotFound");
454
547
  }
455
548
  };
456
- this.matchmaker.on("msg", listener);
457
- this.matchmaker.send({
549
+ this.matchmakerListeners.push(listener);
550
+ this.matchmaker.send(JSON.stringify({
458
551
  command: "joinGame",
459
552
  id: data,
460
553
  observe: false,
461
554
  sessionId: this.account.sessionId
462
- });
555
+ }));
463
556
  });
464
557
  if (joinResult === "gameNotFound")
465
- return "game_not_found";
466
- if (!this.game.raw.id)
467
- return this.processError("an internal error occured while joining the game, please report this to developers");
468
- }
469
- if (typeof data === "object") {
470
- if (this.account.id === 0)
471
- await this.loginAnonymously();
558
+ return createError(GameJoinError.GameNotFound);
559
+ if (!this.game.raw.id) {
560
+ this.errorLogger("join: invalid game data received from matchmaker:", this.game.raw);
561
+ return createError(GameJoinError.InternalError);
562
+ }
563
+ } else if (typeof data === "object") {
564
+ if (!data.id || !data.subdomain || !data.uuid || !data.region)
565
+ return createError(GameJoinError.InvalidObject);
566
+ if (this.account.id === 0) {
567
+ const anonAttempt = await this.loginAnonymously();
568
+ if (!anonAttempt.ok)
569
+ return anonAttempt;
570
+ }
472
571
  this.game.raw = data;
473
572
  this.game.code = this.game.raw.id;
474
- if (!this.game.raw.id)
475
- return "invalid_game_object";
476
- if (!this.game.raw.subdomain)
477
- return "invalid_game_object";
478
- if (!this.game.raw.uuid)
479
- return "invalid_game_object";
480
- if (data.useAltGameURL)
481
- this.state.useAltGameURL = true;
482
- }
483
- const host = this.state.useAltGameURL || this.host === "proxy.yolkbot.xyz" ? `${this.instance}/servers/${this.game.raw.subdomain}` : `${this.game.raw.subdomain}.${this.instance}`;
484
- this.game.socket = new yolkws(`${this.protocol}://${host}/game/${this.game.raw.id}`, this.proxy);
573
+ this.game.region = this.game.raw.region;
574
+ } else
575
+ return createError(GameJoinError.MissingParams);
576
+ const host = this.state.useAltGameURL || this.instance === "proxy.yolkbot.xyz" ? `${this.instance}/servers/${this.game.raw.subdomain}` : `${this.game.raw.subdomain}.${this.instance}`;
577
+ this.game.socket = new yolkws(`${this.protocol}://${host}/game/${this.game.raw.id}`, { proxy: this.proxy, errorLogger: this.errorLogger });
485
578
  this.game.socket.binaryType = "arraybuffer";
486
579
  this.game.socket.connectionTimeout = this.connectionTimeout;
487
580
  this.game.socket.onBeforeConnect = () => {
488
581
  this.game.socket.onmessage = (msg) => this.processPacket(msg.data);
489
582
  this.game.socket.onclose = (e) => {
490
- if (this.state.left)
491
- this.state.left = false;
492
- else {
493
- this.emit("close", e.code);
583
+ if (this.state?.inGame) {
584
+ this.$emit("close", e.code);
494
585
  this.leave(-1);
495
586
  }
496
587
  };
497
588
  };
498
589
  const didConnect = await this.game.socket.tryConnect();
499
590
  if (!didConnect)
500
- return "websocket_tryconnect_fail";
501
- return true;
591
+ return createError(GameJoinError.WebSocketConnectFail);
592
+ return { ok: true };
593
+ }
594
+ #processMovement(ndx, ndy, ndz) {
595
+ this.state.onGround = Math.max(--this.state.onGround, 0);
596
+ this.me.position.x += ndx;
597
+ this.me.position.y += ndy;
598
+ this.me.position.z += ndz;
599
+ const thisBlockX = Math.floor(this.me.position.x);
600
+ const thisBlockY = Math.floor(this.me.position.y);
601
+ const thisBlockZ = Math.floor(this.me.position.z);
602
+ const blockInMyHitbox = this.pathing.nodeList.at(thisBlockX, thisBlockY, thisBlockZ);
603
+ if (blockInMyHitbox && !blockInMyHitbox.isAir()) {
604
+ if (!this.climbing && blockInMyHitbox.isLadder()) {}
605
+ }
606
+ if (this.me.climbing) {} else if (blockInMyHitbox && blockInMyHitbox.isJumpPad()) {
607
+ this.me.position.y += 0.26;
608
+ this.me.jumping = true;
609
+ this.state.onGround = 0;
610
+ }
502
611
  }
503
612
  #processPathfinding() {
504
- const myPositionStr = Object.entries(this.me.position).map((entry) => Math.floor(entry[1])).join(",");
505
- if (myPositionStr === this.pathing.activePath[this.pathing.activePath.length - 1].positionStr) {
506
- this.pathing.followingPath = false;
613
+ const pathLen = this.pathing.activePath.length;
614
+ const lastNode = this.pathing.activePath[pathLen - 1];
615
+ const myPos = this.me.position;
616
+ const myFloorY = Math.floor(myPos.y);
617
+ const distToEndCenter = Math.hypot(myPos.x - lastNode.flatCenter.x, myPos.z - lastNode.flatCenter.z);
618
+ if (distToEndCenter < 0.3) {
507
619
  this.pathing.activePath = null;
508
620
  this.pathing.activeNode = null;
509
621
  this.pathing.activeNodeIdx = 0;
510
622
  this.dispatch(new MovementDispatch(0));
623
+ this.$emit("pathfindComplete");
511
624
  } else {
512
- let positionTarget;
513
- if (this.pathing.activeNodeIdx < this.pathing.activePath.length - 1) {
514
- positionTarget = this.pathing.activePath[this.pathing.activeNodeIdx + 1].flatCenter();
515
- this.dispatch(new LookAtPosDispatch(positionTarget));
516
- } else {
517
- positionTarget = this.pathing.activePath[this.pathing.activeNodeIdx].flatCenter();
518
- this.dispatch(new LookAtPosDispatch(positionTarget));
625
+ let shouldJump = false;
626
+ if (this.pathing.activeNodeIdx < pathLen - 1) {
627
+ const currentNode = this.pathing.activePath[this.pathing.activeNodeIdx];
628
+ const nextNode = this.pathing.activePath[this.pathing.activeNodeIdx + 1];
629
+ const dx = Math.abs(currentNode.x - nextNode.x);
630
+ const dz = Math.abs(currentNode.z - nextNode.z);
631
+ const isParkourJump = dx === 2 && dz === 0 || dx === 0 && dz === 2 || dx === 2 && dz === 1 || dx === 1 && dz === 2 || dx === 2 && dz === 2;
632
+ if (isParkourJump) {
633
+ const localX = myPos.x - Math.floor(myPos.x);
634
+ const localZ = myPos.z - Math.floor(myPos.z);
635
+ const headingX = nextNode.x > currentNode.x ? 1 : nextNode.x < currentNode.x ? -1 : 0;
636
+ const headingZ = nextNode.z > currentNode.z ? 1 : nextNode.z < currentNode.z ? -1 : 0;
637
+ let distToEdge = 0;
638
+ if (headingX > 0)
639
+ distToEdge = 1 - localX;
640
+ else if (headingX < 0)
641
+ distToEdge = localX;
642
+ else if (headingZ > 0)
643
+ distToEdge = 1 - localZ;
644
+ else if (headingZ < 0)
645
+ distToEdge = localZ;
646
+ if (distToEdge <= 0.04)
647
+ shouldJump = true;
648
+ }
519
649
  }
520
- for (const node of this.pathing.activePath) {
521
- if (node.flatRadialDistance(this.me.position) < 0.1 && node.position.y === Math.floor(this.me.position.y)) {
522
- if (this.pathing.activePath.indexOf(node) >= this.pathing.activeNodeIdx) {
523
- this.pathing.activeNodeIdx = this.pathing.activePath.indexOf(node) + 1;
524
- this.pathing.activeNode = this.pathing.activePath[this.pathing.activeNodeIdx];
525
- break;
526
- }
650
+ if (this.pathing.activeNodeIdx < pathLen) {
651
+ const positionTarget = this.pathing.activePath[this.pathing.activeNodeIdx].flatCenter;
652
+ if (this.state.onGround)
653
+ this.dispatch(new LookAtPosDispatch(positionTarget));
654
+ }
655
+ for (let i = this.pathing.activeNodeIdx;i < pathLen; i++) {
656
+ const node = this.pathing.activePath[i];
657
+ if (node.flatRadialDistance(myPos) < 0.1 && node.y === myFloorY) {
658
+ this.pathing.activeNodeIdx = i + 1;
659
+ this.pathing.activeNode = this.pathing.activePath[this.pathing.activeNodeIdx];
660
+ break;
527
661
  }
528
662
  }
529
- if (!(this.state.controlKeys & Movement.Forward))
530
- this.dispatch(new MovementDispatch(Movement.Forward));
663
+ const movementKeys = Movement.Forward | (shouldJump ? Movement.Jump : 0);
664
+ this.dispatch(new MovementDispatch(movementKeys));
531
665
  }
532
666
  }
533
667
  update() {
534
668
  if (this.hasQuit)
535
669
  return;
536
- if (this.pathing.followingPath && this.intents.includes(this.Intents.PATHFINDING))
670
+ if (this.pathing.activePath && this.intents.includes(Intents.PATHFINDING))
537
671
  this.#processPathfinding();
538
- for (let i = 0;i < this.#dispatches.length; i++) {
672
+ for (let i = this.#dispatches.length - 1;i >= 0; i--) {
539
673
  const disp = this.#dispatches[i];
540
674
  if (disp.check(this)) {
541
675
  disp.execute(this);
@@ -545,25 +679,99 @@ export class Bot {
545
679
  this.state.chatLines = Math.max(0, this.state.chatLines - 1 / (30 * 4));
546
680
  if (this.me.playing) {
547
681
  const currentIdx = this.state.stateIdx;
548
- if (this.intents.includes(this.Intents.DEBUG_BUFFER)) {
682
+ const isBufferDebug = this.intents.includes(Intents.DEBUG_BUFFER);
683
+ if (isBufferDebug)
549
684
  console.log("setting buffer for idx", currentIdx);
550
- console.log("checking...shotsFired", this.state.shotsFired);
551
- }
552
685
  this.me.jumping = !!(this.state.controlKeys & Movement.Jump);
553
686
  this.state.buffer[currentIdx] = {
554
687
  controlKeys: this.state.controlKeys,
555
688
  yaw: this.state.yaw,
556
689
  pitch: this.state.pitch,
557
- shotsFired: this.state.shotsFired
690
+ shotsFired: this.state.shotsFired,
691
+ position: isBufferDebug ? { ...this.me.position } : {}
558
692
  };
559
693
  this.state.shotsFired = 0;
560
- if (this.lastUpdateTick >= 2) {
561
- this.emit("tick");
694
+ if (this.intents.includes(Intents.SIMULATION)) {
695
+ let dx = 0;
696
+ let dy = 0;
697
+ let dz = 0;
698
+ const state = this.state.buffer[currentIdx];
699
+ if (state.controlKeys & Movement.Left) {
700
+ dx -= Math.cos(state.yaw);
701
+ dz += Math.sin(state.yaw);
702
+ }
703
+ if (state.controlKeys & Movement.Right) {
704
+ dx += Math.cos(state.yaw);
705
+ dz -= Math.sin(state.yaw);
706
+ }
707
+ if (state.controlKeys & Movement.Forward) {
708
+ if (this.me.climbing)
709
+ dy += 1;
710
+ else {
711
+ dx -= Math.sin(state.yaw);
712
+ dz -= Math.cos(state.yaw);
713
+ }
714
+ }
715
+ if (state.controlKeys & Movement.Backward) {
716
+ if (this.me.climbing)
717
+ dy -= 1;
718
+ else {
719
+ dx += Math.sin(state.yaw);
720
+ dz += Math.cos(state.yaw);
721
+ }
722
+ }
723
+ if (state.controlKeys & Movement.Jump)
724
+ this.state.controlKeys ^= this.state.controlKeys & Movement.Jump;
725
+ if (this.me.climbing) {
726
+ this.me.jumping = false;
727
+ dy += dy * 0.028;
728
+ this.me.position.y += dy;
729
+ dy *= 0.5;
730
+ this.#processMovement(0, dy, 0);
731
+ } else {
732
+ const mag = Math.sqrt(dx * dx + dy * dy + dz * dz);
733
+ if (mag > 0) {
734
+ const normDx = dx / mag;
735
+ const normDz = dz / mag;
736
+ dx = this.state.dx + normDx * 0.025;
737
+ dz = this.state.dz + normDz * 0.025;
738
+ } else {
739
+ dx = this.state.dx;
740
+ dz = this.state.dz;
741
+ }
742
+ dy = this.state.dy - this.game.options.gravity * 0.012;
743
+ dy = Math.max(-0.29, dy);
744
+ const oldX = this.me.position.x;
745
+ const oldY = this.me.position.y;
746
+ const oldZ = this.me.position.z;
747
+ this.#processMovement(dx, dy, dz);
748
+ dx = this.me.position.x - oldX;
749
+ dy = this.me.position.y - oldY;
750
+ dz = this.me.position.z - oldZ;
751
+ if (this.state.onGround && dy > 0) {
752
+ const normY = mag === 0 ? 0 : dy / mag;
753
+ const n = 1 - normY * 0.5;
754
+ dx *= n;
755
+ dz *= n;
756
+ }
757
+ }
758
+ dx *= 0.64;
759
+ dz *= 0.64;
760
+ if (this.me.climbing)
761
+ dy *= 0.64;
762
+ this.state.dx = dx;
763
+ this.state.dy = dy;
764
+ this.state.dz = dz;
765
+ }
766
+ if (this.state.stateIdx % FramesBetweenSyncs === 0) {
767
+ this.$emit("tick");
562
768
  const out = new CommOut;
563
769
  out.packInt8(CommCode.syncMe);
564
770
  out.packInt8(this.state.stateIdx);
565
771
  out.packInt8(this.state.serverStateIdx);
566
772
  const startIdx = mod(this.state.stateIdx - FramesBetweenSyncs + 1, StateBufferSize);
773
+ if (isBufferDebug)
774
+ console.log("--- START THIS SYNC LOGGING ---");
567
775
  for (let i = 0;i < FramesBetweenSyncs; i++) {
568
776
  const idx = mod(startIdx + i, StateBufferSize);
569
777
  const frame = this.state.buffer[idx] || {};
@@ -571,28 +779,30 @@ export class Bot {
571
779
  const shots = frame.shotsFired || 0;
572
780
  const yaw = frame.yaw ?? this.state.yaw;
573
781
  const pitch = frame.pitch ?? this.state.pitch;
574
- if (this.intents.includes(this.Intents.DEBUG_BUFFER))
575
- console.log("going with", this.state.stateIdx, startIdx, idx, frame);
782
+ if (isBufferDebug)
783
+ console.log("adding", this.state.stateIdx, startIdx, idx, frame);
576
784
  out.packInt8(keys);
577
785
  out.packInt8(shots);
578
786
  out.packString(coords(yaw, pitch));
579
787
  out.packInt8(100);
580
788
  }
581
789
  out.send(this.game.socket);
790
+ if (isBufferDebug)
791
+ console.log("--- END SYNC LOGGING ---");
582
792
  this.state.buffer = [];
583
- this.lastUpdateTick = 0;
584
- } else
585
- this.lastUpdateTick++;
793
+ }
586
794
  this.state.stateIdx = mod(this.state.stateIdx + 1, StateBufferSize);
587
795
  }
588
- if (!this.intents.includes(this.Intents.PLAYER_HEALTH))
796
+ if (!this.intents.includes(Intents.PLAYER_HEALTH))
589
797
  return;
590
798
  const regen = 0.1 * (this.game.isPrivate ? this.game.options.healthRegen : 1);
591
- for (const player of Object.values(this.players)) {
799
+ const players = Object.values(this.players);
800
+ for (let i = 0;i < players.length; i++) {
801
+ const player = players[i];
592
802
  if (player.playing && player.hp > 0) {
593
- const overHeal = player.streakRewards.includes(ShellStreak.OverHeal);
594
- player.hp += overHeal ? -regen : regen;
595
- player.hp = overHeal ? Math.max(100, player.hp) : Math.min(100, player.hp);
803
+ const hasOverHeal = player.streakRewards.includes(ShellStreak.OverHeal);
804
+ player.hp += hasOverHeal ? -regen : regen;
805
+ player.hp = hasOverHeal ? Math.max(100, player.hp) : Math.min(100, player.hp);
596
806
  }
597
807
  if (player.spawnShield > 0)
598
808
  player.spawnShield -= 6;
@@ -604,13 +814,6 @@ export class Bot {
604
814
  else
605
815
  this.#hooks[event] = [cb];
606
816
  }
607
- once(event, cb) {
608
- const onceCb = (...args) => {
609
- cb(...args);
610
- this.off(event, onceCb);
611
- };
612
- this.on(event, onceCb);
613
- }
614
817
  onAny(cb) {
615
818
  this.#globalHooks.push(cb);
616
819
  }
@@ -620,7 +823,7 @@ export class Bot {
620
823
  else
621
824
  this.#hooks[event] = [];
622
825
  }
623
- emit(event, ...args) {
826
+ $emit(event, ...args) {
624
827
  if (this.hasQuit)
625
828
  return;
626
829
  if (this.#hooks[event])
@@ -629,589 +832,12 @@ export class Bot {
629
832
  for (const cb of this.#globalHooks)
630
833
  cb(event, ...args);
631
834
  }
632
- #processChatPacket() {
633
- const id = CommIn.unPackInt8U();
634
- const msgFlags = CommIn.unPackInt8U();
635
- const text = CommIn.unPackString().valueOf();
636
- const player = this.players[id];
637
- this.emit("chat", player, text, msgFlags);
638
- }
639
- #processAddPlayerPacket() {
640
- const id = CommIn.unPackInt8U();
641
- const findCosmetics = this.intents.includes(this.Intents.COSMETIC_DATA);
642
- const playerData = {
643
- id,
644
- uniqueId: CommIn.unPackString(),
645
- name: CommIn.unPackString(),
646
- safeName: CommIn.unPackString(),
647
- charClass: CommIn.unPackInt8U(),
648
- team: CommIn.unPackInt8U(),
649
- primaryWeaponItem: findCosmetics ? findItemById(CommIn.unPackInt16U()) : CommIn.unPackInt16U(),
650
- secondaryWeaponItem: findCosmetics ? findItemById(CommIn.unPackInt16U()) : CommIn.unPackInt16U(),
651
- shellColor: CommIn.unPackInt8U(),
652
- hatItem: findCosmetics ? findItemById(CommIn.unPackInt16U()) : CommIn.unPackInt16U(),
653
- stampItem: findCosmetics ? findItemById(CommIn.unPackInt16U()) : CommIn.unPackInt16U(),
654
- stampPosX: CommIn.unPackInt8(),
655
- stampPosY: CommIn.unPackInt8(),
656
- grenadeItem: findCosmetics ? findItemById(CommIn.unPackInt16U()) : CommIn.unPackInt16U(),
657
- meleeItem: findCosmetics ? findItemById(CommIn.unPackInt16U()) : CommIn.unPackInt16U(),
658
- x: CommIn.unPackFloat(),
659
- y: CommIn.unPackFloat(),
660
- z: CommIn.unPackFloat(),
661
- $dx: CommIn.unPackFloat(),
662
- $dy: CommIn.unPackFloat(),
663
- $dz: CommIn.unPackFloat(),
664
- yaw: CommIn.unPackRadU(),
665
- pitch: CommIn.unPackRad(),
666
- score: CommIn.unPackInt32U(),
667
- $kills: CommIn.unPackInt16U(),
668
- $deaths: CommIn.unPackInt16U(),
669
- $streak: CommIn.unPackInt16U(),
670
- totalKills: CommIn.unPackInt32U(),
671
- totalDeaths: CommIn.unPackInt32U(),
672
- bestStreak: CommIn.unPackInt16U(),
673
- $bestOverallStreak: CommIn.unPackInt16U(),
674
- shield: CommIn.unPackInt8U(),
675
- hp: CommIn.unPackInt8U(),
676
- playing: CommIn.unPackInt8U(),
677
- weaponIdx: CommIn.unPackInt8U(),
678
- $controlKeys: CommIn.unPackInt8U(),
679
- upgradeProductId: CommIn.unPackInt8U(),
680
- activeShellStreaks: CommIn.unPackInt8U(),
681
- social: CommIn.unPackLongString(),
682
- hideBadge: CommIn.unPackInt8U()
683
- };
684
- this.game.mapIdx = CommIn.unPackInt8U();
685
- this.game.isPrivate = CommIn.unPackInt8U() === 1;
686
- this.game.gameModeId = CommIn.unPackInt8U();
687
- const player = new GamePlayer(playerData, this.game.gameMode === GameMode.KOTC ? this.game.activeZone : null);
688
- if (!this.players[playerData.id])
689
- this.players[playerData.id] = player;
690
- this.emit("playerJoin", player);
691
- if (this.me.id === playerData.id) {
692
- this.me = player;
693
- this.emit("botJoin", this.me);
694
- }
695
- }
696
- #processRespawnPacket() {
697
- const id = CommIn.unPackInt8U();
698
- const seed = CommIn.unPackInt16U();
699
- const x = CommIn.unPackFloat();
700
- const y = CommIn.unPackFloat();
701
- const z = CommIn.unPackFloat();
702
- const rounds0 = CommIn.unPackInt8U();
703
- const store0 = CommIn.unPackInt8U();
704
- const rounds1 = CommIn.unPackInt8U();
705
- const store1 = CommIn.unPackInt8U();
706
- const grenades = CommIn.unPackInt8U();
707
- const player = this.players[id];
708
- if (player) {
709
- player.playing = true;
710
- player.randomSeed = seed;
711
- if (player.weapons[0] && player.weapons[0].ammo)
712
- player.weapons[0].ammo.rounds = rounds0;
713
- if (player.weapons[0] && player.weapons[0].ammo)
714
- player.weapons[0].ammo.store = store0;
715
- if (player.weapons[1] && player.weapons[1].ammo)
716
- player.weapons[1].ammo.rounds = rounds1;
717
- if (player.weapons[1] && player.weapons[1].ammo)
718
- player.weapons[1].ammo.store = store1;
719
- player.grenades = grenades;
720
- player.position = { x, y, z };
721
- player.spawnShield = 120;
722
- this.emit("playerRespawn", player);
723
- }
724
- }
725
- #processSyncThemPacket() {
726
- const id = CommIn.unPackInt8U();
727
- const x = CommIn.unPackFloat();
728
- const y = CommIn.unPackFloat();
729
- const z = CommIn.unPackFloat();
730
- const climbing = CommIn.unPackInt8U();
731
- const player = this.players[id];
732
- if (!player || player.id === this.me.id) {
733
- for (let i2 = 0;i2 < FramesBetweenSyncs; i2++) {
734
- CommIn.unPackInt8U();
735
- CommIn.unPackRadU();
736
- CommIn.unPackRad();
737
- CommIn.unPackInt8U();
738
- }
739
- return;
740
- }
741
- for (let i2 = 0;i2 < FramesBetweenSyncs; i2++) {
742
- const controlKeys = CommIn.unPackInt8U();
743
- if (controlKeys & Movement.Jump)
744
- player.jumping = true;
745
- else
746
- player.jumping = false;
747
- if (controlKeys & Movement.Scope)
748
- player.scoping = true;
749
- else
750
- player.scoping = false;
751
- const oldView = { ...player.view };
752
- player.view.yaw = CommIn.unPackRadU();
753
- player.view.pitch = CommIn.unPackRad();
754
- if (player.view.yaw !== oldView.yaw || player.view.pitch !== oldView.pitch)
755
- this.emit("playerRotate", player, oldView, player.view);
756
- player.scale = CommIn.unPackInt8U();
757
- }
758
- const px = player.position;
759
- const posChanged = px.x !== x || px.y !== y || px.z !== z;
760
- const climbingChanged = player.climbing !== climbing;
761
- const didChange = posChanged || climbingChanged;
762
- const oldPosition = didChange ? { ...px } : null;
763
- if (px.x !== x)
764
- px.x = x;
765
- if (px.z !== z)
766
- px.z = z;
767
- if (!player.jumping || Math.abs(px.y - y) > 0.5)
768
- px.y = y;
769
- if (climbingChanged)
770
- player.climbing = climbing;
771
- if (!didChange)
772
- return;
773
- this.emit("playerMove", player, oldPosition, px);
774
- if (this.game.gameModeId !== GameMode.KOTC)
775
- return;
776
- const zone = this.game.activeZone;
777
- const wasIn = !!player.inKotcZone;
778
- if (!zone && wasIn) {
779
- player.inKotcZone = false;
780
- this.emit("playerLeaveZone", player);
781
- return;
782
- }
783
- player.updateKotcZone(zone);
784
- const nowIn = !!player.inKotcZone;
785
- if (wasIn !== nowIn) {
786
- player.inKotcZone = nowIn;
787
- this.emit(nowIn ? "playerEnterZone" : "playerLeaveZone", player);
788
- }
789
- }
790
- #processPausePacket() {
791
- const id = CommIn.unPackInt8U();
792
- const player = this.players[id];
793
- if (player) {
794
- player.playing = false;
795
- if (player.streakRewards)
796
- player.streakRewards = [];
797
- this.emit("playerPause", player);
798
- if (player.inKotcZone) {
799
- player.inKotcZone = false;
800
- this.emit("playerLeaveZone", player);
801
- }
802
- }
803
- }
804
- #processSwapWeaponPacket() {
805
- const id = CommIn.unPackInt8U();
806
- const newWeaponId = CommIn.unPackInt8U();
807
- const player = this.players[id];
808
- if (player) {
809
- player.activeGun = newWeaponId;
810
- this.emit("playerSwapWeapon", player, newWeaponId);
811
- }
812
- }
813
- #processDeathPacket() {
814
- const killedId = CommIn.unPackInt8U();
815
- const killerId = CommIn.unPackInt8U();
816
- CommIn.unPackInt8U();
817
- CommIn.unPackInt8U();
818
- const damageCauseInt = CommIn.unPackInt8U();
819
- const killed = this.players[killedId];
820
- const killer = this.players[killerId];
821
- const oldKilled = killed ? { ...killed } : null;
822
- if (killed) {
823
- if (killed.id === this.me.id)
824
- this.lastDeathTime = Date.now();
825
- killed.playing = false;
826
- killed.streak = 0;
827
- killed.hp = 100;
828
- killed.spawnShield = 0;
829
- killed.stats.totalDeaths++;
830
- killed.inKotcZone = false;
831
- this.emit("playerLeaveZone", killed);
832
- }
833
- if (killer) {
834
- killer.streak++;
835
- killer.stats.totalKills++;
836
- if (killer.streak > killer.stats.bestStreak)
837
- killer.stats.bestStreak = killer.streak;
838
- }
839
- this.emit("playerDeath", killed, killer, oldKilled, damageCauseInt);
840
- }
841
- #processFirePacket() {
842
- const id = CommIn.unPackInt8U();
843
- const bullet = {
844
- posX: CommIn.unPackFloat(),
845
- posY: CommIn.unPackFloat(),
846
- posZ: CommIn.unPackFloat(),
847
- dirX: CommIn.unPackFloat(),
848
- dirY: CommIn.unPackFloat(),
849
- dirZ: CommIn.unPackFloat()
850
- };
851
- const player = this.players[id];
852
- if (!player)
853
- return;
854
- const playerWeapon = player.weapons[player.activeGun];
855
- if (playerWeapon && playerWeapon.ammo) {
856
- playerWeapon.ammo.rounds--;
857
- this.emit("playerFire", player, playerWeapon, bullet);
858
- }
859
- }
860
- #processSpawnItemPacket() {
861
- const id = CommIn.unPackInt16U();
862
- const type = CommIn.unPackInt8U();
863
- const x = CommIn.unPackFloat();
864
- const y = CommIn.unPackFloat();
865
- const z = CommIn.unPackFloat();
866
- this.game.collectables[type].push({ id, x, y, z });
867
- this.emit("spawnItem", type, { x, y, z }, id);
868
- }
869
- #processCollectPacket() {
870
- const playerId = CommIn.unPackInt8U();
871
- const type = CommIn.unPackInt8U();
872
- const applyToWeaponIdx = CommIn.unPackInt8U();
873
- const id = CommIn.unPackInt16U();
874
- const player = this.players[playerId];
875
- if (!player)
876
- return;
877
- this.game.collectables[type] = this.game.collectables[type].filter((c) => c.id !== id);
878
- if (type === CollectType.Ammo) {
879
- const playerWeapon = player.weapons[applyToWeaponIdx];
880
- if (playerWeapon && playerWeapon.ammo) {
881
- playerWeapon.ammo.store = Math.min(playerWeapon.ammo.storeMax, playerWeapon.ammo.store + playerWeapon.ammo.pickup);
882
- this.emit("playerCollectAmmo", player, playerWeapon, id);
883
- }
884
- }
885
- if (type === CollectType.Grenade) {
886
- player.grenades++;
887
- if (player.grenades > 3)
888
- player.grenades = 3;
889
- this.emit("playerCollectGrenade", player, id);
890
- }
891
- }
892
- #processHitThemPacket() {
893
- const id = CommIn.unPackInt8U();
894
- const hp = CommIn.unPackInt8U();
895
- const player = this.players[id];
896
- if (!player)
897
- return;
898
- const oldHealth = player.hp;
899
- player.hp = hp;
900
- this.emit("playerDamage", player, oldHealth, player.hp);
901
- }
902
- #processHitMePacket() {
903
- const hp = CommIn.unPackInt8U();
904
- CommIn.unPackFloat();
905
- CommIn.unPackFloat();
906
- const oldHealth = this.me.hp;
907
- this.me.hp = hp;
908
- this.emit("playerDamage", this.me, oldHealth, this.me.hp);
909
- }
910
- #processSyncMePacket() {
911
- const id = CommIn.unPackInt8U();
912
- const player = this.players[id];
913
- CommIn.unPackInt8U();
914
- const serverStateIdx = CommIn.unPackInt8U();
915
- const newX = CommIn.unPackFloat();
916
- const newY = CommIn.unPackFloat();
917
- const newZ = CommIn.unPackFloat();
918
- this.me.climbing = !!CommIn.unPackInt8U();
919
- CommIn.unPackInt8U();
920
- CommIn.unPackInt8U();
921
- if (!player)
922
- return;
923
- this.state.serverStateIdx = serverStateIdx;
924
- const oldX = player.position.x;
925
- const oldY = player.position.y;
926
- const oldZ = player.position.z;
927
- player.position.x = newX;
928
- player.position.y = newY;
929
- player.position.z = newZ;
930
- if (oldX !== newX || oldY !== newY || oldZ !== newZ)
931
- this.emit("playerMove", player, { x: oldX, y: oldY, z: oldZ }, { x: newX, y: newY, z: newZ });
932
- }
933
- #processEventModifierPacket() {
934
- const out = new CommOut;
935
- out.packInt8(CommCode.eventModifier);
936
- out.send(this.game.socket);
937
- }
938
- #processRemovePlayerPacket() {
939
- const id = CommIn.unPackInt8U();
940
- const removedPlayer = { ...this.players[id] };
941
- delete this.players[id];
942
- this.emit("playerLeave", removedPlayer);
943
- }
944
- #processGameStatePacket() {
945
- if (this.game.gameModeId === GameMode.Spatula) {
946
- const oldGame = { ...this.game };
947
- this.game.teamScore[1] = CommIn.unPackInt16U();
948
- this.game.teamScore[2] = CommIn.unPackInt16U();
949
- const spatulaCoords = {
950
- x: CommIn.unPackFloat(),
951
- y: CommIn.unPackFloat(),
952
- z: CommIn.unPackFloat()
953
- };
954
- const controlledBy = CommIn.unPackInt8U();
955
- const controlledByTeam = CommIn.unPackInt8U();
956
- this.game.spatula = { coords: spatulaCoords, controlledBy, controlledByTeam };
957
- this.emit("gameStateChange", oldGame, this.game);
958
- } else if (this.game.gameModeId === GameMode.KOTC) {
959
- const oldGame = { ...this.game };
960
- this.game.stage = CommIn.unPackInt8U();
961
- this.game.zoneNumber = CommIn.unPackInt8U();
962
- this.game.capturing = CommIn.unPackInt8U();
963
- this.game.captureProgress = CommIn.unPackInt16U();
964
- this.game.numCapturing = CommIn.unPackInt8U();
965
- this.game.teamScore[1] = CommIn.unPackInt8U();
966
- this.game.teamScore[2] = CommIn.unPackInt8U();
967
- this.game.capturePercent = this.game.captureProgress / 1000;
968
- this.game.activeZone = this.game.map.zones ? this.game.map.zones[this.game.zoneNumber - 1] : null;
969
- const oldPlayersOnZone = Object.values(this.players).filter((p) => p.inKotcZone && p.playing);
970
- if (this.game.activeZone)
971
- Object.values(this.players).forEach((player) => player.updateKotcZone(this.game.activeZone));
972
- if (this.game.numCapturing <= 0)
973
- Object.values(this.players).forEach((player) => {
974
- player.inKotcZone = false;
975
- this.emit("playerLeaveZone", player);
976
- });
977
- this.emit("gameStateChange", oldGame, this.game, oldPlayersOnZone);
978
- } else if (this.game.gameModeId === GameMode.Team) {
979
- this.game.teamScore[1] = CommIn.unPackInt16U();
980
- this.game.teamScore[2] = CommIn.unPackInt16U();
981
- }
982
- if (this.game.gameModeId !== GameMode.Spatula)
983
- delete this.game.spatula;
984
- if (this.game.gameModeId !== GameMode.KOTC) {
985
- delete this.game.stage;
986
- delete this.game.zoneNumber;
987
- delete this.game.capturing;
988
- delete this.game.captureProgress;
989
- delete this.game.numCapturing;
990
- delete this.game.numCapturing;
991
- delete this.game.activeZone;
992
- }
993
- if (this.game.gameModeId === GameMode.FFA)
994
- delete this.game.teamScore;
995
- }
996
- #processBeginStreakPacket() {
997
- const id = CommIn.unPackInt8U();
998
- const ksType = CommIn.unPackInt8U();
999
- const player = this.players[id];
1000
- if (!player)
1001
- return;
1002
- switch (ksType) {
1003
- case ShellStreak.HardBoiled:
1004
- if (id === this.me.id)
1005
- this.me.shieldHp = 100;
1006
- player.streakRewards.push(ShellStreak.HardBoiled);
1007
- break;
1008
- case ShellStreak.EggBreaker:
1009
- player.streakRewards.push(ShellStreak.EggBreaker);
1010
- break;
1011
- case ShellStreak.Restock: {
1012
- player.grenades = 3;
1013
- if (player.weapons[0] && player.weapons[0].ammo) {
1014
- player.weapons[0].ammo.rounds = player.weapons[0].ammo.capacity;
1015
- player.weapons[0].ammo.store = player.weapons[0].ammo.storeMax;
1016
- }
1017
- if (player.weapons[1] && player.weapons[1].ammo) {
1018
- player.weapons[1].ammo.rounds = player.weapons[1].ammo.capacity;
1019
- player.weapons[1].ammo.store = player.weapons[1].ammo.storeMax;
1020
- }
1021
- break;
1022
- }
1023
- case ShellStreak.OverHeal:
1024
- player.hp = Math.min(200, player.hp + 100);
1025
- player.streakRewards.push(ShellStreak.OverHeal);
1026
- break;
1027
- case ShellStreak.DoubleEggs:
1028
- player.streakRewards.push(ShellStreak.DoubleEggs);
1029
- break;
1030
- case ShellStreak.MiniEgg:
1031
- player.scale = 0.5;
1032
- player.streakRewards.push(ShellStreak.MiniEgg);
1033
- break;
1034
- }
1035
- this.emit("playerBeginStreak", player, ksType);
1036
- }
1037
- #processEndStreakPacket() {
1038
- const id = CommIn.unPackInt8U();
1039
- const ksType = CommIn.unPackInt8U();
1040
- const player = this.players[id];
1041
- if (!player)
1042
- return;
1043
- const streaks = [
1044
- ShellStreak.EggBreaker,
1045
- ShellStreak.OverHeal,
1046
- ShellStreak.DoubleEggs,
1047
- ShellStreak.MiniEgg
1048
- ];
1049
- if (streaks.includes(ksType) && player.streakRewards.includes(ksType))
1050
- player.streakRewards = player.streakRewards.filter((r) => r !== ksType);
1051
- if (ksType === ShellStreak.MiniEgg)
1052
- player.scale = 1;
1053
- this.emit("playerEndStreak", player, ksType);
1054
- }
1055
- #processHitShieldPacket() {
1056
- const shieldHealth = CommIn.unPackInt8U();
1057
- const playerHealth = CommIn.unPackInt8U();
1058
- const dx = CommIn.unPackFloat();
1059
- const dz = CommIn.unPackFloat();
1060
- if (!this.me)
1061
- return;
1062
- this.me.shieldHp = shieldHealth;
1063
- this.me.hp = playerHealth;
1064
- if (this.me.shieldHp <= 0) {
1065
- this.me.streakRewards = this.me.streakRewards.filter((r) => r !== ShellStreak.HardBoiled);
1066
- this.emit("selfShieldLost", this.me.hp, { dx, dz });
1067
- } else
1068
- this.emit("selfShieldHit", this.me.shieldHp, this.me.hp, { dx, dz });
1069
- }
1070
- #processGameOptionsPacket() {
1071
- const oldOptions = { ...this.game.options };
1072
- let gravity = CommIn.unPackInt8U();
1073
- let damage = CommIn.unPackInt8U();
1074
- let healthRegen = CommIn.unPackInt8U();
1075
- if (gravity < 1 || gravity > 4) {
1076
- gravity = 4;
1077
- }
1078
- if (damage < 0 || damage > 8) {
1079
- damage = 4;
1080
- }
1081
- if (healthRegen > 16) {
1082
- healthRegen = 4;
1083
- }
1084
- this.game.options.gravity = gravity / 4;
1085
- this.game.options.damage = damage / 4;
1086
- this.game.options.healthRegen = healthRegen / 4;
1087
- const rawFlags = CommIn.unPackInt8U();
1088
- Object.keys(CCGameOptionFlag).forEach((optionFlagName) => {
1089
- const value = rawFlags & CCGameOptionFlag[optionFlagName] ? 1 : 0;
1090
- this.game.options[optionFlagName] = value;
1091
- });
1092
- this.game.options.weaponsDisabled = Array.from({ length: 7 }, () => CommIn.unPackInt8U() === 1);
1093
- this.game.options.mustUseSecondary = this.game.options.weaponsDisabled.every((v) => v);
1094
- this.emit("gameOptionsChange", oldOptions, this.game.options);
1095
- }
1096
- #processGameActionPacket() {
1097
- const action = CommIn.unPackInt8U();
1098
- if (action === GameAction.Pause) {
1099
- this.emit("gameForcePause");
1100
- setTimeout(() => this.me.playing = false, 3000);
1101
- }
1102
- if (action === GameAction.Reset) {
1103
- Object.values(this.players).forEach((player) => player.streak = 0);
1104
- if (this.game.gameModeId !== GameMode.FFA)
1105
- this.game.teamScore = [0, 0, 0];
1106
- if (this.game.gameModeId === GameMode.Spatula) {
1107
- this.game.spatula.controlledBy = 0;
1108
- this.game.spatula.controlledByTeam = 0;
1109
- this.game.spatula.coords = { x: 0, y: 0, z: 0 };
1110
- }
1111
- if (this.game.gameModeId === GameMode.KOTC) {
1112
- this.game.stage = CoopState.Capturing;
1113
- this.game.zoneNumber = 0;
1114
- this.game.activeZone = null;
1115
- this.game.capturing = 0;
1116
- this.game.captureProgress = 0;
1117
- this.game.numCapturing = 0;
1118
- this.game.capturePercent = 0;
1119
- }
1120
- this.emit("gameReset");
1121
- }
1122
- }
1123
- #processPingPacket() {
1124
- if (!this.intents.includes(this.Intents.PING))
1125
- return;
1126
- const oldPing = this.ping;
1127
- this.ping = Date.now() - this.lastPingTime;
1128
- this.emit("pingUpdate", oldPing, this.ping);
1129
- setTimeout(() => {
1130
- const out = new CommOut;
1131
- out.packInt8(CommCode.ping);
1132
- out.send(this.game.socket);
1133
- this.lastPingTime = Date.now();
1134
- }, 1000);
1135
- }
1136
- #processSwitchTeamPacket() {
1137
- const id = CommIn.unPackInt8U();
1138
- const toTeam = CommIn.unPackInt8U();
1139
- const player = this.players[id];
1140
- if (!player)
1141
- return;
1142
- const oldTeam = player.team;
1143
- player.team = toTeam;
1144
- player.streak = 0;
1145
- this.emit("playerSwitchTeam", player, oldTeam, toTeam);
1146
- }
1147
- #processChangeCharacterPacket() {
1148
- const id = CommIn.unPackInt8U();
1149
- const weaponIndex = CommIn.unPackInt8U();
1150
- const primaryWeaponIdx = CommIn.unPackInt16U();
1151
- const secondaryWeaponIdx = CommIn.unPackInt16U();
1152
- const shellColor = CommIn.unPackInt8U();
1153
- const hatIdx = CommIn.unPackInt16U();
1154
- const stampIdx = CommIn.unPackInt16U();
1155
- const grenadeIdx = CommIn.unPackInt16U();
1156
- const meleeIdx = CommIn.unPackInt16U();
1157
- const stampPositionX = CommIn.unPackInt8();
1158
- const stampPositionY = CommIn.unPackInt8();
1159
- const findCosmetics = this.intents.includes(this.Intents.COSMETIC_DATA);
1160
- const primaryWeaponItem = findCosmetics ? findItemById(primaryWeaponIdx) : primaryWeaponIdx;
1161
- const secondaryWeaponItem = findCosmetics ? findItemById(secondaryWeaponIdx) : secondaryWeaponIdx;
1162
- const hatItem = findCosmetics ? findItemById(hatIdx) : hatIdx;
1163
- const stampItem = findCosmetics ? findItemById(stampIdx) : stampIdx;
1164
- const grenadeItem = findCosmetics ? findItemById(grenadeIdx) : grenadeIdx;
1165
- const meleeItem = findCosmetics ? findItemById(meleeIdx) : meleeIdx;
1166
- const player = this.players[id];
1167
- if (player) {
1168
- const oldCharacter = { ...player.character };
1169
- const oldWeaponIdx = player.selectedGun;
1170
- player.character.eggColor = shellColor;
1171
- player.character.primaryGun = primaryWeaponItem;
1172
- player.character.secondaryGun = secondaryWeaponItem;
1173
- player.character.stamp = stampItem;
1174
- player.character.hat = hatItem;
1175
- player.character.grenade = grenadeItem;
1176
- player.character.melee = meleeItem;
1177
- player.character.stampPos.x = stampPositionX;
1178
- player.character.stampPos.y = stampPositionY;
1179
- player.selectedGun = weaponIndex;
1180
- player.weapons[0] = new GunList[weaponIndex];
1181
- if (oldWeaponIdx !== player.selectedGun)
1182
- this.emit("playerChangeGun", player, oldWeaponIdx, player.selectedGun);
1183
- if (oldCharacter !== player.character)
1184
- this.emit("playerChangeCharacter", player, oldCharacter, player.character);
1185
- }
1186
- }
1187
- #processUpdateBalancePacket() {
1188
- const newBalance = CommIn.unPackInt32U();
1189
- const oldBalance = this.account.eggBalance;
1190
- this.account.eggBalance = newBalance;
1191
- this.emit("balanceUpdate", oldBalance, newBalance);
1192
- }
1193
- #processRespawnDeniedPacket() {
1194
- this.me.playing = false;
1195
- this.emit("respawnDenied");
1196
- }
1197
- #processMeleePacket() {
1198
- const id = CommIn.unPackInt8U();
1199
- const player = this.players[id];
1200
- if (player)
1201
- this.emit("playerMelee", player);
1202
- }
1203
- #processReloadPacket() {
1204
- const id = CommIn.unPackInt8U();
1205
- const player = this.players[id];
1206
- if (!player)
1207
- return;
1208
- const playerActiveWeapon = player.weapons[player.activeGun];
1209
- if (playerActiveWeapon.ammo) {
1210
- const newRounds = Math.min(Math.min(playerActiveWeapon.ammo.capacity, playerActiveWeapon.ammo.reload) - playerActiveWeapon.ammo.rounds, playerActiveWeapon.ammo.store);
1211
- playerActiveWeapon.ammo.rounds += newRounds;
1212
- playerActiveWeapon.ammo.store -= newRounds;
1213
- }
1214
- this.emit("playerReload", player, playerActiveWeapon);
835
+ emit(event, ...args) {
836
+ const dispatch = DispatchIndex[event];
837
+ if (dispatch)
838
+ this.dispatch(new dispatch(...args), true);
839
+ else
840
+ throw new Error(`no event found for "${event}"`);
1215
841
  }
1216
842
  updateGameOptions() {
1217
843
  const out = new CommOut;
@@ -1221,193 +847,78 @@ export class Bot {
1221
847
  out.packInt8(this.game.options.healthRegen * 4);
1222
848
  const flags = (this.game.options.locked ? 1 : 0) | (this.game.options.noTeamChange ? 2 : 0) | (this.game.options.noTeamShuffle ? 4 : 0);
1223
849
  out.packInt8(flags);
1224
- this.game.options.weaponsDisabled.forEach((v) => {
1225
- out.packInt8(v ? 1 : 0);
1226
- });
850
+ this.game.options.weaponsDisabled.forEach((v) => out.packInt8(v ? 1 : 0));
1227
851
  out.send(this.game.socket);
1228
852
  }
1229
- #processGameRequestOptionsPacket() {
1230
- this.game.isPrivate = true;
1231
- this.updateGameOptions();
1232
- }
1233
- #processExplodePacket() {
1234
- const itemType = CommIn.unPackInt8U();
1235
- let item = CommIn.unPackInt16U();
1236
- const x = CommIn.unPackFloat();
1237
- const y = CommIn.unPackFloat();
1238
- const z = CommIn.unPackFloat();
1239
- const damage = CommIn.unPackInt8U();
1240
- const radius = CommIn.unPackFloat();
1241
- if (this.intents.includes(this.Intents.COSMETIC_DATA))
1242
- item = findItemById(item);
1243
- if (itemType === ItemType.Grenade)
1244
- this.emit("grenadeExplode", item, { x, y, z }, damage, radius);
1245
- else
1246
- this.emit("rocketHit", { x, y, z }, damage, radius);
1247
- }
1248
- #processThrowGrenadePacket() {
1249
- const id = CommIn.unPackInt8U();
1250
- const x = CommIn.unPackFloat();
1251
- const y = CommIn.unPackFloat();
1252
- const z = CommIn.unPackFloat();
1253
- const dx = CommIn.unPackFloat();
1254
- const dy = CommIn.unPackFloat();
1255
- const dz = CommIn.unPackFloat();
1256
- const player = this.players[id];
1257
- if (player) {
1258
- player.grenades--;
1259
- this.emit("playerThrowGrenade", player, { x, y, z }, { x: dx, y: dy, z: dz });
1260
- }
1261
- }
1262
- #processChallengeCompletePacket() {
1263
- const id = CommIn.unPackInt8U();
1264
- const challengeId = CommIn.unPackInt8U();
1265
- const player = this.players[id];
1266
- if (!player)
1267
- return;
1268
- if (!this.intents.includes(this.Intents.CHALLENGES))
1269
- return this.emit("challengeComplete", player, challengeId);
1270
- const challenge = this.account.challenges.find((c) => c.id === challengeId);
1271
- this.emit("challengeComplete", player, challenge);
1272
- if (player.id === this.me.id)
1273
- this.refreshChallenges();
1274
- }
1275
- #processSocketReadyPacket() {
1276
- const out = new CommOut;
1277
- out.packInt8(this.intents.includes(this.Intents.OBSERVE_GAME) ? CommCode.observeGame : CommCode.joinGame);
1278
- out.packString(this.game.raw.uuid);
1279
- out.packInt8(+this.intents.includes(this.Intents.VIP_HIDE_BADGE));
1280
- out.packInt8(this.state.weaponIdx || this.account?.loadout?.classIdx || 0);
1281
- out.packString(this.state.name);
1282
- out.packInt32(this.account.session);
1283
- out.packString(this.account.sessionId);
1284
- out.packString(this.account.firebaseId);
1285
- out.send(this.game.socket);
1286
- }
1287
- async#processGameJoinedPacket() {
1288
- this.me.id = CommIn.unPackInt8U();
1289
- this.me.team = CommIn.unPackInt8U();
1290
- this.game.gameModeId = CommIn.unPackInt8U();
1291
- this.game.gameMode = GameModeById[this.game.gameModeId];
1292
- this.game.mapIdx = CommIn.unPackInt8U();
1293
- this.game.map = Maps[this.game.mapIdx];
1294
- this.game.playerLimit = CommIn.unPackInt8U();
1295
- this.game.isGameOwner = CommIn.unPackInt8U() === 1;
1296
- this.game.isPrivate = CommIn.unPackInt8U() === 1 || this.game.isGameOwner;
1297
- CommIn.unPackInt8U();
1298
- if (this.intents.includes(this.Intents.LOAD_MAP) || this.intents.includes(this.Intents.PATHFINDING)) {
1299
- this.game.map.raw = await fetchMap(this.game.map.filename, this.game.map.hash);
1300
- this.emit("mapLoad", this.game.map.raw);
1301
- if (this.game.gameModeId === GameMode.KOTC) {
1302
- const meshData = this.game.map.raw.data["DYNAMIC.capture-zone.none"];
1303
- if (meshData) {
1304
- this.game.map.zones = initKotcZones(meshData);
1305
- if (!this.game.activeZone)
1306
- this.game.activeZone = this.game.map.zones[this.game.zoneNumber - 1];
1307
- } else
1308
- delete this.game.map.zones;
1309
- }
1310
- if (this.intents.includes(this.Intents.PATHFINDING))
1311
- this.pathing.nodeList = new NodeList(this.game.map.raw);
1312
- }
1313
- this.state.inGame = true;
1314
- this.lastDeathTime = Date.now();
1315
- const out = new CommOut;
1316
- out.packInt8(CommCode.clientReady);
1317
- out.send(this.game.socket);
1318
- this.updateIntervalId = setInterval(() => this.update(), 100 / 3);
1319
- if (this.intents.includes(this.Intents.PING)) {
1320
- this.lastPingTime = Date.now();
1321
- const out2 = new CommOut;
1322
- out2.packInt8(CommCode.ping);
1323
- out2.send(this.game.socket);
1324
- }
1325
- if (this.intents.includes(this.Intents.NO_AFK_KICK))
1326
- this.afkKickInterval = setInterval(() => {
1327
- if (this.state.inGame && !this.me.playing && Date.now() - this.lastDeathTime >= 15000) {
1328
- const out3 = new CommOut;
1329
- out3.packInt8(CommCode.keepAlive);
1330
- out3.send(this.game.socket);
1331
- }
1332
- }, 15000);
1333
- this.emit("gameReady");
1334
- }
1335
- #processPlayerInfoPacket() {
1336
- const playerId = CommIn.unPackInt8U();
1337
- const playerDBId = CommIn.unPackString(128);
1338
- const playerIp = CommIn.unPackString(32);
1339
- const player = this.players[playerId];
1340
- if (!player)
1341
- return;
1342
- player.admin = {
1343
- ip: playerIp,
1344
- dbId: playerDBId
1345
- };
1346
- this.emit("playerInfo", player, playerIp, playerDBId);
1347
- }
1348
853
  packetHandlers = {
1349
- [CommCode.syncThem]: () => this.#processSyncThemPacket(),
1350
- [CommCode.fire]: () => this.#processFirePacket(),
1351
- [CommCode.hitThem]: () => this.#processHitThemPacket(),
1352
- [CommCode.syncMe]: () => this.#processSyncMePacket(),
1353
- [CommCode.hitMe]: () => this.#processHitMePacket(),
1354
- [CommCode.swapWeapon]: () => this.#processSwapWeaponPacket(),
1355
- [CommCode.collectItem]: () => this.#processCollectPacket(),
1356
- [CommCode.respawn]: () => this.#processRespawnPacket(),
1357
- [CommCode.die]: () => this.#processDeathPacket(),
1358
- [CommCode.pause]: () => this.#processPausePacket(),
1359
- [CommCode.chat]: () => this.#processChatPacket(),
1360
- [CommCode.addPlayer]: () => this.#processAddPlayerPacket(),
1361
- [CommCode.removePlayer]: () => this.#processRemovePlayerPacket(),
1362
- [CommCode.eventModifier]: () => this.#processEventModifierPacket(),
1363
- [CommCode.metaGameState]: () => this.#processGameStatePacket(),
1364
- [CommCode.beginShellStreak]: () => this.#processBeginStreakPacket(),
1365
- [CommCode.endShellStreak]: () => this.#processEndStreakPacket(),
1366
- [CommCode.hitMeHardBoiled]: () => this.#processHitShieldPacket(),
1367
- [CommCode.gameOptions]: () => this.#processGameOptionsPacket(),
1368
- [CommCode.ping]: () => this.#processPingPacket(),
1369
- [CommCode.switchTeam]: () => this.#processSwitchTeamPacket(),
1370
- [CommCode.changeCharacter]: () => this.#processChangeCharacterPacket(),
1371
- [CommCode.reload]: () => this.#processReloadPacket(),
1372
- [CommCode.explode]: () => this.#processExplodePacket(),
1373
- [CommCode.throwGrenade]: () => this.#processThrowGrenadePacket(),
1374
- [CommCode.spawnItem]: () => this.#processSpawnItemPacket(),
1375
- [CommCode.melee]: () => this.#processMeleePacket(),
1376
- [CommCode.updateBalance]: () => this.#processUpdateBalancePacket(),
1377
- [CommCode.challengeCompleted]: () => this.#processChallengeCompletePacket(),
1378
- [CommCode.socketReady]: () => this.#processSocketReadyPacket(),
1379
- [CommCode.gameJoined]: () => this.#processGameJoinedPacket(),
1380
- [CommCode.gameAction]: () => this.#processGameActionPacket(),
1381
- [CommCode.requestGameOptions]: () => this.#processGameRequestOptionsPacket(),
1382
- [CommCode.respawnDenied]: () => this.#processRespawnDeniedPacket(),
1383
- [CommCode.playerInfo]: () => this.#processPlayerInfoPacket(),
854
+ [CommCode.syncThem]: processSyncThemPacket.bind(null, this),
855
+ [CommCode.fire]: processFirePacket.bind(null, this),
856
+ [CommCode.hitThem]: processHitThemPacket.bind(null, this),
857
+ [CommCode.syncMe]: processSyncMePacket.bind(null, this),
858
+ [CommCode.hitMe]: processHitMePacket.bind(null, this),
859
+ [CommCode.swapWeapon]: processSwapWeaponPacket.bind(null, this),
860
+ [CommCode.collectItem]: processCollectItemPacket.bind(null, this),
861
+ [CommCode.respawn]: processRespawnPacket.bind(null, this),
862
+ [CommCode.die]: processDiePacket.bind(null, this),
863
+ [CommCode.pause]: processPausePacket.bind(null, this),
864
+ [CommCode.chat]: processChatPacket.bind(null, this),
865
+ [CommCode.addPlayer]: processAddPlayerPacket.bind(null, this),
866
+ [CommCode.removePlayer]: processRemovePlayerPacket.bind(null, this),
867
+ [CommCode.eventModifier]: processEventModifierPacket.bind(null, this),
868
+ [CommCode.metaGameState]: processMetaGameStatePacket.bind(null, this),
869
+ [CommCode.beginShellStreak]: processBeginShellStreakPacket.bind(null, this),
870
+ [CommCode.endShellStreak]: processEndShellStreakPacket.bind(null, this),
871
+ [CommCode.hitMeHardBoiled]: processHitMeHardBoiledPacket.bind(null, this),
872
+ [CommCode.gameOptions]: processGameOptionsPacket.bind(null, this),
873
+ [CommCode.ping]: processPingPacket.bind(null, this),
874
+ [CommCode.switchTeam]: processSwitchTeamPacket.bind(null, this),
875
+ [CommCode.changeCharacter]: processChangeCharacterPacket.bind(null, this),
876
+ [CommCode.reload]: processReloadPacket.bind(null, this),
877
+ [CommCode.explode]: processExplodePacket.bind(null, this),
878
+ [CommCode.throwGrenade]: processThrowGrenadePacket.bind(null, this),
879
+ [CommCode.spawnItem]: processSpawnItemPacket.bind(null, this),
880
+ [CommCode.melee]: processMeleePacket.bind(null, this),
881
+ [CommCode.updateBalance]: processUpdateBalancePacket.bind(null, this),
882
+ [CommCode.challengeCompleted]: processChallengeCompletedPacket.bind(null, this),
883
+ [CommCode.gameAction]: processGameActionPacket.bind(null, this),
884
+ [CommCode.socketReady]: processSocketReadyPacket.bind(null, this),
885
+ [CommCode.gameJoined]: processGameJoinedPacket.bind(null, this),
886
+ [CommCode.playerInfo]: processPlayerInfoPacket.bind(null, this),
887
+ [CommCode.respawnDenied]: () => {
888
+ this.me.playing = false;
889
+ this.$emit("respawnDenied");
890
+ },
891
+ [CommCode.requestGameOptions]: () => {
892
+ this.game.isPrivate = true;
893
+ this.updateGameOptions();
894
+ },
1384
895
  [CommCode.expireUpgrade]: () => {},
1385
896
  [CommCode.clientReady]: () => {},
1386
897
  [CommCode.musicInfo]: () => CommIn.unPackLongString()
1387
898
  };
1388
899
  processPacket(packet) {
900
+ if (this.hasQuit)
901
+ return;
1389
902
  CommIn.init(packet);
1390
- if (this.intents.includes(this.Intents.PACKET_HOOK))
1391
- this.emit("packet", packet);
903
+ if (this.intents.includes(Intents.PACKET_HOOK))
904
+ this.$emit("packet", packet);
1392
905
  let lastCommand = 0;
1393
906
  let lastCode = 0;
1394
- let abort = false;
1395
- while (CommIn.isMoreDataAvailable() && !abort) {
907
+ while (CommIn.isMoreDataAvailable()) {
1396
908
  const cmd = CommIn.unPackInt8U();
1397
909
  const handler = this.packetHandlers[cmd];
1398
910
  if (handler)
1399
911
  handler();
1400
912
  else {
1401
- console.error(`processPacket: I got but couldn't identify a: ${Object.keys(CommCode).find((k) => CommCode[k] === cmd)} ${cmd}`);
913
+ console.error(`processPacket: got but couldn't identify: ${Object.keys(CommCode).find((k) => CommCode[k] === cmd)} ${cmd}`);
1402
914
  if (lastCommand)
1403
- console.error(`processPacket: It may be a result of the ${lastCommand} command (${lastCode}).`);
1404
- abort = true;
915
+ console.error(`processPacket: it may be a result of the ${lastCommand} command (${lastCode}).`);
1405
916
  break;
1406
917
  }
1407
918
  lastCommand = Object.keys(CommCode).find((k) => CommCode[k] === cmd);
1408
919
  lastCode = cmd;
1409
- if (this.intents.includes(this.Intents.LOG_PACKETS))
1410
- console.log(`[LOG_PACKETS] Packet ${lastCommand}: ${lastCode}`);
920
+ if (this.intents.includes(Intents.LOG_PACKETS))
921
+ console.log(`[LOG_PACKETS] packet ${lastCommand}: ${lastCode}`);
1411
922
  }
1412
923
  }
1413
924
  async checkChiknWinner() {
@@ -1416,19 +927,19 @@ export class Bot {
1416
927
  id: this.account.id,
1417
928
  sessionId: this.account.sessionId
1418
929
  });
1419
- if (typeof response === "string")
930
+ if (!response.ok)
1420
931
  return response;
1421
932
  this.account.cw.limit = response.limit;
1422
933
  this.account.cw.atLimit = response.limit >= 4;
1423
934
  this.account.cw.secondsUntilPlay = (this.account.cw.atLimit ? response.period : response.span) || 0;
1424
935
  this.account.cw.canPlayAgain = Date.now() + this.account.cw.secondsUntilPlay * 1000;
1425
- return this.account.cw;
936
+ return { ok: true, cw: this.account.cw };
1426
937
  }
1427
938
  async playChiknWinner(doPrematureCooldownCheck = true) {
1428
939
  if (this.account.cw.atLimit || this.account.cw.limit > ChiknWinnerDailyLimit)
1429
- return "hit_daily_limit";
940
+ return createError(ChicknWinnerError.HitDailyLimit);
1430
941
  if (this.account.cw.canPlayAgain > Date.now() && doPrematureCooldownCheck)
1431
- return "on_cooldown";
942
+ return createError(ChicknWinnerError.OnCooldown);
1432
943
  const response = await this.api.queryServices({
1433
944
  cmd: "incentivizedVideoReward",
1434
945
  firebaseId: this.account.firebaseId,
@@ -1436,62 +947,73 @@ export class Bot {
1436
947
  sessionId: this.account.sessionId,
1437
948
  token: null
1438
949
  });
1439
- if (typeof response === "string")
950
+ if (!response.ok)
1440
951
  return response;
1441
952
  if (response.error) {
1442
953
  if (response.error === "RATELIMITED" || response.error === "RATELMITED") {
1443
954
  await this.checkChiknWinner();
1444
- return "on_cooldown";
955
+ return createError(ChicknWinnerError.OnCooldown);
1445
956
  } else if (response.error === "SESSION_EXPIRED") {
1446
- this.emit("sessionExpired");
1447
- return "session_expired";
957
+ this.$emit("sessionExpired");
958
+ return createError(ChicknWinnerError.SessionExpired);
1448
959
  }
1449
- console.error("Unknown Chikn Winner response, report this on Github:", response);
1450
- return "unknown_error";
960
+ this.errorLogger("unknown Chikn Winner response, report this on Github:", response);
961
+ return createError(ChicknWinnerError.InternalError);
1451
962
  }
1452
963
  if (response.reward) {
1453
964
  this.account.eggBalance += response.reward.eggsGiven;
1454
965
  response.reward.itemIds.forEach((id) => this.account.ownedItemIds.push(id));
1455
966
  await this.checkChiknWinner();
1456
- return response.reward;
967
+ return { ok: true, ...response.reward };
1457
968
  }
1458
- console.error("Unknown Chikn Winner response, report this on Github:", response);
1459
- return "unknown_error";
969
+ this.errorLogger("unknown Chikn Winner response, report this on Github:", response);
970
+ return createError(ChicknWinnerError.InternalError);
1460
971
  }
1461
972
  async resetChiknWinner() {
1462
973
  if (this.account.eggBalance < 200)
1463
- return "not_enough_eggs";
974
+ return createError(ChicknWinnerError.NotEnoughResetEggs);
1464
975
  if (!this.account.cw.atLimit)
1465
- return "not_at_limit";
976
+ return createError(ChicknWinnerError.NotAtDailyLimit);
1466
977
  const response = await this.api.queryServices({
1467
978
  cmd: "chwReset",
1468
979
  sessionId: this.account.sessionId
1469
980
  });
1470
- if (typeof response === "string")
981
+ if (!response.ok)
1471
982
  return response;
1472
983
  if (response.result !== "SUCCESS") {
1473
- console.error("Unknown Chikn Winner reset response, report this on Github:", response);
1474
- return "unknown_error";
984
+ this.errorLogger("unknown Chikn Winner reset response, report this on Github:", response);
985
+ return createError(ChicknWinnerError.InternalError);
1475
986
  }
1476
987
  this.account.eggBalance -= 200;
1477
988
  await this.checkChiknWinner();
1478
- return this.account.cw;
989
+ return { ok: true, cw: this.account.cw };
1479
990
  }
1480
991
  canSee(target) {
1481
- if (!this.intents.includes(this.Intents.PATHFINDING))
1482
- return this.processError("You must have the PATHFINDING intent to use this method.");
992
+ if (!this.intents.includes(Intents.PATHFINDING))
993
+ throw new Error("canSee: you need enable the PATHFINDING intent");
1483
994
  return this.pathing.nodeList.hasLineOfSight(this.me.position, target.position);
1484
995
  }
996
+ getBestTarget() {
997
+ const options = Object.values(this.players).filter((player) => player?.playing).filter((player) => player.hp > 0).filter((player) => player.id !== this.me.id).filter((player) => this.me.team === 0 || player.team !== this.me.team);
998
+ const distancePlayers = options.map((player) => ({
999
+ player,
1000
+ distance: (player.position.x - this.me.position.x) ** 2 + 5 * (player.position.y - this.me.position.y) ** 2 + (player.position.z - this.me.position.z) ** 2
1001
+ })).sort((a, b) => a.distance - b.distance);
1002
+ if (distancePlayers.length)
1003
+ return distancePlayers[0].player;
1004
+ else
1005
+ return null;
1006
+ }
1485
1007
  async refreshChallenges() {
1486
1008
  const result = await this.api.queryServices({
1487
1009
  cmd: "challengeGetDaily",
1488
1010
  sessionId: this.account.sessionId,
1489
1011
  playerId: this.account.id
1490
1012
  });
1491
- if (typeof result === "string")
1013
+ if (!result.ok)
1492
1014
  return result;
1493
1015
  this.#importChallenges(result);
1494
- return this.account.challenges;
1016
+ return { ok: true, challenges: this.account.challenges };
1495
1017
  }
1496
1018
  async rerollChallenge(challengeId) {
1497
1019
  const result = await this.api.queryServices({
@@ -1499,10 +1021,17 @@ export class Bot {
1499
1021
  sessionId: this.account.sessionId,
1500
1022
  slotId: challengeId
1501
1023
  });
1502
- if (typeof result === "string")
1024
+ if (!result.ok)
1503
1025
  return result;
1504
- this.#importChallenges(result);
1505
- return this.account.challenges;
1026
+ if (result["0"]) {
1027
+ this.#importChallenges(Object.values(result));
1028
+ return { ok: true, challenges: this.account.challenges };
1029
+ }
1030
+ const isInEnum = Object.values(ChallengeRerollError).includes(result.error);
1031
+ if (isInEnum)
1032
+ return { ok: false, error: result.error };
1033
+ this.errorLogger("rerollChallenge: unknown error response", result);
1034
+ return { ok: false, error: ChallengeRerollError.InternalError };
1506
1035
  }
1507
1036
  async claimChallenge(challengeId) {
1508
1037
  const result = await this.api.queryServices({
@@ -1510,15 +1039,19 @@ export class Bot {
1510
1039
  sessionId: this.account.sessionId,
1511
1040
  slotId: challengeId
1512
1041
  });
1513
- if (typeof result === "string")
1042
+ if (!result.ok)
1514
1043
  return result;
1515
- this.#importChallenges(result.challenges);
1516
- if (result.reward > 0)
1517
- this.account.eggBalance += result.reward;
1518
- return {
1519
- eggReward: result.reward,
1520
- updatedChallenges: this.account.challenges
1521
- };
1044
+ if (result.reward) {
1045
+ this.#importChallenges(result.challenges);
1046
+ if (result.reward > 0)
1047
+ this.account.eggBalance += result.reward;
1048
+ return { ok: true, eggReward: result.reward, updatedChallenges: this.account.challenges };
1049
+ }
1050
+ const isInEnum = Object.values(ChallengeClaimError).includes(result.error);
1051
+ if (isInEnum)
1052
+ return { ok: false, error: result.error };
1053
+ this.errorLogger("claimChallenge: unknown error response", result);
1054
+ return { ok: false, error: ChallengeClaimError.InternalError };
1522
1055
  }
1523
1056
  async refreshBalance() {
1524
1057
  const result = await this.api.queryServices({
@@ -1526,10 +1059,10 @@ export class Bot {
1526
1059
  firebaseId: this.account.firebaseId,
1527
1060
  sessionId: this.account.sessionId
1528
1061
  });
1529
- if (typeof result === "string")
1062
+ if (!result.ok)
1530
1063
  return result;
1531
1064
  this.account.eggBalance = result.currentBalance;
1532
- return result.currentBalance;
1065
+ return { ok: true, currentBalance: result.currentBalance };
1533
1066
  }
1534
1067
  async redeemCode(code) {
1535
1068
  const result = await this.api.queryServices({
@@ -1539,18 +1072,18 @@ export class Bot {
1539
1072
  id: this.account.id,
1540
1073
  code
1541
1074
  });
1542
- if (typeof result === "string")
1075
+ if (!result.ok)
1543
1076
  return result;
1544
1077
  if (result.result === "SUCCESS") {
1545
- this.account.eggBalance = result.eggs_given;
1078
+ this.account.eggBalance += result.eggs_given;
1546
1079
  result.item_ids.forEach((id) => this.account.ownedItemIds.push(id));
1547
- return {
1548
- result,
1549
- eggsGiven: result.eggs_given,
1550
- itemIds: result.item_ids
1551
- };
1080
+ return { ok: true, eggsGiven: result.eggs_given, itemIds: result.item_ids };
1552
1081
  }
1553
- return result;
1082
+ const isInEnum = Object.values(RedeemCodeError).includes(result.result);
1083
+ if (isInEnum)
1084
+ return { ok: false, error: result.result };
1085
+ this.errorLogger("redeemCode: unknown error response", result);
1086
+ return { ok: false, error: RedeemCodeError.InternalError };
1554
1087
  }
1555
1088
  async claimURLReward(reward) {
1556
1089
  const result = await this.api.queryServices({
@@ -1559,13 +1092,18 @@ export class Bot {
1559
1092
  sessionId: this.account.sessionId,
1560
1093
  reward
1561
1094
  });
1562
- if (typeof result === "string")
1095
+ if (!result.ok)
1563
1096
  return result;
1564
1097
  if (result.result === "SUCCESS") {
1565
1098
  this.account.eggBalance += result.eggsGiven;
1566
1099
  result.itemIds.forEach((id) => this.account.ownedItemIds.push(id));
1100
+ return { ok: true, eggsGiven: result.eggsGiven, itemIds: result.itemIds };
1567
1101
  }
1568
- return result;
1102
+ const isInEnum = Object.values(ClaimURLError).includes(result.result);
1103
+ if (isInEnum)
1104
+ return { ok: false, error: result.result };
1105
+ this.errorLogger("claimURLReward: unknown error response", result);
1106
+ return { ok: false, error: ClaimURLError.InternalError };
1569
1107
  }
1570
1108
  async claimSocialReward(rewardTag) {
1571
1109
  const result = await this.api.queryServices({
@@ -1574,13 +1112,18 @@ export class Bot {
1574
1112
  sessionId: this.account.sessionId,
1575
1113
  rewardTag
1576
1114
  });
1577
- if (typeof result === "string")
1115
+ if (!result.ok)
1578
1116
  return result;
1579
1117
  if (result.result === "SUCCESS") {
1580
1118
  this.account.eggBalance += result.eggsGiven;
1581
1119
  result.itemIds.forEach((id) => this.account.ownedItemIds.push(id));
1120
+ return { ok: true, eggsGiven: result.eggsGiven, itemIds: result.itemIds };
1582
1121
  }
1583
- return result;
1122
+ const isInEnum = Object.values(ClaimSocialError).includes(result.result);
1123
+ if (isInEnum)
1124
+ return { ok: false, error: result.result };
1125
+ this.errorLogger("claimSocialReward: unknown error response", result);
1126
+ return { ok: false, error: ClaimSocialError.InternalError };
1584
1127
  }
1585
1128
  async buyItem(itemId) {
1586
1129
  const result = await this.api.queryServices({
@@ -1590,34 +1133,32 @@ export class Bot {
1590
1133
  itemId,
1591
1134
  save: true
1592
1135
  });
1593
- if (typeof result === "string")
1136
+ if (!result.ok)
1594
1137
  return result;
1595
1138
  if (result.result === "SUCCESS") {
1596
1139
  this.account.eggBalance = result.currentBalance;
1597
1140
  this.account.ownedItemIds.push(result.itemId);
1141
+ return { ok: true, itemId: result.itemId, currentBalance: result.currentBalance };
1598
1142
  }
1599
- return result;
1600
- }
1601
- processError(...params) {
1602
- const error = params.join(" ");
1603
- if (this.#hooks.error && this.#hooks.error.length)
1604
- this.emit("error", error);
1605
- else if (this.intents.includes(this.Intents.NO_EXIT_ON_ERROR))
1606
- console.error(error);
1607
- else
1608
- throw new Error(error);
1143
+ const isInEnum = Object.values(BuyItemError).includes(result.result);
1144
+ if (isInEnum)
1145
+ return { ok: false, error: result.result };
1146
+ this.errorLogger("buyItem: unknown error response", result);
1147
+ return { ok: false, error: BuyItemError.InternalError };
1609
1148
  }
1610
1149
  leave(code = CloseCode.mainMenu) {
1611
1150
  if (this.hasQuit)
1612
1151
  return;
1152
+ this.state.inGame = false;
1613
1153
  if (code > -1) {
1614
1154
  this.game?.socket?.close(code);
1615
- this.state.left = true;
1616
- this.emit("leave", code);
1155
+ this.$emit("leave", code);
1617
1156
  }
1618
- clearInterval(this.updateIntervalId);
1157
+ if (this.updateIntervalId)
1158
+ clearInterval(this.updateIntervalId);
1159
+ if (this.pingTimeoutId)
1160
+ clearTimeout(this.pingTimeoutId);
1619
1161
  this.#dispatches = [];
1620
- this.state.inGame = false;
1621
1162
  this.state.chatLines = 0;
1622
1163
  this.state.reloading = false;
1623
1164
  this.state.swappingGun = false;
@@ -1632,10 +1173,8 @@ export class Bot {
1632
1173
  this.ping = 0;
1633
1174
  this.lastPingTime = -1;
1634
1175
  this.lastDeathTime = -1;
1635
- this.lastUpdateTick = 0;
1636
1176
  this.pathing = {
1637
1177
  nodeList: null,
1638
- followingPath: false,
1639
1178
  activePath: null,
1640
1179
  activeNode: null,
1641
1180
  activeNodeIdx: 0
@@ -1643,37 +1182,45 @@ export class Bot {
1643
1182
  }
1644
1183
  logout() {
1645
1184
  this.account = this.#initialAccount;
1646
- if (this.intents.includes(this.Intents.RENEW_SESSION))
1185
+ if (this.intents.includes(Intents.RENEW_SESSION))
1647
1186
  clearInterval(this.renewSessionInterval);
1648
1187
  }
1649
- quit(noCleanup = false) {
1188
+ quit(cleanupLevel = CleanupLevel.Partial) {
1650
1189
  if (this.hasQuit)
1651
1190
  return;
1191
+ this.hasQuit = true;
1652
1192
  this.leave();
1653
1193
  if (this.matchmaker) {
1654
- this.matchmaker.close();
1655
- if (!noCleanup)
1656
- delete this.matchmaker;
1194
+ try {
1195
+ this.matchmaker.close();
1196
+ } catch {}
1197
+ if (cleanupLevel >= CleanupLevel.Partial)
1198
+ this.matchmaker = null;
1657
1199
  }
1658
- if (!noCleanup) {
1659
- delete this.account;
1660
- delete this.api;
1661
- delete this.game;
1662
- delete this.me;
1663
- delete this.pathing;
1664
- delete this.players;
1665
- delete this.state;
1200
+ if (this.intents.includes(Intents.NO_AFK_KICK))
1201
+ clearInterval(this.afkKickInterval);
1202
+ if (this.intents.includes(Intents.RENEW_SESSION))
1203
+ clearInterval(this.renewSessionInterval);
1204
+ if (this.pingTimeoutId)
1205
+ clearTimeout(this.pingTimeoutId);
1206
+ if (cleanupLevel >= CleanupLevel.Partial) {
1207
+ this.api = null;
1208
+ this.packetHandlers = null;
1209
+ this.pathing = null;
1666
1210
  this.#initialAccount = {};
1667
1211
  this.#initialGame = {};
1668
1212
  this.#hooks = {};
1669
1213
  this.#globalHooks = [];
1670
1214
  this.#dispatches = [];
1215
+ this.matchmakerListeners = [];
1216
+ if (cleanupLevel >= CleanupLevel.Full) {
1217
+ this.account = null;
1218
+ this.game = null;
1219
+ this.me = null;
1220
+ this.players = null;
1221
+ this.state = null;
1222
+ }
1671
1223
  }
1672
- if (this.intents.includes(this.Intents.NO_AFK_KICK))
1673
- clearInterval(this.afkKickInterval);
1674
- if (this.intents.includes(this.Intents.RENEW_SESSION))
1675
- clearInterval(this.renewSessionInterval);
1676
- this.hasQuit = true;
1677
1224
  }
1678
1225
  }
1679
1226
  export default Bot;