290 lines
13 KiB
Java
290 lines
13 KiB
Java
package com.songoda.ultimatetimber.manager;
|
|
|
|
import com.songoda.core.compatibility.CompatibleMaterial;
|
|
import com.songoda.ultimatetimber.UltimateTimber;
|
|
import com.songoda.ultimatetimber.tree.*;
|
|
import org.bukkit.Location;
|
|
import org.bukkit.block.Block;
|
|
import org.bukkit.block.BlockFace;
|
|
import org.bukkit.util.Vector;
|
|
|
|
import java.util.*;
|
|
|
|
public class TreeDetectionManager extends Manager {
|
|
|
|
private final Set<Vector> VALID_TRUNK_OFFSETS, VALID_BRANCH_OFFSETS, VALID_LEAF_OFFSETS;
|
|
|
|
private TreeDefinitionManager treeDefinitionManager;
|
|
private PlacedBlockManager placedBlockManager;
|
|
private int numLeavesRequiredForTree;
|
|
private boolean onlyBreakLogsUpwards, entireTreeBase, destroyLeaves;
|
|
|
|
public TreeDetectionManager(UltimateTimber ultimateTimber) {
|
|
super(ultimateTimber);
|
|
|
|
this.VALID_BRANCH_OFFSETS = new HashSet<>();
|
|
this.VALID_TRUNK_OFFSETS = new HashSet<>();
|
|
this.VALID_LEAF_OFFSETS = new HashSet<>();
|
|
|
|
// 3x2x3 centered around log, excluding -y axis
|
|
for (int y = 0; y <= 1; y++)
|
|
for (int x = -1; x <= 1; x++)
|
|
for (int z = -1; z <= 1; z++)
|
|
this.VALID_BRANCH_OFFSETS.add(new Vector(x, y, z));
|
|
|
|
// 3x3x3 centered around log
|
|
for (int y = -1; y <= 1; y++)
|
|
for (int x = -1; x <= 1; x++)
|
|
for (int z = -1; z <= 1; z++)
|
|
this.VALID_TRUNK_OFFSETS.add(new Vector(x, y, z));
|
|
|
|
// Adjacent blocks to log
|
|
for (int i = -1; i <= 1; i += 2) {
|
|
this.VALID_LEAF_OFFSETS.add(new Vector(i, 0, 0));
|
|
this.VALID_LEAF_OFFSETS.add(new Vector(0, i, 0));
|
|
this.VALID_LEAF_OFFSETS.add(new Vector(0, 0, i));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void reload() {
|
|
this.treeDefinitionManager = this.plugin.getTreeDefinitionManager();
|
|
this.placedBlockManager = this.plugin.getPlacedBlockManager();
|
|
this.numLeavesRequiredForTree = ConfigurationManager.Setting.LEAVES_REQUIRED_FOR_TREE.getInt();
|
|
this.onlyBreakLogsUpwards = ConfigurationManager.Setting.ONLY_DETECT_LOGS_UPWARDS.getBoolean();
|
|
this.entireTreeBase = ConfigurationManager.Setting.BREAK_ENTIRE_TREE_BASE.getBoolean();
|
|
this.destroyLeaves = ConfigurationManager.Setting.DESTROY_LEAVES.getBoolean();
|
|
}
|
|
|
|
@Override
|
|
public void disable() {
|
|
|
|
}
|
|
|
|
/**
|
|
* Detects a tree given an initial starting block
|
|
*
|
|
* @param initialBlock The starting Block of the detection
|
|
* @return A DetectedTree if one was found, otherwise null
|
|
*/
|
|
public DetectedTree detectTree(Block initialBlock) {
|
|
TreeDefinitionManager treeDefinitionManager = this.plugin.getTreeDefinitionManager();
|
|
|
|
TreeBlock initialTreeBlock = new TreeBlock(initialBlock, TreeBlockType.LOG);
|
|
TreeBlockSet<Block> detectedTreeBlocks = new TreeBlockSet<>(initialTreeBlock);
|
|
Set<TreeDefinition> possibleTreeDefinitions = this.treeDefinitionManager.getTreeDefinitionsForLog(initialBlock);
|
|
|
|
if (possibleTreeDefinitions.isEmpty())
|
|
return null;
|
|
|
|
// Detect tree trunk
|
|
List<Block> trunkBlocks = new ArrayList<>();
|
|
trunkBlocks.add(initialBlock);
|
|
Block targetBlock = initialBlock;
|
|
while (this.isValidLogType(possibleTreeDefinitions, null, (targetBlock = targetBlock.getRelative(BlockFace.UP)))) {
|
|
trunkBlocks.add(targetBlock);
|
|
possibleTreeDefinitions.retainAll(this.treeDefinitionManager.narrowTreeDefinition(possibleTreeDefinitions, targetBlock, TreeBlockType.LOG));
|
|
}
|
|
|
|
if (!this.onlyBreakLogsUpwards) {
|
|
targetBlock = initialBlock;
|
|
while (this.isValidLogType(possibleTreeDefinitions, null, (targetBlock = targetBlock.getRelative(BlockFace.DOWN)))) {
|
|
trunkBlocks.add(targetBlock);
|
|
possibleTreeDefinitions.retainAll(this.treeDefinitionManager.narrowTreeDefinition(possibleTreeDefinitions, targetBlock, TreeBlockType.LOG));
|
|
}
|
|
}
|
|
|
|
// Lowest blocks at the front of the list
|
|
Collections.reverse(trunkBlocks);
|
|
|
|
// Detect branches off the main trunk
|
|
for (Block trunkBlock : trunkBlocks)
|
|
this.recursiveBranchSearch(possibleTreeDefinitions, trunkBlocks, detectedTreeBlocks, trunkBlock, initialBlock.getLocation().getBlockY());
|
|
|
|
// Detect leaves off the trunk/branches
|
|
Set<ITreeBlock<Block>> branchBlocks = new HashSet<>(detectedTreeBlocks.getLogBlocks());
|
|
for (ITreeBlock<Block> branchBlock : branchBlocks)
|
|
this.recursiveLeafSearch(possibleTreeDefinitions, detectedTreeBlocks, branchBlock.getBlock(), new HashSet<>());
|
|
|
|
// Use the first tree definition in the set
|
|
TreeDefinition actualTreeDefinition = possibleTreeDefinitions.iterator().next();
|
|
|
|
// Trees need at least a certain number of leaves
|
|
if (detectedTreeBlocks.getLeafBlocks().size() < this.numLeavesRequiredForTree)
|
|
return null;
|
|
|
|
// Remove leaves if we don't care about the leaves
|
|
if (!this.destroyLeaves)
|
|
detectedTreeBlocks.removeAll(TreeBlockType.LEAF);
|
|
|
|
// Check that the tree isn't on the ground if enabled
|
|
if (this.entireTreeBase) {
|
|
Set<Block> groundBlocks = new HashSet<>();
|
|
for (ITreeBlock<Block> treeBlock : detectedTreeBlocks.getLogBlocks())
|
|
if (treeBlock != detectedTreeBlocks.getInitialLogBlock() && treeBlock.getLocation().getBlockY() == initialBlock.getLocation().getBlockY())
|
|
groundBlocks.add(treeBlock.getBlock());
|
|
|
|
for (Block block : groundBlocks) {
|
|
Block blockBelow = block.getRelative(BlockFace.DOWN);
|
|
boolean blockBelowIsLog = this.isValidLogType(possibleTreeDefinitions, null, blockBelow);
|
|
boolean blockBelowIsSoil = false;
|
|
for (CompatibleMaterial material : treeDefinitionManager.getPlantableSoilMaterial(actualTreeDefinition)) {
|
|
if (material.equals(CompatibleMaterial.getMaterial(blockBelow))) {
|
|
blockBelowIsSoil = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (blockBelowIsLog || blockBelowIsSoil)
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return new DetectedTree(actualTreeDefinition, detectedTreeBlocks);
|
|
}
|
|
|
|
/**
|
|
* Recursively searches for branches off a given block
|
|
*
|
|
* @param treeDefinitions The possible tree definitions
|
|
* @param trunkBlocks The tree trunk blocks
|
|
* @param treeBlocks The detected tree blocks
|
|
* @param block The next block to check for a branch
|
|
* @param startingBlockY The Y coordinate of the initial block
|
|
*/
|
|
private void recursiveBranchSearch(Set<TreeDefinition> treeDefinitions, List<Block> trunkBlocks, TreeBlockSet<Block> treeBlocks, Block block, int startingBlockY) {
|
|
for (Vector offset : this.onlyBreakLogsUpwards ? this.VALID_BRANCH_OFFSETS : this.VALID_TRUNK_OFFSETS) {
|
|
Block targetBlock = block.getRelative(offset.getBlockX(), offset.getBlockY(), offset.getBlockZ());
|
|
TreeBlock treeBlock = new TreeBlock(targetBlock, TreeBlockType.LOG);
|
|
if (this.isValidLogType(treeDefinitions, trunkBlocks, targetBlock) && !treeBlocks.contains(treeBlock)) {
|
|
treeBlocks.add(treeBlock);
|
|
treeDefinitions.retainAll(this.treeDefinitionManager.narrowTreeDefinition(treeDefinitions, targetBlock, TreeBlockType.LOG));
|
|
if (!this.onlyBreakLogsUpwards || targetBlock.getLocation().getBlockY() > startingBlockY)
|
|
this.recursiveBranchSearch(treeDefinitions, trunkBlocks, treeBlocks, targetBlock, startingBlockY);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recursively searches for leaves that are next to this tree
|
|
*
|
|
* @param treeDefinitions The possible tree definitions
|
|
* @param treeBlocks The detected tree blocks
|
|
* @param block The next block to check for a leaf
|
|
*/
|
|
private void recursiveLeafSearch(Set<TreeDefinition> treeDefinitions, TreeBlockSet<Block> treeBlocks, Block block, Set<Block> visitedBlocks) {
|
|
boolean detectLeavesDiagonally = treeDefinitions.stream().anyMatch(TreeDefinition::shouldDetectLeavesDiagonally);
|
|
|
|
for (Vector offset : !detectLeavesDiagonally ? this.VALID_LEAF_OFFSETS : this.VALID_TRUNK_OFFSETS) {
|
|
Block targetBlock = block.getRelative(offset.getBlockX(), offset.getBlockY(), offset.getBlockZ());
|
|
if (visitedBlocks.contains(targetBlock))
|
|
continue;
|
|
|
|
visitedBlocks.add(targetBlock);
|
|
TreeBlock treeBlock = new TreeBlock(targetBlock, TreeBlockType.LEAF);
|
|
if (this.isValidLeafType(treeDefinitions, treeBlocks, targetBlock) && !treeBlocks.contains(treeBlock) && !this.doesLeafBorderInvalidLog(treeDefinitions, treeBlocks, targetBlock)) {
|
|
treeBlocks.add(treeBlock);
|
|
treeDefinitions.retainAll(this.treeDefinitionManager.narrowTreeDefinition(treeDefinitions, targetBlock, TreeBlockType.LEAF));
|
|
this.recursiveLeafSearch(treeDefinitions, treeBlocks, targetBlock, visitedBlocks);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if a leaf is bordering a log that isn't part of this tree
|
|
*
|
|
* @param treeDefinitions The possible tree definitions
|
|
* @param treeBlocks The detected tree blocks
|
|
* @param block The block to check
|
|
* @return True if the leaf borders an invalid log, otherwise false
|
|
*/
|
|
private boolean doesLeafBorderInvalidLog(Set<TreeDefinition> treeDefinitions, TreeBlockSet<Block> treeBlocks, Block block) {
|
|
for (Vector offset : this.VALID_TRUNK_OFFSETS) {
|
|
Block targetBlock = block.getRelative(offset.getBlockX(), offset.getBlockY(), offset.getBlockZ());
|
|
if (this.isValidLogType(treeDefinitions, null, targetBlock) && !treeBlocks.contains(new TreeBlock(targetBlock, TreeBlockType.LOG)))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks if a given block is valid for the given TreeDefinitions
|
|
*
|
|
* @param treeDefinitions The Set of TreeDefinitions to compare against
|
|
* @param trunkBlocks The trunk blocks of the tree for checking the distance
|
|
* @param block The Block to check
|
|
* @return True if the block is a valid log type, otherwise false
|
|
*/
|
|
private boolean isValidLogType(Set<TreeDefinition> treeDefinitions, List<Block> trunkBlocks, Block block) {
|
|
// Check if block is placed
|
|
if (this.placedBlockManager.isBlockPlaced(block))
|
|
return false;
|
|
|
|
// Check if it matches the tree definition
|
|
boolean isCorrectType = false;
|
|
for (TreeDefinition treeDefinition : treeDefinitions) {
|
|
for (CompatibleMaterial material : treeDefinition.getLogMaterial()) {
|
|
if (material.equals(CompatibleMaterial.getMaterial(block))) {
|
|
isCorrectType = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isCorrectType)
|
|
return false;
|
|
|
|
// Check that it is close enough to the trunk
|
|
if (trunkBlocks == null || trunkBlocks.isEmpty())
|
|
return true;
|
|
|
|
Location location = block.getLocation();
|
|
for (TreeDefinition treeDefinition : treeDefinitions) {
|
|
double maxDistance = treeDefinition.getMaxLogDistanceFromTrunk() * treeDefinition.getMaxLogDistanceFromTrunk();
|
|
if (!this.onlyBreakLogsUpwards) // Help detect logs more often if the tree isn't broken at the base
|
|
maxDistance *= 1.5;
|
|
for (Block trunkBlock : trunkBlocks)
|
|
if (location.distanceSquared(trunkBlock.getLocation()) < maxDistance)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks if a given block is valid for the given TreeDefinitions
|
|
*
|
|
* @param treeDefinitions The Set of TreeDefinitions to compare against
|
|
* @param treeBlocks The detected blocks of the tree for checking leaf distance
|
|
* @param block The Block to check
|
|
* @return True if the block is a valid log type, otherwise false
|
|
*/
|
|
private boolean isValidLeafType(Set<TreeDefinition> treeDefinitions, TreeBlockSet<Block> treeBlocks, Block block) {
|
|
// Check if block is placed
|
|
if (this.placedBlockManager.isBlockPlaced(block))
|
|
return false;
|
|
|
|
// Check if it matches the tree definition
|
|
boolean isCorrectType = false;
|
|
for (TreeDefinition treeDefinition : treeDefinitions) {
|
|
for (CompatibleMaterial material : treeDefinition.getLeafMaterial()) {
|
|
if (material.equals(CompatibleMaterial.getMaterial(block))) {
|
|
isCorrectType = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isCorrectType)
|
|
return false;
|
|
|
|
// Check that it is close enough to a log
|
|
if (treeBlocks == null || treeBlocks.isEmpty())
|
|
return true;
|
|
|
|
int maxDistanceFromLog = treeDefinitions.stream().map(TreeDefinition::getMaxLeafDistanceFromLog).max(Integer::compareTo).orElse(0);
|
|
return treeBlocks.getLogBlocks().stream().anyMatch(x -> x.getLocation().distanceSquared(block.getLocation()) < maxDistanceFromLog * maxDistanceFromLog);
|
|
}
|
|
|
|
}
|