diff --git a/README.md b/README.md index e12f384..c4074c0 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ Administrative commands require `treefeller.admin`, granted to server operators - Crimson and warped fungi - Giant red and brown mushrooms -Azalea-grown logs count as oak. Bamboo is not treated as a tree. +Azalea-grown logs count as oak. Unlock progress counts unstripped natural log and Nether stem materials even after the surrounding tree has been disrupted; wood, hyphae, stripped variants, and bamboo do not count. Player-placed qualifying materials are indistinguishable from generated materials in Spigot and therefore also count. Giant mushroom stems require enough cap context to identify their species. ## Configuration and state diff --git a/design/log.md b/design/log.md index 4e92e86..c8e8dfc 100644 --- a/design/log.md +++ b/design/log.md @@ -2,6 +2,14 @@ ## 2026-08-11 +### US-001 disrupted-tree progress corrected + +- Reproduced remaining lower logs failing to count after an upper trunk block was removed because progression unnecessarily re-ran full intact-tree validation. +- Progress now classifies unstripped natural `*_LOG`, crimson stem, and warped stem materials directly, so each qualifying block counts even after the tree structure is disrupted. +- Wood, hyphae, stripped variants, and bamboo remain excluded; player-placed qualifying materials count because Spigot does not expose generation provenance. +- Giant mushroom stems retain cap-based validation because their shared stem material cannot distinguish red from brown, while automatic felling still requires a validated intact tree and an axe. +- Verified disrupted-tree progress, excluded processed materials, tool-independent progress, axe-only felling, and the complete build with `./gradlew clean check jar`. + ### US-001 tool-independent progress corrected - Reproduced the progression gap caused by sharing the automatic-felling axe restriction with manual unlock progress. 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 38d2bd7..aead755 100644 --- a/design/user-stories/us-001-earn-tree-type-unlocks.md +++ b/design/user-stories/us-001-earn-tree-type-unlocks.md @@ -13,7 +13,11 @@ 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 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] A manually broken unstripped natural trunk material with an unambiguous species contributes Survival progress even when the remaining structure no longer passes full tree validation. +- [x] Natural trunk materials include the supported `*_LOG`, `CRIMSON_STEM`, and `WARPED_STEM` blocks; wood, hyphae, stripped variants, and bamboo do not contribute. +- [x] Giant mushroom stems contribute only when surrounding cap context allows Tree Feller to distinguish red from brown. +- [x] Player-placed blocks using an otherwise qualifying natural trunk material contribute because Spigot does not expose reliable generation provenance. +- [x] A qualifying block contributes 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. diff --git a/src/main/java/games/dmg/treefeller/TreeProgressListener.java b/src/main/java/games/dmg/treefeller/TreeProgressListener.java index bda1176..869160e 100644 --- a/src/main/java/games/dmg/treefeller/TreeProgressListener.java +++ b/src/main/java/games/dmg/treefeller/TreeProgressListener.java @@ -43,12 +43,16 @@ public final class TreeProgressListener implements Listener { || automaticBreaks.isMarked(event.getBlock())) { return; } - Optional detected = detector.detect(event.getBlock()); - if (detected.isEmpty()) { + Optional classified = TreeTaxonomy.directSpecies(event.getBlock().getType()); + if (classified.isEmpty() + && event.getBlock().getType() == org.bukkit.Material.MUSHROOM_STEM) { + classified = detector.detect(event.getBlock()).map(TreeStructure::species); + } + if (classified.isEmpty()) { return; } - TreeSpecies species = detected.orElseThrow().species(); + TreeSpecies species = classified.orElseThrow(); PlayerTreeFellerState state = states.load(player.getUniqueId()) .orElseGet(() -> PlayerTreeFellerState.initial( player.getUniqueId(), player.getName())); diff --git a/src/test/java/games/dmg/treefeller/TreeProgressListenerTest.java b/src/test/java/games/dmg/treefeller/TreeProgressListenerTest.java index d6c2d53..f66f152 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 recordsManualSurvivalBreaksWithAnEmptyHandOrAnyToolFromValidatedTrees() throws Exception { + void recordsNaturalTrunkMaterialAfterTheTreeStructureIsNoLongerIntact() throws Exception { UUID playerId = UUID.randomUUID(); Player player = mock(Player.class); PlayerInventory inventory = mock(PlayerInventory.class); @@ -31,15 +31,15 @@ class TreeProgressListenerTest { when(player.getInventory()).thenReturn(inventory); when(inventory.getItemInMainHand()).thenReturn(new ItemStack(Material.IRON_AXE)); when(block.getWorld()).thenReturn(world); + when(block.getType()).thenReturn(Material.OAK_LOG); when(world.getUID()).thenReturn(UUID.randomUUID()); when(block.getX()).thenReturn(1); when(block.getY()).thenReturn(64); when(block.getZ()).thenReturn(2); - TreeStructure tree = new TreeStructure(TreeSpecies.OAK, List.of(new BlockPoint(0, 0, 0))); InMemoryStateStore states = new InMemoryStateStore(); AutomaticBreakRegistry automaticBreaks = new AutomaticBreakRegistry(); TreeProgressListener listener = new TreeProgressListener( - ignored -> Optional.of(tree), + ignored -> Optional.empty(), states, ignored -> 100, automaticBreaks, diff --git a/src/test/java/games/dmg/treefeller/TreeTaxonomyTest.java b/src/test/java/games/dmg/treefeller/TreeTaxonomyTest.java index 2b48b0d..dc5fcca 100644 --- a/src/test/java/games/dmg/treefeller/TreeTaxonomyTest.java +++ b/src/test/java/games/dmg/treefeller/TreeTaxonomyTest.java @@ -26,5 +26,8 @@ class TreeTaxonomyTest { expected.forEach((material, species) -> assertEquals(species, TreeTaxonomy.directSpecies(material).orElseThrow())); assertTrue(TreeTaxonomy.directSpecies(Material.BAMBOO).isEmpty()); + assertTrue(TreeTaxonomy.directSpecies(Material.OAK_WOOD).isEmpty()); + assertTrue(TreeTaxonomy.directSpecies(Material.STRIPPED_OAK_LOG).isEmpty()); + assertTrue(TreeTaxonomy.directSpecies(Material.CRIMSON_HYPHAE).isEmpty()); } }