2019-03-28 05:22:13 +01:00
|
|
|
package com.songoda.ultimatetimber.manager;
|
|
|
|
|
|
|
|
import com.songoda.ultimatetimber.UltimateTimber;
|
2019-03-28 21:34:17 +01:00
|
|
|
import com.songoda.ultimatetimber.adapter.IBlockData;
|
2019-03-28 18:38:37 +01:00
|
|
|
import com.songoda.ultimatetimber.adapter.VersionAdapter;
|
|
|
|
import com.songoda.ultimatetimber.tree.*;
|
|
|
|
import org.bukkit.block.Block;
|
|
|
|
import org.bukkit.block.BlockFace;
|
|
|
|
import org.bukkit.util.Vector;
|
|
|
|
|
|
|
|
import java.util.Comparator;
|
|
|
|
import java.util.HashSet;
|
|
|
|
import java.util.Set;
|
2019-03-28 05:22:13 +01:00
|
|
|
|
|
|
|
public class TreeDetectionManager extends Manager {
|
|
|
|
|
2019-03-28 18:38:37 +01:00
|
|
|
private final Set<Vector> VALID_TRUNK_OFFSETS, VALID_BRANCH_OFFSETS, VALID_LEAF_OFFSETS;
|
|
|
|
|
|
|
|
private TreeDefinitionManager treeDefinitionManager;
|
|
|
|
private int maxBranchBlocksAllowed;
|
|
|
|
private int numLeavesRequiredForTree;
|
|
|
|
private boolean allowMixedTreeTypes;
|
|
|
|
private boolean onlyBreakLogsUpwards;
|
|
|
|
private boolean destroyBaseLog;
|
|
|
|
private boolean entireTreeBase;
|
2019-03-28 05:22:13 +01:00
|
|
|
|
|
|
|
public TreeDetectionManager(UltimateTimber ultimateTimber) {
|
|
|
|
super(ultimateTimber);
|
2019-03-28 18:38:37 +01:00
|
|
|
|
|
|
|
VALID_BRANCH_OFFSETS = new HashSet<>();
|
|
|
|
VALID_TRUNK_OFFSETS = new HashSet<>();
|
|
|
|
VALID_LEAF_OFFSETS = new HashSet<>();
|
|
|
|
|
|
|
|
// 3x2x3 centered around log, excluding -y axis
|
|
|
|
for (int x = -1; x <= 1; x++)
|
|
|
|
for (int y = 0; y <= 1; y++)
|
|
|
|
for (int z = -1; z <= 1; z++)
|
|
|
|
VALID_BRANCH_OFFSETS.add(new Vector(x, y, z));
|
|
|
|
|
|
|
|
// 3x3x3 centered around log
|
|
|
|
for (int x = -1; x <= 1; x++)
|
|
|
|
for (int y = -1; y <= 1; y++)
|
|
|
|
for (int z = -1; z <= 1; z++)
|
|
|
|
VALID_TRUNK_OFFSETS.add(new Vector(x, y, z));
|
|
|
|
|
|
|
|
// Adjacent blocks to log
|
|
|
|
for (int i = -1; i <= 1; i += 2) {
|
|
|
|
VALID_LEAF_OFFSETS.add(new Vector(i, 0, 0));
|
|
|
|
VALID_LEAF_OFFSETS.add(new Vector(0, i, 0));
|
|
|
|
VALID_LEAF_OFFSETS.add(new Vector(0, 0, i));
|
|
|
|
}
|
2019-03-28 05:22:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void reload() {
|
2019-03-28 18:38:37 +01:00
|
|
|
this.treeDefinitionManager = this.ultimateTimber.getTreeDefinitionManager();
|
|
|
|
this.maxBranchBlocksAllowed = ConfigurationManager.Setting.MAX_LOGS_PER_CHOP.getInt();
|
|
|
|
this.numLeavesRequiredForTree = ConfigurationManager.Setting.LEAVES_REQUIRED_FOR_TREE.getInt();
|
|
|
|
this.allowMixedTreeTypes = ConfigurationManager.Setting.MIX_ALL_TREE_TYPES.getBoolean();
|
|
|
|
this.onlyBreakLogsUpwards = ConfigurationManager.Setting.ONLY_DETECT_LOGS_UPWARDS.getBoolean();
|
|
|
|
this.destroyBaseLog = ConfigurationManager.Setting.DESTROY_INITIATED_BLOCK.getBoolean();
|
|
|
|
this.entireTreeBase = ConfigurationManager.Setting.BREAK_ENTIRE_TREE_BASE.getBoolean();
|
2019-03-28 05:22:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void disable() {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-03-28 18:38:37 +01:00
|
|
|
/**
|
|
|
|
* 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) {
|
|
|
|
TreeBlock initialTreeBlock = new TreeBlock(initialBlock, TreeBlockType.LOG);
|
|
|
|
TreeBlockSet<Block> detectedTreeBlocks = new TreeBlockSet<>(initialTreeBlock);
|
2019-03-28 21:34:17 +01:00
|
|
|
Set<TreeDefinition> possibleTreeDefinitions = treeDefinitionManager.getTreeDefinitionsForLog(initialBlock);
|
2019-03-28 18:38:37 +01:00
|
|
|
|
|
|
|
// Detect tree trunk
|
|
|
|
Set<Block> trunkBlocks = new HashSet<>();
|
|
|
|
trunkBlocks.add(initialBlock);
|
|
|
|
Block targetBlock = initialBlock;
|
|
|
|
while (this.isValidLogType(possibleTreeDefinitions, (targetBlock = targetBlock.getRelative(BlockFace.UP)))) {
|
|
|
|
TreeBlock treeBlock = new TreeBlock(targetBlock, TreeBlockType.LOG);
|
|
|
|
detectedTreeBlocks.add(treeBlock);
|
|
|
|
trunkBlocks.add(initialBlock);
|
2019-03-28 21:34:17 +01:00
|
|
|
possibleTreeDefinitions.retainAll(this.treeDefinitionManager.narrowTreeDefinition(possibleTreeDefinitions, targetBlock, TreeBlockType.LOG));
|
2019-03-28 18:38:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Tree must be at least 2 blocks tall
|
|
|
|
if (detectedTreeBlocks.size() < 2)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
// Detect branches off the main trunk
|
|
|
|
for (Block trunkBlock : trunkBlocks)
|
|
|
|
this.recursiveBranchSearch(possibleTreeDefinitions, detectedTreeBlocks, trunkBlock, initialBlock.getY());
|
|
|
|
|
|
|
|
// 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(), 1);
|
|
|
|
|
2019-03-28 21:34:17 +01:00
|
|
|
TreeDefinition actualTreeDefinition = possibleTreeDefinitions.iterator().next();
|
|
|
|
|
2019-03-28 18:38:37 +01:00
|
|
|
// Trees need at least a certain number of leaves
|
|
|
|
if (detectedTreeBlocks.getLeafBlocks().size() < this.numLeavesRequiredForTree)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
// TODO: Soil detection
|
2019-03-28 21:34:17 +01:00
|
|
|
if (this.entireTreeBase) {
|
|
|
|
// TODO: Yadda yadda
|
|
|
|
}
|
2019-03-28 18:38:37 +01:00
|
|
|
|
|
|
|
// Delete the starting block if applicable
|
|
|
|
if (this.destroyBaseLog)
|
|
|
|
detectedTreeBlocks.remove(initialTreeBlock);
|
|
|
|
|
|
|
|
// Use the first tree definition in the set
|
2019-03-28 21:34:17 +01:00
|
|
|
return new DetectedTree(actualTreeDefinition, detectedTreeBlocks);
|
2019-03-28 18:38:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Recursively searches for branches off a given block
|
|
|
|
*
|
|
|
|
* @param treeDefinitions The possible tree definitions
|
|
|
|
* @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, TreeBlockSet<Block> treeBlocks, Block block, int startingBlockY) {
|
|
|
|
if (treeBlocks.size() > this.maxBranchBlocksAllowed)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (Vector offset : this.onlyBreakLogsUpwards ? VALID_BRANCH_OFFSETS : VALID_TRUNK_OFFSETS) {
|
|
|
|
Block targetBlock = block.getRelative(offset.getBlockX(), offset.getBlockY(), offset.getBlockZ());
|
|
|
|
TreeBlock treeBlock = new TreeBlock(targetBlock, TreeBlockType.LOG);
|
|
|
|
if (this.isValidLogType(treeDefinitions, targetBlock) && !treeBlocks.contains(treeBlock)) {
|
|
|
|
treeBlocks.add(treeBlock);
|
2019-03-28 21:34:17 +01:00
|
|
|
treeDefinitions.retainAll(this.treeDefinitionManager.narrowTreeDefinition(treeDefinitions, block, TreeBlockType.LOG));
|
2019-03-28 18:38:37 +01:00
|
|
|
if (!this.onlyBreakLogsUpwards || targetBlock.getLocation().getBlockY() > startingBlockY)
|
|
|
|
this.recursiveBranchSearch(treeDefinitions, 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
|
|
|
|
* @param distanceFromLog The distance this leaf is from a log
|
|
|
|
*/
|
|
|
|
private void recursiveLeafSearch(Set<TreeDefinition> treeDefinitions, TreeBlockSet<Block> treeBlocks, Block block, int distanceFromLog) {
|
|
|
|
int maxDistanceFromLog = treeDefinitions.stream().max(Comparator.comparingInt(TreeDefinition::getMaxLeafDistanceFromLog)).get().getMaxLeafDistanceFromLog();
|
|
|
|
boolean detectLeavesDiagonally = treeDefinitions.stream().anyMatch(TreeDefinition::shouldDetectLeavesDiagonally);
|
|
|
|
|
|
|
|
if (distanceFromLog > maxDistanceFromLog)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (Vector offset : !detectLeavesDiagonally ? VALID_LEAF_OFFSETS : VALID_TRUNK_OFFSETS) {
|
|
|
|
Block targetBlock = block.getRelative(offset.getBlockX(), offset.getBlockY(), offset.getBlockZ());
|
|
|
|
TreeBlock treeBlock = new TreeBlock(targetBlock, TreeBlockType.LEAF);
|
|
|
|
if (this.isValidLeafType(treeDefinitions, targetBlock)) {
|
|
|
|
if (!treeBlocks.contains(treeBlock) && !this.doesLeafBorderInvalidLog(treeDefinitions, treeBlocks, targetBlock)) {
|
|
|
|
treeBlocks.add(treeBlock);
|
2019-03-28 21:34:17 +01:00
|
|
|
treeDefinitions.retainAll(this.treeDefinitionManager.narrowTreeDefinition(treeDefinitions, block, TreeBlockType.LEAF));
|
2019-03-28 18:38:37 +01:00
|
|
|
}
|
|
|
|
this.recursiveLeafSearch(treeDefinitions, treeBlocks, targetBlock, distanceFromLog + 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 : VALID_TRUNK_OFFSETS) {
|
|
|
|
Block targetBlock = block.getRelative(offset.getBlockX(), offset.getBlockY(), offset.getBlockZ());
|
|
|
|
if (this.isValidLogType(treeDefinitions, 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 block The Block to check
|
|
|
|
* @return True if the block is a valid log type, otherwise false
|
|
|
|
*/
|
|
|
|
private boolean isValidLogType(Set<TreeDefinition> treeDefinitions, Block block) {
|
|
|
|
VersionAdapter versionAdapter = this.ultimateTimber.getVersionAdapter();
|
|
|
|
for (TreeDefinition treeDefinition : treeDefinitions)
|
2019-03-28 21:34:17 +01:00
|
|
|
for (IBlockData logBlockData : treeDefinition.getLogBlockData())
|
|
|
|
if (logBlockData.isSimilar(block))
|
2019-03-28 18:38:37 +01:00
|
|
|
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 block The Block to check
|
|
|
|
* @return True if the block is a valid log type, otherwise false
|
|
|
|
*/
|
|
|
|
private boolean isValidLeafType(Set<TreeDefinition> treeDefinitions, Block block) {
|
|
|
|
VersionAdapter versionAdapter = this.ultimateTimber.getVersionAdapter();
|
|
|
|
for (TreeDefinition treeDefinition : treeDefinitions)
|
2019-03-28 21:34:17 +01:00
|
|
|
for (IBlockData leafBlockData : treeDefinition.getLeafBlockData())
|
|
|
|
if (leafBlockData.isSimilar(block))
|
2019-03-28 18:38:37 +01:00
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-03-28 05:22:13 +01:00
|
|
|
}
|