From e7a5a14529e8706a31abb13994b14771fe82c4a9 Mon Sep 17 00:00:00 2001 From: Dylan Garvis Date: Tue, 11 Aug 2026 18:27:21 -0400 Subject: [PATCH] fix(progress): count tree blocks broken with any tool --- design/log.md | 7 +++++ .../us-001-earn-tree-type-unlocks.md | 4 +-- .../dmg/treefeller/TreeProgressListener.java | 1 - .../treefeller/TreeFellingListenerTest.java | 28 +++++++++++++++++++ .../treefeller/TreeProgressListenerTest.java | 7 +++-- 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/design/log.md b/design/log.md index ff88a3c..4e92e86 100644 --- a/design/log.md +++ b/design/log.md @@ -2,6 +2,13 @@ ## 2026-08-11 +### US-001 tool-independent progress corrected + +- Reproduced the progression gap caused by sharing the automatic-felling axe restriction with manual unlock progress. +- Qualifying validated Survival tree blocks now add progress when broken with an axe, another tool, or an empty hand; cancelled, Creative, and automatically felled blocks remain excluded. +- Automatic felling remains strictly axe-only, including for players who have already unlocked the species. +- Verified empty-hand and pickaxe progress, automatic-break suppression, axe-only felling, and the complete build with `./gradlew clean check jar`. + ### US-010 offline operator identity corrected - Reproduced WindMagi's missing operator privileges and found that name-based image setup stored the online account UUID while offline-mode login assigned deterministic UUID `6a3b6e9f-1a2f-380c-8a75-7a7ca6392c0e`. diff --git a/design/user-stories/us-001-earn-tree-type-unlocks.md b/design/user-stories/us-001-earn-tree-type-unlocks.md index 6401596..38d2bd7 100644 --- a/design/user-stories/us-001-earn-tree-type-unlocks.md +++ b/design/user-stories/us-001-earn-tree-type-unlocks.md @@ -13,8 +13,8 @@ As a **survival player**, I want to unlock Tree Feller by practicing with each t - [x] Progress is tracked independently for oak, spruce, birch, jungle, acacia, dark oak, mangrove, cherry, pale oak, crimson fungi, warped fungi, giant red mushrooms, and giant brown mushrooms as represented by Spigot 26.2 materials. - [x] Azalea-grown oak logs contribute to oak progress, and bamboo does not contribute to any tree species. -- [x] A block contributes progress only when a player manually breaks it in Survival mode, with an axe, from a structure that passes Tree Feller's tree validation. -- [x] Creative-mode breaks, non-axe breaks, cancelled breaks, and blocks removed by automatic felling do not contribute progress. +- [x] A block contributes progress when a player manually breaks it in Survival mode from a structure that passes Tree Feller's tree validation, regardless of whether the player uses an axe, another tool, or an empty hand. +- [x] Creative-mode breaks, cancelled breaks, and blocks removed by automatic felling do not contribute progress. - [x] Each qualifying manually mined block contributes exactly one point to its species. - [x] Each species has an independently configurable unlock threshold that defaults to 100 blocks. - [x] Reaching the active threshold permanently unlocks automatic felling for that species. diff --git a/src/main/java/games/dmg/treefeller/TreeProgressListener.java b/src/main/java/games/dmg/treefeller/TreeProgressListener.java index 705714a..bda1176 100644 --- a/src/main/java/games/dmg/treefeller/TreeProgressListener.java +++ b/src/main/java/games/dmg/treefeller/TreeProgressListener.java @@ -40,7 +40,6 @@ public final class TreeProgressListener implements Listener { Player player = event.getPlayer(); if (event.isCancelled() || player.getGameMode() != GameMode.SURVIVAL - || !TreeTools.isAxe(player.getInventory().getItemInMainHand().getType()) || automaticBreaks.isMarked(event.getBlock())) { return; } diff --git a/src/test/java/games/dmg/treefeller/TreeFellingListenerTest.java b/src/test/java/games/dmg/treefeller/TreeFellingListenerTest.java index ab912d4..29cbfa9 100644 --- a/src/test/java/games/dmg/treefeller/TreeFellingListenerTest.java +++ b/src/test/java/games/dmg/treefeller/TreeFellingListenerTest.java @@ -54,6 +54,34 @@ class TreeFellingListenerTest { verify(starter, org.mockito.Mockito.times(1)).start(any(), any(), any(), eq(2)); } + @Test + void aNonAxeCanEarnProgressButNeverStartsAutomaticFelling() { + UUID playerId = UUID.randomUUID(); + Player player = mock(Player.class); + PlayerInventory inventory = mock(PlayerInventory.class); + when(player.getUniqueId()).thenReturn(playerId); + when(player.getName()).thenReturn("Player"); + when(player.getGameMode()).thenReturn(GameMode.SURVIVAL); + when(player.getInventory()).thenReturn(inventory); + when(inventory.getItemInMainHand()).thenReturn(new ItemStack(Material.DIAMOND_PICKAXE)); + PlayerStateStore states = mock(PlayerStateStore.class); + when(states.load(playerId)).thenReturn(Optional.of( + PlayerTreeFellerState.initial(playerId, "Player") + .withUnlocked(TreeSpecies.OAK, true))); + TreeFellingStarter starter = mock(TreeFellingStarter.class); + TreeFellingListener listener = new TreeFellingListener( + ignored -> Optional.of(new TreeStructure( + TreeSpecies.OAK, List.of(new BlockPoint(0, 0, 0)))), + states, + new AutomaticBreakRegistry(), + starter, + () -> 2); + + listener.onBlockBreak(new BlockBreakEvent(mock(Block.class), player)); + + verify(starter, never()).start(any(), any(), any(), any(Integer.class)); + } + @Test void administrativeLockSuppressesFellingWithoutChangingTheOrdinaryBreak() { UUID playerId = UUID.randomUUID(); diff --git a/src/test/java/games/dmg/treefeller/TreeProgressListenerTest.java b/src/test/java/games/dmg/treefeller/TreeProgressListenerTest.java index d49888b..d6c2d53 100644 --- a/src/test/java/games/dmg/treefeller/TreeProgressListenerTest.java +++ b/src/test/java/games/dmg/treefeller/TreeProgressListenerTest.java @@ -19,7 +19,7 @@ import org.junit.jupiter.api.Test; class TreeProgressListenerTest { @Test - void recordsOnlyManualSurvivalAxeBreaksFromValidatedTrees() throws Exception { + void recordsManualSurvivalBreaksWithAnEmptyHandOrAnyToolFromValidatedTrees() throws Exception { UUID playerId = UUID.randomUUID(); Player player = mock(Player.class); PlayerInventory inventory = mock(PlayerInventory.class); @@ -50,9 +50,12 @@ class TreeProgressListenerTest { automaticBreaks.mark(block); listener.onBlockBreak(event); automaticBreaks.unmark(block); + when(inventory.getItemInMainHand()).thenReturn(new ItemStack(Material.AIR)); + listener.onBlockBreak(event); + when(inventory.getItemInMainHand()).thenReturn(new ItemStack(Material.DIAMOND_PICKAXE)); listener.onBlockBreak(event); - assertEquals(1, states.state.progress(TreeSpecies.OAK)); + assertEquals(2, states.state.progress(TreeSpecies.OAK)); } private static final class InMemoryStateStore implements PlayerStateStore {