@vaitosoi/minejs 0.2.1 → 0.3.1

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 (118) hide show
  1. package/assets/minecraft/26.2/packets.json +4741 -0
  2. package/dist/base/bitset.d.ts +9 -0
  3. package/dist/base/bitset.d.ts.map +1 -0
  4. package/dist/base/bitset.js +46 -0
  5. package/dist/base/bitset.js.map +1 -0
  6. package/dist/base/error.d.ts +105 -0
  7. package/dist/base/error.d.ts.map +1 -0
  8. package/dist/base/error.js +194 -0
  9. package/dist/base/error.js.map +1 -0
  10. package/dist/base/event.d.ts +7 -0
  11. package/dist/base/event.d.ts.map +1 -0
  12. package/dist/base/event.js +3 -0
  13. package/dist/base/event.js.map +1 -0
  14. package/dist/base/math.d.ts +25 -0
  15. package/dist/base/math.d.ts.map +1 -0
  16. package/dist/base/math.js +69 -0
  17. package/dist/base/math.js.map +1 -0
  18. package/dist/base/typing.d.ts +9 -0
  19. package/dist/base/typing.d.ts.map +1 -0
  20. package/dist/base/typing.js +3 -0
  21. package/dist/base/typing.js.map +1 -0
  22. package/dist/client/auth.d.ts +39 -0
  23. package/dist/client/auth.d.ts.map +1 -0
  24. package/dist/client/auth.js +307 -0
  25. package/dist/client/auth.js.map +1 -0
  26. package/dist/client/client.d.ts +73 -0
  27. package/dist/client/client.d.ts.map +1 -0
  28. package/dist/client/client.js +136 -0
  29. package/dist/client/client.js.map +1 -0
  30. package/dist/client/static.d.ts +28 -0
  31. package/dist/client/static.d.ts.map +1 -0
  32. package/dist/client/static.js +43 -0
  33. package/dist/client/static.js.map +1 -0
  34. package/dist/client/tcp.d.ts +84 -0
  35. package/dist/client/tcp.d.ts.map +1 -0
  36. package/dist/client/tcp.js +173 -0
  37. package/dist/client/tcp.js.map +1 -0
  38. package/dist/index.d.ts +21 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +12 -12
  41. package/dist/index.js.map +1 -0
  42. package/dist/packet/decoder.d.ts +293 -0
  43. package/dist/packet/decoder.d.ts.map +1 -0
  44. package/dist/packet/decoder.js +668 -0
  45. package/dist/packet/decoder.js.map +1 -0
  46. package/dist/packet/dispatcher.d.ts +46 -0
  47. package/dist/packet/dispatcher.d.ts.map +1 -0
  48. package/dist/packet/dispatcher.js +626 -0
  49. package/dist/packet/dispatcher.js.map +1 -0
  50. package/dist/packet/encoder.d.ts +163 -0
  51. package/dist/packet/encoder.d.ts.map +1 -0
  52. package/dist/packet/encoder.js +441 -0
  53. package/dist/packet/encoder.js.map +1 -0
  54. package/dist/packet/static.d.ts +33 -0
  55. package/dist/packet/static.d.ts.map +1 -0
  56. package/dist/packet/static.js +42 -0
  57. package/dist/packet/static.js.map +1 -0
  58. package/dist/physics/aabb.d.ts +197 -0
  59. package/dist/physics/aabb.d.ts.map +1 -0
  60. package/dist/physics/aabb.js +576 -0
  61. package/dist/physics/aabb.js.map +1 -0
  62. package/dist/physics/direction.d.ts +153 -0
  63. package/dist/physics/direction.d.ts.map +1 -0
  64. package/dist/physics/direction.js +288 -0
  65. package/dist/physics/direction.js.map +1 -0
  66. package/dist/physics/player.d.ts +110 -0
  67. package/dist/physics/player.d.ts.map +1 -0
  68. package/dist/physics/player.js +558 -0
  69. package/dist/physics/player.js.map +1 -0
  70. package/dist/version/codec.d.ts +18 -0
  71. package/dist/version/codec.d.ts.map +1 -0
  72. package/dist/version/codec.js +214 -0
  73. package/dist/version/codec.js.map +1 -0
  74. package/dist/version/registry.d.ts +236 -0
  75. package/dist/version/registry.d.ts.map +1 -0
  76. package/dist/version/registry.js +289 -0
  77. package/dist/version/registry.js.map +1 -0
  78. package/dist/world/block.d.ts +86 -0
  79. package/dist/world/block.d.ts.map +1 -0
  80. package/dist/world/block.js +232 -0
  81. package/dist/world/block.js.map +1 -0
  82. package/dist/world/entity.d.ts +43 -0
  83. package/dist/world/entity.d.ts.map +1 -0
  84. package/dist/world/entity.js +97 -0
  85. package/dist/world/entity.js.map +1 -0
  86. package/dist/world/state.d.ts +151 -0
  87. package/dist/world/state.d.ts.map +1 -0
  88. package/dist/world/state.js +108 -0
  89. package/dist/world/state.js.map +1 -0
  90. package/dist/world/tick.d.ts +19 -0
  91. package/dist/world/tick.d.ts.map +1 -0
  92. package/dist/world/tick.js +49 -0
  93. package/dist/world/tick.js.map +1 -0
  94. package/package.json +7 -2
  95. package/src/base/error.ts +36 -0
  96. package/src/base/typing.ts +9 -1
  97. package/src/client/client.ts +99 -42
  98. package/src/client/tcp.ts +49 -954
  99. package/src/index.ts +12 -12
  100. package/src/{translator → packet}/decoder.ts +3 -3
  101. package/src/packet/dispatcher.ts +785 -0
  102. package/src/{translator → packet}/encoder.ts +20 -9
  103. package/src/{base → physics}/aabb.ts +5 -5
  104. package/src/{base → physics}/player.ts +41 -25
  105. package/src/version/codec.ts +227 -0
  106. package/src/version/registry.ts +333 -0
  107. package/src/{base → world}/block.ts +12 -11
  108. package/src/{base → world}/entity.ts +5 -5
  109. package/src/world/state.ts +232 -0
  110. package/test/adapter/index.ts +14 -0
  111. package/test/client/index.ts +1 -1
  112. package/test/json/index.ts +3 -0
  113. package/src/base/registry.ts +0 -87
  114. /package/assets/minecraft/{blocks.json → 26.2/blocks.json} +0 -0
  115. /package/assets/minecraft/{entities.json → 26.2/entities.json} +0 -0
  116. /package/src/{translator → packet}/static.ts +0 -0
  117. /package/src/{base → physics}/direction.ts +0 -0
  118. /package/src/{base → world}/tick.ts +0 -0
@@ -0,0 +1,785 @@
1
+ import zod from "zod";
2
+ import { createCipheriv, createDecipheriv, createPublicKey, publicEncrypt, randomBytes, constants as crypto_constants } from "node:crypto";
3
+ import { BlockEntity, ChunkSection, ClientPlayer, ClientStatus, ConnectionState, Entity, GameMode, PaletteContainer, ServerKnownPack, ServerWorld, SharedState } from "../world/state";
4
+ import { BinaryDecoder, getTextFromTextComponent } from "./decoder";
5
+ import { packBlockPos, SectionsPerChunk } from "../client/static";
6
+ import { Vec3 } from "../physics/direction";
7
+ import { ProtocolVersionMapping } from "../version/registry";
8
+ import { TypedEmmiter } from "../base/event";
9
+ import { ClientEvents } from "../client/client";
10
+ import { makeMovementFlag } from "./encoder";
11
+
12
+ function zodParse<Type extends zod.ZodType>(data: object, zod: Type): zod.infer<Type> {
13
+ return zod.parse(data);
14
+ }
15
+
16
+ export class Dispatcher {
17
+ private readonly mapping: Record<string, (data: object) => void>;
18
+
19
+ constructor(
20
+ private state: SharedState,
21
+ private emit: TypedEmmiter<ClientEvents>["emit"]
22
+ ) {
23
+ this.mapping = {
24
+ "login:login_disconnect": this.handleLoginDisconnect,
25
+ "login:hello": this.handleEncryption,
26
+ "login:login_finished": this.handleLoginSucess,
27
+ "login:login_compression": this.handleSetCompression,
28
+ "configuration:disconnect": this.handleConfiguarionPlayDisconnect,
29
+ "configuration:keep_alive": this.handleKeepAlive,
30
+ "configuration:registry_data": this.handleRegistryData,
31
+ "configuration:select_known_packs": this.handleKnownPack,
32
+ "configuration:finish_configuration": this.handleFinishConfiguration,
33
+ "play:add_entity": this.handleSpawnEntity,
34
+ "play:disconnect": this.handleConfiguarionPlayDisconnect,
35
+ "play:disguised_chat": this.handleDisguisedChatMessage,
36
+ "play:entity_position_sync": this.handleTeleportEntity,
37
+ "play:forget_level_chunk": this.handleUnloadChunk,
38
+ "play:keep_alive": this.handleKeepAlive,
39
+ "play:level_chunk_with_light": this.handleChunkData,
40
+ "play:login": this.handlePlayLogin,
41
+ "play:move_entity_pos": this.handleUpdateEntityPosition,
42
+ "play:move_entity_pos_rot": this.handleUpdateEntityPositionRotation,
43
+ "play:player_chat": this.handlePlayerChat,
44
+ "play:player_position": this.handleSynchronizePlayerPosition,
45
+ "play:remove_entities": this.handleRemoveEntity,
46
+ // "play:rotate_head":
47
+ "play:set_action_bar_text": this.handleSetActionBar,
48
+ "play:set_entity_motion": this.handleSetEntityVelocity,
49
+ "play:system_chat": this.handleSystemMessage,
50
+ };
51
+ }
52
+
53
+ public sendPacket: (packetId: string, data: object) => void = (...args) => { throw new Error("method not implemented"); };
54
+ public tcpDisconnect: () => void = () => { throw new Error("method not implemented"); };
55
+
56
+
57
+ public handlePacket(packetId: string, data: object) {
58
+ if (packetId in this.mapping)
59
+ this.mapping[packetId]!.bind(this)(data);
60
+ }
61
+
62
+ // Login
63
+
64
+ private handleLoginDisconnect(data: object) {
65
+ const { reason } = zodParse(data, zod.object({ reason: zod.record(zod.string(), zod.any()) }));
66
+ const text = getTextFromTextComponent(reason);
67
+
68
+ this.state.enqueueMutation((state) => {
69
+ state.status = ClientStatus.Disconnected;
70
+ state.state = ConnectionState.Disconnected;
71
+ state.pruneStates();
72
+ });
73
+
74
+ this.emit("disconnect", text.toString());
75
+ this.emit("disconnectRaw", reason);
76
+ this.tcpDisconnect();
77
+ }
78
+
79
+ private handleEncryption(data: object) {
80
+ const {
81
+ server_id,
82
+ public_key,
83
+ verify_token,
84
+ shoudl_call_mojang
85
+ } = zodParse(data, zod.object({
86
+ server_id: zod.string(),
87
+ public_key: zod.array(zod.number()),
88
+ verify_token: zod.array(zod.number()),
89
+ shoudl_call_mojang: zod.boolean()
90
+ }));
91
+
92
+ const sharedSecret = randomBytes(16);
93
+ this.state.enqueueMutation((state) => {
94
+ state.sharedSecret = sharedSecret;
95
+ state.cipher = createCipheriv("aes-128-cfb8", state.sharedSecret, state.sharedSecret);
96
+ state.decipher = createDecipheriv("aes-128-cfb8", state.sharedSecret, state.sharedSecret);
97
+ });
98
+
99
+ this.sendEncryptionResponse(Buffer.from(public_key), Buffer.from(verify_token));
100
+ }
101
+
102
+ private handleSetCompression(data: object) {
103
+ const { threshold } = zodParse(data, zod.object({ threshold: zod.number() }));
104
+ if (threshold < 0) this.state.compressionThreshold = -1;
105
+ else this.state.compressionThreshold = threshold;
106
+ }
107
+
108
+ private handleLoginSucess(data: object) {
109
+ const { profile: { uuid, username } } = zodParse(data,
110
+ zod.object({
111
+ profile: zod.object({ uuid: zod.string(), username: zod.string() }),
112
+ session_id: zod.string()
113
+ }));
114
+ this.state.player = {
115
+ uuid,
116
+ username,
117
+ entityId: 0,
118
+ dimension: "",
119
+ gameMode: GameMode.Survival,
120
+
121
+ position: {
122
+ x: 0,
123
+ y: 0,
124
+ z: 0,
125
+ },
126
+ velocity: {
127
+ x: 0,
128
+ y: 0,
129
+ z: 0,
130
+ },
131
+ angle: {
132
+ yaw: 0,
133
+ pitch: 0,
134
+ }
135
+ } satisfies ClientPlayer as any;
136
+ this.sendLoginAck();
137
+ }
138
+
139
+ // Configure
140
+
141
+ private handleKnownPack(data: object) {
142
+ const { known_packs } = zodParse(data, zod.object({
143
+ known_packs: zod.array(zod.object({
144
+ namespace: zod.string(),
145
+ id: zod.string(),
146
+ version: zod.string(),
147
+ }))
148
+ }));
149
+ this.state.server!.knownPacks = known_packs satisfies ServerKnownPack[] as any;
150
+ this.sendKnownPack(this.state.clientOptions.loadRegistry === true ? [] : known_packs);
151
+ // this.sendKnownPack([]);
152
+ }
153
+
154
+ private handleRegistryData(data: object) {
155
+ const { registry_id, entries } = zodParse(data,
156
+ zod.object({
157
+ registry_id: zod.string(),
158
+ entries: zod.array(zod.object({
159
+ entry_id: zod.string(),
160
+ data: zod.record(zod.string(), zod.any()).nullable()
161
+ }))
162
+ })
163
+ );
164
+
165
+ if (!this.state.registry) this.state.registry = {} as any;
166
+ const registry = this.state.registry as Record<string, any[]>;
167
+ if (!(registry_id in registry)) registry[registry_id] = [];
168
+ registry[registry_id]!.push(...entries);
169
+ }
170
+
171
+ private handleFinishConfiguration(data: object) {
172
+ this.sendConfigureAck();
173
+ }
174
+
175
+ private handleConfiguarionPlayDisconnect(data: object) {
176
+ const { reason } = zodParse(data, zod.object({ reason: zod.record(zod.string(), zod.any()) }));
177
+ const text = getTextFromTextComponent(reason);
178
+
179
+ this.state.status = ClientStatus.Disconnected;
180
+ this.state.state = ConnectionState.Disconnected;
181
+ this.state.pruneStates();
182
+
183
+ this.emit("disconnect", text.toString());
184
+ this.emit("disconnectRaw", reason);
185
+ this.tcpDisconnect();
186
+ }
187
+
188
+ // Play
189
+
190
+ private handlePlayLogin(data: object) {
191
+ const {
192
+ entity_id,
193
+ is_hardcore,
194
+ dimension_names,
195
+ max_players,
196
+ view_distance,
197
+ simulation_distance,
198
+ dimension_name,
199
+ game_mode
200
+ } = zodParse(data,
201
+ zod.object({
202
+ entity_id: zod.int(),
203
+ is_hardcore: zod.boolean(),
204
+ dimension_names: zod.array(zod.string()),
205
+ max_players: zod.int(),
206
+ view_distance: zod.int(),
207
+ simulation_distance: zod.int(),
208
+ dimension_name: zod.string(),
209
+ game_mode: zod.int()
210
+ })
211
+ );
212
+
213
+
214
+ this.state.world = {
215
+ hardcore: is_hardcore,
216
+ dimensions: dimension_names,
217
+ maxPlayers: max_players,
218
+ viewDistance: view_distance,
219
+ simulationDistance: simulation_distance,
220
+ chunks: {},
221
+ entities: {}
222
+ } satisfies ServerWorld as any; // To avoid type conflict
223
+ this.state.player!.dimension = dimension_name;
224
+ this.state.player!.entityId = entity_id;
225
+ this.state.player!.gameMode = game_mode;
226
+ this.state.status = ClientStatus.Ready;
227
+
228
+ this.emit("ready");
229
+ }
230
+
231
+ private handleSynchronizePlayerPosition(data: object) {
232
+ const {
233
+ teleport_id,
234
+ x,
235
+ y,
236
+ z,
237
+ velocity_x,
238
+ velocity_y,
239
+ velocity_z,
240
+ yaw,
241
+ pitch,
242
+ flags
243
+ } = zodParse(data,
244
+ zod.object({
245
+ teleport_id: zod.int(),
246
+ x: zod.number(),
247
+ y: zod.number(),
248
+ z: zod.number(),
249
+ velocity_x: zod.number(),
250
+ velocity_y: zod.number(),
251
+ velocity_z: zod.number(),
252
+ yaw: zod.number(),
253
+ pitch: zod.number(),
254
+ flags: zod.object({
255
+ x: zod.boolean(),
256
+ y: zod.boolean(),
257
+ z: zod.boolean(),
258
+ yaw: zod.boolean(),
259
+ pitch: zod.boolean(),
260
+ velX: zod.boolean(),
261
+ velY: zod.boolean(),
262
+ velZ: zod.boolean(),
263
+ rotateVelocity: zod.boolean(),
264
+ }),
265
+ })
266
+ );
267
+
268
+ const { position, velocity, angle } = this.state.player!;
269
+ let { x: newPosX, y: newPosY, z: newPosZ } = position;
270
+ let { x: newVelX, y: newVelY, z: newVelZ } = velocity;
271
+ let { yaw: newYaw, pitch: newPitch } = angle;
272
+ if (flags.x) newPosX += x; else newPosX = x;
273
+ if (flags.y) newPosY += y; else newPosY = y;
274
+ if (flags.z) newPosZ += z; else newPosZ = z;
275
+ if (flags.velX) newVelX += velocity_x; else newVelX = velocity_x;
276
+ if (flags.velY) newVelY += velocity_y; else newVelY = velocity_y;
277
+ if (flags.velZ) newVelZ += velocity_z; else newVelZ = velocity_z;
278
+ if (flags.yaw) newYaw += yaw; else newYaw = yaw;
279
+ if (flags.pitch) newPitch += pitch; else newPitch = pitch;
280
+ const newPosition = { x: newPosX, y: newPosY, z: newPosZ },
281
+ newVelocity = { x: newVelX, y: newVelY, z: newVelZ },
282
+ newAngle = { yaw: newYaw, pitch: newPitch };
283
+ this.state.enqueueMutation(state =>
284
+ state.player = {
285
+ ...state.player!,
286
+ position: newPosition,
287
+ velocity: newVelocity,
288
+ angle: newAngle,
289
+ } satisfies ClientPlayer as any
290
+ );
291
+ this.state.enqueueEvent("playerPosition", { x: newPosX, y: newPosY, z: newPosZ });
292
+ this.sendConfirmTeleportation(teleport_id);
293
+ this.sendPacket("move_player_pos_rot", {
294
+ x: newPosition.x,
295
+ feet_y: newPosition.y,
296
+ z: newPosition.z,
297
+ yaw: newAngle.yaw,
298
+ pitch: newAngle.pitch,
299
+ flags: makeMovementFlag(true, false)
300
+ });
301
+ }
302
+
303
+ private handleKeepAlive(data: object) {
304
+ const { keep_alive_id } = zodParse(data,
305
+ zod.object({
306
+ keep_alive_id: zod.bigint().or(zod.number())
307
+ })
308
+ );
309
+ this.sendKeepAlive(BigInt(keep_alive_id));
310
+ }
311
+
312
+ private handleChangeGameMode(data: object) {
313
+ const { gameMode } = zodParse(data, zod.object({ gameMode: zod.number() }));
314
+ this.state.enqueueMutation(state => state.player!.gameMode = gameMode);
315
+ }
316
+
317
+ // Chunk
318
+ private handleChunkData(data_: object) {
319
+ const {
320
+ chunk_x,
321
+ chunk_z,
322
+ heightmaps,
323
+ data,
324
+ block_entities,
325
+ light,
326
+ } = zodParse(data_,
327
+ zod.object({
328
+ chunk_x: zod.int(),
329
+ chunk_z: zod.int(),
330
+ heightmaps: zod.array(zod.any()),
331
+ data: zod.array(zod.int()),
332
+ block_entities: zod.array(zod.object({
333
+ packed_xz: zod.int(),
334
+ y: zod.int(),
335
+ type: zod.int(),
336
+ data: zod.record(zod.string(), zod.any())
337
+ })),
338
+ light: zod.any(),
339
+ })
340
+ );
341
+
342
+ const chunkSections: Record<number, ChunkSection> = {};
343
+
344
+ const chunkDataDecoder = new BinaryDecoder(Buffer.from(data));
345
+ for (let height = 0; height < (SectionsPerChunk[this.state.player!.dimension] || 0); height++) {
346
+ const blockCount = chunkDataDecoder.readShort();
347
+ const fluidCount = chunkDataDecoder.readShort();
348
+
349
+ // Blocks
350
+ let blockStateBPE = chunkDataDecoder.readUByte();
351
+ let blockStatePalettes: number[] = [];
352
+
353
+ if (blockStateBPE === 0) {
354
+ blockStatePalettes = [chunkDataDecoder.readVarInt()];
355
+ } else if (blockStateBPE <= 8) {
356
+ // Indirect palette
357
+ if (blockStateBPE < 4) blockStateBPE = 4; // BPE smaller than 4 will be rounđe up to 4
358
+ blockStatePalettes = chunkDataDecoder.readPrefixedArray(decoder => decoder.readVarInt());
359
+ } else {
360
+ // Direct palette (no palette array)
361
+ }
362
+
363
+ const blockEntriesPerSection = 4096;
364
+ const blockEntriesPerLong = Math.floor(64 / blockStateBPE);
365
+ let blockDataArray: BigInt64Array | null = null;
366
+ if (blockStateBPE !== 0) {
367
+ const blockDataArrayLength = Math.floor((blockEntriesPerSection + blockEntriesPerLong - 1) / blockEntriesPerLong);
368
+ blockDataArray = new BigInt64Array(chunkDataDecoder.readArray(blockDataArrayLength, (decoder) => decoder.readLong()));
369
+ }
370
+
371
+ // Biomes - Not used yet, but need to be read to advance decoder
372
+ const biomeBPE = chunkDataDecoder.readUByte();
373
+ let biomePalettes: number[] = [];
374
+ if (biomeBPE === 0) {
375
+ biomePalettes = [chunkDataDecoder.readVarInt()]; // Palette value
376
+ } else if (biomeBPE <= 3) {
377
+ // Indirect palette
378
+ biomePalettes = chunkDataDecoder.readPrefixedArray(decoder => decoder.readVarInt());
379
+ } else {
380
+ // Direct palette (no palette array)
381
+ }
382
+
383
+ const biomeEntriesPerSection = 64;
384
+ const biomeEntriesPerLong = Math.floor(64 / biomeBPE);
385
+ let biomeDataArray: BigInt64Array | null = null;
386
+ if (biomeBPE !== 0) {
387
+ const biomeDataArrayLength = Math.floor((biomeEntriesPerSection + biomeEntriesPerLong - 1) / biomeEntriesPerLong);
388
+ biomeDataArray = new BigInt64Array(chunkDataDecoder.readArray(biomeDataArrayLength, (decoder) => decoder.readLong()));
389
+ }
390
+
391
+ chunkSections[height] = {
392
+ block: {
393
+ bpe: blockStateBPE,
394
+ palette: blockStatePalettes,
395
+ data: blockDataArray
396
+ },
397
+ biome: {
398
+ bpe: biomeBPE,
399
+ palette: biomePalettes,
400
+ data: biomeDataArray
401
+ }
402
+ };
403
+
404
+ this.state.enqueueEvent("loadChunk", chunk_x, height, chunk_z);
405
+ }
406
+
407
+ const blockEntitiesObj: Record<number, BlockEntity> = Object.fromEntries(
408
+ block_entities.map(val => [val.packed_xz, { type: val.type, data: val.data }])
409
+ );
410
+
411
+
412
+ this.state.enqueueMutation(state =>
413
+ state.world!.chunks[`${state.player!.dimension}:${chunk_x}:${chunk_z}`] = {
414
+ sections: chunkSections,
415
+ blockEntities: blockEntitiesObj
416
+ });
417
+ }
418
+
419
+ private handleUnloadChunk(data: object) {
420
+ const {
421
+ chunk_x,
422
+ chunk_z
423
+ } = zodParse(data, zod.object({
424
+ chunk_x: zod.number(),
425
+ chunk_z: zod.number()
426
+ }));
427
+ this.state.enqueueMutation(state => delete state.world!.chunks[`${state.player!.dimension}:${chunk_x}:${chunk_z}`]);
428
+ this.state.enqueueEvent("unloadChunk", chunk_x, chunk_z);
429
+ }
430
+
431
+ private handleBlockEntityData(data_: object) {
432
+ const {
433
+ location,
434
+ type,
435
+ data
436
+ } = zodParse(data_, zod.object({
437
+ location: zod.object({ x: zod.number(), y: zod.number(), z: zod.number() }),
438
+ type: zod.int(),
439
+ data: zod.record(zod.string(), zod.any())
440
+ }));
441
+
442
+ const chunkX = Math.floor(location.x / 16),
443
+ chunkZ = Math.floor(location.z / 16);
444
+ const xWitinChunk = location.x % 16,
445
+ zWitinChunk = location.z % 16;
446
+ const packedPosition = packBlockPos(xWitinChunk, location.y, zWitinChunk);
447
+ if (`${this.state.player!.dimension}:${chunkX}:${chunkZ}` in this.state.world!.chunks)
448
+ this.state.enqueueMutation(state =>
449
+ state.world!.chunks[`${state.player!.dimension}:${chunkX}:${chunkZ}`]!.blockEntities[packedPosition] = {
450
+ type,
451
+ data
452
+ }
453
+ );
454
+ }
455
+
456
+ // Entity
457
+ private handleSpawnEntity(data_: object) {
458
+ const {
459
+ entity_id,
460
+ entity_uuid,
461
+ type,
462
+ x,
463
+ y,
464
+ z,
465
+ velocity,
466
+ pitch,
467
+ yaw,
468
+ head_yaw,
469
+ data
470
+ } = zodParse(data_, zod.object({
471
+ entity_id: zod.int(),
472
+ entity_uuid: zod.string(),
473
+ type: zod.int(),
474
+ x: zod.number(),
475
+ y: zod.number(),
476
+ z: zod.number(),
477
+ velocity: zod.instanceof(Vec3),
478
+ pitch: zod.number(),
479
+ yaw: zod.number(),
480
+ head_yaw: zod.number(),
481
+ data: zod.int(),
482
+ }));
483
+
484
+ const entity: Entity = {
485
+ id: entity_id,
486
+ type,
487
+ position: { x, y, z },
488
+ velocity,
489
+ angle: {
490
+ pitch,
491
+ yaw
492
+ },
493
+ data
494
+ };
495
+ this.state.enqueueMutation(state => state.world!.entities[entity_id] = entity);
496
+ this.state.enqueueEvent("spawnEntity", entity);
497
+ }
498
+
499
+ private handleTeleportEntity(data: object) {
500
+ const {
501
+ entity_id,
502
+ x,
503
+ y,
504
+ z,
505
+ velocity_x,
506
+ velocity_y,
507
+ velocity_z,
508
+ pitch,
509
+ yaw
510
+ } = zodParse(data, zod.object({
511
+ entity_id: zod.int(),
512
+ x: zod.number(),
513
+ y: zod.number(),
514
+ z: zod.number(),
515
+ velocity_x: zod.number(),
516
+ velocity_y: zod.number(),
517
+ velocity_z: zod.number(),
518
+ pitch: zod.number(),
519
+ yaw: zod.number(),
520
+ }));
521
+
522
+ if (entity_id in this.state.world!.entities) this.state.enqueueMutation(state => {
523
+ state.world!.entities[entity_id]!.position = { x, y, z };
524
+ state.world!.entities[entity_id]!.velocity = { x: velocity_x, y: velocity_y, z: velocity_z };
525
+ state.world!.entities[entity_id]!.angle = { pitch, yaw };
526
+ });
527
+ this.state.enqueueEvent("updateEntity", entity_id);
528
+ }
529
+
530
+ private handleUpdateEntityPosition(data: object) {
531
+ const {
532
+ entity_id,
533
+ delta_x,
534
+ delta_y,
535
+ delta_z
536
+ } = zodParse(data, zod.object({
537
+ entity_id: zod.number(),
538
+ delta_x: zod.number(),
539
+ delta_y: zod.number(),
540
+ delta_z: zod.number(),
541
+ }));
542
+
543
+ if (entity_id in this.state.world!.entities) this.state.enqueueMutation(state => {
544
+ let { x, y, z } = state.world!.entities[entity_id]!.position;
545
+ x += delta_x;
546
+ y += delta_y;
547
+ z += delta_z;
548
+ state.world!.entities[entity_id]!.position = { x, y, z };
549
+ });
550
+ this.state.enqueueEvent("updateEntity", entity_id);
551
+ }
552
+
553
+ private handleUpdateEntityPositionRotation(data: object) {
554
+ const {
555
+ entity_id,
556
+ delta_x,
557
+ delta_y,
558
+ delta_z,
559
+ pitch,
560
+ yaw
561
+ } = zodParse(data, zod.object({
562
+ entity_id: zod.number(),
563
+ delta_x: zod.number(),
564
+ delta_y: zod.number(),
565
+ delta_z: zod.number(),
566
+ pitch: zod.number(),
567
+ yaw: zod.number(),
568
+ }));
569
+ if (entity_id in this.state.world!.entities) {
570
+ this.state.enqueueMutation(state => {
571
+ let { x, y, z } = state.world!.entities[entity_id]!.position;
572
+ x += delta_x;
573
+ y += delta_y;
574
+ z += delta_z;
575
+ state.world!.entities[entity_id]!.position = { x, y, z };
576
+ state.world!.entities[entity_id]!.angle = { yaw, pitch };
577
+ });
578
+ this.state.enqueueEvent("updateEntity", entity_id);
579
+ }
580
+ }
581
+
582
+ private handleSetEntityVelocity(data: object) {
583
+ const { entity_id, velocity } = zodParse(data, zod.object({
584
+ entity_id: zod.number(),
585
+ velocity: zod.instanceof(Vec3)
586
+ }));
587
+
588
+ if (entity_id in this.state.world!.entities)
589
+ this.state.enqueueMutation(state => state.world!.entities[entity_id]!.velocity = velocity);
590
+ }
591
+
592
+ private handleRemoveEntity(data: object) {
593
+ const { entity_ids } = zodParse(data, zod.object({ entity_ids: zod.array(zod.number()) }));
594
+
595
+ this.state.enqueueMutation(state => {
596
+ for (const entityId of entity_ids) {
597
+ delete state.world!.entities[entityId];
598
+ this.state.enqueueEvent("removeEntity", entityId);
599
+ }
600
+ });
601
+ }
602
+
603
+ // Chat
604
+ private handlePlayerChat(data: object) {
605
+ const {
606
+ global_index,
607
+ sender,
608
+ index,
609
+ message_signature_bytes,
610
+ message,
611
+ timestamp,
612
+ salt,
613
+ previous_messages,
614
+ unsigned_content,
615
+ filter_type,
616
+ filter_type_bits,
617
+ chat_type,
618
+ sender_name,
619
+ target_name
620
+ } = zodParse(data, zod.object({
621
+ global_index: zod.int(),
622
+ sender: zod.string(),
623
+ index: zod.int(),
624
+ message_signature_bytes: zod.array(zod.number()).nullable(),
625
+ message: zod.string(),
626
+ timestamp: zod.bigint(),
627
+ salt: zod.bigint(),
628
+ previous_messages: zod.array(zod.object({
629
+ message_id: zod.number(),
630
+ signature: zod.array(zod.number()).optional()
631
+ })),
632
+ unsigned_content: zod.record(zod.string(), zod.any()).nullable(),
633
+ filter_type: zod.int(),
634
+ filter_type_bits: zod.array(zod.bigint()).nullable(),
635
+ chat_type: zod.int().or(zod.record(zod.string(), zod.any())),
636
+ sender_name: zod.record(zod.string(), zod.any()),
637
+ target_name: zod.record(zod.string(), zod.any()).nullable()
638
+ }));
639
+
640
+ const senderNameText = getTextFromTextComponent(sender_name).toString(),
641
+ targetNameText = target_name ? getTextFromTextComponent(target_name).toString() : undefined;
642
+
643
+ this.emit("message", {
644
+ sender: senderNameText,
645
+ target: targetNameText,
646
+ content: message,
647
+ raw: {
648
+ sender: sender_name,
649
+ target: target_name || undefined,
650
+ content: unsigned_content || undefined,
651
+ }
652
+ });
653
+ }
654
+
655
+ private handleSystemMessage(data: object) {
656
+ const {
657
+ content,
658
+ overlay: isActionbar
659
+ } = zodParse(data, zod.object({
660
+ content: zod.record(zod.string(), zod.any()),
661
+ overlay: zod.boolean()
662
+ }));
663
+ const text = getTextFromTextComponent(content);
664
+
665
+ if (isActionbar) {
666
+ this.emit("actionBar", text.toString());
667
+ this.emit("actionBarRaw", content);
668
+ } else {
669
+ this.emit("systemMessage", text.toString());
670
+ this.emit("systemMessageRaw", content);
671
+ }
672
+ }
673
+
674
+ private handleSetActionBar(data: object) {
675
+ const { action_bar_text: content } = zodParse(data, zod.object({ action_bar_text: zod.record(zod.string(), zod.any()) }));
676
+ const text = getTextFromTextComponent(content);
677
+ this.emit("actionBar", text.toString());
678
+ this.emit("actionBarRaw", content);
679
+ }
680
+
681
+ private handleDisguisedChatMessage(data: object) {
682
+ const {
683
+ message,
684
+ chatType,
685
+ senderName,
686
+ targetName
687
+ } = zodParse(data, zod.object({
688
+ message: zod.record(zod.string(), zod.any()),
689
+ chatType: zod.string().or(zod.record(zod.string(), zod.any())),
690
+ senderName: zod.record(zod.string(), zod.any()),
691
+ targetName: zod.record(zod.string(), zod.any()).nullable(),
692
+ }));
693
+ const messageText = getTextFromTextComponent(message).toString(),
694
+ senderNameText = getTextFromTextComponent(senderName).toString(),
695
+ targetNameText = targetName ? getTextFromTextComponent(targetName).toString() : undefined;
696
+ this.emit("message", {
697
+ sender: senderNameText,
698
+ target: targetNameText,
699
+ content: messageText,
700
+ raw: {
701
+ sender: senderName,
702
+ target: targetName || undefined,
703
+ content: message,
704
+ }
705
+ });
706
+ }
707
+
708
+ /*
709
+ * Send packet
710
+ */
711
+
712
+ // Login
713
+
714
+ public sendHandshake() {
715
+ this.state.state = ConnectionState.Handshake;
716
+ this.sendPacket("intention",
717
+ {
718
+ protocol_version: ProtocolVersionMapping[this.state.clientOptions.version]!,
719
+ server_address: this.state.clientOptions.host,
720
+ server_port: this.state.clientOptions.port,
721
+ intent: 2
722
+ }
723
+ );
724
+ }
725
+
726
+ public sendLoginStart() {
727
+ this.state.state = ConnectionState.Login;
728
+ this.state.status = ClientStatus.Logining;
729
+ this.sendPacket("hello", {
730
+ name: this.state.clientOptions.playerName,
731
+ player_uuid: this.state.getUuid()
732
+ });
733
+ }
734
+
735
+ private sendEncryptionResponse(publicKey: Buffer, verifyToken: Buffer) {
736
+ const encryptedSecret = publicEncrypt({
737
+ key: createPublicKey({ key: publicKey, format: 'der', type: 'spki' }),
738
+ padding: crypto_constants.RSA_PKCS1_PADDING
739
+ }, this.state.sharedSecret!);
740
+ const encryptedVerifyToken = publicEncrypt({
741
+ key: createPublicKey({ key: publicKey, format: 'der', type: 'spki' }),
742
+ padding: crypto_constants.RSA_PKCS1_PADDING
743
+ }, verifyToken);
744
+ this.sendPacket("key", {
745
+ shared_secret: { value: encryptedSecret, length: 8 },
746
+ verify_token: { value: encryptedVerifyToken, length: 8 }
747
+ });
748
+ }
749
+
750
+ private sendLoginAck() {
751
+ this.sendPacket("login_acknowledged", {});
752
+ this.state.state = ConnectionState.Configure;
753
+ this.state.server = {};
754
+ }
755
+
756
+ // Configuring
757
+
758
+ private sendKnownPack(knownPacks: ServerKnownPack[]) {
759
+ this.sendPacket("select_known_packs", {
760
+ known_packs: {
761
+ value: knownPacks,
762
+ length: knownPacks.length
763
+ }
764
+ });
765
+ }
766
+
767
+ private sendConfigureAck() {
768
+ this.sendPacket("finish_configuration", {});
769
+ this.state.state = ConnectionState.Play;
770
+ }
771
+
772
+ // Play
773
+
774
+ private sendConfirmTeleportation(teleportId: number) {
775
+ this.sendPacket("accept_teleportation", {
776
+ teleport_id: teleportId
777
+ });
778
+ }
779
+
780
+ private sendKeepAlive(id: bigint) {
781
+ this.sendPacket("keep_alive", {
782
+ keep_alive_id: id
783
+ });
784
+ }
785
+ }