@vaitosoi/minejs 0.2.2 → 0.4.0
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 +1 -1
- package/assets/minecraft/26.2/packets.json +4758 -0
- package/dist/base/buffer.d.ts +2 -0
- package/dist/base/buffer.d.ts.map +1 -0
- package/dist/base/buffer.js +12 -0
- package/dist/base/buffer.js.map +1 -0
- package/dist/base/error.d.ts +59 -3
- package/dist/base/error.d.ts.map +1 -1
- package/dist/base/error.js +123 -7
- package/dist/base/error.js.map +1 -1
- package/dist/base/http.d.ts +2 -0
- package/dist/base/http.d.ts.map +1 -0
- package/dist/base/http.js +14 -0
- package/dist/base/http.js.map +1 -0
- package/dist/base/math.d.ts +22 -1
- package/dist/base/math.d.ts.map +1 -1
- package/dist/base/math.js +49 -2
- package/dist/base/math.js.map +1 -1
- package/dist/base/typing.d.ts +7 -1
- package/dist/base/typing.d.ts.map +1 -1
- package/dist/client/auth.d.ts +41 -7
- package/dist/client/auth.d.ts.map +1 -1
- package/dist/client/auth.js +96 -39
- package/dist/client/auth.js.map +1 -1
- package/dist/client/client.d.ts +73 -17
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +111 -44
- package/dist/client/client.js.map +1 -1
- package/dist/client/tcp.d.ts +17 -206
- package/dist/client/tcp.d.ts.map +1 -1
- package/dist/client/tcp.js +37 -715
- package/dist/client/tcp.js.map +1 -1
- package/dist/index.d.ts +12 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -12
- package/dist/index.js.map +1 -1
- package/dist/message/link.d.ts +10 -0
- package/dist/message/link.d.ts.map +1 -0
- package/dist/message/link.js +22 -0
- package/dist/message/link.js.map +1 -0
- package/dist/message/sigCache.d.ts +13 -0
- package/dist/message/sigCache.d.ts.map +1 -0
- package/dist/message/sigCache.js +26 -0
- package/dist/message/sigCache.js.map +1 -0
- package/dist/{translator → packet}/decoder.d.ts +14 -1
- package/dist/packet/decoder.d.ts.map +1 -0
- package/dist/{translator → packet}/decoder.js +24 -3
- package/dist/packet/decoder.js.map +1 -0
- package/dist/packet/dispatcher.d.ts +53 -0
- package/dist/packet/dispatcher.d.ts.map +1 -0
- package/dist/packet/dispatcher.js +738 -0
- package/dist/packet/dispatcher.js.map +1 -0
- package/dist/{translator → packet}/encoder.d.ts +4 -3
- package/dist/packet/encoder.d.ts.map +1 -0
- package/dist/{translator → packet}/encoder.js +18 -7
- package/dist/packet/encoder.js.map +1 -0
- package/dist/packet/static.d.ts.map +1 -0
- package/dist/packet/static.js.map +1 -0
- package/dist/{base → physics}/aabb.d.ts +2 -2
- package/dist/physics/aabb.d.ts.map +1 -0
- package/dist/{base → physics}/aabb.js +5 -5
- package/dist/physics/aabb.js.map +1 -0
- package/dist/physics/direction.d.ts.map +1 -0
- package/dist/physics/direction.js.map +1 -0
- package/dist/{base → physics}/player.d.ts +25 -28
- package/dist/physics/player.d.ts.map +1 -0
- package/dist/{base → physics}/player.js +81 -68
- 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 +235 -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 +284 -0
- package/dist/version/registry.js.map +1 -0
- package/dist/{base → world}/block.d.ts +5 -5
- package/dist/world/block.d.ts.map +1 -0
- package/dist/{base → world}/block.js +13 -13
- package/dist/world/block.js.map +1 -0
- package/dist/{base → world}/entity.d.ts +4 -4
- package/dist/world/entity.d.ts.map +1 -0
- package/dist/{base → world}/entity.js +6 -6
- package/dist/world/entity.js.map +1 -0
- package/dist/world/state.d.ts +175 -0
- package/dist/world/state.d.ts.map +1 -0
- package/dist/world/state.js +163 -0
- package/dist/world/state.js.map +1 -0
- package/dist/world/tick.d.ts.map +1 -0
- package/dist/world/tick.js.map +1 -0
- package/package.json +7 -2
- package/src/base/buffer.ts +12 -0
- package/src/base/error.ts +93 -4
- package/src/base/http.ts +10 -0
- package/src/base/math.ts +55 -3
- package/src/base/typing.ts +9 -1
- package/src/client/auth.ts +130 -52
- package/src/client/client.ts +182 -50
- package/src/client/tcp.ts +45 -967
- package/src/index.ts +12 -12
- package/src/message/link.ts +17 -0
- package/src/message/sigCache.ts +25 -0
- package/src/{translator → packet}/decoder.ts +24 -3
- package/src/packet/dispatcher.ts +919 -0
- package/src/{translator → packet}/encoder.ts +20 -9
- package/src/{base → physics}/aabb.ts +5 -5
- package/src/{base → physics}/player.ts +106 -65
- package/src/version/codec.ts +248 -0
- package/src/version/registry.ts +328 -0
- package/src/{base → world}/block.ts +12 -11
- package/src/{base → world}/entity.ts +5 -5
- package/src/world/state.ts +300 -0
- package/test/adapter/index.ts +14 -0
- package/test/client/index.ts +28 -21
- package/test/json/index.ts +3 -0
- package/dist/base/aabb.d.ts.map +0 -1
- package/dist/base/aabb.js.map +0 -1
- package/dist/base/block.d.ts.map +0 -1
- package/dist/base/block.js.map +0 -1
- package/dist/base/direction.d.ts.map +0 -1
- package/dist/base/direction.js.map +0 -1
- package/dist/base/entity.d.ts.map +0 -1
- package/dist/base/entity.js.map +0 -1
- package/dist/base/player.d.ts.map +0 -1
- package/dist/base/player.js.map +0 -1
- package/dist/base/registry.d.ts +0 -50
- package/dist/base/registry.d.ts.map +0 -1
- package/dist/base/registry.js +0 -89
- package/dist/base/registry.js.map +0 -1
- package/dist/base/tick.d.ts.map +0 -1
- package/dist/base/tick.js.map +0 -1
- package/dist/translator/decoder.d.ts.map +0 -1
- package/dist/translator/decoder.js.map +0 -1
- package/dist/translator/encoder.d.ts.map +0 -1
- package/dist/translator/encoder.js.map +0 -1
- package/dist/translator/static.d.ts.map +0 -1
- package/dist/translator/static.js.map +0 -1
- 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/dist/{translator → packet}/static.d.ts +0 -0
- /package/dist/{translator → packet}/static.js +0 -0
- /package/dist/{base → physics}/direction.d.ts +0 -0
- /package/dist/{base → physics}/direction.js +0 -0
- /package/dist/{base → world}/tick.d.ts +0 -0
- /package/dist/{base → world}/tick.js +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,248 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
import { MissingField, MissingPacketField, NotImplemented, UnexpectedValue } from "../base/error";
|
|
3
|
+
import { BinaryDecoder } from "../packet/decoder";
|
|
4
|
+
import { ConnectionState, SharedState } from "../world/state";
|
|
5
|
+
import { FieldNode, PacketObject, PacketRegistry, VersionDefinitions } from "./registry";
|
|
6
|
+
import { BinaryEncoder } from "../packet/encoder";
|
|
7
|
+
|
|
8
|
+
const TCPStateMapping: Record<ConnectionState, keyof z.infer<typeof VersionDefinitions> | null> = {
|
|
9
|
+
[ConnectionState.Disconnected]: null,
|
|
10
|
+
[ConnectionState.Handshake]: "handshaking",
|
|
11
|
+
[ConnectionState.Login]: "login",
|
|
12
|
+
[ConnectionState.Configure]: "configuration",
|
|
13
|
+
[ConnectionState.Play]: "play",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export interface EncodeResult {
|
|
17
|
+
buffer: Buffer,
|
|
18
|
+
packet: z.infer<typeof PacketObject>,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class VersionCodec {
|
|
22
|
+
constructor(private readonly state: SharedState) { }
|
|
23
|
+
|
|
24
|
+
public consumePacket: (packetId: string, data: object) => void = () => { throw new NotImplemented(); };
|
|
25
|
+
|
|
26
|
+
public handlePacket(packetId: number, decoder: BinaryDecoder) {
|
|
27
|
+
const mappedState = TCPStateMapping[this.state.state];
|
|
28
|
+
if (!mappedState) return; // console.log("no state");
|
|
29
|
+
const packet = PacketRegistry.getPacket(mappedState, "clientbound", packetId);
|
|
30
|
+
if (!packet) return; // console.log("no packet");
|
|
31
|
+
|
|
32
|
+
const { name, structure, skipForNow } = packet;
|
|
33
|
+
|
|
34
|
+
if (skipForNow) return;
|
|
35
|
+
|
|
36
|
+
const resolvedObject: Record<string, any> = {};
|
|
37
|
+
if (Object.keys(structure).length > 0)
|
|
38
|
+
for (const [name, field] of Object.entries(structure))
|
|
39
|
+
try {
|
|
40
|
+
resolvedObject[name] = this.readField(packetId, resolvedObject, name, field, decoder);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
if (err instanceof NotImplemented) return;
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
if (this.state.clientOptions.debug?.packetLogger)
|
|
46
|
+
console.dir({
|
|
47
|
+
dir: "to client",
|
|
48
|
+
state: mappedState,
|
|
49
|
+
name,
|
|
50
|
+
data: resolvedObject,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
this.consumePacket(`${TCPStateMapping[this.state.state]}:${name}`, resolvedObject);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public encodePacket(packetId: number | string, data: Record<string, any>): EncodeResult {
|
|
57
|
+
const mappedState = TCPStateMapping[this.state.state];
|
|
58
|
+
if (!mappedState) throw new UnexpectedValue("map-able state", this.state.state.toString());
|
|
59
|
+
const packet = PacketRegistry.getPacket(mappedState, "serverbound", packetId);
|
|
60
|
+
if (!packet) throw new UnexpectedValue("valid serverbound packet id", packetId.toString());
|
|
61
|
+
|
|
62
|
+
if (this.state.clientOptions.debug?.packetLogger)
|
|
63
|
+
console.dir({
|
|
64
|
+
dir: "to server",
|
|
65
|
+
state: mappedState,
|
|
66
|
+
id: packetId,
|
|
67
|
+
data,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const { name: packetName, structure } = packet;
|
|
71
|
+
let encoder = new BinaryEncoder();
|
|
72
|
+
for (const [name, field] of Object.entries(structure)) {
|
|
73
|
+
if (!(name in data))
|
|
74
|
+
throw new MissingField(name, `packet ${packetName}`);
|
|
75
|
+
if (typeof data[name] === "object")
|
|
76
|
+
if ("value" in data[name])
|
|
77
|
+
if ("metadata" in data[name])
|
|
78
|
+
encoder = this.writeField(field, encoder, data[name]["value"], data[name]["metadata"]);
|
|
79
|
+
else if ("length" in data[name])
|
|
80
|
+
encoder = this.writeField(field, encoder, data[name]["value"], { length: data[name]["length"] });
|
|
81
|
+
else
|
|
82
|
+
encoder = this.writeField(field, encoder, data[name]["value"]);
|
|
83
|
+
else
|
|
84
|
+
encoder = this.writeField(field, encoder, data[name]);
|
|
85
|
+
else
|
|
86
|
+
encoder = this.writeField(field, encoder, data[name]);
|
|
87
|
+
}
|
|
88
|
+
return { buffer: encoder.getBuffer(), packet };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private readField(
|
|
92
|
+
// Current reading state
|
|
93
|
+
packetId: number,
|
|
94
|
+
readObject: Record<string, any>,
|
|
95
|
+
|
|
96
|
+
// Current field
|
|
97
|
+
fieldName: string,
|
|
98
|
+
field: FieldNode,
|
|
99
|
+
decoder: BinaryDecoder
|
|
100
|
+
): any {
|
|
101
|
+
switch (field.type) {
|
|
102
|
+
case "string": return decoder.readString();
|
|
103
|
+
case "boolean": return decoder.readBoolean();
|
|
104
|
+
case "byte": return decoder.readByte();
|
|
105
|
+
case "unsigned_byte": return decoder.readUByte();
|
|
106
|
+
case "short": return decoder.readShort();
|
|
107
|
+
case "unsigned_short": return decoder.readUShort();
|
|
108
|
+
case "int": return decoder.readInt();
|
|
109
|
+
case "long": return decoder.readLong();
|
|
110
|
+
case "float": return decoder.readFloat();
|
|
111
|
+
case "double": return decoder.readDouble();
|
|
112
|
+
case "var_int": return decoder.readVarInt();
|
|
113
|
+
case "var_long": return decoder.readVarLong();
|
|
114
|
+
case "uuid": return decoder.readUUID();
|
|
115
|
+
case "position": return decoder.readPosition();
|
|
116
|
+
case "array": {
|
|
117
|
+
let length;
|
|
118
|
+
if (!isNaN(Number(field.length)))
|
|
119
|
+
length = Number(field.length);
|
|
120
|
+
else
|
|
121
|
+
length = readObject[field.length];
|
|
122
|
+
if (length === undefined) throw new MissingPacketField(packetId, fieldName, field.length.toString());
|
|
123
|
+
return decoder.readArray(length, (decoder) => this.readField(packetId, readObject, fieldName, field.subType, decoder));
|
|
124
|
+
}
|
|
125
|
+
case "prefixed_array": return decoder.readPrefixedArray((decoder) => this.readField(packetId, readObject, fieldName, field.subType, decoder));
|
|
126
|
+
case "teleport_flag": return decoder.readTeleportFlag();
|
|
127
|
+
case "prefixed_optional": return decoder.readPrefixedOptional((decoder) => this.readField(packetId, readObject, fieldName, field.subType, decoder));
|
|
128
|
+
case "nbt": return decoder.readNBT();
|
|
129
|
+
case "lpvec3": return decoder.readLpVec3();
|
|
130
|
+
case "fixed_point": {
|
|
131
|
+
const value = this.readField(packetId, readObject, fieldName, field.subType, decoder);
|
|
132
|
+
return decoder.readFixedPoint(value, field.fractionBits);
|
|
133
|
+
}
|
|
134
|
+
case "angle": return decoder.readAngle();
|
|
135
|
+
case "id_or_x": return decoder.readIdOrX((decoder) => this.readField(packetId, readObject, fieldName, field.subType, decoder));
|
|
136
|
+
case "id_set": return decoder.readIdSet();
|
|
137
|
+
case "chat_type_decoration": return decoder.readChatTypeDecoration();
|
|
138
|
+
case "chat_type": return decoder.readChatType();
|
|
139
|
+
case "enum": return this.readField(packetId, readObject, fieldName, field.subType, decoder);
|
|
140
|
+
case "json_text": return JSON.parse(decoder.readString());
|
|
141
|
+
case "object": {
|
|
142
|
+
const obj: Record<string, any> = {};
|
|
143
|
+
for (const [key, type] of Object.entries(field.fields))
|
|
144
|
+
obj[key] = this.readField(packetId, readObject, key, type, decoder);
|
|
145
|
+
return obj;
|
|
146
|
+
}
|
|
147
|
+
case "game_profile": {
|
|
148
|
+
return {
|
|
149
|
+
uuid: decoder.readUUID(),
|
|
150
|
+
username: decoder.readString(),
|
|
151
|
+
pproperties: decoder.readPrefixedArray((decoder) => ({
|
|
152
|
+
name: decoder.readString(),
|
|
153
|
+
value: decoder.readString(),
|
|
154
|
+
signature: decoder.readPrefixedOptional((decoder) => decoder.readString())
|
|
155
|
+
}))
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
case "heightmap": {
|
|
159
|
+
return {
|
|
160
|
+
type: decoder.readVarInt(),
|
|
161
|
+
data: decoder.readPrefixedArray((decoder) => decoder.readLong())
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
case "bitset": return decoder.readPrefixedArray((decoder) => decoder.readLong());
|
|
165
|
+
case "teleport_flags": return decoder.readTeleportFlag();
|
|
166
|
+
case "null": return null;
|
|
167
|
+
case "not_implemented": throw new NotImplemented();
|
|
168
|
+
case "switch": {
|
|
169
|
+
if (!(field.dependsOn in readObject)) throw new MissingPacketField(packetId, fieldName, field.dependsOn);
|
|
170
|
+
const dependencyValue = readObject[field.dependsOn];
|
|
171
|
+
for (const [value, type] of field.cases)
|
|
172
|
+
if (dependencyValue === value)
|
|
173
|
+
return this.readField(packetId, readObject, fieldName, type, decoder);
|
|
174
|
+
if (field.default)
|
|
175
|
+
return this.readField(packetId, readObject, fieldName, field.default, decoder);
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
private writeField(
|
|
183
|
+
field: FieldNode,
|
|
184
|
+
encoder: BinaryEncoder,
|
|
185
|
+
value: any,
|
|
186
|
+
metadata: Record<string, any> = {}
|
|
187
|
+
): BinaryEncoder {
|
|
188
|
+
switch (field.type) {
|
|
189
|
+
case "string": return encoder.writeString(value);
|
|
190
|
+
case "boolean": return encoder.writeBoolean(value);
|
|
191
|
+
case "byte": return encoder.writeByte(value);
|
|
192
|
+
case "unsigned_byte": return encoder.writeUByte(value);
|
|
193
|
+
case "short": return encoder.writeShort(value);
|
|
194
|
+
case "unsigned_short": return encoder.writeUShort(value);
|
|
195
|
+
case "int": return encoder.writeInt(value);
|
|
196
|
+
case "long": return encoder.writeLong(value);
|
|
197
|
+
case "float": return encoder.writeFloat(value);
|
|
198
|
+
case "double": return encoder.writeDouble(value);
|
|
199
|
+
case "var_int": return encoder.writeVarInt(value);
|
|
200
|
+
case "var_long": return encoder.writeVarLong(value);
|
|
201
|
+
case "uuid": return encoder.writeRaw(value);
|
|
202
|
+
case "position": return encoder.writePosition(value);
|
|
203
|
+
// case "array": {
|
|
204
|
+
// const length = metadata["length"];
|
|
205
|
+
// // if (!length) throw new MissingField(packetId, fieldName, field.length);
|
|
206
|
+
// return encoder.writeArray(length, (decoder) => this.readField(packetId, readObject, fieldName, field.subType, decoder));
|
|
207
|
+
// }
|
|
208
|
+
case "prefixed_array": {
|
|
209
|
+
const length = metadata["length"] || field.length;
|
|
210
|
+
if (!length) throw new MissingField("length", "prefixed array metadata object");
|
|
211
|
+
if (!Array.isArray(value))
|
|
212
|
+
throw new UnexpectedValue("an array for prefixed array", typeof value);
|
|
213
|
+
return encoder.writePrefixedArray(length, (encoder, index) => this.writeField(field.subType, encoder, value[index], metadata));
|
|
214
|
+
}
|
|
215
|
+
// case "teleport_flag": return encoder.writeTeleportFlag();
|
|
216
|
+
// case "prefixed_optional": return encoder.writePrefixedOptional((decoder) => this.readField(packetId, readObject, fieldName, field.subType, decoder));
|
|
217
|
+
// case "nbt": return encoder.writeNBT(value);
|
|
218
|
+
case "lpvec3": return encoder.writeLpVec3(value);
|
|
219
|
+
// case "fixed_point": {
|
|
220
|
+
// const value = this.readField(packetId, readObject, fieldName, field.subType, encoder);
|
|
221
|
+
// return encoder.writeFixedPoint(value, field.fractionBits);
|
|
222
|
+
// }
|
|
223
|
+
// case "angle": return encoder.writeAngle();
|
|
224
|
+
// case "id_or_x": return encoder.writeIdOrX((decoder) => this.readField(packetId, readObject, fieldName, field.subType, decoder));
|
|
225
|
+
// case "chat_type_decoration": return encoder.writeChatTypeDecoration();
|
|
226
|
+
// case "chat_type": return encoder.writeChatType();
|
|
227
|
+
case "enum": return this.writeField(field.subType, encoder, value, metadata);
|
|
228
|
+
case "json_text": return encoder.writeString(JSON.parse(value));
|
|
229
|
+
case "object": {
|
|
230
|
+
for (const [name, subField] of Object.entries(field.fields))
|
|
231
|
+
encoder = this.writeField(subField, encoder, value[name], metadata);
|
|
232
|
+
return encoder;
|
|
233
|
+
}
|
|
234
|
+
// case "switch": {
|
|
235
|
+
// const dependencyValue = readObject[field.dependsOn];
|
|
236
|
+
// if (!dependencyValue) throw new MissingField(packetId, fieldName, field.dependsOn);
|
|
237
|
+
// for (const [value, type] of field.cases)
|
|
238
|
+
// if (dependencyValue === value)
|
|
239
|
+
// return this.readField(packetId, readObject, fieldName, type, encoder);
|
|
240
|
+
// if (field.default)
|
|
241
|
+
// return this.readField(packetId, readObject, fieldName, field.default, encoder);
|
|
242
|
+
// break;
|
|
243
|
+
// }
|
|
244
|
+
default:
|
|
245
|
+
throw new UnexpectedValue("encoding type", field.type);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -0,0 +1,328 @@
|
|
|
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
|
+
"id_set",
|
|
129
|
+
"chat_type_decoration",
|
|
130
|
+
"chat_type",
|
|
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
|
+
this.nameToId[stateKey]![typeKey]![value.name] = key;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
this.definition = definition;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
public static getPacket(
|
|
312
|
+
state: keyof z.infer<typeof VersionDefinitions>,
|
|
313
|
+
type: keyof z.infer<typeof StatesDefinition>,
|
|
314
|
+
id: string | number
|
|
315
|
+
): z.infer<typeof PacketObject> | undefined {
|
|
316
|
+
const nameToId = this.nameToId[state]?.[type]?.[id]?.toString();
|
|
317
|
+
if (nameToId)
|
|
318
|
+
return this.definition[state][type][nameToId]!;
|
|
319
|
+
let numbericId;
|
|
320
|
+
if (typeof id === "string")
|
|
321
|
+
numbericId = parseInt(id, 16);
|
|
322
|
+
else numbericId = id;
|
|
323
|
+
if (isNaN(numbericId))
|
|
324
|
+
throw new UnexpectedValue("valid packet id", id.toString(), "name or number or hex");
|
|
325
|
+
else
|
|
326
|
+
return this.definition[state][type]["0x" + numbericId.toString(16).toUpperCase()];
|
|
327
|
+
}
|
|
328
|
+
}
|
|
@@ -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);
|