@vaitosoi/minejs 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +5 -8
  2. package/assets/minecraft/26.2/packets.json +27 -10
  3. package/dist/base/buffer.d.ts +2 -0
  4. package/dist/base/buffer.d.ts.map +1 -0
  5. package/dist/base/buffer.js +12 -0
  6. package/dist/base/buffer.js.map +1 -0
  7. package/dist/base/error.d.ts +33 -6
  8. package/dist/base/error.d.ts.map +1 -1
  9. package/dist/base/error.js +74 -11
  10. package/dist/base/error.js.map +1 -1
  11. package/dist/base/http.d.ts +2 -0
  12. package/dist/base/http.d.ts.map +1 -0
  13. package/dist/base/http.js +14 -0
  14. package/dist/base/http.js.map +1 -0
  15. package/dist/base/math.d.ts +22 -1
  16. package/dist/base/math.d.ts.map +1 -1
  17. package/dist/base/math.js +49 -2
  18. package/dist/base/math.js.map +1 -1
  19. package/dist/client/auth.d.ts +41 -7
  20. package/dist/client/auth.d.ts.map +1 -1
  21. package/dist/client/auth.js +96 -39
  22. package/dist/client/auth.js.map +1 -1
  23. package/dist/client/client.d.ts +50 -5
  24. package/dist/client/client.d.ts.map +1 -1
  25. package/dist/client/client.js +47 -18
  26. package/dist/client/client.js.map +1 -1
  27. package/dist/client/tcp.d.ts +1 -5
  28. package/dist/client/tcp.d.ts.map +1 -1
  29. package/dist/client/tcp.js +8 -21
  30. package/dist/client/tcp.js.map +1 -1
  31. package/dist/message/link.d.ts +10 -0
  32. package/dist/message/link.d.ts.map +1 -0
  33. package/dist/message/link.js +22 -0
  34. package/dist/message/link.js.map +1 -0
  35. package/dist/message/sigCache.d.ts +13 -0
  36. package/dist/message/sigCache.d.ts.map +1 -0
  37. package/dist/message/sigCache.js +26 -0
  38. package/dist/message/sigCache.js.map +1 -0
  39. package/dist/packet/decoder.d.ts +13 -0
  40. package/dist/packet/decoder.d.ts.map +1 -1
  41. package/dist/packet/decoder.js +21 -0
  42. package/dist/packet/decoder.js.map +1 -1
  43. package/dist/packet/dispatcher.d.ts +8 -1
  44. package/dist/packet/dispatcher.d.ts.map +1 -1
  45. package/dist/packet/dispatcher.js +142 -30
  46. package/dist/packet/dispatcher.js.map +1 -1
  47. package/dist/physics/player.d.ts +17 -23
  48. package/dist/physics/player.d.ts.map +1 -1
  49. package/dist/physics/player.js +59 -61
  50. package/dist/physics/player.js.map +1 -1
  51. package/dist/version/codec.d.ts.map +1 -1
  52. package/dist/version/codec.js +28 -7
  53. package/dist/version/codec.js.map +1 -1
  54. package/dist/version/registry.d.ts +1 -1
  55. package/dist/version/registry.d.ts.map +1 -1
  56. package/dist/version/registry.js +2 -7
  57. package/dist/version/registry.js.map +1 -1
  58. package/dist/world/state.d.ts +26 -2
  59. package/dist/world/state.d.ts.map +1 -1
  60. package/dist/world/state.js +58 -3
  61. package/dist/world/state.js.map +1 -1
  62. package/package.json +1 -1
  63. package/src/base/buffer.ts +12 -0
  64. package/src/base/error.ts +63 -10
  65. package/src/base/http.ts +10 -0
  66. package/src/base/math.ts +55 -3
  67. package/src/client/auth.ts +130 -52
  68. package/src/client/client.ts +96 -21
  69. package/src/client/tcp.ts +10 -27
  70. package/src/message/link.ts +17 -0
  71. package/src/message/sigCache.ts +25 -0
  72. package/src/packet/decoder.ts +21 -0
  73. package/src/packet/dispatcher.ts +169 -35
  74. package/src/physics/player.ts +83 -58
  75. package/src/version/codec.ts +32 -11
  76. package/src/version/registry.ts +2 -7
  77. package/src/world/state.ts +72 -4
  78. package/test/client/index.ts +27 -20
@@ -0,0 +1,17 @@
1
+ export class MessageLink {
2
+ constructor (
3
+ public index: number,
4
+ public sender: string,
5
+ public session: string
6
+ ) { }
7
+
8
+ public static root(sender: string, session: string) {
9
+ return new MessageLink(0, sender, session);
10
+ }
11
+
12
+ public static unsinged(sender: string) { return this.root(sender, "null"); }
13
+
14
+ public isDescendantOf(link: MessageLink) {
15
+ return link.index > this.index && this.sender === link.sender && this.session === link.session;
16
+ }
17
+ }
@@ -0,0 +1,25 @@
1
+ export class SignatureCache {
2
+ private entries: Buffer[];
3
+
4
+ constructor(public capacity: number) {
5
+ this.entries = Array.from({ length: this.capacity });
6
+ }
7
+
8
+ public static default() { return new SignatureCache(128); }
9
+
10
+ public get(index: number) { return this.entries[index]; }
11
+
12
+ public unpack = (messages: { message_id: number, signature?: number[] | undefined }[]) =>
13
+ messages.map(({ message_id: id, signature }) => signature ? Buffer.from(signature) : this.get(id)!);
14
+
15
+ public push(input: Buffer[]) {
16
+ const queue = input.slice();
17
+ const set = new Set(queue.map(v => v.toString("hex")));
18
+ for (let i = 0; i < this.entries.length && queue.length > 0; i++) {
19
+ const entry = !!this.entries[i] && Buffer.from(this.entries[i]!);
20
+ this.entries[i] = queue.pop()!;
21
+ if (entry && !set.has(entry.toString("hex")))
22
+ queue.unshift(entry);
23
+ }
24
+ }
25
+ }
@@ -397,6 +397,27 @@ export class BinaryDecoder {
397
397
  return readX(this);
398
398
  }
399
399
 
400
+ /**
401
+ * Represents a set of IDs in a certain registry (implied by context), either directly (enumerated IDs) or indirectly (tag name).
402
+ * | Field Name | Field Type | Meaning |
403
+ * |------------|------------|---------|
404
+ * | Type | VarInt | Value used to determine the data that follows. It can be either: 0 - Represents a named set of IDs defined by a tag. Anything else - Represents an ad-hoc set of IDs enumerated inline. |
405
+ * | Tag Name | Optional Identifier | The registry tag defining the ID set. Only present if Type is 0. |
406
+ * | IDs | Optional Array of VarInt | An array of registry IDs. Only present if Type is not 0. The size of the array is equal to Type - 1. |
407
+ */
408
+ public readIdSet() {
409
+ const type = this.readVarInt();
410
+ let tagName: string | null = null,
411
+ ids: number[] | null = null;
412
+ if (type === 0) tagName = this.readString();
413
+ else ids = this.readArray(type - 1, decoder => decoder.readVarInt());
414
+ return {
415
+ type,
416
+ tagName,
417
+ ids
418
+ };
419
+ }
420
+
400
421
  /**
401
422
  * Read Chat Type Decoration
402
423
  *
@@ -1,13 +1,18 @@
1
1
  import zod from "zod";
2
- import { createCipheriv, createDecipheriv, createPublicKey, publicEncrypt, randomBytes, constants as crypto_constants } from "node:crypto";
2
+ import { createCipheriv, createDecipheriv, createPublicKey, publicEncrypt, randomBytes, constants as crypto_constants, createVerify } from "node:crypto";
3
3
  import { BlockEntity, ChunkSection, ClientPlayer, ClientStatus, ConnectionState, Entity, GameMode, PaletteContainer, ServerKnownPack, ServerWorld, SharedState } from "../world/state";
4
4
  import { BinaryDecoder, getTextFromTextComponent } from "./decoder";
5
5
  import { packBlockPos, SectionsPerChunk } from "../client/static";
6
- import { Vec3 } from "../physics/direction";
6
+ import { Angle, BaseVec3, Vec3 } from "../physics/direction";
7
7
  import { ProtocolVersionMapping } from "../version/registry";
8
8
  import { TypedEmmiter } from "../base/event";
9
9
  import { ClientEvents } from "../client/client";
10
10
  import { makeMovementFlag } from "./encoder";
11
+ import { TextComponent } from "../base/typing";
12
+ import { AuthRelatedNotFound, HaveSignatureButNotIndex, MessageLinkNotFound, MissingAuthOption, NotImplemented, UnexpectedValue } from "../base/error";
13
+ import { randomUUIDBytes, uuidToBuffer } from "../base/math";
14
+ import { MessageLink } from "../message/link";
15
+ import { sliceBuffer } from "../base/buffer";
11
16
 
12
17
  function zodParse<Type extends zod.ZodType>(data: object, zod: Type): zod.infer<Type> {
13
18
  return zod.parse(data);
@@ -50,8 +55,8 @@ export class Dispatcher {
50
55
  };
51
56
  }
52
57
 
53
- public sendPacket: (packetId: string, data: object) => void = (...args) => { throw new Error("method not implemented"); };
54
- public tcpDisconnect: () => void = () => { throw new Error("method not implemented"); };
58
+ public sendPacket: (packetId: string, data: object) => void = () => { throw new NotImplemented(); };
59
+ public disconnect: (reason: string, reasonRaw?: TextComponent) => void = () => { throw new NotImplemented(); };
55
60
 
56
61
 
57
62
  public handlePacket(packetId: string, data: object) {
@@ -65,15 +70,7 @@ export class Dispatcher {
65
70
  const { reason } = zodParse(data, zod.object({ reason: zod.record(zod.string(), zod.any()) }));
66
71
  const text = getTextFromTextComponent(reason);
67
72
 
68
- this.state.enqueueMutation((state) => {
69
- state.status = ClientStatus.Disconnected;
70
- state.state = ConnectionState.Disconnected;
71
- state.pruneStates();
72
- });
73
-
74
- this.emit("disconnect", text.toString());
75
- this.emit("disconnectRaw", reason);
76
- this.tcpDisconnect();
73
+ this.disconnect(text, reason);
77
74
  }
78
75
 
79
76
  private handleEncryption(data: object) {
@@ -81,22 +78,30 @@ export class Dispatcher {
81
78
  server_id,
82
79
  public_key,
83
80
  verify_token,
84
- shoudl_call_mojang
81
+ should_authenticate
85
82
  } = zodParse(data, zod.object({
86
83
  server_id: zod.string(),
87
84
  public_key: zod.array(zod.number()),
88
85
  verify_token: zod.array(zod.number()),
89
- shoudl_call_mojang: zod.boolean()
86
+ should_authenticate: zod.boolean()
90
87
  }));
91
88
 
92
89
  const sharedSecret = randomBytes(16);
93
- this.state.enqueueMutation((state) => {
94
- state.sharedSecret = sharedSecret;
95
- state.cipher = createCipheriv("aes-128-cfb8", state.sharedSecret, state.sharedSecret);
96
- state.decipher = createDecipheriv("aes-128-cfb8", state.sharedSecret, state.sharedSecret);
97
- });
98
-
99
- this.sendEncryptionResponse(Buffer.from(public_key), Buffer.from(verify_token));
90
+ this.state.sharedSecret = sharedSecret;
91
+ this.state.cipher = createCipheriv("aes-128-cfb8", sharedSecret, sharedSecret);
92
+ this.state.decipher = createDecipheriv("aes-128-cfb8", sharedSecret, sharedSecret);
93
+ this.state.useEncryption = true;
94
+
95
+ const publicKey = Buffer.from(public_key),
96
+ verifyToken = Buffer.from(verify_token);
97
+ if (should_authenticate)
98
+ if (!this.state.authClient)
99
+ throw new AuthRelatedNotFound("auth client");
100
+ else
101
+ this.state.authClient.sendAuth(server_id, sharedSecret, publicKey)
102
+ .then(() => this.sendEncryptionResponse(publicKey, verifyToken));
103
+ else
104
+ this.sendEncryptionResponse(publicKey, verifyToken);
100
105
  }
101
106
 
102
107
  private handleSetCompression(data: object) {
@@ -176,13 +181,7 @@ export class Dispatcher {
176
181
  const { reason } = zodParse(data, zod.object({ reason: zod.record(zod.string(), zod.any()) }));
177
182
  const text = getTextFromTextComponent(reason);
178
183
 
179
- this.state.status = ClientStatus.Disconnected;
180
- this.state.state = ConnectionState.Disconnected;
181
- this.state.pruneStates();
182
-
183
- this.emit("disconnect", text.toString());
184
- this.emit("disconnectRaw", reason);
185
- this.tcpDisconnect();
184
+ this.disconnect(text, reason);
186
185
  }
187
186
 
188
187
  // Play
@@ -225,6 +224,9 @@ export class Dispatcher {
225
224
  this.state.player!.gameMode = game_mode;
226
225
  this.state.status = ClientStatus.Ready;
227
226
 
227
+ if (this.state.useEncryption)
228
+ this.sendPlayerSession();
229
+
228
230
  this.emit("ready");
229
231
  }
230
232
 
@@ -640,16 +642,84 @@ export class Dispatcher {
640
642
  const senderNameText = getTextFromTextComponent(sender_name).toString(),
641
643
  targetNameText = target_name ? getTextFromTextComponent(target_name).toString() : undefined;
642
644
 
643
- this.emit("message", {
645
+ const emitObject = {
644
646
  sender: senderNameText,
645
647
  target: targetNameText,
646
- content: message,
648
+ content: getTextFromTextComponent(unsigned_content),
647
649
  raw: {
648
650
  sender: sender_name,
649
651
  target: target_name || undefined,
650
652
  content: unsigned_content || undefined,
651
653
  }
652
- });
654
+ };
655
+
656
+ let link: MessageLink | undefined = undefined;
657
+ if (this.state.clientOptions.shouldVerifyMessageOrder && this.state.messageLinks) {
658
+ if (index === 0) {
659
+ if (this.state.sessionID)
660
+ link = MessageLink.root(sender, this.state.sessionID.toString("hex"));
661
+ else
662
+ link = MessageLink.unsinged(sender);
663
+ this.state.messageLinks[sender] = link;
664
+ } else {
665
+ const oldLink = this.state.messageLinks[sender];
666
+ if (!oldLink)
667
+ throw new MessageLinkNotFound(sender);
668
+ let newLink;
669
+ if (this.state.sessionID)
670
+ newLink = new MessageLink(global_index, sender, this.state.sessionID.toString("hex"));
671
+ else
672
+ newLink = MessageLink.unsinged(sender);
673
+ if (!newLink.isDescendantOf(oldLink))
674
+ throw new UnexpectedValue("message order", newLink.index.toString(), (oldLink.index + 1).toString());
675
+ this.state.messageLinks[sender] = newLink;
676
+ link = newLink;
677
+ }
678
+ }
679
+
680
+ if (this.state.clientOptions.shouldVerifyMessageSignature === true && message_signature_bytes) {
681
+ if (!this.state.sessionID)
682
+ throw new AuthRelatedNotFound("session uuid");
683
+ if (!this.state.messageSignatureCache)
684
+ throw new AuthRelatedNotFound("signature cache");
685
+ if (!link)
686
+ throw new HaveSignatureButNotIndex();
687
+ const { publicKey } = this.state.getSignature();
688
+
689
+ const formatVersion = Buffer.alloc(4); formatVersion.writeInt32BE(1);
690
+ const senderUUIDBuf = uuidToBuffer(sender);
691
+ const sessionUUIDBuf = this.state.sessionID;
692
+ const indexBuf = Buffer.alloc(4); indexBuf.writeInt32BE(link.index);
693
+ const saltBuf = Buffer.alloc(8); saltBuf.writeBigInt64BE(salt);
694
+ const timestampBuf = Buffer.alloc(8); timestampBuf.writeBigInt64BE(timestamp);
695
+ const _messageBuf = Buffer.from(message, "utf-8");
696
+ const lengthBuf = Buffer.alloc(4); lengthBuf.writeInt32BE(_messageBuf.length);
697
+ const messageBuf = _messageBuf;
698
+ const previousMessagesLenghtBuf = Buffer.alloc(4); previousMessagesLenghtBuf.writeInt32BE(previous_messages.length);
699
+ const previousMessagesBuf = Buffer.concat(this.state.messageSignatureCache.unpack(previous_messages));
700
+ const signaturePayload = Buffer.concat([
701
+ formatVersion,
702
+ senderUUIDBuf,
703
+ sessionUUIDBuf,
704
+ indexBuf,
705
+ saltBuf,
706
+ timestampBuf,
707
+ lengthBuf,
708
+ messageBuf,
709
+ previousMessagesLenghtBuf,
710
+ previousMessagesBuf
711
+ ]);
712
+
713
+ const verify = createVerify("RSA-SHA256");
714
+ verify.update(signaturePayload);
715
+ verify.end();
716
+ const signature = Buffer.from(message_signature_bytes);
717
+
718
+ if (!verify.verify(publicKey!, signature))
719
+ this.emit("failedMessage", emitObject);
720
+ }
721
+
722
+ this.emit("message", emitObject);
653
723
  }
654
724
 
655
725
  private handleSystemMessage(data: object) {
@@ -728,7 +798,7 @@ export class Dispatcher {
728
798
  this.state.status = ClientStatus.Logining;
729
799
  this.sendPacket("hello", {
730
800
  name: this.state.clientOptions.playerName,
731
- player_uuid: this.state.getUuid()
801
+ player_uuid: Buffer.from(this.state.playerUUID!, "hex")
732
802
  });
733
803
  }
734
804
 
@@ -741,9 +811,11 @@ export class Dispatcher {
741
811
  key: createPublicKey({ key: publicKey, format: 'der', type: 'spki' }),
742
812
  padding: crypto_constants.RSA_PKCS1_PADDING
743
813
  }, verifyToken);
814
+ const secretBuf = sliceBuffer(encryptedSecret, 1, (buf) => buf.readInt8());
815
+ const token = sliceBuffer(encryptedVerifyToken, 1, (buf) => buf.readInt8());
744
816
  this.sendPacket("key", {
745
- shared_secret: { value: encryptedSecret, length: 8 },
746
- verify_token: { value: encryptedVerifyToken, length: 8 }
817
+ shared_secret: { value: secretBuf, length: secretBuf.length },
818
+ verify_token: { value: token, length: token.length }
747
819
  });
748
820
  }
749
821
 
@@ -771,6 +843,20 @@ export class Dispatcher {
771
843
 
772
844
  // Play
773
845
 
846
+ private sendPlayerSession() {
847
+ const sessionUUID = randomUUIDBytes();
848
+ const expireTime = 15 * 60 * 1000; // 15min in ms
849
+ const expiredAt = Date.now() + expireTime;
850
+ const { publicKey, signature } = this.state.getSignature();
851
+ this.state.sessionID = sessionUUID;
852
+ this.sendPacket("chat_session_update", {
853
+ session_id: sessionUUID,
854
+ expire_at: expiredAt,
855
+ public_key: publicKey,
856
+ signature
857
+ });
858
+ }
859
+
774
860
  private sendConfirmTeleportation(teleportId: number) {
775
861
  this.sendPacket("accept_teleportation", {
776
862
  teleport_id: teleportId
@@ -782,4 +868,52 @@ export class Dispatcher {
782
868
  keep_alive_id: id
783
869
  });
784
870
  }
871
+
872
+ public sendPlayerPos(
873
+ position: BaseVec3,
874
+ onGround: boolean,
875
+ horizontalCollision: boolean
876
+ ) {
877
+ const { x, y, z } = position;
878
+ this.state.enqueuePacket("move_player_pos", {
879
+ x, feet_y: y, z,
880
+ flags: makeMovementFlag(onGround, horizontalCollision)
881
+ });
882
+ }
883
+
884
+ public sendPlayerPosRot(
885
+ position: BaseVec3,
886
+ angle: Angle,
887
+ onGround: boolean,
888
+ horizontalCollision: boolean
889
+ ) {
890
+ const { x, y, z } = position,
891
+ { yaw, pitch } = angle;
892
+ this.state.enqueuePacket("move_player_pos_rot", {
893
+ x, feet_y: y, z,
894
+ yaw, pitch,
895
+ flags: makeMovementFlag(onGround, horizontalCollision)
896
+ });
897
+ }
898
+
899
+ public sendPlayerRot(
900
+ angle: Angle,
901
+ onGround: boolean,
902
+ horizontalCollision: boolean
903
+ ) {
904
+ const { yaw, pitch } = angle;
905
+ this.state.enqueuePacket("move_player_rot", {
906
+ yaw, pitch,
907
+ flags: makeMovementFlag(onGround, horizontalCollision)
908
+ });
909
+ }
910
+
911
+ public sendPlayerStatus(
912
+ onGround: boolean,
913
+ horizontalCollision: boolean
914
+ ) {
915
+ this.state.enqueuePacket("move_player_status_only", {
916
+ flags: makeMovementFlag(onGround, horizontalCollision)
917
+ });
918
+ }
785
919
  }
@@ -3,25 +3,31 @@ import { Angle, Axis, BaseAxis, BaseVec3, Vec3 } from "./direction";
3
3
  import { AABB, Shapes, VoxelShape } from "./aabb";
4
4
  import { BlockGetter, BlockManager, BlockState } from "../world/block";
5
5
  import { clamp, Epsilon, equal, lerp } from "../base/math";
6
- import { ClientNotReady } from "../base/error";
6
+ import { ClientNotReady, NotImplemented } from "../base/error";
7
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
11
  import { makeMovementFlag } from "../packet/encoder";
12
12
 
13
- export enum MoveDirection {
13
+ export enum Input {
14
14
  Forward = "W",
15
15
  Backward = "S",
16
16
  Left = "A",
17
17
  Right = "D",
18
+ Crouching = "shift",
19
+ Jump = "space",
20
+ Sprint = "cttl"
18
21
  }
19
22
 
20
- const VectorMovement: Record<MoveDirection, Vec3> = {
21
- [MoveDirection.Forward]: new Vec3(0, 0, 1),
22
- [MoveDirection.Backward]: new Vec3(0, 0, -1),
23
- [MoveDirection.Left]: new Vec3(1, 0, 0),
24
- [MoveDirection.Right]: new Vec3(-1, 0, 0),
23
+ const VectorMovement: Record<Input, Vec3> = {
24
+ [Input.Forward]: new Vec3(0, 0, 1),
25
+ [Input.Backward]: new Vec3(0, 0, -1),
26
+ [Input.Left]: new Vec3(1, 0, 0),
27
+ [Input.Right]: new Vec3(-1, 0, 0),
28
+ [Input.Crouching]: Vec3.Zero,
29
+ [Input.Jump]: Vec3.Zero,
30
+ [Input.Sprint]: Vec3.Zero,
25
31
  };
26
32
 
27
33
  /**
@@ -42,12 +48,11 @@ export class Player {
42
48
 
43
49
  // Moving state
44
50
  // TODO: need is sprinting
45
- private heldInputs: Set<MoveDirection> = new Set();
46
- private isCrouncing: boolean = false;
47
- private isJumping: boolean = false;
51
+ private heldInputs: Set<Input> = new Set();
48
52
  private lastPos: BaseVec3 | null = null;
49
53
  private lastAngle: Angle | null = null;
50
54
  private positionReminder: number = 0;
55
+ private noJumpDelay: number = 0;
51
56
 
52
57
  // Collision
53
58
  private horizontalCollision: boolean = false;
@@ -67,9 +72,35 @@ export class Player {
67
72
  this.horizontalCollision = false;
68
73
  this.fallDistance = 0;
69
74
  this.deltaMovement = new Vec3(this.state.player!.velocity);
75
+ this.setInitialVal();
70
76
  });
71
77
  }
72
78
 
79
+
80
+ public sendPlayerPos: (
81
+ position: BaseVec3,
82
+ onGround: boolean,
83
+ horizontalCollision: boolean
84
+ ) => void = () => { throw new NotImplemented(); };
85
+
86
+ public sendPlayerPosRot: (
87
+ position: BaseVec3,
88
+ angle: Angle,
89
+ onGround: boolean,
90
+ horizontalCollision: boolean
91
+ ) => void = () => { throw new NotImplemented(); };
92
+
93
+ public sendPlayerRot: (
94
+ angle: Angle,
95
+ onGround: boolean,
96
+ horizontalCollision: boolean
97
+ ) => void = () => { throw new NotImplemented(); };
98
+
99
+ public sendPlayerStatus: (
100
+ onGround: boolean,
101
+ horizontalCollision: boolean
102
+ ) => void = () => { throw new NotImplemented(); };
103
+
73
104
  /**
74
105
  * Set initial value
75
106
  */
@@ -85,8 +116,6 @@ export class Player {
85
116
  this.deltaMovement = Vec3.Zero;
86
117
  this.supportBlockPos = null;
87
118
  this.heldInputs = new Set();
88
- this.isCrouncing = false;
89
- this.isJumping = false;
90
119
  this.lastPos = null;
91
120
  this.lastAngle = null;
92
121
  this.positionReminder = 0;
@@ -101,31 +130,19 @@ export class Player {
101
130
  /**
102
131
  * Press and hold a key
103
132
  */
104
- public input(input: MoveDirection) { this.heldInputs.add(input); }
133
+ public input(input: Input) { this.heldInputs.add(input); }
105
134
  /**
106
135
  * Release a key
107
136
  */
108
- public releaseInput(input: MoveDirection) { this.heldInputs.delete(input); }
137
+ public releaseInput(input: Input) { this.heldInputs.delete(input); }
109
138
  /**
110
139
  * Release all key
111
140
  */
112
141
  public releaseAllInputs() { this.heldInputs.clear(); }
113
- /**
114
- * Press and hold shift. Do nothing for now
115
- */
116
- public pressShift() { this.isCrouncing = true; }
117
- /**
118
- * Release shift
119
- */
120
- public releaseShift() { this.isCrouncing = false; }
121
- /**
122
- * Press and hold space. Do nothing for now
123
- */
124
- public pressSpace() { this.isJumping = true; }
125
- /**
126
- * Release shift
127
- */
128
- public releaseSpace() { this.isJumping = false; }
142
+
143
+ private get isCrouncing() { return this.heldInputs.has(Input.Crouching); };
144
+ private get isJumping() { return this.heldInputs.has(Input.Jump); };
145
+ private get isSprinting() { return this.heldInputs.has(Input.Sprint); }
129
146
 
130
147
  private getInputVector(): Vec3 {
131
148
  let vec = Vec3.Zero;
@@ -141,12 +158,19 @@ export class Player {
141
158
  }
142
159
  private setPos(pos: Vec3) {
143
160
  this.state.checkReady();
144
- this.state.player!.position = pos.copyBase();
161
+ this.state.enqueueMutation((state) => state.player!.position = pos.copyBase());
145
162
  }
146
163
  private getAngle() {
147
164
  this.state.checkReady();
148
165
  return this.state.player!.angle;
149
166
  }
167
+ public setAngle(yaw: number, pitch: number) {
168
+ this.state.checkReady();
169
+ this.state.enqueueMutation((state) => state.player!.angle = {
170
+ yaw: yaw % 360,
171
+ pitch: pitch % 360
172
+ });
173
+ }
150
174
  private getDimension() {
151
175
  this.state.checkReady();
152
176
  return this.state.player!.dimension;
@@ -241,28 +265,16 @@ export class Player {
241
265
  const move = new Vec3(deltaX, deltaY, deltaZ).lengthSqr() > (2e-4 * 2e-4) || this.positionReminder > 20,
242
266
  rot = deltaXRot !== 0 || deltaYRot !== 0;
243
267
  if (move && rot)
244
- this.state.enqueuePacket("move_player_pos_rot", {
245
- x, feet_y: y, z,
246
- yaw, pitch,
247
- flags: makeMovementFlag(this.onGround, this.horizontalCollision)
248
- });
268
+ this.sendPlayerPosRot(this.getPos(), this.getAngle(), this.onGround, this.horizontalCollision);
249
269
  else if (move)
250
- this.state.enqueuePacket("move_player_pos", {
251
- x, feet_y: y, z,
252
- flags: makeMovementFlag(this.onGround, this.horizontalCollision)
253
- });
270
+ this.sendPlayerPos(this.getPos(), this.onGround, this.horizontalCollision);
254
271
  else if (rot)
255
- this.state.enqueuePacket("move_player_rot", {
256
- yaw, pitch,
257
- flags: makeMovementFlag(this.onGround, this.horizontalCollision)
258
- });
272
+ this.sendPlayerRot(this.getAngle(), this.onGround, this.horizontalCollision);
259
273
  else if (
260
274
  this.onGround !== this.lastOnGround ||
261
275
  this.horizontalCollision !== this.lastHorizontalCollision
262
276
  )
263
- this.state.enqueuePacket("move_player_status_only", {
264
- flags: makeMovementFlag(this.onGround, this.horizontalCollision)
265
- });
277
+ this.sendPlayerStatus(this.onGround, this.horizontalCollision);
266
278
 
267
279
  if (move) {
268
280
  this.lastPos = this.getPos();
@@ -276,6 +288,10 @@ export class Player {
276
288
  }
277
289
 
278
290
  private aiStep() {
291
+ if (this.noJumpDelay > 0) {
292
+ this.noJumpDelay--;
293
+ }
294
+
279
295
  // eslint-disable-next-line prefer-const
280
296
  let { x, y, z } = this.deltaMovement;
281
297
  if (this.deltaMovement.horizontalDistanceSqr() < 9e-6) {
@@ -284,13 +300,23 @@ export class Player {
284
300
  }
285
301
  this.setDeltaMovement(x, y, z);
286
302
 
287
- if (this.isJumping) {
303
+ if (this.isJumping && this.noJumpDelay === 0) {
288
304
  const jumpPower = this.getJumpFactor();
289
305
 
290
306
  if (jumpPower > 1e-5) {
291
307
  const { x: dx, y: dy, z: dz } = this.deltaMovement;
292
308
  this.setDeltaMovement(dx, Math.max(dy, jumpPower), dz);
309
+ if (this.isSprinting) {
310
+ const yawInRad = this.getAngle().yaw * (Math.PI / 180.0);
311
+ this.setDeltaMovement(
312
+ this.deltaMovement.add(
313
+ new Vec3(-Math.sin(yawInRad) * 0.2, 0.0, Math.cos(yawInRad) * 0.2)
314
+ )
315
+ );
316
+ }
293
317
  }
318
+
319
+ this.noJumpDelay = 15; // Original value to 10
294
320
  }
295
321
  // TODO: Handle in fluid
296
322
 
@@ -312,14 +338,15 @@ export class Player {
312
338
  const { x, y, z } = this.getPos();
313
339
  const inBlock = this.blocks.at(x, y, z)!;
314
340
  const belowBlock = this.blocks.at(x, y - 0.5, z)!;
315
- return inBlock.owner.getJumpFactor() === 1 ? belowBlock.owner.getJumpFactor() : inBlock.owner.getJumpFactor();
341
+ // 0.42F is the default jump strength, which will apply for player here
342
+ return (inBlock.owner.getJumpFactor() === 1 ? belowBlock.owner.getJumpFactor() : inBlock.owner.getJumpFactor()) * 0.42;
316
343
  }
317
344
 
318
345
  /*
319
346
  * Invoking flow
320
347
  *
321
348
  * |-> travelInAir ---|
322
- * travel -|-> travelInFluid -|--> move -> colision
349
+ * travel -|-> travelInFluid -+--> move -> colision
323
350
  * |-> flying? -------|
324
351
  */
325
352
 
@@ -334,8 +361,8 @@ export class Player {
334
361
  private travelInAir(input: Vec3) {
335
362
  const posBelow = this.getOnPos(0.50001);
336
363
  if (!this.blocks.hasChunkAt(posBelow))
337
- return; // // console.log(`no chunk at ${Math.floor(posBelow.x / 16)} ${Math.floor(posBelow.y / 16)} ${Math.floor(posBelow.z / 16)}`);
338
- // // console.log('posBelow', posBelow, 'block', this.blocks.at(posBelow)?.owner.getType());
364
+ return; // console.log(`no chunk at ${Math.floor(posBelow.x / 16)} ${Math.floor(posBelow.y / 16)} ${Math.floor(posBelow.z / 16)}`);
365
+ // console.log('posBelow', posBelow, 'block', this.blocks.at(posBelow)?.owner.getType());
339
366
  const blockFriction = this.onGround ? this.blocks.at(posBelow)!.owner.getFriction() : 1;
340
367
  const movement = this.handleFrictionAndCalculateMovement(input, blockFriction);
341
368
  let movementY = movement.y;
@@ -389,7 +416,7 @@ export class Player {
389
416
  private getSpeed() {
390
417
  // return the base for now
391
418
  // TODO: Handle other cases: sprinting, speed-enchanted gear and potion
392
- return 0.1;
419
+ return this.isSprinting ? 0.13 : 0.1;
393
420
  }
394
421
 
395
422
  private moveRelative(input: Vec3, speed: number) {
@@ -433,14 +460,12 @@ export class Player {
433
460
  to: checkTo,
434
461
  getBlockState: (position) => this.blocks.at(position)!.shape
435
462
  });
436
- if (hitResult?.miss !== false) {
463
+ if (hitResult?.miss !== false)
437
464
  this.fallDistance = 0;
438
- }
439
465
  }
440
466
 
441
467
  const pos = this.getPos();
442
468
  const newPosition = pos.add(movement);
443
- // addMovementThisTick(new Movement(pos, newPosition, delta2));
444
469
  this.setPos(newPosition);
445
470
  }
446
471
 
@@ -600,8 +625,8 @@ export class Player {
600
625
  }
601
626
  }
602
627
 
603
- // // console.log('expandedBB', expandedBB, 'colliderCount', colliders.length);
604
- // // console.log('movement.y', movement.y, 'movementStep.y', movementStep.y);
628
+ // console.log('expandedBB', expandedBB, 'colliderCount', colliders.length);
629
+ // console.log('movement.y', movement.y, 'movementStep.y', movementStep.y);
605
630
 
606
631
  return movementStep;
607
632
  }