2019-11-07 11:30:29 +01:00
|
|
|
package com.simibubi.create.foundation.utility;
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.HashSet;
|
|
|
|
import java.util.LinkedList;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Set;
|
|
|
|
|
|
|
|
import com.google.common.base.Predicates;
|
|
|
|
|
|
|
|
import net.minecraft.block.BlockState;
|
|
|
|
import net.minecraft.block.LeavesBlock;
|
|
|
|
import net.minecraft.tags.BlockTags;
|
|
|
|
import net.minecraft.util.Direction;
|
|
|
|
import net.minecraft.util.math.BlockPos;
|
|
|
|
import net.minecraft.world.IBlockReader;
|
|
|
|
|
|
|
|
public class TreeCutter {
|
|
|
|
|
|
|
|
public static class Tree {
|
|
|
|
public List<BlockPos> logs;
|
|
|
|
public List<BlockPos> leaves;
|
|
|
|
|
|
|
|
public Tree(List<BlockPos> logs, List<BlockPos> leaves) {
|
|
|
|
this.logs = logs;
|
|
|
|
this.leaves = leaves;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Finds a tree at the given pos. Block at the position should be air
|
|
|
|
*
|
|
|
|
* @param reader
|
|
|
|
* @param pos
|
|
|
|
* @return null if not found or not fully cut
|
|
|
|
*/
|
|
|
|
public static Tree cutTree(IBlockReader reader, BlockPos pos) {
|
|
|
|
List<BlockPos> logs = new ArrayList<>();
|
|
|
|
List<BlockPos> leaves = new ArrayList<>();
|
|
|
|
Set<BlockPos> visited = new HashSet<>();
|
|
|
|
List<BlockPos> frontier = new LinkedList<>();
|
|
|
|
|
|
|
|
if (!validateCut(reader, pos))
|
|
|
|
return null;
|
|
|
|
|
|
|
|
visited.add(pos);
|
2020-02-01 14:26:19 +01:00
|
|
|
BlockPos.getAllInBox(pos.add(-1, 0, -1), pos.add(1, 1, 1)).forEach(p -> frontier.add(new BlockPos(p)));
|
2019-11-07 11:30:29 +01:00
|
|
|
|
|
|
|
// Find all logs
|
|
|
|
while (!frontier.isEmpty()) {
|
|
|
|
BlockPos currentPos = frontier.remove(0);
|
2019-12-13 18:15:11 +01:00
|
|
|
if (visited.contains(currentPos))
|
|
|
|
continue;
|
2019-11-07 11:30:29 +01:00
|
|
|
visited.add(currentPos);
|
|
|
|
|
|
|
|
if (!isLog(reader.getBlockState(currentPos)))
|
|
|
|
continue;
|
|
|
|
logs.add(currentPos);
|
|
|
|
addNeighbours(currentPos, frontier, visited);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find all leaves
|
|
|
|
visited.clear();
|
|
|
|
visited.addAll(logs);
|
|
|
|
frontier.addAll(logs);
|
|
|
|
while (!frontier.isEmpty()) {
|
|
|
|
BlockPos currentPos = frontier.remove(0);
|
2019-12-13 18:15:11 +01:00
|
|
|
if (!logs.contains(currentPos))
|
|
|
|
if (visited.contains(currentPos))
|
|
|
|
continue;
|
2019-11-07 11:30:29 +01:00
|
|
|
visited.add(currentPos);
|
|
|
|
|
|
|
|
BlockState blockState = reader.getBlockState(currentPos);
|
2019-11-10 15:53:44 +01:00
|
|
|
boolean isLog = isLog(blockState);
|
|
|
|
boolean isLeaf = isLeaf(blockState);
|
2019-11-07 11:30:29 +01:00
|
|
|
|
|
|
|
if (!isLog && !isLeaf)
|
|
|
|
continue;
|
|
|
|
if (isLeaf)
|
|
|
|
leaves.add(currentPos);
|
|
|
|
|
|
|
|
int distance = isLog ? 0 : blockState.get(LeavesBlock.DISTANCE);
|
|
|
|
for (Direction direction : Direction.values()) {
|
|
|
|
BlockPos offset = currentPos.offset(direction);
|
|
|
|
if (visited.contains(offset))
|
|
|
|
continue;
|
|
|
|
BlockState state = reader.getBlockState(offset);
|
|
|
|
if (isLeaf(state) && state.get(LeavesBlock.DISTANCE) > distance)
|
|
|
|
frontier.add(offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Tree(logs, leaves);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks whether a tree was fully cut by seeing whether the layer above the cut
|
|
|
|
* is not supported by any more logs.
|
|
|
|
*
|
|
|
|
* @param reader
|
|
|
|
* @param pos
|
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
private static boolean validateCut(IBlockReader reader, BlockPos pos) {
|
|
|
|
Set<BlockPos> visited = new HashSet<>();
|
|
|
|
List<BlockPos> frontier = new LinkedList<>();
|
2020-02-01 14:26:19 +01:00
|
|
|
frontier.add(pos);
|
2019-11-07 11:30:29 +01:00
|
|
|
frontier.add(pos.up());
|
2020-02-01 14:26:19 +01:00
|
|
|
int posY = pos.getY();
|
2019-11-07 11:30:29 +01:00
|
|
|
|
|
|
|
while (!frontier.isEmpty()) {
|
|
|
|
BlockPos currentPos = frontier.remove(0);
|
|
|
|
visited.add(currentPos);
|
2020-02-01 14:26:19 +01:00
|
|
|
boolean lowerLayer = currentPos.getY() == posY;
|
2019-11-07 11:30:29 +01:00
|
|
|
|
|
|
|
if (!isLog(reader.getBlockState(currentPos)))
|
|
|
|
continue;
|
2020-02-01 14:26:19 +01:00
|
|
|
if (!lowerLayer && !pos.equals(currentPos.down()) && isLog(reader.getBlockState(currentPos.down())))
|
2019-11-07 11:30:29 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
for (Direction direction : Direction.values()) {
|
2020-02-01 14:26:19 +01:00
|
|
|
if (direction == Direction.DOWN)
|
|
|
|
continue;
|
|
|
|
if (direction == Direction.UP && !lowerLayer)
|
2019-11-07 11:30:29 +01:00
|
|
|
continue;
|
|
|
|
BlockPos offset = currentPos.offset(direction);
|
|
|
|
if (visited.contains(offset))
|
|
|
|
continue;
|
|
|
|
frontier.add(offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void addNeighbours(BlockPos pos, List<BlockPos> frontier, Set<BlockPos> visited) {
|
|
|
|
BlockPos.getAllInBox(pos.add(-1, -1, -1), pos.add(1, 1, 1)).filter(Predicates.not(visited::contains))
|
2019-11-10 15:53:44 +01:00
|
|
|
.forEach(p -> frontier.add(new BlockPos(p)));
|
2019-11-07 11:30:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private static boolean isLog(BlockState state) {
|
|
|
|
return state.isIn(BlockTags.LOGS);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static boolean isLeaf(BlockState state) {
|
|
|
|
return state.has(LeavesBlock.DISTANCE);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|