@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.
- package/assets/minecraft/26.2/packets.json +4741 -0
- package/dist/base/bitset.d.ts +9 -0
- package/dist/base/bitset.d.ts.map +1 -0
- package/dist/base/bitset.js +46 -0
- package/dist/base/bitset.js.map +1 -0
- package/dist/base/error.d.ts +105 -0
- package/dist/base/error.d.ts.map +1 -0
- package/dist/base/error.js +194 -0
- package/dist/base/error.js.map +1 -0
- package/dist/base/event.d.ts +7 -0
- package/dist/base/event.d.ts.map +1 -0
- package/dist/base/event.js +3 -0
- package/dist/base/event.js.map +1 -0
- package/dist/base/math.d.ts +25 -0
- package/dist/base/math.d.ts.map +1 -0
- package/dist/base/math.js +69 -0
- package/dist/base/math.js.map +1 -0
- package/dist/base/typing.d.ts +9 -0
- package/dist/base/typing.d.ts.map +1 -0
- package/dist/base/typing.js +3 -0
- package/dist/base/typing.js.map +1 -0
- package/dist/client/auth.d.ts +39 -0
- package/dist/client/auth.d.ts.map +1 -0
- package/dist/client/auth.js +307 -0
- package/dist/client/auth.js.map +1 -0
- package/dist/client/client.d.ts +73 -0
- package/dist/client/client.d.ts.map +1 -0
- package/dist/client/client.js +136 -0
- package/dist/client/client.js.map +1 -0
- package/dist/client/static.d.ts +28 -0
- package/dist/client/static.d.ts.map +1 -0
- package/dist/client/static.js +43 -0
- package/dist/client/static.js.map +1 -0
- package/dist/client/tcp.d.ts +84 -0
- package/dist/client/tcp.d.ts.map +1 -0
- package/dist/client/tcp.js +173 -0
- package/dist/client/tcp.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -12
- package/dist/index.js.map +1 -0
- package/dist/packet/decoder.d.ts +293 -0
- package/dist/packet/decoder.d.ts.map +1 -0
- package/dist/packet/decoder.js +668 -0
- package/dist/packet/decoder.js.map +1 -0
- package/dist/packet/dispatcher.d.ts +46 -0
- package/dist/packet/dispatcher.d.ts.map +1 -0
- package/dist/packet/dispatcher.js +626 -0
- package/dist/packet/dispatcher.js.map +1 -0
- package/dist/packet/encoder.d.ts +163 -0
- package/dist/packet/encoder.d.ts.map +1 -0
- package/dist/packet/encoder.js +441 -0
- package/dist/packet/encoder.js.map +1 -0
- package/dist/packet/static.d.ts +33 -0
- package/dist/packet/static.d.ts.map +1 -0
- package/dist/packet/static.js +42 -0
- package/dist/packet/static.js.map +1 -0
- package/dist/physics/aabb.d.ts +197 -0
- package/dist/physics/aabb.d.ts.map +1 -0
- package/dist/physics/aabb.js +576 -0
- package/dist/physics/aabb.js.map +1 -0
- package/dist/physics/direction.d.ts +153 -0
- package/dist/physics/direction.d.ts.map +1 -0
- package/dist/physics/direction.js +288 -0
- package/dist/physics/direction.js.map +1 -0
- package/dist/physics/player.d.ts +110 -0
- package/dist/physics/player.d.ts.map +1 -0
- package/dist/physics/player.js +558 -0
- package/dist/physics/player.js.map +1 -0
- package/dist/version/codec.d.ts +18 -0
- package/dist/version/codec.d.ts.map +1 -0
- package/dist/version/codec.js +214 -0
- package/dist/version/codec.js.map +1 -0
- package/dist/version/registry.d.ts +236 -0
- package/dist/version/registry.d.ts.map +1 -0
- package/dist/version/registry.js +289 -0
- package/dist/version/registry.js.map +1 -0
- package/dist/world/block.d.ts +86 -0
- package/dist/world/block.d.ts.map +1 -0
- package/dist/world/block.js +232 -0
- package/dist/world/block.js.map +1 -0
- package/dist/world/entity.d.ts +43 -0
- package/dist/world/entity.d.ts.map +1 -0
- package/dist/world/entity.js +97 -0
- package/dist/world/entity.js.map +1 -0
- package/dist/world/state.d.ts +151 -0
- package/dist/world/state.d.ts.map +1 -0
- package/dist/world/state.js +108 -0
- package/dist/world/state.js.map +1 -0
- package/dist/world/tick.d.ts +19 -0
- package/dist/world/tick.d.ts.map +1 -0
- package/dist/world/tick.js +49 -0
- package/dist/world/tick.js.map +1 -0
- package/package.json +7 -2
- package/src/base/error.ts +36 -0
- package/src/base/typing.ts +9 -1
- package/src/client/client.ts +99 -42
- package/src/client/tcp.ts +49 -954
- package/src/index.ts +12 -12
- package/src/{translator → packet}/decoder.ts +3 -3
- package/src/packet/dispatcher.ts +785 -0
- package/src/{translator → packet}/encoder.ts +20 -9
- package/src/{base → physics}/aabb.ts +5 -5
- package/src/{base → physics}/player.ts +41 -25
- package/src/version/codec.ts +227 -0
- package/src/version/registry.ts +333 -0
- package/src/{base → world}/block.ts +12 -11
- package/src/{base → world}/entity.ts +5 -5
- package/src/world/state.ts +232 -0
- package/test/adapter/index.ts +14 -0
- package/test/client/index.ts +1 -1
- package/test/json/index.ts +3 -0
- package/src/base/registry.ts +0 -87
- /package/assets/minecraft/{blocks.json → 26.2/blocks.json} +0 -0
- /package/assets/minecraft/{entities.json → 26.2/entities.json} +0 -0
- /package/src/{translator → packet}/static.ts +0 -0
- /package/src/{base → physics}/direction.ts +0 -0
- /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 "
|
|
3
|
-
import { Epsilon, getFrac, getSign, lerp } from "
|
|
4
|
-
import { BlockRegistry } from "
|
|
5
|
-
import { BaseVec3, Direction, Vec3 } from "
|
|
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
|
|
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.
|
|
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
|
-
//
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 {
|
|
2
|
-
import {
|
|
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
|
|
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.
|
|
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.
|
|
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
|
+
}
|
package/test/client/index.ts
CHANGED