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 : - * - * - * @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/Tree.java b/common/src/main/java/fr/rakambda/fallingtree/common/tree/Tree.java index 7cc324c2..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,142 +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 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<>(); +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; - }); - } + 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(); - @NonNull - public Optional getTopMostLog(){ - return getBreakableLogs().stream() - .map(TreePart::blockPos) - .max(comparingInt(IBlockPos::getY)); - } + @NonNull Optional getStart(); - @NonNull - public Optional getBottomMostLog(){ - return getBreakableLogs().stream() - .map(TreePart::blockPos) - .min(comparingInt(IBlockPos::getY)); - } + @NonNull Stream getPartsStream(); - @NonNull - private Optional getTopMostPart(){ - return getParts().stream() - .map(TreePart::blockPos) - .max(comparingInt(IBlockPos::getY)); - } + ILevel getLevel(); - @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 getParts().stream() - .filter(part -> part.treePartType() == TreePartType.LOG_START) - .findFirst(); - } + IBlockPos getHitPos(); } + 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..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,9 +32,32 @@ public class TreeHandler{ @NonNull private final FallingTreeCommon mod; - private final Map speedCache = new ConcurrentHashMap<>(); + @NonNull + private final Map speedCache; + + @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; - public boolean shouldCancelEvent(@NonNull ILevel level, @NonNull IPlayer player, @NonNull IBlockPos originPos, @NonNull IBlockState originState, @Nullable 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); + } + return cachedTree; + } + + public boolean shouldCancelEvent(){ if(!mod.isPlayerInRightState(player)){ return false; } @@ -42,7 +65,7 @@ public boolean shouldCancelEvent(@NonNull ILevel level, @NonNull IPlayer player, return true; } try{ - mod.getTreeBuilder().getTree(player, level, originPos, originState, originEntity).isEmpty(); + getTree(); } catch(TreeTooBigException e){ return false; @@ -50,13 +73,8 @@ public boolean shouldCancelEvent(@NonNull ILevel level, @NonNull IPlayer player, return false; } - private boolean shouldPreserveTool(@NonNull IPlayer player){ - var handItem = player.getMainHandItem(); - return mod.getConfiguration().getTools().getDurabilityMode().shouldPreserve(handItem.getDurability()); - } - @NonNull - public IBreakAttemptResult breakTree(boolean isCancellable, @NonNull ILevel level, @NonNull IPlayer player, @NonNull IBlockPos originPos, @NonNull IBlockState originState, @Nullable IBlockEntity originEntity){ + public IBreakAttemptResult breakTree(boolean isCancellable){ if(!level.isServer()){ return AbortedResult.NOT_SERVER; } @@ -74,12 +92,11 @@ public IBreakAttemptResult breakTree(boolean isCancellable, @NonNull ILevel leve } try{ - var treeOptional = mod.getTreeBuilder().getTree(player, level, originPos, originState, originEntity); - if(treeOptional.isEmpty()){ + var tree = getTree(); + if(tree == null){ return AbortedResult.NO_SUCH_TREE; } - var tree = treeOptional.get(); var breakMode = getBreakMode(player.getMainHandItem()); return getBreakingHandler(breakMode).breakTree(isCancellable, player, tree); } @@ -98,25 +115,7 @@ public IBreakAttemptResult breakTree(boolean isCancellable, @NonNull ILevel leve } @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); - }; - } - - @NonNull - public Optional getBreakSpeed(@NonNull IPlayer player, @NonNull IBlockPos blockPos, @NonNull IBlockState blockState, float originalSpeed){ + public Optional getBreakSpeed(float originalSpeed){ if(!mod.getConfiguration().getTrees().isTreeBreaking()){ return Optional.empty(); } @@ -128,8 +127,8 @@ public Optional getBreakSpeed(@NonNull IPlayer player, @NonNull IBlockPos } var cacheSpeed = speedCache.compute(player.getUUID(), (uuid, speed) -> { - if(isNull(speed) || !speed.isValid(blockPos)){ - speed = getSpeed(player, blockPos, blockState, originalSpeed); + if(isNull(speed) || !speed.isValid(originPos)){ + speed = getSpeed(originalSpeed); } return speed; }); @@ -137,16 +136,39 @@ public Optional getBreakSpeed(@NonNull IPlayer player, @NonNull IBlockPos } @Nullable - private CacheSpeed getSpeed(@NonNull IPlayer player, @NonNull IBlockPos pos, @NonNull IBlockState blockState, float originalSpeed){ + private CacheSpeed getSpeed(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()))) + return speedMultiplicand <= 0 + ? null + : Optional.ofNullable(getTree()) + .map(tree -> new CacheSpeed(originPos, originalSpeed / ((float) speedMultiplicand * tree.getLogCount()))) .orElse(null); } catch(TreeTooBigException e){ return null; } } + + private boolean shouldPreserveTool(@NonNull IPlayer player){ + var handItem = player.getMainHandItem(); + return mod.getConfiguration().getTools().getDurabilityMode().shouldPreserve(handItem.getDurability()); + } + + @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/TreeHandlerFactory.java b/common/src/main/java/fr/rakambda/fallingtree/common/tree/TreeHandlerFactory.java new file mode 100644 index 00000000..d706df5d --- /dev/null +++ b/common/src/main/java/fr/rakambda/fallingtree/common/tree/TreeHandlerFactory.java @@ -0,0 +1,29 @@ +package fr.rakambda.fallingtree.common.tree; + +import fr.rakambda.fallingtree.common.FallingTreeCommon; +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.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); + } +} 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/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 new file mode 100644 index 00000000..f6fd5e83 --- /dev/null +++ b/common/src/main/java/fr/rakambda/fallingtree/common/tree/builder/MutableTree.java @@ -0,0 +1,56 @@ +package fr.rakambda.fallingtree.common.tree.builder; + +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 org.jspecify.annotations.NonNull; +import java.util.LinkedHashSet; +import java.util.Set; +import static java.util.Objects.isNull; + +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); + } + + public void addPart(@NonNull TreePart treePart){ + getParts().add(treePart); + getPartCounts().compute(treePart.treePartType(), (key, value) -> { + if(isNull(value)){ + return 1; + } + return value + 1; + }); + } + + public void removePartsHigherThan(int y, @NonNull TreePartType partType){ + getParts().removeIf(part -> { + if(part.treePartType() != partType || part.blockPos().getY() <= y){ + return false; + } + getPartCounts().computeIfPresent(partType, (type, count) -> Math.max(0, count - 1)); + onPartRemoved(part); + return true; + }); + } + + public int getSize(){ + return getPartCounts().values().stream().mapToInt(i -> i).sum(); + } + + private void onPartRemoved(@NonNull TreePart removedTreePart){ + getTopMostLogCache().invalidateIfEqual(removedTreePart); + getBottomMostLogCache().invalidateIfEqual(removedTreePart); + getStartLogCache().invalidateIfEqual(removedTreePart); + } + + @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 40e53aa3..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 @@ -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)); @@ -103,10 +103,10 @@ public Optional getTree(@NonNull IPlayer player, @NonNull ILevel level, @N } } - return Optional.of(tree); + return Optional.of(tree.asImmutableTree()); } - 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)); } 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..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().shouldCancelEvent(wrappedLevel, wrappedPlayer, wrappedPos, wrappedState, wrappedEntity); + 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().breakTree(false, wrappedLevel, wrappedPlayer, wrappedPos, wrappedState, wrappedEntity); + 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 92edcdaf..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().getBreakSpeed(wrappedPlayer, wrappedPos, wrappedState, event.getNewSpeed()); + var result = mod.getTreeHandler(wrappedPlayer.getLevel(), wrappedPlayer, wrappedPos, wrappedState, null).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)){ + var treeHandler = mod.getTreeHandler(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..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 @@ -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(wrappedPlayer.getLevel(), wrappedPlayer, wrappedPos, wrappedState, null).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(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); }