From a65da38223330693bdd58bb66316a95ee3230035 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 13:43:05 +0000 Subject: [PATCH 1/5] Update to version 26.1.2 --- .github/workflows/ci.yml | 3 +++ docs/README.md | 2 +- lib/version.js | 2 +- package.json | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 223ab66b4a..002cdae81a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,6 +62,9 @@ jobs: java-package: jre - name: Install Dependencies run: npm install + - run: cd node_modules && cd minecraft-data && mv minecraft-data minecraft-data-old && git clone -b pc_26_1_2 https://github.com/PrismarineJS/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: Start Tests run: | diff --git a/docs/README.md b/docs/README.md index ea94a2b0f8..9b8f95240a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -17,7 +17,7 @@ First time using Node.js? You may want to start with the [tutorial](tutorial.md) ## Features - * Supports Minecraft 1.8 to 1.21.11 (1.8, 1.9, 1.10, 1.11, 1.12, 1.13, 1.14, 1.15, 1.16, 1.17, 1.18, 1.19, 1.20, 1.21, 1.21.9, 1.21.11) + * Supports Minecraft 1.8 to 26.1.2 (1.8, 1.9, 1.10, 1.11, 1.12, 1.13, 1.14, 1.15, 1.16, 1.17, 1.18, 1.19, 1.20, 1.21, 1.21.9, 1.21.11, 26.1.2) * Entity knowledge and tracking. * Block knowledge. You can query the world around you. Milliseconds to find any block. * Physics and movement - handle all bounding boxes diff --git a/lib/version.js b/lib/version.js index c4843bb089..d96f9f89fc 100644 --- a/lib/version.js +++ b/lib/version.js @@ -1,4 +1,4 @@ -const testedVersions = ['1.8.8', '1.9.4', '1.10.2', '1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.5', '1.17.1', '1.18.2', '1.19', '1.19.2', '1.19.3', '1.19.4', '1.20.1', '1.20.2', '1.20.4', '1.20.6', '1.21.1', '1.21.3', '1.21.4', '1.21.5', '1.21.6', '1.21.8', '1.21.9', '1.21.11'] +const testedVersions = ['1.8.8', '1.9.4', '1.10.2', '1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.5', '1.17.1', '1.18.2', '1.19', '1.19.2', '1.19.3', '1.19.4', '1.20.1', '1.20.2', '1.20.4', '1.20.6', '1.21.1', '1.21.3', '1.21.4', '1.21.5', '1.21.6', '1.21.8', '1.21.9', '1.21.11', '26.1.2'] module.exports = { testedVersions, diff --git a/package.json b/package.json index b90f5de3c6..6ec687fcef 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "license": "MIT", "dependencies": { "minecraft-data": "^3.108.0", - "minecraft-protocol": "^1.66.0", + "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#pc26_1_2", "mojangson": "^2.0.4", "prismarine-biome": "^1.1.1", "prismarine-block": "^1.22.0", From 59658760d4b434b4b8ad4f8733e56d1cfcc2ab53 Mon Sep 17 00:00:00 2001 From: Marc Neuhaus Date: Mon, 11 May 2026 22:02:06 +0200 Subject: [PATCH 2/5] Handle 26.1.2 entity use and time packets --- lib/plugins/entities.js | 19 +++++++++++++++++++ lib/plugins/inventory.js | 24 ++++++++++++++++++++++++ lib/plugins/time.js | 10 ++++++++-- 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/lib/plugins/entities.js b/lib/plugins/entities.js index 64cb790c40..005db2482a 100644 --- a/lib/plugins/entities.js +++ b/lib/plugins/entities.js @@ -911,6 +911,25 @@ function inject (bot) { function useEntity (target, leftClick, x, y, z) { const sneaking = bot.getControlState('sneak') + if (bot.supportFeature('useEntityUsesEntityId')) { + if (leftClick === 1 && bot.supportFeature('attackUsesOwnPacket')) { + bot._client.write('attack', { entityId: target.id }) + return + } + + bot._client.write('use_entity', { + entityId: target.id, + hand: 0, + location: { + x: x ?? 0, + y: y ?? 0, + z: z ?? 0 + }, + usingSecondaryAction: sneaking + }) + return + } + if (x && y && z) { bot._client.write('use_entity', { target: target.id, diff --git a/lib/plugins/inventory.js b/lib/plugins/inventory.js index 0636faab7c..f2f1583be1 100644 --- a/lib/plugins/inventory.js +++ b/lib/plugins/inventory.js @@ -243,6 +243,16 @@ function inject (bot, { hideErrors }) { async function activateEntity (entity) { // TODO: tell the server that we are not sneaking while doing this await bot.lookAt(entity.position.offset(0, 1, 0), false) + if (bot.supportFeature('useEntityUsesEntityId')) { + bot._client.write('use_entity', { + entityId: entity.id, + hand: 0, + location: { x: 0, y: 0, z: 0 }, + usingSecondaryAction: false + }) + return + } + bot._client.write('use_entity', { target: entity.id, mouse: 0, // interact with entity @@ -254,6 +264,20 @@ function inject (bot, { hideErrors }) { async function activateEntityAt (entity, position) { // TODO: tell the server that we are not sneaking while doing this await bot.lookAt(position, false) + if (bot.supportFeature('useEntityUsesEntityId')) { + bot._client.write('use_entity', { + entityId: entity.id, + hand: 0, + location: { + x: position.x - entity.position.x, + y: position.y - entity.position.y, + z: position.z - entity.position.z + }, + usingSecondaryAction: false + }) + return + } + bot._client.write('use_entity', { target: entity.id, mouse: 2, // interact with entity at diff --git a/lib/plugins/time.js b/lib/plugins/time.js index 0b9411db0f..5d1de9d1d8 100644 --- a/lib/plugins/time.js +++ b/lib/plugins/time.js @@ -13,8 +13,12 @@ function inject (bot) { age: null } bot._client.on('update_time', (packet) => { - const time = longToBigInt(packet.time) - const age = longToBigInt(packet.age) + const packetTime = packet.time ?? packet.gameTime ?? packet.age + const packetAge = packet.age ?? packet.gameTime ?? packet.time + if (packetTime == null || packetAge == null) return + + const time = longToBigInt(packetTime) + const age = longToBigInt(packetAge) const doDaylightCycle = packet.tickDayTime !== undefined ? !!packet.tickDayTime : time >= 0n // When doDaylightCycle is false, we need to take the absolute value of time const finalTime = doDaylightCycle ? time : (time < 0n ? -time : time) @@ -34,5 +38,7 @@ function inject (bot) { } function longToBigInt (arr) { + if (typeof arr === 'bigint') return BigInt.asIntN(64, arr) + if (typeof arr === 'number') return BigInt.asIntN(64, BigInt(arr)) return BigInt.asIntN(64, (BigInt(arr[0]) << 32n)) | BigInt(arr[1]) } From a4b44c09056e2e83b79b7c744ec13b6b22b17443 Mon Sep 17 00:00:00 2001 From: Marc Neuhaus Date: Tue, 12 May 2026 12:09:29 +0200 Subject: [PATCH 3/5] Fix Mineflayer external tests for 26.1.2 --- lib/plugins/bed.js | 22 +++++++ lib/plugins/entities.js | 6 +- lib/plugins/inventory.js | 4 ++ lib/plugins/physics.js | 7 +++ lib/plugins/time.js | 73 ++++++++++++++++++++++-- test/externalTest.js | 43 ++++++++++---- test/externalTests/elytra.js | 6 ++ test/externalTests/plugins/testCommon.js | 20 ++++--- 8 files changed, 155 insertions(+), 26 deletions(-) diff --git a/lib/plugins/bed.js b/lib/plugins/bed.js index 9b526f698d..46a76992fb 100644 --- a/lib/plugins/bed.js +++ b/lib/plugins/bed.js @@ -22,6 +22,28 @@ function inject (bot) { headOffset: new Vec3(0, 0, 1) } + const properties = bedBlock.getProperties?.() + if (properties?.part) { + metadata.part = properties.part === 'head' + metadata.occupied = Boolean(properties.occupied) + + switch (properties.facing) { + case 'west': + metadata.facing = 1 + metadata.headOffset.set(-1, 0, 0) + break + case 'north': + metadata.facing = 2 + metadata.headOffset.set(0, 0, -1) + break + case 'east': + metadata.facing = 3 + metadata.headOffset.set(1, 0, 0) + break + } + return metadata + } + if (bot.supportFeature('blockStateId')) { const state = bedBlock.stateId - bot.registry.blocksByStateId[bedBlock.stateId].minStateId const bitMetadata = state.toString(2).padStart(4, '0') // FACING (first 2 bits), PART (3rd bit), OCCUPIED (4th bit) diff --git a/lib/plugins/entities.js b/lib/plugins/entities.js index 005db2482a..08cbaf593c 100644 --- a/lib/plugins/entities.js +++ b/lib/plugins/entities.js @@ -422,12 +422,15 @@ function inject (bot) { bot.emit('entityElytraFlew', entity) } } + bot._setElytraFlyingState = setElytraFlyingState const knownFireworks = new Set() function handleBotUsedFireworkRocket (fireworkEntityId, fireworkInfo) { if (knownFireworks.has(fireworkEntityId)) return knownFireworks.add(fireworkEntityId) - let flightDur = fireworkInfo?.nbtData?.value?.Fireworks?.value?.Flight.value ?? 1 + let flightDur = fireworkInfo?.nbtData?.value?.Fireworks?.value?.Flight.value ?? + fireworkInfo?.nbt?.value?.Fireworks?.value?.Flight.value ?? + 1 if (typeof flightDur !== 'number') { flightDur = 1 } const baseDuration = 10 * (flightDur + 1) const randomDuration = Math.floor(Math.random() * 6) + Math.floor(Math.random() * 7) @@ -435,6 +438,7 @@ function inject (bot) { bot.emit('usedFirework', fireworkEntityId) } + bot._handleFireworkRocketUse = handleBotUsedFireworkRocket let fireworkEntityName if (bot.supportFeature('fireworkNamePlural')) { diff --git a/lib/plugins/inventory.js b/lib/plugins/inventory.js index f2f1583be1..8c6224f020 100644 --- a/lib/plugins/inventory.js +++ b/lib/plugins/inventory.js @@ -115,6 +115,7 @@ function inject (bot, { hideErrors }) { function activateItem (offHand = false) { bot.usingHeldItem = true sequence++ + const heldItem = offHand ? bot.inventory.slots[45] : bot.heldItem if (bot.supportFeature('useItemWithBlockPlace')) { bot._client.write('block_place', { @@ -135,6 +136,9 @@ function inject (bot, { hideErrors }) { } }) } + if (heldItem?.name === 'firework_rocket' && bot.entity?.elytraFlying) { + bot._handleFireworkRocketUse?.(`local:${sequence}`, heldItem) + } } function deactivateItem () { diff --git a/lib/plugins/physics.js b/lib/plugins/physics.js index cecae6968c..eedc712e1f 100644 --- a/lib/plugins/physics.js +++ b/lib/plugins/physics.js @@ -83,6 +83,12 @@ function inject (bot, { physicsEnabled, maxCatchupTicks }) { bot.emit('physicsTick') bot.emit('physicTick') // Deprecated, only exists to support old plugins. May be removed in the future } + if (bot.entity.elytraFlying && bot.entity.onGround) { + bot._setElytraFlyingState?.(bot.entity, false) + } + if (bot.fireworkRocketDuration > 0) { + bot.fireworkRocketDuration-- + } if (shouldUsePhysics) { updatePosition(now) } @@ -240,6 +246,7 @@ function inject (bot, { physicsEnabled, maxCatchupTicks }) { actionId: bot.supportFeature('entityActionUsesStringMapper') ? 'start_elytra_flying' : 8, jumpBoost: 0 }) + bot._setElytraFlyingState?.(bot.entity, true) } bot.setControlState = (control, state) => { diff --git a/lib/plugins/time.js b/lib/plugins/time.js index 5d1de9d1d8..306f33437a 100644 --- a/lib/plugins/time.js +++ b/lib/plugins/time.js @@ -13,13 +13,33 @@ function inject (bot) { age: null } bot._client.on('update_time', (packet) => { - const packetTime = packet.time ?? packet.gameTime ?? packet.age + const clockUpdate = readClockUpdates(packet.clockUpdates) const packetAge = packet.age ?? packet.gameTime ?? packet.time - if (packetTime == null || packetAge == null) return + if (packetAge == null) return - const time = longToBigInt(packetTime) const age = longToBigInt(packetAge) - const doDaylightCycle = packet.tickDayTime !== undefined ? !!packet.tickDayTime : time >= 0n + const previousAge = bot.time.bigAge + const previousTime = bot.time.bigTime + + let packetTime = packet.time ?? clockUpdate?.time + const daylightBasis = packetTime ?? packet.gameTime ?? packet.age + let doDaylightCycle + if (packet.tickDayTime !== undefined) { + doDaylightCycle = !!packet.tickDayTime + } else if (clockUpdate?.tickRate !== undefined) { + doDaylightCycle = clockUpdate.tickRate > 0 + } else { + doDaylightCycle = bot.time.doDaylightCycle ?? (longToBigInt(daylightBasis) >= 0n) + } + + if (packetTime == null && previousTime != null && previousAge != null) { + const ageDelta = age - previousAge + packetTime = doDaylightCycle ? previousTime + ageDelta : previousTime + } + packetTime ??= packet.gameTime ?? packet.age + if (packetTime == null) return + + const time = longToBigInt(packetTime) // When doDaylightCycle is false, we need to take the absolute value of time const finalTime = doDaylightCycle ? time : (time < 0n ? -time : time) @@ -37,6 +57,51 @@ function inject (bot) { }) } +function readClockUpdates (buffer) { + if (!buffer || buffer.length === 0) return null + let offset = 0 + const count = readVarInt(buffer, offset) + offset = count.offset + + for (let i = 0; i < count.value && offset < buffer.length; i++) { + const type = readVarInt(buffer, offset) + offset = type.offset + const time = readVarLong(buffer, offset) + offset = time.offset + const tickRate = offset + 8 <= buffer.length ? buffer.readFloatBE(offset + 4) : undefined + offset += 8 + + if (type.value === 0) return { time: time.value, tickRate } + } + return null +} + +function readVarInt (buffer, offset) { + let value = 0 + let shift = 0 + let cursor = offset + while (cursor < buffer.length) { + const byte = buffer[cursor++] + value |= (byte & 0x7f) << shift + if ((byte & 0x80) === 0) return { value, offset: cursor } + shift += 7 + } + throw new Error('Invalid varint in update_time clockUpdates') +} + +function readVarLong (buffer, offset) { + let value = 0n + let shift = 0n + let cursor = offset + while (cursor < buffer.length) { + const byte = buffer[cursor++] + value |= BigInt(byte & 0x7f) << shift + if ((byte & 0x80) === 0) return { value: BigInt.asIntN(64, value), offset: cursor } + shift += 7n + } + throw new Error('Invalid varlong in update_time clockUpdates') +} + function longToBigInt (arr) { if (typeof arr === 'bigint') return BigInt.asIntN(64, arr) if (typeof arr === 'number') return BigInt.asIntN(64, BigInt(arr)) diff --git a/test/externalTest.js b/test/externalTest.js index 4433464611..cb04c1a18f 100644 --- a/test/externalTest.js +++ b/test/externalTest.js @@ -34,6 +34,31 @@ const download = require('minecraft-wrap').download const MC_SERVER_PATH = path.join(__dirname, 'server') +async function pingServerWithRetry (port, version, attempts = 3) { + let lastError + for (let attempt = 1; attempt <= attempts; attempt++) { + try { + console.log(`pinging ${version} port : ${port} attempt ${attempt}/${attempts}`) + const results = await mc.ping({ + port, + host: '127.0.0.1', + version, + closeTimeout: 10000, + noPongTimeout: 2000 + }) + console.log('pong') + assert.ok(results.latency >= 0) + assert.ok(results.latency <= 1000) + return + } catch (err) { + lastError = err + console.log(`ping failed attempt ${attempt}/${attempts}: ${err.message}`) + if (attempt < attempts) await new Promise(resolve => setTimeout(resolve, 1000)) + } + } + throw lastError +} + for (const supportedVersion of mineflayer.testedVersions) { let PORT = 25565 const registry = require('prismarine-registry')(supportedVersion) @@ -91,20 +116,14 @@ for (const supportedVersion of mineflayer.testedVersions) { return } propOverrides['server-port'] = PORT - wrap.startServer(propOverrides, (err) => { + wrap.startServer(propOverrides, async (err) => { if (err) return done(err) - console.log(`pinging ${version.minecraftVersion} port : ${PORT}`) - mc.ping({ - port: PORT, - host: '127.0.0.1', - version: supportedVersion - }, (err, results) => { - if (err) return done(err) - console.log('pong') - assert.ok(results.latency >= 0) - assert.ok(results.latency <= 1000) + try { + await pingServerWithRetry(PORT, supportedVersion) begin() - }) + } catch (err) { + done(err) + } }) }) } else begin() diff --git a/test/externalTests/elytra.js b/test/externalTests/elytra.js index b79cc85b6f..0b277289dd 100644 --- a/test/externalTests/elytra.js +++ b/test/externalTests/elytra.js @@ -8,10 +8,16 @@ module.exports = () => async (bot) => { const Item = require('prismarine-item')(bot.registry) await bot.test.setInventorySlot(6, new Item(bot.registry.itemsByName.elytra.id, 1)) + // 1.21.6+ does not acknowledge creative armor-slot writes, so ensure the + // server-side armor slot is populated before testing elytra flight. + bot.chat(`/item replace entity ${bot.username} armor.chest with minecraft:elytra`) + await bot.test.wait(500) if (supportsFireworkRockets) { const fireworkItem = bot.registry.itemsArray.find(item => item.displayName === 'Firework Rocket') assert.ok(fireworkItem !== undefined) await bot.test.setInventorySlot(36, new Item(fireworkItem.id, 64)) + bot.chat(`/item replace entity ${bot.username} hotbar.0 with minecraft:firework_rocket 64`) + await bot.test.wait(500) } await bot.test.teleport(bot.entity.position.offset(0, 100, 0)) await bot.test.becomeSurvival() diff --git a/test/externalTests/plugins/testCommon.js b/test/externalTests/plugins/testCommon.js index e623d609b1..3675b305d1 100644 --- a/test/externalTests/plugins/testCommon.js +++ b/test/externalTests/plugins/testCommon.js @@ -123,19 +123,21 @@ function inject (bot, wrap) { } async function clearInventory () { - // Use bot.chat for /give (server console /give doesn't send inventory - // update packets on 1.21.9+). Use server console for /clear. - bot.chat('/give @a stone 1') - await onceWithCleanup(bot.inventory, 'updateSlot', { - timeout: 10000, - checkCondition: (slot, oldItem, newItem) => newItem?.name === 'stone' - }) const clearMsg = onceWithCleanup(bot, 'message', { timeout: 10000, - checkCondition: msg => msg.translate === 'commands.clear.success.single' || msg.translate === 'commands.clear.success' + checkCondition: msg => { + return msg.translate === 'commands.clear.success.single' || + msg.translate === 'commands.clear.success' || + msg.translate === 'commands.clear.failure.no.items' || + msg.translate === 'commands.clear.failure.single' + } }) bot.chat('/clear') - await clearMsg + await Promise.race([clearMsg.catch(() => {}), sleep(1000)]) + await sleep(100) + for (let slot = 0; slot < bot.inventory.slots.length; slot++) { + if (bot.inventory.slots[slot]) bot._setSlot(slot, null) + } } // you need to be in creative mode for this to work From 0839040a62c32944504fdfa243f9f0e71154d52c Mon Sep 17 00:00:00 2001 From: Marc Neuhaus Date: Tue, 12 May 2026 15:48:08 +0200 Subject: [PATCH 4/5] Use 26.1.2 dependency branches in CI --- .github/workflows/ci.yml | 9 +++------ package.json | 8 ++++---- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 002cdae81a..2cd89b62e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,15 +56,12 @@ jobs: with: node-version: 24 - name: Setup Java JDK - uses: actions/setup-java@v1.4.3 + uses: actions/setup-java@v4 with: - java-version: 21 - java-package: jre + distribution: 'temurin' + java-version: ${{ contains(matrix.versions, '26.1.2') && '26' || '21' }} - name: Install Dependencies run: npm install - - run: cd node_modules && cd minecraft-data && mv minecraft-data minecraft-data-old && git clone -b pc_26_1_2 https://github.com/PrismarineJS/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: Start Tests run: | diff --git a/package.json b/package.json index 6ec687fcef..bd4bdb4373 100644 --- a/package.json +++ b/package.json @@ -21,17 +21,17 @@ }, "license": "MIT", "dependencies": { - "minecraft-data": "^3.108.0", - "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#pc26_1_2", + "minecraft-data": "github:mneuhaus/node-minecraft-data#add-26.1.2-data-wrapper", + "minecraft-protocol": "github:mneuhaus/node-minecraft-protocol#pc26_1_2-clean", "mojangson": "^2.0.4", "prismarine-biome": "^1.1.1", "prismarine-block": "^1.22.0", "prismarine-chat": "^1.7.1", - "prismarine-chunk": "^1.39.0", + "prismarine-chunk": "github:mneuhaus/prismarine-chunk#pc26_1_2-fluid-count", "prismarine-entity": "^2.5.0", "prismarine-item": "^1.17.0", "prismarine-nbt": "^2.0.0", - "prismarine-physics": "^1.9.0", + "prismarine-physics": "github:mneuhaus/prismarine-physics#add-26.1-physics-features", "prismarine-recipe": "^1.5.0", "prismarine-registry": "^1.10.0", "prismarine-windows": "^2.9.0", From b30c85cb24d9fc7a009f61fe71a4fead516f8802 Mon Sep 17 00:00:00 2001 From: Marc Neuhaus Date: Tue, 12 May 2026 16:36:57 +0200 Subject: [PATCH 5/5] Stabilize time and bed handling across versions --- lib/plugins/bed.js | 2 +- lib/plugins/time.js | 5 ++++- test/externalTests/time.js | 34 ++++++++++++++++++++++------------ 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/lib/plugins/bed.js b/lib/plugins/bed.js index 46a76992fb..110d837614 100644 --- a/lib/plugins/bed.js +++ b/lib/plugins/bed.js @@ -25,7 +25,7 @@ function inject (bot) { const properties = bedBlock.getProperties?.() if (properties?.part) { metadata.part = properties.part === 'head' - metadata.occupied = Boolean(properties.occupied) + metadata.occupied = properties.occupied === true || properties.occupied === 'true' switch (properties.facing) { case 'west': diff --git a/lib/plugins/time.js b/lib/plugins/time.js index 306f33437a..df8f46372b 100644 --- a/lib/plugins/time.js +++ b/lib/plugins/time.js @@ -23,11 +23,14 @@ function inject (bot) { let packetTime = packet.time ?? clockUpdate?.time const daylightBasis = packetTime ?? packet.gameTime ?? packet.age + const packetDayTime = packetTime == null ? null : longToBigInt(packetTime) let doDaylightCycle if (packet.tickDayTime !== undefined) { doDaylightCycle = !!packet.tickDayTime } else if (clockUpdate?.tickRate !== undefined) { doDaylightCycle = clockUpdate.tickRate > 0 + } else if (packetDayTime != null) { + doDaylightCycle = packetDayTime >= 0n } else { doDaylightCycle = bot.time.doDaylightCycle ?? (longToBigInt(daylightBasis) >= 0n) } @@ -39,7 +42,7 @@ function inject (bot) { packetTime ??= packet.gameTime ?? packet.age if (packetTime == null) return - const time = longToBigInt(packetTime) + const time = packetDayTime ?? longToBigInt(packetTime) // When doDaylightCycle is false, we need to take the absolute value of time const finalTime = doDaylightCycle ? time : (time < 0n ? -time : time) diff --git a/test/externalTests/time.js b/test/externalTests/time.js index 31d12ea00a..c2c35fb573 100644 --- a/test/externalTests/time.js +++ b/test/externalTests/time.js @@ -42,6 +42,22 @@ module.exports = () => async (bot) => { await once(bot, 'time') } } + const waitForDaylightCycle = async (expectedValue) => { + await onceWithCleanup(bot, 'time', { + timeout: 5000, + checkCondition: () => bot.time.doDaylightCycle === expectedValue + }) + } + const setDaylightCycleAndWait = async (value) => { + setDaylightCycle(value) + try { + await waitForDaylightCycle(value) + } catch (err) { + if (!value) throw err + bot.test.sayEverywhere('/time add 1') + await waitForDaylightCycle(value) + } + } // Helper to set gamerule using the correct name for the version const setDaylightCycle = (value) => { @@ -55,8 +71,7 @@ module.exports = () => async (bot) => { // Disable daylight cycle before time transition tests to prevent // time from drifting between /time set and the assertion const originalDaylightCycle = bot.time.doDaylightCycle - setDaylightCycle(false) - await waitForTime() + await setDaylightCycleAndWait(false) // Test time transitions const timeTests = [ @@ -74,8 +89,7 @@ module.exports = () => async (bot) => { } // Re-enable daylight cycle for progression test - setDaylightCycle(true) - await waitForTime() + await setDaylightCycleAndWait(true) // Test day and moon phase progression const currentDay = bot.time.day @@ -86,17 +100,14 @@ module.exports = () => async (bot) => { assert.notStrictEqual(bot.time.moonPhase, currentPhase, 'Moon phase should change after a full day') // Test daylight cycle toggle - setDaylightCycle(false) - await waitForTime() + await setDaylightCycleAndWait(false) assert.strictEqual(bot.time.doDaylightCycle, false) - setDaylightCycle(originalDaylightCycle) - await waitForTime() + await setDaylightCycleAndWait(originalDaylightCycle) assert.strictEqual(bot.time.doDaylightCycle, originalDaylightCycle) // Disable daylight cycle again for day/night range tests - setDaylightCycle(false) - await waitForTime() + await setDaylightCycleAndWait(false) // Test day/night transitions const dayNightTests = [ @@ -112,6 +123,5 @@ module.exports = () => async (bot) => { } // Restore original daylight cycle setting - setDaylightCycle(originalDaylightCycle) - await waitForTime() + await setDaylightCycleAndWait(originalDaylightCycle) }