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.
- package/README.md +66 -174
- package/dist/bot.d.ts +17 -3
- package/dist/bot.d.ts.map +1 -1
- package/dist/bot.js +413 -153
- package/dist/bot.js.map +1 -1
- package/dist/chat.js +1 -1
- package/dist/chat.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/inventory.d.ts.map +1 -1
- package/dist/inventory.js +4 -1
- package/dist/inventory.js.map +1 -1
- package/dist/plugins/built-in/chat.d.ts +0 -4
- package/dist/plugins/built-in/chat.d.ts.map +1 -1
- package/dist/plugins/built-in/chat.js +0 -40
- package/dist/plugins/built-in/chat.js.map +1 -1
- package/dist/protocol/compression.d.ts +3 -1
- package/dist/protocol/compression.d.ts.map +1 -1
- package/dist/protocol/compression.js +16 -3
- package/dist/protocol/compression.js.map +1 -1
- package/dist/protocol/connection.d.ts.map +1 -1
- package/dist/protocol/connection.js +27 -6
- package/dist/protocol/connection.js.map +1 -1
- package/dist/protocol/packet.d.ts +3 -0
- package/dist/protocol/packet.d.ts.map +1 -1
- package/dist/protocol/packet.js +28 -4
- package/dist/protocol/packet.js.map +1 -1
- package/dist/protocol/states/handshake.d.ts +2 -2
- package/dist/protocol/states/handshake.d.ts.map +1 -1
- package/dist/protocol/states/handshake.js +3 -2
- package/dist/protocol/states/handshake.js.map +1 -1
- package/dist/protocol/states/login.d.ts +3 -3
- package/dist/protocol/states/login.d.ts.map +1 -1
- package/dist/protocol/states/login.js +27 -9
- package/dist/protocol/states/login.js.map +1 -1
- package/dist/protocol/states/play.d.ts +20 -14
- package/dist/protocol/states/play.d.ts.map +1 -1
- package/dist/protocol/states/play.js +279 -61
- package/dist/protocol/states/play.js.map +1 -1
- package/dist/protocol/types.d.ts.map +1 -1
- package/dist/protocol/types.js +12 -0
- package/dist/protocol/types.js.map +1 -1
- package/dist/protocol/version.d.ts +23 -0
- package/dist/protocol/version.d.ts.map +1 -0
- package/dist/protocol/version.js +137 -0
- package/dist/protocol/version.js.map +1 -0
- package/docs/README.md +1 -1
- package/docs/api/protocol.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/guides/architecture.md +1 -1
- 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
|
|
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:
|
|
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
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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(
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
|
213
|
-
this.handleEncryptionRequest(packet.data);
|
|
354
|
+
case this.loginClientIds.encryptionBegin: {
|
|
355
|
+
await this.handleEncryptionRequest(packet.data);
|
|
214
356
|
break;
|
|
215
357
|
}
|
|
216
|
-
case
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
}
|
|
236
|
-
this.logger.error('Configuration state failed:', err);
|
|
237
|
-
});
|
|
390
|
+
}
|
|
238
391
|
break;
|
|
239
392
|
}
|
|
240
|
-
case
|
|
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
|
-
|
|
397
|
+
throw new Error(`Disconnected during login: ${reason}`);
|
|
245
398
|
}
|
|
246
|
-
case
|
|
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.
|
|
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
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
316
|
-
this.connection.sendPacket(
|
|
317
|
-
|
|
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
|
|
481
|
+
case this.configurationClientIds.keepAlive: {
|
|
323
482
|
// Keep Alive (clientbound) — must respond or get kicked
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
519
|
+
case this.configurationClientIds.ping: {
|
|
363
520
|
// Ping — respond with pong
|
|
364
521
|
this.logger.debug('Config: Ping received');
|
|
365
|
-
this.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
424
|
-
|
|
425
|
-
|
|
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
|
|
592
|
+
case this.playClientIds.chunkBatchFinished: {
|
|
429
593
|
// Must respond with chunk_batch_received or server kicks
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
893
|
+
case this.playClientIds.systemChat: {
|
|
669
894
|
try {
|
|
670
895
|
const chat = (0, play_js_1.parseSystemChat)(packet.data);
|
|
671
|
-
this.
|
|
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.
|
|
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
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
754
|
-
this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
827
|
-
this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 '
|
|
1185
|
+
* import { createBot } from 'specprotocol';
|
|
926
1186
|
*
|
|
927
1187
|
* const bot = await createBot({
|
|
928
1188
|
* host: 'localhost',
|