From 79dec91303a95666300b4024c246bf7528ea4483 Mon Sep 17 00:00:00 2001 From: Flavio Felder Date: Wed, 23 Sep 2026 18:43:20 +0200 Subject: [PATCH 1/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 71eeef2..0d9cd56 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This is a really useful addition for those who don't want to spend expensive time on gathering resources! After installing, the Timber Datapack allows you to instantly chop down a tree just by breaking one log with any axe. -By default, sneaking while breaking will not chop the tree. + Optionally, you can adjust everything as you wish. # Under the Hood Features From 2de979800e39d4b6ff29312ee65c254df1048cb9 Mon Sep 17 00:00:00 2001 From: Flavio Felder Date: Wed, 23 Sep 2026 19:20:36 +0200 Subject: [PATCH 2/7] Fix autoplant on moss, mud and farmland (#39) Use #minecraft:supports_vegetation, keep farmland moisture, generate a test per plantable block Co-Authored-By: Claude Sonnet 5 --- .github/scripts/gen-sapling-ground-tests.py | 70 +++++++++++++++++++ .github/workflows/latest.yml | 5 ++ .github/workflows/test.yml | 4 ++ .../function/autoplant/plant.mcfunction | 17 ++++- .../autoplant/farmland_fix.mcfunction | 9 +++ .../function/autoplant/plant.mcfunction | 17 ++++- data/timber/function/uninstall.mcfunction | 1 + data/timber/tags/block/plantable_ground.json | 6 +- ..._keeps_the_moisture_of_farmland.mcfunction | 14 ++++ 9 files changed, 132 insertions(+), 11 deletions(-) create mode 100644 .github/scripts/gen-sapling-ground-tests.py create mode 100644 data/timber/function/autoplant/farmland_fix.mcfunction create mode 100644 tests/data/timber_test/test/settings_plant_sapling_on_keeps_the_moisture_of_farmland.mcfunction diff --git a/.github/scripts/gen-sapling-ground-tests.py b/.github/scripts/gen-sapling-ground-tests.py new file mode 100644 index 0000000..598acbd --- /dev/null +++ b/.github/scripts/gen-sapling-ground-tests.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +"""Generates one autoplant test per block a sapling can be planted on, so a block that is added to (or dropped from) Minecraft is +covered without anybody keeping a list. + +Usage: gen-sapling-ground-tests.py + +The blocks are the resolved tag #minecraft:supports_vegetation of the vanilla server jar, the tag the game itself asks when a sapling is planted. +Writes data/timber_test/test/generated/plant_sapling_on_.mcfunction. Fails when the tag is not found, so a rename can't leave a test that checks nothing. +""" +import glob +import io +import json +import os +import sys +import zipfile + +server, dest = sys.argv[1], sys.argv[2] +TAG = "supports_vegetation" + +jars = sorted(glob.glob(os.path.join(server, "versions", "*", "server-*.jar"))) +if not jars: + sys.exit("gen-sapling-ground-tests: no vanilla server jar in " + server + "/versions") +z = zipfile.ZipFile(jars[-1]) +# the vanilla server jar is a bundler, the game itself is a jar inside of it +for n in z.namelist(): + if n.startswith("META-INF/versions/") and n.endswith(".jar"): + z = zipfile.ZipFile(io.BytesIO(z.read(n))) + break + + +def resolve(tag, seen=()): + if tag in seen: + return set() + entries = json.loads(z.read(f"data/minecraft/tags/block/{tag}.json"))["values"] + blocks = set() + for entry in entries: + entry = entry["id"] if isinstance(entry, dict) else entry + if entry.startswith("#minecraft:"): + blocks |= resolve(entry[len("#minecraft:"):], seen + (tag,)) + else: + blocks.add(entry) + return blocks + + +try: + blocks = sorted(resolve(TAG)) +except KeyError: + sys.exit(f"gen-sapling-ground-tests: block tag #minecraft:{TAG} not found in {jars[-1]}") +if not blocks: + sys.exit(f"gen-sapling-ground-tests: #minecraft:{TAG} is empty") + +out = os.path.join(dest, "data", "timber_test", "test", "generated") +os.makedirs(out, exist_ok=True) +for block in blocks: + name = block.split(":")[1] + with open(os.path.join(out, f"plant_sapling_on_{name}.mcfunction"), "w", newline="\n") as f: + f.write(f"""#> GENERATED by .github/scripts/gen-sapling-ground-tests.py, do not edit. With plant_sapling on, a sapling item that lies on {block} gets planted +# @environment timber_test:plant_sapling_on +# @template timber_test:arena +# @timeout 400 +# @skyaccess true + +function timber_test:platform +setblock ~10 ~-1 ~10 {block} +summon minecraft:item ~10.5 ~0.5 ~10.5 {{Item:{{id:"minecraft:oak_sapling",count:1}}}} + +# the loop runs every 2 seconds +await block ~10 ~ ~10 minecraft:oak_sapling +""") +print(f"gen-sapling-ground-tests: {len(blocks)} blocks: {', '.join(b.split(':')[1] for b in blocks)}") diff --git a/.github/workflows/latest.yml b/.github/workflows/latest.yml index d79c820..f55952f 100644 --- a/.github/workflows/latest.yml +++ b/.github/workflows/latest.yml @@ -74,6 +74,11 @@ jobs: if: steps.setup.outputs.ready == 'true' run: python3 .github/scripts/gen-uninstall-check.py . world/datapacks/timber_test + # one autoplant test per block a sapling can be planted on, read from the tag of the vanilla jar, so new blocks are covered + - name: Generate the sapling ground tests + if: steps.setup.outputs.ready == 'true' + run: python3 .github/scripts/gen-sapling-ground-tests.py . world/datapacks/timber_test + - name: Run tests id: tests if: steps.setup.outputs.ready == 'true' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3f9dc64..f5d971b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,6 +38,10 @@ jobs: - name: Generate the uninstall check run: python3 .github/scripts/gen-uninstall-check.py . world/datapacks/timber_test + # one autoplant test per block a sapling can be planted on, read from the tag of the vanilla jar, so new blocks are covered + - name: Generate the sapling ground tests + run: python3 .github/scripts/gen-sapling-ground-tests.py . world/datapacks/timber_test + - name: Run tests run: | set -o pipefail diff --git a/121/data/timber/function/autoplant/plant.mcfunction b/121/data/timber/function/autoplant/plant.mcfunction index e0ad702..0380e9f 100644 --- a/121/data/timber/function/autoplant/plant.mcfunction +++ b/121/data/timber/function/autoplant/plant.mcfunction @@ -1,7 +1,18 @@ # replace item-sapling with block-sapling (special case for mud, because fallingblock get destroyed. 0.125 is the height when you sink into the mud) -execute unless block ~ ~ ~ minecraft:air run summon marker ~ ~ ~ {Tags:["timber_mud","global.ignore"]} -execute unless block ~ ~ ~ minecraft:air run schedule function timber:autoplant/mud_fix 1t -execute unless block ~ ~ ~ minecraft:air run setblock ~ ~ ~ minecraft:muddy_mangrove_roots +execute if block ~ ~ ~ minecraft:mud run summon marker ~ ~ ~ {Tags:["timber_mud","global.ignore"]} +execute if block ~ ~ ~ minecraft:mud run schedule function timber:autoplant/mud_fix 1t +execute if block ~ ~ ~ minecraft:mud run setblock ~ ~ ~ minecraft:muddy_mangrove_roots +# same for farmland (a bit lower than a full block, the falling block would land inside of it): a dirt block for a moment, so the falling block has something to land on. The marker remembers the moisture, farmland_fix sets it again +execute if block ~ ~ ~ minecraft:farmland[moisture=0] run summon marker ~ ~ ~ {Tags:["timber_farmland","timber_moisture_0","global.ignore"]} +execute if block ~ ~ ~ minecraft:farmland[moisture=1] run summon marker ~ ~ ~ {Tags:["timber_farmland","timber_moisture_1","global.ignore"]} +execute if block ~ ~ ~ minecraft:farmland[moisture=2] run summon marker ~ ~ ~ {Tags:["timber_farmland","timber_moisture_2","global.ignore"]} +execute if block ~ ~ ~ minecraft:farmland[moisture=3] run summon marker ~ ~ ~ {Tags:["timber_farmland","timber_moisture_3","global.ignore"]} +execute if block ~ ~ ~ minecraft:farmland[moisture=4] run summon marker ~ ~ ~ {Tags:["timber_farmland","timber_moisture_4","global.ignore"]} +execute if block ~ ~ ~ minecraft:farmland[moisture=5] run summon marker ~ ~ ~ {Tags:["timber_farmland","timber_moisture_5","global.ignore"]} +execute if block ~ ~ ~ minecraft:farmland[moisture=6] run summon marker ~ ~ ~ {Tags:["timber_farmland","timber_moisture_6","global.ignore"]} +execute if block ~ ~ ~ minecraft:farmland[moisture=7] run summon marker ~ ~ ~ {Tags:["timber_farmland","timber_moisture_7","global.ignore"]} +execute if block ~ ~ ~ minecraft:farmland run schedule function timber:autoplant/farmland_fix 3t +execute if block ~ ~ ~ minecraft:farmland run setblock ~ ~ ~ minecraft:dirt summon minecraft:falling_block ~ ~0.125 ~ {BlockState:"minecraft:barrier",Time:1,DropItem:0b,Tags:["timber_item_to_block"]} execute positioned ~ ~0.125 ~ run data modify entity @e[type=minecraft:falling_block,tag=timber_item_to_block,distance=...1,sort=arbitrary,limit=1] BlockState set from entity @s Item.id diff --git a/data/timber/function/autoplant/farmland_fix.mcfunction b/data/timber/function/autoplant/farmland_fix.mcfunction new file mode 100644 index 0000000..25d8f5c --- /dev/null +++ b/data/timber/function/autoplant/farmland_fix.mcfunction @@ -0,0 +1,9 @@ +execute at @e[type=minecraft:marker,tag=timber_moisture_0] run setblock ~ ~ ~ minecraft:farmland[moisture=0] +execute at @e[type=minecraft:marker,tag=timber_moisture_1] run setblock ~ ~ ~ minecraft:farmland[moisture=1] +execute at @e[type=minecraft:marker,tag=timber_moisture_2] run setblock ~ ~ ~ minecraft:farmland[moisture=2] +execute at @e[type=minecraft:marker,tag=timber_moisture_3] run setblock ~ ~ ~ minecraft:farmland[moisture=3] +execute at @e[type=minecraft:marker,tag=timber_moisture_4] run setblock ~ ~ ~ minecraft:farmland[moisture=4] +execute at @e[type=minecraft:marker,tag=timber_moisture_5] run setblock ~ ~ ~ minecraft:farmland[moisture=5] +execute at @e[type=minecraft:marker,tag=timber_moisture_6] run setblock ~ ~ ~ minecraft:farmland[moisture=6] +execute at @e[type=minecraft:marker,tag=timber_moisture_7] run setblock ~ ~ ~ minecraft:farmland[moisture=7] +kill @e[type=minecraft:marker,tag=timber_farmland] \ No newline at end of file diff --git a/data/timber/function/autoplant/plant.mcfunction b/data/timber/function/autoplant/plant.mcfunction index d701739..fb72d66 100644 --- a/data/timber/function/autoplant/plant.mcfunction +++ b/data/timber/function/autoplant/plant.mcfunction @@ -1,7 +1,18 @@ # replace item-sapling with block-sapling (special case for mud, because fallingblock get destroyed. 0.125 is the height when you sink into the mud) -execute unless block ~ ~ ~ minecraft:air run summon marker ~ ~ ~ {Tags:["timber_mud","global.ignore"]} -execute unless block ~ ~ ~ minecraft:air run schedule function timber:autoplant/mud_fix 1t -execute unless block ~ ~ ~ minecraft:air run setblock ~ ~ ~ minecraft:muddy_mangrove_roots +execute if block ~ ~ ~ minecraft:mud run summon marker ~ ~ ~ {Tags:["timber_mud","global.ignore"]} +execute if block ~ ~ ~ minecraft:mud run schedule function timber:autoplant/mud_fix 1t +execute if block ~ ~ ~ minecraft:mud run setblock ~ ~ ~ minecraft:muddy_mangrove_roots +# same for farmland (a bit lower than a full block, the falling block would land inside of it): a dirt block for a moment, so the falling block has something to land on. The marker remembers the moisture, farmland_fix sets it again +execute if block ~ ~ ~ minecraft:farmland[moisture=0] run summon marker ~ ~ ~ {Tags:["timber_farmland","timber_moisture_0","global.ignore"]} +execute if block ~ ~ ~ minecraft:farmland[moisture=1] run summon marker ~ ~ ~ {Tags:["timber_farmland","timber_moisture_1","global.ignore"]} +execute if block ~ ~ ~ minecraft:farmland[moisture=2] run summon marker ~ ~ ~ {Tags:["timber_farmland","timber_moisture_2","global.ignore"]} +execute if block ~ ~ ~ minecraft:farmland[moisture=3] run summon marker ~ ~ ~ {Tags:["timber_farmland","timber_moisture_3","global.ignore"]} +execute if block ~ ~ ~ minecraft:farmland[moisture=4] run summon marker ~ ~ ~ {Tags:["timber_farmland","timber_moisture_4","global.ignore"]} +execute if block ~ ~ ~ minecraft:farmland[moisture=5] run summon marker ~ ~ ~ {Tags:["timber_farmland","timber_moisture_5","global.ignore"]} +execute if block ~ ~ ~ minecraft:farmland[moisture=6] run summon marker ~ ~ ~ {Tags:["timber_farmland","timber_moisture_6","global.ignore"]} +execute if block ~ ~ ~ minecraft:farmland[moisture=7] run summon marker ~ ~ ~ {Tags:["timber_farmland","timber_moisture_7","global.ignore"]} +execute if block ~ ~ ~ minecraft:farmland run schedule function timber:autoplant/farmland_fix 3t +execute if block ~ ~ ~ minecraft:farmland run setblock ~ ~ ~ minecraft:dirt summon minecraft:falling_block ~ ~0.125 ~ {BlockState:{Name:"minecraft:barrier"},Time:1,DropItem:0b,Tags:["timber_item_to_block"]} execute positioned ~ ~0.125 ~ run data modify entity @e[type=minecraft:falling_block,tag=timber_item_to_block,distance=...1,sort=arbitrary,limit=1] BlockState.Name set from entity @s Item.id diff --git a/data/timber/function/uninstall.mcfunction b/data/timber/function/uninstall.mcfunction index 4abe3f4..908b517 100644 --- a/data/timber/function/uninstall.mcfunction +++ b/data/timber/function/uninstall.mcfunction @@ -2,6 +2,7 @@ schedule clear timber:main schedule clear timber:autoplant/loop schedule clear timber:autoplant/mud_fix +schedule clear timber:autoplant/farmland_fix schedule clear timber:settings/reset_gamerule schedule clear timber:slow_chop/loop schedule clear timber:utils/check_server diff --git a/data/timber/tags/block/plantable_ground.json b/data/timber/tags/block/plantable_ground.json index 3b4ca02..67fd6b5 100644 --- a/data/timber/tags/block/plantable_ground.json +++ b/data/timber/tags/block/plantable_ground.json @@ -1,9 +1,5 @@ { "values": [ - "#minecraft:dirt", - { - "id": "#minecraft:grass_blocks", - "required": false - } + "#minecraft:supports_vegetation" ] } diff --git a/tests/data/timber_test/test/settings_plant_sapling_on_keeps_the_moisture_of_farmland.mcfunction b/tests/data/timber_test/test/settings_plant_sapling_on_keeps_the_moisture_of_farmland.mcfunction new file mode 100644 index 0000000..754468e --- /dev/null +++ b/tests/data/timber_test/test/settings_plant_sapling_on_keeps_the_moisture_of_farmland.mcfunction @@ -0,0 +1,14 @@ +#> With plant_sapling on, the farmland under an auto-planted sapling keeps its moisture +# @environment timber_test:plant_sapling_on +# @template timber_test:arena +# @timeout 400 +# @skyaccess true + +function timber_test:platform +setblock ~10 ~-1 ~10 minecraft:farmland[moisture=3] +summon minecraft:item ~10.5 ~0.5 ~10.5 {Item:{id:"minecraft:oak_sapling",count:1}} + +await block ~10 ~ ~10 minecraft:oak_sapling +# the farmland is put back a few ticks after the planting +await delay 1s +assert block ~10 ~-1 ~10 minecraft:farmland[moisture=3] From 6f0771fc4ea314a7853e5f61ff7f84f85f03e9f1 Mon Sep 17 00:00:00 2001 From: Flavio Felder Date: Wed, 23 Sep 2026 20:11:29 +0200 Subject: [PATCH 3/7] Keep mangrove root search from spreading to neighboring trees Roots only follow roots sideways and down; a chop started on a root walks up and sideways until the first log Co-Authored-By: Claude Opus 5.5 --- .../algorithm/tree/chop_roots.mcfunction | 5 +++-- .../tree/roots/chop_start.mcfunction | 20 +++++++++++++++++++ .../algorithm/tree/roots/search_1.mcfunction | 20 +++++++++++++++++++ .../algorithm/tree/roots/search_2.mcfunction | 4 ++++ .../tree/roots/search_start_1.mcfunction | 20 +++++++++++++++++++ .../tree/roots/search_start_2.mcfunction | 8 ++++++++ .../function/algorithm/tree/run.mcfunction | 3 ++- .../algorithm/tree/search_1.mcfunction | 2 -- 8 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 data/timber/function/algorithm/tree/roots/chop_start.mcfunction create mode 100644 data/timber/function/algorithm/tree/roots/search_1.mcfunction create mode 100644 data/timber/function/algorithm/tree/roots/search_2.mcfunction create mode 100644 data/timber/function/algorithm/tree/roots/search_start_1.mcfunction create mode 100644 data/timber/function/algorithm/tree/roots/search_start_2.mcfunction diff --git a/data/timber/function/algorithm/tree/chop_roots.mcfunction b/data/timber/function/algorithm/tree/chop_roots.mcfunction index 0b57e1e..bf3c6a9 100644 --- a/data/timber/function/algorithm/tree/chop_roots.mcfunction +++ b/data/timber/function/algorithm/tree/chop_roots.mcfunction @@ -13,5 +13,6 @@ execute if score stop_chopping timber matches 1.. unless score unbreakable timbe # mark block that will get destroyed execute if score durability_okay timber matches 1.. if score tree_size timber < max_tree_size timber run summon marker ~ ~ ~ {Tags:["timber_root"]} -# search for next log/root -execute if score durability_okay timber matches 1.. if score tree_size timber < max_tree_size timber run function timber:algorithm/tree/search_1 \ No newline at end of file +# search for next root, only sideways and downwards and never into a log: roots of trees next to each other touch, +# going up from a root would climb into the trunk of the neighbor and chop the whole forest +execute if score durability_okay timber matches 1.. if score tree_size timber < max_tree_size timber if score destroy_roots timber matches 1.. run function timber:algorithm/tree/roots/search_1 \ No newline at end of file diff --git a/data/timber/function/algorithm/tree/roots/chop_start.mcfunction b/data/timber/function/algorithm/tree/roots/chop_start.mcfunction new file mode 100644 index 0000000..42a5037 --- /dev/null +++ b/data/timber/function/algorithm/tree/roots/chop_start.mcfunction @@ -0,0 +1,20 @@ +# a root on the way from the mined root to the trunk (start_chop_on_root) +# resets +scoreboard players set durability_okay timber 1 + +# keep track of tree size +scoreboard players add tree_size timber 1 + +# wear out tool +function timber:tool/durability_count + +# tool durability check +execute if score stop_chopping timber matches 1.. unless score unbreakable timber matches 1 run function timber:tool/durability_check + +# mark block that will get destroyed +execute if score durability_okay timber matches 1.. if score tree_size timber < max_tree_size timber run summon marker ~ ~ ~ {Tags:["timber_root"]} + +# search upwards and sideways for the trunk, roots can run sideways for a long way before they reach it +execute if score durability_okay timber matches 1.. if score tree_size timber < max_tree_size timber run function timber:algorithm/tree/roots/search_start_1 +# search for roots downwards +execute if score durability_okay timber matches 1.. if score tree_size timber < max_tree_size timber if score destroy_roots timber matches 1.. run function timber:algorithm/tree/roots/search_1 diff --git a/data/timber/function/algorithm/tree/roots/search_1.mcfunction b/data/timber/function/algorithm/tree/roots/search_1.mcfunction new file mode 100644 index 0000000..66ce93c --- /dev/null +++ b/data/timber/function/algorithm/tree/roots/search_1.mcfunction @@ -0,0 +1,20 @@ +# search for next root sideways +execute positioned ~ ~ ~1 run function timber:algorithm/tree/roots/search_2 +execute positioned ~ ~ ~-1 run function timber:algorithm/tree/roots/search_2 +execute positioned ~1 ~ ~ run function timber:algorithm/tree/roots/search_2 +execute positioned ~1 ~ ~1 run function timber:algorithm/tree/roots/search_2 +execute positioned ~1 ~ ~-1 run function timber:algorithm/tree/roots/search_2 +execute positioned ~-1 ~ ~ run function timber:algorithm/tree/roots/search_2 +execute positioned ~-1 ~ ~1 run function timber:algorithm/tree/roots/search_2 +execute positioned ~-1 ~ ~-1 run function timber:algorithm/tree/roots/search_2 + +# search for next root downwards +execute positioned ~ ~-1 ~ run function timber:algorithm/tree/roots/search_2 +execute positioned ~ ~-1 ~1 run function timber:algorithm/tree/roots/search_2 +execute positioned ~ ~-1 ~-1 run function timber:algorithm/tree/roots/search_2 +execute positioned ~1 ~-1 ~ run function timber:algorithm/tree/roots/search_2 +execute positioned ~1 ~-1 ~1 run function timber:algorithm/tree/roots/search_2 +execute positioned ~1 ~-1 ~-1 run function timber:algorithm/tree/roots/search_2 +execute positioned ~-1 ~-1 ~ run function timber:algorithm/tree/roots/search_2 +execute positioned ~-1 ~-1 ~1 run function timber:algorithm/tree/roots/search_2 +execute positioned ~-1 ~-1 ~-1 run function timber:algorithm/tree/roots/search_2 diff --git a/data/timber/function/algorithm/tree/roots/search_2.mcfunction b/data/timber/function/algorithm/tree/roots/search_2.mcfunction new file mode 100644 index 0000000..1242d54 --- /dev/null +++ b/data/timber/function/algorithm/tree/roots/search_2.mcfunction @@ -0,0 +1,4 @@ +scoreboard players add log_probes timber_bench 1 + +# if root (a log is never followed from here) +execute if block ~ ~ ~ minecraft:mangrove_roots unless entity @e[type=minecraft:marker,tag=timber_root,distance=...1,sort=arbitrary,limit=1] run function timber:algorithm/tree/chop_roots diff --git a/data/timber/function/algorithm/tree/roots/search_start_1.mcfunction b/data/timber/function/algorithm/tree/roots/search_start_1.mcfunction new file mode 100644 index 0000000..9c38585 --- /dev/null +++ b/data/timber/function/algorithm/tree/roots/search_start_1.mcfunction @@ -0,0 +1,20 @@ +# search for next log/root upwards +execute positioned ~ ~1 ~ run function timber:algorithm/tree/roots/search_start_2 +execute positioned ~ ~1 ~1 run function timber:algorithm/tree/roots/search_start_2 +execute positioned ~ ~1 ~-1 run function timber:algorithm/tree/roots/search_start_2 +execute positioned ~1 ~1 ~ run function timber:algorithm/tree/roots/search_start_2 +execute positioned ~1 ~1 ~1 run function timber:algorithm/tree/roots/search_start_2 +execute positioned ~1 ~1 ~-1 run function timber:algorithm/tree/roots/search_start_2 +execute positioned ~-1 ~1 ~ run function timber:algorithm/tree/roots/search_start_2 +execute positioned ~-1 ~1 ~1 run function timber:algorithm/tree/roots/search_start_2 +execute positioned ~-1 ~1 ~-1 run function timber:algorithm/tree/roots/search_start_2 + +# search for next log/root sideways +execute positioned ~ ~ ~1 run function timber:algorithm/tree/roots/search_start_2 +execute positioned ~ ~ ~-1 run function timber:algorithm/tree/roots/search_start_2 +execute positioned ~1 ~ ~ run function timber:algorithm/tree/roots/search_start_2 +execute positioned ~1 ~ ~1 run function timber:algorithm/tree/roots/search_start_2 +execute positioned ~1 ~ ~-1 run function timber:algorithm/tree/roots/search_start_2 +execute positioned ~-1 ~ ~ run function timber:algorithm/tree/roots/search_start_2 +execute positioned ~-1 ~ ~1 run function timber:algorithm/tree/roots/search_start_2 +execute positioned ~-1 ~ ~-1 run function timber:algorithm/tree/roots/search_start_2 diff --git a/data/timber/function/algorithm/tree/roots/search_start_2.mcfunction b/data/timber/function/algorithm/tree/roots/search_start_2.mcfunction new file mode 100644 index 0000000..86a4744 --- /dev/null +++ b/data/timber/function/algorithm/tree/roots/search_start_2.mcfunction @@ -0,0 +1,8 @@ +scoreboard players add log_probes timber_bench 1 + +# if log: the trunk is found, from now on roots are only followed sideways and downwards (chop_roots), so the walk doesn't go on to the next tree +execute if predicate timber:block/log unless entity @e[type=minecraft:marker,tag=timber_log,distance=...1,sort=arbitrary,limit=1] run scoreboard players set root_walk timber 0 +execute if predicate timber:block/log unless entity @e[type=minecraft:marker,tag=timber_log,distance=...1,sort=arbitrary,limit=1] run function timber:algorithm/tree/chop +# if root +execute if score root_walk timber matches 1 if block ~ ~ ~ minecraft:mangrove_roots unless entity @e[type=minecraft:marker,tag=timber_root,distance=...1,sort=arbitrary,limit=1] run function timber:algorithm/tree/roots/chop_start +execute if score root_walk timber matches 0 if block ~ ~ ~ minecraft:mangrove_roots unless entity @e[type=minecraft:marker,tag=timber_root,distance=...1,sort=arbitrary,limit=1] run function timber:algorithm/tree/chop_roots diff --git a/data/timber/function/algorithm/tree/run.mcfunction b/data/timber/function/algorithm/tree/run.mcfunction index 1dc31ab..eb7d7e0 100644 --- a/data/timber/function/algorithm/tree/run.mcfunction +++ b/data/timber/function/algorithm/tree/run.mcfunction @@ -2,5 +2,6 @@ summon minecraft:armor_stand ~ ~ ~ {Silent:1b,Invulnerable:1b,Marker:1b,Invisible:1b,Tags:["timber_item_check"]} data modify entity @e[type=minecraft:armor_stand,tag=timber_item_check,distance=...1,sort=arbitrary,limit=1] equipment.mainhand set from entity @e[type=minecraft:item,distance=...1,sort=nearest,limit=1,nbt={Age:0s}] Item execute if entity @e[type=minecraft:armor_stand,tag=timber_item_check,distance=...1,sort=arbitrary,limit=1,predicate=timber:item/log] run function timber:algorithm/tree/chop -execute if score start_chop_on_root timber matches 1.. if entity @e[type=minecraft:armor_stand,tag=timber_item_check,distance=...1,sort=arbitrary,limit=1,predicate=timber:item/root] run function timber:algorithm/tree/chop_roots +scoreboard players set root_walk timber 1 +execute if score start_chop_on_root timber matches 1.. if entity @e[type=minecraft:armor_stand,tag=timber_item_check,distance=...1,sort=arbitrary,limit=1,predicate=timber:item/root] run function timber:algorithm/tree/roots/chop_start kill @e[type=minecraft:armor_stand,tag=timber_item_check,distance=...1,sort=arbitrary,limit=1] diff --git a/data/timber/function/algorithm/tree/search_1.mcfunction b/data/timber/function/algorithm/tree/search_1.mcfunction index 020d7eb..352eb51 100644 --- a/data/timber/function/algorithm/tree/search_1.mcfunction +++ b/data/timber/function/algorithm/tree/search_1.mcfunction @@ -20,5 +20,3 @@ execute positioned ~-1 ~ ~-1 run function timber:algorithm/tree/search_2 # search for next log downwards execute if score chop_down timber matches 1.. run function timber:algorithm/tree/search_down -# search for roots downwards -execute unless score chop_down timber matches 1.. if score destroy_roots timber matches 1.. if entity @e[type=minecraft:marker,tag=timber_root,distance=...1,sort=arbitrary,limit=1] run function timber:algorithm/tree/search_down From e2425d7dfe6e784efad91db0222d0ed41d3479bf Mon Sep 17 00:00:00 2001 From: Flavio Felder Date: Wed, 23 Sep 2026 20:52:46 +0200 Subject: [PATCH 4/7] Count leaves within 2 blocks of the trunk when too few touch it (fixes #35) Small mangroves often have fewer than 5 leaves right at the trunk; mine_here also finds a spot next to a mangrove log Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- .../algorithm/tree/find_leaves_2.mcfunction | 19 ++++++++ data/timber/function/run.mcfunction | 3 ++ .../function/settings/open/leaves.mcfunction | 2 +- .../function/settings/open/menu.mcfunction | 2 +- .../function/count_adjacent.mcfunction | 43 +++++++++++++++++-- .../timber_test/function/mine_here.mcfunction | 31 ++++++++++++- .../function/warn_few_leaves.mcfunction | 4 +- .../tags/block/dummy_can_stand_in.json | 9 ++++ .../test/chop_mangrove_log_tree.mcfunction | 6 +-- .../test/chop_mangrove_roots_tree.mcfunction | 6 +-- .../chop_tall_mangrove_roots_tree.mcfunction | 6 +-- .../test/chop_tall_mangrove_tree.mcfunction | 6 +-- ...d_five_leaves_within_two_blocks.mcfunction | 33 ++++++++++++++ ...d_four_leaves_within_two_blocks.mcfunction | 31 +++++++++++++ ...ettings_min_leaves_13_chops_oak.mcfunction | 2 +- ...s_min_leaves_14_keeps_13_leaves.mcfunction | 39 +++++++++++++++++ ...ettings_min_leaves_14_keeps_oak.mcfunction | 26 ----------- 18 files changed, 222 insertions(+), 48 deletions(-) create mode 100644 data/timber/function/algorithm/tree/find_leaves_2.mcfunction create mode 100644 tests/data/timber_test/tags/block/dummy_can_stand_in.json create mode 100644 tests/data/timber_test/test/chopped_five_leaves_within_two_blocks.mcfunction create mode 100644 tests/data/timber_test/test/not_chopped_four_leaves_within_two_blocks.mcfunction create mode 100644 tests/data/timber_test/test/settings_min_leaves_14_keeps_13_leaves.mcfunction delete mode 100644 tests/data/timber_test/test/settings_min_leaves_14_keeps_oak.mcfunction diff --git a/README.md b/README.md index 0d9cd56..c683bf5 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Optionally, you can adjust everything as you wish. - Takes account of any enchantment - Many options for customization -¹ Trees are defined as logs that are next to naturally generated leaves. +¹ Trees are defined as logs with naturally generated leaves within 2 blocks of the trunk. ² Logs in the tag #minecraft:logs. ³ All kind of custom trees like from **BiomesO'Plenty**, **The Biome Overhaul**, **Terrestria and Traverse**. diff --git a/data/timber/function/algorithm/tree/find_leaves_2.mcfunction b/data/timber/function/algorithm/tree/find_leaves_2.mcfunction new file mode 100644 index 0000000..635470c --- /dev/null +++ b/data/timber/function/algorithm/tree/find_leaves_2.mcfunction @@ -0,0 +1,19 @@ +# run at every log of the tree: marks the leaves 2 blocks away from it (straight or around an edge), for trees with too few leaves right at the trunk +execute positioned ~ ~ ~2 if predicate timber:block/leaf unless entity @e[type=minecraft:marker,tag=timber_leaves_found,distance=...1,sort=nearest,limit=1] run summon marker ~ ~ ~ {Tags:["timber_leaves_found"]} +execute positioned ~ ~ ~-2 if predicate timber:block/leaf unless entity @e[type=minecraft:marker,tag=timber_leaves_found,distance=...1,sort=nearest,limit=1] run summon marker ~ ~ ~ {Tags:["timber_leaves_found"]} +execute positioned ~2 ~ ~ if predicate timber:block/leaf unless entity @e[type=minecraft:marker,tag=timber_leaves_found,distance=...1,sort=nearest,limit=1] run summon marker ~ ~ ~ {Tags:["timber_leaves_found"]} +execute positioned ~-2 ~ ~ if predicate timber:block/leaf unless entity @e[type=minecraft:marker,tag=timber_leaves_found,distance=...1,sort=nearest,limit=1] run summon marker ~ ~ ~ {Tags:["timber_leaves_found"]} +execute positioned ~ ~2 ~ if predicate timber:block/leaf unless entity @e[type=minecraft:marker,tag=timber_leaves_found,distance=...1,sort=nearest,limit=1] run summon marker ~ ~ ~ {Tags:["timber_leaves_found"]} +execute positioned ~ ~-2 ~ if predicate timber:block/leaf unless entity @e[type=minecraft:marker,tag=timber_leaves_found,distance=...1,sort=nearest,limit=1] run summon marker ~ ~ ~ {Tags:["timber_leaves_found"]} +execute positioned ~1 ~ ~1 if predicate timber:block/leaf unless entity @e[type=minecraft:marker,tag=timber_leaves_found,distance=...1,sort=nearest,limit=1] run summon marker ~ ~ ~ {Tags:["timber_leaves_found"]} +execute positioned ~1 ~ ~-1 if predicate timber:block/leaf unless entity @e[type=minecraft:marker,tag=timber_leaves_found,distance=...1,sort=nearest,limit=1] run summon marker ~ ~ ~ {Tags:["timber_leaves_found"]} +execute positioned ~-1 ~ ~1 if predicate timber:block/leaf unless entity @e[type=minecraft:marker,tag=timber_leaves_found,distance=...1,sort=nearest,limit=1] run summon marker ~ ~ ~ {Tags:["timber_leaves_found"]} +execute positioned ~-1 ~ ~-1 if predicate timber:block/leaf unless entity @e[type=minecraft:marker,tag=timber_leaves_found,distance=...1,sort=nearest,limit=1] run summon marker ~ ~ ~ {Tags:["timber_leaves_found"]} +execute positioned ~1 ~1 ~ if predicate timber:block/leaf unless entity @e[type=minecraft:marker,tag=timber_leaves_found,distance=...1,sort=nearest,limit=1] run summon marker ~ ~ ~ {Tags:["timber_leaves_found"]} +execute positioned ~-1 ~1 ~ if predicate timber:block/leaf unless entity @e[type=minecraft:marker,tag=timber_leaves_found,distance=...1,sort=nearest,limit=1] run summon marker ~ ~ ~ {Tags:["timber_leaves_found"]} +execute positioned ~ ~1 ~1 if predicate timber:block/leaf unless entity @e[type=minecraft:marker,tag=timber_leaves_found,distance=...1,sort=nearest,limit=1] run summon marker ~ ~ ~ {Tags:["timber_leaves_found"]} +execute positioned ~ ~1 ~-1 if predicate timber:block/leaf unless entity @e[type=minecraft:marker,tag=timber_leaves_found,distance=...1,sort=nearest,limit=1] run summon marker ~ ~ ~ {Tags:["timber_leaves_found"]} +execute positioned ~1 ~-1 ~ if predicate timber:block/leaf unless entity @e[type=minecraft:marker,tag=timber_leaves_found,distance=...1,sort=nearest,limit=1] run summon marker ~ ~ ~ {Tags:["timber_leaves_found"]} +execute positioned ~-1 ~-1 ~ if predicate timber:block/leaf unless entity @e[type=minecraft:marker,tag=timber_leaves_found,distance=...1,sort=nearest,limit=1] run summon marker ~ ~ ~ {Tags:["timber_leaves_found"]} +execute positioned ~ ~-1 ~1 if predicate timber:block/leaf unless entity @e[type=minecraft:marker,tag=timber_leaves_found,distance=...1,sort=nearest,limit=1] run summon marker ~ ~ ~ {Tags:["timber_leaves_found"]} +execute positioned ~ ~-1 ~-1 if predicate timber:block/leaf unless entity @e[type=minecraft:marker,tag=timber_leaves_found,distance=...1,sort=nearest,limit=1] run summon marker ~ ~ ~ {Tags:["timber_leaves_found"]} diff --git a/data/timber/function/run.mcfunction b/data/timber/function/run.mcfunction index 8ef82c8..6ca201e 100644 --- a/data/timber/function/run.mcfunction +++ b/data/timber/function/run.mcfunction @@ -28,6 +28,9 @@ function timber:tool/durability_get # tree (limit=1,sort=arbitrary removed, because carpet on log breaks this) execute if score chop_trees timber matches 1.. at @e[type=minecraft:marker,tag=timber_tree,tag=!timber_slow_chop,distance=..7] run function timber:algorithm/tree/run execute store result score leaves_found timber if entity @e[type=minecraft:marker,tag=timber_leaves_found] + # too few leaves right at the trunk (small mangroves): count the leaves 2 blocks away from it too + execute if score leaves_found timber < min_leaves_found timber unless score tree_size timber matches 0 at @e[type=minecraft:marker,tag=timber_log,tag=!timber_destroy] run function timber:algorithm/tree/find_leaves_2 + execute store result score leaves_found timber if entity @e[type=minecraft:marker,tag=timber_leaves_found] # if tree is valid execute if score leaves_found timber >= min_leaves_found timber unless score tree_size timber matches 0 run scoreboard players set tree_type timber 1 diff --git a/data/timber/function/settings/open/leaves.mcfunction b/data/timber/function/settings/open/leaves.mcfunction index 468e806..ff6abf2 100644 --- a/data/timber/function/settings/open/leaves.mcfunction +++ b/data/timber/function/settings/open/leaves.mcfunction @@ -1,6 +1,6 @@ # opens the "Min. Leaves" slider with the current value # only reachable for admins, so the trigger gets enabled only for them scoreboard players enable @s TimberSetLeaves -data modify storage timber:settings dialog set value {"type":"minecraft:multi_action","title":"Timber Settings / Min. Leaves","external_title":"Timber Min. Leaves","body":{"type":"minecraft:plain_message","contents":["",{"text":"This datapack considers a tree only if it has this set value amount of naturally generated leaves at its trunk.\nFungi will get checked by the amount of cap blocks and additionally a 3×3 big cross on top of the stem."},"\n\n",{"text":"Default: 5","color":"gray"}],"width":300},"inputs":[{"type":"minecraft:number_range","key":"min_leaves_found","width":300,"label":["",{"text":"Set Min. Leaves to Valid a Tree","hover_event":{"action":"show_text","value":["",{"text":"Default: 5"},{"text":"\n\nThis datapack considers a tree only if it has this set value amount of naturally generated leaves at its trunk.\nFungi will get checked by the amount of cap blocks and additionally a 3×3 big cross on top of the stem."}]}}],"start":0,"end":10,"step":1,"initial":0}],"can_close_with_escape":true,"pause":false,"after_action":"none","columns":1,"actions":[{"label":"Save","width":300,"action":{"type":"minecraft:dynamic/run_command","template":"trigger TimberSetLeaves set 1$(min_leaves_found)"}}],"exit_action":{"label":{"translate":"gui.back"},"width":300,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 1"}}} +data modify storage timber:settings dialog set value {"type":"minecraft:multi_action","title":"Timber Settings / Min. Leaves","external_title":"Timber Min. Leaves","body":{"type":"minecraft:plain_message","contents":["",{"text":"This datapack considers a tree only if it has at least this many naturally generated leaves within 2 blocks of its trunk.\nFungi will get checked by the amount of cap blocks and additionally a 3×3 big cross on top of the stem."},"\n\n",{"text":"Default: 5","color":"gray"}],"width":300},"inputs":[{"type":"minecraft:number_range","key":"min_leaves_found","width":300,"label":["",{"text":"Set Min. Leaves to Valid a Tree","hover_event":{"action":"show_text","value":["",{"text":"Default: 5"},{"text":"\n\nThis datapack considers a tree only if it has at least this many naturally generated leaves within 2 blocks of its trunk.\nFungi will get checked by the amount of cap blocks and additionally a 3×3 big cross on top of the stem."}]}}],"start":0,"end":10,"step":1,"initial":0}],"can_close_with_escape":true,"pause":false,"after_action":"none","columns":1,"actions":[{"label":"Save","width":300,"action":{"type":"minecraft:dynamic/run_command","template":"trigger TimberSetLeaves set 1$(min_leaves_found)"}}],"exit_action":{"label":{"translate":"gui.back"},"width":300,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 1"}}} execute store result storage timber:settings dialog.inputs[0].initial float 1 run scoreboard players get min_leaves_found timber function timber:settings/show with storage timber:settings diff --git a/data/timber/function/settings/open/menu.mcfunction b/data/timber/function/settings/open/menu.mcfunction index cc79e3e..3bb1e69 100644 --- a/data/timber/function/settings/open/menu.mcfunction +++ b/data/timber/function/settings/open/menu.mcfunction @@ -46,6 +46,6 @@ execute unless score chop_fungi timber matches 1 run data modify storage timber: data modify storage timber:settings dialog.actions append value {"label":"Blocks per Chop","width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 6"},"tooltip":"Amount of blocks which get destroyed per chop.\nOnly used when \"Chop Slowly\" is enabled."} data modify storage timber:settings dialog.actions append value {"label":"Ticks Between Chops","width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 7"},"tooltip":"Time in ticks between two chops.\nOnly used when \"Chop Slowly\" is enabled."} data modify storage timber:settings dialog.actions append value {"label":"Max. Tree Size","width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 8"},"tooltip":"Trees bigger than this set value won't get further processed."} -data modify storage timber:settings dialog.actions append value {"label":"Min. Leaves","width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 9"},"tooltip":"This datapack considers a tree only if it has this set value amount of naturally generated leaves at its trunk.\nFungi will get checked by the amount of cap blocks and additionally a 3×3 big cross on top of the stem."} +data modify storage timber:settings dialog.actions append value {"label":"Min. Leaves","width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 9"},"tooltip":"This datapack considers a tree only if it has at least this many naturally generated leaves within 2 blocks of its trunk.\nFungi will get checked by the amount of cap blocks and additionally a 3×3 big cross on top of the stem."} data modify storage timber:settings dialog.actions append value {"label":"Tools","width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 3"},"tooltip":"Axes"} function timber:settings/show with storage timber:settings diff --git a/tests/data/timber_test/function/count_adjacent.mcfunction b/tests/data/timber_test/function/count_adjacent.mcfunction index b6d1f08..d0b349b 100644 --- a/tests/data/timber_test/function/count_adjacent.mcfunction +++ b/tests/data/timber_test/function/count_adjacent.mcfunction @@ -1,6 +1,6 @@ -#> Counts the leaves (and hanging mangrove propagules, the datapack's leaf predicate counts them too) that touch a log on one of their 6 sides into #adj_, this is the number the datapack compares with min_leaves_found. -#> Doesn't change the tree. The leaves are copied far above the arena, then the logs are copied over them shifted by one block in every -#> direction: every leaf a log lands on is next to a log, what is left of the copy is not (checked against oak: 13, same as the datapack). +#> Counts the leaves (and hanging mangrove propagules, the datapack's leaf predicate counts them too) within 2 blocks of a log (1 block on one of its 6 sides, or 2 blocks straight or around an edge) into #adj_, this is the number the datapack compares with min_leaves_found. +#> Doesn't change the tree. The leaves are copied far above the arena, then the logs are copied over them shifted to every place within 2 blocks: +#> every leaf a log lands on is within 2 blocks of a log, what is left of the copy is not. The source of a shift is cut so nothing is copied outside the arena. #> @param test name of the test scoreboard objectives add timber_test dummy fill ~0 ~64 ~0 ~39 ~83 ~39 minecraft:air @@ -23,6 +23,43 @@ clone ~0 ~0 ~0 ~39 ~19 ~39 ~0 ~64 ~1 filtered #minecraft:logs clone ~0 ~20 ~0 ~39 ~39 ~39 ~0 ~84 ~1 filtered #minecraft:logs clone ~0 ~0 ~0 ~39 ~19 ~39 ~0 ~64 ~-1 filtered #minecraft:logs clone ~0 ~20 ~0 ~39 ~39 ~39 ~0 ~84 ~-1 filtered #minecraft:logs +# and 2 blocks away (straight or around an edge), the datapack counts those too when there are too few right at the trunk +clone ~0 ~0 ~0 ~39 ~19 ~37 ~0 ~64 ~2 filtered #minecraft:logs +clone ~0 ~20 ~0 ~39 ~39 ~37 ~0 ~84 ~2 filtered #minecraft:logs +clone ~0 ~0 ~2 ~39 ~19 ~39 ~0 ~64 ~0 filtered #minecraft:logs +clone ~0 ~20 ~2 ~39 ~39 ~39 ~0 ~84 ~0 filtered #minecraft:logs +clone ~0 ~0 ~0 ~37 ~19 ~39 ~2 ~64 ~0 filtered #minecraft:logs +clone ~0 ~20 ~0 ~37 ~39 ~39 ~2 ~84 ~0 filtered #minecraft:logs +clone ~2 ~0 ~0 ~39 ~19 ~39 ~0 ~64 ~0 filtered #minecraft:logs +clone ~2 ~20 ~0 ~39 ~39 ~39 ~0 ~84 ~0 filtered #minecraft:logs +clone ~0 ~0 ~0 ~39 ~19 ~39 ~0 ~66 ~0 filtered #minecraft:logs +clone ~0 ~20 ~0 ~39 ~39 ~39 ~0 ~86 ~0 filtered #minecraft:logs +clone ~0 ~0 ~0 ~39 ~19 ~39 ~0 ~62 ~0 filtered #minecraft:logs +clone ~0 ~20 ~0 ~39 ~39 ~39 ~0 ~82 ~0 filtered #minecraft:logs +clone ~0 ~0 ~0 ~38 ~19 ~38 ~1 ~64 ~1 filtered #minecraft:logs +clone ~0 ~20 ~0 ~38 ~39 ~38 ~1 ~84 ~1 filtered #minecraft:logs +clone ~0 ~0 ~1 ~38 ~19 ~39 ~1 ~64 ~0 filtered #minecraft:logs +clone ~0 ~20 ~1 ~38 ~39 ~39 ~1 ~84 ~0 filtered #minecraft:logs +clone ~1 ~0 ~0 ~39 ~19 ~38 ~0 ~64 ~1 filtered #minecraft:logs +clone ~1 ~20 ~0 ~39 ~39 ~38 ~0 ~84 ~1 filtered #minecraft:logs +clone ~1 ~0 ~1 ~39 ~19 ~39 ~0 ~64 ~0 filtered #minecraft:logs +clone ~1 ~20 ~1 ~39 ~39 ~39 ~0 ~84 ~0 filtered #minecraft:logs +clone ~0 ~0 ~0 ~38 ~19 ~39 ~1 ~65 ~0 filtered #minecraft:logs +clone ~0 ~20 ~0 ~38 ~39 ~39 ~1 ~85 ~0 filtered #minecraft:logs +clone ~1 ~0 ~0 ~39 ~19 ~39 ~0 ~65 ~0 filtered #minecraft:logs +clone ~1 ~20 ~0 ~39 ~39 ~39 ~0 ~85 ~0 filtered #minecraft:logs +clone ~0 ~0 ~0 ~39 ~19 ~38 ~0 ~65 ~1 filtered #minecraft:logs +clone ~0 ~20 ~0 ~39 ~39 ~38 ~0 ~85 ~1 filtered #minecraft:logs +clone ~0 ~0 ~1 ~39 ~19 ~39 ~0 ~65 ~0 filtered #minecraft:logs +clone ~0 ~20 ~1 ~39 ~39 ~39 ~0 ~85 ~0 filtered #minecraft:logs +clone ~0 ~0 ~0 ~38 ~19 ~39 ~1 ~63 ~0 filtered #minecraft:logs +clone ~0 ~20 ~0 ~38 ~39 ~39 ~1 ~83 ~0 filtered #minecraft:logs +clone ~1 ~0 ~0 ~39 ~19 ~39 ~0 ~63 ~0 filtered #minecraft:logs +clone ~1 ~20 ~0 ~39 ~39 ~39 ~0 ~83 ~0 filtered #minecraft:logs +clone ~0 ~0 ~0 ~39 ~19 ~38 ~0 ~63 ~1 filtered #minecraft:logs +clone ~0 ~20 ~0 ~39 ~39 ~38 ~0 ~83 ~1 filtered #minecraft:logs +clone ~0 ~0 ~1 ~39 ~19 ~39 ~0 ~63 ~0 filtered #minecraft:logs +clone ~0 ~20 ~1 ~39 ~39 ~39 ~0 ~83 ~0 filtered #minecraft:logs execute store result score #ra timber_test run fill ~0 ~64 ~0 ~39 ~83 ~39 minecraft:air replace #minecraft:leaves execute store result score #rb timber_test run fill ~0 ~84 ~0 ~39 ~103 ~39 minecraft:air replace #minecraft:leaves execute store result score #pa timber_test run fill ~0 ~64 ~0 ~39 ~83 ~39 minecraft:air replace minecraft:mangrove_propagule[hanging=true] diff --git a/tests/data/timber_test/function/mine_here.mcfunction b/tests/data/timber_test/function/mine_here.mcfunction index 9b12c63..128d8bf 100644 --- a/tests/data/timber_test/function/mine_here.mcfunction +++ b/tests/data/timber_test/function/mine_here.mcfunction @@ -1,6 +1,9 @@ #> Run as and at the tb_scan marker standing on the log to mine: puts the dummy next to that log and lets it mine it. #> The datapack looks for the dropped item within 7 blocks of the player, a dummy standing on the ground is too far away from a log -#> high up. The dummy floats (gravity 0) on a free side of the log, the first of +x, -x, +z, -z that is air. +#> high up. The dummy floats (gravity 0) on the first free spot (air) of: the 4 sides of the log (+x, -x, +z, -z), the 4 diagonals +#> (the lowest log of a mangrove has roots on all 4 sides), 2 blocks away, then a side with a block that doesn't suffocate (roots, leaves). +#> Without a spot the test fails: a dummy that mines from too far away would make the datapack do nothing, and the test would only +#> warn about a tree that wasn't chopped. #> @param name dummy name $attribute @e[type=minecraft:player,name=$(name),limit=1] minecraft:gravity base set 0 scoreboard players set #placed timber_test 0 @@ -12,4 +15,30 @@ $execute unless score #placed timber_test matches 1 if block ~ ~ ~1 minecraft:ai execute unless score #placed timber_test matches 1 if block ~ ~ ~1 minecraft:air run scoreboard players set #placed timber_test 1 $execute unless score #placed timber_test matches 1 if block ~ ~ ~-1 minecraft:air run tp @e[type=minecraft:player,name=$(name),limit=1] ~ ~-0.5 ~-1 execute unless score #placed timber_test matches 1 if block ~ ~ ~-1 minecraft:air run scoreboard players set #placed timber_test 1 +$execute unless score #placed timber_test matches 1 if block ~1 ~ ~1 minecraft:air run tp @e[type=minecraft:player,name=$(name),limit=1] ~1 ~-0.5 ~1 +execute unless score #placed timber_test matches 1 if block ~1 ~ ~1 minecraft:air run scoreboard players set #placed timber_test 1 +$execute unless score #placed timber_test matches 1 if block ~1 ~ ~-1 minecraft:air run tp @e[type=minecraft:player,name=$(name),limit=1] ~1 ~-0.5 ~-1 +execute unless score #placed timber_test matches 1 if block ~1 ~ ~-1 minecraft:air run scoreboard players set #placed timber_test 1 +$execute unless score #placed timber_test matches 1 if block ~-1 ~ ~1 minecraft:air run tp @e[type=minecraft:player,name=$(name),limit=1] ~-1 ~-0.5 ~1 +execute unless score #placed timber_test matches 1 if block ~-1 ~ ~1 minecraft:air run scoreboard players set #placed timber_test 1 +$execute unless score #placed timber_test matches 1 if block ~-1 ~ ~-1 minecraft:air run tp @e[type=minecraft:player,name=$(name),limit=1] ~-1 ~-0.5 ~-1 +execute unless score #placed timber_test matches 1 if block ~-1 ~ ~-1 minecraft:air run scoreboard players set #placed timber_test 1 +$execute unless score #placed timber_test matches 1 if block ~2 ~ ~ minecraft:air run tp @e[type=minecraft:player,name=$(name),limit=1] ~2 ~-0.5 ~ +execute unless score #placed timber_test matches 1 if block ~2 ~ ~ minecraft:air run scoreboard players set #placed timber_test 1 +$execute unless score #placed timber_test matches 1 if block ~-2 ~ ~ minecraft:air run tp @e[type=minecraft:player,name=$(name),limit=1] ~-2 ~-0.5 ~ +execute unless score #placed timber_test matches 1 if block ~-2 ~ ~ minecraft:air run scoreboard players set #placed timber_test 1 +$execute unless score #placed timber_test matches 1 if block ~ ~ ~2 minecraft:air run tp @e[type=minecraft:player,name=$(name),limit=1] ~ ~-0.5 ~2 +execute unless score #placed timber_test matches 1 if block ~ ~ ~2 minecraft:air run scoreboard players set #placed timber_test 1 +$execute unless score #placed timber_test matches 1 if block ~ ~ ~-2 minecraft:air run tp @e[type=minecraft:player,name=$(name),limit=1] ~ ~-0.5 ~-2 +execute unless score #placed timber_test matches 1 if block ~ ~ ~-2 minecraft:air run scoreboard players set #placed timber_test 1 +# no air: the 4 sides again, now also mangrove roots, moss carpet and leaves (they don't suffocate, the datapack only needs the distance) +$execute unless score #placed timber_test matches 1 if block ~1 ~ ~ #timber_test:dummy_can_stand_in run tp @e[type=minecraft:player,name=$(name),limit=1] ~1 ~-0.5 ~ +execute unless score #placed timber_test matches 1 if block ~1 ~ ~ #timber_test:dummy_can_stand_in run scoreboard players set #placed timber_test 1 +$execute unless score #placed timber_test matches 1 if block ~-1 ~ ~ #timber_test:dummy_can_stand_in run tp @e[type=minecraft:player,name=$(name),limit=1] ~-1 ~-0.5 ~ +execute unless score #placed timber_test matches 1 if block ~-1 ~ ~ #timber_test:dummy_can_stand_in run scoreboard players set #placed timber_test 1 +$execute unless score #placed timber_test matches 1 if block ~ ~ ~1 #timber_test:dummy_can_stand_in run tp @e[type=minecraft:player,name=$(name),limit=1] ~ ~-0.5 ~1 +execute unless score #placed timber_test matches 1 if block ~ ~ ~1 #timber_test:dummy_can_stand_in run scoreboard players set #placed timber_test 1 +$execute unless score #placed timber_test matches 1 if block ~ ~ ~-1 #timber_test:dummy_can_stand_in run tp @e[type=minecraft:player,name=$(name),limit=1] ~ ~-0.5 ~-1 +execute unless score #placed timber_test matches 1 if block ~ ~ ~-1 #timber_test:dummy_can_stand_in run scoreboard players set #placed timber_test 1 +$execute unless score #placed timber_test matches 1 run fail {"text":"mine_here: no free spot next to the log for dummy $(name), it would mine from too far away"} $dummy $(name) mine ~ ~ ~ diff --git a/tests/data/timber_test/function/warn_few_leaves.mcfunction b/tests/data/timber_test/function/warn_few_leaves.mcfunction index 55413d9..9d7cc60 100644 --- a/tests/data/timber_test/function/warn_few_leaves.mcfunction +++ b/tests/data/timber_test/function/warn_few_leaves.mcfunction @@ -1,3 +1,3 @@ #> Warns that a tree was not chopped because too few leaves touch its trunk. Same two paths as warn_d7 (log line for the CI, orange chat for players). -$execute unless entity @a[tag=!timber_test_dummy] run say TIMBER-WARNING $(test): tree was not chopped, only $(adj) leaves next to the trunk (min_leaves_found is $(min)), every tree should be choppable -$tellraw @a[tag=!timber_test_dummy] {"text":"Test warning $(test): tree was not chopped, only $(adj) leaves next to the trunk (min_leaves_found is $(min)), every tree should be choppable","color":"gold"} +$execute unless entity @a[tag=!timber_test_dummy] run say TIMBER-WARNING $(test): tree was not chopped, only $(adj) leaves within 2 blocks of the trunk (min_leaves_found is $(min)), every tree should be choppable +$tellraw @a[tag=!timber_test_dummy] {"text":"Test warning $(test): tree was not chopped, only $(adj) leaves within 2 blocks of the trunk (min_leaves_found is $(min)), every tree should be choppable","color":"gold"} diff --git a/tests/data/timber_test/tags/block/dummy_can_stand_in.json b/tests/data/timber_test/tags/block/dummy_can_stand_in.json new file mode 100644 index 0000000..9292700 --- /dev/null +++ b/tests/data/timber_test/tags/block/dummy_can_stand_in.json @@ -0,0 +1,9 @@ +{ + "values": [ + "minecraft:mangrove_roots", + "minecraft:moss_carpet", + "#minecraft:leaves", + "minecraft:mangrove_propagule", + "minecraft:vine" + ] +} diff --git a/tests/data/timber_test/test/chop_mangrove_log_tree.mcfunction b/tests/data/timber_test/test/chop_mangrove_log_tree.mcfunction index f19a12d..438eaf3 100644 --- a/tests/data/timber_test/test/chop_mangrove_log_tree.mcfunction +++ b/tests/data/timber_test/test/chop_mangrove_log_tree.mcfunction @@ -28,9 +28,9 @@ await entity @e[type=minecraft:item,dx=39,dy=39,dz=39,nbt={Item:{id:"minecraft:m await delay 2t await not entity @e[type=minecraft:marker,dx=39,dy=39,dz=39] -# the datapack only chops a tree with at least min_leaves_found leaves next to a trunk. Some mangroves have fewer (measured with -# 40 trees each: mangrove 15 %, tall mangrove 10 % below 5, the tall one down to 0). That is a gap in the datapack, the goal is to -# chop every tree, so such a tree doesn't fail the test but must not be chopped and gives a warning +# the datapack only chops a tree with at least min_leaves_found leaves within 2 blocks of a trunk. Small mangroves often have fewer +# than 5 right next to it, within 2 blocks all of them had enough (measured with 40 trees each on 26.1.2 and 26.3). If one ever has +# fewer, that is a gap in the datapack, the goal is to chop every tree, so such a tree doesn't fail the test but must not be chopped and gives a warning # a chop from a log can leave parts of the tree standing or chop nothing (it searches upwards and sideways, see report_leftovers): warning, no failure execute if score #adj_chop_mangrove_log_tree timber_test >= min_leaves_found timber run function timber_test:report_leftovers {test:"chop_mangrove_log_tree"} execute if score #adj_chop_mangrove_log_tree timber_test < min_leaves_found timber run function timber_test:assert_not_chopped {test:"chop_mangrove_log_tree"} diff --git a/tests/data/timber_test/test/chop_mangrove_roots_tree.mcfunction b/tests/data/timber_test/test/chop_mangrove_roots_tree.mcfunction index 9e585c1..5eefef0 100644 --- a/tests/data/timber_test/test/chop_mangrove_roots_tree.mcfunction +++ b/tests/data/timber_test/test/chop_mangrove_roots_tree.mcfunction @@ -76,9 +76,9 @@ await entity @e[type=minecraft:item,dx=39,dy=39,dz=39,nbt={Item:{id:"minecraft:m await delay 2t await not entity @e[type=minecraft:marker,dx=39,dy=39,dz=39] -# the datapack only chops a tree with at least min_leaves_found leaves next to a trunk. Some mangroves have fewer (measured with -# 40 trees each: mangrove 15 %, tall mangrove 10 % below 5, the tall one down to 0). That is a gap in the datapack, the goal is to -# chop every tree, so such a tree doesn't fail the test but must not be chopped and gives a warning +# the datapack only chops a tree with at least min_leaves_found leaves within 2 blocks of a trunk. Small mangroves often have fewer +# than 5 right next to it, within 2 blocks all of them had enough (measured with 40 trees each on 26.1.2 and 26.3). If one ever has +# fewer, that is a gap in the datapack, the goal is to chop every tree, so such a tree doesn't fail the test but must not be chopped and gives a warning # a chop that starts at a root can leave parts of the tree standing too (branches that bend down again are not reached, see report_leftovers): warning, no failure execute if score #adj_chop_mangrove_roots_tree timber_test >= min_leaves_found timber run function timber_test:report_leftovers {test:"chop_mangrove_roots_tree"} execute if score #adj_chop_mangrove_roots_tree timber_test < min_leaves_found timber run function timber_test:assert_not_chopped {test:"chop_mangrove_roots_tree"} diff --git a/tests/data/timber_test/test/chop_tall_mangrove_roots_tree.mcfunction b/tests/data/timber_test/test/chop_tall_mangrove_roots_tree.mcfunction index 930b808..39fb600 100644 --- a/tests/data/timber_test/test/chop_tall_mangrove_roots_tree.mcfunction +++ b/tests/data/timber_test/test/chop_tall_mangrove_roots_tree.mcfunction @@ -76,9 +76,9 @@ await entity @e[type=minecraft:item,dx=39,dy=39,dz=39,nbt={Item:{id:"minecraft:m await delay 2t await not entity @e[type=minecraft:marker,dx=39,dy=39,dz=39] -# the datapack only chops a tree with at least min_leaves_found leaves next to a trunk. Some mangroves have fewer (measured with -# 40 trees each: mangrove 15 %, tall mangrove 10 % below 5, the tall one down to 0). That is a gap in the datapack, the goal is to -# chop every tree, so such a tree doesn't fail the test but must not be chopped and gives a warning +# the datapack only chops a tree with at least min_leaves_found leaves within 2 blocks of a trunk. Small mangroves often have fewer +# than 5 right next to it, within 2 blocks all of them had enough (measured with 40 trees each on 26.1.2 and 26.3). If one ever has +# fewer, that is a gap in the datapack, the goal is to chop every tree, so such a tree doesn't fail the test but must not be chopped and gives a warning # a chop that starts at a root can leave parts of the tree standing too (branches that bend down again are not reached, see report_leftovers): warning, no failure execute if score #adj_chop_tall_mangrove_roots_tree timber_test >= min_leaves_found timber run function timber_test:report_leftovers {test:"chop_tall_mangrove_roots_tree"} execute if score #adj_chop_tall_mangrove_roots_tree timber_test < min_leaves_found timber run function timber_test:assert_not_chopped {test:"chop_tall_mangrove_roots_tree"} diff --git a/tests/data/timber_test/test/chop_tall_mangrove_tree.mcfunction b/tests/data/timber_test/test/chop_tall_mangrove_tree.mcfunction index dbc4891..ce6fc00 100644 --- a/tests/data/timber_test/test/chop_tall_mangrove_tree.mcfunction +++ b/tests/data/timber_test/test/chop_tall_mangrove_tree.mcfunction @@ -28,9 +28,9 @@ await entity @e[type=minecraft:item,dx=39,dy=39,dz=39,nbt={Item:{id:"minecraft:m await delay 2t await not entity @e[type=minecraft:marker,dx=39,dy=39,dz=39] -# the datapack only chops a tree with at least min_leaves_found leaves next to a trunk. Some mangroves have fewer (measured with -# 40 trees each: mangrove 15 %, tall mangrove 10 % below 5, the tall one down to 0). That is a gap in the datapack, the goal is to -# chop every tree, so such a tree doesn't fail the test but must not be chopped and gives a warning +# the datapack only chops a tree with at least min_leaves_found leaves within 2 blocks of a trunk. Small mangroves often have fewer +# than 5 right next to it, within 2 blocks all of them had enough (measured with 40 trees each on 26.1.2 and 26.3). If one ever has +# fewer, that is a gap in the datapack, the goal is to chop every tree, so such a tree doesn't fail the test but must not be chopped and gives a warning # a chop from a log can leave parts of the tree standing or chop nothing (it searches upwards and sideways, see report_leftovers): warning, no failure execute if score #adj_chop_tall_mangrove_tree timber_test >= min_leaves_found timber run function timber_test:report_leftovers {test:"chop_tall_mangrove_tree"} execute if score #adj_chop_tall_mangrove_tree timber_test < min_leaves_found timber run function timber_test:assert_not_chopped {test:"chop_tall_mangrove_tree"} diff --git a/tests/data/timber_test/test/chopped_five_leaves_within_two_blocks.mcfunction b/tests/data/timber_test/test/chopped_five_leaves_within_two_blocks.mcfunction new file mode 100644 index 0000000..e536daa --- /dev/null +++ b/tests/data/timber_test/test/chopped_five_leaves_within_two_blocks.mcfunction @@ -0,0 +1,33 @@ +#> Mining a log with 3 leaves next to the trunk and 2 more 2 blocks away chops the whole tree (min_leaves_found is 5, leaves within 2 blocks count) +# @environment timber_test:group_5 +# @template timber_test:arena +# @timeout 200 +# @skyaccess true + +function timber_test:platform +# the rule is built for the default of 5 leaves +assert score min_leaves_found timber matches 5 +# a column of 6 logs with 3 leaves at its sides, and 2 leaves 2 blocks away that hang on 2 of them +fill ~20 ~0 ~20 ~20 ~5 ~20 minecraft:oak_log +setblock ~21 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=false] +setblock ~19 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=false] +setblock ~20 ~3 ~21 minecraft:oak_leaves[distance=1,persistent=false] +setblock ~22 ~3 ~20 minecraft:oak_leaves[distance=2,persistent=false] +setblock ~18 ~3 ~20 minecraft:oak_leaves[distance=2,persistent=false] + +function timber_test:count_d7 {test:"chopped_five_leaves_within_two_blocks"} + +function timber_test:count_logs {test:"chopped_five_leaves_within_two_blocks"} +function timber_test:player {name:"pos5w_t",axe:"minecraft:iron_axe"} +function timber_test:mine {name:"pos5w_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 structure +# 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:"chopped_five_leaves_within_two_blocks"} diff --git a/tests/data/timber_test/test/not_chopped_four_leaves_within_two_blocks.mcfunction b/tests/data/timber_test/test/not_chopped_four_leaves_within_two_blocks.mcfunction new file mode 100644 index 0000000..52a4f13 --- /dev/null +++ b/tests/data/timber_test/test/not_chopped_four_leaves_within_two_blocks.mcfunction @@ -0,0 +1,31 @@ +#> Mining a log with 2 leaves next to the trunk, 2 more 2 blocks away and 1 that is 3 blocks away does not chop it (min_leaves_found is 5, only leaves within 2 blocks count) +# @environment timber_test:group_5 +# @template timber_test:arena +# @timeout 200 +# @skyaccess true + +function timber_test:platform +# the rule is built for the default of 5 leaves +assert score min_leaves_found timber matches 5 +# a column of 6 logs with 2 leaves at its sides, 1 more leaf behind each of them (2 blocks away) and 1 leaf 3 blocks away +fill ~20 ~0 ~20 ~20 ~5 ~20 minecraft:oak_log +setblock ~21 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=false] +setblock ~22 ~3 ~20 minecraft:oak_leaves[distance=2,persistent=false] +setblock ~23 ~3 ~20 minecraft:oak_leaves[distance=3,persistent=false] +setblock ~19 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=false] +setblock ~18 ~3 ~20 minecraft:oak_leaves[distance=2,persistent=false] + +function timber_test:count_logs {test:"not_chopped_four_leaves_within_two_blocks"} +function timber_test:player {name:"neg4w_t",axe:"minecraft:iron_axe"} +function timber_test:mine {name:"neg4w_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 structure +# 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:"not_chopped_four_leaves_within_two_blocks"} diff --git a/tests/data/timber_test/test/settings_min_leaves_13_chops_oak.mcfunction b/tests/data/timber_test/test/settings_min_leaves_13_chops_oak.mcfunction index 6e523ca..ba28b9f 100644 --- a/tests/data/timber_test/test/settings_min_leaves_13_chops_oak.mcfunction +++ b/tests/data/timber_test/test/settings_min_leaves_13_chops_oak.mcfunction @@ -1,4 +1,4 @@ -#> An oak has 13 leaves next to its trunk: min_leaves_found 13 chops it +#> An oak has 13 leaves next to its trunk: min_leaves_found 13 chops it (the leaves 2 blocks away are only counted when there are too few) # @environment timber_test:min_leaves_13 # @template timber_test:arena # @timeout 200 diff --git a/tests/data/timber_test/test/settings_min_leaves_14_keeps_13_leaves.mcfunction b/tests/data/timber_test/test/settings_min_leaves_14_keeps_13_leaves.mcfunction new file mode 100644 index 0000000..47d37ad --- /dev/null +++ b/tests/data/timber_test/test/settings_min_leaves_14_keeps_13_leaves.mcfunction @@ -0,0 +1,39 @@ +#> A tree with 13 leaves within 2 blocks of its trunk: min_leaves_found 14 doesn't chop it +#> (An oak can't be used here: it has 13 leaves next to its trunk, but 33 to 37 within 2 blocks, a random number.) +# @environment timber_test:min_leaves_14 +# @template timber_test:arena +# @timeout 200 +# @skyaccess true + +function timber_test:platform +# a column of 6 logs with 5 leaves next to it (4 at its sides, one on top) and 8 leaves 2 blocks away: 4 straight out from the side +# leaves, 4 on the diagonals between them +fill ~20 ~0 ~20 ~20 ~5 ~20 minecraft:oak_log +setblock ~21 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=false] +setblock ~19 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=false] +setblock ~20 ~3 ~21 minecraft:oak_leaves[distance=1,persistent=false] +setblock ~20 ~3 ~19 minecraft:oak_leaves[distance=1,persistent=false] +setblock ~20 ~6 ~20 minecraft:oak_leaves[distance=1,persistent=false] +setblock ~22 ~3 ~20 minecraft:oak_leaves[distance=2,persistent=false] +setblock ~18 ~3 ~20 minecraft:oak_leaves[distance=2,persistent=false] +setblock ~20 ~3 ~22 minecraft:oak_leaves[distance=2,persistent=false] +setblock ~20 ~3 ~18 minecraft:oak_leaves[distance=2,persistent=false] +setblock ~21 ~3 ~21 minecraft:oak_leaves[distance=2,persistent=false] +setblock ~21 ~3 ~19 minecraft:oak_leaves[distance=2,persistent=false] +setblock ~19 ~3 ~21 minecraft:oak_leaves[distance=2,persistent=false] +setblock ~19 ~3 ~19 minecraft:oak_leaves[distance=2,persistent=false] + +function timber_test:count_logs {test:"settings_min_leaves_14_keeps_13_leaves"} +function timber_test:player {name:"minl14_t",axe:"minecraft:iron_axe"} +function timber_test:mine {name:"minl14_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 structure +# 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:"settings_min_leaves_14_keeps_13_leaves"} diff --git a/tests/data/timber_test/test/settings_min_leaves_14_keeps_oak.mcfunction b/tests/data/timber_test/test/settings_min_leaves_14_keeps_oak.mcfunction deleted file mode 100644 index e3a153b..0000000 --- a/tests/data/timber_test/test/settings_min_leaves_14_keeps_oak.mcfunction +++ /dev/null @@ -1,26 +0,0 @@ -#> An oak has 13 leaves next to its trunk: min_leaves_found 14 doesn't chop it -# @environment timber_test:min_leaves_14 -# @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 -assert block ~20 ~2 ~20 #minecraft:logs - -await delay 1s -function timber_test:count_logs {test:"settings_min_leaves_14_keeps_oak"} -function timber_test:player {name:"minl14_t",axe:"minecraft:iron_axe"} -dummy minl14_t mine ~20 ~0 ~20 - -# 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 mined 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 ~0 ~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:"settings_min_leaves_14_keeps_oak"} From 08252a514925ec3bf1a5effcd08166beb75a4d25 Mon Sep 17 00:00:00 2001 From: Flavio Felder Date: Wed, 23 Sep 2026 21:39:25 +0200 Subject: [PATCH 5/7] Raise min_leaves_found to 6, give fungi their own min_caps_found Mangrove tests fail on leftover logs and roots; tests run without random ticks Co-Authored-By: Claude Opus 5.5 --- data/timber/function/run_fungi.mcfunction | 3 +- .../settings/default_values.mcfunction | 3 +- .../function/settings/open/leaves.mcfunction | 2 +- .../function/settings/open/menu.mcfunction | 2 +- .../function/assert_not_chopped.mcfunction | 21 ------ .../function/count_adjacent.mcfunction | 75 ------------------- .../env/min_leaves_13_teardown.mcfunction | 2 +- .../env/min_leaves_14_teardown.mcfunction | 2 +- .../env/restore_all_defaults.mcfunction | 3 +- .../data/timber_test/function/load.mcfunction | 3 + .../function/report_leftovers.mcfunction | 41 ---------- .../function/warn_few_leaves.mcfunction | 3 - .../timber_test/function/warn_left.mcfunction | 3 - .../test/chop_mangrove_log_tree.mcfunction | 11 +-- .../test/chop_mangrove_roots_tree.mcfunction | 11 +-- .../chop_tall_mangrove_roots_tree.mcfunction | 11 +-- .../test/chop_tall_mangrove_tree.mcfunction | 11 +-- ...function => chopped_six_leaves.mcfunction} | 22 +++--- ...d_six_leaves_within_two_blocks.mcfunction} | 20 ++--- ...ion => not_chopped_five_leaves.mcfunction} | 17 ++--- ..._five_leaves_within_two_blocks.mcfunction} | 23 +++--- .../placed_leaves_should_not_count.mcfunction | 9 ++- ...roy_roots_default_removes_roots.mcfunction | 3 +- ...s_destroy_roots_off_keeps_roots.mcfunction | 3 +- ...ngs_max_tree_size_caps_the_chop.mcfunction | 3 +- ...sistent_on_counts_placed_leaves.mcfunction | 5 +- ...op_ignores_player_placed_leaves.mcfunction | 3 +- ...tings_sneak_only_sneaking_chops.mcfunction | 3 +- ...ttings_sneaking_default_no_chop.mcfunction | 3 +- ...tart_chop_on_root_default_chops.mcfunction | 3 +- ..._start_chop_on_root_off_no_chop.mcfunction | 3 +- 31 files changed, 94 insertions(+), 233 deletions(-) delete mode 100644 tests/data/timber_test/function/assert_not_chopped.mcfunction delete mode 100644 tests/data/timber_test/function/count_adjacent.mcfunction delete mode 100644 tests/data/timber_test/function/report_leftovers.mcfunction delete mode 100644 tests/data/timber_test/function/warn_few_leaves.mcfunction delete mode 100644 tests/data/timber_test/function/warn_left.mcfunction rename tests/data/timber_test/test/{not_chopped_four_leaves.mcfunction => chopped_six_leaves.mcfunction} (53%) rename tests/data/timber_test/test/{chopped_five_leaves_within_two_blocks.mcfunction => chopped_six_leaves_within_two_blocks.mcfunction} (55%) rename tests/data/timber_test/test/{chopped_five_leaves.mcfunction => not_chopped_five_leaves.mcfunction} (65%) rename tests/data/timber_test/test/{not_chopped_four_leaves_within_two_blocks.mcfunction => not_chopped_five_leaves_within_two_blocks.mcfunction} (64%) diff --git a/data/timber/function/run_fungi.mcfunction b/data/timber/function/run_fungi.mcfunction index ff8a737..4043231 100644 --- a/data/timber/function/run_fungi.mcfunction +++ b/data/timber/function/run_fungi.mcfunction @@ -11,7 +11,8 @@ function timber:tool/durability_get execute at @e[type=minecraft:marker,tag=timber_tree,tag=!timber_slow_chop,distance=..7,sort=arbitrary,limit=1] run function timber:algorithm/fungus/run execute store result score leaves_found timber if entity @e[type=minecraft:marker,tag=timber_leaves_found] # if fungi is valid -execute if score leaves_found timber >= min_leaves_found timber unless score tree_size timber matches 0 run scoreboard players set tree_type timber 2 +# a fungus has far fewer cap blocks next to its stem than a tree has leaves (warped fungus and huge mushrooms down to 5), so it has its own minimum +execute if score leaves_found timber >= min_caps_found timber unless score tree_size timber matches 0 run scoreboard players set tree_type timber 2 # don't destroy the blocks in a radius of 5 blocks from other stems execute if score tree_type timber matches 2 run function timber:algorithm/fungus/leaves/find_other_stems diff --git a/data/timber/function/settings/default_values.mcfunction b/data/timber/function/settings/default_values.mcfunction index d4284a3..4171bea 100644 --- a/data/timber/function/settings/default_values.mcfunction +++ b/data/timber/function/settings/default_values.mcfunction @@ -20,4 +20,5 @@ execute unless score persistent timber = persistent timber run scoreboard player execute unless score blocks_per_chop timber = blocks_per_chop timber run scoreboard players set blocks_per_chop timber 1 execute unless score time_between_chops timber = time_between_chops timber run scoreboard players set time_between_chops timber 1 execute unless score max_tree_size timber = max_tree_size timber run scoreboard players set max_tree_size timber 555 -execute unless score min_leaves_found timber = min_leaves_found timber run scoreboard players set min_leaves_found timber 5 +execute unless score min_leaves_found timber = min_leaves_found timber run scoreboard players set min_leaves_found timber 6 +execute unless score min_caps_found timber = min_caps_found timber run scoreboard players set min_caps_found timber 5 diff --git a/data/timber/function/settings/open/leaves.mcfunction b/data/timber/function/settings/open/leaves.mcfunction index ff6abf2..d72592d 100644 --- a/data/timber/function/settings/open/leaves.mcfunction +++ b/data/timber/function/settings/open/leaves.mcfunction @@ -1,6 +1,6 @@ # opens the "Min. Leaves" slider with the current value # only reachable for admins, so the trigger gets enabled only for them scoreboard players enable @s TimberSetLeaves -data modify storage timber:settings dialog set value {"type":"minecraft:multi_action","title":"Timber Settings / Min. Leaves","external_title":"Timber Min. Leaves","body":{"type":"minecraft:plain_message","contents":["",{"text":"This datapack considers a tree only if it has at least this many naturally generated leaves within 2 blocks of its trunk.\nFungi will get checked by the amount of cap blocks and additionally a 3×3 big cross on top of the stem."},"\n\n",{"text":"Default: 5","color":"gray"}],"width":300},"inputs":[{"type":"minecraft:number_range","key":"min_leaves_found","width":300,"label":["",{"text":"Set Min. Leaves to Valid a Tree","hover_event":{"action":"show_text","value":["",{"text":"Default: 5"},{"text":"\n\nThis datapack considers a tree only if it has at least this many naturally generated leaves within 2 blocks of its trunk.\nFungi will get checked by the amount of cap blocks and additionally a 3×3 big cross on top of the stem."}]}}],"start":0,"end":10,"step":1,"initial":0}],"can_close_with_escape":true,"pause":false,"after_action":"none","columns":1,"actions":[{"label":"Save","width":300,"action":{"type":"minecraft:dynamic/run_command","template":"trigger TimberSetLeaves set 1$(min_leaves_found)"}}],"exit_action":{"label":{"translate":"gui.back"},"width":300,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 1"}}} +data modify storage timber:settings dialog set value {"type":"minecraft:multi_action","title":"Timber Settings / Min. Leaves","external_title":"Timber Min. Leaves","body":{"type":"minecraft:plain_message","contents":["",{"text":"This datapack considers a tree only if it has at least this many naturally generated leaves within 2 blocks of its trunk."},"\n\n",{"text":"Default: 6","color":"gray"}],"width":300},"inputs":[{"type":"minecraft:number_range","key":"min_leaves_found","width":300,"label":["",{"text":"Set Min. Leaves to Valid a Tree","hover_event":{"action":"show_text","value":["",{"text":"Default: 6"},{"text":"\n\nThis datapack considers a tree only if it has at least this many naturally generated leaves within 2 blocks of its trunk."}]}}],"start":0,"end":10,"step":1,"initial":0}],"can_close_with_escape":true,"pause":false,"after_action":"none","columns":1,"actions":[{"label":"Save","width":300,"action":{"type":"minecraft:dynamic/run_command","template":"trigger TimberSetLeaves set 1$(min_leaves_found)"}}],"exit_action":{"label":{"translate":"gui.back"},"width":300,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 1"}}} execute store result storage timber:settings dialog.inputs[0].initial float 1 run scoreboard players get min_leaves_found timber function timber:settings/show with storage timber:settings diff --git a/data/timber/function/settings/open/menu.mcfunction b/data/timber/function/settings/open/menu.mcfunction index 3bb1e69..38cd58d 100644 --- a/data/timber/function/settings/open/menu.mcfunction +++ b/data/timber/function/settings/open/menu.mcfunction @@ -46,6 +46,6 @@ execute unless score chop_fungi timber matches 1 run data modify storage timber: data modify storage timber:settings dialog.actions append value {"label":"Blocks per Chop","width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 6"},"tooltip":"Amount of blocks which get destroyed per chop.\nOnly used when \"Chop Slowly\" is enabled."} data modify storage timber:settings dialog.actions append value {"label":"Ticks Between Chops","width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 7"},"tooltip":"Time in ticks between two chops.\nOnly used when \"Chop Slowly\" is enabled."} data modify storage timber:settings dialog.actions append value {"label":"Max. Tree Size","width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 8"},"tooltip":"Trees bigger than this set value won't get further processed."} -data modify storage timber:settings dialog.actions append value {"label":"Min. Leaves","width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 9"},"tooltip":"This datapack considers a tree only if it has at least this many naturally generated leaves within 2 blocks of its trunk.\nFungi will get checked by the amount of cap blocks and additionally a 3×3 big cross on top of the stem."} +data modify storage timber:settings dialog.actions append value {"label":"Min. Leaves","width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 9"},"tooltip":"This datapack considers a tree only if it has at least this many naturally generated leaves within 2 blocks of its trunk."} data modify storage timber:settings dialog.actions append value {"label":"Tools","width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 3"},"tooltip":"Axes"} function timber:settings/show with storage timber:settings diff --git a/tests/data/timber_test/function/assert_not_chopped.mcfunction b/tests/data/timber_test/function/assert_not_chopped.mcfunction deleted file mode 100644 index 36a016f..0000000 --- a/tests/data/timber_test/function/assert_not_chopped.mcfunction +++ /dev/null @@ -1,21 +0,0 @@ -#> For a tree the datapack is not allowed to chop (too few leaves next to the trunk): fails the test if it chopped anyway, -#> i.e. if more than the one mined log is gone. Then warns, because every tree should be choppable, this is a known gap. -#> Replaces logs with air while counting, so run it last. -#> @param test name of the test, count_logs and count_adjacent have to be called with the same name -scoreboard objectives add timber_test dummy -execute store result score #logs1 timber_test run fill ~0 ~0 ~0 ~39 ~19 ~39 minecraft:air replace #minecraft:logs -execute store result score #dtmp timber_test run fill ~0 ~20 ~0 ~39 ~39 ~39 minecraft:air replace #minecraft:logs -scoreboard players operation #logs1 timber_test += #dtmp timber_test - -# warning first, the CI workflow picks it up from the log -$data modify storage timber_test:tmp test set value "$(test)" -$execute store result storage timber_test:tmp adj int 1 run scoreboard players get #adj_$(test) timber_test -execute store result storage timber_test:tmp min int 1 run scoreboard players get min_leaves_found timber -function timber_test:warn_few_leaves with storage timber_test:tmp - -# the one mined log may be gone, nothing else -scoreboard players add #logs1 timber_test 1 -# PackTest's assert doesn't take a comparison of two scores (the parse error inside a macro function is swallowed, the test can't fail): -# subtract and compare with matches -$scoreboard players operation #logs1 timber_test -= #logs0_$(test) timber_test -assert score #logs1 timber_test matches 0.. diff --git a/tests/data/timber_test/function/count_adjacent.mcfunction b/tests/data/timber_test/function/count_adjacent.mcfunction deleted file mode 100644 index d0b349b..0000000 --- a/tests/data/timber_test/function/count_adjacent.mcfunction +++ /dev/null @@ -1,75 +0,0 @@ -#> Counts the leaves (and hanging mangrove propagules, the datapack's leaf predicate counts them too) within 2 blocks of a log (1 block on one of its 6 sides, or 2 blocks straight or around an edge) into #adj_, this is the number the datapack compares with min_leaves_found. -#> Doesn't change the tree. The leaves are copied far above the arena, then the logs are copied over them shifted to every place within 2 blocks: -#> every leaf a log lands on is within 2 blocks of a log, what is left of the copy is not. The source of a shift is cut so nothing is copied outside the arena. -#> @param test name of the test -scoreboard objectives add timber_test dummy -fill ~0 ~64 ~0 ~39 ~83 ~39 minecraft:air -fill ~0 ~84 ~0 ~39 ~103 ~39 minecraft:air -execute store result score #ta timber_test run clone ~0 ~0 ~0 ~39 ~19 ~39 ~0 ~64 ~0 filtered #minecraft:leaves -execute store result score #tb timber_test run clone ~0 ~20 ~0 ~39 ~39 ~39 ~0 ~84 ~0 filtered #minecraft:leaves -execute store result score #pa timber_test run clone ~0 ~0 ~0 ~39 ~19 ~39 ~0 ~64 ~0 filtered minecraft:mangrove_propagule[hanging=true] -execute store result score #pb timber_test run clone ~0 ~20 ~0 ~39 ~39 ~39 ~0 ~84 ~0 filtered minecraft:mangrove_propagule[hanging=true] -scoreboard players operation #ta timber_test += #pa timber_test -scoreboard players operation #tb timber_test += #pb timber_test -clone ~0 ~0 ~0 ~39 ~19 ~39 ~1 ~64 ~0 filtered #minecraft:logs -clone ~0 ~20 ~0 ~39 ~39 ~39 ~1 ~84 ~0 filtered #minecraft:logs -clone ~0 ~0 ~0 ~39 ~19 ~39 ~-1 ~64 ~0 filtered #minecraft:logs -clone ~0 ~20 ~0 ~39 ~39 ~39 ~-1 ~84 ~0 filtered #minecraft:logs -clone ~0 ~0 ~0 ~39 ~19 ~39 ~0 ~65 ~0 filtered #minecraft:logs -clone ~0 ~20 ~0 ~39 ~39 ~39 ~0 ~85 ~0 filtered #minecraft:logs -clone ~0 ~0 ~0 ~39 ~19 ~39 ~0 ~63 ~0 filtered #minecraft:logs -clone ~0 ~20 ~0 ~39 ~39 ~39 ~0 ~83 ~0 filtered #minecraft:logs -clone ~0 ~0 ~0 ~39 ~19 ~39 ~0 ~64 ~1 filtered #minecraft:logs -clone ~0 ~20 ~0 ~39 ~39 ~39 ~0 ~84 ~1 filtered #minecraft:logs -clone ~0 ~0 ~0 ~39 ~19 ~39 ~0 ~64 ~-1 filtered #minecraft:logs -clone ~0 ~20 ~0 ~39 ~39 ~39 ~0 ~84 ~-1 filtered #minecraft:logs -# and 2 blocks away (straight or around an edge), the datapack counts those too when there are too few right at the trunk -clone ~0 ~0 ~0 ~39 ~19 ~37 ~0 ~64 ~2 filtered #minecraft:logs -clone ~0 ~20 ~0 ~39 ~39 ~37 ~0 ~84 ~2 filtered #minecraft:logs -clone ~0 ~0 ~2 ~39 ~19 ~39 ~0 ~64 ~0 filtered #minecraft:logs -clone ~0 ~20 ~2 ~39 ~39 ~39 ~0 ~84 ~0 filtered #minecraft:logs -clone ~0 ~0 ~0 ~37 ~19 ~39 ~2 ~64 ~0 filtered #minecraft:logs -clone ~0 ~20 ~0 ~37 ~39 ~39 ~2 ~84 ~0 filtered #minecraft:logs -clone ~2 ~0 ~0 ~39 ~19 ~39 ~0 ~64 ~0 filtered #minecraft:logs -clone ~2 ~20 ~0 ~39 ~39 ~39 ~0 ~84 ~0 filtered #minecraft:logs -clone ~0 ~0 ~0 ~39 ~19 ~39 ~0 ~66 ~0 filtered #minecraft:logs -clone ~0 ~20 ~0 ~39 ~39 ~39 ~0 ~86 ~0 filtered #minecraft:logs -clone ~0 ~0 ~0 ~39 ~19 ~39 ~0 ~62 ~0 filtered #minecraft:logs -clone ~0 ~20 ~0 ~39 ~39 ~39 ~0 ~82 ~0 filtered #minecraft:logs -clone ~0 ~0 ~0 ~38 ~19 ~38 ~1 ~64 ~1 filtered #minecraft:logs -clone ~0 ~20 ~0 ~38 ~39 ~38 ~1 ~84 ~1 filtered #minecraft:logs -clone ~0 ~0 ~1 ~38 ~19 ~39 ~1 ~64 ~0 filtered #minecraft:logs -clone ~0 ~20 ~1 ~38 ~39 ~39 ~1 ~84 ~0 filtered #minecraft:logs -clone ~1 ~0 ~0 ~39 ~19 ~38 ~0 ~64 ~1 filtered #minecraft:logs -clone ~1 ~20 ~0 ~39 ~39 ~38 ~0 ~84 ~1 filtered #minecraft:logs -clone ~1 ~0 ~1 ~39 ~19 ~39 ~0 ~64 ~0 filtered #minecraft:logs -clone ~1 ~20 ~1 ~39 ~39 ~39 ~0 ~84 ~0 filtered #minecraft:logs -clone ~0 ~0 ~0 ~38 ~19 ~39 ~1 ~65 ~0 filtered #minecraft:logs -clone ~0 ~20 ~0 ~38 ~39 ~39 ~1 ~85 ~0 filtered #minecraft:logs -clone ~1 ~0 ~0 ~39 ~19 ~39 ~0 ~65 ~0 filtered #minecraft:logs -clone ~1 ~20 ~0 ~39 ~39 ~39 ~0 ~85 ~0 filtered #minecraft:logs -clone ~0 ~0 ~0 ~39 ~19 ~38 ~0 ~65 ~1 filtered #minecraft:logs -clone ~0 ~20 ~0 ~39 ~39 ~38 ~0 ~85 ~1 filtered #minecraft:logs -clone ~0 ~0 ~1 ~39 ~19 ~39 ~0 ~65 ~0 filtered #minecraft:logs -clone ~0 ~20 ~1 ~39 ~39 ~39 ~0 ~85 ~0 filtered #minecraft:logs -clone ~0 ~0 ~0 ~38 ~19 ~39 ~1 ~63 ~0 filtered #minecraft:logs -clone ~0 ~20 ~0 ~38 ~39 ~39 ~1 ~83 ~0 filtered #minecraft:logs -clone ~1 ~0 ~0 ~39 ~19 ~39 ~0 ~63 ~0 filtered #minecraft:logs -clone ~1 ~20 ~0 ~39 ~39 ~39 ~0 ~83 ~0 filtered #minecraft:logs -clone ~0 ~0 ~0 ~39 ~19 ~38 ~0 ~63 ~1 filtered #minecraft:logs -clone ~0 ~20 ~0 ~39 ~39 ~38 ~0 ~83 ~1 filtered #minecraft:logs -clone ~0 ~0 ~1 ~39 ~19 ~39 ~0 ~63 ~0 filtered #minecraft:logs -clone ~0 ~20 ~1 ~39 ~39 ~39 ~0 ~83 ~0 filtered #minecraft:logs -execute store result score #ra timber_test run fill ~0 ~64 ~0 ~39 ~83 ~39 minecraft:air replace #minecraft:leaves -execute store result score #rb timber_test run fill ~0 ~84 ~0 ~39 ~103 ~39 minecraft:air replace #minecraft:leaves -execute store result score #pa timber_test run fill ~0 ~64 ~0 ~39 ~83 ~39 minecraft:air replace minecraft:mangrove_propagule[hanging=true] -execute store result score #pb timber_test run fill ~0 ~84 ~0 ~39 ~103 ~39 minecraft:air replace minecraft:mangrove_propagule[hanging=true] -scoreboard players operation #ra timber_test += #pa timber_test -scoreboard players operation #rb timber_test += #pb timber_test -scoreboard players operation #atmp timber_test = #ta timber_test -scoreboard players operation #atmp timber_test += #tb timber_test -scoreboard players operation #atmp timber_test -= #ra timber_test -scoreboard players operation #atmp timber_test -= #rb timber_test -$scoreboard players operation #adj_$(test) timber_test = #atmp timber_test -fill ~0 ~64 ~0 ~39 ~83 ~39 minecraft:air -fill ~0 ~84 ~0 ~39 ~103 ~39 minecraft:air diff --git a/tests/data/timber_test/function/env/min_leaves_13_teardown.mcfunction b/tests/data/timber_test/function/env/min_leaves_13_teardown.mcfunction index 8a0c084..d6fc407 100644 --- a/tests/data/timber_test/function/env/min_leaves_13_teardown.mcfunction +++ b/tests/data/timber_test/function/env/min_leaves_13_teardown.mcfunction @@ -1,2 +1,2 @@ #> Back to the defaults -scoreboard players set min_leaves_found timber 5 +scoreboard players set min_leaves_found timber 6 diff --git a/tests/data/timber_test/function/env/min_leaves_14_teardown.mcfunction b/tests/data/timber_test/function/env/min_leaves_14_teardown.mcfunction index 8a0c084..d6fc407 100644 --- a/tests/data/timber_test/function/env/min_leaves_14_teardown.mcfunction +++ b/tests/data/timber_test/function/env/min_leaves_14_teardown.mcfunction @@ -1,2 +1,2 @@ #> Back to the defaults -scoreboard players set min_leaves_found timber 5 +scoreboard players set min_leaves_found timber 6 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 78d2e47..b5d49a1 100644 --- a/tests/data/timber_test/function/env/restore_all_defaults.mcfunction +++ b/tests/data/timber_test/function/env/restore_all_defaults.mcfunction @@ -26,6 +26,7 @@ scoreboard players set persistent timber 0 scoreboard players set blocks_per_chop timber 1 scoreboard players set time_between_chops timber 1 scoreboard players set max_tree_size timber 555 -scoreboard players set min_leaves_found timber 5 +scoreboard players set min_leaves_found timber 6 +scoreboard players set min_caps_found timber 5 schedule clear timber:slow_chop/loop schedule clear timber:autoplant/loop diff --git a/tests/data/timber_test/function/load.mcfunction b/tests/data/timber_test/function/load.mcfunction index 0b279e7..9195242 100644 --- a/tests/data/timber_test/function/load.mcfunction +++ b/tests/data/timber_test/function/load.mcfunction @@ -4,3 +4,6 @@ #> left a few leaves of a tree standing (0 in 30 runs with it). One player chopping at a time in a real world does not get near the limit: #> the biggest trees of the tests (500 blocks) pass with the default when they run alone. gamerule minecraft:max_command_forks 1000000000 +#> No random block ticks: farmland dries out without water nearby, leaves with distance=7 decay and saplings grow while a test waits. +#> Then a test would depend on luck. The datapack itself doesn't need them. +gamerule minecraft:random_tick_speed 0 diff --git a/tests/data/timber_test/function/report_leftovers.mcfunction b/tests/data/timber_test/function/report_leftovers.mcfunction deleted file mode 100644 index d7bc94b..0000000 --- a/tests/data/timber_test/function/report_leftovers.mcfunction +++ /dev/null @@ -1,41 +0,0 @@ -#> Like assert_no_tree, but nothing fails: leftovers, and a tree that wasn't chopped at all, only warn. -#> For chops that start at a mangrove log. The datapack searches upwards and sideways from the mined log, downwards only with chop_down, -#> so a branch that bends down again can stay, and if the logs it reaches touch fewer than min_leaves_found leaves it chops nothing at all. -#> That is a gap (the goal is to chop every tree), it is reported, not failed. Nothing can be asserted here that always holds. -#> Replaces logs and leaves with air while counting, so run it last. Two slabs, a command may only touch 32768 blocks. -#> @param test name of the test, count_d7 and count_logs have to be called with the same name -scoreboard objectives add timber_test dummy -execute store result score #d7_after timber_test run clone ~0 ~0 ~0 ~39 ~19 ~39 ~0 ~64 ~0 filtered #minecraft:leaves[distance=7] -execute store result score #dtmp timber_test run clone ~0 ~20 ~0 ~39 ~39 ~39 ~0 ~64 ~0 filtered #minecraft:leaves[distance=7] -scoreboard players operation #d7_after timber_test += #dtmp timber_test -fill ~0 ~64 ~0 ~39 ~83 ~39 minecraft:air -fill ~0 ~0 ~0 ~39 ~19 ~39 minecraft:air replace #minecraft:leaves[distance=7] -fill ~0 ~20 ~0 ~39 ~39 ~39 minecraft:air replace #minecraft:leaves[distance=7] -execute store result score #logs timber_test run fill ~0 ~0 ~0 ~39 ~19 ~39 minecraft:air replace #minecraft:logs -execute store result score #dtmp timber_test run fill ~0 ~20 ~0 ~39 ~39 ~39 minecraft:air replace #minecraft:logs -scoreboard players operation #logs timber_test += #dtmp timber_test -execute store result score #leaves timber_test run fill ~0 ~0 ~0 ~39 ~19 ~39 minecraft:air replace #minecraft:leaves -execute store result score #dtmp timber_test run fill ~0 ~20 ~0 ~39 ~39 ~39 minecraft:air replace #minecraft:leaves -scoreboard players operation #leaves timber_test += #dtmp timber_test - -execute store result score #roots timber_test run fill ~0 ~0 ~0 ~39 ~19 ~39 minecraft:air replace minecraft:mangrove_roots -execute store result score #dtmp timber_test run fill ~0 ~20 ~0 ~39 ~39 ~39 minecraft:air replace minecraft:mangrove_roots -scoreboard players operation #roots timber_test += #dtmp timber_test -execute store result score #dtmp timber_test run fill ~0 ~0 ~0 ~39 ~19 ~39 minecraft:air replace minecraft:muddy_mangrove_roots -scoreboard players operation #roots timber_test += #dtmp timber_test -execute store result score #dtmp timber_test run fill ~0 ~20 ~0 ~39 ~39 ~39 minecraft:air replace minecraft:muddy_mangrove_roots -scoreboard players operation #roots timber_test += #dtmp timber_test - -# warnings -$data modify storage timber_test:tmp test set value "$(test)" -$execute store result storage timber_test:tmp before int 1 run scoreboard players get #d7_$(test) timber_test -execute store result storage timber_test:tmp after int 1 run scoreboard players get #d7_after timber_test -execute if score #d7_after timber_test matches 1.. run function timber_test:warn_d7 with storage timber_test:tmp -$execute store result storage timber_test:tmp total int 1 run scoreboard players get #logs0_$(test) timber_test -execute store result storage timber_test:tmp logs int 1 run scoreboard players get #logs timber_test -execute store result storage timber_test:tmp leaves int 1 run scoreboard players get #leaves timber_test -execute store result storage timber_test:tmp roots int 1 run scoreboard players get #roots timber_test -scoreboard players operation #left timber_test = #logs timber_test -scoreboard players operation #left timber_test += #leaves timber_test -scoreboard players operation #left timber_test += #roots timber_test -execute unless score #left timber_test matches 0 run function timber_test:warn_left with storage timber_test:tmp diff --git a/tests/data/timber_test/function/warn_few_leaves.mcfunction b/tests/data/timber_test/function/warn_few_leaves.mcfunction deleted file mode 100644 index 9d7cc60..0000000 --- a/tests/data/timber_test/function/warn_few_leaves.mcfunction +++ /dev/null @@ -1,3 +0,0 @@ -#> Warns that a tree was not chopped because too few leaves touch its trunk. Same two paths as warn_d7 (log line for the CI, orange chat for players). -$execute unless entity @a[tag=!timber_test_dummy] run say TIMBER-WARNING $(test): tree was not chopped, only $(adj) leaves within 2 blocks of the trunk (min_leaves_found is $(min)), every tree should be choppable -$tellraw @a[tag=!timber_test_dummy] {"text":"Test warning $(test): tree was not chopped, only $(adj) leaves within 2 blocks of the trunk (min_leaves_found is $(min)), every tree should be choppable","color":"gold"} diff --git a/tests/data/timber_test/function/warn_left.mcfunction b/tests/data/timber_test/function/warn_left.mcfunction deleted file mode 100644 index 56808b5..0000000 --- a/tests/data/timber_test/function/warn_left.mcfunction +++ /dev/null @@ -1,3 +0,0 @@ -#> Warns that a chop left logs or leaves standing. Same two paths as warn_d7 (log line for the CI, orange chat for players). -$execute unless entity @a[tag=!timber_test_dummy] run say TIMBER-WARNING $(test): chop left $(logs) of $(total) logs, $(roots) roots and $(leaves) leaves standing, every tree should be chopped completely -$tellraw @a[tag=!timber_test_dummy] {"text":"Test warning $(test): chop left $(logs) of $(total) logs, $(roots) roots and $(leaves) leaves standing, every tree should be chopped completely","color":"gold"} diff --git a/tests/data/timber_test/test/chop_mangrove_log_tree.mcfunction b/tests/data/timber_test/test/chop_mangrove_log_tree.mcfunction index 438eaf3..3be806c 100644 --- a/tests/data/timber_test/test/chop_mangrove_log_tree.mcfunction +++ b/tests/data/timber_test/test/chop_mangrove_log_tree.mcfunction @@ -10,8 +10,6 @@ function timber_test:tree {feature:"minecraft:mangrove"} # let the leaves settle before the tree is chopped await delay 1s function timber_test:count_d7 {test:"chop_mangrove_log_tree"} -function timber_test:count_adjacent {test:"chop_mangrove_log_tree"} -function timber_test:count_logs {test:"chop_mangrove_log_tree"} function timber_test:player {name:"mangrove_log_t",axe:"minecraft:iron_axe"} # mine the lowest log of the whole tree, from a spot next to it (see find_lowest_log and mine_here for why) @@ -28,9 +26,6 @@ await entity @e[type=minecraft:item,dx=39,dy=39,dz=39,nbt={Item:{id:"minecraft:m await delay 2t await not entity @e[type=minecraft:marker,dx=39,dy=39,dz=39] -# the datapack only chops a tree with at least min_leaves_found leaves within 2 blocks of a trunk. Small mangroves often have fewer -# than 5 right next to it, within 2 blocks all of them had enough (measured with 40 trees each on 26.1.2 and 26.3). If one ever has -# fewer, that is a gap in the datapack, the goal is to chop every tree, so such a tree doesn't fail the test but must not be chopped and gives a warning -# a chop from a log can leave parts of the tree standing or chop nothing (it searches upwards and sideways, see report_leftovers): warning, no failure -execute if score #adj_chop_mangrove_log_tree timber_test >= min_leaves_found timber run function timber_test:report_leftovers {test:"chop_mangrove_log_tree"} -execute if score #adj_chop_mangrove_log_tree timber_test < min_leaves_found timber run function timber_test:assert_not_chopped {test:"chop_mangrove_log_tree"} +# big crown: logs and roots have to be gone, leaves that are left only warn (see assert_no_logs_warn_leaves) +function timber_test:assert_no_logs_warn_leaves {test:"chop_mangrove_log_tree"} +function timber_test:assert_no_roots diff --git a/tests/data/timber_test/test/chop_mangrove_roots_tree.mcfunction b/tests/data/timber_test/test/chop_mangrove_roots_tree.mcfunction index 5eefef0..56371b6 100644 --- a/tests/data/timber_test/test/chop_mangrove_roots_tree.mcfunction +++ b/tests/data/timber_test/test/chop_mangrove_roots_tree.mcfunction @@ -10,8 +10,6 @@ function timber_test:tree {feature:"minecraft:mangrove"} # let the leaves settle before the tree is chopped await delay 1s function timber_test:count_d7 {test:"chop_mangrove_roots_tree"} -function timber_test:count_adjacent {test:"chop_mangrove_roots_tree"} -function timber_test:count_logs {test:"chop_mangrove_roots_tree"} function timber_test:player {name:"mangrove_root_t",axe:"minecraft:iron_axe"} # mine the lowest root of the trunk column (first match from the bottom) @@ -76,9 +74,6 @@ await entity @e[type=minecraft:item,dx=39,dy=39,dz=39,nbt={Item:{id:"minecraft:m await delay 2t await not entity @e[type=minecraft:marker,dx=39,dy=39,dz=39] -# the datapack only chops a tree with at least min_leaves_found leaves within 2 blocks of a trunk. Small mangroves often have fewer -# than 5 right next to it, within 2 blocks all of them had enough (measured with 40 trees each on 26.1.2 and 26.3). If one ever has -# fewer, that is a gap in the datapack, the goal is to chop every tree, so such a tree doesn't fail the test but must not be chopped and gives a warning -# a chop that starts at a root can leave parts of the tree standing too (branches that bend down again are not reached, see report_leftovers): warning, no failure -execute if score #adj_chop_mangrove_roots_tree timber_test >= min_leaves_found timber run function timber_test:report_leftovers {test:"chop_mangrove_roots_tree"} -execute if score #adj_chop_mangrove_roots_tree timber_test < min_leaves_found timber run function timber_test:assert_not_chopped {test:"chop_mangrove_roots_tree"} +# big crown: logs and roots have to be gone, leaves that are left only warn (see assert_no_logs_warn_leaves) +function timber_test:assert_no_logs_warn_leaves {test:"chop_mangrove_roots_tree"} +function timber_test:assert_no_roots diff --git a/tests/data/timber_test/test/chop_tall_mangrove_roots_tree.mcfunction b/tests/data/timber_test/test/chop_tall_mangrove_roots_tree.mcfunction index 39fb600..a23d7e7 100644 --- a/tests/data/timber_test/test/chop_tall_mangrove_roots_tree.mcfunction +++ b/tests/data/timber_test/test/chop_tall_mangrove_roots_tree.mcfunction @@ -10,8 +10,6 @@ function timber_test:tree {feature:"minecraft:tall_mangrove"} # let the leaves settle before the tree is chopped await delay 1s function timber_test:count_d7 {test:"chop_tall_mangrove_roots_tree"} -function timber_test:count_adjacent {test:"chop_tall_mangrove_roots_tree"} -function timber_test:count_logs {test:"chop_tall_mangrove_roots_tree"} function timber_test:player {name:"tall_mg_root_t",axe:"minecraft:iron_axe"} # mine the lowest root of the trunk column (first match from the bottom) @@ -76,9 +74,6 @@ await entity @e[type=minecraft:item,dx=39,dy=39,dz=39,nbt={Item:{id:"minecraft:m await delay 2t await not entity @e[type=minecraft:marker,dx=39,dy=39,dz=39] -# the datapack only chops a tree with at least min_leaves_found leaves within 2 blocks of a trunk. Small mangroves often have fewer -# than 5 right next to it, within 2 blocks all of them had enough (measured with 40 trees each on 26.1.2 and 26.3). If one ever has -# fewer, that is a gap in the datapack, the goal is to chop every tree, so such a tree doesn't fail the test but must not be chopped and gives a warning -# a chop that starts at a root can leave parts of the tree standing too (branches that bend down again are not reached, see report_leftovers): warning, no failure -execute if score #adj_chop_tall_mangrove_roots_tree timber_test >= min_leaves_found timber run function timber_test:report_leftovers {test:"chop_tall_mangrove_roots_tree"} -execute if score #adj_chop_tall_mangrove_roots_tree timber_test < min_leaves_found timber run function timber_test:assert_not_chopped {test:"chop_tall_mangrove_roots_tree"} +# big crown: logs and roots have to be gone, leaves that are left only warn (see assert_no_logs_warn_leaves) +function timber_test:assert_no_logs_warn_leaves {test:"chop_tall_mangrove_roots_tree"} +function timber_test:assert_no_roots diff --git a/tests/data/timber_test/test/chop_tall_mangrove_tree.mcfunction b/tests/data/timber_test/test/chop_tall_mangrove_tree.mcfunction index ce6fc00..33c92e7 100644 --- a/tests/data/timber_test/test/chop_tall_mangrove_tree.mcfunction +++ b/tests/data/timber_test/test/chop_tall_mangrove_tree.mcfunction @@ -10,8 +10,6 @@ function timber_test:tree {feature:"minecraft:tall_mangrove"} # let the leaves settle before the tree is chopped await delay 1s function timber_test:count_d7 {test:"chop_tall_mangrove_tree"} -function timber_test:count_adjacent {test:"chop_tall_mangrove_tree"} -function timber_test:count_logs {test:"chop_tall_mangrove_tree"} function timber_test:player {name:"tall_mangrove_t",axe:"minecraft:iron_axe"} # mine the lowest log of the whole tree, from a spot next to it (see find_lowest_log and mine_here for why) @@ -28,9 +26,6 @@ await entity @e[type=minecraft:item,dx=39,dy=39,dz=39,nbt={Item:{id:"minecraft:m await delay 2t await not entity @e[type=minecraft:marker,dx=39,dy=39,dz=39] -# the datapack only chops a tree with at least min_leaves_found leaves within 2 blocks of a trunk. Small mangroves often have fewer -# than 5 right next to it, within 2 blocks all of them had enough (measured with 40 trees each on 26.1.2 and 26.3). If one ever has -# fewer, that is a gap in the datapack, the goal is to chop every tree, so such a tree doesn't fail the test but must not be chopped and gives a warning -# a chop from a log can leave parts of the tree standing or chop nothing (it searches upwards and sideways, see report_leftovers): warning, no failure -execute if score #adj_chop_tall_mangrove_tree timber_test >= min_leaves_found timber run function timber_test:report_leftovers {test:"chop_tall_mangrove_tree"} -execute if score #adj_chop_tall_mangrove_tree timber_test < min_leaves_found timber run function timber_test:assert_not_chopped {test:"chop_tall_mangrove_tree"} +# big crown: logs and roots have to be gone, leaves that are left only warn (see assert_no_logs_warn_leaves) +function timber_test:assert_no_logs_warn_leaves {test:"chop_tall_mangrove_tree"} +function timber_test:assert_no_roots diff --git a/tests/data/timber_test/test/not_chopped_four_leaves.mcfunction b/tests/data/timber_test/test/chopped_six_leaves.mcfunction similarity index 53% rename from tests/data/timber_test/test/not_chopped_four_leaves.mcfunction rename to tests/data/timber_test/test/chopped_six_leaves.mcfunction index 3f1e5ce..f989d71 100644 --- a/tests/data/timber_test/test/not_chopped_four_leaves.mcfunction +++ b/tests/data/timber_test/test/chopped_six_leaves.mcfunction @@ -1,22 +1,26 @@ -#> Mining a log with only 4 leaves next to the trunk does not chop it (min_leaves_found is 5) -# @environment timber_test:group_1 +#> Mining a log with exactly 6 leaves next to the trunk chops the whole tree (min_leaves_found is 6) +# @environment timber_test:group_5 # @template timber_test:arena # @timeout 200 # @skyaccess true function timber_test:platform -# the rule is built for the default of 5 leaves -assert score min_leaves_found timber matches 5 -# a column of 6 logs with 4 leaves at its sides +# the rule is built for the default of 6 leaves +assert score min_leaves_found timber matches 6 +# a column of 6 logs with 6 leaves: 4 at its sides, 1 more side one block higher, one on top fill ~20 ~0 ~20 ~20 ~5 ~20 minecraft:oak_log setblock ~21 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=false] setblock ~19 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=false] setblock ~20 ~3 ~21 minecraft:oak_leaves[distance=1,persistent=false] setblock ~20 ~3 ~19 minecraft:oak_leaves[distance=1,persistent=false] +setblock ~21 ~4 ~20 minecraft:oak_leaves[distance=1,persistent=false] +setblock ~20 ~6 ~20 minecraft:oak_leaves[distance=1,persistent=false] -function timber_test:count_logs {test:"not_chopped_four_leaves"} -function timber_test:player {name:"neg4_t",axe:"minecraft:iron_axe"} -function timber_test:mine {name:"neg4_t"} +function timber_test:count_d7 {test:"chopped_six_leaves"} + +function timber_test:count_logs {test:"chopped_six_leaves"} +function timber_test:player {name:"pos6_t",axe:"minecraft:iron_axe"} +function timber_test:mine {name:"pos6_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) @@ -27,4 +31,4 @@ 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:"not_chopped_four_leaves"} +function timber_test:assert_no_tree {test:"chopped_six_leaves"} diff --git a/tests/data/timber_test/test/chopped_five_leaves_within_two_blocks.mcfunction b/tests/data/timber_test/test/chopped_six_leaves_within_two_blocks.mcfunction similarity index 55% rename from tests/data/timber_test/test/chopped_five_leaves_within_two_blocks.mcfunction rename to tests/data/timber_test/test/chopped_six_leaves_within_two_blocks.mcfunction index e536daa..9aba239 100644 --- a/tests/data/timber_test/test/chopped_five_leaves_within_two_blocks.mcfunction +++ b/tests/data/timber_test/test/chopped_six_leaves_within_two_blocks.mcfunction @@ -1,25 +1,27 @@ -#> Mining a log with 3 leaves next to the trunk and 2 more 2 blocks away chops the whole tree (min_leaves_found is 5, leaves within 2 blocks count) +#> Mining a log with 3 leaves next to the trunk and 3 more 2 blocks away chops the whole tree (min_leaves_found is 6, leaves within 2 blocks count) # @environment timber_test:group_5 # @template timber_test:arena # @timeout 200 # @skyaccess true function timber_test:platform -# the rule is built for the default of 5 leaves -assert score min_leaves_found timber matches 5 -# a column of 6 logs with 3 leaves at its sides, and 2 leaves 2 blocks away that hang on 2 of them +# the rule is built for the default of 6 leaves +assert score min_leaves_found timber matches 6 +# a column of 6 logs with 3 leaves at its sides and 3 leaves 2 blocks away: 2 straight out from side leaves, 1 on a diagonal +# between two of them fill ~20 ~0 ~20 ~20 ~5 ~20 minecraft:oak_log setblock ~21 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=false] setblock ~19 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=false] setblock ~20 ~3 ~21 minecraft:oak_leaves[distance=1,persistent=false] setblock ~22 ~3 ~20 minecraft:oak_leaves[distance=2,persistent=false] setblock ~18 ~3 ~20 minecraft:oak_leaves[distance=2,persistent=false] +setblock ~21 ~3 ~21 minecraft:oak_leaves[distance=2,persistent=false] -function timber_test:count_d7 {test:"chopped_five_leaves_within_two_blocks"} +function timber_test:count_d7 {test:"chopped_six_leaves_within_two_blocks"} -function timber_test:count_logs {test:"chopped_five_leaves_within_two_blocks"} -function timber_test:player {name:"pos5w_t",axe:"minecraft:iron_axe"} -function timber_test:mine {name:"pos5w_t"} +function timber_test:count_logs {test:"chopped_six_leaves_within_two_blocks"} +function timber_test:player {name:"pos6w_t",axe:"minecraft:iron_axe"} +function timber_test:mine {name:"pos6w_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) @@ -30,4 +32,4 @@ 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:"chopped_five_leaves_within_two_blocks"} +function timber_test:assert_no_tree {test:"chopped_six_leaves_within_two_blocks"} diff --git a/tests/data/timber_test/test/chopped_five_leaves.mcfunction b/tests/data/timber_test/test/not_chopped_five_leaves.mcfunction similarity index 65% rename from tests/data/timber_test/test/chopped_five_leaves.mcfunction rename to tests/data/timber_test/test/not_chopped_five_leaves.mcfunction index 013ee19..b9b52c4 100644 --- a/tests/data/timber_test/test/chopped_five_leaves.mcfunction +++ b/tests/data/timber_test/test/not_chopped_five_leaves.mcfunction @@ -1,12 +1,12 @@ -#> Mining a log with exactly 5 leaves next to the trunk chops the whole tree (min_leaves_found is 5) -# @environment timber_test:group_5 +#> Mining a log with only 5 leaves next to the trunk does not chop it (min_leaves_found is 6) +# @environment timber_test:group_1 # @template timber_test:arena # @timeout 200 # @skyaccess true function timber_test:platform -# the rule is built for the default of 5 leaves -assert score min_leaves_found timber matches 5 +# the rule is built for the default of 6 leaves +assert score min_leaves_found timber matches 6 # a column of 6 logs with 5 leaves: 4 at its sides, one on top fill ~20 ~0 ~20 ~20 ~5 ~20 minecraft:oak_log setblock ~21 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=false] @@ -15,11 +15,10 @@ setblock ~20 ~3 ~21 minecraft:oak_leaves[distance=1,persistent=false] setblock ~20 ~3 ~19 minecraft:oak_leaves[distance=1,persistent=false] setblock ~20 ~6 ~20 minecraft:oak_leaves[distance=1,persistent=false] -function timber_test:count_d7 {test:"chopped_five_leaves"} -function timber_test:count_logs {test:"chopped_five_leaves"} -function timber_test:player {name:"pos5_t",axe:"minecraft:iron_axe"} -function timber_test:mine {name:"pos5_t"} +function timber_test:count_logs {test:"not_chopped_five_leaves"} +function timber_test:player {name:"neg5_t",axe:"minecraft:iron_axe"} +function timber_test:mine {name:"neg5_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) @@ -30,4 +29,4 @@ 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:"chopped_five_leaves"} +function timber_test:assert_only_mined_gone {test:"not_chopped_five_leaves"} diff --git a/tests/data/timber_test/test/not_chopped_four_leaves_within_two_blocks.mcfunction b/tests/data/timber_test/test/not_chopped_five_leaves_within_two_blocks.mcfunction similarity index 64% rename from tests/data/timber_test/test/not_chopped_four_leaves_within_two_blocks.mcfunction rename to tests/data/timber_test/test/not_chopped_five_leaves_within_two_blocks.mcfunction index 52a4f13..6eb8fd3 100644 --- a/tests/data/timber_test/test/not_chopped_four_leaves_within_two_blocks.mcfunction +++ b/tests/data/timber_test/test/not_chopped_five_leaves_within_two_blocks.mcfunction @@ -1,23 +1,26 @@ -#> Mining a log with 2 leaves next to the trunk, 2 more 2 blocks away and 1 that is 3 blocks away does not chop it (min_leaves_found is 5, only leaves within 2 blocks count) +#> Mining a log with 3 leaves next to the trunk, 2 more 2 blocks away and 1 that is 3 blocks away does not chop it (min_leaves_found is 6, only leaves within 2 blocks count) # @environment timber_test:group_5 # @template timber_test:arena # @timeout 200 # @skyaccess true function timber_test:platform -# the rule is built for the default of 5 leaves -assert score min_leaves_found timber matches 5 -# a column of 6 logs with 2 leaves at its sides, 1 more leaf behind each of them (2 blocks away) and 1 leaf 3 blocks away +# the rule is built for the default of 6 leaves +assert score min_leaves_found timber matches 6 +# a column of 6 logs with 3 leaves at its sides, 2 leaves 2 blocks away straight out from side leaves and 1 leaf 3 blocks away +# behind one of them fill ~20 ~0 ~20 ~20 ~5 ~20 minecraft:oak_log setblock ~21 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=false] -setblock ~22 ~3 ~20 minecraft:oak_leaves[distance=2,persistent=false] -setblock ~23 ~3 ~20 minecraft:oak_leaves[distance=3,persistent=false] setblock ~19 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=false] +setblock ~20 ~3 ~21 minecraft:oak_leaves[distance=1,persistent=false] +setblock ~22 ~3 ~20 minecraft:oak_leaves[distance=2,persistent=false] setblock ~18 ~3 ~20 minecraft:oak_leaves[distance=2,persistent=false] +setblock ~23 ~3 ~20 minecraft:oak_leaves[distance=3,persistent=false] + -function timber_test:count_logs {test:"not_chopped_four_leaves_within_two_blocks"} -function timber_test:player {name:"neg4w_t",axe:"minecraft:iron_axe"} -function timber_test:mine {name:"neg4w_t"} +function timber_test:count_logs {test:"not_chopped_five_leaves_within_two_blocks"} +function timber_test:player {name:"neg5w_t",axe:"minecraft:iron_axe"} +function timber_test:mine {name:"neg5w_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) @@ -28,4 +31,4 @@ 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:"not_chopped_four_leaves_within_two_blocks"} +function timber_test:assert_only_mined_gone {test:"not_chopped_five_leaves_within_two_blocks"} diff --git a/tests/data/timber_test/test/placed_leaves_should_not_count.mcfunction b/tests/data/timber_test/test/placed_leaves_should_not_count.mcfunction index 7022e6f..513997a 100644 --- a/tests/data/timber_test/test/placed_leaves_should_not_count.mcfunction +++ b/tests/data/timber_test/test/placed_leaves_should_not_count.mcfunction @@ -1,19 +1,20 @@ -#> Five leaves placed by a player (persistent) next to a stack of logs: only natural leaves count (setting persistent is off), so it must not be chopped +#> Six leaves placed by a player (persistent) next to a stack of logs: only natural leaves count (setting persistent is off), so it must not be chopped # @environment timber_test:group_4 # @template timber_test:arena # @timeout 200 # @skyaccess true function timber_test:platform -# the rule is built for the default of 5 leaves -assert score min_leaves_found timber matches 5 -# a column of 6 logs with 5 player placed leaves (persistent=true) next to it +# the rule is built for the default of 6 leaves +assert score min_leaves_found timber matches 6 +# a column of 6 logs with 6 player placed leaves (persistent=true) next to it fill ~20 ~0 ~20 ~20 ~5 ~20 minecraft:oak_log setblock ~21 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=true] setblock ~19 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=true] setblock ~20 ~3 ~21 minecraft:oak_leaves[distance=1,persistent=true] setblock ~20 ~3 ~19 minecraft:oak_leaves[distance=1,persistent=true] setblock ~21 ~4 ~20 minecraft:oak_leaves[distance=1,persistent=true] +setblock ~19 ~4 ~20 minecraft:oak_leaves[distance=1,persistent=true] function timber_test:count_logs {test:"placed_leaves_should_not_count"} function timber_test:player {name:"placed_lv_t",axe:"minecraft:iron_axe"} diff --git a/tests/data/timber_test/test/settings_destroy_roots_default_removes_roots.mcfunction b/tests/data/timber_test/test/settings_destroy_roots_default_removes_roots.mcfunction index e355484..d398537 100644 --- a/tests/data/timber_test/test/settings_destroy_roots_default_removes_roots.mcfunction +++ b/tests/data/timber_test/test/settings_destroy_roots_default_removes_roots.mcfunction @@ -5,7 +5,7 @@ # @skyaccess true function timber_test:platform -# a mangrove: 6 logs from height 1 on, 6 roots (three sides at height 1 and below them), 5 natural leaves. The dummy stands on the fourth side +# a mangrove: 6 logs from height 1 on, 6 roots (three sides at height 1 and below them), 6 natural leaves. The dummy stands on the fourth side fill ~20 ~1 ~20 ~20 ~6 ~20 minecraft:mangrove_log setblock ~21 ~1 ~20 minecraft:mangrove_roots setblock ~20 ~1 ~21 minecraft:mangrove_roots @@ -18,6 +18,7 @@ setblock ~19 ~4 ~20 minecraft:mangrove_leaves[distance=1,persistent=false] setblock ~20 ~4 ~21 minecraft:mangrove_leaves[distance=1,persistent=false] setblock ~20 ~4 ~19 minecraft:mangrove_leaves[distance=1,persistent=false] setblock ~20 ~7 ~20 minecraft:mangrove_leaves[distance=1,persistent=false] +setblock ~21 ~5 ~20 minecraft:mangrove_leaves[distance=1,persistent=false] await delay 1s function timber_test:count_d7 {test:"settings_destroy_roots_default_removes_roots"} diff --git a/tests/data/timber_test/test/settings_destroy_roots_off_keeps_roots.mcfunction b/tests/data/timber_test/test/settings_destroy_roots_off_keeps_roots.mcfunction index c02393c..21c39cc 100644 --- a/tests/data/timber_test/test/settings_destroy_roots_off_keeps_roots.mcfunction +++ b/tests/data/timber_test/test/settings_destroy_roots_off_keeps_roots.mcfunction @@ -5,7 +5,7 @@ # @skyaccess true function timber_test:platform -# a mangrove: 6 logs from height 1 on, 6 roots (three sides at height 1 and below them), 5 natural leaves. The dummy stands on the fourth side +# a mangrove: 6 logs from height 1 on, 6 roots (three sides at height 1 and below them), 6 natural leaves. The dummy stands on the fourth side fill ~20 ~1 ~20 ~20 ~6 ~20 minecraft:mangrove_log setblock ~21 ~1 ~20 minecraft:mangrove_roots setblock ~20 ~1 ~21 minecraft:mangrove_roots @@ -18,6 +18,7 @@ setblock ~19 ~4 ~20 minecraft:mangrove_leaves[distance=1,persistent=false] setblock ~20 ~4 ~21 minecraft:mangrove_leaves[distance=1,persistent=false] setblock ~20 ~4 ~19 minecraft:mangrove_leaves[distance=1,persistent=false] setblock ~20 ~7 ~20 minecraft:mangrove_leaves[distance=1,persistent=false] +setblock ~21 ~5 ~20 minecraft:mangrove_leaves[distance=1,persistent=false] await delay 1s function timber_test:count_d7 {test:"settings_destroy_roots_off_keeps_roots"} diff --git a/tests/data/timber_test/test/settings_max_tree_size_caps_the_chop.mcfunction b/tests/data/timber_test/test/settings_max_tree_size_caps_the_chop.mcfunction index 3b9d25f..6c944e3 100644 --- a/tests/data/timber_test/test/settings_max_tree_size_caps_the_chop.mcfunction +++ b/tests/data/timber_test/test/settings_max_tree_size_caps_the_chop.mcfunction @@ -5,13 +5,14 @@ # @skyaccess true function timber_test:platform -# a column of 8 logs with 5 natural leaves at its two lowest logs, so it is a tree for the datapack even if only the first logs are looked at +# a column of 8 logs with 6 natural leaves at its two lowest logs, so it is a tree for the datapack even if only the first logs are looked at fill ~20 ~0 ~20 ~20 ~7 ~20 minecraft:oak_log setblock ~21 ~1 ~20 minecraft:oak_leaves[distance=1,persistent=false] setblock ~19 ~1 ~20 minecraft:oak_leaves[distance=1,persistent=false] setblock ~20 ~1 ~21 minecraft:oak_leaves[distance=1,persistent=false] setblock ~20 ~1 ~19 minecraft:oak_leaves[distance=1,persistent=false] setblock ~21 ~2 ~20 minecraft:oak_leaves[distance=1,persistent=false] +setblock ~21 ~0 ~20 minecraft:oak_leaves[distance=1,persistent=false] await delay 1s function timber_test:count_logs {test:"settings_max_tree_size_caps_the_chop"} diff --git a/tests/data/timber_test/test/settings_persistent_on_counts_placed_leaves.mcfunction b/tests/data/timber_test/test/settings_persistent_on_counts_placed_leaves.mcfunction index 772ab78..9b05eb7 100644 --- a/tests/data/timber_test/test/settings_persistent_on_counts_placed_leaves.mcfunction +++ b/tests/data/timber_test/test/settings_persistent_on_counts_placed_leaves.mcfunction @@ -1,17 +1,18 @@ -#> With persistent on leaves placed by a player count: a log stack with 5 of them is chopped +#> With persistent on leaves placed by a player count: a log stack with 6 of them is chopped # @environment timber_test:persistent_on # @template timber_test:arena # @timeout 200 # @skyaccess true function timber_test:platform -# a column of 6 logs with 5 leaves placed by a player (persistent=true) +# a column of 6 logs with 6 leaves placed by a player (persistent=true) fill ~20 ~0 ~20 ~20 ~5 ~20 minecraft:oak_log setblock ~21 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=true] setblock ~19 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=true] setblock ~20 ~3 ~21 minecraft:oak_leaves[distance=1,persistent=true] setblock ~20 ~3 ~19 minecraft:oak_leaves[distance=1,persistent=true] setblock ~21 ~4 ~20 minecraft:oak_leaves[distance=1,persistent=true] +setblock ~19 ~4 ~20 minecraft:oak_leaves[distance=1,persistent=true] await delay 1s function timber_test:count_d7 {test:"settings_persistent_on_counts_placed_leaves"} diff --git a/tests/data/timber_test/test/settings_slow_chop_ignores_player_placed_leaves.mcfunction b/tests/data/timber_test/test/settings_slow_chop_ignores_player_placed_leaves.mcfunction index f676993..006c1a2 100644 --- a/tests/data/timber_test/test/settings_slow_chop_ignores_player_placed_leaves.mcfunction +++ b/tests/data/timber_test/test/settings_slow_chop_ignores_player_placed_leaves.mcfunction @@ -5,13 +5,14 @@ # @skyaccess true function timber_test:platform -# a column of 6 logs with 5 leaves placed by a player (persistent=true) +# a column of 6 logs with 6 leaves placed by a player (persistent=true) fill ~20 ~0 ~20 ~20 ~5 ~20 minecraft:oak_log setblock ~21 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=true] setblock ~19 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=true] setblock ~20 ~3 ~21 minecraft:oak_leaves[distance=1,persistent=true] setblock ~20 ~3 ~19 minecraft:oak_leaves[distance=1,persistent=true] setblock ~21 ~4 ~20 minecraft:oak_leaves[distance=1,persistent=true] +setblock ~19 ~4 ~20 minecraft:oak_leaves[distance=1,persistent=true] await delay 1s function timber_test:count_logs {test:"settings_slow_chop_ignores_player_placed_leaves"} diff --git a/tests/data/timber_test/test/settings_sneak_only_sneaking_chops.mcfunction b/tests/data/timber_test/test/settings_sneak_only_sneaking_chops.mcfunction index a6667bd..6f1707e 100644 --- a/tests/data/timber_test/test/settings_sneak_only_sneaking_chops.mcfunction +++ b/tests/data/timber_test/test/settings_sneak_only_sneaking_chops.mcfunction @@ -5,13 +5,14 @@ # @skyaccess true function timber_test:platform -# a column of 6 logs with 5 natural leaves from height 3 on (a random oak can have leaves right above the dummy, then it can't duck) +# a column of 6 logs with 6 natural leaves from height 3 on (a random oak can have leaves right above the dummy, then it can't duck) fill ~20 ~0 ~20 ~20 ~5 ~20 minecraft:oak_log setblock ~21 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=false] setblock ~19 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=false] setblock ~20 ~3 ~21 minecraft:oak_leaves[distance=1,persistent=false] setblock ~20 ~3 ~19 minecraft:oak_leaves[distance=1,persistent=false] setblock ~21 ~4 ~20 minecraft:oak_leaves[distance=1,persistent=false] +setblock ~19 ~4 ~20 minecraft:oak_leaves[distance=1,persistent=false] await delay 1s function timber_test:count_d7 {test:"settings_sneak_only_sneaking_chops"} diff --git a/tests/data/timber_test/test/settings_sneaking_default_no_chop.mcfunction b/tests/data/timber_test/test/settings_sneaking_default_no_chop.mcfunction index 77bd6bc..c7ec26c 100644 --- a/tests/data/timber_test/test/settings_sneaking_default_no_chop.mcfunction +++ b/tests/data/timber_test/test/settings_sneaking_default_no_chop.mcfunction @@ -5,13 +5,14 @@ # @skyaccess true function timber_test:platform -# a column of 6 logs with 5 natural leaves from height 3 on (a random oak can have leaves right above the dummy, then it can't duck) +# a column of 6 logs with 6 natural leaves from height 3 on (a random oak can have leaves right above the dummy, then it can't duck) fill ~20 ~0 ~20 ~20 ~5 ~20 minecraft:oak_log setblock ~21 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=false] setblock ~19 ~3 ~20 minecraft:oak_leaves[distance=1,persistent=false] setblock ~20 ~3 ~21 minecraft:oak_leaves[distance=1,persistent=false] setblock ~20 ~3 ~19 minecraft:oak_leaves[distance=1,persistent=false] setblock ~21 ~4 ~20 minecraft:oak_leaves[distance=1,persistent=false] +setblock ~19 ~4 ~20 minecraft:oak_leaves[distance=1,persistent=false] await delay 1s function timber_test:count_logs {test:"settings_sneaking_default_no_chop"} diff --git a/tests/data/timber_test/test/settings_start_chop_on_root_default_chops.mcfunction b/tests/data/timber_test/test/settings_start_chop_on_root_default_chops.mcfunction index 4847b40..7dbfd2e 100644 --- a/tests/data/timber_test/test/settings_start_chop_on_root_default_chops.mcfunction +++ b/tests/data/timber_test/test/settings_start_chop_on_root_default_chops.mcfunction @@ -5,7 +5,7 @@ # @skyaccess true function timber_test:platform -# a mangrove: 6 logs from height 1 on, 6 roots (three sides at height 1 and below them), 5 natural leaves. The dummy stands on the fourth side +# a mangrove: 6 logs from height 1 on, 6 roots (three sides at height 1 and below them), 6 natural leaves. The dummy stands on the fourth side fill ~20 ~1 ~20 ~20 ~6 ~20 minecraft:mangrove_log setblock ~21 ~1 ~20 minecraft:mangrove_roots setblock ~20 ~1 ~21 minecraft:mangrove_roots @@ -18,6 +18,7 @@ setblock ~19 ~4 ~20 minecraft:mangrove_leaves[distance=1,persistent=false] setblock ~20 ~4 ~21 minecraft:mangrove_leaves[distance=1,persistent=false] setblock ~20 ~4 ~19 minecraft:mangrove_leaves[distance=1,persistent=false] setblock ~20 ~7 ~20 minecraft:mangrove_leaves[distance=1,persistent=false] +setblock ~21 ~5 ~20 minecraft:mangrove_leaves[distance=1,persistent=false] await delay 1s function timber_test:count_d7 {test:"settings_start_chop_on_root_default_chops"} diff --git a/tests/data/timber_test/test/settings_start_chop_on_root_off_no_chop.mcfunction b/tests/data/timber_test/test/settings_start_chop_on_root_off_no_chop.mcfunction index 689cb44..3cccac5 100644 --- a/tests/data/timber_test/test/settings_start_chop_on_root_off_no_chop.mcfunction +++ b/tests/data/timber_test/test/settings_start_chop_on_root_off_no_chop.mcfunction @@ -5,7 +5,7 @@ # @skyaccess true function timber_test:platform -# a mangrove: 6 logs from height 1 on, 6 roots (three sides at height 1 and below them), 5 natural leaves. The dummy stands on the fourth side +# a mangrove: 6 logs from height 1 on, 6 roots (three sides at height 1 and below them), 6 natural leaves. The dummy stands on the fourth side fill ~20 ~1 ~20 ~20 ~6 ~20 minecraft:mangrove_log setblock ~21 ~1 ~20 minecraft:mangrove_roots setblock ~20 ~1 ~21 minecraft:mangrove_roots @@ -18,6 +18,7 @@ setblock ~19 ~4 ~20 minecraft:mangrove_leaves[distance=1,persistent=false] setblock ~20 ~4 ~21 minecraft:mangrove_leaves[distance=1,persistent=false] setblock ~20 ~4 ~19 minecraft:mangrove_leaves[distance=1,persistent=false] setblock ~20 ~7 ~20 minecraft:mangrove_leaves[distance=1,persistent=false] +setblock ~21 ~5 ~20 minecraft:mangrove_leaves[distance=1,persistent=false] await delay 1s function timber_test:count_d7 {test:"settings_start_chop_on_root_off_no_chop"} From 7fa361508a6b18837fd1490b3f0cb67189b20143 Mon Sep 17 00:00:00 2001 From: Flavio Felder Date: Wed, 23 Sep 2026 21:49:29 +0200 Subject: [PATCH 6/7] Let the Min. Leaves slider go up to 30 Co-Authored-By: Claude Opus 5.5 --- data/timber/function/settings/open/leaves.mcfunction | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/timber/function/settings/open/leaves.mcfunction b/data/timber/function/settings/open/leaves.mcfunction index d72592d..be4c1ef 100644 --- a/data/timber/function/settings/open/leaves.mcfunction +++ b/data/timber/function/settings/open/leaves.mcfunction @@ -1,6 +1,6 @@ # opens the "Min. Leaves" slider with the current value # only reachable for admins, so the trigger gets enabled only for them scoreboard players enable @s TimberSetLeaves -data modify storage timber:settings dialog set value {"type":"minecraft:multi_action","title":"Timber Settings / Min. Leaves","external_title":"Timber Min. Leaves","body":{"type":"minecraft:plain_message","contents":["",{"text":"This datapack considers a tree only if it has at least this many naturally generated leaves within 2 blocks of its trunk."},"\n\n",{"text":"Default: 6","color":"gray"}],"width":300},"inputs":[{"type":"minecraft:number_range","key":"min_leaves_found","width":300,"label":["",{"text":"Set Min. Leaves to Valid a Tree","hover_event":{"action":"show_text","value":["",{"text":"Default: 6"},{"text":"\n\nThis datapack considers a tree only if it has at least this many naturally generated leaves within 2 blocks of its trunk."}]}}],"start":0,"end":10,"step":1,"initial":0}],"can_close_with_escape":true,"pause":false,"after_action":"none","columns":1,"actions":[{"label":"Save","width":300,"action":{"type":"minecraft:dynamic/run_command","template":"trigger TimberSetLeaves set 1$(min_leaves_found)"}}],"exit_action":{"label":{"translate":"gui.back"},"width":300,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 1"}}} +data modify storage timber:settings dialog set value {"type":"minecraft:multi_action","title":"Timber Settings / Min. Leaves","external_title":"Timber Min. Leaves","body":{"type":"minecraft:plain_message","contents":["",{"text":"This datapack considers a tree only if it has at least this many naturally generated leaves within 2 blocks of its trunk."},"\n\n",{"text":"Default: 6","color":"gray"}],"width":300},"inputs":[{"type":"minecraft:number_range","key":"min_leaves_found","width":300,"label":["",{"text":"Set Min. Leaves to Valid a Tree","hover_event":{"action":"show_text","value":["",{"text":"Default: 6"},{"text":"\n\nThis datapack considers a tree only if it has at least this many naturally generated leaves within 2 blocks of its trunk."}]}}],"start":0,"end":30,"step":1,"initial":0}],"can_close_with_escape":true,"pause":false,"after_action":"none","columns":1,"actions":[{"label":"Save","width":300,"action":{"type":"minecraft:dynamic/run_command","template":"trigger TimberSetLeaves set 1$(min_leaves_found)"}}],"exit_action":{"label":{"translate":"gui.back"},"width":300,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 1"}}} execute store result storage timber:settings dialog.inputs[0].initial float 1 run scoreboard players get min_leaves_found timber function timber:settings/show with storage timber:settings From 2823ec3137a135ded1b97e52e2e2934644bdf8d4 Mon Sep 17 00:00:00 2001 From: Flavio Felder Date: Wed, 23 Sep 2026 22:00:03 +0200 Subject: [PATCH 7/7] Download the vanilla server jar in the CI setup gen-sapling-ground-tests.py runs before the first server start, when Fabric hasn't downloaded it yet Co-Authored-By: Claude Opus 5.5 --- .github/scripts/setup-test-server.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/scripts/setup-test-server.sh b/.github/scripts/setup-test-server.sh index 1365bad..78d9c5a 100644 --- a/.github/scripts/setup-test-server.sh +++ b/.github/scripts/setup-test-server.sh @@ -20,9 +20,14 @@ LOADER=$(curl -fsSL https://meta.fabricmc.net/v2/versions/loader | jq -r '[.[] | INSTALLER=$(curl -fsSL https://meta.fabricmc.net/v2/versions/installer | jq -r '[.[] | select(.stable)][0].version') # the meta api answers unknown versions with an error or an empty list SERVER=$(curl -sSL "https://meta.fabricmc.net/v2/versions/loader/$MC" | jq -r 'if type == "array" and length > 0 then "yes" else empty end' 2>/dev/null || true) +# the vanilla server jar: the Fabric server only downloads it on its first start, gen-sapling-ground-tests.py needs it before that +VANILLA_META=$(curl -fsSL https://piston-meta.mojang.com/mc/game/version_manifest_v2.json | jq -r --arg v "$MC" '.versions[] | select(.id == $v) | .url') +VANILLA="" +[ -z "$VANILLA_META" ] || VANILLA=$(curl -fsSL "$VANILLA_META" | jq -r '.downloads.server.url // empty') MISSING="" [ -n "$SERVER" ] || MISSING="$MISSING Fabric server," +[ -n "$VANILLA" ] || MISSING="$MISSING vanilla server," [ -n "$FABRIC_API" ] || MISSING="$MISSING Fabric API," [ -n "$PACKTEST" ] || MISSING="$MISSING PackTest," if [ -n "$MISSING" ]; then @@ -30,7 +35,9 @@ if [ -n "$MISSING" ]; then exit 3 fi -mkdir -p mods +mkdir -p mods "versions/$MC" +# where the Fabric server puts it too (it replaces it with its own copy on the first start, gen-sapling-ground-tests.py reads both) +curl -fsSL -o "versions/$MC/server-$MC.jar" "$VANILLA" curl -fsSL -o server.jar "https://meta.fabricmc.net/v2/versions/loader/$MC/$LOADER/$INSTALLER/server/jar" curl -fsSL -o mods/fabric-api.jar "$FABRIC_API" curl -fsSL -o mods/packtest.jar "$PACKTEST"