From 5144ed333d7e78c12c53ef5df7a07d1ad70887b3 Mon Sep 17 00:00:00 2001 From: Pix3lPirat3 Date: Fri, 18 Sep 2026 03:59:51 -0700 Subject: [PATCH 1/3] Compute dig time from block tags when the registry has them When registry.tags carries block tags (minecraft-data 1.17+), take the breaking speed from mineable/* membership and the tool's tier speed in materials.json, with the vanilla sword and shears rules (cobweb 15, sword_efficient 1.5, sword_instantly_mines instant; leaves 15, wool 5, vine/glow_lichen 2), instead of the single material table. A block can be in several mineable tags, and the harvest gate stays on harvestTools. Without tags the existing material path is unchanged. --- index.js | 48 ++++++++++++++++++++++++++++++++++++----- test/basic.test.js | 54 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index e91fb33..044a730 100644 --- a/index.js +++ b/index.js @@ -95,6 +95,41 @@ function provider (registry, { Biome, version }) { return 0 } + // Block tags (registry.tags, minecraft-data 1.13+) describe which tools mine a block since 1.17. When present they + // replace the single-material speed lookup: a block can be in several mineable/* tags, and the sword and shears + // rules below are the vanilla ToolMaterial/ShearsItem constants, which are not in any data file. + const blockTags = registry.tags?.['minecraft:block'] + const tagSets = {} + function inTag (tag, blockName) { + if (!tagSets[tag]) tagSets[tag] = new Set(blockTags?.[`minecraft:${tag}`] ?? []) + return tagSets[tag].has(`minecraft:${blockName}`) + } + const hasToolTags = Boolean(blockTags?.['minecraft:mineable/pickaxe']) + const TOOL_TYPES = ['pickaxe', 'axe', 'shovel', 'hoe'] + + function tagBreakingSpeed (block, heldItemType) { + const item = registry.items[heldItemType] + if (!item) return 1 + const name = block.name + if (item.name === 'shears') { + if (name === 'cobweb' || inTag('leaves', name)) return 15 + if (inTag('wool', name)) return 5 + if (name === 'vine' || name === 'glow_lichen') return 2 + return 1 + } + if (item.name.endsWith('_sword')) { + if (name === 'cobweb') return 15 + if (inTag('sword_instantly_mines', name)) return Infinity + if (inTag('sword_efficient', name)) return 1.5 + return 1 + } + for (const type of TOOL_TYPES) { + const speed = registry.materials[`mineable/${type}`]?.[heldItemType] + if (speed && inTag(`mineable/${type}`, name)) return speed + } + return 1 + } + function getMiningFatigueMultiplier (effectLevel) { switch (effectLevel) { case 0: return 1.0 @@ -306,14 +341,17 @@ function provider (registry, { Biome, version }) { digTime (heldItemType, creative, inWater, notOnGround, enchantments = [], effects = {}) { if (creative) return 0 - const materialToolMultipliers = registry.materials[this.material] - const isBestTool = heldItemType && materialToolMultipliers && materialToolMultipliers[heldItemType] - // Compute breaking speed multiplier let blockBreakingSpeed = 1 - if (isBestTool) { - blockBreakingSpeed = materialToolMultipliers[heldItemType] + if (heldItemType && hasToolTags) { + blockBreakingSpeed = tagBreakingSpeed(this, heldItemType) + if (blockBreakingSpeed === Infinity) return 0 + } else { + const materialToolMultipliers = registry.materials[this.material] + if (heldItemType && materialToolMultipliers && materialToolMultipliers[heldItemType]) { + blockBreakingSpeed = materialToolMultipliers[heldItemType] + } } // Efficiency is applied if tools speed multiplier is more than 1.0 diff --git a/test/basic.test.js b/test/basic.test.js index 8c16554..581c1ea 100644 --- a/test/basic.test.js +++ b/test/basic.test.js @@ -2,6 +2,60 @@ const expect = require('expect').default +// Block tags decide which tools mine a block from 1.17 on (minecraft-data tags.json, exposed as registry.tags). +// The fixture is the subset of vanilla 26.1 tags these cases need, in the tags.json shape. +describe('Dig time from block tags', () => { + const registry = require('prismarine-registry')('26.1') + registry.tags = { + 'minecraft:block': { + 'minecraft:leaves': ['minecraft:oak_leaves'], + 'minecraft:mineable/axe': ['minecraft:melon', 'minecraft:oak_log'], + 'minecraft:mineable/hoe': ['minecraft:oak_leaves'], + 'minecraft:mineable/pickaxe': ['minecraft:iron_ore', 'minecraft:obsidian', 'minecraft:stone'], + 'minecraft:sword_efficient': ['minecraft:melon'], + 'minecraft:sword_instantly_mines': ['minecraft:bamboo'], + 'minecraft:wool': ['minecraft:white_wool'] + } + } + const Block = require('prismarine-block')(registry) + const block = name => Block.fromStateId(registry.blocksByName[name].defaultState, 0) + const item = name => registry.itemsByName[name].id + const dig = (blockName, itemName, enchantments = []) => block(blockName).digTime(itemName ? item(itemName) : null, false, false, false, enchantments) + + it('uses the pickaxe tier speed for a tier-gated ore', () => { + expect(dig('iron_ore', 'diamond_pickaxe')).toBe(600) + expect(dig('obsidian', 'diamond_pickaxe')).toBe(9400) + }) + it('applies efficiency on top of the tag speed', () => { + expect(dig('iron_ore', 'diamond_pickaxe', [{ name: 'efficiency', lvl: 5 }])).toBe(150) + }) + it('keeps the harvest penalty for a tool below the required tier', () => { + expect(dig('iron_ore', 'wooden_pickaxe')).toBe(7500) + expect(dig('stone', null)).toBe(7500) + }) + it('uses the vanilla sword rules', () => { + expect(dig('melon', 'diamond_sword')).toBe(1000) + expect(dig('melon', 'diamond_axe')).toBe(200) + expect(dig('bamboo', 'diamond_sword')).toBe(0) + }) + it('uses the vanilla shears rules', () => { + expect(dig('oak_leaves', 'shears')).toBe(0) + expect(dig('white_wool', 'shears')).toBe(250) + }) + it('ignores tools that do not apply to the block', () => { + expect(dig('oak_log', 'diamond_pickaxe')).toBe(dig('oak_log', null)) + }) +}) + +describe('Dig time without block tags (before 1.17)', () => { + const registry = require('prismarine-registry')('1.16.4') + const Block = require('prismarine-block')(registry) + it('mines stone with an iron pickaxe from materials', () => { + const stone = Block.fromStateId(registry.blocksByName.stone.defaultState, 0) + expect(stone.digTime(registry.itemsByName.iron_pickaxe.id, false, false, false)).toBe(400) + }) +}) + // https://minecraft.gamepedia.com/Breaking#Blocks_by_hardness describe('Dig time', () => { describe('1.20.5', () => { From 0240a4b66120170a7844cf34ae6f21f8a49a2a38 Mon Sep 17 00:00:00 2001 From: Pix3lPirat3 Date: Fri, 18 Sep 2026 15:06:19 -0700 Subject: [PATCH 2/3] Keep the pre-1.20 sword rules and bamboo's instant break in the tag path Before 1.20 SwordItem.getDestroySpeed gives 1.5 for the PLANT, REPLACEABLE_PLANT and VEGETABLE materials and the leaves tag; there is no sword_efficient tag to read, so the tag path returned 1 and a diamond sword on leaves took 300 ms instead of 200 ms on 1.17 to 1.19. When the registry has no sword_efficient tag, take the sword speed from the material table of those versions, which encodes exactly those rules. Bamboo and bamboo saplings break instantly with a sword through BambooStalkBlock/BambooSaplingBlock.getDestroyProgress until 1.21.4, the sword_instantly_mines tag only exists from 1.21.5; special-case the two blocks when the tag is absent. Checked every diggable block against every sword on 1.17.1, 1.18.2 and 1.19.4 with the real tags: the tag path now equals the material path everywhere except bamboo, where it is instant like the game. --- index.js | 11 ++++++++--- test/basic.test.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 044a730..80ed4d3 100644 --- a/index.js +++ b/index.js @@ -119,9 +119,14 @@ function provider (registry, { Biome, version }) { } if (item.name.endsWith('_sword')) { if (name === 'cobweb') return 15 - if (inTag('sword_instantly_mines', name)) return Infinity - if (inTag('sword_efficient', name)) return 1.5 - return 1 + // bamboo breaks instantly with a sword: BambooStalkBlock/BambooSaplingBlock.getDestroyProgress up to 1.21.4, the + // sword_instantly_mines tag from 1.21.5 + const instantly = blockTags['minecraft:sword_instantly_mines'] ? inTag('sword_instantly_mines', name) : (name === 'bamboo' || name === 'bamboo_sapling') + if (instantly) return Infinity + // 1.20+: SwordItem.getDestroySpeed is 1.5 for the sword_efficient tag. 1.17 to 1.19: it is 1.5 for the PLANT, + // REPLACEABLE_PLANT and VEGETABLE materials and the leaves tag, which the material table of those versions encodes + if (blockTags['minecraft:sword_efficient']) return inTag('sword_efficient', name) ? 1.5 : 1 + return registry.materials[block.material]?.[heldItemType] ?? 1 } for (const type of TOOL_TYPES) { const speed = registry.materials[`mineable/${type}`]?.[heldItemType] diff --git a/test/basic.test.js b/test/basic.test.js index 581c1ea..7ca768c 100644 --- a/test/basic.test.js +++ b/test/basic.test.js @@ -47,6 +47,35 @@ describe('Dig time from block tags', () => { }) }) +describe('Dig time from block tags before the sword tags (1.17 to 1.19)', () => { + const registry = require('prismarine-registry')('1.19.4') + registry.tags = { + 'minecraft:block': { + 'minecraft:leaves': ['minecraft:oak_leaves'], + 'minecraft:mineable/axe': ['minecraft:bamboo', 'minecraft:melon'], + 'minecraft:mineable/hoe': ['minecraft:oak_leaves'], + 'minecraft:mineable/pickaxe': ['minecraft:stone'] + } + } + const Block = require('prismarine-block')(registry) + const block = name => Block.fromStateId(registry.blocksByName[name].defaultState, 0) + const dig = (blockName, itemName) => block(blockName).digTime(registry.itemsByName[itemName].id, false, false, false) + + it('keeps the sword speeds of the material table', () => { + expect(dig('oak_leaves', 'diamond_sword')).toBe(200) // SwordItem: 1.5 for leaves + expect(dig('melon', 'diamond_sword')).toBe(1000) // and for the VEGETABLE material + expect(dig('stone', 'diamond_sword')).toBe(7500) + }) + it('mines bamboo instantly with a sword', () => { + expect(dig('bamboo', 'diamond_sword')).toBe(0) + expect(dig('bamboo', 'diamond_axe')).toBe(200) + }) + it('still takes the tool tiers from the mineable tags', () => { + expect(dig('oak_leaves', 'diamond_hoe')).toBe(0) + expect(dig('stone', 'diamond_pickaxe')).toBe(300) + }) +}) + describe('Dig time without block tags (before 1.17)', () => { const registry = require('prismarine-registry')('1.16.4') const Block = require('prismarine-block')(registry) From 528850dbc48f85dc26c2ed2ebc7500f6d083b0ce Mon Sep 17 00:00:00 2001 From: Pix3lPirat3 Date: Sun, 20 Sep 2026 14:04:00 -0700 Subject: [PATCH 3/3] Derive pre-1.20 sword speed from tags, not materials SwordItem.getDestroySpeed is 1.5 for the leaves tag and the plant, vegetable and vine materials on 1.17-1.19. Reading it from the material table's composite sword speeds broke once tags were regenerated (leaves dropped from 1.5 to 1); derive it from the leaves tag and material names instead. Covers vine in the older-version test. --- index.js | 12 +++++++++--- test/basic.test.js | 11 +++++++---- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index 80ed4d3..d214f62 100644 --- a/index.js +++ b/index.js @@ -123,10 +123,16 @@ function provider (registry, { Biome, version }) { // sword_instantly_mines tag from 1.21.5 const instantly = blockTags['minecraft:sword_instantly_mines'] ? inTag('sword_instantly_mines', name) : (name === 'bamboo' || name === 'bamboo_sapling') if (instantly) return Infinity - // 1.20+: SwordItem.getDestroySpeed is 1.5 for the sword_efficient tag. 1.17 to 1.19: it is 1.5 for the PLANT, - // REPLACEABLE_PLANT and VEGETABLE materials and the leaves tag, which the material table of those versions encodes + // 1.20+: SwordItem.getDestroySpeed is 1.5 for the sword_efficient tag. if (blockTags['minecraft:sword_efficient']) return inTag('sword_efficient', name) ? 1.5 : 1 - return registry.materials[block.material]?.[heldItemType] ?? 1 + // 1.17 to 1.19 have no sword_efficient tag: getDestroySpeed returns 1.5 for the leaves tag and the PLANT, + // REPLACEABLE_PLANT, VEGETABLE (gourd) and VINE materials, 1 otherwise. These are ToolMaterial constants, so + // encode them here rather than trusting the material table, whose composite sword speeds do not survive + // regeneration (e.g. leaves' sword speed drops from 1.5 to 1 when the tags are rebuilt). + if (inTag('leaves', name)) return 1.5 + const materials = (block.material ?? '').split(';') + if (materials.some(m => m === 'plant' || m === 'gourd' || m === 'vine_or_glow_lichen')) return 1.5 + return 1 } for (const type of TOOL_TYPES) { const speed = registry.materials[`mineable/${type}`]?.[heldItemType] diff --git a/test/basic.test.js b/test/basic.test.js index 7ca768c..13d805a 100644 --- a/test/basic.test.js +++ b/test/basic.test.js @@ -61,10 +61,13 @@ describe('Dig time from block tags before the sword tags (1.17 to 1.19)', () => const block = name => Block.fromStateId(registry.blocksByName[name].defaultState, 0) const dig = (blockName, itemName) => block(blockName).digTime(registry.itemsByName[itemName].id, false, false, false) - it('keeps the sword speeds of the material table', () => { - expect(dig('oak_leaves', 'diamond_sword')).toBe(200) // SwordItem: 1.5 for leaves - expect(dig('melon', 'diamond_sword')).toBe(1000) // and for the VEGETABLE material - expect(dig('stone', 'diamond_sword')).toBe(7500) + it('applies the pre-1.20 sword rule from the leaves tag and plant materials', () => { + // SwordItem.getDestroySpeed is 1.5 for the leaves tag and the PLANT/VEGETABLE/VINE materials. This comes from the + // tag and material names, not the material table's composite sword speeds, which drop to 1 when tags are regenerated. + expect(dig('oak_leaves', 'diamond_sword')).toBe(200) // leaves tag -> 1.5 + expect(dig('melon', 'diamond_sword')).toBe(1000) // gourd (VEGETABLE) material -> 1.5 + expect(dig('vine', 'diamond_sword')).toBe(200) // vine material -> 1.5 + expect(dig('stone', 'diamond_sword')).toBe(7500) // no sword rule -> 1 }) it('mines bamboo instantly with a sword', () => { expect(dig('bamboo', 'diamond_sword')).toBe(0)