From f0ed1a995b97414497a90f9cfa6215ad50f22b9d Mon Sep 17 00:00:00 2001 From: av Date: Tue, 12 May 2026 20:03:38 +0300 Subject: [PATCH 1/5] improved: extremum blocks optimized from O(n) to O(1) for retrieval --- .../fallingtree/common/tree/Tree.java | 79 ++++++++++++++++--- 1 file changed, 70 insertions(+), 9 deletions(-) diff --git a/common/src/main/java/fr/rakambda/fallingtree/common/tree/Tree.java b/common/src/main/java/fr/rakambda/fallingtree/common/tree/Tree.java index 7cc324c2..682067e3 100644 --- a/common/src/main/java/fr/rakambda/fallingtree/common/tree/Tree.java +++ b/common/src/main/java/fr/rakambda/fallingtree/common/tree/Tree.java @@ -5,6 +5,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; @@ -27,6 +28,7 @@ public class Tree{ @Getter private final Set parts = new LinkedHashSet<>(); private final Map partCounts = new LinkedHashMap<>(); + private final ExtremumBlocks extremumBlocks = new ExtremumBlocks(); public void addPart(@NonNull TreePart treePart){ parts.add(treePart); @@ -36,6 +38,7 @@ public void addPart(@NonNull TreePart treePart){ } return value + 1; }); + extremumBlocks.update(treePart); } public void removePartsHigherThan(int y, @NonNull TreePartType partType){ @@ -99,18 +102,78 @@ public int getLogCount(){ return getPartCount(TreePartType.LOG); } + private static class ExtremumBlocks { + + @Nullable + private TreePart topMostLog; + + @Nullable + private TreePart bottomMostLog; + + @Nullable + private TreePart logStart; + + private void updateTopMostLog(@NonNull TreePart candidate){ + if(this.topMostLog == null){ + this.topMostLog = candidate; + return; + } + + if(candidate.treePartType().isBreakable() + && candidate.treePartType().isLog() + && candidate.blockPos().getY() > this.topMostLog.blockPos().getY()){ + this.topMostLog = candidate; + } + } + + private void updateBottomMostLog(@NonNull TreePart candidate){ + if(this.bottomMostLog == null){ + this.bottomMostLog = candidate; + return; + } + + if(candidate.treePartType().isBreakable() + && candidate.treePartType().isLog() + && candidate.blockPos().getY() < this.bottomMostLog.blockPos().getY()){ + this.bottomMostLog = candidate; + } + } + + private void updateLogStart(@NonNull TreePart candidate) { + if(this.logStart == null && candidate.treePartType() == TreePartType.LOG_START){ + this.logStart = candidate; + } + } + + protected void update(@NonNull TreePart candidate){ + updateLogStart(candidate); + updateTopMostLog(candidate); + updateBottomMostLog(candidate); + } + + public Optional getTopMostLog(){ + return Optional.ofNullable(topMostLog); + } + + public Optional getBottomMostLog(){ + return Optional.ofNullable(bottomMostLog); + } + + public Optional getLogStart(){ + return Optional.ofNullable(logStart); + } + } + @NonNull public Optional getTopMostLog(){ - return getBreakableLogs().stream() - .map(TreePart::blockPos) - .max(comparingInt(IBlockPos::getY)); + return extremumBlocks.getTopMostLog() + .map(TreePart::blockPos); } @NonNull public Optional getBottomMostLog(){ - return getBreakableLogs().stream() - .map(TreePart::blockPos) - .min(comparingInt(IBlockPos::getY)); + return extremumBlocks.getBottomMostLog() + .map(TreePart::blockPos); } @NonNull @@ -136,8 +199,6 @@ public Collection getMangroveRoots(){ @NonNull public Optional getStart(){ - return getParts().stream() - .filter(part -> part.treePartType() == TreePartType.LOG_START) - .findFirst(); + return extremumBlocks.getLogStart(); } } From 29b9c6eb22f0bec7be008f69867163b44abf7ce6 Mon Sep 17 00:00:00 2001 From: av Date: Tue, 12 May 2026 20:05:17 +0300 Subject: [PATCH 2/5] improved: tree handler state now shared for one event to build tree only once --- .../fallingtree/common/tree/TreeHandler.java | 207 +++++++++++------- .../fabric/event/BlockBreakListener.java | 4 +- .../forge/event/BlockBreakListener.java | 8 +- .../neoforge/event/BlockBreakListener.java | 9 +- 4 files changed, 136 insertions(+), 92 deletions(-) diff --git a/common/src/main/java/fr/rakambda/fallingtree/common/tree/TreeHandler.java b/common/src/main/java/fr/rakambda/fallingtree/common/tree/TreeHandler.java index 073f1a97..5276e73d 100644 --- a/common/src/main/java/fr/rakambda/fallingtree/common/tree/TreeHandler.java +++ b/common/src/main/java/fr/rakambda/fallingtree/common/tree/TreeHandler.java @@ -34,67 +34,143 @@ public class TreeHandler{ private final FallingTreeCommon mod; private final Map speedCache = new ConcurrentHashMap<>(); - public boolean shouldCancelEvent(@NonNull ILevel level, @NonNull IPlayer player, @NonNull IBlockPos originPos, @NonNull IBlockState originState, @Nullable IBlockEntity originEntity){ - if(!mod.isPlayerInRightState(player)){ + public class TreeHandlerState{ + @NonNull + private final IPlayer player; + @NonNull + private final ILevel level; + @NonNull + private final IBlockPos originPos; + @NonNull + private final IBlockState originState; + @Nullable + private final IBlockEntity originEntity; + + @Nullable + private Tree treeCache = null; + + public TreeHandlerState(@NonNull IPlayer player, @NonNull ILevel level, @NonNull IBlockPos originPos, @NonNull IBlockState originState, @Nullable IBlockEntity originEntity){ + this.player = player; + this.level = level; + this.originPos = originPos; + this.originState = originState; + this.originEntity = originEntity; + } + + public Tree getTree() throws TreeTooBigException{ + if(this.treeCache == null){ + this.treeCache = mod.getTreeBuilder().getTree(player, level, originPos, originState, originEntity).orElse(null); + } + + return this.treeCache; + } + + public boolean shouldCancelEvent(){ + if(!mod.isPlayerInRightState(player)){ + return false; + } + if(shouldPreserveTool(player)){ + return true; + } + try{ + this.getTree(); + } + catch(TreeTooBigException e){ + return false; + } return false; } - if(shouldPreserveTool(player)){ - return true; + + @NonNull + public IBreakAttemptResult breakTree(boolean isCancellable){ + if(!level.isServer()){ + return AbortedResult.NOT_SERVER; + } + if(!mod.getConfiguration().getTrees().isTreeBreaking()){ + return AbortedResult.NOT_ENABLED; + } + + if(!mod.checkForceToolUsage(player, level, originPos)){ + mod.notifyPlayer(player, mod.translate("chat.fallingtree.force_tool_usage", mod.getConfiguration().getTrees().getMaxScanSize())); + return AbortedResult.REQUIRED_TOOL_ABSENT; + } + + if(!mod.isPlayerInRightState(player)){ + return AbortedResult.INVALID_PLAYER_STATE; + } + + try{ + var tree = this.getTree(); + if(tree == null){ + return AbortedResult.NO_SUCH_TREE; + } + + var breakMode = getBreakMode(player.getMainHandItem()); + return getBreakingHandler(breakMode).breakTree(isCancellable, player, tree); + } + catch(TreeTooBigException e){ + mod.notifyPlayer(player, mod.translate("chat.fallingtree.tree_too_big", mod.getConfiguration().getTrees().getMaxScanSize())); + return AbortedResult.TREE_TOO_BIG_SCAN; + } + catch(BreakTreeTooSmallException e){ + // mod.notifyPlayer(player, mod.translate("chat.fallingtree.break_tree_too_small", mod.getConfiguration().getTrees().getMinSize())); + return AbortedResult.TREE_TOO_SMALL_BREAK; + } + catch(BreakTreeTooBigException e){ + mod.notifyPlayer(player, mod.translate("chat.fallingtree.break_tree_too_big", mod.getConfiguration().getTrees().getMaxSize())); + return AbortedResult.TREE_TOO_BIG_BREAK; + } } - try{ - mod.getTreeBuilder().getTree(player, level, originPos, originState, originEntity).isEmpty(); + + @NonNull + public Optional getBreakSpeed(float originalSpeed){ + if(!mod.getConfiguration().getTrees().isTreeBreaking()){ + return Optional.empty(); + } + if(!getBreakMode(player.getMainHandItem()).isApplySpeedMultiplier()){ + return Optional.empty(); + } + if(!mod.isPlayerInRightState(player)){ + return Optional.empty(); + } + + var cacheSpeed = speedCache.compute(player.getUUID(), (uuid, speed) -> { + if(isNull(speed) || !speed.isValid(originPos)){ + speed = getSpeed(originalSpeed); + } + return speed; + }); + return Optional.ofNullable(cacheSpeed).map(CacheSpeed::getSpeed); } - catch(TreeTooBigException e){ - return false; + + @Nullable + private CacheSpeed getSpeed(float originalSpeed){ + var speedMultiplicand = mod.getConfiguration().getTools().getSpeedMultiplicand(); + try{ + return speedMultiplicand <= 0 ? null : + Optional.ofNullable(this.getTree()) + .map(tree -> new CacheSpeed(originPos, originalSpeed / ((float) speedMultiplicand * tree.getLogCount()))) + .orElse(null); + } + catch(TreeTooBigException e){ + return null; + } } - return false; } private boolean shouldPreserveTool(@NonNull IPlayer player){ var handItem = player.getMainHandItem(); return mod.getConfiguration().getTools().getDurabilityMode().shouldPreserve(handItem.getDurability()); } + @NonNull + public TreeHandlerState create(@NonNull ILevel level, @NonNull IPlayer player, @NonNull IBlockPos originPos, @NonNull IBlockState originState) { + return create(level, player, originPos, originState, null); + } + @NonNull - public IBreakAttemptResult breakTree(boolean isCancellable, @NonNull ILevel level, @NonNull IPlayer player, @NonNull IBlockPos originPos, @NonNull IBlockState originState, @Nullable IBlockEntity originEntity){ - if(!level.isServer()){ - return AbortedResult.NOT_SERVER; - } - if(!mod.getConfiguration().getTrees().isTreeBreaking()){ - return AbortedResult.NOT_ENABLED; - } - - if(!mod.checkForceToolUsage(player, level, originPos)){ - mod.notifyPlayer(player, mod.translate("chat.fallingtree.force_tool_usage", mod.getConfiguration().getTrees().getMaxScanSize())); - return AbortedResult.REQUIRED_TOOL_ABSENT; - } - - if(!mod.isPlayerInRightState(player)){ - return AbortedResult.INVALID_PLAYER_STATE; - } - - try{ - var treeOptional = mod.getTreeBuilder().getTree(player, level, originPos, originState, originEntity); - if(treeOptional.isEmpty()){ - return AbortedResult.NO_SUCH_TREE; - } - - var tree = treeOptional.get(); - var breakMode = getBreakMode(player.getMainHandItem()); - return getBreakingHandler(breakMode).breakTree(isCancellable, player, tree); - } - catch(TreeTooBigException e){ - mod.notifyPlayer(player, mod.translate("chat.fallingtree.tree_too_big", mod.getConfiguration().getTrees().getMaxScanSize())); - return AbortedResult.TREE_TOO_BIG_SCAN; - } - catch(BreakTreeTooSmallException e){ - // mod.notifyPlayer(player, mod.translate("chat.fallingtree.break_tree_too_small", mod.getConfiguration().getTrees().getMinSize())); - return AbortedResult.TREE_TOO_SMALL_BREAK; - } - catch(BreakTreeTooBigException e){ - mod.notifyPlayer(player, mod.translate("chat.fallingtree.break_tree_too_big", mod.getConfiguration().getTrees().getMaxSize())); - return AbortedResult.TREE_TOO_BIG_BREAK; - } + public TreeHandlerState create(@NonNull ILevel level, @NonNull IPlayer player, @NonNull IBlockPos originPos, @NonNull IBlockState originState, @Nullable IBlockEntity originEntity) { + return new TreeHandlerState(player, level, originPos, originState, originEntity); } @NonNull @@ -114,39 +190,4 @@ private ITreeBreakingHandler getBreakingHandler(@NonNull BreakMode breakMode){ case SHIFT_DOWN -> ShiftDownTreeBreakingHandler.getInstance(mod); }; } - - @NonNull - public Optional getBreakSpeed(@NonNull IPlayer player, @NonNull IBlockPos blockPos, @NonNull IBlockState blockState, float originalSpeed){ - if(!mod.getConfiguration().getTrees().isTreeBreaking()){ - return Optional.empty(); - } - if(!getBreakMode(player.getMainHandItem()).isApplySpeedMultiplier()){ - return Optional.empty(); - } - if(!mod.isPlayerInRightState(player)){ - return Optional.empty(); - } - - var cacheSpeed = speedCache.compute(player.getUUID(), (uuid, speed) -> { - if(isNull(speed) || !speed.isValid(blockPos)){ - speed = getSpeed(player, blockPos, blockState, originalSpeed); - } - return speed; - }); - return Optional.ofNullable(cacheSpeed).map(CacheSpeed::getSpeed); - } - - @Nullable - private CacheSpeed getSpeed(@NonNull IPlayer player, @NonNull IBlockPos pos, @NonNull IBlockState blockState, float originalSpeed){ - var speedMultiplicand = mod.getConfiguration().getTools().getSpeedMultiplicand(); - try{ - return speedMultiplicand <= 0 ? null : - mod.getTreeBuilder().getTree(player, player.getLevel(), pos, blockState, null) - .map(tree -> new CacheSpeed(pos, originalSpeed / ((float) speedMultiplicand * tree.getLogCount()))) - .orElse(null); - } - catch(TreeTooBigException e){ - return null; - } - } } diff --git a/fabric/src/main/java/fr/rakambda/fallingtree/fabric/event/BlockBreakListener.java b/fabric/src/main/java/fr/rakambda/fallingtree/fabric/event/BlockBreakListener.java index 2439a120..3bc6987d 100644 --- a/fabric/src/main/java/fr/rakambda/fallingtree/fabric/event/BlockBreakListener.java +++ b/fabric/src/main/java/fr/rakambda/fallingtree/fabric/event/BlockBreakListener.java @@ -39,7 +39,7 @@ public boolean beforeBlockBreak(Level level, Player player, BlockPos blockPos, B return true; } - return !mod.getTreeHandler().shouldCancelEvent(wrappedLevel, wrappedPlayer, wrappedPos, wrappedState, wrappedEntity); + return !mod.getTreeHandler().create(wrappedLevel, wrappedPlayer, wrappedPos, wrappedState, wrappedEntity).shouldCancelEvent(); } @Override @@ -50,6 +50,6 @@ public void afterBlockBreak(Level level, Player player, BlockPos blockPos, Block var wrappedState = new BlockStateWrapper(blockState); var wrappedEntity = Optional.ofNullable(blockEntity).map(BlockEntityWrapper::new).orElse(null); - mod.getTreeHandler().breakTree(false, wrappedLevel, wrappedPlayer, wrappedPos, wrappedState, wrappedEntity); + mod.getTreeHandler().create(wrappedLevel, wrappedPlayer, wrappedPos, wrappedState, wrappedEntity).breakTree(false); } } diff --git a/forge/src/main/java/fr/rakambda/fallingtree/forge/event/BlockBreakListener.java b/forge/src/main/java/fr/rakambda/fallingtree/forge/event/BlockBreakListener.java index 92edcdaf..7adf7c69 100644 --- a/forge/src/main/java/fr/rakambda/fallingtree/forge/event/BlockBreakListener.java +++ b/forge/src/main/java/fr/rakambda/fallingtree/forge/event/BlockBreakListener.java @@ -29,7 +29,7 @@ public void onBreakSpeed(@Nonnull PlayerEvent.BreakSpeed event){ var wrappedPos = new BlockPosWrapper(optionalPos.get()); var wrappedState = new BlockStateWrapper(event.getState()); - var result = mod.getTreeHandler().getBreakSpeed(wrappedPlayer, wrappedPos, wrappedState, event.getNewSpeed()); + var result = mod.getTreeHandler().create(wrappedPlayer.getLevel(), wrappedPlayer, wrappedPos, wrappedState).getBreakSpeed(event.getNewSpeed()); if(result.isEmpty()){ return; } @@ -48,11 +48,13 @@ public boolean onBlockBreakEvent(@Nonnull BlockEvent.BreakEvent event){ var wrappedState = new BlockStateWrapper(event.getState()); var wrappedEntity = wrappedLevel.getBlockEntity(wrappedPos); - if(mod.getTreeHandler().shouldCancelEvent(wrappedLevel, wrappedPlayer, wrappedPos, wrappedState, wrappedEntity)){ + final var treeHandler = mod.getTreeHandler().create(wrappedLevel, wrappedPlayer, wrappedPos, wrappedState, wrappedEntity); + + if(treeHandler.shouldCancelEvent()){ return true; } - var result = mod.getTreeHandler().breakTree(true, wrappedLevel, wrappedPlayer, wrappedPos, wrappedState, wrappedEntity); + var result = treeHandler.breakTree(true); return result.shouldCancel(); } } diff --git a/neoforge/src/main/java/fr/rakambda/fallingtree/neoforge/event/BlockBreakListener.java b/neoforge/src/main/java/fr/rakambda/fallingtree/neoforge/event/BlockBreakListener.java index 30b00d10..c534799d 100644 --- a/neoforge/src/main/java/fr/rakambda/fallingtree/neoforge/event/BlockBreakListener.java +++ b/neoforge/src/main/java/fr/rakambda/fallingtree/neoforge/event/BlockBreakListener.java @@ -11,7 +11,6 @@ import net.minecraft.server.level.ServerLevel; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.neoforge.event.entity.player.PlayerEvent; -import net.neoforged.neoforge.event.level.BlockEvent; import net.neoforged.neoforge.event.level.block.BreakBlockEvent; import org.jspecify.annotations.NonNull; import javax.annotation.Nonnull; @@ -36,7 +35,7 @@ public void onBreakSpeed(@Nonnull PlayerEvent.BreakSpeed event){ var wrappedPos = new BlockPosWrapper(optionalPos.get()); var wrappedState = new BlockStateWrapper(event.getState()); - var result = mod.getTreeHandler().getBreakSpeed(wrappedPlayer, wrappedPos, wrappedState, event.getNewSpeed()); + var result = mod.getTreeHandler().create(wrappedPlayer.getLevel(), wrappedPlayer, wrappedPos, wrappedState).getBreakSpeed(event.getNewSpeed()); if(result.isEmpty()){ return; } @@ -59,12 +58,14 @@ public void onBlockBreakEvent(@Nonnull BreakBlockEvent event){ var wrappedState = new BlockStateWrapper(event.getState()); var wrappedEntity = wrappedLevel.getBlockEntity(wrappedPos); - if(mod.getTreeHandler().shouldCancelEvent(wrappedLevel, wrappedPlayer, wrappedPos, wrappedState, wrappedEntity)){ + final var treeHandler = mod.getTreeHandler().create(wrappedLevel, wrappedPlayer, wrappedPos, wrappedState, wrappedEntity); + + if(treeHandler.shouldCancelEvent()){ event.setCanceled(true); return; } - var result = mod.getTreeHandler().breakTree(true, wrappedLevel, wrappedPlayer, wrappedPos, wrappedState, wrappedEntity); + var result = treeHandler.breakTree(true); if(result.shouldCancel()){ event.setCanceled(true); } From 572240d4c1657d94a52958ef26cfbb72179bb18f Mon Sep 17 00:00:00 2001 From: av Date: Wed, 13 May 2026 22:57:30 +0300 Subject: [PATCH 3/5] improved: extremum points now cached on first call, instead of rewriting on `addPart` (lazy-init) refactored: commited interface separation to make accessible mutation methods visible to TreeBuilder only; secured immutability of `parts` set by exposing only stream of parts in interface --- .../fallingtree/common/tree/Tree.java | 198 ++-------------- .../FallingAnimationTreeBreakingHandler.java | 2 +- .../common/tree/builder/MutableTree.java | 219 ++++++++++++++++++ .../common/tree/builder/TreeBuilder.java | 4 +- 4 files changed, 240 insertions(+), 183 deletions(-) create mode 100644 common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/MutableTree.java diff --git a/common/src/main/java/fr/rakambda/fallingtree/common/tree/Tree.java b/common/src/main/java/fr/rakambda/fallingtree/common/tree/Tree.java index 682067e3..64669be1 100644 --- a/common/src/main/java/fr/rakambda/fallingtree/common/tree/Tree.java +++ b/common/src/main/java/fr/rakambda/fallingtree/common/tree/Tree.java @@ -2,203 +2,41 @@ import fr.rakambda.fallingtree.common.wrapper.IBlockPos; import fr.rakambda.fallingtree.common.wrapper.ILevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; -import java.util.Arrays; import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; import java.util.Optional; -import java.util.Set; -import static java.util.Comparator.comparingInt; -import static java.util.Objects.isNull; -import static java.util.stream.Collectors.toSet; +import java.util.stream.Stream; -@RequiredArgsConstructor -public class Tree{ - @Getter - @NonNull - private final ILevel level; - @Getter - @NonNull - private final IBlockPos hitPos; - @Getter - private final Set parts = new LinkedHashSet<>(); - private final Map partCounts = new LinkedHashMap<>(); - private final ExtremumBlocks extremumBlocks = new ExtremumBlocks(); +public interface Tree{ - public void addPart(@NonNull TreePart treePart){ - parts.add(treePart); - partCounts.compute(treePart.treePartType(), (key, value) -> { - if(isNull(value)){ - return 1; - } - return value + 1; - }); - extremumBlocks.update(treePart); - } + int getBreakableCount(); - public void removePartsHigherThan(int y, @NonNull TreePartType partType){ - parts.removeIf(part -> { - if(part.treePartType() == partType && part.blockPos().getY() > y){ - decrementPartCount(partType); - return true; - } - return false; - }); - } + @NonNull Optional getLastSequencePart(); - public int getBreakableCount(){ - return Arrays.stream(TreePartType.getValues()) - .filter(TreePartType::isBreakable) - .mapToInt(this::getPartCount) - .sum(); - } + @NonNull Optional getLastSequenceLogPart(); - private int getPartCount(@NonNull TreePartType treePartType){ - return partCounts.computeIfAbsent(treePartType, key -> 0); - } + @NonNull Collection getBreakableLogs(); - public int getSize(){ - return partCounts.values().stream().mapToInt(i -> i).sum(); - } + @NonNull Collection getBreakableParts(); - private void decrementPartCount(@NonNull TreePartType partType){ - partCounts.computeIfPresent(partType, (type, count) -> Math.max(0, count - 1)); - } + int getLogCount(); - @NonNull - public Optional getLastSequencePart(){ - return getParts().stream() - .max(comparingInt(TreePart::sequence)); - } + @NonNull Optional getTopMostLog(); - @NonNull - public Optional getLastSequenceLogPart(){ - return getParts().stream() - .filter(part -> part.treePartType().isLog()) - .max(comparingInt(TreePart::sequence)); - } + @NonNull Optional getBottomMostLog(); - @NonNull - public Collection getBreakableLogs(){ - return getParts().stream() - .filter(part -> part.treePartType().isLog()) - .filter(part -> part.treePartType().isBreakable()) - .collect(toSet()); - } + @NonNull Optional getTopMostPart(); - @NonNull - public Collection getBreakableParts(){ - return getParts().stream() - .filter(part -> part.treePartType().isBreakable()) - .collect(toSet()); - } + @NonNull Collection getNetherWarts(); - public int getLogCount(){ - return getPartCount(TreePartType.LOG); - } + @NonNull Collection getMangroveRoots(); - private static class ExtremumBlocks { - - @Nullable - private TreePart topMostLog; - - @Nullable - private TreePart bottomMostLog; - - @Nullable - private TreePart logStart; - - private void updateTopMostLog(@NonNull TreePart candidate){ - if(this.topMostLog == null){ - this.topMostLog = candidate; - return; - } - - if(candidate.treePartType().isBreakable() - && candidate.treePartType().isLog() - && candidate.blockPos().getY() > this.topMostLog.blockPos().getY()){ - this.topMostLog = candidate; - } - } - - private void updateBottomMostLog(@NonNull TreePart candidate){ - if(this.bottomMostLog == null){ - this.bottomMostLog = candidate; - return; - } - - if(candidate.treePartType().isBreakable() - && candidate.treePartType().isLog() - && candidate.blockPos().getY() < this.bottomMostLog.blockPos().getY()){ - this.bottomMostLog = candidate; - } - } - - private void updateLogStart(@NonNull TreePart candidate) { - if(this.logStart == null && candidate.treePartType() == TreePartType.LOG_START){ - this.logStart = candidate; - } - } - - protected void update(@NonNull TreePart candidate){ - updateLogStart(candidate); - updateTopMostLog(candidate); - updateBottomMostLog(candidate); - } - - public Optional getTopMostLog(){ - return Optional.ofNullable(topMostLog); - } - - public Optional getBottomMostLog(){ - return Optional.ofNullable(bottomMostLog); - } - - public Optional getLogStart(){ - return Optional.ofNullable(logStart); - } - } + @NonNull Optional getStart(); - @NonNull - public Optional getTopMostLog(){ - return extremumBlocks.getTopMostLog() - .map(TreePart::blockPos); - } + @NonNull Stream getPartsStream(); - @NonNull - public Optional getBottomMostLog(){ - return extremumBlocks.getBottomMostLog() - .map(TreePart::blockPos); - } + ILevel getLevel(); - @NonNull - private Optional getTopMostPart(){ - return getParts().stream() - .map(TreePart::blockPos) - .max(comparingInt(IBlockPos::getY)); - } - - @NonNull - public Collection getNetherWarts(){ - return getParts().stream() - .filter(part -> part.treePartType() == TreePartType.NETHER_WART) - .collect(toSet()); - } - - @NonNull - public Collection getMangroveRoots(){ - return getParts().stream() - .filter(part -> part.treePartType() == TreePartType.MANGROVE_ROOTS) - .collect(toSet()); - } - - @NonNull - public Optional getStart(){ - return extremumBlocks.getLogStart(); - } + IBlockPos getHitPos(); } + diff --git a/common/src/main/java/fr/rakambda/fallingtree/common/tree/breaking/FallingAnimationTreeBreakingHandler.java b/common/src/main/java/fr/rakambda/fallingtree/common/tree/breaking/FallingAnimationTreeBreakingHandler.java index 9ac8b2b6..3eef9bdd 100644 --- a/common/src/main/java/fr/rakambda/fallingtree/common/tree/breaking/FallingAnimationTreeBreakingHandler.java +++ b/common/src/main/java/fr/rakambda/fallingtree/common/tree/breaking/FallingAnimationTreeBreakingHandler.java @@ -81,7 +81,7 @@ public IBreakAttemptResult breakTree(boolean isCancellable, @NonNull IPlayer pla var lootHandler = new LootHandler(wantToBreakCount, mod.getConfiguration().getTrees().getTrunkLootPercentage()); var brokenCount = 0; var breakablePartsLeft = wantToBreakCount; - var breakableParts = tree.getParts().stream().sorted(mod.getConfiguration().getTrees().getBreakOrder().getComparator()).toList(); + var breakableParts = tree.getPartsStream().sorted(mod.getConfiguration().getTrees().getBreakOrder().getComparator()).toList(); for(var part : breakableParts){ if(part.treePartType().isBreakable()){ if(breakablePartsLeft == 0){ diff --git a/common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/MutableTree.java b/common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/MutableTree.java new file mode 100644 index 00000000..1521d854 --- /dev/null +++ b/common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/MutableTree.java @@ -0,0 +1,219 @@ +package fr.rakambda.fallingtree.common.tree.builder; + +import fr.rakambda.fallingtree.common.tree.Tree; +import fr.rakambda.fallingtree.common.tree.TreePart; +import fr.rakambda.fallingtree.common.tree.TreePartType; +import fr.rakambda.fallingtree.common.wrapper.IBlockPos; +import fr.rakambda.fallingtree.common.wrapper.ILevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; +import static java.util.Comparator.comparingInt; +import static java.util.Objects.isNull; +import static java.util.stream.Collectors.toSet; + +@RequiredArgsConstructor +class MutableTree implements Tree{ + @Getter + @NonNull + private final ILevel level; + @Getter + @NonNull + private final IBlockPos hitPos; + @Getter + private final Set parts = new LinkedHashSet<>(); + private final Map partCounts = new LinkedHashMap<>(); + private final ExtremumBlocks extremumBlocks = new ExtremumBlocks(); + + void addPart(@NonNull TreePart treePart){ + parts.add(treePart); + partCounts.compute(treePart.treePartType(), (key, value) -> { + if(isNull(value)){ + return 1; + } + return value + 1; + }); + } + + void removePartsHigherThan(int y, @NonNull TreePartType partType){ + parts.removeIf(part -> { + if(part.treePartType() == partType && part.blockPos().getY() > y){ + decrementPartCount(partType); + this.extremumBlocks.onRemoved(part); + return true; + } + return false; + }); + } + + @Override + public int getBreakableCount(){ + return Arrays.stream(TreePartType.getValues()) + .filter(TreePartType::isBreakable) + .mapToInt(this::getPartCount) + .sum(); + } + + private int getPartCount(@NonNull TreePartType treePartType){ + return partCounts.computeIfAbsent(treePartType, key -> 0); + } + + int getSize(){ + return partCounts.values().stream().mapToInt(i -> i).sum(); + } + + private void decrementPartCount(@NonNull TreePartType partType){ + partCounts.computeIfPresent(partType, (type, count) -> Math.max(0, count - 1)); + } + + @Override + public @NonNull Optional getLastSequencePart(){ + return getParts().stream() + .max(comparingInt(TreePart::sequence)); + } + + @Override + public @NonNull Optional getLastSequenceLogPart(){ + return getParts().stream() + .filter(part -> part.treePartType().isLog()) + .max(comparingInt(TreePart::sequence)); + } + + @Override + public @NonNull Collection getBreakableLogs(){ + return getParts().stream() + .filter(part -> part.treePartType().isLog()) + .filter(part -> part.treePartType().isBreakable()) + .collect(toSet()); + } + + @Override + public @NonNull Collection getBreakableParts(){ + return getParts().stream() + .filter(part -> part.treePartType().isBreakable()) + .collect(toSet()); + } + + @Override + public int getLogCount(){ + return getPartCount(TreePartType.LOG); + } + + private class ExtremumBlocks{ + + @Nullable + private TreePart topMostLog = null; + + @Nullable + private TreePart bottomMostLog = null; + + @Nullable + private TreePart logStart = null; + + public Optional getTopMostLog(){ + if(topMostLog != null){ + return Optional.of(topMostLog); + } + + this.topMostLog = parts.stream() + .filter(treePart -> treePart.treePartType() == TreePartType.LOG) + .max(Comparator.comparing(treePart -> treePart.blockPos().getY())) + .orElse(null); + + return Optional.ofNullable(topMostLog); + } + + public Optional getBottomMostLog(){ + if(bottomMostLog != null){ + return Optional.of(bottomMostLog); + } + + this.bottomMostLog = parts.stream() + .filter(treePart -> treePart.treePartType() == TreePartType.LOG) + .min(Comparator.comparing(treePart -> treePart.blockPos().getY())) + .orElse(null); + + return Optional.ofNullable(bottomMostLog); + } + + public Optional getLogStart(){ + if(logStart != null){ + return Optional.of(logStart); + } + + this.logStart = parts.stream() + .filter(treePart -> treePart.treePartType() == TreePartType.LOG_START) + .findFirst() + .orElse(null); + + return Optional.ofNullable(logStart); + } + + public void onRemoved(@NonNull final TreePart removedTreePart){ + if(topMostLog != null && topMostLog.equals(removedTreePart)){ + this.topMostLog = null; + } + + if(bottomMostLog != null && bottomMostLog.equals(removedTreePart)){ + this.bottomMostLog = null; + } + + if(logStart != null && logStart.equals(removedTreePart)){ + this.logStart = null; + } + } + } + + @Override + public @NonNull Optional getTopMostLog(){ + return extremumBlocks.getTopMostLog() + .map(TreePart::blockPos); + } + + @Override + public @NonNull Optional getBottomMostLog(){ + return extremumBlocks.getBottomMostLog() + .map(TreePart::blockPos); + } + + @Override + public @NonNull Optional getTopMostPart(){ + return getParts().stream() + .map(TreePart::blockPos) + .max(comparingInt(IBlockPos::getY)); + } + + @Override + public @NonNull Collection getNetherWarts(){ + return getParts().stream() + .filter(part -> part.treePartType() == TreePartType.NETHER_WART) + .collect(toSet()); + } + + @Override + public @NonNull Collection getMangroveRoots(){ + return getParts().stream() + .filter(part -> part.treePartType() == TreePartType.MANGROVE_ROOTS) + .collect(toSet()); + } + + @Override + public @NonNull Optional getStart(){ + return extremumBlocks.getLogStart(); + } + + @Override + public @NonNull Stream getPartsStream(){ + return getParts().stream(); + } +} diff --git a/common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/TreeBuilder.java b/common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/TreeBuilder.java index 40e53aa3..45160fdf 100644 --- a/common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/TreeBuilder.java +++ b/common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/TreeBuilder.java @@ -48,7 +48,7 @@ public Optional getTree(@NonNull IPlayer player, @NonNull ILevel level, @N var maxScanSize = mod.getConfiguration().getTrees().getMaxScanSize(); var toAnalyzePos = new PriorityQueue(); var analyzedPos = new HashSet(); - var tree = new Tree(level, originPos); + var tree = new MutableTree(level, originPos); var detectionMode = getDetectionMode(level, originPos); var firstPositionFetcher = getFirstPositionFetcher(detectionMode); toAnalyzePos.add(new ToAnalyzePos(firstPositionFetcher, originPos, originBlock, originPos, originBlock, originState, originEntity, TreePartType.LOG_START, 0, 0)); @@ -106,7 +106,7 @@ public Optional getTree(@NonNull IPlayer player, @NonNull ILevel level, @N return Optional.of(tree); } - private static void postProcess(@NonNull Tree tree){ + private static void postProcess(@NonNull MutableTree tree){ tree.getTopMostLog().ifPresent(topMostLog -> tree.removePartsHigherThan(topMostLog.getY() + 1, TreePartType.NETHER_WART)); } From 9d25ae2598062f27df7dfdb010de8468d39cbe65 Mon Sep 17 00:00:00 2001 From: Thomas Couchoud <1688389+Rakambda@users.noreply.github.com> Date: Wed, 20 May 2026 14:34:32 +0200 Subject: [PATCH 4/5] Refactor --- .../fallingtree/common/FallingTreeCommon.java | 407 +++++++++--------- .../fallingtree/common/tree/TreeHandler.java | 227 +++++----- .../common/tree/TreeHandlerFactory.java | 54 +++ .../common/tree/builder/ImmutableTree.java | 158 +++++++ .../common/tree/builder/MutableTree.java | 217 ++-------- .../tree/builder/PartOfInterestCache.java | 46 ++ .../common/tree/builder/TreeBuilder.java | 2 +- .../fabric/event/BlockBreakListener.java | 4 +- .../fabric/mixin/AbstractBlockMixin.java | 2 +- .../forge/event/BlockBreakListener.java | 4 +- .../neoforge/event/BlockBreakListener.java | 4 +- 11 files changed, 602 insertions(+), 523 deletions(-) create mode 100644 common/src/main/java/fr/rakambda/fallingtree/common/tree/TreeHandlerFactory.java create mode 100644 common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/ImmutableTree.java create mode 100644 common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/PartOfInterestCache.java diff --git a/common/src/main/java/fr/rakambda/fallingtree/common/FallingTreeCommon.java b/common/src/main/java/fr/rakambda/fallingtree/common/FallingTreeCommon.java index 5be1d649..eb0fbea3 100644 --- a/common/src/main/java/fr/rakambda/fallingtree/common/FallingTreeCommon.java +++ b/common/src/main/java/fr/rakambda/fallingtree/common/FallingTreeCommon.java @@ -9,11 +9,13 @@ import fr.rakambda.fallingtree.common.network.PacketUtils; import fr.rakambda.fallingtree.common.network.ServerPacketHandler; import fr.rakambda.fallingtree.common.tree.TreeHandler; +import fr.rakambda.fallingtree.common.tree.TreeHandlerFactory; import fr.rakambda.fallingtree.common.tree.TreePartType; import fr.rakambda.fallingtree.common.tree.builder.TreeBuilder; import fr.rakambda.fallingtree.common.wrapper.DirectionCompat; import fr.rakambda.fallingtree.common.wrapper.IBlock; import fr.rakambda.fallingtree.common.wrapper.IBlockBreakEvent; +import fr.rakambda.fallingtree.common.wrapper.IBlockEntity; import fr.rakambda.fallingtree.common.wrapper.IBlockPos; import fr.rakambda.fallingtree.common.wrapper.IBlockState; import fr.rakambda.fallingtree.common.wrapper.IComponent; @@ -24,6 +26,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import java.util.Collection; import java.util.Objects; import java.util.Set; @@ -32,208 +35,208 @@ @RequiredArgsConstructor @Getter -public abstract class FallingTreeCommon> { - private final Configuration ownConfiguration; - private final ProxyConfiguration proxyConfiguration; - private final TreeBuilder treeBuilder; - private final TreeHandler treeHandler; - private final PacketUtils packetUtils; - - public FallingTreeCommon() { - ownConfiguration = Configuration.read(); - proxyConfiguration = new ProxyConfiguration(ownConfiguration); - treeBuilder = new TreeBuilder(this); - treeHandler = new TreeHandler(this); - packetUtils = new PacketUtils(this); - } - - public IConfiguration getConfiguration() { - return getProxyConfiguration(); - } - - @NonNull - public abstract IComponent translate(@NonNull String key, Object... objects); - - public void notifyPlayer(@NonNull IPlayer player, @NonNull IComponent component) { - player.sendMessage(component, getConfiguration().getNotificationMode()); - } - - /** - * Checks if a player is allowed to break a block. - *
- * These conditions must be met in order, otherwise player is denied : - *
    - *
  • If {@link IToolConfiguration#isForceToolUsage()} is set to false, player is allowed
  • - *
  • If block is not a whitelisted one, player is allowed
  • - *
  • If tool is valid, player is allowed
  • - *
- * - * @return true if the player is allowed to break that block, false otherwise. - */ - public boolean checkForceToolUsage(@NonNull IPlayer player, @NonNull ILevel level, @NonNull IBlockPos blockPos) { - if (!getConfiguration().getTools().isForceToolUsage()) { - return true; - } - var originBlock = level.getBlockState(blockPos).getBlock(); - if (!isLogBlock(originBlock)) { - return true; - } - return isValidTool(player.getMainHandItem()); - } - - public boolean isPlayerInRightState(@NonNull IPlayer player) { - if (player.isCreative() && !getConfiguration().isBreakInCreative()) { - return false; - } - if (!getConfiguration().getSneakMode().test(player.isCrouching())) { - return false; - } - if (playerHasToggledOff(player)) { - return false; - } - if (!playerHasRequiredTags(player)) { - return false; - } - return canPlayerBreakTree(player); - } - - private boolean playerHasToggledOff(@NonNull IPlayer player) { - return player.getTags().contains(ToggleCommand.FALLINGTREE_DISABLE_TAG); - } - - private boolean playerHasRequiredTags(@NonNull IPlayer player) { - var tags = getConfiguration().getPlayer().getAllowedTagsNormalized(); - if (tags.isEmpty()) { - return true; - } - - var playerTags = player.getTags(); - return tags.stream().anyMatch(playerTags::contains); - } - - public boolean canPlayerBreakTree(@NonNull IPlayer player) { - var heldItemStack = player.getMainHandItem(); - - if (!isValidTool(heldItemStack)) { - return false; - } - - if (getConfiguration().getEnchantment().isRequireEnchantment() - && !heldItemStack.hasChopperEnchant()) { - return false; - } - - return true; - } - - public boolean isValidTool(@NonNull IItemStack heldItemStack) { - var toolConfiguration = getConfiguration().getTools(); - var heldItem = heldItemStack.getItem(); - - var isAllowedTool = toolConfiguration.isIgnoreTools() - || heldItem.isAxe() - || toolConfiguration.getAllowedItems(this).stream().anyMatch(tool -> tool.equals(heldItem)) - || heldItemStack.canPerformAxeAction(); - if (!isAllowedTool) { - return false; - } - - var isDeniedTool = toolConfiguration.getDeniedItems(this).stream().anyMatch(tool -> tool.equals(heldItem)); - return !isDeniedTool; - } - - @NonNull - public TreeHandler getTreeHandler() { - return treeHandler; - } - - @NonNull - public TreeBuilder getTreeBuilder() { - return treeBuilder; - } - - @NonNull - public abstract LeafBreakingHandler getLeafBreakingHandler(); - - @NonNull - public abstract ServerPacketHandler getServerPacketHandler(); - - @NonNull - public Set getAsBlocks(@NonNull Collection names) { - return names.stream() - .filter(Objects::nonNull) - .filter(val -> !val.isEmpty()) - .flatMap(this::getBlock) - .filter(Objects::nonNull) - .filter(block -> !block.isAir()) - .collect(toSet()); - } - - @NonNull - public abstract Stream getBlock(@NonNull String name); - - @NonNull - public Set getAsItems(Collection names) { - return names.stream() - .filter(Objects::nonNull) - .filter(val -> !val.isEmpty()) - .flatMap(this::getItem) - .filter(Objects::nonNull) - .filter(item -> !item.isAir()) - .collect(toSet()); - } - - @NonNull - public abstract Stream getItem(@NonNull String name); - - public abstract boolean isLeafBlock(@NonNull IBlock block); - - public abstract boolean isLogBlock(@NonNull IBlock block); - - @NonNull - public abstract Set getAllNonStrippedLogsBlocks(); - - @NonNull - public abstract DirectionCompat asDirectionCompat(@NonNull D dir); - - @NonNull - public abstract D asDirection(@NonNull DirectionCompat dir); - - public boolean isLeafNeedBreakBlock(@NonNull IBlock block) { - return getConfiguration().getTrees() - .getAllowedNonDecayLeaveBlocks(this) - .stream() - .anyMatch(log -> log.equals(block)); - } - - public abstract boolean isNetherWartOrShroomlight(@NonNull IBlock block); - - public abstract boolean isMangroveRoots(@NonNull IBlock block); - - @NonNull - public TreePartType getTreePart(@NonNull IBlock checkBlock) { - if (isLogBlock(checkBlock)) { - return TreePartType.LOG; - } - if (isNetherWartOrShroomlight(checkBlock)) { - return TreePartType.NETHER_WART; - } - if (isMangroveRoots(checkBlock)) { - return TreePartType.MANGROVE_ROOTS; - } - if (isLeafNeedBreakBlock(checkBlock)) { - return TreePartType.LEAF_NEED_BREAK; - } - if (isLeafBlock(checkBlock)) { - return TreePartType.LEAF; - } - return TreePartType.OTHER; - } - - public abstract boolean checkCanBreakBlock(@NonNull ILevel level, @NonNull IBlockPos blockPos, @NonNull IBlockState blockState, @NonNull IPlayer player); - - @NonNull - public abstract IItemStack getEmptyItemStack(); +public abstract class FallingTreeCommon>{ + private final Configuration ownConfiguration; + private final ProxyConfiguration proxyConfiguration; + private final TreeBuilder treeBuilder; + private final TreeHandlerFactory treeHandlerFactory; + private final PacketUtils packetUtils; + + public FallingTreeCommon(){ + ownConfiguration = Configuration.read(); + proxyConfiguration = new ProxyConfiguration(ownConfiguration); + treeBuilder = new TreeBuilder(this); + treeHandlerFactory = new TreeHandlerFactory(this); + packetUtils = new PacketUtils(this); + } + + public IConfiguration getConfiguration(){ + return getProxyConfiguration(); + } + + @NonNull + public abstract IComponent translate(@NonNull String key, Object... objects); + + public void notifyPlayer(@NonNull IPlayer player, @NonNull IComponent component){ + player.sendMessage(component, getConfiguration().getNotificationMode()); + } + + /** + * Checks if a player is allowed to break a block. + *
+ * These conditions must be met in order, otherwise player is denied : + *
    + *
  • If {@link IToolConfiguration#isForceToolUsage()} is set to false, player is allowed
  • + *
  • If block is not a whitelisted one, player is allowed
  • + *
  • If tool is valid, player is allowed
  • + *
+ * + * @return true if the player is allowed to break that block, false otherwise. + */ + public boolean checkForceToolUsage(@NonNull IPlayer player, @NonNull ILevel level, @NonNull IBlockPos blockPos){ + if(!getConfiguration().getTools().isForceToolUsage()){ + return true; + } + var originBlock = level.getBlockState(blockPos).getBlock(); + if(!isLogBlock(originBlock)){ + return true; + } + return isValidTool(player.getMainHandItem()); + } + + public boolean isPlayerInRightState(@NonNull IPlayer player){ + if(player.isCreative() && !getConfiguration().isBreakInCreative()){ + return false; + } + if(!getConfiguration().getSneakMode().test(player.isCrouching())){ + return false; + } + if(playerHasToggledOff(player)){ + return false; + } + if(!playerHasRequiredTags(player)){ + return false; + } + return canPlayerBreakTree(player); + } + + private boolean playerHasToggledOff(@NonNull IPlayer player){ + return player.getTags().contains(ToggleCommand.FALLINGTREE_DISABLE_TAG); + } + + private boolean playerHasRequiredTags(@NonNull IPlayer player){ + var tags = getConfiguration().getPlayer().getAllowedTagsNormalized(); + if(tags.isEmpty()){ + return true; + } + + var playerTags = player.getTags(); + return tags.stream().anyMatch(playerTags::contains); + } + + public boolean canPlayerBreakTree(@NonNull IPlayer player){ + var heldItemStack = player.getMainHandItem(); + + if(!isValidTool(heldItemStack)){ + return false; + } + + if(getConfiguration().getEnchantment().isRequireEnchantment() + && !heldItemStack.hasChopperEnchant()){ + return false; + } + + return true; + } + + public boolean isValidTool(@NonNull IItemStack heldItemStack){ + var toolConfiguration = getConfiguration().getTools(); + var heldItem = heldItemStack.getItem(); + + var isAllowedTool = toolConfiguration.isIgnoreTools() + || heldItem.isAxe() + || toolConfiguration.getAllowedItems(this).stream().anyMatch(tool -> tool.equals(heldItem)) + || heldItemStack.canPerformAxeAction(); + if(!isAllowedTool){ + return false; + } + + var isDeniedTool = toolConfiguration.getDeniedItems(this).stream().anyMatch(tool -> tool.equals(heldItem)); + return !isDeniedTool; + } + + @NonNull + public TreeHandler getTreeHandler(@NonNull ILevel level, @NonNull IPlayer player, @NonNull IBlockPos originPos, @NonNull IBlockState originState, @Nullable IBlockEntity originEntity){ + return treeHandlerFactory.create(level, player, originPos, originState, originEntity); + } + + @NonNull + public TreeBuilder getTreeBuilder(){ + return treeBuilder; + } + + @NonNull + public abstract LeafBreakingHandler getLeafBreakingHandler(); + + @NonNull + public abstract ServerPacketHandler getServerPacketHandler(); + + @NonNull + public Set getAsBlocks(@NonNull Collection names){ + return names.stream() + .filter(Objects::nonNull) + .filter(val -> !val.isEmpty()) + .flatMap(this::getBlock) + .filter(Objects::nonNull) + .filter(block -> !block.isAir()) + .collect(toSet()); + } + + @NonNull + public abstract Stream getBlock(@NonNull String name); + + @NonNull + public Set getAsItems(Collection names){ + return names.stream() + .filter(Objects::nonNull) + .filter(val -> !val.isEmpty()) + .flatMap(this::getItem) + .filter(Objects::nonNull) + .filter(item -> !item.isAir()) + .collect(toSet()); + } + + @NonNull + public abstract Stream getItem(@NonNull String name); + + public abstract boolean isLeafBlock(@NonNull IBlock block); + + public abstract boolean isLogBlock(@NonNull IBlock block); + + @NonNull + public abstract Set getAllNonStrippedLogsBlocks(); + + @NonNull + public abstract DirectionCompat asDirectionCompat(@NonNull D dir); + + @NonNull + public abstract D asDirection(@NonNull DirectionCompat dir); + + public boolean isLeafNeedBreakBlock(@NonNull IBlock block){ + return getConfiguration().getTrees() + .getAllowedNonDecayLeaveBlocks(this) + .stream() + .anyMatch(log -> log.equals(block)); + } + + public abstract boolean isNetherWartOrShroomlight(@NonNull IBlock block); + + public abstract boolean isMangroveRoots(@NonNull IBlock block); + + @NonNull + public TreePartType getTreePart(@NonNull IBlock checkBlock){ + if(isLogBlock(checkBlock)){ + return TreePartType.LOG; + } + if(isNetherWartOrShroomlight(checkBlock)){ + return TreePartType.NETHER_WART; + } + if(isMangroveRoots(checkBlock)){ + return TreePartType.MANGROVE_ROOTS; + } + if(isLeafNeedBreakBlock(checkBlock)){ + return TreePartType.LEAF_NEED_BREAK; + } + if(isLeafBlock(checkBlock)){ + return TreePartType.LEAF; + } + return TreePartType.OTHER; + } + + public abstract boolean checkCanBreakBlock(@NonNull ILevel level, @NonNull IBlockPos blockPos, @NonNull IBlockState blockState, @NonNull IPlayer player); + + @NonNull + public abstract IItemStack getEmptyItemStack(); public boolean isOwnEvent(@NonNull IBlockBreakEvent event){ return false; diff --git a/common/src/main/java/fr/rakambda/fallingtree/common/tree/TreeHandler.java b/common/src/main/java/fr/rakambda/fallingtree/common/tree/TreeHandler.java index 5276e73d..e8b77867 100644 --- a/common/src/main/java/fr/rakambda/fallingtree/common/tree/TreeHandler.java +++ b/common/src/main/java/fr/rakambda/fallingtree/common/tree/TreeHandler.java @@ -22,9 +22,9 @@ import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; import static java.util.Objects.isNull; @Log4j2 @@ -32,129 +32,121 @@ public class TreeHandler{ @NonNull private final FallingTreeCommon mod; - private final Map speedCache = new ConcurrentHashMap<>(); + @NonNull + private final Map speedCache; - public class TreeHandlerState{ - @NonNull - private final IPlayer player; - @NonNull - private final ILevel level; - @NonNull - private final IBlockPos originPos; - @NonNull - private final IBlockState originState; - @Nullable - private final IBlockEntity originEntity; - - @Nullable - private Tree treeCache = null; - - public TreeHandlerState(@NonNull IPlayer player, @NonNull ILevel level, @NonNull IBlockPos originPos, @NonNull IBlockState originState, @Nullable IBlockEntity originEntity){ - this.player = player; - this.level = level; - this.originPos = originPos; - this.originState = originState; - this.originEntity = originEntity; + @NonNull + private final IPlayer player; + @NonNull + private final ILevel level; + @NonNull + private final IBlockPos originPos; + @NonNull + private final IBlockState originState; + @Nullable + private final IBlockEntity originEntity; + + @Nullable + private Tree cachedTree = null; + + @Nullable + public Tree getTree() throws TreeTooBigException{ + if(Objects.isNull(cachedTree)){ + cachedTree = mod.getTreeBuilder().getTree(player, level, originPos, originState, originEntity).orElse(null); } - - public Tree getTree() throws TreeTooBigException{ - if(this.treeCache == null){ - this.treeCache = mod.getTreeBuilder().getTree(player, level, originPos, originState, originEntity).orElse(null); - } - - return this.treeCache; + return cachedTree; + } + + public boolean shouldCancelEvent(){ + if(!mod.isPlayerInRightState(player)){ + return false; } - - public boolean shouldCancelEvent(){ - if(!mod.isPlayerInRightState(player)){ - return false; - } - if(shouldPreserveTool(player)){ - return true; - } - try{ - this.getTree(); - } - catch(TreeTooBigException e){ - return false; - } + if(shouldPreserveTool(player)){ + return true; + } + try{ + getTree(); + } + catch(TreeTooBigException e){ return false; } + return false; + } + + @NonNull + public IBreakAttemptResult breakTree(boolean isCancellable){ + if(!level.isServer()){ + return AbortedResult.NOT_SERVER; + } + if(!mod.getConfiguration().getTrees().isTreeBreaking()){ + return AbortedResult.NOT_ENABLED; + } - @NonNull - public IBreakAttemptResult breakTree(boolean isCancellable){ - if(!level.isServer()){ - return AbortedResult.NOT_SERVER; - } - if(!mod.getConfiguration().getTrees().isTreeBreaking()){ - return AbortedResult.NOT_ENABLED; - } - - if(!mod.checkForceToolUsage(player, level, originPos)){ - mod.notifyPlayer(player, mod.translate("chat.fallingtree.force_tool_usage", mod.getConfiguration().getTrees().getMaxScanSize())); - return AbortedResult.REQUIRED_TOOL_ABSENT; - } - - if(!mod.isPlayerInRightState(player)){ - return AbortedResult.INVALID_PLAYER_STATE; - } - - try{ - var tree = this.getTree(); - if(tree == null){ - return AbortedResult.NO_SUCH_TREE; - } - - var breakMode = getBreakMode(player.getMainHandItem()); - return getBreakingHandler(breakMode).breakTree(isCancellable, player, tree); - } - catch(TreeTooBigException e){ - mod.notifyPlayer(player, mod.translate("chat.fallingtree.tree_too_big", mod.getConfiguration().getTrees().getMaxScanSize())); - return AbortedResult.TREE_TOO_BIG_SCAN; - } - catch(BreakTreeTooSmallException e){ - // mod.notifyPlayer(player, mod.translate("chat.fallingtree.break_tree_too_small", mod.getConfiguration().getTrees().getMinSize())); - return AbortedResult.TREE_TOO_SMALL_BREAK; - } - catch(BreakTreeTooBigException e){ - mod.notifyPlayer(player, mod.translate("chat.fallingtree.break_tree_too_big", mod.getConfiguration().getTrees().getMaxSize())); - return AbortedResult.TREE_TOO_BIG_BREAK; - } + if(!mod.checkForceToolUsage(player, level, originPos)){ + mod.notifyPlayer(player, mod.translate("chat.fallingtree.force_tool_usage", mod.getConfiguration().getTrees().getMaxScanSize())); + return AbortedResult.REQUIRED_TOOL_ABSENT; } - @NonNull - public Optional getBreakSpeed(float originalSpeed){ - if(!mod.getConfiguration().getTrees().isTreeBreaking()){ - return Optional.empty(); - } - if(!getBreakMode(player.getMainHandItem()).isApplySpeedMultiplier()){ - return Optional.empty(); - } - if(!mod.isPlayerInRightState(player)){ - return Optional.empty(); + if(!mod.isPlayerInRightState(player)){ + return AbortedResult.INVALID_PLAYER_STATE; + } + + try{ + var tree = getTree(); + if(tree == null){ + return AbortedResult.NO_SUCH_TREE; } - var cacheSpeed = speedCache.compute(player.getUUID(), (uuid, speed) -> { - if(isNull(speed) || !speed.isValid(originPos)){ - speed = getSpeed(originalSpeed); - } - return speed; - }); - return Optional.ofNullable(cacheSpeed).map(CacheSpeed::getSpeed); + var breakMode = getBreakMode(player.getMainHandItem()); + return getBreakingHandler(breakMode).breakTree(isCancellable, player, tree); + } + catch(TreeTooBigException e){ + mod.notifyPlayer(player, mod.translate("chat.fallingtree.tree_too_big", mod.getConfiguration().getTrees().getMaxScanSize())); + return AbortedResult.TREE_TOO_BIG_SCAN; + } + catch(BreakTreeTooSmallException e){ + // mod.notifyPlayer(player, mod.translate("chat.fallingtree.break_tree_too_small", mod.getConfiguration().getTrees().getMinSize())); + return AbortedResult.TREE_TOO_SMALL_BREAK; + } + catch(BreakTreeTooBigException e){ + mod.notifyPlayer(player, mod.translate("chat.fallingtree.break_tree_too_big", mod.getConfiguration().getTrees().getMaxSize())); + return AbortedResult.TREE_TOO_BIG_BREAK; + } + } + + @NonNull + public Optional getBreakSpeed(float originalSpeed){ + if(!mod.getConfiguration().getTrees().isTreeBreaking()){ + return Optional.empty(); + } + if(!getBreakMode(player.getMainHandItem()).isApplySpeedMultiplier()){ + return Optional.empty(); + } + if(!mod.isPlayerInRightState(player)){ + return Optional.empty(); } - @Nullable - private CacheSpeed getSpeed(float originalSpeed){ - var speedMultiplicand = mod.getConfiguration().getTools().getSpeedMultiplicand(); - try{ - return speedMultiplicand <= 0 ? null : - Optional.ofNullable(this.getTree()) - .map(tree -> new CacheSpeed(originPos, originalSpeed / ((float) speedMultiplicand * tree.getLogCount()))) - .orElse(null); - } - catch(TreeTooBigException e){ - return null; + var cacheSpeed = speedCache.compute(player.getUUID(), (uuid, speed) -> { + if(isNull(speed) || !speed.isValid(originPos)){ + speed = getSpeed(originalSpeed); } + return speed; + }); + return Optional.ofNullable(cacheSpeed).map(CacheSpeed::getSpeed); + } + + @Nullable + private CacheSpeed getSpeed(float originalSpeed){ + var speedMultiplicand = mod.getConfiguration().getTools().getSpeedMultiplicand(); + try{ + return speedMultiplicand <= 0 + ? null + : Optional.ofNullable(getTree()) + .map(tree -> new CacheSpeed(originPos, originalSpeed / ((float) speedMultiplicand * tree.getLogCount()))) + .orElse(null); + } + catch(TreeTooBigException e){ + return null; } } @@ -162,21 +154,10 @@ private boolean shouldPreserveTool(@NonNull IPlayer player){ var handItem = player.getMainHandItem(); return mod.getConfiguration().getTools().getDurabilityMode().shouldPreserve(handItem.getDurability()); } - @NonNull - public TreeHandlerState create(@NonNull ILevel level, @NonNull IPlayer player, @NonNull IBlockPos originPos, @NonNull IBlockState originState) { - return create(level, player, originPos, originState, null); - } - - - @NonNull - public TreeHandlerState create(@NonNull ILevel level, @NonNull IPlayer player, @NonNull IBlockPos originPos, @NonNull IBlockState originState, @Nullable IBlockEntity originEntity) { - return new TreeHandlerState(player, level, originPos, originState, originEntity); - } @NonNull private BreakMode getBreakMode(@NonNull IItemStack itemStack){ - return itemStack.getBreakModeFromEnchant() - .orElseGet(() -> mod.getConfiguration().getTrees().getBreakMode()); + return itemStack.getBreakModeFromEnchant().orElseGet(() -> mod.getConfiguration().getTrees().getBreakMode()); } @NonNull diff --git a/common/src/main/java/fr/rakambda/fallingtree/common/tree/TreeHandlerFactory.java b/common/src/main/java/fr/rakambda/fallingtree/common/tree/TreeHandlerFactory.java new file mode 100644 index 00000000..30e55c59 --- /dev/null +++ b/common/src/main/java/fr/rakambda/fallingtree/common/tree/TreeHandlerFactory.java @@ -0,0 +1,54 @@ +package fr.rakambda.fallingtree.common.tree; + +import fr.rakambda.fallingtree.common.FallingTreeCommon; +import fr.rakambda.fallingtree.common.config.enums.BreakMode; +import fr.rakambda.fallingtree.common.tree.breaking.FallingAnimationTreeBreakingHandler; +import fr.rakambda.fallingtree.common.tree.breaking.FallingAnimationTreeBreakingHandler.FallingAnimationTreeBreakingConfig; +import fr.rakambda.fallingtree.common.tree.breaking.ITreeBreakingHandler; +import fr.rakambda.fallingtree.common.tree.breaking.InstantaneousTreeBreakingHandler; +import fr.rakambda.fallingtree.common.tree.breaking.ShiftDownTreeBreakingHandler; +import fr.rakambda.fallingtree.common.utils.CacheSpeed; +import fr.rakambda.fallingtree.common.wrapper.IBlockEntity; +import fr.rakambda.fallingtree.common.wrapper.IBlockPos; +import fr.rakambda.fallingtree.common.wrapper.IBlockState; +import fr.rakambda.fallingtree.common.wrapper.IItemStack; +import fr.rakambda.fallingtree.common.wrapper.ILevel; +import fr.rakambda.fallingtree.common.wrapper.IPlayer; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +@Log4j2 +@RequiredArgsConstructor +public class TreeHandlerFactory{ + @NonNull + private final FallingTreeCommon mod; + private final Map speedCache = new ConcurrentHashMap<>(); + + @NonNull + public TreeHandler create(@NonNull ILevel level, @NonNull IPlayer player, @NonNull IBlockPos originPos, @NonNull IBlockState originState, @Nullable IBlockEntity originEntity){ + return new TreeHandler(mod, speedCache, player, level, originPos, originState, originEntity); + } + + @NonNull + private BreakMode getBreakMode(@NonNull IItemStack itemStack){ + return itemStack.getBreakModeFromEnchant() + .orElseGet(() -> mod.getConfiguration().getTrees().getBreakMode()); + } + + @NonNull + private ITreeBreakingHandler getBreakingHandler(@NonNull BreakMode breakMode){ + return switch(breakMode){ + case INSTANTANEOUS -> InstantaneousTreeBreakingHandler.getInstance(mod); + case FALL_ITEM -> FallingAnimationTreeBreakingHandler.getInstance(mod, FallingAnimationTreeBreakingConfig.withRandomSpread(true, true)); + case FALL_ITEM_STRAIGHT -> FallingAnimationTreeBreakingHandler.getInstance(mod, FallingAnimationTreeBreakingConfig.straightDown(true, true)); + case FALL_BLOCK -> FallingAnimationTreeBreakingHandler.getInstance(mod, FallingAnimationTreeBreakingConfig.withRandomSpread(false, true)); + case FALL_ALL_BLOCK -> FallingAnimationTreeBreakingHandler.getInstance(mod, FallingAnimationTreeBreakingConfig.withRandomSpread(false, false)); + case SHIFT_DOWN -> ShiftDownTreeBreakingHandler.getInstance(mod); + }; + } +} diff --git a/common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/ImmutableTree.java b/common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/ImmutableTree.java new file mode 100644 index 00000000..85318a41 --- /dev/null +++ b/common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/ImmutableTree.java @@ -0,0 +1,158 @@ +package fr.rakambda.fallingtree.common.tree.builder; + +import fr.rakambda.fallingtree.common.tree.Tree; +import fr.rakambda.fallingtree.common.tree.TreePart; +import fr.rakambda.fallingtree.common.tree.TreePartType; +import fr.rakambda.fallingtree.common.wrapper.IBlockPos; +import fr.rakambda.fallingtree.common.wrapper.ILevel; +import lombok.Getter; +import org.jspecify.annotations.NonNull; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; +import static java.util.Comparator.comparingInt; +import static java.util.stream.Collectors.toSet; +import static lombok.AccessLevel.PROTECTED; + +public class ImmutableTree implements Tree{ + @Getter + @NonNull + private final ILevel level; + @Getter + @NonNull + private final IBlockPos hitPos; + @NonNull + @Getter + private final Set parts; + + @Getter(PROTECTED) + private final Map partCounts; + @Getter(PROTECTED) + private final PartOfInterestCache topMostLogCache; + @Getter(PROTECTED) + private final PartOfInterestCache bottomMostLogCache; + @Getter(PROTECTED) + private final PartOfInterestCache startLogCache; + + public ImmutableTree(@NonNull ILevel level, @NonNull IBlockPos hitPos, @NonNull Set parts){ + this( + level, hitPos, parts, + new LinkedHashMap<>(), + new PartOfInterestCache(TreePartType.LOG, parts, s -> s.max(Comparator.comparing(treePart -> treePart.blockPos().getY()))), + new PartOfInterestCache(TreePartType.LOG, parts, s -> s.min(Comparator.comparing(treePart -> treePart.blockPos().getY()))), + new PartOfInterestCache(TreePartType.LOG_START, parts, Stream::findFirst) + ); + } + + protected ImmutableTree(@NonNull ILevel level, @NonNull IBlockPos hitPos, @NonNull Set parts, Map partCounts, PartOfInterestCache topMostLogCache, PartOfInterestCache bottomMostLogCache, PartOfInterestCache startLogCache){ + this.level = level; + this.hitPos = hitPos; + this.parts = parts; + this.partCounts = partCounts; + this.topMostLogCache = topMostLogCache; + this.bottomMostLogCache = bottomMostLogCache; + this.startLogCache = startLogCache; + } + + @Override + public int getBreakableCount(){ + return Arrays.stream(TreePartType.getValues()) + .filter(TreePartType::isBreakable) + .mapToInt(this::getPartCount) + .sum(); + } + + protected int getPartCount(@NonNull TreePartType treePartType){ + return partCounts.computeIfAbsent(treePartType, key -> 0); + } + + @Override + @NonNull + public Optional getLastSequencePart(){ + return getParts().stream() + .max(comparingInt(TreePart::sequence)); + } + + @Override + @NonNull + public Optional getLastSequenceLogPart(){ + return getParts().stream() + .filter(part -> part.treePartType().isLog()) + .max(comparingInt(TreePart::sequence)); + } + + @Override + @NonNull + public Collection getBreakableLogs(){ + return getParts().stream() + .filter(part -> part.treePartType().isLog()) + .filter(part -> part.treePartType().isBreakable()) + .collect(toSet()); + } + + @Override + @NonNull + public Collection getBreakableParts(){ + return getParts().stream() + .filter(part -> part.treePartType().isBreakable()) + .collect(toSet()); + } + + @Override + public int getLogCount(){ + return getPartCount(TreePartType.LOG); + } + + @Override + @NonNull + public Optional getTopMostLog(){ + return getTopMostLogCache().getPart().map(TreePart::blockPos); + } + + @Override + @NonNull + public Optional getBottomMostLog(){ + return getBottomMostLogCache().getPart().map(TreePart::blockPos); + } + + @Override + @NonNull + public Optional getTopMostPart(){ + return getParts().stream() + .map(TreePart::blockPos) + .max(comparingInt(IBlockPos::getY)); + } + + @Override + @NonNull + public Collection getNetherWarts(){ + return getParts().stream() + .filter(part -> part.treePartType() == TreePartType.NETHER_WART) + .collect(toSet()); + } + + @Override + @NonNull + public Collection getMangroveRoots(){ + return getParts().stream() + .filter(part -> part.treePartType() == TreePartType.MANGROVE_ROOTS) + .collect(toSet()); + } + + @Override + @NonNull + public Optional getStart(){ + return getStartLogCache().getPart(); + } + + @Override + @NonNull + public Stream getPartsStream(){ + return getParts().stream(); + } +} diff --git a/common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/MutableTree.java b/common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/MutableTree.java index 1521d854..f6fd5e83 100644 --- a/common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/MutableTree.java +++ b/common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/MutableTree.java @@ -1,43 +1,26 @@ package fr.rakambda.fallingtree.common.tree.builder; -import fr.rakambda.fallingtree.common.tree.Tree; import fr.rakambda.fallingtree.common.tree.TreePart; import fr.rakambda.fallingtree.common.tree.TreePartType; import fr.rakambda.fallingtree.common.wrapper.IBlockPos; import fr.rakambda.fallingtree.common.wrapper.ILevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; -import java.util.Arrays; -import java.util.Collection; -import java.util.Comparator; -import java.util.LinkedHashMap; import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Optional; import java.util.Set; -import java.util.stream.Stream; -import static java.util.Comparator.comparingInt; import static java.util.Objects.isNull; -import static java.util.stream.Collectors.toSet; -@RequiredArgsConstructor -class MutableTree implements Tree{ - @Getter - @NonNull - private final ILevel level; - @Getter - @NonNull - private final IBlockPos hitPos; - @Getter - private final Set parts = new LinkedHashSet<>(); - private final Map partCounts = new LinkedHashMap<>(); - private final ExtremumBlocks extremumBlocks = new ExtremumBlocks(); +public class MutableTree extends ImmutableTree{ + public MutableTree(@NonNull ILevel level, @NonNull IBlockPos hitPos){ + this(level, hitPos, new LinkedHashSet<>()); + } + + public MutableTree(@NonNull ILevel level, @NonNull IBlockPos hitPos, @NonNull Set parts){ + super(level, hitPos, parts); + } - void addPart(@NonNull TreePart treePart){ - parts.add(treePart); - partCounts.compute(treePart.treePartType(), (key, value) -> { + public void addPart(@NonNull TreePart treePart){ + getParts().add(treePart); + getPartCounts().compute(treePart.treePartType(), (key, value) -> { if(isNull(value)){ return 1; } @@ -45,175 +28,29 @@ void addPart(@NonNull TreePart treePart){ }); } - void removePartsHigherThan(int y, @NonNull TreePartType partType){ - parts.removeIf(part -> { - if(part.treePartType() == partType && part.blockPos().getY() > y){ - decrementPartCount(partType); - this.extremumBlocks.onRemoved(part); - return true; + public void removePartsHigherThan(int y, @NonNull TreePartType partType){ + getParts().removeIf(part -> { + if(part.treePartType() != partType || part.blockPos().getY() <= y){ + return false; } - return false; + getPartCounts().computeIfPresent(partType, (type, count) -> Math.max(0, count - 1)); + onPartRemoved(part); + return true; }); } - @Override - public int getBreakableCount(){ - return Arrays.stream(TreePartType.getValues()) - .filter(TreePartType::isBreakable) - .mapToInt(this::getPartCount) - .sum(); - } - - private int getPartCount(@NonNull TreePartType treePartType){ - return partCounts.computeIfAbsent(treePartType, key -> 0); - } - - int getSize(){ - return partCounts.values().stream().mapToInt(i -> i).sum(); - } - - private void decrementPartCount(@NonNull TreePartType partType){ - partCounts.computeIfPresent(partType, (type, count) -> Math.max(0, count - 1)); - } - - @Override - public @NonNull Optional getLastSequencePart(){ - return getParts().stream() - .max(comparingInt(TreePart::sequence)); - } - - @Override - public @NonNull Optional getLastSequenceLogPart(){ - return getParts().stream() - .filter(part -> part.treePartType().isLog()) - .max(comparingInt(TreePart::sequence)); - } - - @Override - public @NonNull Collection getBreakableLogs(){ - return getParts().stream() - .filter(part -> part.treePartType().isLog()) - .filter(part -> part.treePartType().isBreakable()) - .collect(toSet()); - } - - @Override - public @NonNull Collection getBreakableParts(){ - return getParts().stream() - .filter(part -> part.treePartType().isBreakable()) - .collect(toSet()); - } - - @Override - public int getLogCount(){ - return getPartCount(TreePartType.LOG); - } - - private class ExtremumBlocks{ - - @Nullable - private TreePart topMostLog = null; - - @Nullable - private TreePart bottomMostLog = null; - - @Nullable - private TreePart logStart = null; - - public Optional getTopMostLog(){ - if(topMostLog != null){ - return Optional.of(topMostLog); - } - - this.topMostLog = parts.stream() - .filter(treePart -> treePart.treePartType() == TreePartType.LOG) - .max(Comparator.comparing(treePart -> treePart.blockPos().getY())) - .orElse(null); - - return Optional.ofNullable(topMostLog); - } - - public Optional getBottomMostLog(){ - if(bottomMostLog != null){ - return Optional.of(bottomMostLog); - } - - this.bottomMostLog = parts.stream() - .filter(treePart -> treePart.treePartType() == TreePartType.LOG) - .min(Comparator.comparing(treePart -> treePart.blockPos().getY())) - .orElse(null); - - return Optional.ofNullable(bottomMostLog); - } - - public Optional getLogStart(){ - if(logStart != null){ - return Optional.of(logStart); - } - - this.logStart = parts.stream() - .filter(treePart -> treePart.treePartType() == TreePartType.LOG_START) - .findFirst() - .orElse(null); - - return Optional.ofNullable(logStart); - } - - public void onRemoved(@NonNull final TreePart removedTreePart){ - if(topMostLog != null && topMostLog.equals(removedTreePart)){ - this.topMostLog = null; - } - - if(bottomMostLog != null && bottomMostLog.equals(removedTreePart)){ - this.bottomMostLog = null; - } - - if(logStart != null && logStart.equals(removedTreePart)){ - this.logStart = null; - } - } - } - - @Override - public @NonNull Optional getTopMostLog(){ - return extremumBlocks.getTopMostLog() - .map(TreePart::blockPos); - } - - @Override - public @NonNull Optional getBottomMostLog(){ - return extremumBlocks.getBottomMostLog() - .map(TreePart::blockPos); - } - - @Override - public @NonNull Optional getTopMostPart(){ - return getParts().stream() - .map(TreePart::blockPos) - .max(comparingInt(IBlockPos::getY)); + public int getSize(){ + return getPartCounts().values().stream().mapToInt(i -> i).sum(); } - @Override - public @NonNull Collection getNetherWarts(){ - return getParts().stream() - .filter(part -> part.treePartType() == TreePartType.NETHER_WART) - .collect(toSet()); + private void onPartRemoved(@NonNull TreePart removedTreePart){ + getTopMostLogCache().invalidateIfEqual(removedTreePart); + getBottomMostLogCache().invalidateIfEqual(removedTreePart); + getStartLogCache().invalidateIfEqual(removedTreePart); } - @Override - public @NonNull Collection getMangroveRoots(){ - return getParts().stream() - .filter(part -> part.treePartType() == TreePartType.MANGROVE_ROOTS) - .collect(toSet()); - } - - @Override - public @NonNull Optional getStart(){ - return extremumBlocks.getLogStart(); - } - - @Override - public @NonNull Stream getPartsStream(){ - return getParts().stream(); + @NonNull + public ImmutableTree asImmutableTree(){ + return new ImmutableTree(getLevel(), getHitPos(), getParts(), getPartCounts(), getTopMostLogCache(), getBottomMostLogCache(), getStartLogCache()); } } diff --git a/common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/PartOfInterestCache.java b/common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/PartOfInterestCache.java new file mode 100644 index 00000000..286ea958 --- /dev/null +++ b/common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/PartOfInterestCache.java @@ -0,0 +1,46 @@ +package fr.rakambda.fallingtree.common.tree.builder; + +import fr.rakambda.fallingtree.common.tree.TreePart; +import fr.rakambda.fallingtree.common.tree.TreePartType; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import java.util.Collection; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +@RequiredArgsConstructor +public class PartOfInterestCache{ + @NonNull + @Getter + private final TreePartType treePartType; + @NonNull + private final Collection parts; + @NonNull + private final Function, Optional> selector; + + @Nullable + private TreePart cached = null; + + @NonNull + public Optional getPart(){ + if(cached != null){ + return Optional.of(cached); + } + + cached = selector.apply(parts.stream().filter(treePart -> treePart.treePartType() == treePartType)).orElse(null); + return Optional.ofNullable(cached); + } + + public void invalidate(){ + cached = null; + } + + public void invalidateIfEqual(@NonNull TreePart treePart){ + if(treePart.equals(cached)){ + invalidate(); + } + } +} diff --git a/common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/TreeBuilder.java b/common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/TreeBuilder.java index 45160fdf..2b67521e 100644 --- a/common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/TreeBuilder.java +++ b/common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/TreeBuilder.java @@ -103,7 +103,7 @@ public Optional getTree(@NonNull IPlayer player, @NonNull ILevel level, @N } } - return Optional.of(tree); + return Optional.of(tree.asImmutableTree()); } private static void postProcess(@NonNull MutableTree tree){ diff --git a/fabric/src/main/java/fr/rakambda/fallingtree/fabric/event/BlockBreakListener.java b/fabric/src/main/java/fr/rakambda/fallingtree/fabric/event/BlockBreakListener.java index 3bc6987d..43b952c7 100644 --- a/fabric/src/main/java/fr/rakambda/fallingtree/fabric/event/BlockBreakListener.java +++ b/fabric/src/main/java/fr/rakambda/fallingtree/fabric/event/BlockBreakListener.java @@ -39,7 +39,7 @@ public boolean beforeBlockBreak(Level level, Player player, BlockPos blockPos, B return true; } - return !mod.getTreeHandler().create(wrappedLevel, wrappedPlayer, wrappedPos, wrappedState, wrappedEntity).shouldCancelEvent(); + return !mod.getTreeHandler(wrappedLevel, wrappedPlayer, wrappedPos, wrappedState, wrappedEntity).shouldCancelEvent(); } @Override @@ -50,6 +50,6 @@ public void afterBlockBreak(Level level, Player player, BlockPos blockPos, Block var wrappedState = new BlockStateWrapper(blockState); var wrappedEntity = Optional.ofNullable(blockEntity).map(BlockEntityWrapper::new).orElse(null); - mod.getTreeHandler().create(wrappedLevel, wrappedPlayer, wrappedPos, wrappedState, wrappedEntity).breakTree(false); + mod.getTreeHandler(wrappedLevel, wrappedPlayer, wrappedPos, wrappedState, wrappedEntity).breakTree(false); } } diff --git a/fabric/src/main/java/fr/rakambda/fallingtree/fabric/mixin/AbstractBlockMixin.java b/fabric/src/main/java/fr/rakambda/fallingtree/fabric/mixin/AbstractBlockMixin.java index 351557d9..ba1feaab 100644 --- a/fabric/src/main/java/fr/rakambda/fallingtree/fabric/mixin/AbstractBlockMixin.java +++ b/fabric/src/main/java/fr/rakambda/fallingtree/fabric/mixin/AbstractBlockMixin.java @@ -22,7 +22,7 @@ public void calcBlockBreakingDelta(BlockState blockState, Player player, BlockGe var wrappedPos = new BlockPosWrapper(blockPos); var wrappedState = new BlockStateWrapper(blockState); - var result = FallingTree.getMod().getTreeHandler().getBreakSpeed(wrappedPlayer, wrappedPos, wrappedState, callbackInfoReturnable.getReturnValue()); + var result = FallingTree.getMod().getTreeHandler(wrappedPlayer.getLevel(), wrappedPlayer, wrappedPos, wrappedState, null).getBreakSpeed(callbackInfoReturnable.getReturnValue()); if(result.isEmpty()){ return; } diff --git a/forge/src/main/java/fr/rakambda/fallingtree/forge/event/BlockBreakListener.java b/forge/src/main/java/fr/rakambda/fallingtree/forge/event/BlockBreakListener.java index 7adf7c69..0fc90704 100644 --- a/forge/src/main/java/fr/rakambda/fallingtree/forge/event/BlockBreakListener.java +++ b/forge/src/main/java/fr/rakambda/fallingtree/forge/event/BlockBreakListener.java @@ -29,7 +29,7 @@ public void onBreakSpeed(@Nonnull PlayerEvent.BreakSpeed event){ var wrappedPos = new BlockPosWrapper(optionalPos.get()); var wrappedState = new BlockStateWrapper(event.getState()); - var result = mod.getTreeHandler().create(wrappedPlayer.getLevel(), wrappedPlayer, wrappedPos, wrappedState).getBreakSpeed(event.getNewSpeed()); + var result = mod.getTreeHandler(wrappedPlayer.getLevel(), wrappedPlayer, wrappedPos, wrappedState, null).getBreakSpeed(event.getNewSpeed()); if(result.isEmpty()){ return; } @@ -48,7 +48,7 @@ public boolean onBlockBreakEvent(@Nonnull BlockEvent.BreakEvent event){ var wrappedState = new BlockStateWrapper(event.getState()); var wrappedEntity = wrappedLevel.getBlockEntity(wrappedPos); - final var treeHandler = mod.getTreeHandler().create(wrappedLevel, wrappedPlayer, wrappedPos, wrappedState, wrappedEntity); + var treeHandler = mod.getTreeHandler(wrappedLevel, wrappedPlayer, wrappedPos, wrappedState, wrappedEntity); if(treeHandler.shouldCancelEvent()){ return true; diff --git a/neoforge/src/main/java/fr/rakambda/fallingtree/neoforge/event/BlockBreakListener.java b/neoforge/src/main/java/fr/rakambda/fallingtree/neoforge/event/BlockBreakListener.java index c534799d..0150550c 100644 --- a/neoforge/src/main/java/fr/rakambda/fallingtree/neoforge/event/BlockBreakListener.java +++ b/neoforge/src/main/java/fr/rakambda/fallingtree/neoforge/event/BlockBreakListener.java @@ -35,7 +35,7 @@ public void onBreakSpeed(@Nonnull PlayerEvent.BreakSpeed event){ var wrappedPos = new BlockPosWrapper(optionalPos.get()); var wrappedState = new BlockStateWrapper(event.getState()); - var result = mod.getTreeHandler().create(wrappedPlayer.getLevel(), wrappedPlayer, wrappedPos, wrappedState).getBreakSpeed(event.getNewSpeed()); + var result = mod.getTreeHandler(wrappedPlayer.getLevel(), wrappedPlayer, wrappedPos, wrappedState, null).getBreakSpeed(event.getNewSpeed()); if(result.isEmpty()){ return; } @@ -58,7 +58,7 @@ public void onBlockBreakEvent(@Nonnull BreakBlockEvent event){ var wrappedState = new BlockStateWrapper(event.getState()); var wrappedEntity = wrappedLevel.getBlockEntity(wrappedPos); - final var treeHandler = mod.getTreeHandler().create(wrappedLevel, wrappedPlayer, wrappedPos, wrappedState, wrappedEntity); + final var treeHandler = mod.getTreeHandler(wrappedLevel, wrappedPlayer, wrappedPos, wrappedState, wrappedEntity); if(treeHandler.shouldCancelEvent()){ event.setCanceled(true); From cacbb13e895d07c40153bdc3aa9b2310d9bbb5f1 Mon Sep 17 00:00:00 2001 From: Thomas Couchoud <1688389+Rakambda@users.noreply.github.com> Date: Wed, 20 May 2026 14:37:19 +0200 Subject: [PATCH 5/5] Remove unused methods --- .../common/tree/TreeHandlerFactory.java | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/common/src/main/java/fr/rakambda/fallingtree/common/tree/TreeHandlerFactory.java b/common/src/main/java/fr/rakambda/fallingtree/common/tree/TreeHandlerFactory.java index 30e55c59..d706df5d 100644 --- a/common/src/main/java/fr/rakambda/fallingtree/common/tree/TreeHandlerFactory.java +++ b/common/src/main/java/fr/rakambda/fallingtree/common/tree/TreeHandlerFactory.java @@ -1,17 +1,10 @@ package fr.rakambda.fallingtree.common.tree; import fr.rakambda.fallingtree.common.FallingTreeCommon; -import fr.rakambda.fallingtree.common.config.enums.BreakMode; -import fr.rakambda.fallingtree.common.tree.breaking.FallingAnimationTreeBreakingHandler; -import fr.rakambda.fallingtree.common.tree.breaking.FallingAnimationTreeBreakingHandler.FallingAnimationTreeBreakingConfig; -import fr.rakambda.fallingtree.common.tree.breaking.ITreeBreakingHandler; -import fr.rakambda.fallingtree.common.tree.breaking.InstantaneousTreeBreakingHandler; -import fr.rakambda.fallingtree.common.tree.breaking.ShiftDownTreeBreakingHandler; import fr.rakambda.fallingtree.common.utils.CacheSpeed; import fr.rakambda.fallingtree.common.wrapper.IBlockEntity; import fr.rakambda.fallingtree.common.wrapper.IBlockPos; import fr.rakambda.fallingtree.common.wrapper.IBlockState; -import fr.rakambda.fallingtree.common.wrapper.IItemStack; import fr.rakambda.fallingtree.common.wrapper.ILevel; import fr.rakambda.fallingtree.common.wrapper.IPlayer; import lombok.RequiredArgsConstructor; @@ -33,22 +26,4 @@ public class TreeHandlerFactory{ public TreeHandler create(@NonNull ILevel level, @NonNull IPlayer player, @NonNull IBlockPos originPos, @NonNull IBlockState originState, @Nullable IBlockEntity originEntity){ return new TreeHandler(mod, speedCache, player, level, originPos, originState, originEntity); } - - @NonNull - private BreakMode getBreakMode(@NonNull IItemStack itemStack){ - return itemStack.getBreakModeFromEnchant() - .orElseGet(() -> mod.getConfiguration().getTrees().getBreakMode()); - } - - @NonNull - private ITreeBreakingHandler getBreakingHandler(@NonNull BreakMode breakMode){ - return switch(breakMode){ - case INSTANTANEOUS -> InstantaneousTreeBreakingHandler.getInstance(mod); - case FALL_ITEM -> FallingAnimationTreeBreakingHandler.getInstance(mod, FallingAnimationTreeBreakingConfig.withRandomSpread(true, true)); - case FALL_ITEM_STRAIGHT -> FallingAnimationTreeBreakingHandler.getInstance(mod, FallingAnimationTreeBreakingConfig.straightDown(true, true)); - case FALL_BLOCK -> FallingAnimationTreeBreakingHandler.getInstance(mod, FallingAnimationTreeBreakingConfig.withRandomSpread(false, true)); - case FALL_ALL_BLOCK -> FallingAnimationTreeBreakingHandler.getInstance(mod, FallingAnimationTreeBreakingConfig.withRandomSpread(false, false)); - case SHIFT_DOWN -> ShiftDownTreeBreakingHandler.getInstance(mod); - }; - } }