Created
October 22, 2020 21:54
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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