@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.
Files changed (106) hide show
  1. package/README.md +53 -5
  2. package/assets/doc/rejected.png +0 -0
  3. package/assets/minecraft/26.2/effects.json +42 -0
  4. package/assets/minecraft/26.2/packets.json +3 -2
  5. package/dist/base/bitset.d.ts +43 -4
  6. package/dist/base/bitset.d.ts.map +1 -1
  7. package/dist/base/bitset.js +251 -30
  8. package/dist/base/bitset.js.map +1 -1
  9. package/dist/base/cache.d.ts +24 -0
  10. package/dist/base/cache.d.ts.map +1 -0
  11. package/dist/base/cache.js +58 -0
  12. package/dist/base/cache.js.map +1 -0
  13. package/dist/base/error.d.ts +13 -0
  14. package/dist/base/error.d.ts.map +1 -1
  15. package/dist/base/error.js +19 -1
  16. package/dist/base/error.js.map +1 -1
  17. package/dist/base/math.d.ts +12 -0
  18. package/dist/base/math.d.ts.map +1 -1
  19. package/dist/base/math.js +39 -0
  20. package/dist/base/math.js.map +1 -1
  21. package/dist/base/typing.d.ts +7 -0
  22. package/dist/base/typing.d.ts.map +1 -1
  23. package/dist/{packet → binary}/decoder.d.ts.map +1 -1
  24. package/dist/{packet → binary}/decoder.js.map +1 -1
  25. package/dist/{packet → binary}/encoder.d.ts +2 -2
  26. package/dist/binary/encoder.d.ts.map +1 -0
  27. package/dist/{packet → binary}/encoder.js +6 -2
  28. package/dist/binary/encoder.js.map +1 -0
  29. package/dist/{packet → binary}/static.d.ts.map +1 -1
  30. package/dist/{packet → binary}/static.js.map +1 -1
  31. package/dist/client/client.d.ts +23 -2
  32. package/dist/client/client.d.ts.map +1 -1
  33. package/dist/client/client.js +34 -19
  34. package/dist/client/client.js.map +1 -1
  35. package/dist/index.d.ts +12 -4
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +12 -4
  38. package/dist/index.js.map +1 -1
  39. package/dist/{client → net}/tcp.d.ts +2 -2
  40. package/dist/net/tcp.d.ts.map +1 -0
  41. package/dist/{client → net}/tcp.js +4 -2
  42. package/dist/net/tcp.js.map +1 -0
  43. package/dist/packet/{dispatcher.d.ts → listener.d.ts} +7 -18
  44. package/dist/packet/listener.d.ts.map +1 -0
  45. package/dist/packet/{dispatcher.js → listener.js} +73 -139
  46. package/dist/packet/listener.js.map +1 -0
  47. package/dist/packet/sender.d.ts +25 -0
  48. package/dist/packet/sender.d.ts.map +1 -0
  49. package/dist/packet/sender.js +144 -0
  50. package/dist/packet/sender.js.map +1 -0
  51. package/dist/physics/aabb.d.ts.map +1 -1
  52. package/dist/physics/aabb.js +9 -8
  53. package/dist/physics/aabb.js.map +1 -1
  54. package/dist/physics/player.d.ts +1 -0
  55. package/dist/physics/player.d.ts.map +1 -1
  56. package/dist/physics/player.js +22 -4
  57. package/dist/physics/player.js.map +1 -1
  58. package/dist/version/codec.d.ts +1 -1
  59. package/dist/version/codec.d.ts.map +1 -1
  60. package/dist/version/codec.js +28 -6
  61. package/dist/version/codec.js.map +1 -1
  62. package/dist/version/registry.d.ts +19 -2
  63. package/dist/version/registry.d.ts.map +1 -1
  64. package/dist/version/registry.js +35 -3
  65. package/dist/version/registry.js.map +1 -1
  66. package/dist/world/block.d.ts +5 -0
  67. package/dist/world/block.d.ts.map +1 -1
  68. package/dist/world/block.js +56 -0
  69. package/dist/world/block.js.map +1 -1
  70. package/dist/world/state.d.ts +22 -3
  71. package/dist/world/state.d.ts.map +1 -1
  72. package/dist/world/state.js +36 -12
  73. package/dist/world/state.js.map +1 -1
  74. package/package.json +8 -10
  75. package/src/base/bitset.ts +312 -31
  76. package/src/base/cache.ts +62 -0
  77. package/src/base/error.ts +16 -0
  78. package/src/base/math.ts +43 -1
  79. package/src/base/typing.ts +5 -0
  80. package/src/{packet → binary}/encoder.ts +6 -2
  81. package/src/client/client.ts +56 -23
  82. package/src/index.ts +12 -4
  83. package/src/{client → net}/tcp.ts +5 -3
  84. package/src/packet/{dispatcher.ts → listener.ts} +92 -177
  85. package/src/packet/sender.ts +176 -0
  86. package/src/physics/aabb.ts +9 -8
  87. package/src/physics/player.ts +20 -5
  88. package/src/version/codec.ts +29 -8
  89. package/src/version/registry.ts +58 -5
  90. package/src/world/block.ts +72 -2
  91. package/src/world/state.ts +59 -14
  92. package/test/bitset/index.ts +3 -8
  93. package/test/client/index.ts +23 -13
  94. package/test/state/index.ts +7 -0
  95. package/dist/client/tcp.d.ts.map +0 -1
  96. package/dist/client/tcp.js.map +0 -1
  97. package/dist/packet/dispatcher.d.ts.map +0 -1
  98. package/dist/packet/dispatcher.js.map +0 -1
  99. package/dist/packet/encoder.d.ts.map +0 -1
  100. package/dist/packet/encoder.js.map +0 -1
  101. /package/dist/{packet → binary}/decoder.d.ts +0 -0
  102. /package/dist/{packet → binary}/decoder.js +0 -0
  103. /package/dist/{packet → binary}/static.d.ts +0 -0
  104. /package/dist/{packet → binary}/static.js +0 -0
  105. /package/src/{packet → binary}/decoder.ts +0 -0
  106. /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
+ }
@@ -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), 1);
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
- this.storage.setRange(0, (((xs.length - 1) * ys.length) + (ys.length - 1)) * zs.length + (zs.length - 1), 0);
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), 1);
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)) === 1;
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.clear(this.getIndex(x, y, startZ), this.getIndex(x, y, endZ));
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 (const i of this.lower)
683
+ for (let i = 0; i < this.lower.length; i++)
683
684
  consumer(i, -1, i);
684
685
 
685
- for (const i of this.upper)
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 (const i of this.coords)
706
+ for (let i = 0; i < this.coords.length; i++)
706
707
  consumer(i, i, i);
707
708
  }
708
709
 
@@ -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 { makeMovementFlag } from "../packet/encoder";
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 = a instanceof Vec3 ? a : new Vec3(Vec3.loadArgs(a, b, c));
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
- // TODO: Handle potion effect: Slow falling and Levitation
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: sprinting, speed-enchanted gear and potion
433
+ // TODO: Handle other cases: speed-enchanted gear and potion
419
434
  return this.isSprinting ? 0.13 : 0.1;
420
435
  }
421
436
 
@@ -1,9 +1,10 @@
1
1
  import z from "zod";
2
- import { MissingField, MissingPacketField, NotImplemented, UnexpectedValue } from "../base/error";
3
- import { BinaryDecoder } from "../packet/decoder";
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 "../packet/encoder";
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": throw new NotImplemented();
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"] || field.length;
210
- if (!length) throw new MissingField("length", "prefixed array metadata object");
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
- // case "prefixed_optional": return encoder.writePrefixedOptional((decoder) => this.readField(packetId, readObject, fieldName, field.subType, decoder));
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);
@@ -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>, "prefixed_array" | "prefixed_optional" | "string" | "id_or_x" | "array" | "switch" | "fixed_point" | "enum" | "object" | "not_implemented">,
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(),
@@ -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
- return BlockRegistry.getState(String(stateId))!;
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
  /**