Skip to content

Instantly share code, notes, and snippets.

@GorgeousOne
Created October 22, 2020 21:54
Show Gist options
  • Save GorgeousOne/6020512714e973bb611002f2b41af5b8 to your computer and use it in GitHub Desktop.
Save GorgeousOne/6020512714e973bb611002f2b41af5b8 to your computer and use it in GitHub Desktop.
A small algorithm that tries to chop down trees whole but also to remove only few leaves from neabry trees. I like to use it for quickly editing forests for screenshots etc.
package me.gorgeousone.treexpert;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.data.type.Leaves;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.util.Vector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public final class TreeFeller extends JavaPlugin implements Listener {
private final List<Vector> directFaces = new ArrayList<>(Arrays.asList(
new Vector(0, 1, 0),
new Vector(0, -1, 0),
new Vector(1, 0, 0),
new Vector(-1, 0, 0),
new Vector(0, 0, 1),
new Vector(0, 0, -1)
));
private final List<Vector> indirectFacesUp = new ArrayList<>(Arrays.asList(
new Vector(1, 0, 1),
new Vector(-1, 0, 1),
new Vector(-1, 0, -1),
new Vector(1, 0, -1),
new Vector(1, 1, -1),
new Vector(1, 1, 0),
new Vector(1, 1, 1),
new Vector(0, 1, 1),
new Vector(-1, 1, 1),
new Vector(-1, 1, 0),
new Vector(-1, 1, -1),
new Vector(0, 1, -1)
));
private final HashMap<Material, Material> treeTypes = new HashMap<>();
@Override
public void onEnable() {
Bukkit.getPluginManager().registerEvents(this, this);
treeTypes.put(Material.ACACIA_LOG, Material.ACACIA_LEAVES);
treeTypes.put(Material.BIRCH_LOG, Material.BIRCH_LEAVES);
treeTypes.put(Material.DARK_OAK_LOG, Material.DARK_OAK_LEAVES);
treeTypes.put(Material.JUNGLE_LOG, Material.JUNGLE_LEAVES);
treeTypes.put(Material.OAK_LOG, Material.OAK_LEAVES);
treeTypes.put(Material.SPRUCE_LOG, Material.SPRUCE_LEAVES);
}
@EventHandler
public void onBlockBreak(BlockBreakEvent event) {
ItemStack usedItem = event.getPlayer().getInventory().getItemInMainHand();
if (usedItem.getType() != Material.GOLDEN_AXE) {
return;
}
Block block = event.getBlock();
if (!treeTypes.containsKey(block.getType())) {
return;
}
event.setCancelled(true);
fellTree(block);
}
/**
* Detects the tree structure at the given block and chops it away
* @param startLog any log of the tree to locating from
*/
private void fellTree(Block startLog) {
Map<Integer, List<Block>> tree = detectTree(startLog);
tree.values().forEach(leaveSet -> leaveSet.forEach(leaf -> leaf.setType(Material.AIR)));
}
/**
* Detects the log and the leaf blocks of a tree structure trying to consider where other tree structures start (at least for the leaves)
* @param startLog any log of the tree structure to start at
* @return a map with the different layers of the tree where the value for key 0 are all trunk blocks
* and values for keys 1 - 6 are all the leaves sorted by their distance to the trunk
*/
public Map<Integer, List<Block>> detectTree(Block startLog) {
Material logType = startLog.getType();
Material leafType = treeTypes.get(logType);
Map<Integer, List<Block>> leafLayerBlocks = new HashMap<>();
List<Block> trunkBlocks = detectTrunk(startLog);
leafLayerBlocks.put(0, trunkBlocks);
//detects the layers of leaves one after another by their distance to the trunk
for (int leafTrunkDist = 1; leafTrunkDist < 7; leafTrunkDist++) {
int lastTrunkDist = leafTrunkDist - 1;
List<Block> lastLeafLayer = leafLayerBlocks.get(lastTrunkDist);
List<Block> currentLeafLayer = new ArrayList<>();
for (Block lastLeaf : lastLeafLayer) {
Set<Block> nearbyLeaves = getRelativeBlocks(lastLeaf, directFaces, leafType)
.stream()
.filter(newLeaf -> !currentLeafLayer.contains(newLeaf))
.collect(Collectors.toSet());
Iterator<Block> iterator = nearbyLeaves.iterator();
//sorts out leaves that are connected to a log from another tree equidistant to this tree
while (iterator.hasNext()) {
for (Block sourceLog : getSourceLogsOfLeaf(iterator.next(), logType)) {
if (!trunkBlocks.contains(sourceLog)) {
iterator.remove();
break;
}
}
}
currentLeafLayer.addAll(nearbyLeaves);
}
if (currentLeafLayer.isEmpty()) {
return leafLayerBlocks;
} else {
leafLayerBlocks.put(leafTrunkDist, currentLeafLayer);
}
}
return leafLayerBlocks;
}
/**
* Detects logs that are connected (hopefully to a tree structure) directly or indirectly, but then only same level and upwards
* @param startLog a piece of wood to start detecting from
* @return a list of all located logs
*/
public List<Block> detectTrunk(Block startLog) {
Material logType = startLog.getType();
//already detected logs
List<Block> trunkBlocks = new ArrayList<>();
//logs where it is still uncertain if they are connected to more logs
List<Block> newTrunkBlocks = new ArrayList<>();
trunkBlocks.add(startLog);
newTrunkBlocks.add(startLog);
while (!newTrunkBlocks.isEmpty()) {
Block nextLog = newTrunkBlocks.remove(0);
Set<Block> directNearbyLogs = getRelativeBlocks(nextLog, directFaces, logType).stream()
.filter(log -> !trunkBlocks.contains(log))
.collect(Collectors.toSet());
trunkBlocks.addAll(directNearbyLogs);
newTrunkBlocks.addAll(directNearbyLogs);
Set<Block> indirectNearbyLogs = getRelativeBlocks(nextLog, indirectFacesUp, logType).stream()
.filter(log -> !trunkBlocks.contains(log))
.collect(Collectors.toSet());
trunkBlocks.addAll(indirectNearbyLogs);
newTrunkBlocks.addAll(indirectNearbyLogs);
}
return trunkBlocks;
}
private int trunkDist(Block leaf) {
return ((Leaves) leaf.getBlockData()).getDistance();
}
/**
* Returns a set of matching blocks relative to a block.
* @param block center block from where relative blocks will be detected
* @param directions vectors for all relative places to look for
* @param material the material that the relative blocks have to match
*/
private Set<Block> getRelativeBlocks(Block block, Collection<Vector> directions, Material material) {
Set<Block> matchingBlocks = new HashSet<>();
for (Vector dir : directions) {
Block relative = block.getRelative(dir.getBlockX(), dir.getBlockY(), dir.getBlockZ());
if (relative.getType() == material) {
matchingBlocks.add(relative);
}
}
return matchingBlocks;
}
/**
* Detects all log blocks this leaf connects to on the shortest way (still may be 2 different trees you know?)
* @param logType type of log this leaf attaches to
* @return a set of all possible blocks
*/
private Set<Block> getSourceLogsOfLeaf(Block leaf, Material logType) {
Material leafType = leaf.getType();
int firstTrunkDist = trunkDist(leaf);
Set<Block> firstStage = new HashSet<>();
firstStage.add(leaf);
HashMap<Integer, Set<Block>> distStages = new HashMap<>();
distStages.put(firstTrunkDist, firstStage);
for (int i = firstTrunkDist - 1; i > 0; i--) {
final int trunkDist = i;
Set<Block> lastStage = distStages.get(trunkDist + 1);
Set<Block> stage = new HashSet<>();
for (Block stageLeaf : lastStage) {
stage.addAll(getRelativeBlocks(stageLeaf, directFaces, leafType).stream()
.filter(leafy -> trunkDist(leafy) == trunkDist)
.collect(Collectors.toSet()));
}
distStages.put(trunkDist, stage);
}
Set<Block> possibleSourceLogs = new HashSet<>();
for (Block stageLeaf : distStages.get(1)) {
possibleSourceLogs.addAll(getRelativeBlocks(stageLeaf, directFaces, logType));
}
return possibleSourceLogs;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment