Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
distribution: 'adopt'
- name: Install dependencies
run: npm install
- run: cd node_modules && cd minecraft-data && mv minecraft-data minecraft-data-old && git clone -b pc-1_21_9 https://github.com/PrismarineJS/minecraft-data --depth 1 && node bin/generate_data.js
- run: cd node_modules && cd minecraft-data && mv minecraft-data minecraft-data-old && git clone -b pc-1_21_10 https://github.com/SuperGamerTron/minecraft-data --depth 1 && node bin/generate_data.js
- run: curl -o node_modules/protodef/src/serializer.js https://raw.githubusercontent.com/extremeheat/node-protodef/refs/heads/dlog/src/serializer.js && curl -o node_modules/protodef/src/compiler.js https://raw.githubusercontent.com/extremeheat/node-protodef/refs/heads/dlog/src/compiler.js

- name: Run tests
Expand Down
3 changes: 3 additions & 0 deletions src/client/play.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ module.exports = function (client, options) {
client.once('select_known_packs', () => {
client.write('select_known_packs', { packs: [] })
})
client.once('code_of_conduct', () => {
client.write('accept_code_of_conduct', {})
})
// Server should send finish_configuration on its own right after sending the client a dimension codec
// for login (that has data about world height, world gen, etc) after getting a login success from client
client.once('finish_configuration', () => {
Expand Down
9 changes: 6 additions & 3 deletions src/datatypes/compiler-minecraft.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ if (n !== 0) {
return { value: { ${opts.otherwise.name}: set }, size: accSize }
}
`.trim())
}]
}],
lpVec3: ['native', minecraft.lpVec3[0]]
},
Write: {
varlong: ['native', minecraft.varlong[1]],
Expand Down Expand Up @@ -135,7 +136,8 @@ if (${baseName} != null) {
}
return offset
`.trim())
}]
}],
lpVec3: ['native', minecraft.lpVec3[1]]
},
SizeOf: {
varlong: ['native', minecraft.varlong[2]],
Expand Down Expand Up @@ -194,6 +196,7 @@ if (${baseName} != null) {
}
return size
`.trim())
}]
}],
lpVec3: ['native', minecraft.lpVec3[2]]
}
}
117 changes: 117 additions & 0 deletions src/datatypes/lpVec3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
const [readVarInt, writeVarInt, sizeOfVarInt] = require('protodef').types.varint

// Adapted from https://github.com/extremeheat/extracted_minecraft_data/blob/client1.21.10/client/net/minecraft/network/LpVec3.java

const DATA_BITS_MASK = 32767
const MAX_QUANTIZED_VALUE = 32766.0
const SCALE_BITS = 2
const SCALE_BITS_MASK = 3
const CONTINUATION_FLAG = 4
const X_OFFSET = 3
const Y_OFFSET = 18
const Z_OFFSET = 33
const ABS_MAX_VALUE = 1.7179869183e10
const ABS_MIN_VALUE = 3.051944088384301e-5

function hasContinuationBit (a) {
return (a & CONTINUATION_FLAG) === CONTINUATION_FLAG
}

function sanitize (value) {
if (Number.isNaN(value)) return 0.0
return Math.max(-ABS_MAX_VALUE, Math.min(value, ABS_MAX_VALUE))
}

function pack (value) {
return BigInt(Math.round((value * 0.5 + 0.5) * MAX_QUANTIZED_VALUE))
}

function unpack (bits) {
const masked = Number(bits & BigInt(DATA_BITS_MASK))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bigint ops are very slow in JS. Why is it needed here?

const clamped = Math.min(masked, MAX_QUANTIZED_VALUE)
return (clamped * 2.0) / MAX_QUANTIZED_VALUE - 1.0
}

function readLpVec3 (buffer, offset) {
if (offset + 1 > buffer.length) throw new Error('Unexpected end while reading LpVec3')
const a = buffer.readUInt8(offset)

if (a === 0) {
return { value: { x: 0, y: 0, z: 0 }, size: 1 }
}

if (offset + 6 > buffer.length) throw new Error('Unexpected end while reading LpVec3')
const b = buffer.readUInt8(offset + 1)
const c = buffer.readUInt32LE(offset + 2)

const packed = (BigInt(c >>> 0) << 16n) | (BigInt(b & 0xff) << 8n) | BigInt(a & 0xff)

let scale = BigInt(a & SCALE_BITS_MASK)
let totalSize = 6

if (hasContinuationBit(a)) {
const dRes = readVarInt(buffer, offset + 6)
scale |= (BigInt(dRes.value >>> 0) << BigInt(SCALE_BITS))
totalSize = 6 + dRes.size
}

const x = unpack(packed >> BigInt(X_OFFSET)) * Number(scale)
const y = unpack(packed >> BigInt(Y_OFFSET)) * Number(scale)
const z = unpack(packed >> BigInt(Z_OFFSET)) * Number(scale)

return { value: { x, y, z }, size: totalSize }
}

function writeLpVec3 (value, buffer, offset) {
const x = sanitize(value.x)
const y = sanitize(value.y)
const z = sanitize(value.z)

const max = Math.max(Math.abs(x), Math.abs(y), Math.abs(z))

if (max < ABS_MIN_VALUE) {
buffer.writeUInt8(0, offset)
return offset + 1
}

const scale = BigInt(Math.ceil(max))
const needsContinuation = (scale & BigInt(SCALE_BITS_MASK)) !== scale

const scaleByte = needsContinuation ? ((scale & BigInt(SCALE_BITS_MASK)) | BigInt(CONTINUATION_FLAG)) : scale
const scaleNum = Number(scale)

const packedX = pack(x / scaleNum) << BigInt(X_OFFSET)
const packedY = pack(y / scaleNum) << BigInt(Y_OFFSET)
const packedZ = pack(z / scaleNum) << BigInt(Z_OFFSET)

const packed = scaleByte | packedX | packedY | packedZ

buffer.writeUInt8(Number(packed) & 0xff, offset)
buffer.writeUInt8(Number((packed >> 8n) & 0xffn) & 0xff, offset + 1)
buffer.writeUInt32LE(Number((packed >> 16n) & 0xffffffffn) >>> 0, offset + 2)

if (needsContinuation) {
return writeVarInt(Number(scale >> BigInt(SCALE_BITS)) >>> 0, buffer, offset + 6)
}
return offset + 6
}

function sizeOfLpVec3 (value) {
const x = sanitize(value.x)
const y = sanitize(value.y)
const z = sanitize(value.z)

const max = Math.max(Math.abs(x), Math.abs(y), Math.abs(z))

if (max < ABS_MIN_VALUE) return 1

const scale = BigInt(Math.ceil(max))
const needsContinuation = (scale & BigInt(SCALE_BITS_MASK)) !== scale

if (needsContinuation) {
return 6 + sizeOfVarInt(Number(scale >> BigInt(SCALE_BITS)) >>> 0)
}
return 6
}

module.exports = [readLpVec3, writeLpVec3, sizeOfLpVec3]
4 changes: 3 additions & 1 deletion src/datatypes/minecraft.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ const nbt = require('prismarine-nbt')
const UUID = require('uuid-1345')
const zlib = require('zlib')
const [readVarInt, writeVarInt, sizeOfVarInt] = require('protodef').types.varint
const [readLpVec3, writeLpVec3, sizeOfLpVec3] = require('./lpVec3')

module.exports = {
varlong: [readVarLong, writeVarLong, sizeOfVarLong],
UUID: [readUUID, writeUUID, 16],
compressedNbt: [readCompressedNbt, writeCompressedNbt, sizeOfCompressedNbt],
restBuffer: [readRestBuffer, writeRestBuffer, sizeOfRestBuffer],
entityMetadataLoop: [readEntityMetadata, writeEntityMetadata, sizeOfEntityMetadata],
topBitSetTerminatedArray: [readTopBitSetTerminatedArray, writeTopBitSetTerminatedArray, sizeOfTopBitSetTerminatedArray]
topBitSetTerminatedArray: [readTopBitSetTerminatedArray, writeTopBitSetTerminatedArray, sizeOfTopBitSetTerminatedArray],
lpVec3: [readLpVec3, writeLpVec3, sizeOfLpVec3]
}
const PartialReadError = require('protodef').utils.PartialReadError

Expand Down
28 changes: 28 additions & 0 deletions test/packetTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,34 @@ const values = {
RecipeBookSetting: {
open: false,
filtering: false
},
lpVec3: { x: 0, y: 0, z: 0 },
DebugSubscriptionDataType: 0,
DebugSubscriptionUpdate: {
type: 0
},
DebugSubscriptionEvent: {
type: 0
},
RespawnData: {
globalPos: {
dimensionName: 'minecraft:overworld',
location: { x: 0, y: 64, z: 0 }
},
yaw: 0,
pitch: 0
},
GlobalPos: {
dimensionName: 'minecraft:overworld',
location: { x: 0, y: 64, z: 0 }
},
ExplosionParticleInfo: {
particle: {
particleId: 0,
data: null
},
speed: 0,
scaling: 0
}
}

Expand Down