specprotocol 1.4.3 → 1.4.5

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 (53) hide show
  1. package/README.md +66 -174
  2. package/dist/bot.d.ts +17 -3
  3. package/dist/bot.d.ts.map +1 -1
  4. package/dist/bot.js +413 -153
  5. package/dist/bot.js.map +1 -1
  6. package/dist/chat.js +1 -1
  7. package/dist/chat.js.map +1 -1
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +7 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/inventory.d.ts.map +1 -1
  13. package/dist/inventory.js +4 -1
  14. package/dist/inventory.js.map +1 -1
  15. package/dist/plugins/built-in/chat.d.ts +0 -4
  16. package/dist/plugins/built-in/chat.d.ts.map +1 -1
  17. package/dist/plugins/built-in/chat.js +0 -40
  18. package/dist/plugins/built-in/chat.js.map +1 -1
  19. package/dist/protocol/compression.d.ts +3 -1
  20. package/dist/protocol/compression.d.ts.map +1 -1
  21. package/dist/protocol/compression.js +16 -3
  22. package/dist/protocol/compression.js.map +1 -1
  23. package/dist/protocol/connection.d.ts.map +1 -1
  24. package/dist/protocol/connection.js +27 -6
  25. package/dist/protocol/connection.js.map +1 -1
  26. package/dist/protocol/packet.d.ts +3 -0
  27. package/dist/protocol/packet.d.ts.map +1 -1
  28. package/dist/protocol/packet.js +28 -4
  29. package/dist/protocol/packet.js.map +1 -1
  30. package/dist/protocol/states/handshake.d.ts +2 -2
  31. package/dist/protocol/states/handshake.d.ts.map +1 -1
  32. package/dist/protocol/states/handshake.js +3 -2
  33. package/dist/protocol/states/handshake.js.map +1 -1
  34. package/dist/protocol/states/login.d.ts +3 -3
  35. package/dist/protocol/states/login.d.ts.map +1 -1
  36. package/dist/protocol/states/login.js +27 -9
  37. package/dist/protocol/states/login.js.map +1 -1
  38. package/dist/protocol/states/play.d.ts +20 -14
  39. package/dist/protocol/states/play.d.ts.map +1 -1
  40. package/dist/protocol/states/play.js +279 -61
  41. package/dist/protocol/states/play.js.map +1 -1
  42. package/dist/protocol/types.d.ts.map +1 -1
  43. package/dist/protocol/types.js +12 -0
  44. package/dist/protocol/types.js.map +1 -1
  45. package/dist/protocol/version.d.ts +23 -0
  46. package/dist/protocol/version.d.ts.map +1 -0
  47. package/dist/protocol/version.js +137 -0
  48. package/dist/protocol/version.js.map +1 -0
  49. package/docs/README.md +1 -1
  50. package/docs/api/protocol.md +1 -1
  51. package/docs/getting-started.md +1 -1
  52. package/docs/guides/architecture.md +1 -1
  53. package/package.json +8 -8
package/dist/bot.js CHANGED
@@ -27,11 +27,13 @@ const encryption_js_1 = require("./protocol/encryption.js");
27
27
  const handshake_js_1 = require("./protocol/states/handshake.js");
28
28
  const login_js_1 = require("./protocol/states/login.js");
29
29
  const play_js_1 = require("./protocol/states/play.js");
30
+ const version_js_1 = require("./protocol/version.js");
30
31
  // World & Entity
31
32
  const world_js_1 = require("./world/world.js");
32
33
  const entity_js_1 = require("./entity/entity.js");
33
34
  // Core modules
34
35
  const tablist_js_1 = require("./tablist.js");
36
+ const chat_js_1 = require("./chat.js");
35
37
  const inventory_js_1 = require("./inventory.js");
36
38
  const scoreboard_js_1 = require("./scoreboard.js");
37
39
  const bossbar_js_1 = require("./bossbar.js");
@@ -39,8 +41,9 @@ const weather_js_1 = require("./weather.js");
39
41
  const block_finder_js_1 = require("./block-finder.js");
40
42
  // Plugins
41
43
  const plugin_manager_js_1 = require("./plugins/plugin-manager.js");
42
- const chat_js_1 = require("./plugins/built-in/chat.js");
44
+ const chat_js_2 = require("./plugins/built-in/chat.js");
43
45
  const combat_js_1 = require("./plugins/built-in/combat.js");
46
+ const MISSING_PACKET_ID = -1;
44
47
  // ─── Bot Class ──────────────────────────────────────────
45
48
  class Bot extends events_js_1.TypedEventEmitter {
46
49
  // Connection
@@ -84,19 +87,130 @@ class Bot extends events_js_1.TypedEventEmitter {
84
87
  weather;
85
88
  // Config
86
89
  options;
90
+ protocolInfo;
91
+ loginClientIds;
92
+ loginServerIds;
93
+ configurationClientIds;
94
+ configurationServerIds;
95
+ playClientIds;
96
+ playServerIds;
87
97
  // Packet handlers for play state
88
98
  packetHandlers = new Map();
99
+ playPacketListener = null;
100
+ onConnectionError = (err) => {
101
+ this.emit('error', err);
102
+ };
103
+ onConnectionClose = () => {
104
+ this.stopPhysicsTick();
105
+ this.emit('end');
106
+ };
89
107
  constructor(options) {
90
108
  super();
109
+ this.protocolInfo = (0, version_js_1.resolveProtocolInfo)(options.version ?? handshake_js_1.PROTOCOL_VERSION);
91
110
  this.options = {
92
111
  host: options.host,
93
112
  port: options.port ?? 25565,
94
- version: options.version ?? handshake_js_1.PROTOCOL_VERSION,
113
+ version: this.protocolInfo.protocolVersion,
95
114
  viewDistance: options.viewDistance ?? 10,
96
115
  };
97
116
  this.authMode = options.auth ?? 'offline';
98
117
  this._username = options.username;
99
118
  this.logger = new logger_js_1.Logger('Bot', options.logLevel ?? logger_js_1.LogLevel.INFO);
119
+ this.logger.info(`Using protocol ${this.protocolInfo.protocolVersion} (${this.protocolInfo.minecraftVersion})`);
120
+ const missing = MISSING_PACKET_ID;
121
+ const loginToClient = this.protocolInfo.packetMappings.login.toClient;
122
+ const loginToServer = this.protocolInfo.packetMappings.login.toServer;
123
+ const configToClient = this.protocolInfo.packetMappings.configuration?.toClient ?? {};
124
+ const configToServer = this.protocolInfo.packetMappings.configuration?.toServer ?? {};
125
+ const playToClient = this.protocolInfo.packetMappings.play.toClient;
126
+ const playToServer = this.protocolInfo.packetMappings.play.toServer;
127
+ this.loginClientIds = {
128
+ disconnect: (0, version_js_1.resolvePacketId)(loginToClient, ['disconnect']) ?? missing,
129
+ encryptionBegin: (0, version_js_1.resolvePacketId)(loginToClient, ['encryption_begin']) ?? missing,
130
+ success: (0, version_js_1.resolvePacketId)(loginToClient, ['success']) ?? missing,
131
+ compress: (0, version_js_1.resolvePacketId)(loginToClient, ['compress']) ?? missing,
132
+ loginPluginRequest: (0, version_js_1.resolvePacketId)(loginToClient, ['login_plugin_request']) ?? missing,
133
+ };
134
+ this.loginServerIds = {
135
+ loginStart: (0, version_js_1.resolvePacketId)(loginToServer, ['login_start']) ?? missing,
136
+ encryptionBegin: (0, version_js_1.resolvePacketId)(loginToServer, ['encryption_begin']) ?? missing,
137
+ loginPluginResponse: (0, version_js_1.resolvePacketId)(loginToServer, ['login_plugin_response']) ?? missing,
138
+ loginAcknowledged: (0, version_js_1.resolvePacketId)(loginToServer, ['login_acknowledged']) ?? missing,
139
+ };
140
+ this.configurationClientIds = {
141
+ disconnect: (0, version_js_1.resolvePacketId)(configToClient, ['disconnect']) ?? missing,
142
+ finishConfiguration: (0, version_js_1.resolvePacketId)(configToClient, ['finish_configuration']) ?? missing,
143
+ keepAlive: (0, version_js_1.resolvePacketId)(configToClient, ['keep_alive']) ?? missing,
144
+ ping: (0, version_js_1.resolvePacketId)(configToClient, ['ping']) ?? missing,
145
+ customPayload: (0, version_js_1.resolvePacketId)(configToClient, ['custom_payload']) ?? missing,
146
+ registryData: (0, version_js_1.resolvePacketId)(configToClient, ['registry_data']) ?? missing,
147
+ featureFlags: (0, version_js_1.resolvePacketId)(configToClient, ['feature_flags']) ?? missing,
148
+ tags: (0, version_js_1.resolvePacketId)(configToClient, ['tags']) ?? missing,
149
+ selectKnownPacks: (0, version_js_1.resolvePacketId)(configToClient, ['select_known_packs']) ?? missing,
150
+ cookieRequest: (0, version_js_1.resolvePacketId)(configToClient, ['cookie_request']) ?? missing,
151
+ addResourcePack: (0, version_js_1.resolvePacketId)(configToClient, ['add_resource_pack', 'resource_pack_send']) ?? missing,
152
+ serverLinks: (0, version_js_1.resolvePacketId)(configToClient, ['server_links']) ?? missing,
153
+ };
154
+ this.configurationServerIds = {
155
+ finishConfiguration: (0, version_js_1.resolvePacketId)(configToServer, ['finish_configuration']) ?? missing,
156
+ keepAlive: (0, version_js_1.resolvePacketId)(configToServer, ['keep_alive']) ?? missing,
157
+ pong: (0, version_js_1.resolvePacketId)(configToServer, ['pong']) ?? missing,
158
+ selectKnownPacks: (0, version_js_1.resolvePacketId)(configToServer, ['select_known_packs']) ?? missing,
159
+ };
160
+ this.playClientIds = {
161
+ keepAlive: (0, version_js_1.resolvePacketId)(playToClient, ['keep_alive']) ?? missing,
162
+ chunkBatchFinished: (0, version_js_1.resolvePacketId)(playToClient, ['chunk_batch_finished']) ?? missing,
163
+ login: (0, version_js_1.resolvePacketId)(playToClient, ['login']) ?? missing,
164
+ position: (0, version_js_1.resolvePacketId)(playToClient, ['position']) ?? missing,
165
+ updateHealth: (0, version_js_1.resolvePacketId)(playToClient, ['update_health']) ?? missing,
166
+ blockChange: (0, version_js_1.resolvePacketId)(playToClient, ['block_change']) ?? missing,
167
+ bossBar: (0, version_js_1.resolvePacketId)(playToClient, ['boss_bar']) ?? missing,
168
+ spawnEntity: (0, version_js_1.resolvePacketId)(playToClient, ['spawn_entity']) ?? missing,
169
+ entityDestroy: (0, version_js_1.resolvePacketId)(playToClient, ['entity_destroy']) ?? missing,
170
+ relEntityMove: (0, version_js_1.resolvePacketId)(playToClient, ['rel_entity_move']) ?? missing,
171
+ entityMoveLook: (0, version_js_1.resolvePacketId)(playToClient, ['entity_move_look']) ?? missing,
172
+ entityVelocity: (0, version_js_1.resolvePacketId)(playToClient, ['entity_velocity']) ?? missing,
173
+ entityHeadRotation: (0, version_js_1.resolvePacketId)(playToClient, ['entity_head_rotation']) ?? missing,
174
+ gameStateChange: (0, version_js_1.resolvePacketId)(playToClient, ['game_state_change']) ?? missing,
175
+ respawn: (0, version_js_1.resolvePacketId)(playToClient, ['respawn']) ?? missing,
176
+ experience: (0, version_js_1.resolvePacketId)(playToClient, ['experience']) ?? missing,
177
+ updateTime: (0, version_js_1.resolvePacketId)(playToClient, ['update_time']) ?? missing,
178
+ spawnPosition: (0, version_js_1.resolvePacketId)(playToClient, ['spawn_position']) ?? missing,
179
+ openWindow: (0, version_js_1.resolvePacketId)(playToClient, ['open_window']) ?? missing,
180
+ windowItems: (0, version_js_1.resolvePacketId)(playToClient, ['window_items']) ?? missing,
181
+ setSlot: (0, version_js_1.resolvePacketId)(playToClient, ['set_slot']) ?? missing,
182
+ closeWindow: (0, version_js_1.resolvePacketId)(playToClient, ['close_window']) ?? missing,
183
+ ping: (0, version_js_1.resolvePacketId)(playToClient, ['ping']) ?? missing,
184
+ acknowledgePlayerDigging: (0, version_js_1.resolvePacketId)(playToClient, ['acknowledge_player_digging']) ?? missing,
185
+ disconnect: (0, version_js_1.resolvePacketId)(playToClient, ['kick_disconnect']) ?? missing,
186
+ abilities: (0, version_js_1.resolvePacketId)(playToClient, ['abilities']) ?? missing,
187
+ systemChat: (0, version_js_1.resolvePacketId)(playToClient, ['system_chat']) ?? missing,
188
+ profilelessChat: (0, version_js_1.resolvePacketId)(playToClient, ['profileless_chat']) ?? missing,
189
+ legacyChat: (0, version_js_1.resolvePacketId)(playToClient, ['chat']) ?? missing,
190
+ };
191
+ this.playServerIds = {
192
+ keepAlive: (0, version_js_1.resolvePacketId)(playToServer, ['keep_alive']) ?? missing,
193
+ chunkBatchReceived: (0, version_js_1.resolvePacketId)(playToServer, ['chunk_batch_received']) ?? missing,
194
+ teleportConfirm: (0, version_js_1.resolvePacketId)(playToServer, ['teleport_confirm']) ?? missing,
195
+ position: (0, version_js_1.resolvePacketId)(playToServer, ['position']) ?? missing,
196
+ positionLook: (0, version_js_1.resolvePacketId)(playToServer, ['position_look']) ?? missing,
197
+ look: (0, version_js_1.resolvePacketId)(playToServer, ['look']) ?? missing,
198
+ flying: (0, version_js_1.resolvePacketId)(playToServer, ['flying']) ?? missing,
199
+ chat: (0, version_js_1.resolvePacketId)(playToServer, ['chat']) ?? missing,
200
+ chatMessage: (0, version_js_1.resolvePacketId)(playToServer, ['chat_message']) ?? missing,
201
+ chatCommand: (0, version_js_1.resolvePacketId)(playToServer, ['chat_command']) ?? missing,
202
+ useEntity: (0, version_js_1.resolvePacketId)(playToServer, ['use_entity']) ?? missing,
203
+ armAnimation: (0, version_js_1.resolvePacketId)(playToServer, ['arm_animation']) ?? missing,
204
+ entityAction: (0, version_js_1.resolvePacketId)(playToServer, ['entity_action']) ?? missing,
205
+ blockDig: (0, version_js_1.resolvePacketId)(playToServer, ['block_dig']) ?? missing,
206
+ blockPlace: (0, version_js_1.resolvePacketId)(playToServer, ['block_place']) ?? missing,
207
+ useItem: (0, version_js_1.resolvePacketId)(playToServer, ['use_item']) ?? missing,
208
+ heldItemSlot: (0, version_js_1.resolvePacketId)(playToServer, ['held_item_slot']) ?? missing,
209
+ abilities: (0, version_js_1.resolvePacketId)(playToServer, ['abilities']) ?? missing,
210
+ clientCommand: (0, version_js_1.resolvePacketId)(playToServer, ['client_command']) ?? missing,
211
+ closeWindow: (0, version_js_1.resolvePacketId)(playToServer, ['close_window']) ?? missing,
212
+ pong: (0, version_js_1.resolvePacketId)(playToServer, ['pong']) ?? missing,
213
+ };
100
214
  // Initialize subsystems
101
215
  this.world = new world_js_1.World();
102
216
  this.entities = new entity_js_1.EntityManager();
@@ -118,8 +232,10 @@ class Bot extends events_js_1.TypedEventEmitter {
118
232
  port: this.options.port,
119
233
  logLevel: options.logLevel,
120
234
  });
235
+ this.connection.on('error', this.onConnectionError);
236
+ this.connection.on('close', this.onConnectionClose);
121
237
  // Register built-in plugins (deferred init)
122
- this.plugins.register(new chat_js_1.ChatPlugin());
238
+ this.plugins.register(new chat_js_2.ChatPlugin());
123
239
  this.plugins.register(new combat_js_1.CombatPlugin());
124
240
  // Register user plugins
125
241
  if (options.plugins) {
@@ -140,15 +256,29 @@ class Bot extends events_js_1.TypedEventEmitter {
140
256
  get gameMode() { return this._gameMode; }
141
257
  get isAlive() { return this._isAlive; }
142
258
  get position() { return this.entity.position; }
259
+ requirePacketId(packetId, packetName) {
260
+ if (packetId === MISSING_PACKET_ID) {
261
+ throw new Error(`Packet '${packetName}' is unavailable for protocol ${this.options.version} (${this.protocolInfo.minecraftVersion})`);
262
+ }
263
+ return packetId;
264
+ }
265
+ sendPlayPacket(packetId, data, packetName) {
266
+ this.connection.sendPacket(this.requirePacketId(packetId, packetName), data);
267
+ }
268
+ emitChatFromComponent(rawContent, isOverlay) {
269
+ const message = chat_js_1.ChatMessage.parse(rawContent);
270
+ this.emit('chatMessage', message, isOverlay);
271
+ this.emit('chat', message.toString(), isOverlay);
272
+ }
143
273
  // ─── Connect & Login ────────────────────────────────
144
274
  /**
145
275
  * Connect to the server and begin the login process.
146
276
  */
147
277
  async connect() {
148
278
  this.logger.info(`Connecting to ${this.options.host}:${this.options.port}...`);
149
- // Setup error/close handling
150
- this.connection.on('error', (err) => this.emit('error', err));
151
- this.connection.on('close', () => this.emit('end'));
279
+ if (this.connection.connected) {
280
+ throw new Error('Bot is already connected');
281
+ }
152
282
  // Connect TCP
153
283
  await this.connection.connect();
154
284
  this.emit('connect');
@@ -180,80 +310,105 @@ class Bot extends events_js_1.TypedEventEmitter {
180
310
  }
181
311
  // Step 3: Send Login Start
182
312
  this.logger.info(`Logging in as ${this._username} (${uuid})...`);
183
- this.connection.sendPacket(login_js_1.LoginServerboundPackets.LOGIN_START, (0, login_js_1.buildLoginStartPacket)(this._username, uuid));
313
+ this.connection.sendPacket(this.requirePacketId(this.loginServerIds.loginStart, 'login_start'), (0, login_js_1.buildLoginStartPacket)(this._username, uuid, this.options.version));
184
314
  // Step 4: Handle login response packets
185
315
  await this.handleLoginSequence();
186
316
  }
187
317
  async handleLoginSequence() {
188
318
  return new Promise((resolve, reject) => {
319
+ let settled = false;
320
+ const finish = (fn) => {
321
+ if (settled)
322
+ return;
323
+ settled = true;
324
+ clearTimeout(timeout);
325
+ this.connection.off('packet', onPacket);
326
+ this.connection.off('close', onClose);
327
+ this.connection.off('error', onError);
328
+ fn();
329
+ };
189
330
  const timeout = setTimeout(() => {
190
- reject(new Error('Login timeout (30s)'));
331
+ finish(() => reject(new Error('Login timeout (30s)')));
191
332
  }, 30000);
333
+ const onClose = () => {
334
+ finish(() => reject(new Error('Connection closed during login')));
335
+ };
336
+ const onError = (err) => {
337
+ finish(() => reject(err));
338
+ };
192
339
  const onPacket = (packet) => {
193
- try {
194
- this.handleLoginPacket(packet, () => {
195
- clearTimeout(timeout);
196
- // Remove login handler IMMEDIATELY before entering config state
197
- this.connection.off('packet', onPacket);
198
- resolve();
199
- });
200
- }
201
- catch (err) {
202
- clearTimeout(timeout);
203
- this.connection.off('packet', onPacket);
204
- reject(err);
205
- }
340
+ this.handleLoginPacket(packet, () => {
341
+ finish(resolve);
342
+ }).catch((err) => {
343
+ const error = err instanceof Error ? err : new Error(String(err));
344
+ finish(() => reject(error));
345
+ });
206
346
  };
207
347
  this.connection.on('packet', onPacket);
348
+ this.connection.on('close', onClose);
349
+ this.connection.on('error', onError);
208
350
  });
209
351
  }
210
- handleLoginPacket(packet, onComplete) {
352
+ async handleLoginPacket(packet, onComplete) {
211
353
  switch (packet.id) {
212
- case login_js_1.LoginClientboundPackets.ENCRYPTION_REQUEST: {
213
- this.handleEncryptionRequest(packet.data);
354
+ case this.loginClientIds.encryptionBegin: {
355
+ await this.handleEncryptionRequest(packet.data);
214
356
  break;
215
357
  }
216
- case login_js_1.LoginClientboundPackets.SET_COMPRESSION: {
358
+ case this.loginClientIds.compress: {
217
359
  const { threshold } = (0, login_js_1.parseSetCompressionPacket)(packet.data);
218
360
  this.connection.enableCompression(threshold);
219
361
  this.logger.info(`Compression enabled (threshold: ${threshold})`);
220
362
  break;
221
363
  }
222
- case login_js_1.LoginClientboundPackets.LOGIN_SUCCESS: {
223
- const success = (0, login_js_1.parseLoginSuccessPacket)(packet.data);
364
+ case this.loginClientIds.success: {
365
+ const success = (0, login_js_1.parseLoginSuccessPacket)(packet.data, this.options.version);
224
366
  this._username = success.username;
225
367
  this._uuid = success.uuid;
226
368
  this.logger.info(`Login successful! ${success.username} (${success.uuid})`);
227
369
  // Send Login Acknowledged
228
- this.connection.sendPacket(login_js_1.LoginServerboundPackets.LOGIN_ACKNOWLEDGED, (0, login_js_1.buildLoginAcknowledgedPacket)());
370
+ if (this.loginServerIds.loginAcknowledged !== MISSING_PACKET_ID) {
371
+ this.connection.sendPacket(this.loginServerIds.loginAcknowledged, (0, login_js_1.buildLoginAcknowledgedPacket)());
372
+ }
229
373
  // Remove login handler FIRST, then enter Configuration state
230
374
  onComplete();
231
375
  // Enter Configuration state — handle config packets before Play
232
- this.handleConfigurationState().then(() => {
376
+ if (this.protocolInfo.supportsConfigurationState) {
377
+ this.handleConfigurationState().then(() => {
378
+ this.setupPlayStateHandlers();
379
+ this.emit('login');
380
+ }).catch((err) => {
381
+ this.logger.error('Configuration state failed:', err);
382
+ const error = err instanceof Error ? err : new Error(String(err));
383
+ this.emit('error', error);
384
+ this.disconnect();
385
+ });
386
+ }
387
+ else {
233
388
  this.setupPlayStateHandlers();
234
389
  this.emit('login');
235
- }).catch((err) => {
236
- this.logger.error('Configuration state failed:', err);
237
- });
390
+ }
238
391
  break;
239
392
  }
240
- case login_js_1.LoginClientboundPackets.DISCONNECT: {
393
+ case this.loginClientIds.disconnect: {
241
394
  const { reason } = (0, login_js_1.parseDisconnectPacket)(packet.data);
242
395
  this.logger.error(`Disconnected during login: ${reason}`);
243
396
  this.emit('kicked', reason);
244
- break;
397
+ throw new Error(`Disconnected during login: ${reason}`);
245
398
  }
246
- case login_js_1.LoginClientboundPackets.LOGIN_PLUGIN_REQUEST: {
399
+ case this.loginClientIds.loginPluginRequest: {
247
400
  const pluginReq = (0, login_js_1.parseLoginPluginRequestPacket)(packet.data);
248
401
  this.logger.debug(`Login plugin request: ${pluginReq.channel}`);
249
402
  // Respond with "not understood"
250
- this.connection.sendPacket(login_js_1.LoginServerboundPackets.LOGIN_PLUGIN_RESPONSE, (0, login_js_1.buildLoginPluginResponsePacket)(pluginReq.messageId));
403
+ if (this.loginServerIds.loginPluginResponse !== MISSING_PACKET_ID) {
404
+ this.connection.sendPacket(this.loginServerIds.loginPluginResponse, (0, login_js_1.buildLoginPluginResponsePacket)(pluginReq.messageId));
405
+ }
251
406
  break;
252
407
  }
253
408
  }
254
409
  }
255
- handleEncryptionRequest(data) {
256
- const request = (0, login_js_1.parseEncryptionRequestPacket)(data);
410
+ async handleEncryptionRequest(data) {
411
+ const request = (0, login_js_1.parseEncryptionRequestPacket)(data, this.options.version);
257
412
  this.logger.info('Handling encryption...');
258
413
  // Generate shared secret
259
414
  const sharedSecret = (0, encryption_js_1.generateSharedSecret)();
@@ -261,25 +416,15 @@ class Bot extends events_js_1.TypedEventEmitter {
261
416
  const encryptedSecret = (0, encryption_js_1.encryptRSA)(request.publicKey, sharedSecret);
262
417
  const encryptedToken = (0, encryption_js_1.encryptRSA)(request.publicKey, request.verifyToken);
263
418
  // If online mode, join session server
264
- if (request.shouldAuthenticate && this.accessToken) {
419
+ if (request.shouldAuthenticate) {
420
+ if (!this.accessToken) {
421
+ throw new Error('Server requested online authentication but no Microsoft access token is available');
422
+ }
265
423
  const serverHash = (0, encryption_js_1.computeServerHash)(request.serverId, sharedSecret, request.publicKey);
266
- (0, microsoft_js_1.joinServer)(this.accessToken, this._uuid, serverHash)
267
- .then(() => {
268
- // Send encryption response
269
- this.connection.sendPacket(login_js_1.LoginServerboundPackets.ENCRYPTION_RESPONSE, (0, login_js_1.buildEncryptionResponsePacket)(encryptedSecret, encryptedToken));
270
- // Enable encryption for all subsequent packets
271
- this.connection.enableEncryption(sharedSecret);
272
- })
273
- .catch((err) => {
274
- this.logger.error('Failed to join session:', err);
275
- this.emit('error', err);
276
- });
277
- }
278
- else {
279
- // Offline mode or no auth needed
280
- this.connection.sendPacket(login_js_1.LoginServerboundPackets.ENCRYPTION_RESPONSE, (0, login_js_1.buildEncryptionResponsePacket)(encryptedSecret, encryptedToken));
281
- this.connection.enableEncryption(sharedSecret);
424
+ await (0, microsoft_js_1.joinServer)(this.accessToken, this._uuid, serverHash);
282
425
  }
426
+ this.connection.sendPacket(this.requirePacketId(this.loginServerIds.encryptionBegin, 'encryption_begin'), (0, login_js_1.buildEncryptionResponsePacket)(encryptedSecret, encryptedToken));
427
+ this.connection.enableEncryption(sharedSecret);
283
428
  }
284
429
  // ─── Configuration State ────────────────────────────
285
430
  /**
@@ -287,7 +432,7 @@ class Bot extends events_js_1.TypedEventEmitter {
287
432
  * In 1.20.2+, the server sends registry data, feature flags, etc. before Play.
288
433
  * When the server sends Finish Configuration (0x03), we transition to Play.
289
434
  *
290
- * 1.21.4 Configuration Clientbound Packet IDs:
435
+ * Example configuration clientbound packet IDs (latest protocol):
291
436
  * 0x00 = Cookie Request
292
437
  * 0x01 = Plugin Message
293
438
  * 0x02 = Disconnect
@@ -303,91 +448,103 @@ class Bot extends events_js_1.TypedEventEmitter {
303
448
  */
304
449
  handleConfigurationState() {
305
450
  return new Promise((resolve, reject) => {
306
- const timeout = setTimeout(() => {
451
+ let settled = false;
452
+ const finish = (fn) => {
453
+ if (settled)
454
+ return;
455
+ settled = true;
456
+ clearTimeout(timeout);
307
457
  this.connection.off('packet', configHandler);
308
- reject(new Error('Configuration state timeout (60s)'));
458
+ this.connection.off('close', onClose);
459
+ this.connection.off('error', onError);
460
+ fn();
461
+ };
462
+ const timeout = setTimeout(() => {
463
+ finish(() => reject(new Error('Configuration state timeout (60s)')));
309
464
  }, 60000);
465
+ const onClose = () => {
466
+ finish(() => reject(new Error('Connection closed during configuration state')));
467
+ };
468
+ const onError = (err) => {
469
+ finish(() => reject(err));
470
+ };
310
471
  const configHandler = (packet) => {
311
472
  switch (packet.id) {
312
- case 0x03: {
473
+ case this.configurationClientIds.finishConfiguration: {
313
474
  // Finish Configuration (clientbound)
314
475
  this.logger.info('Configuration complete, entering Play state...');
315
- // Send Acknowledge Finish Configuration (serverbound 0x03)
316
- this.connection.sendPacket(0x03, Buffer.alloc(0));
317
- clearTimeout(timeout);
318
- this.connection.off('packet', configHandler);
319
- resolve();
476
+ // Send Acknowledge Finish Configuration (serverbound)
477
+ this.connection.sendPacket(this.requirePacketId(this.configurationServerIds.finishConfiguration, 'finish_configuration'), Buffer.alloc(0));
478
+ finish(resolve);
320
479
  break;
321
480
  }
322
- case 0x04: {
481
+ case this.configurationClientIds.keepAlive: {
323
482
  // Keep Alive (clientbound) — must respond or get kicked
324
- const reader = new types_js_1.BufferReader(packet.data);
325
- const keepAliveId = reader.readLong();
326
- this.logger.debug(`Config: Keep alive (${keepAliveId})`);
327
- // Respond with same keep alive ID (serverbound 0x04)
328
- const kaWriter = new types_js_1.BufferWriter();
329
- kaWriter.writeLong(keepAliveId);
330
- this.connection.sendPacket(0x04, kaWriter.toBuffer());
483
+ if (this.configurationServerIds.keepAlive !== MISSING_PACKET_ID) {
484
+ this.connection.sendPacket(this.configurationServerIds.keepAlive, packet.data);
485
+ }
331
486
  break;
332
487
  }
333
- case 0x01: {
488
+ case this.configurationClientIds.customPayload: {
334
489
  // Plugin Message (clientbound) — ignore
335
490
  this.logger.debug('Config: Plugin message received');
336
491
  break;
337
492
  }
338
- case 0x07: {
493
+ case this.configurationClientIds.registryData: {
339
494
  // Registry Data — ignore (we don't track registry data yet)
340
495
  this.logger.debug('Config: Registry data received');
341
496
  break;
342
497
  }
343
- case 0x0C: {
498
+ case this.configurationClientIds.featureFlags: {
344
499
  // Feature Flags — ignore
345
500
  this.logger.debug('Config: Feature flags received');
346
501
  break;
347
502
  }
348
- case 0x0D: {
503
+ case this.configurationClientIds.tags: {
349
504
  // Update Tags — ignore
350
505
  this.logger.debug('Config: Tags update received');
351
506
  break;
352
507
  }
353
- case 0x0E: {
508
+ case this.configurationClientIds.selectKnownPacks: {
354
509
  // Known Packs (clientbound) — respond with empty known packs
355
510
  this.logger.debug('Config: Known packs request');
356
511
  // Send Known Packs response (serverbound 0x07) — empty
357
512
  const knownPacksWriter = new types_js_1.BufferWriter();
358
513
  knownPacksWriter.writeVarInt(0); // 0 known packs
359
- this.connection.sendPacket(0x07, knownPacksWriter.toBuffer());
514
+ if (this.configurationServerIds.selectKnownPacks !== MISSING_PACKET_ID) {
515
+ this.connection.sendPacket(this.configurationServerIds.selectKnownPacks, knownPacksWriter.toBuffer());
516
+ }
360
517
  break;
361
518
  }
362
- case 0x05: {
519
+ case this.configurationClientIds.ping: {
363
520
  // Ping — respond with pong
364
521
  this.logger.debug('Config: Ping received');
365
- this.connection.sendPacket(0x02, packet.data); // Pong (serverbound 0x02)
522
+ if (this.configurationServerIds.pong !== MISSING_PACKET_ID) {
523
+ this.connection.sendPacket(this.configurationServerIds.pong, packet.data);
524
+ }
366
525
  break;
367
526
  }
368
- case 0x00: {
527
+ case this.configurationClientIds.cookieRequest: {
369
528
  // Cookie Request — ignore
370
529
  this.logger.debug('Config: Cookie request');
371
530
  break;
372
531
  }
373
- case 0x09: {
532
+ case this.configurationClientIds.addResourcePack: {
374
533
  // Add Resource Pack — respond with accepted (status 3 = accepted)
375
534
  this.logger.debug('Config: Resource pack request');
376
535
  break;
377
536
  }
378
- case 0x10: {
537
+ case this.configurationClientIds.serverLinks: {
379
538
  // Server Links — ignore
380
539
  this.logger.debug('Config: Server links received');
381
540
  break;
382
541
  }
383
- case 0x02: {
542
+ case this.configurationClientIds.disconnect: {
384
543
  // Disconnect
385
544
  const reader = new types_js_1.BufferReader(packet.data);
386
545
  const reason = reader.readString();
387
546
  this.logger.error(`Disconnected during configuration: ${reason}`);
388
- clearTimeout(timeout);
389
- this.connection.off('packet', configHandler);
390
- reject(new Error(`Disconnected during configuration: ${reason}`));
547
+ finish(() => reject(new Error(`Disconnected during configuration: ${reason}`)));
391
548
  break;
392
549
  }
393
550
  default: {
@@ -397,11 +554,15 @@ class Bot extends events_js_1.TypedEventEmitter {
397
554
  }
398
555
  };
399
556
  this.connection.on('packet', configHandler);
557
+ this.connection.on('close', onClose);
558
+ this.connection.on('error', onError);
400
559
  });
401
560
  }
402
561
  // ─── Play State ─────────────────────────────────────
403
562
  setupPlayStateHandlers() {
404
- this.connection.on('packet', (packet) => {
563
+ if (this.playPacketListener)
564
+ return;
565
+ this.playPacketListener = (packet) => {
405
566
  this.emit('rawPacket', packet);
406
567
  this.handlePlayPacket(packet);
407
568
  // Call registered packet handlers
@@ -416,25 +577,30 @@ class Bot extends events_js_1.TypedEventEmitter {
416
577
  }
417
578
  }
418
579
  }
419
- });
580
+ };
581
+ this.connection.on('packet', this.playPacketListener);
420
582
  }
421
583
  handlePlayPacket(packet) {
422
584
  switch (packet.id) {
423
- case play_js_1.PlayClientboundPackets.KEEP_ALIVE: {
424
- const { keepAliveId } = (0, play_js_1.parseKeepAlive)(packet.data);
425
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.KEEP_ALIVE, (0, play_js_1.buildKeepAlivePacket)(keepAliveId));
585
+ case this.playClientIds.keepAlive: {
586
+ if (this.playServerIds.keepAlive !== MISSING_PACKET_ID) {
587
+ // Keep alive payload type changes by protocol; echo raw payload safely.
588
+ this.connection.sendPacket(this.playServerIds.keepAlive, packet.data);
589
+ }
426
590
  break;
427
591
  }
428
- case play_js_1.PlayClientboundPackets.CHUNK_BATCH_FINISHED: {
592
+ case this.playClientIds.chunkBatchFinished: {
429
593
  // Must respond with chunk_batch_received or server kicks
430
- const writer = new types_js_1.BufferWriter();
431
- writer.writeFloat(20.0); // chunks per tick
432
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.CHUNK_BATCH_RECEIVED, writer.toBuffer());
594
+ if (this.playServerIds.chunkBatchReceived !== MISSING_PACKET_ID) {
595
+ const writer = new types_js_1.BufferWriter();
596
+ writer.writeFloat(20.0); // chunks per tick
597
+ this.connection.sendPacket(this.playServerIds.chunkBatchReceived, writer.toBuffer());
598
+ }
433
599
  break;
434
600
  }
435
- case play_js_1.PlayClientboundPackets.LOGIN: {
601
+ case this.playClientIds.login: {
436
602
  try {
437
- const login = (0, play_js_1.parseLoginPlay)(packet.data);
603
+ const login = (0, play_js_1.parseLoginPlay)(packet.data, this.options.version);
438
604
  this._entityId = login.entityId;
439
605
  this._gameMode = login.gameMode;
440
606
  this.entity.id = login.entityId;
@@ -450,9 +616,9 @@ class Bot extends events_js_1.TypedEventEmitter {
450
616
  }
451
617
  break;
452
618
  }
453
- case play_js_1.PlayClientboundPackets.PLAYER_POSITION: {
619
+ case this.playClientIds.position: {
454
620
  try {
455
- const pos = (0, play_js_1.parsePlayerPosition)(packet.data);
621
+ const pos = (0, play_js_1.parsePlayerPosition)(packet.data, this.options.version);
456
622
  // Apply relative/absolute position based on flags
457
623
  const flags = pos.flags;
458
624
  const x = (flags & 0x01) ? this.entity.position.x + pos.x : pos.x;
@@ -462,7 +628,10 @@ class Bot extends events_js_1.TypedEventEmitter {
462
628
  this.entity.yaw = pos.yaw;
463
629
  this.entity.pitch = pos.pitch;
464
630
  // Must confirm teleportation
465
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.CONFIRM_TELEPORTATION, (0, play_js_1.buildConfirmTeleportationPacket)(pos.teleportId));
631
+ if (this.playServerIds.teleportConfirm !== MISSING_PACKET_ID &&
632
+ pos.teleportId >= 0) {
633
+ this.connection.sendPacket(this.playServerIds.teleportConfirm, (0, play_js_1.buildConfirmTeleportationPacket)(pos.teleportId));
634
+ }
466
635
  this.emit('position', this.entity.position);
467
636
  }
468
637
  catch (err) {
@@ -470,7 +639,7 @@ class Bot extends events_js_1.TypedEventEmitter {
470
639
  }
471
640
  break;
472
641
  }
473
- case play_js_1.PlayClientboundPackets.SET_HEALTH: {
642
+ case this.playClientIds.updateHealth: {
474
643
  const { health, food, saturation } = (0, play_js_1.parseSetHealth)(packet.data);
475
644
  this._health = health;
476
645
  this._food = food;
@@ -491,7 +660,7 @@ class Bot extends events_js_1.TypedEventEmitter {
491
660
  }
492
661
  break;
493
662
  }
494
- case play_js_1.PlayClientboundPackets.BLOCK_UPDATE: {
663
+ case this.playClientIds.blockChange: {
495
664
  try {
496
665
  const update = (0, play_js_1.parseBlockUpdate)(packet.data);
497
666
  this.world.setBlock(update.x, update.y, update.z, update.blockId);
@@ -502,7 +671,17 @@ class Bot extends events_js_1.TypedEventEmitter {
502
671
  }
503
672
  break;
504
673
  }
505
- case play_js_1.PlayClientboundPackets.SPAWN_ENTITY: {
674
+ case this.playClientIds.bossBar: {
675
+ try {
676
+ const boss = (0, play_js_1.parseBossBar)(packet.data);
677
+ this.bossBars.handleBossBar(boss.uuid, boss.action, boss.data);
678
+ }
679
+ catch (err) {
680
+ this.logger.error('Failed to parse boss bar:', err);
681
+ }
682
+ break;
683
+ }
684
+ case this.playClientIds.spawnEntity: {
506
685
  try {
507
686
  const spawn = (0, play_js_1.parseSpawnEntity)(packet.data);
508
687
  const entity = new entity_js_1.Entity(spawn.entityId, spawn.entityUUID, spawn.type, new vec3_js_1.Vec3(spawn.x, spawn.y, spawn.z));
@@ -516,7 +695,7 @@ class Bot extends events_js_1.TypedEventEmitter {
516
695
  }
517
696
  break;
518
697
  }
519
- case play_js_1.PlayClientboundPackets.REMOVE_ENTITIES: {
698
+ case this.playClientIds.entityDestroy: {
520
699
  try {
521
700
  const { entityIds } = (0, play_js_1.parseRemoveEntities)(packet.data);
522
701
  for (const entityId of entityIds) {
@@ -529,7 +708,7 @@ class Bot extends events_js_1.TypedEventEmitter {
529
708
  }
530
709
  break;
531
710
  }
532
- case play_js_1.PlayClientboundPackets.ENTITY_POSITION: {
711
+ case this.playClientIds.relEntityMove: {
533
712
  try {
534
713
  const pos = (0, play_js_1.parseEntityPosition)(packet.data);
535
714
  const entity = this.entities.get(pos.entityId);
@@ -541,7 +720,7 @@ class Bot extends events_js_1.TypedEventEmitter {
541
720
  catch (_) { /* ignore */ }
542
721
  break;
543
722
  }
544
- case play_js_1.PlayClientboundPackets.ENTITY_POSITION_AND_ROTATION: {
723
+ case this.playClientIds.entityMoveLook: {
545
724
  try {
546
725
  const pos = (0, play_js_1.parseEntityPositionAndRotation)(packet.data);
547
726
  const entity = this.entities.get(pos.entityId);
@@ -555,7 +734,7 @@ class Bot extends events_js_1.TypedEventEmitter {
555
734
  catch (_) { /* ignore */ }
556
735
  break;
557
736
  }
558
- case play_js_1.PlayClientboundPackets.SET_ENTITY_VELOCITY: {
737
+ case this.playClientIds.entityVelocity: {
559
738
  try {
560
739
  const vel = (0, play_js_1.parseEntityVelocity)(packet.data);
561
740
  this.emit('entityVelocity', vel.entityId, new vec3_js_1.Vec3(vel.velocityX, vel.velocityY, vel.velocityZ));
@@ -563,7 +742,7 @@ class Bot extends events_js_1.TypedEventEmitter {
563
742
  catch (_) { /* ignore */ }
564
743
  break;
565
744
  }
566
- case play_js_1.PlayClientboundPackets.ENTITY_HEAD_ROTATION: {
745
+ case this.playClientIds.entityHeadRotation: {
567
746
  try {
568
747
  const rot = (0, play_js_1.parseEntityHeadRotation)(packet.data);
569
748
  const entity = this.entities.get(rot.entityId);
@@ -574,22 +753,25 @@ class Bot extends events_js_1.TypedEventEmitter {
574
753
  catch (_) { /* ignore */ }
575
754
  break;
576
755
  }
577
- case play_js_1.PlayClientboundPackets.GAME_EVENT: {
756
+ case this.playClientIds.gameStateChange: {
578
757
  try {
579
758
  const ev = (0, play_js_1.parseGameEvent)(packet.data);
580
759
  if (ev.event === play_js_1.GameEventType.CHANGE_GAME_MODE) {
581
760
  this._gameMode = ev.value;
582
761
  }
762
+ this.weather.handleGameEvent(ev.event, ev.value);
583
763
  if (ev.event === play_js_1.GameEventType.START_WAITING_FOR_LEVEL_CHUNKS) {
584
764
  // Send position confirmation after chunks start loading
585
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.SET_PLAYER_POSITION_AND_ROTATION, (0, play_js_1.buildSetPlayerPositionAndRotationPacket)(this.entity.position.x, this.entity.position.y, this.entity.position.z, this.entity.yaw, this.entity.pitch, true));
765
+ if (this.playServerIds.positionLook !== MISSING_PACKET_ID) {
766
+ this.connection.sendPacket(this.playServerIds.positionLook, (0, play_js_1.buildSetPlayerPositionAndRotationPacket)(this.entity.position.x, this.entity.position.y, this.entity.position.z, this.entity.yaw, this.entity.pitch, true, this.options.version));
767
+ }
586
768
  }
587
769
  this.emit('gameEvent', ev.event, ev.value);
588
770
  }
589
771
  catch (_) { /* ignore */ }
590
772
  break;
591
773
  }
592
- case play_js_1.PlayClientboundPackets.RESPAWN: {
774
+ case this.playClientIds.respawn: {
593
775
  try {
594
776
  const resp = (0, play_js_1.parseRespawn)(packet.data);
595
777
  this._gameMode = resp.gameMode;
@@ -603,7 +785,7 @@ class Bot extends events_js_1.TypedEventEmitter {
603
785
  }
604
786
  break;
605
787
  }
606
- case play_js_1.PlayClientboundPackets.SET_EXPERIENCE: {
788
+ case this.playClientIds.experience: {
607
789
  try {
608
790
  const exp = (0, play_js_1.parseSetExperience)(packet.data);
609
791
  this._experience = exp.totalExperience;
@@ -613,7 +795,7 @@ class Bot extends events_js_1.TypedEventEmitter {
613
795
  catch (_) { /* ignore */ }
614
796
  break;
615
797
  }
616
- case play_js_1.PlayClientboundPackets.UPDATE_TIME: {
798
+ case this.playClientIds.updateTime: {
617
799
  try {
618
800
  const time = (0, play_js_1.parseUpdateTime)(packet.data);
619
801
  this._worldAge = time.worldAge;
@@ -623,7 +805,7 @@ class Bot extends events_js_1.TypedEventEmitter {
623
805
  catch (_) { /* ignore */ }
624
806
  break;
625
807
  }
626
- case play_js_1.PlayClientboundPackets.SET_DEFAULT_SPAWN_POSITION: {
808
+ case this.playClientIds.spawnPosition: {
627
809
  try {
628
810
  const sp = (0, play_js_1.parseSetDefaultSpawnPosition)(packet.data);
629
811
  this._spawnPosition = new vec3_js_1.Vec3(sp.x, sp.y, sp.z);
@@ -632,30 +814,73 @@ class Bot extends events_js_1.TypedEventEmitter {
632
814
  catch (_) { /* ignore */ }
633
815
  break;
634
816
  }
635
- case play_js_1.PlayClientboundPackets.PING: {
817
+ case this.playClientIds.openWindow: {
818
+ try {
819
+ const open = (0, play_js_1.parseOpenScreen)(packet.data);
820
+ this.inventory.openWindow(open.windowId, 0);
821
+ }
822
+ catch (err) {
823
+ this.logger.error('Failed to parse open screen:', err);
824
+ }
825
+ break;
826
+ }
827
+ case this.playClientIds.windowItems: {
828
+ try {
829
+ const reader = new types_js_1.BufferReader(packet.data);
830
+ const windowId = reader.readUByte();
831
+ const stateId = reader.readVarInt();
832
+ const slotCount = reader.readVarInt();
833
+ // Full slot component parsing is protocol-version dependent.
834
+ const items = new Array(slotCount).fill(null);
835
+ this.inventory.setContainerContent(windowId, items, stateId, null);
836
+ }
837
+ catch (err) {
838
+ this.logger.error('Failed to parse container content:', err);
839
+ }
840
+ break;
841
+ }
842
+ case this.playClientIds.setSlot: {
843
+ try {
844
+ const slot = (0, play_js_1.parseSetContainerSlot)(packet.data);
845
+ const item = inventory_js_1.Inventory.parseItem(new types_js_1.BufferReader(slot.slotData));
846
+ this.inventory.setSlot(slot.windowId, slot.slot, item, slot.stateId);
847
+ this.emit('inventoryUpdate', slot.slot, item);
848
+ }
849
+ catch (err) {
850
+ this.logger.error('Failed to parse container slot:', err);
851
+ }
852
+ break;
853
+ }
854
+ case this.playClientIds.closeWindow: {
855
+ this.inventory.closeWindow();
856
+ break;
857
+ }
858
+ case this.playClientIds.ping: {
636
859
  try {
637
860
  const reader = new types_js_1.BufferReader(packet.data);
638
861
  const pingId = reader.readInt();
639
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.PONG, (0, play_js_1.buildPongPacket)(pingId));
862
+ if (this.playServerIds.pong !== MISSING_PACKET_ID) {
863
+ this.connection.sendPacket(this.playServerIds.pong, (0, play_js_1.buildPongPacket)(pingId));
864
+ }
640
865
  }
641
866
  catch (_) { /* ignore */ }
642
867
  break;
643
868
  }
644
- case play_js_1.PlayClientboundPackets.ACKNOWLEDGE_BLOCK_CHANGE: {
869
+ case this.playClientIds.acknowledgePlayerDigging: {
645
870
  try {
646
871
  (0, play_js_1.parseAcknowledgeBlockChange)(packet.data);
647
872
  }
648
873
  catch (_) { /* ignore */ }
649
874
  break;
650
875
  }
651
- case play_js_1.PlayClientboundPackets.DISCONNECT: {
876
+ case this.playClientIds.disconnect: {
652
877
  const { reason } = (0, play_js_1.parseDisconnectPlay)(packet.data);
653
878
  this.logger.warn(`Kicked: ${reason}`);
654
879
  this.stopPhysicsTick();
655
880
  this.emit('kicked', reason);
656
881
  break;
657
882
  }
658
- case play_js_1.PlayClientboundPackets.PLAYER_ABILITIES: {
883
+ case this.playClientIds.abilities: {
659
884
  try {
660
885
  const abilities = (0, play_js_1.parsePlayerAbilities)(packet.data);
661
886
  this.logger.debug(`Abilities: flags=${abilities.flags} fly=${abilities.flyingSpeed} walk=${abilities.walkingSpeed}`);
@@ -665,10 +890,29 @@ class Bot extends events_js_1.TypedEventEmitter {
665
890
  }
666
891
  break;
667
892
  }
668
- case play_js_1.PlayClientboundPackets.SYSTEM_CHAT: {
893
+ case this.playClientIds.systemChat: {
669
894
  try {
670
895
  const chat = (0, play_js_1.parseSystemChat)(packet.data);
671
- this.emit('chat', chat.content, chat.isOverlay);
896
+ this.emitChatFromComponent(chat.content, chat.isOverlay);
897
+ }
898
+ catch (_) { /* ignore */ }
899
+ break;
900
+ }
901
+ case this.playClientIds.profilelessChat: {
902
+ try {
903
+ const reader = new types_js_1.BufferReader(packet.data);
904
+ const content = reader.readString();
905
+ this.emitChatFromComponent(content, false);
906
+ }
907
+ catch (_) { /* ignore */ }
908
+ break;
909
+ }
910
+ case this.playClientIds.legacyChat: {
911
+ try {
912
+ const reader = new types_js_1.BufferReader(packet.data);
913
+ const content = reader.readString();
914
+ const position = reader.remaining > 0 ? reader.readByte() : 0;
915
+ this.emitChatFromComponent(content, position === 2);
672
916
  }
673
917
  catch (_) { /* ignore */ }
674
918
  break;
@@ -684,7 +928,7 @@ class Bot extends events_js_1.TypedEventEmitter {
684
928
  return;
685
929
  // Send position to server every tick to stay "alive"
686
930
  try {
687
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.SET_PLAYER_POSITION_AND_ROTATION, (0, play_js_1.buildSetPlayerPositionAndRotationPacket)(this.entity.position.x, this.entity.position.y, this.entity.position.z, this.entity.yaw, this.entity.pitch, true));
931
+ this.sendPlayPacket(this.playServerIds.positionLook, (0, play_js_1.buildSetPlayerPositionAndRotationPacket)(this.entity.position.x, this.entity.position.y, this.entity.position.z, this.entity.yaw, this.entity.pitch, true, this.options.version), 'position_look');
688
932
  }
689
933
  catch (_) {
690
934
  this.stopPhysicsTick();
@@ -726,38 +970,49 @@ class Bot extends events_js_1.TypedEventEmitter {
726
970
  * Send a chat message.
727
971
  */
728
972
  chat(message) {
729
- const chatPlugin = this.plugins.get('chat');
730
- if (chatPlugin) {
731
- chatPlugin.chat(message);
973
+ if (this.playServerIds.chatMessage !== MISSING_PACKET_ID) {
974
+ this.sendPlayPacket(this.playServerIds.chatMessage, (0, play_js_1.buildChatMessagePacket)(message, this.options.version), 'chat_message');
975
+ return;
976
+ }
977
+ if (this.playServerIds.chat !== MISSING_PACKET_ID) {
978
+ this.sendPlayPacket(this.playServerIds.chat, (0, play_js_1.buildChatMessagePacket)(message, this.options.version), 'chat');
979
+ return;
732
980
  }
981
+ throw new Error(`Chat is not supported for protocol ${this.options.version}`);
733
982
  }
734
983
  /**
735
984
  * Send a / command (e.g. bot.command('gamemode creative')).
736
985
  */
737
986
  command(cmd) {
738
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.CHAT_COMMAND, (0, play_js_1.buildChatCommandPacket)(cmd));
987
+ if (this.playServerIds.chatCommand !== MISSING_PACKET_ID) {
988
+ try {
989
+ this.sendPlayPacket(this.playServerIds.chatCommand, (0, play_js_1.buildChatCommandPacket)(cmd, this.options.version), 'chat_command');
990
+ return;
991
+ }
992
+ catch (_) {
993
+ // Fallback to plain chat command below.
994
+ }
995
+ }
996
+ this.chat(`/${cmd}`);
739
997
  }
740
998
  /**
741
999
  * Attack an entity.
742
1000
  */
743
1001
  attack(entity) {
744
- const combatPlugin = this.plugins.get('combat');
745
- if (combatPlugin) {
746
- combatPlugin.attack(entity);
747
- }
1002
+ this.attackEntity(entity.id);
748
1003
  }
749
1004
  /**
750
1005
  * Attack entity by ID (raw packet).
751
1006
  */
752
1007
  attackEntity(entityId) {
753
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.INTERACT, (0, play_js_1.buildInteractEntityPacket)(entityId, play_js_1.InteractEntityType.ATTACK, this._isSneaking));
754
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.SWING_ARM, (0, play_js_1.buildSwingArmPacket)(0));
1008
+ this.sendPlayPacket(this.playServerIds.useEntity, (0, play_js_1.buildInteractEntityPacket)(entityId, play_js_1.InteractEntityType.ATTACK, this._isSneaking, undefined, undefined, undefined, undefined, this.options.version), 'use_entity');
1009
+ this.sendPlayPacket(this.playServerIds.armAnimation, (0, play_js_1.buildSwingArmPacket)(0, this.options.version), 'arm_animation');
755
1010
  }
756
1011
  /**
757
1012
  * Right-click / use an entity (villager trade, mount, etc.).
758
1013
  */
759
1014
  useEntity(entityId, hand = 0) {
760
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.INTERACT, (0, play_js_1.buildInteractEntityPacket)(entityId, play_js_1.InteractEntityType.INTERACT, this._isSneaking, undefined, undefined, undefined, hand));
1015
+ this.sendPlayPacket(this.playServerIds.useEntity, (0, play_js_1.buildInteractEntityPacket)(entityId, play_js_1.InteractEntityType.INTERACT, this._isSneaking, undefined, undefined, undefined, hand, this.options.version), 'use_entity');
761
1016
  }
762
1017
  /**
763
1018
  * Set the bot's position (sends position packet to server).
@@ -765,7 +1020,7 @@ class Bot extends events_js_1.TypedEventEmitter {
765
1020
  setPosition(x, y, z, onGround = true) {
766
1021
  this.entity.position = new vec3_js_1.Vec3(x, y, z);
767
1022
  this.entity.onGround = onGround;
768
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.SET_PLAYER_POSITION, (0, play_js_1.buildSetPlayerPositionPacket)(x, y, z, onGround));
1023
+ this.sendPlayPacket(this.playServerIds.position, (0, play_js_1.buildSetPlayerPositionPacket)(x, y, z, onGround, this.options.version), 'position');
769
1024
  }
770
1025
  /**
771
1026
  * Set position and rotation.
@@ -775,7 +1030,7 @@ class Bot extends events_js_1.TypedEventEmitter {
775
1030
  this.entity.yaw = yaw;
776
1031
  this.entity.pitch = pitch;
777
1032
  this.entity.onGround = onGround;
778
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.SET_PLAYER_POSITION_AND_ROTATION, (0, play_js_1.buildSetPlayerPositionAndRotationPacket)(x, y, z, yaw, pitch, onGround));
1033
+ this.sendPlayPacket(this.playServerIds.positionLook, (0, play_js_1.buildSetPlayerPositionAndRotationPacket)(x, y, z, yaw, pitch, onGround, this.options.version), 'position_look');
779
1034
  }
780
1035
  /**
781
1036
  * Look at a position.
@@ -787,21 +1042,21 @@ class Bot extends events_js_1.TypedEventEmitter {
787
1042
  const pitch = -Math.atan2(delta.y, distance) * (180 / Math.PI);
788
1043
  this.entity.yaw = yaw;
789
1044
  this.entity.pitch = pitch;
790
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.SET_PLAYER_POSITION_AND_ROTATION, (0, play_js_1.buildSetPlayerPositionAndRotationPacket)(this.entity.position.x, this.entity.position.y, this.entity.position.z, yaw, pitch, this.entity.onGround));
1045
+ this.sendPlayPacket(this.playServerIds.positionLook, (0, play_js_1.buildSetPlayerPositionAndRotationPacket)(this.entity.position.x, this.entity.position.y, this.entity.position.z, yaw, pitch, this.entity.onGround, this.options.version), 'position_look');
791
1046
  }
792
1047
  /**
793
1048
  * Start/stop sprinting.
794
1049
  */
795
1050
  sprint(enabled) {
796
1051
  this._isSprinting = enabled;
797
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.PLAYER_COMMAND, (0, play_js_1.buildPlayerCommandPacket)(this._entityId, enabled ? play_js_1.PlayerCommandAction.START_SPRINTING : play_js_1.PlayerCommandAction.STOP_SPRINTING));
1052
+ this.sendPlayPacket(this.playServerIds.entityAction, (0, play_js_1.buildPlayerCommandPacket)(this._entityId, enabled ? play_js_1.PlayerCommandAction.START_SPRINTING : play_js_1.PlayerCommandAction.STOP_SPRINTING), 'entity_action');
798
1053
  }
799
1054
  /**
800
1055
  * Start/stop sneaking.
801
1056
  */
802
1057
  sneak(enabled) {
803
1058
  this._isSneaking = enabled;
804
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.PLAYER_COMMAND, (0, play_js_1.buildPlayerCommandPacket)(this._entityId, enabled ? play_js_1.PlayerCommandAction.START_SNEAKING : play_js_1.PlayerCommandAction.STOP_SNEAKING));
1059
+ this.sendPlayPacket(this.playServerIds.entityAction, (0, play_js_1.buildPlayerCommandPacket)(this._entityId, enabled ? play_js_1.PlayerCommandAction.START_SNEAKING : play_js_1.PlayerCommandAction.STOP_SNEAKING), 'entity_action');
805
1060
  }
806
1061
  /**
807
1062
  * Start digging a block.
@@ -809,13 +1064,13 @@ class Bot extends events_js_1.TypedEventEmitter {
809
1064
  dig(x, y, z, face = 1) {
810
1065
  this._actionSequence++;
811
1066
  // Start digging
812
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.PLAYER_ACTION, (0, play_js_1.buildPlayerActionPacket)(play_js_1.PlayerActionStatus.STARTED_DIGGING, x, y, z, face, this._actionSequence));
1067
+ this.sendPlayPacket(this.playServerIds.blockDig, (0, play_js_1.buildPlayerActionPacket)(play_js_1.PlayerActionStatus.STARTED_DIGGING, x, y, z, face, this._actionSequence, this.options.version), 'block_dig');
813
1068
  // Swing arm animation
814
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.SWING_ARM, (0, play_js_1.buildSwingArmPacket)(0));
1069
+ this.sendPlayPacket(this.playServerIds.armAnimation, (0, play_js_1.buildSwingArmPacket)(0, this.options.version), 'arm_animation');
815
1070
  // For creative mode, finish immediately
816
1071
  if (this._gameMode === 1) {
817
1072
  this._actionSequence++;
818
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.PLAYER_ACTION, (0, play_js_1.buildPlayerActionPacket)(play_js_1.PlayerActionStatus.FINISHED_DIGGING, x, y, z, face, this._actionSequence));
1073
+ this.sendPlayPacket(this.playServerIds.blockDig, (0, play_js_1.buildPlayerActionPacket)(play_js_1.PlayerActionStatus.FINISHED_DIGGING, x, y, z, face, this._actionSequence, this.options.version), 'block_dig');
819
1074
  }
820
1075
  }
821
1076
  /**
@@ -823,15 +1078,15 @@ class Bot extends events_js_1.TypedEventEmitter {
823
1078
  */
824
1079
  placeBlock(x, y, z, face = 1, hand = 0) {
825
1080
  this._actionSequence++;
826
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.USE_ITEM_ON, (0, play_js_1.buildUseItemOnPacket)(hand, x, y, z, face, 0.5, 0.5, 0.5, false, false, this._actionSequence));
827
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.SWING_ARM, (0, play_js_1.buildSwingArmPacket)(hand));
1081
+ this.sendPlayPacket(this.playServerIds.blockPlace, (0, play_js_1.buildUseItemOnPacket)(hand, x, y, z, face, 0.5, 0.5, 0.5, false, false, this._actionSequence, this.options.version), 'block_place');
1082
+ this.sendPlayPacket(this.playServerIds.armAnimation, (0, play_js_1.buildSwingArmPacket)(hand, this.options.version), 'arm_animation');
828
1083
  }
829
1084
  /**
830
1085
  * Use the item in hand (eat, throw, shoot bow, etc.).
831
1086
  */
832
1087
  useItem(hand = 0) {
833
1088
  this._actionSequence++;
834
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.USE_ITEM, (0, play_js_1.buildUseItemPacket)(hand, this._actionSequence, this.entity.yaw, this.entity.pitch));
1089
+ this.sendPlayPacket(this.playServerIds.useItem, (0, play_js_1.buildUseItemPacket)(hand, this._actionSequence, this.entity.yaw, this.entity.pitch, this.options.version), 'use_item');
835
1090
  }
836
1091
  /**
837
1092
  * Select a hotbar slot (0-8).
@@ -839,52 +1094,57 @@ class Bot extends events_js_1.TypedEventEmitter {
839
1094
  setHeldItem(slot) {
840
1095
  if (slot < 0 || slot > 8)
841
1096
  throw new Error('Slot must be 0-8');
842
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.SET_HELD_ITEM, (0, play_js_1.buildSetHeldItemPacket)(slot));
1097
+ this.sendPlayPacket(this.playServerIds.heldItemSlot, (0, play_js_1.buildSetHeldItemPacket)(slot), 'held_item_slot');
843
1098
  }
844
1099
  /**
845
1100
  * Swing arm animation.
846
1101
  */
847
1102
  swingArm(hand = 0) {
848
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.SWING_ARM, (0, play_js_1.buildSwingArmPacket)(hand));
1103
+ this.sendPlayPacket(this.playServerIds.armAnimation, (0, play_js_1.buildSwingArmPacket)(hand, this.options.version), 'arm_animation');
849
1104
  }
850
1105
  /**
851
1106
  * Drop the held item.
852
1107
  */
853
1108
  dropItem(dropStack = false) {
854
1109
  this._actionSequence++;
855
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.PLAYER_ACTION, (0, play_js_1.buildPlayerActionPacket)(dropStack ? play_js_1.PlayerActionStatus.DROP_ITEM_STACK : play_js_1.PlayerActionStatus.DROP_ITEM, 0, 0, 0, 0, this._actionSequence));
1110
+ this.sendPlayPacket(this.playServerIds.blockDig, (0, play_js_1.buildPlayerActionPacket)(dropStack ? play_js_1.PlayerActionStatus.DROP_ITEM_STACK : play_js_1.PlayerActionStatus.DROP_ITEM, 0, 0, 0, 0, this._actionSequence, this.options.version), 'block_dig');
856
1111
  }
857
1112
  /**
858
1113
  * Swap items between main and off hand.
859
1114
  */
860
1115
  swapHands() {
861
1116
  this._actionSequence++;
862
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.PLAYER_ACTION, (0, play_js_1.buildPlayerActionPacket)(play_js_1.PlayerActionStatus.SWAP_ITEM_IN_HAND, 0, 0, 0, 0, this._actionSequence));
1117
+ this.sendPlayPacket(this.playServerIds.blockDig, (0, play_js_1.buildPlayerActionPacket)(play_js_1.PlayerActionStatus.SWAP_ITEM_IN_HAND, 0, 0, 0, 0, this._actionSequence, this.options.version), 'block_dig');
863
1118
  }
864
1119
  /**
865
1120
  * Toggle flying (creative/spectator mode).
866
1121
  */
867
1122
  fly(enabled) {
868
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.PLAYER_ABILITIES, (0, play_js_1.buildPlayerAbilitiesServerboundPacket)(enabled));
1123
+ this.sendPlayPacket(this.playServerIds.abilities, (0, play_js_1.buildPlayerAbilitiesServerboundPacket)(enabled, this.options.version), 'abilities');
869
1124
  }
870
1125
  /**
871
1126
  * Respawn after death.
872
1127
  */
873
1128
  respawn() {
874
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.CLIENT_STATUS, (0, play_js_1.buildClientStatusPacket)(play_js_1.ClientStatusAction.PERFORM_RESPAWN));
1129
+ this.sendPlayPacket(this.playServerIds.clientCommand, (0, play_js_1.buildClientStatusPacket)(play_js_1.ClientStatusAction.PERFORM_RESPAWN), 'client_command');
875
1130
  this._isAlive = true;
876
1131
  }
877
1132
  /**
878
1133
  * Close the currently open container.
879
1134
  */
880
1135
  closeContainer(windowId = 0) {
881
- this.connection.sendPacket(play_js_1.PlayServerboundPackets.CLOSE_CONTAINER, (0, play_js_1.buildCloseContainerPacket)(windowId));
1136
+ this.sendPlayPacket(this.playServerIds.closeWindow, (0, play_js_1.buildCloseContainerPacket)(windowId), 'close_window');
882
1137
  }
883
1138
  /**
884
1139
  * Disconnect from the server.
885
1140
  */
886
1141
  disconnect() {
887
1142
  this.stopPhysicsTick();
1143
+ if (this.playPacketListener) {
1144
+ this.connection.off('packet', this.playPacketListener);
1145
+ this.playPacketListener = null;
1146
+ }
1147
+ this.packetHandlers.clear();
888
1148
  this.plugins.destroyAll();
889
1149
  this.connection.disconnect();
890
1150
  this.logger.info('Disconnected');
@@ -922,7 +1182,7 @@ exports.Bot = Bot;
922
1182
  *
923
1183
  * @example
924
1184
  * ```ts
925
- * import { createBot } from 'mcbot';
1185
+ * import { createBot } from 'specprotocol';
926
1186
  *
927
1187
  * const bot = await createBot({
928
1188
  * host: 'localhost',