@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,333 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { RegistryItemNotFound, UnexpectedValue, VersionNotSupport } from "../base/error";
4
+ import { Block, BlockState } from "../world/block";
5
+ import z from "zod";
6
+
7
+ export const BASE_REGISTRY_PATH = join(__dirname, "..", "..", "assets", "minecraft");
8
+ export const SupportVersions = [
9
+ "26.2"
10
+ ];
11
+
12
+ export const ProtocolVersionMapping: Record<string, number> = {
13
+ "26.2": 776
14
+ };
15
+
16
+ export class EntityRegistry {
17
+ private static loaded: boolean = false;
18
+ public static readonly data: Record<string, { height: number, width: number, type: number }> = {};
19
+ public static readonly mapTypeToData: Record<number, string> = {};
20
+
21
+ /**
22
+ * Load entity registry from JSON file.
23
+ *
24
+ * Should be called once time
25
+ */
26
+ public static async load(version: string) {
27
+ if (!SupportVersions.includes(version))
28
+ throw new VersionNotSupport(version);
29
+ if (this.loaded) return;
30
+ this.loaded = true;
31
+
32
+ const file = await readFile(join(BASE_REGISTRY_PATH, version, "entities.json"), { encoding: "utf8" });
33
+ const json = JSON.parse(file);
34
+ for (const entity in json) {
35
+ this.data[entity] = json[entity];
36
+ this.mapTypeToData[json[entity]["id"]] = entity;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Get an entity registy item
42
+ */
43
+ public static get(entity: string | number) {
44
+ if (typeof entity === "string") {
45
+ return this.data[entity];
46
+ } else {
47
+ const key = this.mapTypeToData[entity];
48
+ return this.data[key!];
49
+ }
50
+ }
51
+ };
52
+
53
+ export class BlockRegistry {
54
+ private static loaded: boolean = false;
55
+ /**
56
+ * block id to block
57
+ */
58
+ public static readonly blocks: Record<string, Block> = {};
59
+ /**
60
+ * state id to block state
61
+ */
62
+ public static readonly states: Record<string, BlockState> = {};
63
+
64
+ /**
65
+ * Load block registry from JSON file.
66
+ *
67
+ * Should be called once time
68
+ */
69
+ public static async load(version: string) {
70
+ if (!SupportVersions.includes(version))
71
+ throw new VersionNotSupport(version);
72
+ if (this.loaded) return;
73
+ this.loaded = true;
74
+
75
+ const file = await readFile(join(BASE_REGISTRY_PATH, version, "blocks.json"), { encoding: "utf8" });
76
+ const json = JSON.parse(file) as Record<string, any>;
77
+ for (const [type, blockRaw] of Object.entries(json)) {
78
+ const block = new Block(type, blockRaw['definition'], blockRaw['properties'], blockRaw['states']);
79
+ this.blocks[type] = block;
80
+ for (const state of block.states)
81
+ this.states[state.id] = state;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Get a block registy item
87
+ */
88
+ public static getBlock(type: string) {
89
+ if (!(type in this.blocks))
90
+ throw new RegistryItemNotFound(`block ${type}`);
91
+ return this.blocks[type]!;
92
+ }
93
+
94
+ /**
95
+ * Get a block state registy item
96
+ */
97
+ public static getState(id: string) {
98
+ if (!(id in this.states))
99
+ throw new RegistryItemNotFound(`block state ${id}`);
100
+ return this.states[id]!;
101
+ }
102
+ };
103
+
104
+ export const SupportTypes = z.enum([
105
+ "byte",
106
+ "unsigned_byte",
107
+ "short",
108
+ "unsigned_short",
109
+ "int",
110
+ "long",
111
+ "float",
112
+ "double",
113
+ "boolean",
114
+ "var_int",
115
+ "var_long",
116
+ "string",
117
+ "uuid",
118
+ "position",
119
+ "array",
120
+ "prefixed_array",
121
+ "teleport_flag",
122
+ "prefixed_optional",
123
+ "nbt",
124
+ "lpvec3",
125
+ "fixed_point",
126
+ "angle",
127
+ "id_or_x",
128
+ "chat_type_decoration",
129
+ "chat_type",
130
+ "chunk_data",
131
+ "enum",
132
+ "json_text",
133
+ "not_implemented",
134
+ "varies",
135
+ "object",
136
+ "game_profile",
137
+ "heightmap",
138
+ "teleport_flags",
139
+ "bitset",
140
+ "null", // a constant
141
+
142
+ /**
143
+ * This is used for handle field that its type depends on previous field type, or value
144
+ */
145
+ "switch"
146
+ ]);
147
+ export type FieldNode = {
148
+ type: Exclude<z.infer<typeof SupportTypes>, "prefixed_array" | "prefixed_optional" | "string" | "id_or_x" | "array" | "switch" | "fixed_point" | "enum" | "object" | "not_implemented">,
149
+ } | {
150
+ type: "not_implemented",
151
+ comment?: string | undefined
152
+ } | {
153
+ type: "object",
154
+ fields: Record<string, FieldNode>
155
+ } | {
156
+ type: "prefixed_array",
157
+ subType: FieldNode,
158
+ length?: number | undefined,
159
+ } | {
160
+ type: "id_or_x" | "enum" | "prefixed_optional",
161
+ subType: FieldNode
162
+ } | {
163
+ type: "string",
164
+ length?: number | undefined
165
+ } | {
166
+ type: "array",
167
+ subType: FieldNode,
168
+ length: string | number,
169
+ } | {
170
+ type: "switch",
171
+ dependsOn: string,
172
+ cases: [string, FieldNode][],
173
+ default?: FieldNode | undefined
174
+ } | {
175
+ type: "fixed_point",
176
+ subType: FieldNode,
177
+ fractionBits: number
178
+ }
179
+ export const Field: z.ZodType<FieldNode> = z.union([
180
+ z.object({
181
+ type: SupportTypes.exclude(["prefixed_array", "prefixed_optional", "string", "id_or_x", "array", "switch", "fixed_point", "enum", "not_implemented", "object"]),
182
+ }),
183
+ z.object({
184
+ type: z.literal(["not_implemented"]),
185
+ comment: z.string().optional()
186
+ }),
187
+ z.object({
188
+ type: z.literal(["object"]),
189
+ fields: z.record(z.string(), z.lazy(() => Field))
190
+ }),
191
+ z.object({
192
+ type: z.literal(["prefixed_array"]),
193
+ /**
194
+ * For case like `Array<subType>` or `PrefixedOptional<subType>`
195
+ */
196
+ subType: z.lazy(() => Field),
197
+ /**
198
+ * The length of the array type, not used.
199
+ */
200
+ length: z.number().optional()
201
+ }),
202
+ z.object({
203
+ type: z.literal(["id_or_x", "enum", "prefixed_optional"]),
204
+ /**
205
+ * For case like `Array<subType>` or `PrefixedOptional<subType>`
206
+ */
207
+ subType: z.lazy(() => Field),
208
+ }),
209
+ z.object({
210
+ type: z.literal("string"),
211
+ /**
212
+ * The length of the string, not used.
213
+ */
214
+ length: z.number().optional(),
215
+ }),
216
+ z.object({
217
+ type: z.literal("array"),
218
+ /**
219
+ * For case like `Array<subType>` or `PrefixedOptional<subType>`
220
+ */
221
+ subType: z.lazy(() => Field),
222
+ /**
223
+ * Specially for `array` type, which is not prefixed with length.
224
+ *
225
+ * Point to another field, should be numberic field
226
+ */
227
+ length: z.string().or(z.number()),
228
+ }),
229
+ z.object({
230
+ type: z.literal("switch"),
231
+ /**
232
+ * Specially for case that this field type depends on other field value.
233
+ *
234
+ * Point to the field that this field is depended on.
235
+ */
236
+ dependsOn: z.string(),
237
+ /**
238
+ * Mapping depend value to type
239
+ */
240
+ cases: z.array(z.tuple([z.any(), z.lazy(() => Field)])),
241
+ default: z.lazy(() => Field).optional(),
242
+ }),
243
+ z.object({
244
+ type: z.literal(["fixed_point"]),
245
+ /**
246
+ * For case like `Array<subType>` or `PrefixedOptional<subType>`
247
+ */
248
+ subType: z.lazy(() => Field),
249
+ fractionBits: z.number()
250
+ }),
251
+
252
+ ]);
253
+ export const PacketObject = z.object({
254
+ name: z.string(),
255
+ id: z.number(),
256
+ structure: z.record(z.string(), Field),
257
+ skipForNow: z.boolean().default(false)
258
+ });
259
+ export const PacketsDefinition = z.record(z.string().or(z.number()), PacketObject);
260
+ export const StatesDefinition = z.object({
261
+ clientbound: PacketsDefinition,
262
+ serverbound: PacketsDefinition,
263
+ });
264
+ export const VersionDefinitions = z.object({
265
+ handshaking: StatesDefinition,
266
+ status: StatesDefinition,
267
+ login: StatesDefinition,
268
+ configuration: StatesDefinition,
269
+ play: StatesDefinition,
270
+ });
271
+
272
+ export class PacketRegistry {
273
+ private static loaded: boolean = false;
274
+ private static definition: z.infer<typeof VersionDefinitions>;
275
+ private static nameToId: Record<
276
+ string, // State
277
+ Record<
278
+ string, // Type
279
+ Record<string, number> // Packet
280
+ >
281
+ > = {};
282
+
283
+ /**
284
+ * Load block registry from JSON file.
285
+ *
286
+ * Should be called once time
287
+ */
288
+ public static async load(version: string) {
289
+ if (!SupportVersions.includes(version))
290
+ throw new VersionNotSupport(version);
291
+ if (this.loaded) return;
292
+ this.loaded = true;
293
+
294
+ const file = await readFile(join(BASE_REGISTRY_PATH, version, "packets.json"), { encoding: "utf8" });
295
+ const json = JSON.parse(file) as Record<string, any>;
296
+
297
+ const definition = VersionDefinitions.parse(json);
298
+ for (const [stateKey, state] of Object.entries(definition)) {
299
+ this.nameToId[stateKey] = {};
300
+ for (const [typeKey, type] of Object.entries(state)) {
301
+ this.nameToId[stateKey]![typeKey] = {};
302
+ // eslint-disable-next-line prefer-const
303
+ for (let [key, value] of Object.entries(type) as [string | number, any]) {
304
+ if (typeof key === "string" && !isNaN(parseInt(key))) {
305
+ type[parseInt(key)] = value;
306
+ delete type[key];
307
+ key = parseInt(key);
308
+ } // transform all string key into numberic key
309
+ this.nameToId[stateKey]![typeKey]![value.name] = key;
310
+ }
311
+ }
312
+ }
313
+ this.definition = definition;
314
+ }
315
+
316
+ public static getPacket(
317
+ state: keyof z.infer<typeof VersionDefinitions>,
318
+ type: keyof z.infer<typeof StatesDefinition>,
319
+ id: string | number
320
+ ): z.infer<typeof PacketObject> | undefined {
321
+ const nameToId = this.nameToId[state]?.[type]?.[id]?.toString();
322
+ if (nameToId)
323
+ return this.definition[state][type][nameToId]!;
324
+ let numbericId;
325
+ if (typeof id === "string")
326
+ numbericId = parseInt(id, 16);
327
+ else numbericId = id;
328
+ if (isNaN(numbericId))
329
+ throw new UnexpectedValue("valid packet id", id.toString(), "name or number or hex");
330
+ else
331
+ return this.definition[state][type][numbericId.toString()];
332
+ }
333
+ }
@@ -1,8 +1,9 @@
1
1
  import { TCPClient } from "../client/tcp";
2
- import { AABB, BaseAABB, VoxelShape } from "./aabb";
3
- import { Epsilon, getFrac, getSign, lerp } from "./math";
4
- import { BlockRegistry } from "./registry";
5
- import { BaseVec3, Direction, Vec3 } from "./direction";
2
+ import { AABB, BaseAABB, VoxelShape } from "../physics/aabb";
3
+ import { Epsilon, getFrac, getSign, lerp } from "../base/math";
4
+ import { BlockRegistry } from "../version/registry";
5
+ import { BaseVec3, Direction, Vec3 } from "../physics/direction";
6
+ import { SharedState } from "./state";
6
7
 
7
8
  /**
8
9
  * Represent a state of a block
@@ -76,7 +77,7 @@ export class Block {
76
77
  * Somehow, this is an equivalent implementation of Minecraft `Level`
77
78
  */
78
79
  export class BlockManager {
79
- constructor(private tcp: TCPClient) { }
80
+ constructor(private state: SharedState) { }
80
81
 
81
82
  /**
82
83
  * Check if the chunk section is loaded
@@ -84,14 +85,14 @@ export class BlockManager {
84
85
  public hasChunkAt(x: number, y: number, z: number): boolean;
85
86
  public hasChunkAt(position: BaseVec3): boolean;
86
87
  public hasChunkAt(a: BaseVec3 | number, b?: number, c?: number): boolean {
87
- this.tcp.checkReady();
88
+ this.state.checkReady();
88
89
  const { x, y, z } = Vec3.loadArgs(a, b, c);
89
90
  const sx = Math.floor(x / 16), sy = Math.floor((y + 64) / 16), sz = Math.floor(z / 16);
90
- // // console.log({
91
+ // console.log({
91
92
  // key: `${this.tcp.player!.dimension}:${sx}:${sz}`,
92
93
  // hasKey: `${this.tcp.player!.dimension}:${sx}:${sz}` in this.tcp.world!.chunks
93
94
  // });
94
- const section = this.tcp.world!.chunks[`${this.tcp.player!.dimension}:${sx}:${sz}`]?.sections[sy];
95
+ const section = this.state.world!.chunks[`${this.state.player!.dimension}:${sx}:${sz}`]?.sections[sy];
95
96
  if (!section) return false;
96
97
  return true;
97
98
  }
@@ -102,12 +103,12 @@ export class BlockManager {
102
103
  public at(x: number, y: number, z: number): BlockState | null;
103
104
  public at(position: BaseVec3): BlockState | null;
104
105
  public at(a: BaseVec3 | number, b?: number, c?: number): BlockState | null {
105
- this.tcp.checkReady();
106
+ this.state.checkReady();
106
107
  let { x, y, z } = Vec3.loadArgs(a, b, c);
107
108
  x = Math.floor(x); y = Math.floor(y); z = Math.floor(z);
108
109
  const sx = Math.floor(x / 16), sy = Math.floor((y + 64) / 16), sz = Math.floor(z / 16);
109
110
  const px = ((x % 16) + 16) % 16, py = ((y % 16) + 16) % 16, pz = ((z % 16) + 16) % 16;
110
- const section = this.tcp.world!.chunks[`${this.tcp.player!.dimension}:${sx}:${sz}`]?.sections[sy]?.block;
111
+ const section = this.state.world!.chunks[`${this.state.player!.dimension}:${sx}:${sz}`]?.sections[sy]?.block;
111
112
  if (!section) return null;
112
113
  if (section.data === null) return BlockRegistry.getState(section.palette[0]!.toString())!;
113
114
  const dataArray = section.data!;
@@ -128,7 +129,7 @@ export class BlockManager {
128
129
  * @returns
129
130
  */
130
131
  public queryAABB(queryBB: AABB) {
131
- this.tcp.checkReady();
132
+ this.state.checkReady();
132
133
 
133
134
  const { minX, minY, minZ, maxX, maxY, maxZ } = queryBB;
134
135
  const sx0 = Math.floor(minX), sx1 = Math.floor(maxX);
@@ -1,5 +1,5 @@
1
- import { TCPClient } from "../client/tcp";
2
- import { AABB } from "./aabb";
1
+ import { AABB } from "../physics/aabb";
2
+ import { SharedState } from "./state";
3
3
 
4
4
  export class EntitiesManager {
5
5
  /**
@@ -13,7 +13,7 @@ export class EntitiesManager {
13
13
  */
14
14
  public entityToSection: Map<number, string> = new Map();
15
15
 
16
- constructor(private tcp: TCPClient) { }
16
+ constructor(private state: SharedState) { }
17
17
 
18
18
  /**
19
19
  * Clear data
@@ -70,7 +70,7 @@ export class EntitiesManager {
70
70
  * @returns
71
71
  */
72
72
  public queryAABB(queryBB: AABB, exclude: number[] = []) {
73
- this.tcp.checkReady();
73
+ this.state.checkReady();
74
74
 
75
75
  const { minX, minY, minZ, maxX, maxY, maxZ } = queryBB;
76
76
  const results: AABB[] = [];
@@ -84,7 +84,7 @@ export class EntitiesManager {
84
84
  const bucket = this.sections.get(`${sx},${sy},${sz}`);
85
85
  if (bucket) for (const id of bucket) {
86
86
  if (exclude.includes(id)) continue;
87
- const entity = this.tcp.world!.entities[id];
87
+ const entity = this.state.world!.entities[id];
88
88
  const entityBB = AABB.fromEntityType(entity!.type);
89
89
  if (queryBB.isIntersect(entityBB))
90
90
  results.push(entityBB);
@@ -0,0 +1,232 @@
1
+ import { Cipheriv, Decipheriv } from "node:crypto";
2
+ import { If } from "../base/typing";
3
+ import { Angle, BaseVec3 } from "../physics/direction";
4
+ import { ClientNotReady } from "../base/error";
5
+ import { ClientEvents, ClientOption } from "../client/client";
6
+ import { TypedEmmiter } from "../base/event";
7
+ import { TCPClient } from "../client/tcp";
8
+
9
+ export interface Server {
10
+ knownPacks?: ServerKnownPack
11
+ }
12
+
13
+ export interface ServerKnownPack {
14
+ namespace: string,
15
+ id: string,
16
+ version: string
17
+ }
18
+
19
+ /**
20
+ * Represent world information
21
+ */
22
+ export interface ServerWorld {
23
+ /** Is hardcore enabled */
24
+ hardcore: boolean,
25
+ /** All dimensions */
26
+ dimensions: string[],
27
+ /** Max players allowed */
28
+ maxPlayers: number,
29
+ /** View distances */
30
+ viewDistance: number,
31
+ /** Simulation distance */
32
+ simulationDistance: number,
33
+
34
+ /**
35
+ * The record key is a chunk section position packed as `Player Dimension:Chunk X:Chunk Z[Section Y]`
36
+ *
37
+ * For `Section Y`, a chunk is splitted into several cubic 16x16x16 sections, or in other word, a chunk is a bund of 16x16x16 sections stacked on each other.
38
+ */
39
+ chunks: Record<string, ChunkColumn>,
40
+ /**
41
+ * The record key is entity ID
42
+ */
43
+ entities: Record<number, Entity>,
44
+ }
45
+
46
+ /**
47
+ * Represent a chunk collumns, which may contains multiple chunks sections
48
+ */
49
+ export interface ChunkColumn {
50
+ sections: Record<number, ChunkSection>,
51
+ blockEntities: Record<number, BlockEntity>
52
+ }
53
+
54
+ export interface ChunkSection {
55
+ block: PaletteContainer,
56
+ biome: PaletteContainer,
57
+ }
58
+
59
+ /**
60
+ * Used for indirect mapping
61
+ *
62
+ * @see https://minecraft.wiki/w/Java_Edition_protocol/Chunk_format#Paletted_Container_structure
63
+ */
64
+ export interface PaletteContainer {
65
+ bpe: number, // Bit per entry
66
+ palette: number[],
67
+ data: BigInt64Array | null, // null if the whole section contains one 1 type of block
68
+ }
69
+
70
+ /** Represent a block entity */
71
+ export interface BlockEntity {
72
+ type: number,
73
+ data: Record<string, any>
74
+ }
75
+
76
+ /** Represent an entity */
77
+ export interface Entity {
78
+ id: number,
79
+ type: number,
80
+ position: BaseVec3,
81
+ velocity: BaseVec3,
82
+ angle: Angle,
83
+ data: number
84
+ }
85
+
86
+ export enum GameMode {
87
+ Survival = 0,
88
+ Creative = 1,
89
+ Adventure = 2,
90
+ Spectator = 3
91
+ }
92
+
93
+ export interface ClientPlayer {
94
+ /** Player UUID */
95
+ uuid: string,
96
+ /** Player username */
97
+ username: string,
98
+ /** Player entity ID */
99
+ entityId: number,
100
+ /** Current game mode */
101
+ gameMode: GameMode,
102
+
103
+ position: BaseVec3,
104
+ velocity: BaseVec3,
105
+ angle: Angle,
106
+
107
+ /** Current dimension */
108
+ dimension: string
109
+ }
110
+
111
+ export enum ClientStatus {
112
+ Disconnected,
113
+ Connecting,
114
+ Logining,
115
+ Ready,
116
+ }
117
+
118
+ export enum ConnectionState {
119
+ Disconnected,
120
+ Handshake,
121
+ Login,
122
+ Configure,
123
+ Play
124
+ }
125
+
126
+ export interface ServerRegistryEntry {
127
+ id: string,
128
+ data: object
129
+ }
130
+
131
+ export type MutateState = (state: SharedState) => void;
132
+ export type EmittedEvent = {
133
+ [K in keyof ClientEvents]: { event: K, args: ClientEvents[K] }
134
+ }[keyof ClientEvents]
135
+ export type SendingPacket = {
136
+ id: string | number,
137
+ data: Record<string, any>
138
+ }
139
+
140
+ export class SharedState<IsReady extends boolean = boolean> {
141
+ private mutationQueue: MutateState[] = [];
142
+ private eventQueue: EmittedEvent[] = [];
143
+ private sendQueue: SendingPacket[] = [];
144
+
145
+ public state: ConnectionState = ConnectionState.Disconnected;
146
+ public status: ClientStatus = ClientStatus.Disconnected;
147
+ public compressionThreshold: number = 0;
148
+
149
+ public sharedSecret: Buffer | undefined = undefined;
150
+ public cipher: Cipheriv | undefined = undefined;
151
+ public decipher: Decipheriv | undefined = undefined;
152
+
153
+ public server: Server | undefined = undefined;
154
+ public registry: Record<string, ServerRegistryEntry[]> | undefined = undefined;
155
+ public world: If<IsReady, ServerWorld> = null as any;
156
+ public player: If<IsReady, ClientPlayer> = null as any;
157
+
158
+ constructor(
159
+ public clientOptions: ClientOption,
160
+ public getUuid: () => Buffer,
161
+ ) { }
162
+
163
+ public isReady(): this is SharedState<true> {
164
+ return this.status === ClientStatus.Ready;
165
+ }
166
+
167
+ public checkReady() {
168
+ if (!this.isReady())
169
+ throw new ClientNotReady();
170
+ }
171
+
172
+ public pruneStates() {
173
+ this.mutationQueue = [];
174
+ this.eventQueue = [];
175
+ this.sendQueue = [];
176
+ this.state = ConnectionState.Disconnected;
177
+ this.compressionThreshold = 0;
178
+ this.sharedSecret = undefined;
179
+ this.cipher = undefined;
180
+ this.decipher = undefined;
181
+ this.server = undefined;
182
+ this.registry = undefined;
183
+ this.world = undefined as any;
184
+ this.player = undefined as any;
185
+ }
186
+
187
+ public enqueueMutation(mutation: MutateState) { this.mutationQueue.push(mutation); }
188
+
189
+ public enqueuePacket(packet: string, data: object): void
190
+ public enqueuePacket(packet: SendingPacket): void
191
+ public enqueuePacket(packet: SendingPacket | string, data?: object) {
192
+ if (typeof packet === "string")
193
+ this.sendQueue.push({
194
+ id: packet,
195
+ data: data!
196
+ });
197
+ else
198
+ this.sendQueue.push(packet);
199
+ }
200
+
201
+ public enqueueEvent<K extends keyof ClientEvents>(event: K, ...args: ClientEvents[K]): void
202
+ public enqueueEvent(event: EmittedEvent): void
203
+ public enqueueEvent(event: EmittedEvent, ...args: any[]) {
204
+ if (typeof event === "string")
205
+ this.eventQueue.push({
206
+ event,
207
+ args: args as any
208
+ });
209
+ else
210
+ this.eventQueue.push(event);
211
+ }
212
+
213
+ public drainMutation() {
214
+ while (true) {
215
+ const mutation = this.mutationQueue.shift();
216
+ if (mutation)
217
+ mutation(this);
218
+ else
219
+ break;
220
+ }
221
+ }
222
+ public drainEvent() {
223
+ const events = structuredClone(this.eventQueue);
224
+ this.eventQueue = [];
225
+ return events;
226
+ }
227
+ public drainPacket() {
228
+ const packets = structuredClone(this.sendQueue);
229
+ this.sendQueue = [];
230
+ return packets;
231
+ }
232
+ }
@@ -0,0 +1,14 @@
1
+ import { ZodError } from "zod";
2
+ import { VersionDefinitions } from "../../src/version/adapter";
3
+ import { readFileSync } from "node:fs";
4
+
5
+ const raw = readFileSync("assets/minecraft/26.2/packets.json", { encoding: "utf8" });
6
+ const obj = JSON.parse(raw);
7
+ try {
8
+ VersionDefinitions.parse(obj);
9
+ } catch (err) {
10
+ if (err instanceof ZodError) {
11
+ console.dir(err.issues[0], { depth: null });
12
+ } else
13
+ throw err;
14
+ }
@@ -4,7 +4,7 @@ const client = new Client({
4
4
  playerName: "bot",
5
5
  host: "localhost",
6
6
  port: 25565,
7
- protocolVersion: 776,
7
+ version: "26.2",
8
8
  // debug: {
9
9
  // packetLogger: true
10
10
  // },
@@ -0,0 +1,3 @@
1
+ import { PacketDefinitions } from "../../src/version/adapter";
2
+
3
+ console.dir(PacketDefinitions.toJSONSchema({ reused: "ref", target: "openapi-3.0" }), { depth: null });