From 32bd5172623747f6efb9b299a929b6b856cd5922 Mon Sep 17 00:00:00 2001 From: Flavio Felder Date: Tue, 22 Sep 2026 01:57:40 +0200 Subject: [PATCH 01/13] Register axes through a macro API instead of five hand-edited files timber:api/add_axe {item, durability, label} adds an axe. The seven vanilla axes are registered the same way, other datapacks use the function tag timber:register_axes. Scoreboards, the settings button and the durability are made from the registration. An unknown item (mod not installed) adds nothing. Co-Authored-By: Claude Sonnet 5 --- 121/data/timber/item_modifier/wear_out.json | 222 ------------------ 121/data/timber/predicate/used_axe.json | 68 ------ README.md | 6 +- data/timber/function/api/add_axe.mcfunction | 8 + data/timber/function/axes/create.mcfunction | 22 ++ .../function/axes/create_failed.mcfunction | 2 + data/timber/function/axes/handle.mcfunction | 6 + data/timber/function/axes/init.mcfunction | 26 ++ .../function/axes/make_objective.mcfunction | 4 + .../function/axes/name_modded.mcfunction | 1 + .../function/axes/name_objective.mcfunction | 1 + .../function/axes/name_vanilla.mcfunction | 1 + data/timber/function/axes/register.mcfunction | 7 + .../function/axes/remove_objective.mcfunction | 1 + .../axes/remove_objectives.mcfunction | 5 + data/timber/function/axes/scan.mcfunction | 3 + .../timber/function/axes/scan_next.mcfunction | 4 + data/timber/function/axes/scan_one.mcfunction | 1 + .../function/axes/split_done.mcfunction | 9 + .../function/axes/split_failed.mcfunction | 2 + .../function/axes/split_step.mcfunction | 10 + .../timber/function/axes/uninstall.mcfunction | 9 + data/timber/function/init.mcfunction | 11 +- data/timber/function/main.mcfunction | 2 +- .../settings/default_values.mcfunction | 7 - .../function/settings/dispatch.mcfunction | 9 +- .../function/settings/flip/axe.mcfunction | 9 + .../function/settings/flip/axe_at.mcfunction | 1 + .../settings/flip/axe_toggle.mcfunction | 4 + .../settings/flip/copper_axe.mcfunction | 5 - .../settings/flip/diamond_axe.mcfunction | 5 - .../settings/flip/golden_axe.mcfunction | 5 - .../settings/flip/iron_axe.mcfunction | 5 - .../settings/flip/netherite_axe.mcfunction | 5 - .../settings/flip/stone_axe.mcfunction | 5 - .../settings/flip/wooden_axe.mcfunction | 5 - .../function/settings/open/tools.mcfunction | 18 +- .../settings/open/tools_button.mcfunction | 2 + .../settings/open/tools_next.mcfunction | 6 + .../function/tool/durability_check.mcfunction | 10 +- .../function/tool/durability_max.mcfunction | 8 - .../function/tool/set_damage.mcfunction | 3 + data/timber/function/tool/wear_out.mcfunction | 13 +- data/timber/function/tool/worn_out.mcfunction | 16 +- data/timber/function/uninstall.mcfunction | 10 +- data/timber/function/utils/reset.mcfunction | 9 - .../timber/function/utils/used_axe.mcfunction | 19 +- data/timber/item_modifier/wear_out.json | 177 -------------- data/timber/predicate/used_axe.json | 68 ------ data/timber/tags/function/register_axes.json | 3 + 50 files changed, 175 insertions(+), 683 deletions(-) delete mode 100644 121/data/timber/item_modifier/wear_out.json delete mode 100644 121/data/timber/predicate/used_axe.json create mode 100644 data/timber/function/api/add_axe.mcfunction create mode 100644 data/timber/function/axes/create.mcfunction create mode 100644 data/timber/function/axes/create_failed.mcfunction create mode 100644 data/timber/function/axes/handle.mcfunction create mode 100644 data/timber/function/axes/init.mcfunction create mode 100644 data/timber/function/axes/make_objective.mcfunction create mode 100644 data/timber/function/axes/name_modded.mcfunction create mode 100644 data/timber/function/axes/name_objective.mcfunction create mode 100644 data/timber/function/axes/name_vanilla.mcfunction create mode 100644 data/timber/function/axes/register.mcfunction create mode 100644 data/timber/function/axes/remove_objective.mcfunction create mode 100644 data/timber/function/axes/remove_objectives.mcfunction create mode 100644 data/timber/function/axes/scan.mcfunction create mode 100644 data/timber/function/axes/scan_next.mcfunction create mode 100644 data/timber/function/axes/scan_one.mcfunction create mode 100644 data/timber/function/axes/split_done.mcfunction create mode 100644 data/timber/function/axes/split_failed.mcfunction create mode 100644 data/timber/function/axes/split_step.mcfunction create mode 100644 data/timber/function/axes/uninstall.mcfunction create mode 100644 data/timber/function/settings/flip/axe.mcfunction create mode 100644 data/timber/function/settings/flip/axe_at.mcfunction create mode 100644 data/timber/function/settings/flip/axe_toggle.mcfunction delete mode 100644 data/timber/function/settings/flip/copper_axe.mcfunction delete mode 100644 data/timber/function/settings/flip/diamond_axe.mcfunction delete mode 100644 data/timber/function/settings/flip/golden_axe.mcfunction delete mode 100644 data/timber/function/settings/flip/iron_axe.mcfunction delete mode 100644 data/timber/function/settings/flip/netherite_axe.mcfunction delete mode 100644 data/timber/function/settings/flip/stone_axe.mcfunction delete mode 100644 data/timber/function/settings/flip/wooden_axe.mcfunction create mode 100644 data/timber/function/settings/open/tools_button.mcfunction create mode 100644 data/timber/function/settings/open/tools_next.mcfunction delete mode 100644 data/timber/function/tool/durability_max.mcfunction create mode 100644 data/timber/function/tool/set_damage.mcfunction delete mode 100644 data/timber/item_modifier/wear_out.json delete mode 100644 data/timber/predicate/used_axe.json create mode 100644 data/timber/tags/function/register_axes.json diff --git a/121/data/timber/item_modifier/wear_out.json b/121/data/timber/item_modifier/wear_out.json deleted file mode 100644 index 630670c..0000000 --- a/121/data/timber/item_modifier/wear_out.json +++ /dev/null @@ -1,222 +0,0 @@ -{ - "type": "minecraft:sequence", - "functions": [ - { - "type": "minecraft:set_damage", - "damage": { - "type": "minecraft:mul", - "inputs": [ - { - "type": "minecraft:from_int", - "input": { - "type": "minecraft:score", - "target": { - "type": "minecraft:fixed", - "name": "damage" - }, - "score": "timber" - } - }, - 0.01694915254237288 - ] - }, - "condition": { - "type": "minecraft:entity_properties", - "entity": "this", - "predicate": { - "equipment": { - "mainhand": { - "items": "minecraft:wooden_axe" - } - } - } - } - }, - { - "type": "minecraft:set_damage", - "damage": { - "type": "minecraft:mul", - "inputs": [ - { - "type": "minecraft:from_int", - "input": { - "type": "minecraft:score", - "target": { - "type": "minecraft:fixed", - "name": "damage" - }, - "score": "timber" - } - }, - 0.007633587786259542 - ] - }, - "condition": { - "type": "minecraft:entity_properties", - "entity": "this", - "predicate": { - "equipment": { - "mainhand": { - "items": "minecraft:stone_axe" - } - } - } - } - }, - { - "type": "minecraft:set_damage", - "damage": { - "type": "minecraft:mul", - "inputs": [ - { - "type": "minecraft:from_int", - "input": { - "type": "minecraft:score", - "target": { - "type": "minecraft:fixed", - "name": "damage" - }, - "score": "timber" - } - }, - 0.005263157894736842 - ] - }, - "condition": { - "type": "minecraft:entity_properties", - "entity": "this", - "predicate": { - "equipment": { - "mainhand": { - "items": "minecraft:copper_axe" - } - } - } - } - }, - { - "type": "minecraft:set_damage", - "damage": { - "type": "minecraft:mul", - "inputs": [ - { - "type": "minecraft:from_int", - "input": { - "type": "minecraft:score", - "target": { - "type": "minecraft:fixed", - "name": "damage" - }, - "score": "timber" - } - }, - 0.004 - ] - }, - "condition": { - "type": "minecraft:entity_properties", - "entity": "this", - "predicate": { - "equipment": { - "mainhand": { - "items": "minecraft:iron_axe" - } - } - } - } - }, - { - "type": "minecraft:set_damage", - "damage": { - "type": "minecraft:mul", - "inputs": [ - { - "type": "minecraft:from_int", - "input": { - "type": "minecraft:score", - "target": { - "type": "minecraft:fixed", - "name": "damage" - }, - "score": "timber" - } - }, - 0.03125 - ] - }, - "condition": { - "type": "minecraft:entity_properties", - "entity": "this", - "predicate": { - "equipment": { - "mainhand": { - "items": "minecraft:golden_axe" - } - } - } - } - }, - { - "type": "minecraft:set_damage", - "damage": { - "type": "minecraft:mul", - "inputs": [ - { - "type": "minecraft:from_int", - "input": { - "type": "minecraft:score", - "target": { - "type": "minecraft:fixed", - "name": "damage" - }, - "score": "timber" - } - }, - 0.0006393861892583121 - ] - }, - "condition": { - "type": "minecraft:entity_properties", - "entity": "this", - "predicate": { - "equipment": { - "mainhand": { - "items": "minecraft:diamond_axe" - } - } - } - } - }, - { - "type": "minecraft:set_damage", - "damage": { - "type": "minecraft:mul", - "inputs": [ - { - "type": "minecraft:from_int", - "input": { - "type": "minecraft:score", - "target": { - "type": "minecraft:fixed", - "name": "damage" - }, - "score": "timber" - } - }, - 0.0004923682914820286 - ] - }, - "condition": { - "type": "minecraft:entity_properties", - "entity": "this", - "predicate": { - "equipment": { - "mainhand": { - "items": "minecraft:netherite_axe" - } - } - } - } - } - ] -} diff --git a/121/data/timber/predicate/used_axe.json b/121/data/timber/predicate/used_axe.json deleted file mode 100644 index 74c7f1c..0000000 --- a/121/data/timber/predicate/used_axe.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "type": "minecraft:any_of", - "terms": [ - { - "type": "minecraft:entity_scores", - "entity": "this", - "scores": { - "timber_w_axe": { - "min": 1 - } - } - }, - { - "type": "minecraft:entity_scores", - "entity": "this", - "scores": { - "timber_s_axe": { - "min": 1 - } - } - }, - { - "type": "minecraft:entity_scores", - "entity": "this", - "scores": { - "timber_c_axe": { - "min": 1 - } - } - }, - { - "type": "minecraft:entity_scores", - "entity": "this", - "scores": { - "timber_i_axe": { - "min": 1 - } - } - }, - { - "type": "minecraft:entity_scores", - "entity": "this", - "scores": { - "timber_g_axe": { - "min": 1 - } - } - }, - { - "type": "minecraft:entity_scores", - "entity": "this", - "scores": { - "timber_d_axe": { - "min": 1 - } - } - }, - { - "type": "minecraft:entity_scores", - "entity": "this", - "scores": { - "timber_n_axe": { - "min": 1 - } - } - } - ] -} diff --git a/README.md b/README.md index aab7bc6..00b56e3 100644 --- a/README.md +++ b/README.md @@ -178,9 +178,9 @@ Status | Server software | Notes ✅ | Vanilla / Realm ✅ | Forge | ✅ | Fabric | -🆗 | CraftBukkit | - Huge mushrooms don't always drop loot - Whole stack of saplings that got auto-planted get deleted rather than only one -🆗 | Spigot | - Huge mushrooms don't always drop loot - Whole stack of saplings that got auto-planted get deleted rather than only one -🆗 | Paper | - Huge mushrooms don't always drop loot - Whole stack of saplings that got auto-planted get deleted rather than only one +🆗 | CraftBukkit | - Huge mushrooms don't always drop loot
- Whole stack of saplings that got auto-planted get deleted rather than only one +🆗 | Spigot | - Huge mushrooms don't always drop loot
- Whole stack of saplings that got auto-planted get deleted rather than only one +🆗 | Paper | - Huge mushrooms don't always drop loot
- Whole stack of saplings that got auto-planted get deleted rather than only one As of October 4th, 2020 diff --git a/data/timber/function/api/add_axe.mcfunction b/data/timber/function/api/add_axe.mcfunction new file mode 100644 index 0000000..24f9b0f --- /dev/null +++ b/data/timber/function/api/add_axe.mcfunction @@ -0,0 +1,8 @@ +# Adds an axe: timber chops when it is used on a log. Call it from a function of your datapack, see the README. +# It can be called again for the same item, the axe is replaced. +# function timber:api/add_axe {item:"supertools:emeraldaxe", durability:500, label:"Emerald Axe"} +# item the item id, also add it to the item tag timber:axes (mushroom stems only drop their marker for tools of that tag) +# durability the max durability of the item +# label the name in the settings menu +$data modify storage timber:axes build set value {item:"$(item)",durability:$(durability),label:"$(label)"} +function timber:axes/register diff --git a/data/timber/function/axes/create.mcfunction b/data/timber/function/axes/create.mcfunction new file mode 100644 index 0000000..1bb6e45 --- /dev/null +++ b/data/timber/function/axes/create.mcfunction @@ -0,0 +1,22 @@ +# the scoreboard that counts the use of the axe +# An axe that was added before gets a new scoreboard. When the item doesn't exist (the mod is not installed) the scoreboard can't be +# made: then the axe is not added, otherwise the scan would look at a scoreboard that isn't there every tick. +# An unknown criterion is an error of the command when the macro is filled in, not when it runs: the command is in its own function, +# so that only its call fails and this function goes on. +$scoreboard objectives remove $(obj) +scoreboard players set #axe_ok timber 0 +execute store success score #axe_ok timber run function timber:axes/make_objective with storage timber:axes build +execute if score #axe_ok timber matches 0 run return run function timber:axes/create_failed with storage timber:axes build +# enabled by default (the value stays when it is set already) +$execute unless score $(holder) timber = $(holder) timber run scoreboard players set $(holder) timber 1 +# replaces an axe with the same item id +$data remove storage timber:axes list[{item:"$(item)"}] +data modify storage timber:axes entry set value {} +data modify storage timber:axes entry.item set from storage timber:axes build.item +data modify storage timber:axes entry.durability set from storage timber:axes build.durability +data modify storage timber:axes entry.label set from storage timber:axes build.label +data modify storage timber:axes entry.obj set from storage timber:axes build.obj +data modify storage timber:axes entry.holder set from storage timber:axes build.holder +data modify storage timber:axes list append from storage timber:axes entry +data remove storage timber:axes build +data remove storage timber:axes entry diff --git a/data/timber/function/axes/create_failed.mcfunction b/data/timber/function/axes/create_failed.mcfunction new file mode 100644 index 0000000..2c3f40b --- /dev/null +++ b/data/timber/function/axes/create_failed.mcfunction @@ -0,0 +1,2 @@ +$tellraw @a[tag=!noTimberWarning] ["",{text:"Timber Datapack Warning: ",color:"red"},{text:"The item $(item) doesn't exist (is the mod installed?). The axe was not added."}] +data remove storage timber:axes build diff --git a/data/timber/function/axes/handle.mcfunction b/data/timber/function/axes/handle.mcfunction new file mode 100644 index 0000000..b134f5c --- /dev/null +++ b/data/timber/function/axes/handle.mcfunction @@ -0,0 +1,6 @@ +# Run as the player who used the axe. The values of the axe are for the functions of this chop. +$scoreboard players set @s $(obj) 0 +$scoreboard players set axe_max timber $(durability) +$scoreboard players operation axe_on timber = $(holder) timber +# is this axe enabled in the settings and did the player not switch timber off? +execute unless score @s timber_disabled matches 1.. if score axe_on timber matches 1.. run function timber:utils/check_max_command_sequence_length diff --git a/data/timber/function/axes/init.mcfunction b/data/timber/function/axes/init.mcfunction new file mode 100644 index 0000000..1444290 --- /dev/null +++ b/data/timber/function/axes/init.mcfunction @@ -0,0 +1,26 @@ +# Sets up the axes at every load: forgets the ones of the last load, registers the vanilla axes, then asks the other datapacks. +# Their functions are in the function tag #timber:register_axes, so the order of the datapacks doesn't matter. + +# scoreboards of axes that are not registered any more (and of the old version, where they were fixed) +data modify storage timber:axes queue set from storage timber:axes list +function timber:axes/remove_objectives +data remove storage timber:axes list +scoreboard objectives remove timber_w_axe +scoreboard objectives remove timber_s_axe +scoreboard objectives remove timber_c_axe +scoreboard objectives remove timber_i_axe +scoreboard objectives remove timber_g_axe +scoreboard objectives remove timber_d_axe +scoreboard objectives remove timber_n_axe + +# vanilla axes, in the order of the settings menu +function timber:api/add_axe {item:"minecraft:wooden_axe",durability:59,label:"Wooden Axe"} +function timber:api/add_axe {item:"minecraft:stone_axe",durability:131,label:"Stone Axe"} +function timber:api/add_axe {item:"minecraft:copper_axe",durability:190,label:"Copper Axe"} +function timber:api/add_axe {item:"minecraft:iron_axe",durability:250,label:"Iron Axe"} +function timber:api/add_axe {item:"minecraft:golden_axe",durability:32,label:"Golden Axe"} +function timber:api/add_axe {item:"minecraft:diamond_axe",durability:1561,label:"Diamond Axe"} +function timber:api/add_axe {item:"minecraft:netherite_axe",durability:2031,label:"Netherite Axe"} + +# axes of other datapacks +function #timber:register_axes diff --git a/data/timber/function/axes/make_objective.mcfunction b/data/timber/function/axes/make_objective.mcfunction new file mode 100644 index 0000000..0f0856a --- /dev/null +++ b/data/timber/function/axes/make_objective.mcfunction @@ -0,0 +1,4 @@ +# a macro function: when the criterion is unknown the arguments can not be filled in and the call of this function fails, then the caller +# stores 0. Only a function that returns something counts as successful. +$scoreboard objectives add $(obj) minecraft.used:$(ns).$(path) +return 1 diff --git a/data/timber/function/axes/name_modded.mcfunction b/data/timber/function/axes/name_modded.mcfunction new file mode 100644 index 0000000..d09d1c7 --- /dev/null +++ b/data/timber/function/axes/name_modded.mcfunction @@ -0,0 +1 @@ +$data modify storage timber:axes build.holder set value "$(ns).$(path)" diff --git a/data/timber/function/axes/name_objective.mcfunction b/data/timber/function/axes/name_objective.mcfunction new file mode 100644 index 0000000..b09b3ce --- /dev/null +++ b/data/timber/function/axes/name_objective.mcfunction @@ -0,0 +1 @@ +$data modify storage timber:axes build.obj set value "timber_axe.$(ns).$(path)" diff --git a/data/timber/function/axes/name_vanilla.mcfunction b/data/timber/function/axes/name_vanilla.mcfunction new file mode 100644 index 0000000..344d4d9 --- /dev/null +++ b/data/timber/function/axes/name_vanilla.mcfunction @@ -0,0 +1 @@ +$data modify storage timber:axes build.holder set value "$(path)" diff --git a/data/timber/function/axes/register.mcfunction b/data/timber/function/axes/register.mcfunction new file mode 100644 index 0000000..93d4053 --- /dev/null +++ b/data/timber/function/axes/register.mcfunction @@ -0,0 +1,7 @@ +# Registers the axe in the storage timber:axes build (item, durability, label), called by timber:api/add_axe. +# 1. split the item id at the colon: namespace and path, the scoreboard criterion needs them (minecraft.used:namespace.path) +data modify storage timber:axes build.pos set value 0 +data modify storage timber:axes build.next set value 1 +scoreboard players set #axe_pos timber 0 +scoreboard players set #axe_next timber 1 +function timber:axes/split_step with storage timber:axes build diff --git a/data/timber/function/axes/remove_objective.mcfunction b/data/timber/function/axes/remove_objective.mcfunction new file mode 100644 index 0000000..0953e4f --- /dev/null +++ b/data/timber/function/axes/remove_objective.mcfunction @@ -0,0 +1 @@ +$scoreboard objectives remove $(obj) diff --git a/data/timber/function/axes/remove_objectives.mcfunction b/data/timber/function/axes/remove_objectives.mcfunction new file mode 100644 index 0000000..a5f6cd1 --- /dev/null +++ b/data/timber/function/axes/remove_objectives.mcfunction @@ -0,0 +1,5 @@ +# removes the scoreboard of every axe in the queue +execute unless data storage timber:axes queue[0] run return 0 +function timber:axes/remove_objective with storage timber:axes queue[0] +data remove storage timber:axes queue[0] +function timber:axes/remove_objectives diff --git a/data/timber/function/axes/scan.mcfunction b/data/timber/function/axes/scan.mcfunction new file mode 100644 index 0000000..0fcea76 --- /dev/null +++ b/data/timber/function/axes/scan.mcfunction @@ -0,0 +1,3 @@ +# every tick: has a player used one of the registered axes? (the scoreboards count the use of an item) +data modify storage timber:axes queue set from storage timber:axes list +function timber:axes/scan_next diff --git a/data/timber/function/axes/scan_next.mcfunction b/data/timber/function/axes/scan_next.mcfunction new file mode 100644 index 0000000..f7c2867 --- /dev/null +++ b/data/timber/function/axes/scan_next.mcfunction @@ -0,0 +1,4 @@ +execute unless data storage timber:axes queue[0] run return 0 +function timber:axes/scan_one with storage timber:axes queue[0] +data remove storage timber:axes queue[0] +function timber:axes/scan_next diff --git a/data/timber/function/axes/scan_one.mcfunction b/data/timber/function/axes/scan_one.mcfunction new file mode 100644 index 0000000..fb34574 --- /dev/null +++ b/data/timber/function/axes/scan_one.mcfunction @@ -0,0 +1 @@ +$execute as @a[scores={$(obj)=1..},tag=!global.ignore] at @s run function timber:axes/handle with storage timber:axes queue[0] diff --git a/data/timber/function/axes/split_done.mcfunction b/data/timber/function/axes/split_done.mcfunction new file mode 100644 index 0000000..c4fc013 --- /dev/null +++ b/data/timber/function/axes/split_done.mcfunction @@ -0,0 +1,9 @@ +# pos is the position of the colon +$data modify storage timber:axes build.ns set string storage timber:axes build.item 0 $(pos) +$data modify storage timber:axes build.path set string storage timber:axes build.item $(next) +# the name of the score that says if the axe is enabled in the settings: the item name for vanilla ones (iron_axe), namespace.name for the others +execute if data storage timber:axes build{ns:"minecraft"} run function timber:axes/name_vanilla with storage timber:axes build +execute unless data storage timber:axes build{ns:"minecraft"} run function timber:axes/name_modded with storage timber:axes build +# the scoreboard that counts how often the axe was used +function timber:axes/name_objective with storage timber:axes build +function timber:axes/create with storage timber:axes build diff --git a/data/timber/function/axes/split_failed.mcfunction b/data/timber/function/axes/split_failed.mcfunction new file mode 100644 index 0000000..8472f31 --- /dev/null +++ b/data/timber/function/axes/split_failed.mcfunction @@ -0,0 +1,2 @@ +$tellraw @a[tag=!noTimberWarning] ["",{text:"Timber Datapack Warning: ",color:"red"},{text:"timber:api/add_axe needs an item id like namespace:name, got \"$(item)\". The axe was not added."}] +data remove storage timber:axes build diff --git a/data/timber/function/axes/split_step.mcfunction b/data/timber/function/axes/split_step.mcfunction new file mode 100644 index 0000000..84fac5f --- /dev/null +++ b/data/timber/function/axes/split_step.mcfunction @@ -0,0 +1,10 @@ +# looks at one character of the item id, at the position pos +$data modify storage timber:axes build.ch set string storage timber:axes build.item $(pos) $(next) +execute if data storage timber:axes build{ch:":"} run return run function timber:axes/split_done with storage timber:axes build +# no colon in the first 64 characters: not an item id +execute if score #axe_pos timber matches 64.. run return run function timber:axes/split_failed with storage timber:axes build +scoreboard players add #axe_pos timber 1 +scoreboard players add #axe_next timber 1 +execute store result storage timber:axes build.pos int 1 run scoreboard players get #axe_pos timber +execute store result storage timber:axes build.next int 1 run scoreboard players get #axe_next timber +function timber:axes/split_step with storage timber:axes build diff --git a/data/timber/function/axes/uninstall.mcfunction b/data/timber/function/axes/uninstall.mcfunction new file mode 100644 index 0000000..3b668a0 --- /dev/null +++ b/data/timber/function/axes/uninstall.mcfunction @@ -0,0 +1,9 @@ +# used by uninstall: the scoreboards of the axes and everything that is stored for them +data modify storage timber:axes queue set from storage timber:axes list +function timber:axes/remove_objectives +data remove storage timber:axes list +data remove storage timber:axes queue +data remove storage timber:axes build +data remove storage timber:axes entry +data remove storage timber:axes pick +data remove storage timber:axes wear diff --git a/data/timber/function/init.mcfunction b/data/timber/function/init.mcfunction index 7f36fe3..03d902b 100644 --- a/data/timber/function/init.mcfunction +++ b/data/timber/function/init.mcfunction @@ -2,13 +2,6 @@ gamerule minecraft:max_command_sequence_length 1000000000 # create scoreboards -scoreboard objectives add timber_w_axe minecraft.used:minecraft.wooden_axe -scoreboard objectives add timber_s_axe minecraft.used:minecraft.stone_axe -scoreboard objectives add timber_c_axe minecraft.used:minecraft.copper_axe -scoreboard objectives add timber_i_axe minecraft.used:minecraft.iron_axe -scoreboard objectives add timber_g_axe minecraft.used:minecraft.golden_axe -scoreboard objectives add timber_d_axe minecraft.used:minecraft.diamond_axe -scoreboard objectives add timber_n_axe minecraft.used:minecraft.netherite_axe scoreboard objectives add timber dummy scoreboard objectives add TimberToggle trigger {text:"Timber Toggle",color:"#724E21"} scoreboard objectives add TimberSettings trigger {text:"Timber Settings",color:"#724E21"} @@ -28,7 +21,9 @@ scoreboard players reset * TimberSetLeaves # non-overriding default values function timber:settings/default_values -function timber:tool/durability_max + +# axes (they are registered with the same function that is offered to other datapacks) +function timber:axes/init scoreboard players set running timber 0 # start optional loops diff --git a/data/timber/function/main.mcfunction b/data/timber/function/main.mcfunction index 82139cc..a139ecd 100644 --- a/data/timber/function/main.mcfunction +++ b/data/timber/function/main.mcfunction @@ -2,7 +2,7 @@ schedule function timber:main 1t # if any kind of axe is used -execute as @a[predicate=timber:used_axe,tag=!global.ignore] at @s run function timber:utils/check_max_command_sequence_length +function timber:axes/scan # toggle timber datapack per player execute as @a[scores={TimberToggle=1},tag=!global.ignore] run function timber:settings/toggle/timber diff --git a/data/timber/function/settings/default_values.mcfunction b/data/timber/function/settings/default_values.mcfunction index 688e53a..a4a0f6e 100644 --- a/data/timber/function/settings/default_values.mcfunction +++ b/data/timber/function/settings/default_values.mcfunction @@ -13,13 +13,6 @@ execute unless score drop_loot timber = drop_loot timber run scoreboard players execute unless score hunger timber = hunger timber run scoreboard players set hunger timber 0 execute unless score stopsound timber = stopsound timber run scoreboard players set stopsound timber 0 -execute unless score wooden_axe timber = wooden_axe timber run scoreboard players set wooden_axe timber 1 -execute unless score stone_axe timber = stone_axe timber run scoreboard players set stone_axe timber 1 -execute unless score copper_axe timber = copper_axe timber run scoreboard players set copper_axe timber 1 -execute unless score iron_axe timber = iron_axe timber run scoreboard players set iron_axe timber 1 -execute unless score golden_axe timber = golden_axe timber run scoreboard players set golden_axe timber 1 -execute unless score diamond_axe timber = diamond_axe timber run scoreboard players set diamond_axe timber 1 -execute unless score netherite_axe timber = netherite_axe timber run scoreboard players set netherite_axe timber 1 execute unless score chop_trees timber = chop_trees timber run scoreboard players set chop_trees timber 1 execute unless score chop_fungi timber = chop_fungi timber run scoreboard players set chop_fungi timber 0 execute unless score persistent timber = persistent timber run scoreboard players set persistent timber 0 diff --git a/data/timber/function/settings/dispatch.mcfunction b/data/timber/function/settings/dispatch.mcfunction index a1f5f4b..c8a265c 100644 --- a/data/timber/function/settings/dispatch.mcfunction +++ b/data/timber/function/settings/dispatch.mcfunction @@ -28,10 +28,5 @@ execute if score #req timber matches 112 run function timber:settings/flip/stops execute if score #req timber matches 113 run function timber:settings/flip/chop_trees execute if score #req timber matches 114 run function timber:settings/flip/chop_fungi execute if score #req timber matches 115 run function timber:settings/flip/persistent -execute if score #req timber matches 116 run function timber:settings/flip/wooden_axe -execute if score #req timber matches 117 run function timber:settings/flip/golden_axe -execute if score #req timber matches 118 run function timber:settings/flip/stone_axe -execute if score #req timber matches 119 run function timber:settings/flip/copper_axe -execute if score #req timber matches 120 run function timber:settings/flip/iron_axe -execute if score #req timber matches 121 run function timber:settings/flip/diamond_axe -execute if score #req timber matches 122 run function timber:settings/flip/netherite_axe +# 200 and up: the axes of the tools page, in the order of the list +execute if score #req timber matches 200..299 run function timber:settings/flip/axe diff --git a/data/timber/function/settings/flip/axe.mcfunction b/data/timber/function/settings/flip/axe.mcfunction new file mode 100644 index 0000000..ea5d544 --- /dev/null +++ b/data/timber/function/settings/flip/axe.mcfunction @@ -0,0 +1,9 @@ +# toggles "Enable ": the button code is 200 plus the position of the axe in the list +scoreboard players operation #axe_index timber = #req timber +scoreboard players remove #axe_index timber 200 +execute store result storage timber:axes pick.index int 1 run scoreboard players get #axe_index timber +data remove storage timber:axes pick.axe +function timber:settings/flip/axe_at with storage timber:axes pick +# no axe at that position (an old button): nothing to do +execute unless data storage timber:axes pick.axe run return run function timber:settings/open/tools +function timber:settings/flip/axe_toggle with storage timber:axes pick.axe diff --git a/data/timber/function/settings/flip/axe_at.mcfunction b/data/timber/function/settings/flip/axe_at.mcfunction new file mode 100644 index 0000000..431a2ad --- /dev/null +++ b/data/timber/function/settings/flip/axe_at.mcfunction @@ -0,0 +1 @@ +$data modify storage timber:axes pick.axe set from storage timber:axes list[$(index)] diff --git a/data/timber/function/settings/flip/axe_toggle.mcfunction b/data/timber/function/settings/flip/axe_toggle.mcfunction new file mode 100644 index 0000000..f76b29c --- /dev/null +++ b/data/timber/function/settings/flip/axe_toggle.mcfunction @@ -0,0 +1,4 @@ +function timber:settings/before_set +$execute store success score $(holder) timber unless score $(holder) timber matches 1 +function timber:settings/after_set +function timber:settings/open/tools diff --git a/data/timber/function/settings/flip/copper_axe.mcfunction b/data/timber/function/settings/flip/copper_axe.mcfunction deleted file mode 100644 index 79235e9..0000000 --- a/data/timber/function/settings/flip/copper_axe.mcfunction +++ /dev/null @@ -1,5 +0,0 @@ -# toggles "Enable Copper Axe" -function timber:settings/before_set -execute store success score copper_axe timber unless score copper_axe timber matches 1 -function timber:settings/after_set -function timber:settings/open/tools diff --git a/data/timber/function/settings/flip/diamond_axe.mcfunction b/data/timber/function/settings/flip/diamond_axe.mcfunction deleted file mode 100644 index f38a48f..0000000 --- a/data/timber/function/settings/flip/diamond_axe.mcfunction +++ /dev/null @@ -1,5 +0,0 @@ -# toggles "Enable Diamond Axe" -function timber:settings/before_set -execute store success score diamond_axe timber unless score diamond_axe timber matches 1 -function timber:settings/after_set -function timber:settings/open/tools diff --git a/data/timber/function/settings/flip/golden_axe.mcfunction b/data/timber/function/settings/flip/golden_axe.mcfunction deleted file mode 100644 index f3fc533..0000000 --- a/data/timber/function/settings/flip/golden_axe.mcfunction +++ /dev/null @@ -1,5 +0,0 @@ -# toggles "Enable Golden Axe" -function timber:settings/before_set -execute store success score golden_axe timber unless score golden_axe timber matches 1 -function timber:settings/after_set -function timber:settings/open/tools diff --git a/data/timber/function/settings/flip/iron_axe.mcfunction b/data/timber/function/settings/flip/iron_axe.mcfunction deleted file mode 100644 index 79c589a..0000000 --- a/data/timber/function/settings/flip/iron_axe.mcfunction +++ /dev/null @@ -1,5 +0,0 @@ -# toggles "Enable Iron Axe" -function timber:settings/before_set -execute store success score iron_axe timber unless score iron_axe timber matches 1 -function timber:settings/after_set -function timber:settings/open/tools diff --git a/data/timber/function/settings/flip/netherite_axe.mcfunction b/data/timber/function/settings/flip/netherite_axe.mcfunction deleted file mode 100644 index c01cac9..0000000 --- a/data/timber/function/settings/flip/netherite_axe.mcfunction +++ /dev/null @@ -1,5 +0,0 @@ -# toggles "Enable Netherite Axe" -function timber:settings/before_set -execute store success score netherite_axe timber unless score netherite_axe timber matches 1 -function timber:settings/after_set -function timber:settings/open/tools diff --git a/data/timber/function/settings/flip/stone_axe.mcfunction b/data/timber/function/settings/flip/stone_axe.mcfunction deleted file mode 100644 index 75fea06..0000000 --- a/data/timber/function/settings/flip/stone_axe.mcfunction +++ /dev/null @@ -1,5 +0,0 @@ -# toggles "Enable Stone Axe" -function timber:settings/before_set -execute store success score stone_axe timber unless score stone_axe timber matches 1 -function timber:settings/after_set -function timber:settings/open/tools diff --git a/data/timber/function/settings/flip/wooden_axe.mcfunction b/data/timber/function/settings/flip/wooden_axe.mcfunction deleted file mode 100644 index d2ecbae..0000000 --- a/data/timber/function/settings/flip/wooden_axe.mcfunction +++ /dev/null @@ -1,5 +0,0 @@ -# toggles "Enable Wooden Axe" -function timber:settings/before_set -execute store success score wooden_axe timber unless score wooden_axe timber matches 1 -function timber:settings/after_set -function timber:settings/open/tools diff --git a/data/timber/function/settings/open/tools.mcfunction b/data/timber/function/settings/open/tools.mcfunction index 611a153..3366871 100644 --- a/data/timber/function/settings/open/tools.mcfunction +++ b/data/timber/function/settings/open/tools.mcfunction @@ -1,18 +1,8 @@ # opens the "Tools" settings page with the current values # only reachable for admins data modify storage timber:settings dialog set value {"type":"minecraft:multi_action","title":"Timber Settings / Tools","external_title":"Timber Tools","inputs":[],"can_close_with_escape":true,"pause":false,"after_action":"none","columns":2,"actions":[],"exit_action":{"label":{"translate":"gui.back"},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 1"}}} -execute if score wooden_axe timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Enable Wooden Axe"},{"translate":"options.on","color":"green"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 116"}} -execute unless score wooden_axe timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Enable Wooden Axe"},{"translate":"options.off","color":"red"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 116"}} -execute if score golden_axe timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Enable Golden Axe"},{"translate":"options.on","color":"green"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 117"}} -execute unless score golden_axe timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Enable Golden Axe"},{"translate":"options.off","color":"red"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 117"}} -execute if score stone_axe timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Enable Stone Axe"},{"translate":"options.on","color":"green"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 118"}} -execute unless score stone_axe timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Enable Stone Axe"},{"translate":"options.off","color":"red"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 118"}} -execute if score copper_axe timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Enable Copper Axe"},{"translate":"options.on","color":"green"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 119"}} -execute unless score copper_axe timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Enable Copper Axe"},{"translate":"options.off","color":"red"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 119"}} -execute if score iron_axe timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Enable Iron Axe"},{"translate":"options.on","color":"green"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 120"}} -execute unless score iron_axe timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Enable Iron Axe"},{"translate":"options.off","color":"red"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 120"}} -execute if score diamond_axe timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Enable Diamond Axe"},{"translate":"options.on","color":"green"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 121"}} -execute unless score diamond_axe timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Enable Diamond Axe"},{"translate":"options.off","color":"red"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 121"}} -execute if score netherite_axe timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Enable Netherite Axe"},{"translate":"options.on","color":"green"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 122"}} -execute unless score netherite_axe timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Enable Netherite Axe"},{"translate":"options.off","color":"red"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 122"}} +# one button for every registered axe, the code of the button is 200 plus the position of the axe in the list +data modify storage timber:axes queue set from storage timber:axes list +scoreboard players set #axe_code timber 200 +function timber:settings/open/tools_next function timber:settings/show with storage timber:settings diff --git a/data/timber/function/settings/open/tools_button.mcfunction b/data/timber/function/settings/open/tools_button.mcfunction new file mode 100644 index 0000000..0989ec8 --- /dev/null +++ b/data/timber/function/settings/open/tools_button.mcfunction @@ -0,0 +1,2 @@ +$execute if score $(holder) timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Enable $(label)"},{"translate":"options.on","color":"green"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set $(code)"}} +$execute unless score $(holder) timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Enable $(label)"},{"translate":"options.off","color":"red"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set $(code)"}} diff --git a/data/timber/function/settings/open/tools_next.mcfunction b/data/timber/function/settings/open/tools_next.mcfunction new file mode 100644 index 0000000..216fc4e --- /dev/null +++ b/data/timber/function/settings/open/tools_next.mcfunction @@ -0,0 +1,6 @@ +execute unless data storage timber:axes queue[0] run return 0 +execute store result storage timber:axes queue[0].code int 1 run scoreboard players get #axe_code timber +function timber:settings/open/tools_button with storage timber:axes queue[0] +scoreboard players add #axe_code timber 1 +data remove storage timber:axes queue[0] +function timber:settings/open/tools_next diff --git a/data/timber/function/tool/durability_check.mcfunction b/data/timber/function/tool/durability_check.mcfunction index 1ba6e78..c7e384e 100644 --- a/data/timber/function/tool/durability_check.mcfunction +++ b/data/timber/function/tool/durability_check.mcfunction @@ -1,8 +1,2 @@ -# check if axe is broken -execute if score @s timber_w_axe matches 1.. unless score durability timber <= #wooden_axe_max_durability timber run scoreboard players set durability_okay timber 0 -execute if score @s timber_s_axe matches 1.. unless score durability timber <= #stone_axe_max_durability timber run scoreboard players set durability_okay timber 0 -execute if score @s timber_c_axe matches 1.. unless score durability timber <= #copper_axe_max_durability timber run scoreboard players set durability_okay timber 0 -execute if score @s timber_i_axe matches 1.. unless score durability timber <= #iron_axe_max_durability timber run scoreboard players set durability_okay timber 0 -execute if score @s timber_g_axe matches 1.. unless score durability timber <= #golden_axe_max_durability timber run scoreboard players set durability_okay timber 0 -execute if score @s timber_d_axe matches 1.. unless score durability timber <= #diamond_axe_max_durability timber run scoreboard players set durability_okay timber 0 -execute if score @s timber_n_axe matches 1.. unless score durability timber <= #netherite_axe_max_durability timber run scoreboard players set durability_okay timber 0 +# check if axe is broken: axe_max is the max durability of the axe that was used +execute unless score durability timber <= axe_max timber run scoreboard players set durability_okay timber 0 diff --git a/data/timber/function/tool/durability_max.mcfunction b/data/timber/function/tool/durability_max.mcfunction deleted file mode 100644 index cd6f49c..0000000 --- a/data/timber/function/tool/durability_max.mcfunction +++ /dev/null @@ -1,8 +0,0 @@ -# non-overriding max durability for axes -execute unless score #wooden_axe_max_durability timber = #wooden_axe_max_durability timber run scoreboard players set #wooden_axe_max_durability timber 59 -execute unless score #stone_axe_max_durability timber = #stone_axe_max_durability timber run scoreboard players set #stone_axe_max_durability timber 131 -execute unless score #copper_axe_max_durability timber = #copper_axe_max_durability timber run scoreboard players set #copper_axe_max_durability timber 190 -execute unless score #iron_axe_max_durability timber = #iron_axe_max_durability timber run scoreboard players set #iron_axe_max_durability timber 250 -execute unless score #golden_axe_max_durability timber = #golden_axe_max_durability timber run scoreboard players set #golden_axe_max_durability timber 32 -execute unless score #diamond_axe_max_durability timber = #diamond_axe_max_durability timber run scoreboard players set #diamond_axe_max_durability timber 1561 -execute unless score #netherite_axe_max_durability timber = #netherite_axe_max_durability timber run scoreboard players set #netherite_axe_max_durability timber 2031 diff --git a/data/timber/function/tool/set_damage.mcfunction b/data/timber/function/tool/set_damage.mcfunction new file mode 100644 index 0000000..89786ce --- /dev/null +++ b/data/timber/function/tool/set_damage.mcfunction @@ -0,0 +1,3 @@ +# Sets the damage of the axe in the hand. The modifier is inline, the key was renamed from function to type in pack format 121: +# with both keys it works in every version. +$item modify entity @s weapon.mainhand {function:"minecraft:set_components",type:"minecraft:set_components",components:{"minecraft:damage":$(damage)}} diff --git a/data/timber/function/tool/wear_out.mcfunction b/data/timber/function/tool/wear_out.mcfunction index 7d20c48..5ddf1e3 100644 --- a/data/timber/function/tool/wear_out.mcfunction +++ b/data/timber/function/tool/wear_out.mcfunction @@ -1,10 +1,3 @@ -# wear out tool -execute if score @s timber_w_axe matches 1.. run scoreboard players operation damage timber = #wooden_axe_max_durability timber -execute if score @s timber_s_axe matches 1.. run scoreboard players operation damage timber = #stone_axe_max_durability timber -execute if score @s timber_c_axe matches 1.. run scoreboard players operation damage timber = #copper_axe_max_durability timber -execute if score @s timber_i_axe matches 1.. run scoreboard players operation damage timber = #iron_axe_max_durability timber -execute if score @s timber_g_axe matches 1.. run scoreboard players operation damage timber = #golden_axe_max_durability timber -execute if score @s timber_d_axe matches 1.. run scoreboard players operation damage timber = #diamond_axe_max_durability timber -execute if score @s timber_n_axe matches 1.. run scoreboard players operation damage timber = #netherite_axe_max_durability timber -scoreboard players operation damage timber -= durability timber -item modify entity @s weapon.mainhand timber:wear_out +# wear out tool: the durability that was counted is the damage of the axe now +execute store result storage timber:axes wear.damage int 1 run scoreboard players get durability timber +function timber:tool/set_damage with storage timber:axes wear diff --git a/data/timber/function/tool/worn_out.mcfunction b/data/timber/function/tool/worn_out.mcfunction index affb0f5..cdc2a36 100644 --- a/data/timber/function/tool/worn_out.mcfunction +++ b/data/timber/function/tool/worn_out.mcfunction @@ -1,15 +1,3 @@ # break axe if worn out -execute if score @s timber_w_axe matches 1.. if score durability timber >= #wooden_axe_max_durability timber run item replace entity @s weapon.mainhand with minecraft:air -execute if score @s timber_w_axe matches 1.. if score durability timber >= #wooden_axe_max_durability timber run playsound minecraft:entity.item.break player @a ~ ~ ~ -execute if score @s timber_s_axe matches 1.. if score durability timber >= #stone_axe_max_durability timber run item replace entity @s weapon.mainhand with minecraft:air -execute if score @s timber_s_axe matches 1.. if score durability timber >= #stone_axe_max_durability timber run playsound minecraft:entity.item.break player @a ~ ~ ~ -execute if score @s timber_c_axe matches 1.. if score durability timber >= #copper_axe_max_durability timber run item replace entity @s weapon.mainhand with minecraft:air -execute if score @s timber_c_axe matches 1.. if score durability timber >= #copper_axe_max_durability timber run playsound minecraft:entity.item.break player @a ~ ~ ~ -execute if score @s timber_i_axe matches 1.. if score durability timber >= #iron_axe_max_durability timber run item replace entity @s weapon.mainhand with minecraft:air -execute if score @s timber_i_axe matches 1.. if score durability timber >= #iron_axe_max_durability timber run playsound minecraft:entity.item.break player @a ~ ~ ~ -execute if score @s timber_g_axe matches 1.. if score durability timber >= #golden_axe_max_durability timber run item replace entity @s weapon.mainhand with minecraft:air -execute if score @s timber_g_axe matches 1.. if score durability timber >= #golden_axe_max_durability timber run playsound minecraft:entity.item.break player @a ~ ~ ~ -execute if score @s timber_d_axe matches 1.. if score durability timber >= #diamond_axe_max_durability timber run item replace entity @s weapon.mainhand with minecraft:air -execute if score @s timber_d_axe matches 1.. if score durability timber >= #diamond_axe_max_durability timber run playsound minecraft:entity.item.break player @a ~ ~ ~ -execute if score @s timber_n_axe matches 1.. if score durability timber >= #netherite_axe_max_durability timber run item replace entity @s weapon.mainhand with minecraft:air -execute if score @s timber_n_axe matches 1.. if score durability timber >= #netherite_axe_max_durability timber run playsound minecraft:entity.item.break player @a ~ ~ ~ +execute if score durability timber >= axe_max timber run item replace entity @s weapon.mainhand with minecraft:air +execute if score durability timber >= axe_max timber run playsound minecraft:entity.item.break player @a ~ ~ ~ diff --git a/data/timber/function/uninstall.mcfunction b/data/timber/function/uninstall.mcfunction index 4cbe8b4..d9fffef 100644 --- a/data/timber/function/uninstall.mcfunction +++ b/data/timber/function/uninstall.mcfunction @@ -13,14 +13,10 @@ data remove storage timber:slow_chop blocks_per_chop data remove storage timber:slow_chop time_between_chops data remove storage timber:max_command_sequence_length value +# remove the scoreboards of the axes and what is stored for them +function timber:axes/uninstall + # remove scoreboards -scoreboard objectives remove timber_w_axe -scoreboard objectives remove timber_s_axe -scoreboard objectives remove timber_c_axe -scoreboard objectives remove timber_i_axe -scoreboard objectives remove timber_g_axe -scoreboard objectives remove timber_d_axe -scoreboard objectives remove timber_n_axe scoreboard objectives remove timber scoreboard objectives remove TimberToggle scoreboard objectives remove TimberSettings diff --git a/data/timber/function/utils/reset.mcfunction b/data/timber/function/utils/reset.mcfunction index 9f16ba7..a64e488 100644 --- a/data/timber/function/utils/reset.mcfunction +++ b/data/timber/function/utils/reset.mcfunction @@ -1,14 +1,5 @@ scoreboard players set running timber 0 -# reset scores -scoreboard players set @s timber_w_axe 0 -scoreboard players set @s timber_s_axe 0 -scoreboard players set @s timber_c_axe 0 -scoreboard players set @s timber_i_axe 0 -scoreboard players set @s timber_g_axe 0 -scoreboard players set @s timber_d_axe 0 -scoreboard players set @s timber_n_axe 0 - # remove markers execute if score chop_fungi timber matches 1.. run kill @e[type=minecraft:item,nbt={Item:{components:{"minecraft:custom_data":{Tags:["timber_location"]}}}}] #execute if score chop_fungi timber matches 1.. run clear @s knowledge_book{Tags:[timber_location]} diff --git a/data/timber/function/utils/used_axe.mcfunction b/data/timber/function/utils/used_axe.mcfunction index 5e1d329..b538c1a 100644 --- a/data/timber/function/utils/used_axe.mcfunction +++ b/data/timber/function/utils/used_axe.mcfunction @@ -1,20 +1,5 @@ -# is this axe enabled? -execute unless score @s timber_disabled matches 1.. if score @s timber_w_axe matches 1.. if score wooden_axe timber matches 1.. run function timber:utils/sneaking -execute unless score @s timber_disabled matches 1.. if score @s timber_s_axe matches 1.. if score stone_axe timber matches 1.. run function timber:utils/sneaking -execute unless score @s timber_disabled matches 1.. if score @s timber_c_axe matches 1.. if score copper_axe timber matches 1.. run function timber:utils/sneaking -execute unless score @s timber_disabled matches 1.. if score @s timber_i_axe matches 1.. if score iron_axe timber matches 1.. run function timber:utils/sneaking -execute unless score @s timber_disabled matches 1.. if score @s timber_g_axe matches 1.. if score golden_axe timber matches 1.. run function timber:utils/sneaking -execute unless score @s timber_disabled matches 1.. if score @s timber_d_axe matches 1.. if score diamond_axe timber matches 1.. run function timber:utils/sneaking -execute unless score @s timber_disabled matches 1.. if score @s timber_n_axe matches 1.. if score netherite_axe timber matches 1.. run function timber:utils/sneaking +# The caller checked that the axe is enabled: standing or sneaking? +function timber:utils/sneaking # remove mushroom_stem marker kill @e[type=minecraft:item,distance=..7,nbt={Item:{components:{"minecraft:custom_data":{Tags:["timber_location"]}}}}] - -# reset scores -scoreboard players set @s timber_w_axe 0 -scoreboard players set @s timber_s_axe 0 -scoreboard players set @s timber_c_axe 0 -scoreboard players set @s timber_i_axe 0 -scoreboard players set @s timber_g_axe 0 -scoreboard players set @s timber_d_axe 0 -scoreboard players set @s timber_n_axe 0 diff --git a/data/timber/item_modifier/wear_out.json b/data/timber/item_modifier/wear_out.json deleted file mode 100644 index 79af69a..0000000 --- a/data/timber/item_modifier/wear_out.json +++ /dev/null @@ -1,177 +0,0 @@ -[ - { - "function": "minecraft:set_damage", - "damage": { - "type": "minecraft:score", - "target": { - "type": "minecraft:fixed", - "name": "damage" - }, - "score": "timber", - "scale": 0.01694915254237288 - }, - "conditions": [ - { - "condition": "minecraft:entity_properties", - "entity": "this", - "predicate": { - "equipment": { - "mainhand": { - "items": "minecraft:wooden_axe" - } - } - } - } - ] - }, - { - "function": "minecraft:set_damage", - "damage": { - "type": "minecraft:score", - "target": { - "type": "minecraft:fixed", - "name": "damage" - }, - "score": "timber", - "scale": 0.007633587786259542 - }, - "conditions": [ - { - "condition": "minecraft:entity_properties", - "entity": "this", - "predicate": { - "equipment": { - "mainhand": { - "items": "minecraft:stone_axe" - } - } - } - } - ] - }, - { - "function": "minecraft:set_damage", - "damage": { - "type": "minecraft:score", - "target": { - "type": "minecraft:fixed", - "name": "damage" - }, - "score": "timber", - "scale": 0.0052631578947368421 - }, - "conditions": [ - { - "condition": "minecraft:entity_properties", - "entity": "this", - "predicate": { - "equipment": { - "mainhand": { - "items": "minecraft:copper_axe" - } - } - } - } - ] - }, - { - "function": "minecraft:set_damage", - "damage": { - "type": "minecraft:score", - "target": { - "type": "minecraft:fixed", - "name": "damage" - }, - "score": "timber", - "scale": 0.004 - }, - "conditions": [ - { - "condition": "minecraft:entity_properties", - "entity": "this", - "predicate": { - "equipment": { - "mainhand": { - "items": "minecraft:iron_axe" - } - } - } - } - ] - }, - { - "function": "minecraft:set_damage", - "damage": { - "type": "minecraft:score", - "target": { - "type": "minecraft:fixed", - "name": "damage" - }, - "score": "timber", - "scale": 0.03125 - }, - "conditions": [ - { - "condition": "minecraft:entity_properties", - "entity": "this", - "predicate": { - "equipment": { - "mainhand": { - "items": "minecraft:golden_axe" - } - } - } - } - ] - }, - { - "function": "minecraft:set_damage", - "damage": { - "type": "minecraft:score", - "target": { - "type": "minecraft:fixed", - "name": "damage" - }, - "score": "timber", - "scale": 0.0006393861892583121 - }, - "conditions": [ - { - "condition": "minecraft:entity_properties", - "entity": "this", - "predicate": { - "equipment": { - "mainhand": { - "items": "minecraft:diamond_axe" - } - } - } - } - ] - }, - { - "function": "minecraft:set_damage", - "damage": { - "type": "minecraft:score", - "target": { - "type": "minecraft:fixed", - "name": "damage" - }, - "score": "timber", - "scale": 0.0004923682914820286 - }, - "conditions": [ - { - "condition": "minecraft:entity_properties", - "entity": "this", - "predicate": { - "equipment": { - "mainhand": { - "items": "minecraft:netherite_axe" - } - } - } - } - ] - } -] diff --git a/data/timber/predicate/used_axe.json b/data/timber/predicate/used_axe.json deleted file mode 100644 index 262aded..0000000 --- a/data/timber/predicate/used_axe.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "condition": "minecraft:any_of", - "terms": [ - { - "condition": "minecraft:entity_scores", - "entity": "this", - "scores": { - "timber_w_axe": { - "min": 1 - } - } - }, - { - "condition": "minecraft:entity_scores", - "entity": "this", - "scores": { - "timber_s_axe": { - "min": 1 - } - } - }, - { - "condition": "minecraft:entity_scores", - "entity": "this", - "scores": { - "timber_c_axe": { - "min": 1 - } - } - }, - { - "condition": "minecraft:entity_scores", - "entity": "this", - "scores": { - "timber_i_axe": { - "min": 1 - } - } - }, - { - "condition": "minecraft:entity_scores", - "entity": "this", - "scores": { - "timber_g_axe": { - "min": 1 - } - } - }, - { - "condition": "minecraft:entity_scores", - "entity": "this", - "scores": { - "timber_d_axe": { - "min": 1 - } - } - }, - { - "condition": "minecraft:entity_scores", - "entity": "this", - "scores": { - "timber_n_axe": { - "min": 1 - } - } - } - ] -} diff --git a/data/timber/tags/function/register_axes.json b/data/timber/tags/function/register_axes.json new file mode 100644 index 0000000..86dde3d --- /dev/null +++ b/data/timber/tags/function/register_axes.json @@ -0,0 +1,3 @@ +{ + "values": [] +} From a77d6eb22b55e6c37faa127c59fee20637a7423e Mon Sep 17 00:00:00 2001 From: Flavio Felder Date: Tue, 22 Sep 2026 01:57:40 +0200 Subject: [PATCH 02/13] Test the axe API: registered axe chops, wears out, breaks, can be switched off Co-Authored-By: Claude Sonnet 5 --- .github/scripts/gen-uninstall-check.py | 8 +-- .../timber/tags/function/register_axes.json | 5 ++ tests/data/timber/tags/item/axes.json | 5 ++ .../function/env/axes_api_setup.mcfunction | 2 + .../function/env/axes_api_teardown.mcfunction | 3 ++ .../env/custom_axe_off_setup.mcfunction | 2 + .../env/custom_axe_off_teardown.mcfunction | 2 + .../env/restore_all_defaults.mcfunction | 1 + .../function/register_axes.mcfunction | 3 ++ ..._repeated_and_ignores_bad_input.mcfunction | 48 +++++++++++++++++ ...ks_at_its_registered_durability.mcfunction | 28 ++++++++++ ...istered_custom_axe_chops_a_tree.mcfunction | 26 ++++++++++ ...ed_custom_axe_off_does_not_chop.mcfunction | 26 ++++++++++ ...om_axe_wears_it_out_like_an_axe.mcfunction | 28 ++++++++++ ...ttings_menu_flips_every_setting.mcfunction | 51 +++++++++++-------- .../uninstall_removes_everything.mcfunction | 21 ++++++++ .../test_environment/axes_api.json | 5 ++ .../test_environment/custom_axe_off.json | 5 ++ 18 files changed, 245 insertions(+), 24 deletions(-) create mode 100644 tests/data/timber/tags/function/register_axes.json create mode 100644 tests/data/timber/tags/item/axes.json create mode 100644 tests/data/timber_test/function/env/axes_api_setup.mcfunction create mode 100644 tests/data/timber_test/function/env/axes_api_teardown.mcfunction create mode 100644 tests/data/timber_test/function/env/custom_axe_off_setup.mcfunction create mode 100644 tests/data/timber_test/function/env/custom_axe_off_teardown.mcfunction create mode 100644 tests/data/timber_test/function/register_axes.mcfunction create mode 100644 tests/data/timber_test/test/axes_add_axe_can_be_repeated_and_ignores_bad_input.mcfunction create mode 100644 tests/data/timber_test/test/axes_registered_custom_axe_breaks_at_its_registered_durability.mcfunction create mode 100644 tests/data/timber_test/test/axes_registered_custom_axe_chops_a_tree.mcfunction create mode 100644 tests/data/timber_test/test/axes_registered_custom_axe_off_does_not_chop.mcfunction create mode 100644 tests/data/timber_test/test/axes_registered_custom_axe_wears_it_out_like_an_axe.mcfunction create mode 100644 tests/data/timber_test/test_environment/axes_api.json create mode 100644 tests/data/timber_test/test_environment/custom_axe_off.json diff --git a/.github/scripts/gen-uninstall-check.py b/.github/scripts/gen-uninstall-check.py index 21d784d..d2e152a 100644 --- a/.github/scripts/gen-uninstall-check.py +++ b/.github/scripts/gen-uninstall-check.py @@ -32,7 +32,8 @@ if os.path.relpath(path, folder).replace(os.sep, "/") == "uninstall.mcfunction": uninstall_text += text + "\n" continue - objectives.update(re.findall(r"scoreboard objectives add (\S+)", text)) + # a name that is a macro argument ($(obj)) is only known at runtime: the axes, they have their own check in the test + objectives.update(n for n in re.findall(r"scoreboard objectives add (\S+)", text) if "$(" not in n) schedules.update(re.findall(r"schedule function (timber:[\w/]+)", text)) # storage timber:settings dialog.inputs[0].initial -> (timber:settings, dialog), the top level key is what gets removed storage_keys.update(re.findall(r"storage (timber:\w+) (\w+)", text)) @@ -54,9 +55,10 @@ def write(name, lines): # --- before the uninstall: something in every storage key # The first line of both functions sets a marker that the test checks: PackTest doesn't complain about a function that doesn't # exist, without the marker a missing or broken generation step would leave a test that checks nothing and passes. -write("uninstall_prepare.mcfunction", [HEADER, "#> Puts a value into every storage key the datapack writes to.", +write("uninstall_prepare.mcfunction", [HEADER, "#> Puts a value into every storage key the datapack writes to and that is empty.", "scoreboard objectives add timber_test dummy", "scoreboard players set #uninstall_prepare_ran timber_test 1"] - + [f"data modify storage {sid} {key} set value 1" for sid, key in sorted(storage_keys)]) + # only where there is nothing yet: a key that holds real data (the list of axes) must stay usable for the uninstall + + [f"execute unless data storage {sid} {key} run data modify storage {sid} {key} set value 1" for sid, key in sorted(storage_keys)]) # --- after the uninstall lines = [ diff --git a/tests/data/timber/tags/function/register_axes.json b/tests/data/timber/tags/function/register_axes.json new file mode 100644 index 0000000..d3a4efc --- /dev/null +++ b/tests/data/timber/tags/function/register_axes.json @@ -0,0 +1,5 @@ +{ + "values": [ + "timber_test:register_axes" + ] +} diff --git a/tests/data/timber/tags/item/axes.json b/tests/data/timber/tags/item/axes.json new file mode 100644 index 0000000..78c584b --- /dev/null +++ b/tests/data/timber/tags/item/axes.json @@ -0,0 +1,5 @@ +{ + "values": [ + "minecraft:stick" + ] +} diff --git a/tests/data/timber_test/function/env/axes_api_setup.mcfunction b/tests/data/timber_test/function/env/axes_api_setup.mcfunction new file mode 100644 index 0000000..4670c92 --- /dev/null +++ b/tests/data/timber_test/function/env/axes_api_setup.mcfunction @@ -0,0 +1,2 @@ +#> Setting for the test of this environment +#> The test adds and replaces axes diff --git a/tests/data/timber_test/function/env/axes_api_teardown.mcfunction b/tests/data/timber_test/function/env/axes_api_teardown.mcfunction new file mode 100644 index 0000000..c422ad0 --- /dev/null +++ b/tests/data/timber_test/function/env/axes_api_teardown.mcfunction @@ -0,0 +1,3 @@ +#> Back to the default +#> Register the axes again, the way a load does (forgets what the test added) +function timber:axes/init diff --git a/tests/data/timber_test/function/env/custom_axe_off_setup.mcfunction b/tests/data/timber_test/function/env/custom_axe_off_setup.mcfunction new file mode 100644 index 0000000..5f1639d --- /dev/null +++ b/tests/data/timber_test/function/env/custom_axe_off_setup.mcfunction @@ -0,0 +1,2 @@ +#> Setting for the test of this environment +scoreboard players set stick timber 0 diff --git a/tests/data/timber_test/function/env/custom_axe_off_teardown.mcfunction b/tests/data/timber_test/function/env/custom_axe_off_teardown.mcfunction new file mode 100644 index 0000000..34c29ba --- /dev/null +++ b/tests/data/timber_test/function/env/custom_axe_off_teardown.mcfunction @@ -0,0 +1,2 @@ +#> Back to the default +scoreboard players set stick timber 1 diff --git a/tests/data/timber_test/function/env/restore_all_defaults.mcfunction b/tests/data/timber_test/function/env/restore_all_defaults.mcfunction index c5acfcb..78d2e47 100644 --- a/tests/data/timber_test/function/env/restore_all_defaults.mcfunction +++ b/tests/data/timber_test/function/env/restore_all_defaults.mcfunction @@ -19,6 +19,7 @@ scoreboard players set iron_axe timber 1 scoreboard players set golden_axe timber 1 scoreboard players set diamond_axe timber 1 scoreboard players set netherite_axe timber 1 +scoreboard players set stick timber 1 scoreboard players set chop_trees timber 1 scoreboard players set chop_fungi timber 0 scoreboard players set persistent timber 0 diff --git a/tests/data/timber_test/function/register_axes.mcfunction b/tests/data/timber_test/function/register_axes.mcfunction new file mode 100644 index 0000000..6f22c02 --- /dev/null +++ b/tests/data/timber_test/function/register_axes.mcfunction @@ -0,0 +1,3 @@ +#> The axe of the tests: a stick that mines logs (it gets the tool component when it is given), registered like the axe of a mod. +# Its item id has the namespace minecraft, so the scoreboard that says if it is enabled is called "stick". +function timber:api/add_axe {item:"minecraft:stick",durability:30,label:"Test Stick"} diff --git a/tests/data/timber_test/test/axes_add_axe_can_be_repeated_and_ignores_bad_input.mcfunction b/tests/data/timber_test/test/axes_add_axe_can_be_repeated_and_ignores_bad_input.mcfunction new file mode 100644 index 0000000..bd67dcc --- /dev/null +++ b/tests/data/timber_test/test/axes_add_axe_can_be_repeated_and_ignores_bad_input.mcfunction @@ -0,0 +1,48 @@ +#> timber:api/add_axe replaces an axe that was added before, and an item id that is wrong or unknown adds nothing +# @environment timber_test:axes_api +# @timeout 100 + +scoreboard objectives add timber_test dummy + +# 7 vanilla axes and the stick of the tests +execute store result score #n timber_test run data get storage timber:axes list +assert score #n timber_test matches 8 + +# the same axe again: still one +function timber:api/add_axe {item:"minecraft:stick",durability:30,label:"Test Stick"} +execute store result score #n timber_test run data get storage timber:axes list +assert score #n timber_test matches 8 + +# no colon in the item id +function timber:api/add_axe {item:"noColonHere",durability:100,label:"Broken"} +execute store result score #n timber_test run data get storage timber:axes list +assert score #n timber_test matches 8 +execute store success score #left timber_test if data storage timber:axes build +assert score #left timber_test matches 0 + +# an item that doesn't exist (the mod is not installed): no scoreboard can be made, so nothing is added +function timber:api/add_axe {item:"supertools:emeraldaxe",durability:500,label:"Emerald Axe"} +execute store result score #n timber_test run data get storage timber:axes list +assert score #n timber_test matches 8 +execute store success score #left timber_test if data storage timber:axes build +assert score #left timber_test matches 0 + +# a real item is added, with its scoreboard +function timber:api/add_axe {item:"minecraft:shears",durability:238,label:"Test Shears"} +execute store result score #n timber_test run data get storage timber:axes list +assert score #n timber_test matches 9 +execute store success score #exists timber_test run scoreboard players add #probe timber_axe.minecraft.shears 0 +assert score #exists timber_test matches 1 + +# added again with another durability: replaced, not doubled +function timber:api/add_axe {item:"minecraft:shears",durability:300,label:"Test Shears"} +execute store result score #n timber_test run data get storage timber:axes list +assert score #n timber_test matches 9 +assert data storage timber:axes list[{item:"minecraft:shears",durability:300}] + +# the names of an axe of a mod: namespace.name for the score that says if it is enabled, timber_axe.namespace.name for the scoreboard +data modify storage timber:axes build set value {ns:"supertools",path:"emeraldaxe"} +function timber:axes/name_modded with storage timber:axes build +function timber:axes/name_objective with storage timber:axes build +assert data storage timber:axes build{holder:"supertools.emeraldaxe",obj:"timber_axe.supertools.emeraldaxe"} +data remove storage timber:axes build diff --git a/tests/data/timber_test/test/axes_registered_custom_axe_breaks_at_its_registered_durability.mcfunction b/tests/data/timber_test/test/axes_registered_custom_axe_breaks_at_its_registered_durability.mcfunction new file mode 100644 index 0000000..220f7de --- /dev/null +++ b/tests/data/timber_test/test/axes_registered_custom_axe_breaks_at_its_registered_durability.mcfunction @@ -0,0 +1,28 @@ +#> A registered axe breaks at the durability it was registered with (30), also if the item itself could take 1000: about 30 logs +# @environment timber_test:group_3 +# @template timber_test:arena +# @timeout 400 +# @skyaccess true + +function timber_test:platform +function timber_test:tree {feature:"minecraft:mega_spruce"} +assert block ~20 ~ ~20 #minecraft:logs + +await delay 1s +function timber_test:player {name:"stick_break_t",axe:"minecraft:iron_axe"} +item replace entity @e[type=minecraft:player,name=stick_break_t,limit=1] weapon.mainhand with minecraft:stick[minecraft:max_damage=1000,minecraft:max_stack_size=1,minecraft:tool={rules:[{blocks:"#minecraft:logs",speed:6,correct_for_drops:true}],default_mining_speed:1,damage_per_block:1}] +function timber_test:mine {name:"stick_break_t"} + +# wait until the datapack has had its chance to act +# (await doesn't work inside a called function, so this block is repeated in every test) +# 1. the base log is gone: the dummy hit the tree +# 2. five ticks pass: the datapack reacts to the mined block in the next tick or two +# 3. no marker is left in the arena +await not block ~20 ~ ~20 #minecraft:logs +await delay 5t +await not entity @e[type=minecraft:marker,dx=39,dy=39,dz=39] + +scoreboard objectives add timber_test dummy +execute store success score #has timber_test if items entity @e[type=minecraft:player,name=stick_break_t] weapon.mainhand * +# the datapack took the axe when it was worn out, the item itself had 900 durability left +assert score #has timber_test matches 0 diff --git a/tests/data/timber_test/test/axes_registered_custom_axe_chops_a_tree.mcfunction b/tests/data/timber_test/test/axes_registered_custom_axe_chops_a_tree.mcfunction new file mode 100644 index 0000000..ff6679a --- /dev/null +++ b/tests/data/timber_test/test/axes_registered_custom_axe_chops_a_tree.mcfunction @@ -0,0 +1,26 @@ +#> An axe that another datapack registered with timber:api/add_axe chops a tree +# @environment timber_test:group_1 +# @template timber_test:arena +# @timeout 400 +# @skyaccess true + +function timber_test:platform +function timber_test:tree {feature:"minecraft:oak"} +assert block ~20 ~ ~20 #minecraft:logs + +await delay 1s +function timber_test:count_d7 {test:"axes_registered_custom_axe_chops_a_tree"} +function timber_test:player {name:"stick_chop_t",axe:"minecraft:iron_axe"} +item replace entity @e[type=minecraft:player,name=stick_chop_t,limit=1] weapon.mainhand with minecraft:stick[minecraft:max_damage=100,minecraft:max_stack_size=1,minecraft:tool={rules:[{blocks:"#minecraft:logs",speed:6,correct_for_drops:true}],default_mining_speed:1,damage_per_block:1}] +function timber_test:mine {name:"stick_chop_t"} + +# wait until the datapack has had its chance to act +# (await doesn't work inside a called function, so this block is repeated in every test) +# 1. the base log is gone: the dummy hit the tree +# 2. five ticks pass: the datapack reacts to the mined block in the next tick or two +# 3. no marker is left in the arena +await not block ~20 ~ ~20 #minecraft:logs +await delay 5t +await not entity @e[type=minecraft:marker,dx=39,dy=39,dz=39] + +function timber_test:assert_no_tree {test:"axes_registered_custom_axe_chops_a_tree"} diff --git a/tests/data/timber_test/test/axes_registered_custom_axe_off_does_not_chop.mcfunction b/tests/data/timber_test/test/axes_registered_custom_axe_off_does_not_chop.mcfunction new file mode 100644 index 0000000..667bcf2 --- /dev/null +++ b/tests/data/timber_test/test/axes_registered_custom_axe_off_does_not_chop.mcfunction @@ -0,0 +1,26 @@ +#> A registered axe that is switched off in the settings doesn't chop +# @environment timber_test:custom_axe_off +# @template timber_test:arena +# @timeout 400 +# @skyaccess true + +function timber_test:platform +function timber_test:tree {feature:"minecraft:oak"} +assert block ~20 ~ ~20 #minecraft:logs + +await delay 1s +function timber_test:count_logs {test:"axes_registered_custom_axe_off_does_not_chop"} +function timber_test:player {name:"stick_off_t",axe:"minecraft:iron_axe"} +item replace entity @e[type=minecraft:player,name=stick_off_t,limit=1] weapon.mainhand with minecraft:stick[minecraft:max_damage=100,minecraft:max_stack_size=1,minecraft:tool={rules:[{blocks:"#minecraft:logs",speed:6,correct_for_drops:true}],default_mining_speed:1,damage_per_block:1}] +function timber_test:mine {name:"stick_off_t"} + +# wait until the datapack has had its chance to act +# (await doesn't work inside a called function, so this block is repeated in every test) +# 1. the base log is gone: the dummy hit the tree +# 2. five ticks pass: the datapack reacts to the mined block in the next tick or two +# 3. no marker is left in the arena +await not block ~20 ~ ~20 #minecraft:logs +await delay 5t +await not entity @e[type=minecraft:marker,dx=39,dy=39,dz=39] + +function timber_test:assert_only_mined_gone {test:"axes_registered_custom_axe_off_does_not_chop"} diff --git a/tests/data/timber_test/test/axes_registered_custom_axe_wears_it_out_like_an_axe.mcfunction b/tests/data/timber_test/test/axes_registered_custom_axe_wears_it_out_like_an_axe.mcfunction new file mode 100644 index 0000000..2b798c8 --- /dev/null +++ b/tests/data/timber_test/test/axes_registered_custom_axe_wears_it_out_like_an_axe.mcfunction @@ -0,0 +1,28 @@ +#> The damage of a registered axe counts up with every log, a small tree doesn't break it (registered durability 30) +# @environment timber_test:group_2 +# @template timber_test:arena +# @timeout 400 +# @skyaccess true + +function timber_test:platform +function timber_test:tree {feature:"minecraft:oak"} +assert block ~20 ~ ~20 #minecraft:logs + +await delay 1s +function timber_test:player {name:"stick_wear_t",axe:"minecraft:iron_axe"} +item replace entity @e[type=minecraft:player,name=stick_wear_t,limit=1] weapon.mainhand with minecraft:stick[minecraft:max_damage=1000,minecraft:max_stack_size=1,minecraft:tool={rules:[{blocks:"#minecraft:logs",speed:6,correct_for_drops:true}],default_mining_speed:1,damage_per_block:1}] +function timber_test:mine {name:"stick_wear_t"} + +# wait until the datapack has had its chance to act +# (await doesn't work inside a called function, so this block is repeated in every test) +# 1. the base log is gone: the dummy hit the tree +# 2. five ticks pass: the datapack reacts to the mined block in the next tick or two +# 3. no marker is left in the arena +await not block ~20 ~ ~20 #minecraft:logs +await delay 5t +await not entity @e[type=minecraft:marker,dx=39,dy=39,dz=39] + +scoreboard objectives add timber_test dummy +scoreboard players set #dmg timber_test 0 +execute store result score #dmg timber_test run data get entity @e[type=minecraft:player,name=stick_wear_t,limit=1] SelectedItem.components."minecraft:damage" +assert score #dmg timber_test matches 3..29 diff --git a/tests/data/timber_test/test/settings_menu_flips_every_setting.mcfunction b/tests/data/timber_test/test/settings_menu_flips_every_setting.mcfunction index 74b46a0..5fcbd3c 100644 --- a/tests/data/timber_test/test/settings_menu_flips_every_setting.mcfunction +++ b/tests/data/timber_test/test/settings_menu_flips_every_setting.mcfunction @@ -152,63 +152,72 @@ assert score persistent timber matches 0 # wooden_axe: default 1 assert score wooden_axe timber matches 1 -scoreboard players set #req timber 116 +scoreboard players set #req timber 200 execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch assert score wooden_axe timber matches 0 -scoreboard players set #req timber 116 +scoreboard players set #req timber 200 execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch assert score wooden_axe timber matches 1 -# golden_axe: default 1 -assert score golden_axe timber matches 1 -scoreboard players set #req timber 117 -execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch -assert score golden_axe timber matches 0 -scoreboard players set #req timber 117 -execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch -assert score golden_axe timber matches 1 - # stone_axe: default 1 assert score stone_axe timber matches 1 -scoreboard players set #req timber 118 +scoreboard players set #req timber 201 execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch assert score stone_axe timber matches 0 -scoreboard players set #req timber 118 +scoreboard players set #req timber 201 execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch assert score stone_axe timber matches 1 # copper_axe: default 1 assert score copper_axe timber matches 1 -scoreboard players set #req timber 119 +scoreboard players set #req timber 202 execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch assert score copper_axe timber matches 0 -scoreboard players set #req timber 119 +scoreboard players set #req timber 202 execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch assert score copper_axe timber matches 1 # iron_axe: default 1 assert score iron_axe timber matches 1 -scoreboard players set #req timber 120 +scoreboard players set #req timber 203 execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch assert score iron_axe timber matches 0 -scoreboard players set #req timber 120 +scoreboard players set #req timber 203 execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch assert score iron_axe timber matches 1 +# golden_axe: default 1 +assert score golden_axe timber matches 1 +scoreboard players set #req timber 204 +execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch +assert score golden_axe timber matches 0 +scoreboard players set #req timber 204 +execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch +assert score golden_axe timber matches 1 + # diamond_axe: default 1 assert score diamond_axe timber matches 1 -scoreboard players set #req timber 121 +scoreboard players set #req timber 205 execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch assert score diamond_axe timber matches 0 -scoreboard players set #req timber 121 +scoreboard players set #req timber 205 execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch assert score diamond_axe timber matches 1 # netherite_axe: default 1 assert score netherite_axe timber matches 1 -scoreboard players set #req timber 122 +scoreboard players set #req timber 206 execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch assert score netherite_axe timber matches 0 -scoreboard players set #req timber 122 +scoreboard players set #req timber 206 execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch assert score netherite_axe timber matches 1 + +# stick: default 1 +assert score stick timber matches 1 +scoreboard players set #req timber 207 +execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch +assert score stick timber matches 0 +scoreboard players set #req timber 207 +execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch +assert score stick timber matches 1 diff --git a/tests/data/timber_test/test/uninstall_removes_everything.mcfunction b/tests/data/timber_test/test/uninstall_removes_everything.mcfunction index 8eb5b42..81da4c9 100644 --- a/tests/data/timber_test/test/uninstall_removes_everything.mcfunction +++ b/tests/data/timber_test/test/uninstall_removes_everything.mcfunction @@ -17,3 +17,24 @@ function timber_test:generated/uninstall_objectives # the markers a missing generation step (a workflow that forgot it, a wrong path) would leave a test that checks nothing and passes. assert score #uninstall_prepare_ran timber_test matches 1 assert score #uninstall_check_ran timber_test matches 1 + +# The scoreboards of the axes are made from the registered axes, their names are macro arguments in the code and not in the generated check. +# The vanilla ones and the axe of these tests have to be gone too. +data modify storage timber_test:uninstall axes set value [] +execute store success score #exists timber_test run scoreboard players add #probe timber_axe.minecraft.wooden_axe 0 +execute if score #exists timber_test matches 1 run data modify storage timber_test:uninstall axes append value "scoreboard timber_axe.minecraft.wooden_axe" +execute store success score #exists timber_test run scoreboard players add #probe timber_axe.minecraft.stone_axe 0 +execute if score #exists timber_test matches 1 run data modify storage timber_test:uninstall axes append value "scoreboard timber_axe.minecraft.stone_axe" +execute store success score #exists timber_test run scoreboard players add #probe timber_axe.minecraft.copper_axe 0 +execute if score #exists timber_test matches 1 run data modify storage timber_test:uninstall axes append value "scoreboard timber_axe.minecraft.copper_axe" +execute store success score #exists timber_test run scoreboard players add #probe timber_axe.minecraft.iron_axe 0 +execute if score #exists timber_test matches 1 run data modify storage timber_test:uninstall axes append value "scoreboard timber_axe.minecraft.iron_axe" +execute store success score #exists timber_test run scoreboard players add #probe timber_axe.minecraft.golden_axe 0 +execute if score #exists timber_test matches 1 run data modify storage timber_test:uninstall axes append value "scoreboard timber_axe.minecraft.golden_axe" +execute store success score #exists timber_test run scoreboard players add #probe timber_axe.minecraft.diamond_axe 0 +execute if score #exists timber_test matches 1 run data modify storage timber_test:uninstall axes append value "scoreboard timber_axe.minecraft.diamond_axe" +execute store success score #exists timber_test run scoreboard players add #probe timber_axe.minecraft.netherite_axe 0 +execute if score #exists timber_test matches 1 run data modify storage timber_test:uninstall axes append value "scoreboard timber_axe.minecraft.netherite_axe" +execute store success score #exists timber_test run scoreboard players add #probe timber_axe.minecraft.stick 0 +execute if score #exists timber_test matches 1 run data modify storage timber_test:uninstall axes append value "scoreboard timber_axe.minecraft.stick" +execute if data storage timber_test:uninstall axes[0] run fail [{"text":"the scoreboards of the axes are left after uninstall: "},{"nbt":"axes","storage":"timber_test:uninstall"}] diff --git a/tests/data/timber_test/test_environment/axes_api.json b/tests/data/timber_test/test_environment/axes_api.json new file mode 100644 index 0000000..b07bc16 --- /dev/null +++ b/tests/data/timber_test/test_environment/axes_api.json @@ -0,0 +1,5 @@ +{ + "type": "minecraft:function", + "setup": "timber_test:env/axes_api_setup", + "teardown": "timber_test:env/axes_api_teardown" +} diff --git a/tests/data/timber_test/test_environment/custom_axe_off.json b/tests/data/timber_test/test_environment/custom_axe_off.json new file mode 100644 index 0000000..554c9de --- /dev/null +++ b/tests/data/timber_test/test_environment/custom_axe_off.json @@ -0,0 +1,5 @@ +{ + "type": "minecraft:function", + "setup": "timber_test:env/custom_axe_off_setup", + "teardown": "timber_test:env/custom_axe_off_teardown" +} From eec351955ce340bd963a6c35e4ef640d7f9a899d Mon Sep 17 00:00:00 2001 From: Flavio Felder Date: Tue, 22 Sep 2026 01:57:40 +0200 Subject: [PATCH 03/13] Describe the axe API in the Advanced Manual (also includes the staged table change) Co-Authored-By: Claude Sonnet 5 --- README.md | 86 +++++++++++++++++-------------------------------------- 1 file changed, 26 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 00b56e3..fe138c2 100644 --- a/README.md +++ b/README.md @@ -76,83 +76,49 @@ If you have difficulties or errors in trying to change the timber datapack, then ## Add modded axes **(for example the emeraldaxe from supertools)**
-(Small note: Your modded axe will break after or below zero durability. This shouldn't be a problem. It's not really worth to implement a fix, because you're only able to chop a few more logs) +Timber knows its axes from a list, and the seven vanilla axes are in that list the same way. You add an axe with one command, in a small datapack of your own (no need to edit the files of Timber, so an update of Timber doesn't remove it). -Get the name of your modded axe: -Go ingame and type `/scoreboard objectives add x minecraft.used:` in chat. Now search for your modded axe there. -In my example it's called `supertools.emeraldaxe`. - -Give your modded axe a nickname: -In my case a gave him the name `timber_emera_axe`. -This is technically the name of your axe used by the datapack. You can name it however you like just look that you write it everywhere the same and that you follow these points: -Always use the prefix `timber_` -The max. length of the nickname is `timber_123456789` -Don't use names twice for other axes! - -`Timber/data/timber/functions/init.mcfunctions` - -Add this code at the place where similar commands are +The command: ``` -scoreboard objectives add timber_emera_axe minecraft.used:supertools.emeraldaxe +function timber:api/add_axe {item:"supertools:emeraldaxe", durability:500, label:"Emerald Axe"} ``` +- `item` is the id of the axe, the one you get in the game with `/give @s supertools:emeraldaxe`. +- `durability` is how much damage the axe can take. Timber removes the axe when it reaches this value. +- `label` is the name of the button in the settings menu (`/trigger TimberSettings`). The button appears by itself. + +Timber makes the scoreboard for the axe on its own (here `timber_axe.supertools.emeraldaxe`), you don't have to name anything. Calling the command again for the same item replaces the entry, so it doesn't matter how often it runs. If the item doesn't exist (the mod isn't installed) Timber tells you and adds nothing, so you can register axes of mods that not everybody has. -`Timber/data/timber/functions/used_axe.mcfunctions` +**Where to put the command:** in a function that Timber calls whenever it loads (also after `/reload`). -Add this code at the place where similar commands are +`mypack/data/mypack/function/register_axes.mcfunction` ``` -execute unless score @s timber_disabled matches 1.. if score @s timber_emera_axe matches 1.. run function timber:sneaking -Also add this code at the place where similar commands are -scoreboard players set @s timber_emera_axe 0 +function timber:api/add_axe {item:"supertools:emeraldaxe", durability:500, label:"Emerald Axe"} ``` -`Timber/data/timber/predicates/used_axe.json` - -The content of the upper part of this file should look like this. +`mypack/data/timber/tags/function/register_axes.json` ```json { -  "condition": "minecraft:any_of", -  "terms": [ -    { -      "condition": "minecraft:entity_scores", -      "entity": "this", -      "scores": { -        "timber_emera_axe": { -          "min": 1, -          "max": 2147483647 -        } -      } -    }, -    { -      "condition": "minecraft:entity_scores", -      "entity": "this", -      "scores": { -        "timber_w_axe": { -          "min": 1, -          "max": 2147483647 -        } -      } -    }, -    . -    . -    . + "values": [ + "mypack:register_axes" + ] +} ``` -`Timber/data/timber/tags/items/axes.json` +Also add the axe to the item tag, it is used for the loot of mushroom stems: -The content should look like this. Don't forget to add the `,` after the `netherite_axe` and to change the `:` between `supertools` and `emeraldaxe`! +`mypack/data/timber/tags/item/axes.json` ```json { -  "values":[ -    "minecraft:wooden_axe", -    "minecraft:stone_axe", -    "minecraft:iron_axe", -    "minecraft:golden_axe", -    "minecraft:diamond_axe", -    "minecraft:netherite_axe", -    "supertools:emeraldaxe" -  ] + "values": [ + "supertools:emeraldaxe" + ] } ``` +(Without `pack.mcmeta` the folder isn't a datapack. Copy the one of Timber and change the description.) + +Notes: +- Vanilla axes are registered exactly like this by Timber itself (`data/timber/function/axes/init.mcfunction`). You can change their durability there or register them again with another value. +- Timber can only see the damage of the axe, so a modded axe with its own way of breaking may stay in the hand for a few more logs. That is not a problem.
From db9fa2d1bc8a84c3a5109ffa6a80016f0dc2bd1d Mon Sep 17 00:00:00 2001 From: Flavio Felder Date: Tue, 22 Sep 2026 02:32:48 +0200 Subject: [PATCH 04/13] README: the axe one-liner up front, the API for other datapacks in the Advanced Manual Co-Authored-By: Claude Sonnet 5 --- README.md | 79 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index fe138c2..71fa859 100644 --- a/README.md +++ b/README.md @@ -58,44 +58,35 @@ Every player can individually turn the datapack ON or OFF by using: # Advanced Manual -
- -Click to reveal - -## About -This Advanced Manual will help you to add or change certain elements of this datapack which require editing of your side. - -> [!IMPORTANT] -> In order to add or change the following you have to put the content of the datapack (`Timber.zip`) in a folder called `Timber`. -> There are directories to different type of files. Open them with any kind of text editor. Below the directories is the code which you have to insert or replace (it's stated which one of these). +## Add a Custom Axe +To add the axe of a mod (for example the emeraldaxe of supertools) run one command: +``` +function timber:api/add_axe {item:"supertools:emeraldaxe", durability:500, label:"Emerald Axe"} +``` +- `item` is the id of the axe, the one you get with `/give @s supertools:emeraldaxe`. +- `durability` is how much damage the axe can take. Timber takes the axe away when it reaches this value. +- `label` is the name of its button in the settings menu. The button appears by itself. -## Debugging -If you have difficulties or errors in trying to change the timber datapack, then please enable the output log (Minecraft Launcher → Settings → General → Enable `Open output log when Minecraft: Java Edition starts`). +The command is forgotten at a `/reload`, so put it in a function that Timber runs on every load, see below. +## For Other Datapacks -## Add modded axes -**(for example the emeraldaxe from supertools)**
-Timber knows its axes from a list, and the seven vanilla axes are in that list the same way. You add an axe with one command, in a small datapack of your own (no need to edit the files of Timber, so an update of Timber doesn't remove it). +
-The command: -``` -function timber:api/add_axe {item:"supertools:emeraldaxe", durability:500, label:"Emerald Axe"} -``` -- `item` is the id of the axe, the one you get in the game with `/give @s supertools:emeraldaxe`. -- `durability` is how much damage the axe can take. Timber removes the axe when it reaches this value. -- `label` is the name of the button in the settings menu (`/trigger TimberSettings`). The button appears by itself. +Click to reveal
-Timber makes the scoreboard for the axe on its own (here `timber_axe.supertools.emeraldaxe`), you don't have to name anything. Calling the command again for the same item replaces the entry, so it doesn't matter how often it runs. If the item doesn't exist (the mod isn't installed) Timber tells you and adds nothing, so you can register axes of mods that not everybody has. +Timber has an API, so that your datapack can work together with it without changing any file of Timber (an update of Timber doesn't remove your changes). Everything below goes into a datapack of your own, with its own `pack.mcmeta`. Replace `mypack` with the namespace of your datapack. -**Where to put the command:** in a function that Timber calls whenever it loads (also after `/reload`). +### Register Axes When Timber Loads +Timber runs every function of the function tag `timber:register_axes` after it has registered its own axes, at every load and `/reload`. -`mypack/data/mypack/function/register_axes.mcfunction` +`data/mypack/function/register_axes.mcfunction` ``` function timber:api/add_axe {item:"supertools:emeraldaxe", durability:500, label:"Emerald Axe"} ``` -`mypack/data/timber/tags/function/register_axes.json` +`data/timber/tags/function/register_axes.json` ```json { "values": [ @@ -104,9 +95,9 @@ function timber:api/add_axe {item:"supertools:emeraldaxe", durability:500, label } ``` -Also add the axe to the item tag, it is used for the loot of mushroom stems: +Also add the axe to the item tag `timber:axes`, it is used for the loot of mushroom stems: -`mypack/data/timber/tags/item/axes.json` +`data/timber/tags/item/axes.json` ```json { "values": [ @@ -114,15 +105,37 @@ Also add the axe to the item tag, it is used for the loot of mushroom stems: ] } ``` -(Without `pack.mcmeta` the folder isn't a datapack. Copy the one of Timber and change the description.) -Notes: -- Vanilla axes are registered exactly like this by Timber itself (`data/timber/function/axes/init.mcfunction`). You can change their durability there or register them again with another value. -- Timber can only see the damage of the axe, so a modded axe with its own way of breaking may stay in the hand for a few more logs. That is not a problem. +Good to know: +- Timber registers its vanilla axes with the same command (`data/timber/function/axes/init.mcfunction`). If you register one of them again, your values replace the vanilla ones. +- Timber only sees the damage of an axe. A modded axe with its own way of breaking may stay in the hand for a few more logs, that is not a problem. + +### React to Broken Blocks +Timber runs these function tags for every block it chops, at the position of that block. Add your function to the tag to do something with it, for example to give xp or to play a sound. + +| Function tag | Runs for | +|---|---| +| `timber:api/break_log` | a log | +| `timber:api/break_leaf` | leaves | +| `timber:api/break_root` | a root (mangrove) | +| `timber:api/break_stem` | the stem of a huge mushroom | +| `timber:api/break_cap` | the cap of a huge mushroom | + +`data/timber/tags/function/api/break_log.json` +```json +{ + "values": [ + "mypack:on_log" + ] +} +``` + +## Debugging +If something doesn't work, enable the output log (Minecraft Launcher → Settings → General → Enable `Open output log when Minecraft: Java Edition starts`). Timber writes a warning to the chat of everybody without the tag `noTimberWarning` when a call of the API is wrong.

-# Installation instructions +# Installation Instructions - Download the Datapack - Start Minecraft (Java Edition) - Click on `Singleplayer` From 78cff329e659f2825ba381d44740fc6003a0f5ae Mon Sep 17 00:00:00 2001 From: Flavio Felder Date: Tue, 22 Sep 2026 02:48:12 +0200 Subject: [PATCH 05/13] Keep an axe that was added outside of a load through the next /reload timber:axes/init used to clear and rebuild the whole registry at every load, so an axe added by a plain chat command (not through the function tag timber:register_axes) was forgotten again on reload. Axes are now marked custom when they are registered outside of the load pass, and init keeps those instead of wiping them; only the vanilla axes and the ones of the function tag are rebuilt, since something registers them again anyway. Co-Authored-By: Claude Sonnet 5 --- data/timber/function/axes/create.mcfunction | 4 ++++ data/timber/function/axes/init.mcfunction | 17 +++++++++++------ data/timber/function/axes/reset_next.mcfunction | 9 +++++++++ data/timber/function/axes/uninstall.mcfunction | 1 + 4 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 data/timber/function/axes/reset_next.mcfunction diff --git a/data/timber/function/axes/create.mcfunction b/data/timber/function/axes/create.mcfunction index 1bb6e45..a8623c3 100644 --- a/data/timber/function/axes/create.mcfunction +++ b/data/timber/function/axes/create.mcfunction @@ -17,6 +17,10 @@ data modify storage timber:axes entry.durability set from storage timber:axes bu data modify storage timber:axes entry.label set from storage timber:axes build.label data modify storage timber:axes entry.obj set from storage timber:axes build.obj data modify storage timber:axes entry.holder set from storage timber:axes build.holder +# custom axes (added outside of a load, for example typed in chat) survive the next load; timber:axes/init registers the vanilla +# axes and the ones of the function tag itself at every load, so those don't need to +execute if score #axes_loading timber matches 1 run data modify storage timber:axes entry.custom set value 0b +execute unless score #axes_loading timber matches 1 run data modify storage timber:axes entry.custom set value 1b data modify storage timber:axes list append from storage timber:axes entry data remove storage timber:axes build data remove storage timber:axes entry diff --git a/data/timber/function/axes/init.mcfunction b/data/timber/function/axes/init.mcfunction index 1444290..9f309f6 100644 --- a/data/timber/function/axes/init.mcfunction +++ b/data/timber/function/axes/init.mcfunction @@ -1,10 +1,13 @@ -# Sets up the axes at every load: forgets the ones of the last load, registers the vanilla axes, then asks the other datapacks. -# Their functions are in the function tag #timber:register_axes, so the order of the datapacks doesn't matter. +# Sets up the axes at every load: forgets the auto-registered axes of the last load, registers the vanilla axes, then asks the +# other datapacks. Their functions are in the function tag #timber:register_axes, so the order of the datapacks doesn't matter. +# An axe that was added outside of a load (typed in chat, for example) is not forgotten, nothing would register it again. -# scoreboards of axes that are not registered any more (and of the old version, where they were fixed) +# scoreboards of axes that are not registered any more (and of the old version, where they were fixed), keeps the custom ones +data modify storage timber:axes keep set value [] data modify storage timber:axes queue set from storage timber:axes list -function timber:axes/remove_objectives -data remove storage timber:axes list +function timber:axes/reset_next +data modify storage timber:axes list set from storage timber:axes keep +data remove storage timber:axes keep scoreboard objectives remove timber_w_axe scoreboard objectives remove timber_s_axe scoreboard objectives remove timber_c_axe @@ -13,7 +16,8 @@ scoreboard objectives remove timber_g_axe scoreboard objectives remove timber_d_axe scoreboard objectives remove timber_n_axe -# vanilla axes, in the order of the settings menu +# vanilla axes, in the order of the settings menu (axes registered while this score is 1 count as auto-registered, not custom) +scoreboard players set #axes_loading timber 1 function timber:api/add_axe {item:"minecraft:wooden_axe",durability:59,label:"Wooden Axe"} function timber:api/add_axe {item:"minecraft:stone_axe",durability:131,label:"Stone Axe"} function timber:api/add_axe {item:"minecraft:copper_axe",durability:190,label:"Copper Axe"} @@ -24,3 +28,4 @@ function timber:api/add_axe {item:"minecraft:netherite_axe",durability:2031,labe # axes of other datapacks function #timber:register_axes +scoreboard players set #axes_loading timber 0 diff --git a/data/timber/function/axes/reset_next.mcfunction b/data/timber/function/axes/reset_next.mcfunction new file mode 100644 index 0000000..a047cc0 --- /dev/null +++ b/data/timber/function/axes/reset_next.mcfunction @@ -0,0 +1,9 @@ +# walks the old list of axes: keeps the ones that were added outside of a load (custom axes, for example typed in chat), removes +# the scoreboard of the others (they get rebuilt right after by timber:axes/init, from the vanilla axes and the function tag) +execute unless data storage timber:axes queue[0] run return 0 +data modify storage timber:axes entry set from storage timber:axes queue[0] +execute if data storage timber:axes entry{custom:1b} run data modify storage timber:axes keep append from storage timber:axes entry +execute unless data storage timber:axes entry{custom:1b} run function timber:axes/remove_objective with storage timber:axes entry +data remove storage timber:axes queue[0] +data remove storage timber:axes entry +function timber:axes/reset_next diff --git a/data/timber/function/axes/uninstall.mcfunction b/data/timber/function/axes/uninstall.mcfunction index 3b668a0..fb851de 100644 --- a/data/timber/function/axes/uninstall.mcfunction +++ b/data/timber/function/axes/uninstall.mcfunction @@ -3,6 +3,7 @@ data modify storage timber:axes queue set from storage timber:axes list function timber:axes/remove_objectives data remove storage timber:axes list data remove storage timber:axes queue +data remove storage timber:axes keep data remove storage timber:axes build data remove storage timber:axes entry data remove storage timber:axes pick From 78681cd77bf8c3a844ff5cac5c520ceef57839bd Mon Sep 17 00:00:00 2001 From: Flavio Felder Date: Tue, 22 Sep 2026 02:48:12 +0200 Subject: [PATCH 06/13] Test that a custom axe survives timber:axes/init, and reset the axe test env with uninstall+init Co-Authored-By: Claude Sonnet 5 --- .../function/env/axes_api_teardown.mcfunction | 4 +++- ..._axe_can_be_repeated_and_ignores_bad_input.mcfunction | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/data/timber_test/function/env/axes_api_teardown.mcfunction b/tests/data/timber_test/function/env/axes_api_teardown.mcfunction index c422ad0..44790cb 100644 --- a/tests/data/timber_test/function/env/axes_api_teardown.mcfunction +++ b/tests/data/timber_test/function/env/axes_api_teardown.mcfunction @@ -1,3 +1,5 @@ #> Back to the default -#> Register the axes again, the way a load does (forgets what the test added) +#> An axe the test added directly (not through the function tag) now survives timber:axes/init, so a plain reload isn't enough +#> any more to forget it. Uninstall clears the registry completely, then init rebuilds it the way a load does. +function timber:axes/uninstall function timber:axes/init diff --git a/tests/data/timber_test/test/axes_add_axe_can_be_repeated_and_ignores_bad_input.mcfunction b/tests/data/timber_test/test/axes_add_axe_can_be_repeated_and_ignores_bad_input.mcfunction index bd67dcc..e2bbf60 100644 --- a/tests/data/timber_test/test/axes_add_axe_can_be_repeated_and_ignores_bad_input.mcfunction +++ b/tests/data/timber_test/test/axes_add_axe_can_be_repeated_and_ignores_bad_input.mcfunction @@ -46,3 +46,12 @@ function timber:axes/name_modded with storage timber:axes build function timber:axes/name_objective with storage timber:axes build assert data storage timber:axes build{holder:"supertools.emeraldaxe",obj:"timber_axe.supertools.emeraldaxe"} data remove storage timber:axes build + +# a custom axe (added outside of a load, like the shears above) survives timber:axes/init, the way a real /reload would run it: +# nothing would register it again, so init has to leave it alone +function timber:axes/init +execute store result score #n timber_test run data get storage timber:axes list +assert score #n timber_test matches 9 +assert data storage timber:axes list[{item:"minecraft:shears",durability:300}] +execute store success score #exists timber_test run scoreboard players add #probe timber_axe.minecraft.shears 0 +assert score #exists timber_test matches 1 From b4a19b42ea843dc51cfe5163edeb9ce9b040f120 Mon Sep 17 00:00:00 2001 From: Flavio Felder Date: Tue, 22 Sep 2026 02:50:51 +0200 Subject: [PATCH 07/13] README: the axe API is now persistent, correct the note about /reload Co-Authored-By: Claude Sonnet 5 --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 71fa859..71eeef2 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ Every player can individually turn the datapack ON or OFF by using: # Advanced Manual -## Add a Custom Axe +## Add Custom Axes To add the axe of a mod (for example the emeraldaxe of supertools) run one command: ``` @@ -68,8 +68,6 @@ function timber:api/add_axe {item:"supertools:emeraldaxe", durability:500, label - `durability` is how much damage the axe can take. Timber takes the axe away when it reaches this value. - `label` is the name of its button in the settings menu. The button appears by itself. -The command is forgotten at a `/reload`, so put it in a function that Timber runs on every load, see below. - ## For Other Datapacks
From b71beab1ff6e3744c94ff2a7363f8b67cfd68876 Mon Sep 17 00:00:00 2001 From: Flavio Felder Date: Tue, 22 Sep 2026 13:10:16 +0200 Subject: [PATCH 08/13] Fix typo: rename scoreboard timber_prsistent to timber_persistent The old name was truncated to fit Minecraft's former 16-character objective name limit, dropping an 'e' and reading as a typo. Now that longer names are allowed, spell it correctly, and remove the old objective on the next init so upgrading worlds don't keep the stray one around. Fixes #7 Co-Authored-By: Claude Sonnet 5 --- 121/data/timber/predicate/block/leaf.json | 2 +- data/timber/function/init.mcfunction | 3 ++- data/timber/function/run.mcfunction | 4 ++-- data/timber/function/uninstall.mcfunction | 2 +- data/timber/predicate/block/leaf.json | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/121/data/timber/predicate/block/leaf.json b/121/data/timber/predicate/block/leaf.json index 7f82968..33e0408 100644 --- a/121/data/timber/predicate/block/leaf.json +++ b/121/data/timber/predicate/block/leaf.json @@ -27,7 +27,7 @@ "type": "minecraft:entity_scores", "entity": "this", "scores": { - "timber_prsistent": { + "timber_persistent": { "min": 1 } } diff --git a/data/timber/function/init.mcfunction b/data/timber/function/init.mcfunction index 03d902b..7547d8e 100644 --- a/data/timber/function/init.mcfunction +++ b/data/timber/function/init.mcfunction @@ -11,7 +11,8 @@ scoreboard objectives add TimberSetTreeSize trigger scoreboard objectives add TimberSetLeaves trigger scoreboard objectives add timber_menu dummy scoreboard objectives add timber_disabled dummy -scoreboard objectives add timber_prsistent dummy +scoreboard objectives remove timber_prsistent +scoreboard objectives add timber_persistent dummy scoreboard players reset * TimberSettings scoreboard players reset * TimberSetBlocks diff --git a/data/timber/function/run.mcfunction b/data/timber/function/run.mcfunction index 0811087..849ae09 100644 --- a/data/timber/function/run.mcfunction +++ b/data/timber/function/run.mcfunction @@ -4,8 +4,8 @@ scoreboard players set tree_size timber 0 scoreboard players set tree_type timber 0 # load settings to other scoreboard (leaves persistent) -execute unless score persistent timber matches 1.. run scoreboard players set @s timber_prsistent 0 -execute if score persistent timber matches 1.. run scoreboard players set @s timber_prsistent 1 +execute unless score persistent timber matches 1.. run scoreboard players set @s timber_persistent 0 +execute if score persistent timber matches 1.. run scoreboard players set @s timber_persistent 1 # search last broken item (place of tree) and set a marker (limit=1 removed, because carpet on log breaks this) execute at @e[type=minecraft:item,distance=..7,nbt={Age:0s}] run summon marker ~ ~ ~ {Tags:["timber_tree","global.ignore"]} diff --git a/data/timber/function/uninstall.mcfunction b/data/timber/function/uninstall.mcfunction index d9fffef..c73dbcf 100644 --- a/data/timber/function/uninstall.mcfunction +++ b/data/timber/function/uninstall.mcfunction @@ -26,7 +26,7 @@ scoreboard objectives remove TimberSetTreeSize scoreboard objectives remove TimberSetLeaves scoreboard objectives remove timber_menu scoreboard objectives remove timber_disabled -scoreboard objectives remove timber_prsistent +scoreboard objectives remove timber_persistent # remove markers kill @e[type=minecraft:marker,tag=timber_slow_chop] diff --git a/data/timber/predicate/block/leaf.json b/data/timber/predicate/block/leaf.json index 016c5cb..69effa4 100644 --- a/data/timber/predicate/block/leaf.json +++ b/data/timber/predicate/block/leaf.json @@ -27,7 +27,7 @@ "condition": "minecraft:entity_scores", "entity": "this", "scores": { - "timber_prsistent": { + "timber_persistent": { "min": 1 } } From 5e7bcaa32a9eb486a70050ae7b4f2f5703ffe931 Mon Sep 17 00:00:00 2001 From: Flavio Felder Date: Tue, 22 Sep 2026 13:16:07 +0200 Subject: [PATCH 09/13] Lead the Player-Placed Trees tooltip with what the toggle does When the settings menu was rebuilt as buttons, this option's label lost its "Destroy" verb (it used to read "Destroy Player-Placed Trees"), and the tooltip never picked up the slack: it opened with an F3-debug-screen aside instead of saying what enabling it does. Put the plain-language effect first, move the risk warning up, and keep the technical detail as a short aside at the end. Co-Authored-By: Claude Sonnet 5 --- data/timber/function/settings/open/menu.mcfunction | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/timber/function/settings/open/menu.mcfunction b/data/timber/function/settings/open/menu.mcfunction index b27b578..6cbcdd6 100644 --- a/data/timber/function/settings/open/menu.mcfunction +++ b/data/timber/function/settings/open/menu.mcfunction @@ -31,8 +31,8 @@ execute if score hunger timber matches 1 run data modify storage timber:settings execute unless score hunger timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Hunger Effect"},{"translate":"options.off","color":"red"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 111"},"tooltip":"Gives a hunger effect for 1 second with the same level as the size of the chopped tree."} execute if score stopsound timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Mute Breaking Sounds"},{"translate":"options.on","color":"green"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 112"},"tooltip":["",{"text":"Suppresses loud breaking noises when chopping a tree."},{"text":"\n\nThis option is","color":"gray"},{"text":" experimental","color":"dark_red"},{"text":" because sometimes it still sounds loud for a fraction of a second.","color":"gray"}]} execute unless score stopsound timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Mute Breaking Sounds"},{"translate":"options.off","color":"red"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 112"},"tooltip":["",{"text":"Suppresses loud breaking noises when chopping a tree."},{"text":"\n\nThis option is","color":"gray"},{"text":" experimental","color":"dark_red"},{"text":" because sometimes it still sounds loud for a fraction of a second.","color":"gray"}]} -execute if score persistent timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Player-Placed Trees"},{"translate":"options.on","color":"green"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 115"},"tooltip":["",{"text":"Player-placed trees are trees made out of player-placed leaves (Look at leaf block with F3 debug screen: persistent = true. Requires enabling looking_at_block_state via F3+F6)."},{"text":"\n\nUse it on your own risk!\nWhen enabled, this datapack can't differentiate between generated trees and player-placed trees anymore and therefore can potentially destroy your buildings.","color":"dark_red"}]} -execute unless score persistent timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Player-Placed Trees"},{"translate":"options.off","color":"red"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 115"},"tooltip":["",{"text":"Player-placed trees are trees made out of player-placed leaves (Look at leaf block with F3 debug screen: persistent = true. Requires enabling looking_at_block_state via F3+F6)."},{"text":"\n\nUse it on your own risk!\nWhen enabled, this datapack can't differentiate between generated trees and player-placed trees anymore and therefore can potentially destroy your buildings.","color":"dark_red"}]} +execute if score persistent timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Player-Placed Trees"},{"translate":"options.on","color":"green"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 115"},"tooltip":["",{"text":"When enabled, trees made of player-placed leaves get chopped too, not just naturally generated ones."},{"text":"\n\nUse it on your own risk! The datapack can't tell your builds from real trees, so it can destroy them.","color":"dark_red"},{"text":"\n\nA leaf is \"player-placed\" when F3 shows persistent = true (enable looking_at_block_state via F3+F6 first).","color":"gray"}]} +execute unless score persistent timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Player-Placed Trees"},{"translate":"options.off","color":"red"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 115"},"tooltip":["",{"text":"When enabled, trees made of player-placed leaves get chopped too, not just naturally generated ones."},{"text":"\n\nUse it on your own risk! The datapack can't tell your builds from real trees, so it can destroy them.","color":"dark_red"},{"text":"\n\nA leaf is \"player-placed\" when F3 shows persistent = true (enable looking_at_block_state via F3+F6 first).","color":"gray"}]} execute if score chop_trees timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Chop Trees"},{"translate":"options.on","color":"green"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 113"},"tooltip":"All kind of trees will get chopped."} execute unless score chop_trees timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Chop Trees"},{"translate":"options.off","color":"red"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 113"},"tooltip":"All kind of trees will get chopped."} execute if score chop_fungi timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Chop Fungi"},{"translate":"options.on","color":"green"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 114"},"tooltip":["",{"text":"Huge fungi (nether trees) as well as huge mushrooms will get chopped."},{"text":"\n\nUse it on your own risk!\nWhen enabled, this datapack can't differentiate between generated fungi and player-placed fungi and therefore can potentially destroy your buildings.","color":"dark_red"}]} From eb10392fdc5dc88faf11dd47807ea90328b0c773 Mon Sep 17 00:00:00 2001 From: Flavio Felder Date: Tue, 22 Sep 2026 13:55:54 +0200 Subject: [PATCH 10/13] Add "Require Full Durability": skip a tree entirely if the axe can't survive it New opt-in setting, off by default like its sibling "Stop on Axe Break". Where "Stop on Axe Break" still chops whatever fits before the axe would run out, this option checks the whole tree's cost up front and, if the axe can't survive it, chops nothing at all (only the block that was already mined by vanilla is lost, no extra wear applied). It requires "Wear Out Tool More" and is mutually exclusive with "Stop on Axe Break", mirroring the existing wear_out/stop_chopping and slow_chop/drop_loot guard pattern. Fixes #9 Co-Authored-By: Claude Sonnet 5 --- .../function/settings/after_set.mcfunction | 3 ++ .../function/settings/before_set.mcfunction | 1 + .../settings/default_values.mcfunction | 1 + .../function/settings/dispatch.mcfunction | 1 + .../flip/require_full_durability.mcfunction | 7 +++ .../settings/flip/stop_chopping.mcfunction | 1 + .../function/settings/open/menu.mcfunction | 12 +++-- data/timber/function/validated.mcfunction | 3 ++ ...equire_full_durability_on_setup.mcfunction | 2 + ...ire_full_durability_on_teardown.mcfunction | 2 + ...ttings_menu_flips_every_setting.mcfunction | 9 ++++ ...guards_refuse_conflicting_flips.mcfunction | 47 ++++++++++++++++++- ...n_chops_nothing_with_a_worn_axe.mcfunction | 32 +++++++++++++ .../require_full_durability_on.json | 5 ++ 14 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 data/timber/function/settings/flip/require_full_durability.mcfunction create mode 100644 tests/data/timber_test/function/env/require_full_durability_on_setup.mcfunction create mode 100644 tests/data/timber_test/function/env/require_full_durability_on_teardown.mcfunction create mode 100644 tests/data/timber_test/test/settings_require_full_durability_on_chops_nothing_with_a_worn_axe.mcfunction create mode 100644 tests/data/timber_test/test_environment/require_full_durability_on.json diff --git a/data/timber/function/settings/after_set.mcfunction b/data/timber/function/settings/after_set.mcfunction index 24cb9b6..7484dd5 100644 --- a/data/timber/function/settings/after_set.mcfunction +++ b/data/timber/function/settings/after_set.mcfunction @@ -1,7 +1,10 @@ # options which exclude each other: the one that was just enabled wins execute if score slow_chop timber matches 1 if score drop_loot timber matches 1 if score #old_drop_loot timber matches 1 run scoreboard players set drop_loot timber 0 execute if score slow_chop timber matches 1 if score drop_loot timber matches 1 unless score #old_drop_loot timber matches 1 run scoreboard players set slow_chop timber 0 +execute if score require_full_durability timber matches 1 if score stop_chopping timber matches 1 if score #old_stop_chopping timber matches 1 run scoreboard players set stop_chopping timber 0 +execute if score require_full_durability timber matches 1 if score stop_chopping timber matches 1 unless score #old_stop_chopping timber matches 1 run scoreboard players set require_full_durability timber 0 execute if score stop_chopping timber matches 1 run scoreboard players set wear_out timber 1 +execute if score require_full_durability timber matches 1 run scoreboard players set wear_out timber 1 # start optional loops execute if score slow_chop timber matches 1 unless score #old_slow_chop timber matches 1 run function timber:slow_chop/loop diff --git a/data/timber/function/settings/before_set.mcfunction b/data/timber/function/settings/before_set.mcfunction index d0bfc54..9b2a322 100644 --- a/data/timber/function/settings/before_set.mcfunction +++ b/data/timber/function/settings/before_set.mcfunction @@ -2,3 +2,4 @@ scoreboard players operation #old_slow_chop timber = slow_chop timber scoreboard players operation #old_plant_sapling timber = plant_sapling timber scoreboard players operation #old_drop_loot timber = drop_loot timber +scoreboard players operation #old_stop_chopping timber = stop_chopping timber diff --git a/data/timber/function/settings/default_values.mcfunction b/data/timber/function/settings/default_values.mcfunction index a4a0f6e..d4284a3 100644 --- a/data/timber/function/settings/default_values.mcfunction +++ b/data/timber/function/settings/default_values.mcfunction @@ -9,6 +9,7 @@ execute unless score slow_chop timber = slow_chop timber run scoreboard players execute unless score chop_down timber = chop_down timber run scoreboard players set chop_down timber 0 execute unless score stop_chopping timber = stop_chopping timber run scoreboard players set stop_chopping timber 0 execute unless score wear_out timber = wear_out timber run scoreboard players set wear_out timber 1 +execute unless score require_full_durability timber = require_full_durability timber run scoreboard players set require_full_durability timber 0 execute unless score drop_loot timber = drop_loot timber run scoreboard players set drop_loot timber 0 execute unless score hunger timber = hunger timber run scoreboard players set hunger timber 0 execute unless score stopsound timber = stopsound timber run scoreboard players set stopsound timber 0 diff --git a/data/timber/function/settings/dispatch.mcfunction b/data/timber/function/settings/dispatch.mcfunction index c8a265c..1a2d385 100644 --- a/data/timber/function/settings/dispatch.mcfunction +++ b/data/timber/function/settings/dispatch.mcfunction @@ -28,5 +28,6 @@ execute if score #req timber matches 112 run function timber:settings/flip/stops execute if score #req timber matches 113 run function timber:settings/flip/chop_trees execute if score #req timber matches 114 run function timber:settings/flip/chop_fungi execute if score #req timber matches 115 run function timber:settings/flip/persistent +execute if score #req timber matches 116 run function timber:settings/flip/require_full_durability # 200 and up: the axes of the tools page, in the order of the list execute if score #req timber matches 200..299 run function timber:settings/flip/axe diff --git a/data/timber/function/settings/flip/require_full_durability.mcfunction b/data/timber/function/settings/flip/require_full_durability.mcfunction new file mode 100644 index 0000000..c0471c8 --- /dev/null +++ b/data/timber/function/settings/flip/require_full_durability.mcfunction @@ -0,0 +1,7 @@ +# toggles "Require Full Durability" +execute unless score wear_out timber matches 1 run return run function timber:settings/open/menu +execute if score stop_chopping timber matches 1 run return run function timber:settings/open/menu +function timber:settings/before_set +execute store success score require_full_durability timber unless score require_full_durability timber matches 1 +function timber:settings/after_set +function timber:settings/open/menu diff --git a/data/timber/function/settings/flip/stop_chopping.mcfunction b/data/timber/function/settings/flip/stop_chopping.mcfunction index 0cb5f7c..3301d8a 100644 --- a/data/timber/function/settings/flip/stop_chopping.mcfunction +++ b/data/timber/function/settings/flip/stop_chopping.mcfunction @@ -1,5 +1,6 @@ # toggles "Stop on Axe Break" execute unless score wear_out timber matches 1 run return run function timber:settings/open/menu +execute if score require_full_durability timber matches 1 run return run function timber:settings/open/menu function timber:settings/before_set execute store success score stop_chopping timber unless score stop_chopping timber matches 1 function timber:settings/after_set diff --git a/data/timber/function/settings/open/menu.mcfunction b/data/timber/function/settings/open/menu.mcfunction index 6cbcdd6..d86cda6 100644 --- a/data/timber/function/settings/open/menu.mcfunction +++ b/data/timber/function/settings/open/menu.mcfunction @@ -20,10 +20,16 @@ execute if score chop_down timber matches 1 run data modify storage timber:setti execute unless score chop_down timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Chop Down"},{"translate":"options.off","color":"red"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 107"},"tooltip":"Normally when you cut a tree in the middle only the top part of the tree gets destroyed. But if this option is enabled the bottom part will get destroyed as well."} execute unless score wear_out timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Stop on Axe Break"},{"translate":"filled_map.locked","color":"gray"}]},"width":150,"tooltip":["",{"text":"Can't be enabled when \"Wear Out Tool More\" is disabled.","color":"red"}]} execute if score wear_out timber matches 1 if score stop_chopping timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Stop on Axe Break"},{"translate":"options.on","color":"green"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 108"},"tooltip":"Stops the process of chopping the tree as soon as your axe doesn't have any durability left for the whole tree. When disabled, the tree will always be chopped finish."} -execute if score wear_out timber matches 1 unless score stop_chopping timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Stop on Axe Break"},{"translate":"options.off","color":"red"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 108"},"tooltip":"Stops the process of chopping the tree as soon as your axe doesn't have any durability left for the whole tree. When disabled, the tree will always be chopped finish."} +execute if score wear_out timber matches 1 unless score stop_chopping timber matches 1 if score require_full_durability timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Stop on Axe Break"},{"translate":"filled_map.locked","color":"gray"}]},"width":150,"tooltip":["",{"text":"Can't be enabled when \"Require Full Durability\" is enabled.","color":"red"}]} +execute if score wear_out timber matches 1 unless score stop_chopping timber matches 1 unless score require_full_durability timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Stop on Axe Break"},{"translate":"options.off","color":"red"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 108"},"tooltip":"Stops the process of chopping the tree as soon as your axe doesn't have any durability left for the whole tree. When disabled, the tree will always be chopped finish."} execute if score stop_chopping timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Wear Out Tool More"},{"translate":"filled_map.locked","color":"gray"}]},"width":150,"tooltip":["",{"text":"Can't be disabled when \"Stop on Axe Break\" is enabled.","color":"red"}]} -execute unless score stop_chopping timber matches 1 if score wear_out timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Wear Out Tool More"},{"translate":"options.on","color":"green"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 109"},"tooltip":"The tool's durability will get decreased by the amount of destroyed logs (or stems). When disabled, only one durability gets deducted."} -execute unless score stop_chopping timber matches 1 unless score wear_out timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Wear Out Tool More"},{"translate":"options.off","color":"red"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 109"},"tooltip":"The tool's durability will get decreased by the amount of destroyed logs (or stems). When disabled, only one durability gets deducted."} +execute if score require_full_durability timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Wear Out Tool More"},{"translate":"filled_map.locked","color":"gray"}]},"width":150,"tooltip":["",{"text":"Can't be disabled when \"Require Full Durability\" is enabled.","color":"red"}]} +execute unless score stop_chopping timber matches 1 unless score require_full_durability timber matches 1 if score wear_out timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Wear Out Tool More"},{"translate":"options.on","color":"green"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 109"},"tooltip":"The tool's durability will get decreased by the amount of destroyed logs (or stems). When disabled, only one durability gets deducted."} +execute unless score stop_chopping timber matches 1 unless score require_full_durability timber matches 1 unless score wear_out timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Wear Out Tool More"},{"translate":"options.off","color":"red"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 109"},"tooltip":"The tool's durability will get decreased by the amount of destroyed logs (or stems). When disabled, only one durability gets deducted."} +execute unless score wear_out timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Require Full Durability"},{"translate":"filled_map.locked","color":"gray"}]},"width":150,"tooltip":["",{"text":"Can't be enabled when \"Wear Out Tool More\" is disabled.","color":"red"}]} +execute if score wear_out timber matches 1 if score stop_chopping timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Require Full Durability"},{"translate":"filled_map.locked","color":"gray"}]},"width":150,"tooltip":["",{"text":"Can't be enabled when \"Stop on Axe Break\" is enabled.","color":"red"}]} +execute if score wear_out timber matches 1 unless score stop_chopping timber matches 1 if score require_full_durability timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Require Full Durability"},{"translate":"options.on","color":"green"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 116"},"tooltip":"If the axe won't survive the whole tree, nothing gets chopped instead of stopping partway."} +execute if score wear_out timber matches 1 unless score stop_chopping timber matches 1 unless score require_full_durability timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Require Full Durability"},{"translate":"options.off","color":"red"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 116"},"tooltip":"If the axe won't survive the whole tree, nothing gets chopped instead of stopping partway."} execute if score slow_chop timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Loot to Inventory"},{"translate":"filled_map.locked","color":"gray"}]},"width":150,"tooltip":["",{"text":"Can't be enabled when \"Chop Slowly\" is enabled.","color":"red"}]} execute unless score slow_chop timber matches 1 if score drop_loot timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Loot to Inventory"},{"translate":"options.on","color":"green"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 110"},"tooltip":["",{"text":"Lets the dropped items of the tree teleport straight into your own inventory. As soon as you have a full inventory the loot gets dropped on the ground."},{"text":"\n\nThis option can increase performance.","color":"gray"}]} execute unless score slow_chop timber matches 1 unless score drop_loot timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Loot to Inventory"},{"translate":"options.off","color":"red"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 110"},"tooltip":["",{"text":"Lets the dropped items of the tree teleport straight into your own inventory. As soon as you have a full inventory the loot gets dropped on the ground."},{"text":"\n\nThis option can increase performance.","color":"gray"}]} diff --git a/data/timber/function/validated.mcfunction b/data/timber/function/validated.mcfunction index dcdb033..ae671b5 100644 --- a/data/timber/function/validated.mcfunction +++ b/data/timber/function/validated.mcfunction @@ -1,3 +1,6 @@ +# with "Require Full Durability" on, don't chop anything (beyond the block that was already mined) if the axe wouldn't survive the whole tree +execute if score require_full_durability timber matches 1 unless score unbreakable timber matches 1 unless score durability timber <= axe_max timber run return 0 + # when full inventory fix execute if score drop_loot timber matches 1.. store result score inventory timber run data get entity @s Inventory execute if score drop_loot timber matches 1.. if entity @s[nbt={Inventory:[{Slot:100b}]}] run scoreboard players remove inventory timber 1 diff --git a/tests/data/timber_test/function/env/require_full_durability_on_setup.mcfunction b/tests/data/timber_test/function/env/require_full_durability_on_setup.mcfunction new file mode 100644 index 0000000..8ade49d --- /dev/null +++ b/tests/data/timber_test/function/env/require_full_durability_on_setup.mcfunction @@ -0,0 +1,2 @@ +#> Setting for the tests of this environment (a loop that a setting flip starts in a real world is started here) +scoreboard players set require_full_durability timber 1 diff --git a/tests/data/timber_test/function/env/require_full_durability_on_teardown.mcfunction b/tests/data/timber_test/function/env/require_full_durability_on_teardown.mcfunction new file mode 100644 index 0000000..e7223ea --- /dev/null +++ b/tests/data/timber_test/function/env/require_full_durability_on_teardown.mcfunction @@ -0,0 +1,2 @@ +#> Back to the default +scoreboard players set require_full_durability timber 0 diff --git a/tests/data/timber_test/test/settings_menu_flips_every_setting.mcfunction b/tests/data/timber_test/test/settings_menu_flips_every_setting.mcfunction index 5fcbd3c..1e5aba9 100644 --- a/tests/data/timber_test/test/settings_menu_flips_every_setting.mcfunction +++ b/tests/data/timber_test/test/settings_menu_flips_every_setting.mcfunction @@ -150,6 +150,15 @@ scoreboard players set #req timber 115 execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch assert score persistent timber matches 0 +# require_full_durability: default 0 +assert score require_full_durability timber matches 0 +scoreboard players set #req timber 116 +execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch +assert score require_full_durability timber matches 1 +scoreboard players set #req timber 116 +execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch +assert score require_full_durability timber matches 0 + # wooden_axe: default 1 assert score wooden_axe timber matches 1 scoreboard players set #req timber 200 diff --git a/tests/data/timber_test/test/settings_menu_guards_refuse_conflicting_flips.mcfunction b/tests/data/timber_test/test/settings_menu_guards_refuse_conflicting_flips.mcfunction index fa0adf3..d49b85d 100644 --- a/tests/data/timber_test/test/settings_menu_guards_refuse_conflicting_flips.mcfunction +++ b/tests/data/timber_test/test/settings_menu_guards_refuse_conflicting_flips.mcfunction @@ -1,4 +1,5 @@ -#> Flips that would break another setting are refused (slow chop and loot to inventory, stop on axe break and wear out) +#> Flips that would break another setting are refused (slow chop and loot to inventory, stop on axe break and wear out, +#> stop on axe break and require full durability) # @environment timber_test:flip_guards # @timeout 100 @@ -49,3 +50,47 @@ scoreboard players set #req timber 108 execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch assert score stop_chopping timber matches 1 scoreboard players set stop_chopping timber 0 + +# wear out is off: require full durability can't be switched on, with wear out on it can +scoreboard players set wear_out timber 0 +scoreboard players set #req timber 116 +execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch +assert score require_full_durability timber matches 0 +scoreboard players set wear_out timber 1 +scoreboard players set #req timber 116 +execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch +assert score require_full_durability timber matches 1 +scoreboard players set require_full_durability timber 0 + +# stop on axe break is on: require full durability can't be switched on, without it can +scoreboard players set stop_chopping timber 1 +scoreboard players set #req timber 116 +execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch +assert score require_full_durability timber matches 0 +scoreboard players set stop_chopping timber 0 +scoreboard players set #req timber 116 +execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch +assert score require_full_durability timber matches 1 +scoreboard players set require_full_durability timber 0 + +# require full durability is on: stop on axe break can't be switched on, without it can +scoreboard players set require_full_durability timber 1 +scoreboard players set #req timber 108 +execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch +assert score stop_chopping timber matches 0 +scoreboard players set require_full_durability timber 0 +scoreboard players set #req timber 108 +execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch +assert score stop_chopping timber matches 1 +scoreboard players set stop_chopping timber 0 + +# require full durability is on: wear out can't be switched off, without it can +scoreboard players set require_full_durability timber 1 +scoreboard players set #req timber 109 +execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch +assert score wear_out timber matches 1 +scoreboard players set require_full_durability timber 0 +scoreboard players set #req timber 109 +execute as @e[type=minecraft:player,name=adm_t,limit=1] run function timber:settings/dispatch +assert score wear_out timber matches 0 +scoreboard players set wear_out timber 1 diff --git a/tests/data/timber_test/test/settings_require_full_durability_on_chops_nothing_with_a_worn_axe.mcfunction b/tests/data/timber_test/test/settings_require_full_durability_on_chops_nothing_with_a_worn_axe.mcfunction new file mode 100644 index 0000000..348eb3f --- /dev/null +++ b/tests/data/timber_test/test/settings_require_full_durability_on_chops_nothing_with_a_worn_axe.mcfunction @@ -0,0 +1,32 @@ +#> With require_full_durability on, a tree the axe can't fully survive is left untouched (only the mined block is gone, vanilla takes 1) +# @environment timber_test:require_full_durability_on +# @template timber_test:arena +# @timeout 200 +# @skyaccess true + +function timber_test:platform +function timber_test:tree {feature:"minecraft:oak"} +assert block ~20 ~ ~20 #minecraft:logs + +await delay 1s +function timber_test:player {name:"reqfull_t",axe:"minecraft:iron_axe"} +item replace entity @e[type=minecraft:player,name=reqfull_t,limit=1] weapon.mainhand with minecraft:iron_axe[minecraft:damage=248] +function timber_test:mine {name:"reqfull_t"} + +# wait until the datapack has had its chance to act +# (await doesn't work inside a called function, so this block is repeated in every test) +# 1. the base log is gone: the dummy hit the tree +# 2. five ticks pass: the datapack reacts to the mined block in the next tick or two +# 3. no marker is left in the arena +await not block ~20 ~ ~20 #minecraft:logs +await delay 5t +await not entity @e[type=minecraft:marker,dx=39,dy=39,dz=39] + +# the rest of the tree stays: nothing beyond the mined block was chopped +function timber_test:assert_logs_left {n:"1.."} + +# only the vanilla durability loss from the mined block was applied, no extra wear +scoreboard objectives add timber_test dummy +scoreboard players set #dmg timber_test 0 +execute store result score #dmg timber_test run data get entity @e[type=minecraft:player,name=reqfull_t,limit=1] SelectedItem.components."minecraft:damage" +assert score #dmg timber_test matches 249 diff --git a/tests/data/timber_test/test_environment/require_full_durability_on.json b/tests/data/timber_test/test_environment/require_full_durability_on.json new file mode 100644 index 0000000..928bacc --- /dev/null +++ b/tests/data/timber_test/test_environment/require_full_durability_on.json @@ -0,0 +1,5 @@ +{ + "type": "minecraft:function", + "setup": "timber_test:env/require_full_durability_on_setup", + "teardown": "timber_test:env/require_full_durability_on_teardown" +} From e2e7678860594e506047de456d19189cee8ced96 Mon Sep 17 00:00:00 2001 From: Flavio Felder Date: Tue, 22 Sep 2026 16:10:23 +0200 Subject: [PATCH 11/13] Add CHANGELOG.md describing the versioning scheme Documents MAJOR.MINOR.PATCH: MAJOR for breaking changes (including dropping an old MC version or breaking the datapack API), MINOR for new features and new MC version support (even a pack.mcmeta-only bump), PATCH for bug fixes. Points to the existing V0.0.1-V7.2.0 tags for pre-changelog history instead of backfilling them. Fixes #26 Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e7f0511 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +Version numbers are `MAJOR.MINOR.PATCH`: + +- **MAJOR**: breaking change (incompatible behavior, dropping an old MC version via `min_format`, or a broken API) +- **MINOR**: new feature, or support for a new MC version via `max_format` (even a pack.mcmeta-only bump) +- **PATCH**: bug fix + +Releases before this file are tagged `V0.0.1` to `V7.2.0`. New tags use lowercase `v` (e.g. `v7.3.0`). + +## [Unreleased] From 14b5ada7ab5935ba9ce71bfdbec42dda751c6d91 Mon Sep 17 00:00:00 2001 From: Flavio Felder Date: Tue, 22 Sep 2026 17:17:41 +0200 Subject: [PATCH 12/13] Add workflow to publish releases to Modrinth Runs on every published GitHub Release, builds a zip from the tracked datapack files (data, 121, pack.mcmeta, pack.png, LICENSE, README.md) with git archive, and uploads it as a new version on the "timber-datapack" Modrinth project (id gy7p6YNM) via Kira-NT/mc-publish. pack.mcmeta only stores numeric format IDs, not Minecraft version strings, so the supported version range Modrinth needs is kept separately in .github/mc-version-range.txt (currently 26.1 to 26.3, matching the project's current listing) and must be updated in the same commit as any pack.mcmeta min_format/max_format change. Needs a MODRINTH_TOKEN repository secret (a Modrinth personal access token with "Create version" permission) before it can actually publish; PlanetMinecraft upload was dropped from scope per #20. Fixes #20 Co-Authored-By: Claude Sonnet 5 --- .github/mc-version-range.txt | 2 ++ .github/workflows/publish-modrinth.yml | 40 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 .github/mc-version-range.txt create mode 100644 .github/workflows/publish-modrinth.yml diff --git a/.github/mc-version-range.txt b/.github/mc-version-range.txt new file mode 100644 index 0000000..27d20ec --- /dev/null +++ b/.github/mc-version-range.txt @@ -0,0 +1,2 @@ +26.1 +26.3 diff --git a/.github/workflows/publish-modrinth.yml b/.github/workflows/publish-modrinth.yml new file mode 100644 index 0000000..d725d33 --- /dev/null +++ b/.github/workflows/publish-modrinth.yml @@ -0,0 +1,40 @@ +name: Publish to Modrinth + +# Runs whenever a GitHub Release is published and uploads that release as a new Modrinth version. +# .github/mc-version-range.txt holds the two Minecraft versions (lowest, highest) this build supports; update it +# in the same commit where pack.mcmeta's max_format (or min_format) changes. +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + publish: + name: Publish to Modrinth + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build the datapack zip + run: git archive --format=zip -o "Timber-${{ github.event.release.tag_name }}.zip" HEAD -- data 121 pack.mcmeta pack.png LICENSE README.md + + - name: Read the supported Minecraft version range + id: versions + run: | + MIN=$(sed -n '1p' .github/mc-version-range.txt) + MAX=$(sed -n '2p' .github/mc-version-range.txt) + echo "range=[$MIN,$MAX]" >> "$GITHUB_OUTPUT" + + - uses: Kira-NT/mc-publish@v3 + with: + modrinth-id: gy7p6YNM + modrinth-token: ${{ secrets.MODRINTH_TOKEN }} + files: Timber-${{ github.event.release.tag_name }}.zip + name: ${{ github.event.release.name }} + version: ${{ github.event.release.tag_name }} + version-type: release + changelog: ${{ github.event.release.body }} + loaders: datapack + game-versions: ${{ steps.versions.outputs.range }} From 23acf2affb61b7bc04ff6a465916017767322652 Mon Sep 17 00:00:00 2001 From: Flavio Felder Date: Tue, 22 Sep 2026 18:08:20 +0200 Subject: [PATCH 13/13] Add a releasing checklist to CHANGELOG.md and show the version in the help dialog Per-version change entries aren't duplicated here: GitHub's Releases page (with its "Generate release notes" button) already covers that and is what Modrinth publishing reads from. This file only holds what doesn't exist anywhere else: the version-number scheme and the release checklist tying together the pieces that now have to move together on every bump (pack.mcmeta, mc-version-range.txt, the version string in help.json, and the tag/release that triggers the Modrinth upload). Set the help dialog to v8.0.0: dropping support for versions before 26.1 (min_format raised to 101) is a breaking change under the MAJOR.MINOR.PATCH scheme, so the next release after V7.2.0 is a major bump. Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 8 ++++++-- data/timber/dialog/help.json | 5 +++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7f0511..50b3f73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ Version numbers are `MAJOR.MINOR.PATCH`: - **MINOR**: new feature, or support for a new MC version via `max_format` (even a pack.mcmeta-only bump) - **PATCH**: bug fix -Releases before this file are tagged `V0.0.1` to `V7.2.0`. New tags use lowercase `v` (e.g. `v7.3.0`). +Release notes live on the [GitHub Releases page](https://github.com/Moggla/Timber-Datapack/releases), not here. New tags use lowercase `v` (e.g. `v7.3.0`); releases before this file are tagged `V0.0.1` to `V7.2.0`. -## [Unreleased] +## Releasing + +1. If the supported MC versions changed: bump `pack.mcmeta` (`min_format`/`max_format`) and match `.github/mc-version-range.txt` to it. [Pack format](https://minecraft.wiki/w/Pack_format) lists which MC version has which format number. +2. Update the version string in `data/timber/dialog/help.json`. +3. Tag the commit `vX.Y.Z` and publish it as a GitHub Release, using "Generate release notes" for the body; that triggers the Modrinth upload automatically. diff --git a/data/timber/dialog/help.json b/data/timber/dialog/help.json index 7a4bdb0..0328696 100644 --- a/data/timber/dialog/help.json +++ b/data/timber/dialog/help.json @@ -6,6 +6,11 @@ "type": "minecraft:plain_message", "contents": [ "", + { + "text": "Version v8.0.0", + "color": "#724E21" + }, + "\n\n", { "text": "The Timber Datapack allows you to instantly chop down a tree just by breaking one log with any axe." },