UltimateTimber/src/main/java/com/craftaro/ultimatetimber/manager/TreeDetectionManager.java

324 lines
14 KiB
Java

package com.craftaro.ultimatetimber.manager;
import com.craftaro.core.compatibility.CompatibleMaterial;
import com.craftaro.third_party.com.cryptomorin.xseries.XMaterial;
import com.craftaro.ultimatetimber.UltimateTimber;
import com.craftaro.ultimatetimber.tree.DetectedTree;
import com.craftaro.ultimatetimber.tree.ITreeBlock;
import com.craftaro.ultimatetimber.tree.TreeBlock;
import com.craftaro.ultimatetimber.tree.TreeBlockSet;
import com.craftaro.ultimatetimber.tree.TreeBlockType;
import com.craftaro.ultimatetimber.tree.TreeDefinition;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.util.Vector;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
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 plugin) {
super(plugin);
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 (XMaterial material : treeDefinitionManager.getPlantableSoilMaterial(actualTreeDefinition)) {
if (material == CompatibleMaterial.getMaterial(blockBelow.getType()).orElse(null)) {
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 (XMaterial material : treeDefinition.getLogMaterial()) {
if (material == CompatibleMaterial.getMaterial(block.getType()).orElse(null)) {
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 (XMaterial material : treeDefinition.getLeafMaterial()) {
if (material == CompatibleMaterial.getMaterial(block.getType()).orElse(null)) {
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);
}
}