Skip to content
Closed
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"typescript": "^5.0.4"
},
"dependencies": {
"minecraft-data": "github:mneuhaus/node-minecraft-data#add-26.1.2-data-wrapper",
"prismarine-biome": "^1.2.0",
"prismarine-block": "^1.14.1",
"prismarine-nbt": "^2.2.1",
Expand Down
3 changes: 2 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ const chunkImplementations = {
1.18: require('./pc/1.18/chunk'),
1.19: require('./pc/1.18/chunk'),
'1.20': require('./pc/1.18/chunk'),
1.21: require('./pc/1.18/chunk')
1.21: require('./pc/1.18/chunk'),
26.1: require('./pc/1.18/chunk')
},
bedrock: {
0.14: require('./bedrock/0.14/chunk'),
Expand Down
29 changes: 26 additions & 3 deletions src/pc/1.18/ChunkColumn.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ const CAVES_UPDATE_WORLD_HEIGHT = 384
module.exports = (Block, mcData) => {
// 1.21.5+ writes no size prefix before chunk containers, it's computed dynamically to save 1 byte
const noSizePrefix = mcData.version['>=']('1.21.5')
// 26.1.2 serializes a fluid-count short after nonEmptyBlockCount in each section.
const hasFluidCount = mcData.version.minecraftVersion === '26.1.2'
const fluidStateCache = new Map()
const fluidBlockNames = new Set(['water', 'lava', 'seagrass', 'tall_seagrass', 'kelp', 'kelp_plant', 'bubble_column'])
function hasFluidState (stateId) {
if (!stateId) return false
if (!fluidStateCache.has(stateId)) {
const block = Block.fromStateId(stateId)
fluidStateCache.set(stateId, fluidBlockNames.has(block.name) || block.isWaterlogged === true)
}
return fluidStateCache.get(stateId)
}

return class ChunkColumn extends CommonChunkColumn {
static get section () { return ChunkSection }
constructor (options) {
Expand All @@ -24,7 +37,7 @@ module.exports = (Block, mcData) => {
this.maxBitsPerBiome = neededBits(Object.values(mcData.biomes).length)

this.sections = options?.sections ?? Array.from(
{ length: this.numSections }, _ => new ChunkSection({ noSizePrefix, maxBitsPerBlock: this.maxBitsPerBlock })
{ length: this.numSections }, _ => new ChunkSection({ noSizePrefix, hasFluidCount, maxBitsPerBlock: this.maxBitsPerBlock })
)
this.biomes = options?.biomes ?? Array.from(
{ length: this.numSections }, _ => new BiomeSection({ noSizePrefix })
Expand Down Expand Up @@ -180,7 +193,16 @@ module.exports = (Block, mcData) => {

setBlockStateId (pos, stateId) {
const section = this.sections[(pos.y - this.minY) >> 4]
if (section) { section.set(toSectionPos(pos, this.minY), stateId) }
if (section) {
const sectionPos = toSectionPos(pos, this.minY)
if (hasFluidCount) {
const oldStateId = section.get(sectionPos)
const oldHasFluid = hasFluidState(oldStateId)
const newHasFluid = hasFluidState(stateId)
if (oldHasFluid !== newHasFluid) section.fluidCount += newHasFluid ? 1 : -1
}
section.set(sectionPos, stateId)
Comment on lines +196 to +204

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since PaletteChunkSection already does bookkeeping on the solid data, there is no need to add the extra logic in the ChunkColumn itself especially as this is section-level data

}
}

setBlockLight (pos, light) {
Expand Down Expand Up @@ -252,7 +274,7 @@ module.exports = (Block, mcData) => {
load (data) {
const reader = SmartBuffer.fromBuffer(data)
for (let i = 0; i < this.numSections; ++i) {
this.sections[i] = ChunkSection.read(reader, this.maxBitsPerBlock, noSizePrefix)
this.sections[i] = ChunkSection.read(reader, this.maxBitsPerBlock, noSizePrefix, hasFluidCount)
this.biomes[i] = BiomeSection.read(reader, this.maxBitsPerBiome, noSizePrefix)
}
}
Expand Down Expand Up @@ -317,6 +339,7 @@ module.exports = (Block, mcData) => {
const raiseUnknownBiome = biome => { throw new Error(`Failed to map ${JSON.stringify(biome)} to a biome ID`) }
this.sections[y + minCY] = ChunkSection.fromLocalPalette({
noSizePrefix,
hasFluidCount,
data: BitArray.fromLongArray(blockStates.data || {}, blockStates.bitsPerBlock),
palette: blockStates.palette
.map(e => Block.fromProperties(e.Name.replace('minecraft:', ''), e.Properties || {}) ?? raiseUnknownBlock(e))
Expand Down
25 changes: 21 additions & 4 deletions src/pc/common/PaletteChunkSection.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ function getBlockIndex (pos) {
class ChunkSection {
constructor (options) {
this.noSizePrefix = options?.noSizePrefix // 1.21.5+ writes no size prefix before chunk containers, it's computed dynamically to save 1 byte
this.hasFluidCount = options?.hasFluidCount ?? false
this.fluidCount = options?.fluidCount ?? 0
this.data = options?.data
if (!this.data) {
const value = options?.singleValue ?? 0
Expand All @@ -26,6 +28,7 @@ class ChunkSection {
this.solidBlockCount = value ? constants.BLOCK_SECTION_VOLUME : 0
} else {
this.solidBlockCount = options?.solidBlockCount ?? 0
this.fluidCount = options?.fluidCount ?? 0
if (options?.solidBlockCount == null) {
for (let i = 0; i < constants.BLOCK_SECTION_VOLUME; ++i) {
if (this.data.get(i)) { this.solidBlockCount++ }
Expand All @@ -38,15 +41,19 @@ class ChunkSection {
toJson () {
return JSON.stringify({
data: this.data.toJson(),
solidBlockCount: this.solidBlockCount
solidBlockCount: this.solidBlockCount,
hasFluidCount: this.hasFluidCount,
fluidCount: this.fluidCount
})
}

static fromJson (j) {
const parsed = JSON.parse(j)
return new ChunkSection({
data: paletteContainer.fromJson(parsed.data),
solidBlockCount: parsed.solidBlockCount
solidBlockCount: parsed.solidBlockCount,
hasFluidCount: parsed.hasFluidCount,
fluidCount: parsed.fluidCount
})
}

Expand Down Expand Up @@ -74,12 +81,15 @@ class ChunkSection {

write (smartBuffer) {
smartBuffer.writeInt16BE(this.solidBlockCount)
if (this.hasFluidCount) smartBuffer.writeInt16BE(this.fluidCount ?? 0)
this.data.write(smartBuffer)
}

static fromLocalPalette ({ data, palette, noSizePrefix }) {
static fromLocalPalette ({ data, palette, noSizePrefix, hasFluidCount, fluidCount }) {
return new ChunkSection({
noSizePrefix,
hasFluidCount,
fluidCount,
data: palette.length === 1
? new SingleValueContainer({
noSizePrefix,
Expand All @@ -96,15 +106,18 @@ class ChunkSection {
})
}

static read (smartBuffer, maxBitsPerBlock = constants.GLOBAL_BITS_PER_BLOCK, noSizePrefix) {
static read (smartBuffer, maxBitsPerBlock = constants.GLOBAL_BITS_PER_BLOCK, noSizePrefix, hasFluidCount = false) {
const solidBlockCount = smartBuffer.readInt16BE()
const fluidCount = hasFluidCount ? smartBuffer.readInt16BE() : 0
const bitsPerBlock = smartBuffer.readUInt8()
if (bitsPerBlock > 16) throw new Error(`Bits per block is too big: ${bitsPerBlock}`)
// Case 1: Single Value Container (all blocks in the section are the same)
if (bitsPerBlock === 0) {
const section = new ChunkSection({
noSizePrefix,
hasFluidCount,
solidBlockCount,
fluidCount,
singleValue: varInt.read(smartBuffer),
maxBitsPerBlock
})
Expand All @@ -116,7 +129,9 @@ class ChunkSection {
if (bitsPerBlock > constants.MAX_BITS_PER_BLOCK) {
return new ChunkSection({
noSizePrefix,
hasFluidCount,
solidBlockCount,
fluidCount,
data: new DirectPaletteContainer({
noSizePrefix,
bitsPerValue: maxBitsPerBlock,
Expand All @@ -134,7 +149,9 @@ class ChunkSection {

return new ChunkSection({
noSizePrefix,
hasFluidCount,
solidBlockCount,
fluidCount,
data: new IndirectPaletteContainer({
noSizePrefix,
bitsPerValue: bitsPerBlock,
Expand Down
29 changes: 29 additions & 0 deletions test/ChunkColumn.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,5 +205,34 @@ for (const version of allVersions) {
assert.strictEqual(biomeId, column.getBiomeId(pos))
})
}

if (version === '26.1.2') {
it('updates section fluid count when block states change', () => {
const column = new ChunkColumn()
const pos = new Vec3(0, 0, 0)
const section = column.sections[(pos.y - column.minY) >> 4]
const airStateId = registry.blocksByName.air.defaultState
const waterStateId = registry.blocksByName.water.defaultState
const oakStairs = registry.blocksByName.oak_stairs
let waterloggedStairsStateId
let dryStairsStateId

for (let stateId = oakStairs.minStateId; stateId <= oakStairs.maxStateId; stateId++) {
const block = Block.fromStateId(stateId)
if (block.isWaterlogged === true && waterloggedStairsStateId === undefined) waterloggedStairsStateId = stateId
if (block.isWaterlogged === false && dryStairsStateId === undefined) dryStairsStateId = stateId
}

assert.strictEqual(section.fluidCount, 0)
column.setBlockStateId(pos, waterStateId)
assert.strictEqual(section.fluidCount, 1)
column.setBlockStateId(pos.offset(1, 0, 0), waterloggedStairsStateId)
assert.strictEqual(section.fluidCount, 2)
column.setBlockStateId(pos, airStateId)
assert.strictEqual(section.fluidCount, 1)
column.setBlockStateId(pos.offset(1, 0, 0), dryStairsStateId)
assert.strictEqual(section.fluidCount, 0)
})
}
})
}
25 changes: 25 additions & 0 deletions test/ChunkSection.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

const Vec3 = require('vec3').Vec3
const ChunkSection = require('../src/pc/1.13/ChunkSection')
const PaletteChunkSection = require('../src/pc/common/PaletteChunkSection')
const constants = require('../src/pc/common/constants')
const SmartBuffer = require('smart-buffer').SmartBuffer
const assert = require('assert')

describe('pc 1.13 ChunkSection', () => {
Expand Down Expand Up @@ -37,3 +39,26 @@ describe('pc 1.13 ChunkSection', () => {
}
})
})

describe('pc palette ChunkSection', () => {
it('preserves fluid count through binary serialization', () => {
const section = new PaletteChunkSection({ hasFluidCount: true, fluidCount: 9 })
const writer = new SmartBuffer()

section.write(writer)

const read = PaletteChunkSection.read(SmartBuffer.fromBuffer(writer.toBuffer()), undefined, undefined, true)
assert.strictEqual(read.hasFluidCount, true)
assert.strictEqual(read.fluidCount, 9)
})

it('preserves fluid count through JSON serialization', () => {
const section = new PaletteChunkSection({ hasFluidCount: true, fluidCount: 4 })

const read = PaletteChunkSection.fromJson(section.toJson())

assert.strictEqual(read.hasFluidCount, true)
assert.strictEqual(read.fluidCount, 4)
assert.strictEqual(read.toJson(), section.toJson())
})
})
2 changes: 1 addition & 1 deletion test/versions.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const fs = require('fs')
const pcVersions = ['bedrock_0.14', 'bedrock_1.0', '1.8', '1.9', '1.10', '1.11', '1.12', '1.13.2', '1.14.4', '1.15.2', '1.16.1', '1.17', '1.18', '1.19', '1.20']
const pcVersions = ['bedrock_0.14', 'bedrock_1.0', '1.8', '1.9', '1.10', '1.11', '1.12', '1.13.2', '1.14.4', '1.15.2', '1.16.1', '1.17', '1.18', '1.19', '1.20', '26.1.2']
const bedrockVersions = ['bedrock_1.16.220', 'bedrock_1.17.40', 'bedrock_1.18.0']
const allVersions = [...bedrockVersions, ...pcVersions]

Expand Down
Loading