package com.mysticsbiomes.common.levelgen.feature.tree;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.mojang.serialization.Codec;
import com.mysticsbiomes.common.levelgen.feature.config.NewMysticTreeConfiguration;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelSimulatedReader;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.LeavesBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import net.minecraft.world.level.levelgen.feature.treedecorators.TreeDecorator;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape;
import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
import java.util.*;
import java.util.function.BiConsumer;
public abstract class NewMysticTreeFeature extends Feature {
public NewMysticTreeFeature(Codec codec) {
super(codec);
}
@Override
public boolean place(FeaturePlaceContext context) {
final WorldGenLevel level = context.level();
RandomSource random = context.random();
BlockPos initialPos = context.origin();
NewMysticTreeConfiguration config = context.config();
Set trunkPositions = Sets.newHashSet();
Set branchPositions = Sets.newHashSet();
final Set foliagePositions = Sets.newHashSet();
Set decoratorPositions = Sets.newHashSet();
BiConsumer trunkSetter = setter(level, trunkPositions);
BiConsumer branchSetter = setter(level, branchPositions);
BiConsumer foliageSetter = setter(level, foliagePositions);
BiConsumer decoratorSetter = setter(level, decoratorPositions);
boolean flag = this.doPlace(level, random, initialPos, trunkSetter, branchSetter, foliageSetter, config);
if (flag && (!branchPositions.isEmpty() || !foliagePositions.isEmpty())) {
if (!config.decorators.isEmpty()) {
config.decorators.forEach((decorator) -> decorator.place(new TreeDecorator.Context(level, decoratorSetter, random, branchPositions, foliagePositions, trunkPositions)));
}
return BoundingBox.encapsulatingPositions(Iterables.concat(trunkPositions, branchPositions, foliagePositions, decoratorPositions)).map((box) -> {
StructureTemplate.updateShapeAtEdge(level, 3, updateLeaves(level, box, branchPositions, decoratorPositions, trunkPositions), box.minX(), box.minY(), box.minZ());
return true;
}).orElse(false);
} else {
return false;
}
}
private static BiConsumer setter(LevelAccessor level, Set set) {
return (pos, state) -> {
set.add(pos.immutable());
level.setBlock(pos, state, 19);
};
}
public boolean doPlace(WorldGenLevel level, RandomSource random, BlockPos initialPos, BiConsumer trunkSetter, BiConsumer branchSetter, BiConsumer foliageSetter, NewMysticTreeConfiguration config) {
int trunkHeight = config.trunkShape.getTrunkHeight(random);
int minBuildY = Math.min(initialPos.getY(), initialPos.getY());
int maxBuildY = Math.max(initialPos.getY(), initialPos.getY()) + trunkHeight + 1;
if (minBuildY >= level.getMinBuildHeight() + 1 && maxBuildY <= level.getMaxBuildHeight()) {
for (int currentY = 0; currentY <= trunkHeight; currentY++) {
config.trunkShape.placeLog(level, random, initialPos.above(currentY), Direction.Axis.Y, trunkSetter, config);
}
int foliageRadius = config.foliageShape.radius.sample(random);
int foliageHeight = config.foliageShape.height.sample(random);
this.placeFoliage(level, random, initialPos.above(trunkHeight), config, foliageSetter, foliageRadius, foliageHeight);
List list = new ArrayList<>();
for (int count = 0; count < config.branchShape.branchCount.sample(random); count++) {
Direction branchDirection = Direction.Plane.HORIZONTAL.getRandomDirection(random);
list.add(branchDirection);
for (Direction directions : list) {
if (branchDirection != directions) {
int branchYStart = trunkHeight / 2 - config.branchShape.branchYStart.sample(random);
this.generateBranch(level, random, initialPos.above(branchYStart), branchDirection, branchSetter, foliageSetter, config);
}
}
}
return true;
} else {
return false;
}
}
private int getMaxFreeTreeHeight(LevelSimulatedReader level, RandomSource random, BlockPos initialPos, int height, NewMysticTreeConfiguration config) {
BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
for (int y = 0; y <= height + 1; y++) {
int radius = config.foliageShape.foliageRadius(random);
for (int xOffset = -radius; xOffset <= radius; xOffset++) {
for (int zOffset = -radius; zOffset <= radius; zOffset++) {
mutablePos.setWithOffset(initialPos, xOffset, y, zOffset);
boolean flag = !config.trunkShape.isFree(level, mutablePos);
if (flag) {
return y - 2;
}
}
}
}
return height;
}
private static DiscreteVoxelShape updateLeaves(LevelAccessor level, BoundingBox box, Set logs, Set leaves, Set persistentLeaves) {
DiscreteVoxelShape shape = new BitSetDiscreteVoxelShape(box.getXSpan(), box.getYSpan(), box.getZSpan());
List> list = Lists.newArrayList();
for (int i = 0; i < 7; i++) {
list.add(Sets.newHashSet());
}
for (BlockPos pos : Lists.newArrayList(Sets.union(leaves, persistentLeaves))) {
if (box.isInside(pos)) {
shape.fill(pos.getX() - box.minX(), pos.getY() - box.minY(), pos.getZ() - box.minZ());
}
}
BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
int currentDistance = 0;
list.getFirst().addAll(logs);
while (true) {
while (currentDistance >= 7 || !list.get(currentDistance).isEmpty()) {
if (currentDistance >= 7) return shape;
Iterator iterator = list.get(currentDistance).iterator();
BlockPos currentPos = iterator.next();
iterator.remove();
if (box.isInside(currentPos)) {
if (currentDistance != 0) {
BlockState state = level.getBlockState(currentPos);
level.setBlock(currentPos, state.setValue(BlockStateProperties.DISTANCE, 1), 19);
}
shape.fill(currentPos.getX() - box.minX(), currentPos.getY() - box.minY(), currentPos.getZ() - box.minZ());
for (Direction direction : Direction.values()) {
mutablePos.setWithOffset(currentPos, direction);
if (box.isInside(mutablePos)) {
int dx = mutablePos.getX() - box.minX();
int dy = mutablePos.getY() - box.minY();
int dz = mutablePos.getZ() - box.minZ();
if (!shape.isFull(dx, dy, dz)) {
OptionalInt distance = LeavesBlock.getOptionalDistanceAt(level.getBlockState(mutablePos));
if (distance.isPresent()) {
int nextDistance = Math.min(distance.getAsInt(), currentDistance + 1);
if (nextDistance < 7) {
list.get(nextDistance).add(mutablePos.immutable());
currentDistance = Math.min(currentDistance, nextDistance);
}
}
}
}
}
}
}
++currentDistance;
}
}
public static boolean validTreePos(LevelSimulatedReader level, BlockPos pos) {
return level.isStateAtPosition(pos, state -> state.isAir() || state.is(BlockTags.REPLACEABLE_BY_TREES));
}
public static boolean isAirOrLeaves(LevelSimulatedReader level, BlockPos pos) {
return level.isStateAtPosition(pos, state -> state.isAir() || state.is(BlockTags.LEAVES));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
protected void generateBranch(LevelAccessor level, RandomSource random, BlockPos startPos, Direction branchDirection, BiConsumer branchSetter, BiConsumer foliageSetter, NewMysticTreeConfiguration config) {
BlockPos.MutableBlockPos mutablePos = startPos.mutable();
int branchLength = config.branchShape.branchLength.sample(random);
int extendAmount = config.branchShape.extendAmount.sample(random);
if (extendAmount > 0) {
for (int i = 0; i < extendAmount; i++) {
mutablePos.move(branchDirection);
config.trunkShape.placeLog(level, random, mutablePos, branchDirection.getAxis(), branchSetter, config);
}
}
int steps = 0;
while (steps <= branchLength) {
int forwardInterval = 1 + Math.round((float) steps / branchLength * (config.branchShape.forwardInterval + 1));
int upwardInterval = 1 + Math.round((float) steps / branchLength * (config.branchShape.upwardInterval + 1));
for (int i = 0; i < forwardInterval && steps < branchLength; i++, steps++) {
mutablePos.move(branchDirection);
config.trunkShape.placeLog(level, random, mutablePos, branchDirection.getAxis(), branchSetter, config);
}
for (int i = 0; i < upwardInterval && steps < branchLength; i++, steps++) {
mutablePos.move(Direction.UP);
config.trunkShape.placeLog(level, random, mutablePos, Direction.Axis.Y, branchSetter, config);
}
if (steps == branchLength) {
this.placeFoliage(level, random, mutablePos, config, foliageSetter, config.foliageShape.radius.sample(random), config.foliageShape.height.sample(random));
return;
}
}
}
public abstract void placeFoliage(LevelAccessor level, RandomSource random, BlockPos pos, NewMysticTreeConfiguration config, BiConsumer foliageSetter, int foliageRadius, int foliageHeight);
}