@vaitosoi/minejs 0.4.2 → 0.5.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 +53 -5
- package/assets/doc/rejected.png +0 -0
- package/assets/minecraft/26.2/effects.json +42 -0
- package/assets/minecraft/26.2/packets.json +3 -2
- package/dist/base/bitset.d.ts +43 -4
- package/dist/base/bitset.d.ts.map +1 -1
- package/dist/base/bitset.js +251 -30
- package/dist/base/bitset.js.map +1 -1
- package/dist/base/cache.d.ts +24 -0
- package/dist/base/cache.d.ts.map +1 -0
- package/dist/base/cache.js +58 -0
- package/dist/base/cache.js.map +1 -0
- package/dist/base/error.d.ts +13 -0
- package/dist/base/error.d.ts.map +1 -1
- package/dist/base/error.js +19 -1
- package/dist/base/error.js.map +1 -1
- package/dist/base/math.d.ts +12 -0
- package/dist/base/math.d.ts.map +1 -1
- package/dist/base/math.js +39 -0
- package/dist/base/math.js.map +1 -1
- package/dist/base/typing.d.ts +7 -0
- package/dist/base/typing.d.ts.map +1 -1
- package/dist/{packet → binary}/decoder.d.ts.map +1 -1
- package/dist/{packet → binary}/decoder.js.map +1 -1
- package/dist/{packet → binary}/encoder.d.ts +2 -2
- package/dist/binary/encoder.d.ts.map +1 -0
- package/dist/{packet → binary}/encoder.js +6 -2
- package/dist/binary/encoder.js.map +1 -0
- package/dist/{packet → binary}/static.d.ts.map +1 -1
- package/dist/{packet → binary}/static.js.map +1 -1
- package/dist/client/client.d.ts +23 -2
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +34 -19
- package/dist/client/client.js.map +1 -1
- package/dist/index.d.ts +12 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -4
- package/dist/index.js.map +1 -1
- package/dist/{client → net}/tcp.d.ts +2 -2
- package/dist/net/tcp.d.ts.map +1 -0
- package/dist/{client → net}/tcp.js +4 -2
- package/dist/net/tcp.js.map +1 -0
- package/dist/packet/{dispatcher.d.ts → listener.d.ts} +7 -18
- package/dist/packet/listener.d.ts.map +1 -0
- package/dist/packet/{dispatcher.js → listener.js} +73 -139
- package/dist/packet/listener.js.map +1 -0
- package/dist/packet/sender.d.ts +25 -0
- package/dist/packet/sender.d.ts.map +1 -0
- package/dist/packet/sender.js +144 -0
- package/dist/packet/sender.js.map +1 -0
- package/dist/physics/aabb.d.ts.map +1 -1
- package/dist/physics/aabb.js +9 -8
- package/dist/physics/aabb.js.map +1 -1
- package/dist/physics/player.d.ts +1 -0
- package/dist/physics/player.d.ts.map +1 -1
- package/dist/physics/player.js +22 -4
- package/dist/physics/player.js.map +1 -1
- package/dist/version/codec.d.ts +1 -1
- package/dist/version/codec.d.ts.map +1 -1
- package/dist/version/codec.js +28 -6
- package/dist/version/codec.js.map +1 -1
- package/dist/version/registry.d.ts +19 -2
- package/dist/version/registry.d.ts.map +1 -1
- package/dist/version/registry.js +35 -3
- package/dist/version/registry.js.map +1 -1
- package/dist/world/block.d.ts +5 -0
- package/dist/world/block.d.ts.map +1 -1
- package/dist/world/block.js +56 -0
- package/dist/world/block.js.map +1 -1
- package/dist/world/state.d.ts +22 -3
- package/dist/world/state.d.ts.map +1 -1
- package/dist/world/state.js +36 -12
- package/dist/world/state.js.map +1 -1
- package/package.json +8 -10
- package/src/base/bitset.ts +312 -31
- package/src/base/cache.ts +62 -0
- package/src/base/error.ts +16 -0
- package/src/base/math.ts +43 -1
- package/src/base/typing.ts +5 -0
- package/src/{packet → binary}/encoder.ts +6 -2
- package/src/client/client.ts +56 -23
- package/src/index.ts +12 -4
- package/src/{client → net}/tcp.ts +5 -3
- package/src/packet/{dispatcher.ts → listener.ts} +92 -177
- package/src/packet/sender.ts +176 -0
- package/src/physics/aabb.ts +9 -8
- package/src/physics/player.ts +20 -5
- package/src/version/codec.ts +29 -8
- package/src/version/registry.ts +58 -5
- package/src/world/block.ts +72 -2
- package/src/world/state.ts +59 -14
- package/test/bitset/index.ts +3 -8
- package/test/client/index.ts +23 -13
- package/test/state/index.ts +7 -0
- package/dist/client/tcp.d.ts.map +0 -1
- package/dist/client/tcp.js.map +0 -1
- package/dist/packet/dispatcher.d.ts.map +0 -1
- package/dist/packet/dispatcher.js.map +0 -1
- package/dist/packet/encoder.d.ts.map +0 -1
- package/dist/packet/encoder.js.map +0 -1
- /package/dist/{packet → binary}/decoder.d.ts +0 -0
- /package/dist/{packet → binary}/decoder.js +0 -0
- /package/dist/{packet → binary}/static.d.ts +0 -0
- /package/dist/{packet → binary}/static.js +0 -0
- /package/src/{packet → binary}/decoder.ts +0 -0
- /package/src/{packet → binary}/static.ts +0 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { createPublicKey, publicEncrypt, constants as crypto_constants, randomBytes } from "node:crypto";
|
|
2
|
+
import { TypedEmmiter } from "../base/event";
|
|
3
|
+
import { ClientEvents } from "../client/client";
|
|
4
|
+
import { ProtocolVersionMapping } from "../version/registry";
|
|
5
|
+
import { ClientStatus, ConnectionState, ServerKnownPack, SharedState } from "../world/state";
|
|
6
|
+
import { sliceBuffer } from "../base/buffer";
|
|
7
|
+
import { randomUUIDBytes } from "../base/math";
|
|
8
|
+
import { Angle, BaseVec3 } from "../physics/direction";
|
|
9
|
+
import { MessageTooLong, NotImplemented } from "../base/error";
|
|
10
|
+
import BitSet from "../base/bitset";
|
|
11
|
+
import { makeMovementFlag } from "../binary/encoder";
|
|
12
|
+
|
|
13
|
+
export class Sender {
|
|
14
|
+
constructor(
|
|
15
|
+
private state: SharedState,
|
|
16
|
+
private emit: TypedEmmiter<ClientEvents>["emit"]
|
|
17
|
+
) { }
|
|
18
|
+
|
|
19
|
+
public sendPacket: (packetId: string, data: object) => void = () => { throw new NotImplemented(); };
|
|
20
|
+
|
|
21
|
+
public sendHandshake() {
|
|
22
|
+
this.state.state = ConnectionState.Handshake;
|
|
23
|
+
this.sendPacket("intention",
|
|
24
|
+
{
|
|
25
|
+
protocol_version: ProtocolVersionMapping[this.state.clientOptions.version]!,
|
|
26
|
+
server_address: this.state.clientOptions.host,
|
|
27
|
+
server_port: this.state.clientOptions.port,
|
|
28
|
+
intent: 2
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public sendLoginStart() {
|
|
34
|
+
this.state.state = ConnectionState.Login;
|
|
35
|
+
this.state.status = ClientStatus.Logining;
|
|
36
|
+
this.sendPacket("hello", {
|
|
37
|
+
name: this.state.clientOptions.playerName,
|
|
38
|
+
player_uuid: Buffer.from(this.state.playerUUID!, "hex")
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public sendEncryptionResponse(publicKey: Buffer, verifyToken: Buffer) {
|
|
43
|
+
const encryptedSecret = publicEncrypt({
|
|
44
|
+
key: createPublicKey({ key: publicKey, format: 'der', type: 'spki' }),
|
|
45
|
+
padding: crypto_constants.RSA_PKCS1_PADDING
|
|
46
|
+
}, this.state.sharedSecret!);
|
|
47
|
+
const encryptedVerifyToken = publicEncrypt({
|
|
48
|
+
key: createPublicKey({ key: publicKey, format: 'der', type: 'spki' }),
|
|
49
|
+
padding: crypto_constants.RSA_PKCS1_PADDING
|
|
50
|
+
}, verifyToken);
|
|
51
|
+
const secretBuf = sliceBuffer(encryptedSecret, 1, (buf) => buf.readInt8());
|
|
52
|
+
const token = sliceBuffer(encryptedVerifyToken, 1, (buf) => buf.readInt8());
|
|
53
|
+
this.sendPacket("key", {
|
|
54
|
+
shared_secret: { value: secretBuf, length: secretBuf.length },
|
|
55
|
+
verify_token: { value: token, length: token.length }
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public sendLoginAck() {
|
|
60
|
+
this.sendPacket("login_acknowledged", {});
|
|
61
|
+
this.state.state = ConnectionState.Configure;
|
|
62
|
+
this.state.server = {};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Configuring
|
|
66
|
+
|
|
67
|
+
public sendKnownPack(knownPacks: ServerKnownPack[]) {
|
|
68
|
+
this.sendPacket("select_known_packs", {
|
|
69
|
+
known_packs: {
|
|
70
|
+
value: knownPacks,
|
|
71
|
+
length: knownPacks.length
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public sendConfigureAck() {
|
|
77
|
+
this.sendPacket("finish_configuration", {});
|
|
78
|
+
this.state.state = ConnectionState.Play;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Play
|
|
82
|
+
|
|
83
|
+
public sendPlayerSession() {
|
|
84
|
+
const sessionUUID = randomUUIDBytes();
|
|
85
|
+
const expireTime = 15 * 60 * 1000; // 15min in ms
|
|
86
|
+
const expiredAt = Date.now() + expireTime;
|
|
87
|
+
const { publicKey, signature } = this.state.getSignature();
|
|
88
|
+
this.state.sessionID = sessionUUID;
|
|
89
|
+
this.sendPacket("chat_session_update", {
|
|
90
|
+
session_id: sessionUUID,
|
|
91
|
+
expire_at: expiredAt,
|
|
92
|
+
public_key: publicKey,
|
|
93
|
+
signature
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public sendConfirmTeleportation(teleportId: number) {
|
|
98
|
+
this.sendPacket("accept_teleportation", {
|
|
99
|
+
teleport_id: teleportId
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public sendKeepAlive(id: bigint) {
|
|
104
|
+
this.sendPacket("keep_alive", {
|
|
105
|
+
keep_alive_id: id
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public sendPlayerPos(
|
|
110
|
+
position: BaseVec3,
|
|
111
|
+
onGround: boolean,
|
|
112
|
+
horizontalCollision: boolean
|
|
113
|
+
) {
|
|
114
|
+
const { x, y, z } = position;
|
|
115
|
+
this.state.enqueuePacket("move_player_pos", {
|
|
116
|
+
x, feet_y: y, z,
|
|
117
|
+
flags: makeMovementFlag(onGround, horizontalCollision)
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public sendPlayerPosRot(
|
|
122
|
+
position: BaseVec3,
|
|
123
|
+
angle: Angle,
|
|
124
|
+
onGround: boolean,
|
|
125
|
+
horizontalCollision: boolean
|
|
126
|
+
) {
|
|
127
|
+
const { x, y, z } = position,
|
|
128
|
+
{ yaw, pitch } = angle;
|
|
129
|
+
this.state.enqueuePacket("move_player_pos_rot", {
|
|
130
|
+
x, feet_y: y, z,
|
|
131
|
+
yaw, pitch,
|
|
132
|
+
flags: makeMovementFlag(onGround, horizontalCollision)
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
public sendPlayerRot(
|
|
137
|
+
angle: Angle,
|
|
138
|
+
onGround: boolean,
|
|
139
|
+
horizontalCollision: boolean
|
|
140
|
+
) {
|
|
141
|
+
const { yaw, pitch } = angle;
|
|
142
|
+
this.state.enqueuePacket("move_player_rot", {
|
|
143
|
+
yaw, pitch,
|
|
144
|
+
flags: makeMovementFlag(onGround, horizontalCollision)
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
public sendPlayerStatus(
|
|
149
|
+
onGround: boolean,
|
|
150
|
+
horizontalCollision: boolean
|
|
151
|
+
) {
|
|
152
|
+
this.state.enqueuePacket("move_player_status_only", {
|
|
153
|
+
flags: makeMovementFlag(onGround, horizontalCollision)
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public sendMessage(
|
|
158
|
+
content: string
|
|
159
|
+
) {
|
|
160
|
+
if (content.length > 256)
|
|
161
|
+
throw new MessageTooLong(content);
|
|
162
|
+
const bitset = new BitSet(20);
|
|
163
|
+
const timestamp = Date.now();
|
|
164
|
+
const salt = randomBytes(8).readBigInt64BE();
|
|
165
|
+
this.sendPacket("chat", {
|
|
166
|
+
message: content,
|
|
167
|
+
timestamp,
|
|
168
|
+
salt,
|
|
169
|
+
signature: null, // skip for now
|
|
170
|
+
message_count: this.state.messageCount,
|
|
171
|
+
acknowledged: bitset,
|
|
172
|
+
checksum: 0
|
|
173
|
+
});
|
|
174
|
+
this.state.messageCount = 0;
|
|
175
|
+
}
|
|
176
|
+
}
|
package/src/physics/aabb.ts
CHANGED
|
@@ -348,7 +348,7 @@ export class VoxelShape {
|
|
|
348
348
|
mergeY.forMergedIndex((y1, y2, yr) => {
|
|
349
349
|
mergeZ.forMergedIndex((z1, z2, zr) => {
|
|
350
350
|
if (a.isFullWide(x1, y1, z1) || b.isFullWide(x2, y2, z2))
|
|
351
|
-
storage.set(getCoords(xr, yr, zr)
|
|
351
|
+
storage.set(getCoords(xr, yr, zr));
|
|
352
352
|
});
|
|
353
353
|
});
|
|
354
354
|
});
|
|
@@ -377,12 +377,13 @@ export class VoxelShape {
|
|
|
377
377
|
this.storage = cells;
|
|
378
378
|
else {
|
|
379
379
|
this.storage = new BitSet();
|
|
380
|
-
|
|
380
|
+
if ((((xs.length - 1) * ys.length) + (ys.length - 1)) * zs.length + (zs.length - 1) > 0)
|
|
381
|
+
this.storage.clearRange(0, (((xs.length - 1) * ys.length) + (ys.length - 1)) * zs.length + (zs.length - 1));
|
|
381
382
|
for (let x = 0; x < cells.length; x++)
|
|
382
383
|
for (let y = 0; y < cells[x]!.length; y++)
|
|
383
384
|
for (let z = 0; z < cells[x]![y]!.length; z++)
|
|
384
385
|
if (cells[x]![y]![z] === true)
|
|
385
|
-
this.storage.set(this.getIndex(x, y, z)
|
|
386
|
+
this.storage.set(this.getIndex(x, y, z));
|
|
386
387
|
}
|
|
387
388
|
}
|
|
388
389
|
|
|
@@ -432,7 +433,7 @@ export class VoxelShape {
|
|
|
432
433
|
if (x < 0 || x >= this.xs.length - 1) return false;
|
|
433
434
|
if (y < 0 || y >= this.ys.length - 1) return false;
|
|
434
435
|
if (z < 0 || z >= this.zs.length - 1) return false;
|
|
435
|
-
return this.storage.get(this.getIndex(x, y, z))
|
|
436
|
+
return this.storage.get(this.getIndex(x, y, z));
|
|
436
437
|
}
|
|
437
438
|
|
|
438
439
|
/**
|
|
@@ -637,7 +638,7 @@ export class VoxelShape {
|
|
|
637
638
|
return true;
|
|
638
639
|
}
|
|
639
640
|
private clearZStrip(startZ: number, endZ: number, x: number, y: number) {
|
|
640
|
-
this.storage.
|
|
641
|
+
this.storage.clearRange(this.getIndex(x, y, startZ), this.getIndex(x, y, endZ));
|
|
641
642
|
}
|
|
642
643
|
}
|
|
643
644
|
|
|
@@ -679,10 +680,10 @@ class NoneOverlappingMerger extends IndexMerger {
|
|
|
679
680
|
}
|
|
680
681
|
|
|
681
682
|
private forMergedIndexNotSwapped(consumer: IndexMergerConsumer) {
|
|
682
|
-
for (
|
|
683
|
+
for (let i = 0; i < this.lower.length; i++)
|
|
683
684
|
consumer(i, -1, i);
|
|
684
685
|
|
|
685
|
-
for (
|
|
686
|
+
for (let i = 0; i < this.upper.length; i++)
|
|
686
687
|
consumer(this.lower.length - 1, i, this.lower.length + i);
|
|
687
688
|
}
|
|
688
689
|
|
|
@@ -702,7 +703,7 @@ class IdenticalMerger extends IndexMerger {
|
|
|
702
703
|
}
|
|
703
704
|
|
|
704
705
|
public forMergedIndex(consumer: IndexMergerConsumer): void {
|
|
705
|
-
for (
|
|
706
|
+
for (let i = 0; i < this.coords.length; i++)
|
|
706
707
|
consumer(i, i, i);
|
|
707
708
|
}
|
|
708
709
|
|
package/src/physics/player.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { LevelHeightLimit } from "../client/static";
|
|
|
8
8
|
import { SharedState } from "../world/state";
|
|
9
9
|
import { TypedEmmiter } from "../base/event";
|
|
10
10
|
import { ClientEvents } from "../client/client";
|
|
11
|
-
import {
|
|
11
|
+
import { EffectRegistry } from "../version/registry";
|
|
12
12
|
|
|
13
13
|
export enum Input {
|
|
14
14
|
Forward = "W",
|
|
@@ -175,11 +175,21 @@ export class Player {
|
|
|
175
175
|
this.state.checkReady();
|
|
176
176
|
return this.state.player!.dimension;
|
|
177
177
|
}
|
|
178
|
+
private hasEffect(effectName: string) {
|
|
179
|
+
this.state.checkReady();
|
|
180
|
+
const id = EffectRegistry.effects[effectName];
|
|
181
|
+
if (!id) return false;
|
|
182
|
+
if (!(id in this.state.player!.effects)) return false;
|
|
183
|
+
const effect = this.state.player!.effects[id]!;
|
|
184
|
+
if (effect.duration < 0) return true;
|
|
185
|
+
if (effect.date + (effect.duration / 20) * 1000 < Date.now()) return true;
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
178
188
|
|
|
179
189
|
private setDeltaMovement(x: number, y: number, z: number): void;
|
|
180
190
|
private setDeltaMovement(vec: BaseVec3): void;
|
|
181
191
|
private setDeltaMovement(a: BaseVec3 | number, b?: number, c?: number): void {
|
|
182
|
-
const vec3 =
|
|
192
|
+
const vec3 = new Vec3(Vec3.loadArgs(a, b, c));
|
|
183
193
|
if (vec3.isFinite())
|
|
184
194
|
this.deltaMovement = vec3;
|
|
185
195
|
}
|
|
@@ -320,9 +330,14 @@ export class Player {
|
|
|
320
330
|
}
|
|
321
331
|
// TODO: Handle in fluid
|
|
322
332
|
|
|
323
|
-
//
|
|
333
|
+
// Handle potion effect: Slow falling and Levitation
|
|
334
|
+
if (
|
|
335
|
+
this.hasEffect("minecraft:levitation") ||
|
|
336
|
+
this.hasEffect("minecraft:slow_falling")
|
|
337
|
+
)
|
|
338
|
+
this.fallDistance = 0;
|
|
324
339
|
|
|
325
|
-
// TODO: Push entities
|
|
340
|
+
// // TODO: Push entities
|
|
326
341
|
// console.log({
|
|
327
342
|
// type: "BEFORE-TRAVEL",
|
|
328
343
|
// pos: this.getPos(),
|
|
@@ -415,7 +430,7 @@ export class Player {
|
|
|
415
430
|
|
|
416
431
|
private getSpeed() {
|
|
417
432
|
// return the base for now
|
|
418
|
-
// TODO: Handle other cases:
|
|
433
|
+
// TODO: Handle other cases: speed-enchanted gear and potion
|
|
419
434
|
return this.isSprinting ? 0.13 : 0.1;
|
|
420
435
|
}
|
|
421
436
|
|
package/src/version/codec.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import z from "zod";
|
|
2
|
-
import { MissingField, MissingPacketField, NotImplemented, UnexpectedValue } from "../base/error";
|
|
3
|
-
import { BinaryDecoder } from "../
|
|
2
|
+
import { MissingField, MissingPacketField, NotImplemented, SkippableNotImplemented, UnexpectedValue } from "../base/error";
|
|
3
|
+
import { BinaryDecoder } from "../binary/decoder";
|
|
4
4
|
import { ConnectionState, SharedState } from "../world/state";
|
|
5
5
|
import { FieldNode, PacketObject, PacketRegistry, VersionDefinitions } from "./registry";
|
|
6
|
-
import { BinaryEncoder } from "../
|
|
6
|
+
import { BinaryEncoder } from "../binary/encoder";
|
|
7
|
+
import { BitSet } from "../base/bitset";
|
|
7
8
|
|
|
8
9
|
const TCPStateMapping: Record<ConnectionState, keyof z.infer<typeof VersionDefinitions> | null> = {
|
|
9
10
|
[ConnectionState.Disconnected]: null,
|
|
@@ -39,6 +40,7 @@ export class VersionCodec {
|
|
|
39
40
|
try {
|
|
40
41
|
resolvedObject[name] = this.readField(packetId, resolvedObject, name, field, decoder);
|
|
41
42
|
} catch (err) {
|
|
43
|
+
if (err instanceof SkippableNotImplemented) break;
|
|
42
44
|
if (err instanceof NotImplemented) return;
|
|
43
45
|
throw err;
|
|
44
46
|
}
|
|
@@ -72,7 +74,7 @@ export class VersionCodec {
|
|
|
72
74
|
for (const [name, field] of Object.entries(structure)) {
|
|
73
75
|
if (!(name in data))
|
|
74
76
|
throw new MissingField(name, `packet ${packetName}`);
|
|
75
|
-
if (typeof data[name] === "object")
|
|
77
|
+
if (data[name] !== null && data[name] !== undefined && typeof data[name] === "object")
|
|
76
78
|
if ("value" in data[name])
|
|
77
79
|
if ("metadata" in data[name])
|
|
78
80
|
encoder = this.writeField(field, encoder, data[name]["value"], data[name]["metadata"]);
|
|
@@ -164,7 +166,11 @@ export class VersionCodec {
|
|
|
164
166
|
case "bitset": return decoder.readPrefixedArray((decoder) => decoder.readLong());
|
|
165
167
|
case "teleport_flags": return decoder.readTeleportFlag();
|
|
166
168
|
case "null": return null;
|
|
167
|
-
case "not_implemented":
|
|
169
|
+
case "not_implemented": {
|
|
170
|
+
if (field.skip_able === true)
|
|
171
|
+
throw new SkippableNotImplemented();
|
|
172
|
+
throw new NotImplemented();
|
|
173
|
+
}
|
|
168
174
|
case "switch": {
|
|
169
175
|
if (!(field.dependsOn in readObject)) throw new MissingPacketField(packetId, fieldName, field.dependsOn);
|
|
170
176
|
const dependencyValue = readObject[field.dependsOn];
|
|
@@ -206,14 +212,20 @@ export class VersionCodec {
|
|
|
206
212
|
// return encoder.writeArray(length, (decoder) => this.readField(packetId, readObject, fieldName, field.subType, decoder));
|
|
207
213
|
// }
|
|
208
214
|
case "prefixed_array": {
|
|
209
|
-
const length = metadata["length"]
|
|
210
|
-
if (
|
|
215
|
+
const length = metadata["length"] ?? field.length;
|
|
216
|
+
if (length === null || length === undefined) throw new MissingField("length", "prefixed array metadata object");
|
|
211
217
|
if (!Array.isArray(value))
|
|
212
218
|
throw new UnexpectedValue("an array for prefixed array", typeof value);
|
|
213
219
|
return encoder.writePrefixedArray(length, (encoder, index) => this.writeField(field.subType, encoder, value[index], metadata));
|
|
214
220
|
}
|
|
215
221
|
// case "teleport_flag": return encoder.writeTeleportFlag();
|
|
216
|
-
|
|
222
|
+
case "prefixed_optional":
|
|
223
|
+
if (value === null || value === undefined)
|
|
224
|
+
return encoder.writeBoolean(false);
|
|
225
|
+
else {
|
|
226
|
+
encoder.writeBoolean(true);
|
|
227
|
+
return this.writeField(field.subType, encoder, field, metadata);
|
|
228
|
+
}
|
|
217
229
|
// case "nbt": return encoder.writeNBT(value);
|
|
218
230
|
case "lpvec3": return encoder.writeLpVec3(value);
|
|
219
231
|
// case "fixed_point": {
|
|
@@ -231,6 +243,15 @@ export class VersionCodec {
|
|
|
231
243
|
encoder = this.writeField(subField, encoder, value[name], metadata);
|
|
232
244
|
return encoder;
|
|
233
245
|
}
|
|
246
|
+
case "fixed_bitset": {
|
|
247
|
+
if (!(value instanceof BitSet))
|
|
248
|
+
throw new UnexpectedValue("bitset instance", typeof value);
|
|
249
|
+
const arr = value.toByteArray();
|
|
250
|
+
if ("length" in field)
|
|
251
|
+
while (Math.ceil(field.length / 8) > arr.length)
|
|
252
|
+
arr.push(0);
|
|
253
|
+
return encoder.writeRaw(Buffer.from(arr));
|
|
254
|
+
}
|
|
234
255
|
// case "switch": {
|
|
235
256
|
// const dependencyValue = readObject[field.dependsOn];
|
|
236
257
|
// if (!dependencyValue) throw new MissingField(packetId, fieldName, field.dependsOn);
|
package/src/version/registry.ts
CHANGED
|
@@ -101,6 +101,30 @@ export class BlockRegistry {
|
|
|
101
101
|
}
|
|
102
102
|
};
|
|
103
103
|
|
|
104
|
+
export class EffectRegistry {
|
|
105
|
+
private static loaded: boolean = false;
|
|
106
|
+
|
|
107
|
+
public static readonly effects: Record<string, number> = {};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Load effects registry from JSON file.
|
|
111
|
+
*
|
|
112
|
+
* Should be called once time
|
|
113
|
+
*/
|
|
114
|
+
public static async load(version: string) {
|
|
115
|
+
if (!SupportVersions.includes(version))
|
|
116
|
+
throw new VersionNotSupport(version);
|
|
117
|
+
if (this.loaded) return;
|
|
118
|
+
this.loaded = true;
|
|
119
|
+
|
|
120
|
+
const file = await readFile(join(BASE_REGISTRY_PATH, version, "effects.json"), { encoding: "utf8" });
|
|
121
|
+
const json = JSON.parse(file) as Record<string, any>;
|
|
122
|
+
for (const [name, id] of Object.entries(json)) {
|
|
123
|
+
this.effects[name] = id;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
104
128
|
export const SupportTypes = z.enum([
|
|
105
129
|
"byte",
|
|
106
130
|
"unsigned_byte",
|
|
@@ -137,6 +161,8 @@ export const SupportTypes = z.enum([
|
|
|
137
161
|
"heightmap",
|
|
138
162
|
"teleport_flags",
|
|
139
163
|
"bitset",
|
|
164
|
+
"fixed_bitset",
|
|
165
|
+
"byte_array",
|
|
140
166
|
"null", // a constant
|
|
141
167
|
|
|
142
168
|
/**
|
|
@@ -144,8 +170,21 @@ export const SupportTypes = z.enum([
|
|
|
144
170
|
*/
|
|
145
171
|
"switch"
|
|
146
172
|
]);
|
|
147
|
-
export type FieldNode = {
|
|
148
|
-
type: Exclude<z.infer<typeof SupportTypes>,
|
|
173
|
+
export type FieldNode = ({
|
|
174
|
+
type: Exclude<z.infer<typeof SupportTypes>,
|
|
175
|
+
| "prefixed_array"
|
|
176
|
+
| "prefixed_optional"
|
|
177
|
+
| "string"
|
|
178
|
+
| "id_or_x"
|
|
179
|
+
| "array"
|
|
180
|
+
| "switch"
|
|
181
|
+
| "fixed_point"
|
|
182
|
+
| "enum"
|
|
183
|
+
| "object"
|
|
184
|
+
| "not_implemented"
|
|
185
|
+
| "fixed_bitset"
|
|
186
|
+
| "byte_array"
|
|
187
|
+
>,
|
|
149
188
|
} | {
|
|
150
189
|
type: "not_implemented",
|
|
151
190
|
comment?: string | undefined
|
|
@@ -175,10 +214,15 @@ export type FieldNode = {
|
|
|
175
214
|
type: "fixed_point",
|
|
176
215
|
subType: FieldNode,
|
|
177
216
|
fractionBits: number
|
|
217
|
+
} | {
|
|
218
|
+
type: "fixed_bitset" | "byte_array",
|
|
219
|
+
length: number,
|
|
220
|
+
}) & {
|
|
221
|
+
skip_able?: boolean | undefined
|
|
178
222
|
}
|
|
179
223
|
export const Field: z.ZodType<FieldNode> = z.union([
|
|
180
224
|
z.object({
|
|
181
|
-
type: SupportTypes.exclude(["prefixed_array", "prefixed_optional", "string", "id_or_x", "array", "switch", "fixed_point", "enum", "not_implemented", "object"]),
|
|
225
|
+
type: SupportTypes.exclude(["prefixed_array", "prefixed_optional", "string", "id_or_x", "array", "switch", "fixed_point", "enum", "not_implemented", "object", "fixed_bitset", "byte_array"]),
|
|
182
226
|
}),
|
|
183
227
|
z.object({
|
|
184
228
|
type: z.literal(["not_implemented"]),
|
|
@@ -248,8 +292,17 @@ export const Field: z.ZodType<FieldNode> = z.union([
|
|
|
248
292
|
subType: z.lazy(() => Field),
|
|
249
293
|
fractionBits: z.number()
|
|
250
294
|
}),
|
|
251
|
-
|
|
252
|
-
|
|
295
|
+
z.object({
|
|
296
|
+
// These type require predefined-length
|
|
297
|
+
type: z.union([z.literal("fixed_bitset"), z.literal("byte_array")]),
|
|
298
|
+
length: z.int()
|
|
299
|
+
})
|
|
300
|
+
])
|
|
301
|
+
.and(
|
|
302
|
+
z.object({
|
|
303
|
+
skip_able: z.boolean().optional()
|
|
304
|
+
})
|
|
305
|
+
);
|
|
253
306
|
export const PacketObject = z.object({
|
|
254
307
|
name: z.string(),
|
|
255
308
|
id: z.number(),
|
package/src/world/block.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { TCPClient } from "../client/tcp";
|
|
2
1
|
import { AABB, BaseAABB, VoxelShape } from "../physics/aabb";
|
|
3
2
|
import { Epsilon, getFrac, getSign, lerp } from "../base/math";
|
|
4
3
|
import { BlockRegistry } from "../version/registry";
|
|
5
4
|
import { BaseVec3, Direction, Vec3 } from "../physics/direction";
|
|
6
5
|
import { SharedState } from "./state";
|
|
6
|
+
import { NotImplemented } from "../base/error";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Represent a state of a block
|
|
@@ -108,9 +108,74 @@ export class BlockManager {
|
|
|
108
108
|
x = Math.floor(x); y = Math.floor(y); z = Math.floor(z);
|
|
109
109
|
const sx = Math.floor(x / 16), sy = Math.floor((y + 64) / 16), sz = Math.floor(z / 16);
|
|
110
110
|
const px = ((x % 16) + 16) % 16, py = ((y % 16) + 16) % 16, pz = ((z % 16) + 16) % 16;
|
|
111
|
+
|
|
112
|
+
if (this.state.cache)
|
|
113
|
+
return this.getWithCache(sx, sy, sz, px, py, pz);
|
|
114
|
+
else
|
|
115
|
+
return this.getWithoutCache(sx, sy, sz, px, py, pz);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private getBlock(stateId: number | string) {
|
|
119
|
+
return BlockRegistry.getState(String(stateId));
|
|
120
|
+
}
|
|
121
|
+
private getWithCache(
|
|
122
|
+
sx: number, sy: number, sz: number,
|
|
123
|
+
px: number, py: number, pz: number,
|
|
124
|
+
) {
|
|
125
|
+
if (!this.state.cache)
|
|
126
|
+
throw new NotImplemented();
|
|
127
|
+
const localPos = { x: px, y: py, z: pz };
|
|
128
|
+
const sectionKey = `${this.state.player!.dimension}:${sx}:${sy}:${sz}`;
|
|
129
|
+
const sectionCache = this.state.cache.get(sectionKey);
|
|
130
|
+
if (sectionCache) {
|
|
131
|
+
const cachedState = sectionCache.at(this.getLocalPosKey(localPos));
|
|
132
|
+
if (cachedState) {
|
|
133
|
+
console.log("cache hit", sectionKey, localPos);
|
|
134
|
+
return this.getBlock(cachedState);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
console.log("cache miss", sectionKey, localPos);
|
|
139
|
+
const section = this.state.world!.chunks[`${this.state.player!.dimension}:${sx}:${sz}`]?.sections[sy]?.block;
|
|
140
|
+
if (!section) return null;
|
|
141
|
+
if (section.data === null) return BlockRegistry.getState(section.palette[0]!.toString())!;
|
|
142
|
+
|
|
143
|
+
const arr = new Uint16Array(16 * 16 * 16);
|
|
144
|
+
const dataArray = section.data!;
|
|
145
|
+
const bitsPerEntry = section.bpe;
|
|
146
|
+
const entriesPerLong = Math.floor(64 / bitsPerEntry);
|
|
147
|
+
const entryMask = (1 << bitsPerEntry) - 1;
|
|
148
|
+
for (let localX = 0; localX < 16; localX++)
|
|
149
|
+
for (let localY = 0; localY < 16; localY++)
|
|
150
|
+
for (let localZ = 0; localZ < 16; localZ++) {
|
|
151
|
+
const entryIndex = this.getLocalPosKey({ x: localX, y: localY, z: localZ });
|
|
152
|
+
const longIndex = Math.floor(entryIndex / entriesPerLong);
|
|
153
|
+
const bit_index = entryIndex % entriesPerLong * bitsPerEntry;
|
|
154
|
+
const localId = Number((dataArray[longIndex]! >> BigInt(bit_index)) & BigInt(entryMask));
|
|
155
|
+
const stateId = bitsPerEntry <= 8 ? section.palette[localId]! : localId;
|
|
156
|
+
arr[entryIndex]! = stateId;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
this.state.cache.put(sectionKey, arr);
|
|
160
|
+
const state = arr.at(this.getLocalPosKey(localPos))!;
|
|
161
|
+
return this.getBlock(state);
|
|
162
|
+
}
|
|
163
|
+
public deleteChunkCache(
|
|
164
|
+
sx: number, sy: number, sz: number,
|
|
165
|
+
) {
|
|
166
|
+
const sectionKey = `${this.state.player!.dimension}:${sx}:${sy}:${sz}`;
|
|
167
|
+
if (this.state.cache)
|
|
168
|
+
this.state.cache.del(sectionKey);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private getWithoutCache(
|
|
172
|
+
sx: number, sy: number, sz: number,
|
|
173
|
+
px: number, py: number, pz: number,
|
|
174
|
+
) {
|
|
111
175
|
const section = this.state.world!.chunks[`${this.state.player!.dimension}:${sx}:${sz}`]?.sections[sy]?.block;
|
|
112
176
|
if (!section) return null;
|
|
113
177
|
if (section.data === null) return BlockRegistry.getState(section.palette[0]!.toString())!;
|
|
178
|
+
|
|
114
179
|
const dataArray = section.data!;
|
|
115
180
|
const bitsPerEntry = section.bpe;
|
|
116
181
|
const entryIndex = px + (pz * 16) + (py * 16 * 16);
|
|
@@ -120,7 +185,12 @@ export class BlockManager {
|
|
|
120
185
|
const bit_index = entryIndex % entriesPerLong * bitsPerEntry;
|
|
121
186
|
const localId = Number((dataArray[longIndex]! >> BigInt(bit_index)) & BigInt(entryMask));
|
|
122
187
|
const stateId = bitsPerEntry <= 8 ? section.palette[localId]! : localId;
|
|
123
|
-
|
|
188
|
+
|
|
189
|
+
return BlockRegistry.getState(String(stateId));
|
|
190
|
+
|
|
191
|
+
}
|
|
192
|
+
private getLocalPosKey(pos: BaseVec3) {
|
|
193
|
+
return pos.x + 16 * (pos.y + 16 * pos.z);
|
|
124
194
|
}
|
|
125
195
|
|
|
126
196
|
/**
|