mirror of
https://github.com/Sollace/Unicopia.git
synced 2024-11-24 05:47:59 +01:00
Rewrite and improve tree traversal
This commit is contained in:
parent
bbe7f67b7b
commit
0dc3860966
12 changed files with 344 additions and 277 deletions
|
@ -1,182 +0,0 @@
|
||||||
package com.minelittlepony.unicopia;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import com.minelittlepony.unicopia.util.PosHelper;
|
|
||||||
|
|
||||||
import net.minecraft.block.Block;
|
|
||||||
import net.minecraft.block.BlockState;
|
|
||||||
import net.minecraft.block.Blocks;
|
|
||||||
import net.minecraft.block.LeavesBlock;
|
|
||||||
import net.minecraft.util.math.BlockPos;
|
|
||||||
import net.minecraft.util.math.Direction;
|
|
||||||
import net.minecraft.world.World;
|
|
||||||
|
|
||||||
public class TreeTraverser {
|
|
||||||
|
|
||||||
public static class Remover {
|
|
||||||
/**
|
|
||||||
* Removes the tree located at the given position.
|
|
||||||
*/
|
|
||||||
public static void removeTree(World w, BlockPos pos) {
|
|
||||||
BlockState log = w.getBlockState(pos);
|
|
||||||
|
|
||||||
if (Measurer.measureTree(w, log, pos) > 0) {
|
|
||||||
removeTreePart(w, log, Ascender.ascendTrunk(new HashSet<BlockPos>(), w, pos, log, 0).get(), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void removeTreePart(World w, BlockState log, BlockPos pos, int level) {
|
|
||||||
if (level < 10 && isWoodOrLeaf(w, log, pos)) {
|
|
||||||
breakBlock(w, pos, level < 5);
|
|
||||||
|
|
||||||
PosHelper.all(pos, p -> {
|
|
||||||
removeTreePart(w, log, p, level + 1);
|
|
||||||
}, Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Ascender {
|
|
||||||
/**
|
|
||||||
* Locates the top of the tree's trunk. Usually the point where wood meets leaves.
|
|
||||||
*/
|
|
||||||
public static BlockPos ascendTree(World w, BlockState log, BlockPos pos, boolean remove) {
|
|
||||||
int breaks = 0;
|
|
||||||
while (variantAndBlockEquals(w.getBlockState(pos.up()), log)) {
|
|
||||||
if (PosHelper.any(pos, p -> isLeaves(w.getBlockState(p), log), Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remove) {
|
|
||||||
breakBlock(w, pos, breaks++ < 10);
|
|
||||||
}
|
|
||||||
pos = pos.up();
|
|
||||||
}
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Optional<BlockPos> ascendTrunk(Set<BlockPos> done, World w, BlockPos pos, BlockState log, int level) {
|
|
||||||
if (level < 3 && !done.contains(pos)) {
|
|
||||||
done.add(pos);
|
|
||||||
|
|
||||||
BlockPos.Mutable result = new BlockPos.Mutable();
|
|
||||||
result.set(ascendTree(w, log, pos, true));
|
|
||||||
|
|
||||||
PosHelper.all(pos, p -> {
|
|
||||||
if (variantAndBlockEquals(w.getBlockState(pos.east()), log)) {
|
|
||||||
ascendTrunk(done, w, pos.east(), log, level + 1).filter(a -> a.getY() > result.getY()).ifPresent(result::set);
|
|
||||||
}
|
|
||||||
}, Direction.EAST, Direction.WEST, Direction.NORTH, Direction.SOUTH);
|
|
||||||
|
|
||||||
done.add(result.toImmutable());
|
|
||||||
return Optional.of(result.toImmutable());
|
|
||||||
}
|
|
||||||
return Optional.of(pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Descender {
|
|
||||||
/**
|
|
||||||
* Recursively locates the base of the tree.
|
|
||||||
*/
|
|
||||||
public static Optional<BlockPos> descendTree(World w, BlockState log, BlockPos pos) {
|
|
||||||
return descendTreePart(new HashSet<BlockPos>(), w, log, new BlockPos.Mutable(pos.getX(), pos.getY(), pos.getZ()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Optional<BlockPos> descendTreePart(Set<BlockPos> done, World w, BlockState log, BlockPos.Mutable pos) {
|
|
||||||
if (done.contains(pos) || !variantAndBlockEquals(w.getBlockState(pos), log)) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
done.add(pos.toImmutable());
|
|
||||||
while (variantAndBlockEquals(w.getBlockState(pos.down()), log)) {
|
|
||||||
done.add(pos.move(Direction.DOWN).toImmutable());
|
|
||||||
}
|
|
||||||
|
|
||||||
PosHelper.all(pos.toImmutable(), p -> {
|
|
||||||
descendTreePart(done, w, log, new BlockPos.Mutable(p.getX(), p.getY(), p.getZ())).filter(a -> a.getY() < pos.getY()).ifPresent(pos::set);
|
|
||||||
}, Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST);
|
|
||||||
|
|
||||||
done.add(pos.toImmutable());
|
|
||||||
return Optional.of(pos.toImmutable());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Measurer {
|
|
||||||
/**
|
|
||||||
* Counts the number of logs and leaves present in the targeted tree.
|
|
||||||
*/
|
|
||||||
public static int measureTree(World w, BlockState log, BlockPos pos) {
|
|
||||||
Set<BlockPos> logs = new HashSet<>();
|
|
||||||
Set<BlockPos> leaves = new HashSet<>();
|
|
||||||
|
|
||||||
countParts(logs, leaves, w, log, pos);
|
|
||||||
|
|
||||||
return logs.size() <= (leaves.size() / 2) ? logs.size() + leaves.size() : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Counts the number of logs and leaves present in the targeted tree.
|
|
||||||
*/
|
|
||||||
public static Set<BlockPos> getParts(World w, BlockState log, BlockPos pos) {
|
|
||||||
Set<BlockPos> parts = new HashSet<>();
|
|
||||||
|
|
||||||
countParts(parts, parts, w, log, pos);
|
|
||||||
|
|
||||||
return parts;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static void countParts(Set<BlockPos> logs, Set<BlockPos> leaves, World w, BlockState log, BlockPos pos) {
|
|
||||||
if (logs.contains(pos) || leaves.contains(pos)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BlockState state = w.getBlockState(pos);
|
|
||||||
boolean yay = false;
|
|
||||||
|
|
||||||
if (isLeaves(state, log) && !state.get(LeavesBlock.PERSISTENT)) {
|
|
||||||
leaves.add(pos);
|
|
||||||
yay = true;
|
|
||||||
} else if (variantAndBlockEquals(state, log)) {
|
|
||||||
logs.add(pos);
|
|
||||||
yay = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (yay) {
|
|
||||||
PosHelper.all(pos, p -> {
|
|
||||||
countParts(logs, leaves, w, log, p);
|
|
||||||
}, Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void breakBlock(World w, BlockPos pos, boolean destroy) {
|
|
||||||
if (destroy) {
|
|
||||||
w.breakBlock(pos, true);
|
|
||||||
} else {
|
|
||||||
Block.dropStacks(w.getBlockState(pos), w, pos);
|
|
||||||
w.setBlockState(pos, Blocks.AIR.getDefaultState(), 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isWoodOrLeaf(World w, BlockState log, BlockPos pos) {
|
|
||||||
BlockState state = w.getBlockState(pos);
|
|
||||||
return variantAndBlockEquals(state, log) || (isLeaves(state, log) && !state.get(LeavesBlock.PERSISTENT));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isLeaves(BlockState state, BlockState log) {
|
|
||||||
return state.getBlock() instanceof LeavesBlock && variantEquals(state, log);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean variantAndBlockEquals(BlockState one, BlockState two) {
|
|
||||||
return (one.getBlock() == two.getBlock()) && variantEquals(one, two);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean variantEquals(BlockState one, BlockState two) {
|
|
||||||
return TreeType.get(one).equals(TreeType.get(two));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,75 +1,165 @@
|
||||||
package com.minelittlepony.unicopia;
|
package com.minelittlepony.unicopia;
|
||||||
|
|
||||||
import java.util.stream.Collectors;
|
import com.minelittlepony.unicopia.util.PosHelper;
|
||||||
|
|
||||||
import com.minelittlepony.unicopia.item.UItems;
|
|
||||||
import com.minelittlepony.unicopia.util.Weighted;
|
import com.minelittlepony.unicopia.util.Weighted;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import net.minecraft.block.Block;
|
|
||||||
import net.minecraft.block.BlockState;
|
import net.minecraft.block.BlockState;
|
||||||
import net.minecraft.block.Blocks;
|
import net.minecraft.block.LeavesBlock;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.item.ItemStack;
|
||||||
import net.minecraft.item.Items;
|
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.util.math.Direction;
|
||||||
import net.minecraft.util.registry.Registry;
|
import net.minecraft.util.registry.Registry;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
|
||||||
public final class TreeType {
|
public final class TreeType {
|
||||||
// TODO: move to datapack
|
public static final TreeType NONE = new TreeType(
|
||||||
private static final Set<TreeType> REGISTRY = new HashSet<>();
|
new Identifier("unicopia", "none"),
|
||||||
|
false,
|
||||||
|
new Weighted<Supplier<ItemStack>>(),
|
||||||
|
Collections.emptySet(),
|
||||||
|
Collections.emptySet()
|
||||||
|
);
|
||||||
|
private static final Direction[] WIDE_DIRS = new Direction[] { Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST };
|
||||||
|
|
||||||
private static final Supplier<ItemStack> ROTTEN = () -> new ItemStack(UItems.ROTTEN_APPLE);
|
private final Identifier name;
|
||||||
private static final Supplier<ItemStack> SWEET = () -> new ItemStack(UItems.SWEET_APPLE);
|
private final boolean wideTrunk;
|
||||||
private static final Supplier<ItemStack> GREEN = () -> new ItemStack(UItems.GREEN_APPLE);
|
private final Set<Identifier> logs;
|
||||||
private static final Supplier<ItemStack> ZAP = () -> new ItemStack(UItems.ZAP_APPLE);
|
private final Set<Identifier> leaves;
|
||||||
private static final Supplier<ItemStack> SOUR = () -> new ItemStack(UItems.SOUR_APPLE);
|
|
||||||
private static final Supplier<ItemStack> RED = () -> new ItemStack(Items.APPLE);
|
|
||||||
|
|
||||||
public static final TreeType NONE = new TreeType("none", new Weighted<Supplier<ItemStack>>());
|
|
||||||
public static final TreeType OAK = new TreeType("oak", new Weighted<Supplier<ItemStack>>()
|
|
||||||
.put(1, ROTTEN)
|
|
||||||
.put(2, GREEN)
|
|
||||||
.put(3, RED), Blocks.OAK_LOG, Blocks.OAK_LEAVES);
|
|
||||||
public static final TreeType BIRCH = new TreeType("birch", new Weighted<Supplier<ItemStack>>()
|
|
||||||
.put(1, ROTTEN)
|
|
||||||
.put(2, SWEET)
|
|
||||||
.put(5, GREEN), Blocks.BIRCH_LOG, Blocks.BIRCH_LEAVES);
|
|
||||||
public static final TreeType SPRUCE = new TreeType("spruce", new Weighted<Supplier<ItemStack>>()
|
|
||||||
.put(1, SOUR)
|
|
||||||
.put(2, GREEN)
|
|
||||||
.put(3, SWEET)
|
|
||||||
.put(4, ROTTEN), Blocks.SPRUCE_LOG, Blocks.SPRUCE_LEAVES);
|
|
||||||
public static final TreeType ACACIA = new TreeType("acacia", new Weighted<Supplier<ItemStack>>()
|
|
||||||
.put(1, ROTTEN)
|
|
||||||
.put(2, SWEET)
|
|
||||||
.put(5, GREEN), Blocks.ACACIA_LOG, Blocks.ACACIA_LEAVES);
|
|
||||||
public static final TreeType JUNGLE = new TreeType("jungle", new Weighted<Supplier<ItemStack>>()
|
|
||||||
.put(5, GREEN)
|
|
||||||
.put(2, SWEET)
|
|
||||||
.put(1, ZAP), Blocks.JUNGLE_LOG, Blocks.JUNGLE_LEAVES);
|
|
||||||
public static final TreeType DARK_OAK = new TreeType("dark_oak", new Weighted<Supplier<ItemStack>>()
|
|
||||||
.put(1, ROTTEN)
|
|
||||||
.put(2, SWEET)
|
|
||||||
.put(5, ZAP), Blocks.DARK_OAK_LOG, Blocks.DARK_OAK_LEAVES);
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
private final Set<Identifier> blocks;
|
|
||||||
private final Weighted<Supplier<ItemStack>> pool;
|
private final Weighted<Supplier<ItemStack>> pool;
|
||||||
|
|
||||||
private TreeType(String name, Weighted<Supplier<ItemStack>> pool, Block...blocks) {
|
TreeType(Identifier name, boolean wideTrunk, Weighted<Supplier<ItemStack>> pool, Set<Identifier> logs, Set<Identifier> leaves) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.wideTrunk = wideTrunk;
|
||||||
this.pool = pool;
|
this.pool = pool;
|
||||||
this.blocks = Arrays.stream(blocks).map(Registry.BLOCK::getId)
|
this.logs = logs;
|
||||||
.collect(Collectors.toSet());
|
this.leaves = leaves;
|
||||||
REGISTRY.add(this);
|
}
|
||||||
|
|
||||||
|
public void traverse(World w, BlockPos start, Reactor consumer) {
|
||||||
|
traverse(w, start, consumer, consumer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void traverse(World w, BlockPos start, Reactor logConsumer, Reactor leavesConsumer) {
|
||||||
|
traverse(new HashSet<>(), new HashSet<>(), w, start, 0, 50, logConsumer, leavesConsumer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void traverse(Set<BlockPos> logs, Set<BlockPos> leaves, World w, BlockPos start, int recurseLevel, int maxRecurse, Reactor logConsumer, Reactor leavesConsumer) {
|
||||||
|
if (this == NONE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
traverseInner(logs, leaves, w, findBase(w, start), recurseLevel, maxRecurse, logConsumer, leavesConsumer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void traverseInner(Set<BlockPos> logs, Set<BlockPos> leaves, World w, BlockPos pos, int recurseLevel, int maxRecurse, Reactor logConsumer, Reactor leavesConsumer) {
|
||||||
|
|
||||||
|
if (this == NONE || (maxRecurse > 0 && recurseLevel >= maxRecurse) || logs.contains(pos) || leaves.contains(pos)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockState state = w.getBlockState(pos);
|
||||||
|
boolean yay = false;
|
||||||
|
|
||||||
|
if (isLeaves(state)) {
|
||||||
|
leaves.add(pos);
|
||||||
|
yay = true;
|
||||||
|
if (leavesConsumer != null) {
|
||||||
|
leavesConsumer.react(w, state, pos, recurseLevel);
|
||||||
|
}
|
||||||
|
} else if (isLog(state)) {
|
||||||
|
logs.add(pos);
|
||||||
|
yay = true;
|
||||||
|
if (logConsumer != null) {
|
||||||
|
logConsumer.react(w, state, pos, recurseLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yay) {
|
||||||
|
PosHelper.all(pos, p -> traverseInner(logs, leaves, w, p, recurseLevel + 1, maxRecurse, logConsumer, leavesConsumer), WIDE_DIRS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively locates the base of the tree.
|
||||||
|
*/
|
||||||
|
public BlockPos findBase(World w, BlockPos pos) {
|
||||||
|
return findBase(new HashSet<BlockPos>(), w, new BlockPos.Mutable(pos.getX(), pos.getY(), pos.getZ())).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<BlockPos> findBase(Set<BlockPos> done, World w, BlockPos.Mutable pos) {
|
||||||
|
if (done.contains(pos) || !isLog(w.getBlockState(pos))) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
done.add(pos.toImmutable());
|
||||||
|
while (isLog(w.getBlockState(pos.down()))) {
|
||||||
|
done.add(pos.move(Direction.DOWN).toImmutable());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wideTrunk) {
|
||||||
|
PosHelper.all(pos.toImmutable(), p -> findBase(done, w, new BlockPos.Mutable(p.getX(), p.getY(), p.getZ()))
|
||||||
|
.filter(a -> a.getY() < pos.getY())
|
||||||
|
.ifPresent(pos::set), PosHelper.HORIZONTAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
done.add(pos.toImmutable());
|
||||||
|
return Optional.of(pos.toImmutable());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the number of logs and leaves present in the targeted tree.
|
||||||
|
*/
|
||||||
|
public int countBlocks(World w, BlockPos pos) {
|
||||||
|
if (this == NONE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<BlockPos> logs = new HashSet<>();
|
||||||
|
Set<BlockPos> leaves = new HashSet<>();
|
||||||
|
|
||||||
|
traverseInner(logs, leaves, w, findBase(w, pos), 0, 50, null, null);
|
||||||
|
|
||||||
|
int logCount = logs.size();
|
||||||
|
|
||||||
|
logs.clear();
|
||||||
|
leaves.clear();
|
||||||
|
|
||||||
|
traverseInner(logs, leaves, w, findCanopy(w, pos), 0, 50, null, null);
|
||||||
|
|
||||||
|
return logCount <= (leaves.size() / 2) ? logCount + leaves.size() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locates the top of the tree's trunk. Usually the point where wood meets leaves.
|
||||||
|
*/
|
||||||
|
public BlockPos findCanopy(World w, BlockPos pos) {
|
||||||
|
while (isLog(w.getBlockState(pos.up()))) {
|
||||||
|
if (PosHelper.any(pos, p -> isLeaves(w.getBlockState(p)), PosHelper.HORIZONTAL)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = pos.up();
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLeaves(BlockState state) {
|
||||||
|
return findMatch(leaves, state) && (!state.contains(LeavesBlock.PERSISTENT) || !state.get(LeavesBlock.PERSISTENT));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLog(BlockState state) {
|
||||||
|
return findMatch(logs, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean matches(BlockState state) {
|
public boolean matches(BlockState state) {
|
||||||
return blocks.contains(Registry.BLOCK.getId(state.getBlock()));
|
return isLeaves(state) || isLog(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ItemStack pickRandomStack() {
|
public ItemStack pickRandomStack() {
|
||||||
|
@ -77,7 +167,7 @@ public final class TreeType {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TreeType get(BlockState state) {
|
public static TreeType get(BlockState state) {
|
||||||
return REGISTRY.stream().filter(type -> type.matches(state)).findFirst().orElse(TreeType.NONE);
|
return TreeTypeLoader.INSTANCE.get(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -85,8 +175,16 @@ public final class TreeType {
|
||||||
return o instanceof TreeType && name.compareTo(((TreeType)o).name) == 0;
|
return o instanceof TreeType && name.compareTo(((TreeType)o).name) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean findMatch(Set<Identifier> ids, BlockState state) {
|
||||||
|
return ids.contains(Registry.BLOCK.getId(state.getBlock()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return name.hashCode();
|
return name.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface Reactor {
|
||||||
|
void react(World w, BlockState state, BlockPos pos, int recurseLevel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
package com.minelittlepony.unicopia;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonParseException;
|
||||||
|
import com.minelittlepony.common.util.settings.ToStringAdapter;
|
||||||
|
import com.minelittlepony.unicopia.util.Weighted;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.resource.JsonDataLoader;
|
||||||
|
import net.minecraft.resource.ResourceManager;
|
||||||
|
import net.minecraft.util.Identifier;
|
||||||
|
import net.minecraft.util.profiler.Profiler;
|
||||||
|
import net.minecraft.util.registry.Registry;
|
||||||
|
|
||||||
|
public class TreeTypeLoader extends JsonDataLoader implements IdentifiableResourceReloadListener {
|
||||||
|
|
||||||
|
private static final Identifier ID = new Identifier("unicopia", "data/tree_type");
|
||||||
|
|
||||||
|
private static final Gson GSON = new GsonBuilder()
|
||||||
|
.registerTypeAdapter(Identifier.class, new ToStringAdapter<>(Identifier::new))
|
||||||
|
.create();
|
||||||
|
|
||||||
|
static final TreeTypeLoader INSTANCE = new TreeTypeLoader();
|
||||||
|
|
||||||
|
private final Set<TreeType> entries = new HashSet<>();
|
||||||
|
|
||||||
|
TreeTypeLoader() {
|
||||||
|
super(GSON, "tree_types");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Identifier getFabricId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TreeType get(BlockState state) {
|
||||||
|
return entries.stream().filter(type -> type.matches(state)).findFirst().orElse(TreeType.NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void apply(Map<Identifier, JsonElement> resources, ResourceManager manager, Profiler profiler) {
|
||||||
|
entries.clear();
|
||||||
|
|
||||||
|
for (Map.Entry<Identifier, JsonElement> entry : resources.entrySet()) {
|
||||||
|
try {
|
||||||
|
TreeTypeDef typeDef = GSON.fromJson(entry.getValue(), TreeTypeDef.class);
|
||||||
|
|
||||||
|
if (typeDef != null) {
|
||||||
|
entries.add(new TreeType(
|
||||||
|
entry.getKey(),
|
||||||
|
typeDef.wideTrunk,
|
||||||
|
typeDef.getWeighted(new Weighted<Supplier<ItemStack>>()),
|
||||||
|
Objects.requireNonNull(typeDef.logs, "TreeType must have logs"),
|
||||||
|
Objects.requireNonNull(typeDef.leaves, "TreeType must have leaves")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException | JsonParseException e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TreeTypeDef {
|
||||||
|
Set<Identifier> logs;
|
||||||
|
Set<Identifier> leaves;
|
||||||
|
Set<Drop> drops;
|
||||||
|
boolean wideTrunk;
|
||||||
|
|
||||||
|
Weighted<Supplier<ItemStack>> getWeighted(Weighted<Supplier<ItemStack>> weighted) {
|
||||||
|
drops.forEach(drop -> drop.appendDrop(weighted));
|
||||||
|
return weighted;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Drop {
|
||||||
|
int weight;
|
||||||
|
Identifier item;
|
||||||
|
|
||||||
|
void appendDrop(Weighted<Supplier<ItemStack>> weighted) {
|
||||||
|
Registry.ITEM.getOrEmpty(item).ifPresent(item -> {
|
||||||
|
weighted.put(weight, item::getDefaultStack);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,9 @@ package com.minelittlepony.unicopia;
|
||||||
|
|
||||||
import net.fabricmc.api.ModInitializer;
|
import net.fabricmc.api.ModInitializer;
|
||||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
|
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
|
||||||
|
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
|
||||||
|
import net.minecraft.resource.ResourceType;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
@ -37,6 +40,7 @@ public class Unicopia implements ModInitializer {
|
||||||
AwaitTickQueue.tick(w);
|
AwaitTickQueue.tick(w);
|
||||||
((BlockDestructionManager.Source)w).getDestructionManager().tick();
|
((BlockDestructionManager.Source)w).getDestructionManager().tick();
|
||||||
});
|
});
|
||||||
|
ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(TreeTypeLoader.INSTANCE);
|
||||||
|
|
||||||
UItems.bootstrap();
|
UItems.bootstrap();
|
||||||
UPotions.bootstrap();
|
UPotions.bootstrap();
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package com.minelittlepony.unicopia.ability;
|
package com.minelittlepony.unicopia.ability;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@ -9,29 +8,23 @@ import javax.annotation.Nullable;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.minelittlepony.unicopia.BlockDestructionManager;
|
import com.minelittlepony.unicopia.BlockDestructionManager;
|
||||||
import com.minelittlepony.unicopia.Race;
|
import com.minelittlepony.unicopia.Race;
|
||||||
import com.minelittlepony.unicopia.TreeTraverser;
|
|
||||||
import com.minelittlepony.unicopia.TreeType;
|
import com.minelittlepony.unicopia.TreeType;
|
||||||
import com.minelittlepony.unicopia.ability.data.Hit;
|
import com.minelittlepony.unicopia.ability.data.Hit;
|
||||||
import com.minelittlepony.unicopia.ability.data.Pos;
|
import com.minelittlepony.unicopia.ability.data.Pos;
|
||||||
import com.minelittlepony.unicopia.entity.player.Pony;
|
import com.minelittlepony.unicopia.entity.player.Pony;
|
||||||
import com.minelittlepony.unicopia.util.PosHelper;
|
|
||||||
import com.minelittlepony.unicopia.util.RayTraceHelper;
|
import com.minelittlepony.unicopia.util.RayTraceHelper;
|
||||||
import com.minelittlepony.unicopia.util.WorldEvent;
|
import com.minelittlepony.unicopia.util.WorldEvent;
|
||||||
import com.minelittlepony.unicopia.util.shape.Shape;
|
import com.minelittlepony.unicopia.util.shape.Shape;
|
||||||
import com.minelittlepony.unicopia.util.shape.Sphere;
|
import com.minelittlepony.unicopia.util.shape.Sphere;
|
||||||
|
|
||||||
import net.minecraft.block.BlockState;
|
import net.minecraft.block.Block;
|
||||||
import net.minecraft.block.Blocks;
|
import net.minecraft.block.Blocks;
|
||||||
import net.minecraft.block.LeavesBlock;
|
|
||||||
import net.minecraft.entity.ItemEntity;
|
import net.minecraft.entity.ItemEntity;
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
import net.minecraft.particle.BlockStateParticleEffect;
|
import net.minecraft.particle.BlockStateParticleEffect;
|
||||||
import net.minecraft.particle.ParticleTypes;
|
import net.minecraft.particle.ParticleTypes;
|
||||||
import net.minecraft.tag.BlockTags;
|
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
import net.minecraft.util.math.Direction;
|
|
||||||
import net.minecraft.util.math.Vec3d;
|
import net.minecraft.util.math.Vec3d;
|
||||||
import net.minecraft.world.World;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Earth Pony kicking ability
|
* Earth Pony kicking ability
|
||||||
|
@ -65,11 +58,11 @@ public class EarthPonyKickAbility implements Ability<Pos> {
|
||||||
|
|
||||||
if (p.isPresent()) {
|
if (p.isPresent()) {
|
||||||
BlockPos pos = p.get();
|
BlockPos pos = p.get();
|
||||||
BlockState state = player.getWorld().getBlockState(pos);
|
TreeType tree = TreeType.get(player.getWorld().getBlockState(pos));
|
||||||
|
|
||||||
if (state.getBlock().isIn(BlockTags.LOGS)) {
|
if (tree != TreeType.NONE) {
|
||||||
pos = TreeTraverser.Descender.descendTree(player.getWorld(), state, pos).get();
|
pos = tree.findBase(player.getWorld(), pos);
|
||||||
if (TreeTraverser.Measurer.measureTree(player.getWorld(), state, pos) > 0) {
|
if (tree.countBlocks(player.getWorld(), pos) > 0) {
|
||||||
return new Pos(pos);
|
return new Pos(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,12 +94,19 @@ public class EarthPonyKickAbility implements Ability<Pos> {
|
||||||
|
|
||||||
if (destr.getBlockDestruction(pos) + 4 >= BlockDestructionManager.MAX_DAMAGE) {
|
if (destr.getBlockDestruction(pos) + 4 >= BlockDestructionManager.MAX_DAMAGE) {
|
||||||
if (!harmed || player.world.random.nextInt(30) == 0) {
|
if (!harmed || player.world.random.nextInt(30) == 0) {
|
||||||
TreeTraverser.Remover.removeTree(player.world, pos);
|
TreeType.get(player.world.getBlockState(pos)).traverse(player.world, pos, (w, state, p, recurseLevel) -> {
|
||||||
|
if (recurseLevel < 5) {
|
||||||
|
w.breakBlock(p, true);
|
||||||
|
} else {
|
||||||
|
Block.dropStacks(w.getBlockState(p), w, p);
|
||||||
|
w.setBlockState(p, Blocks.AIR.getDefaultState(), 3);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
iplayer.subtractEnergyCost(3);
|
iplayer.subtractEnergyCost(3);
|
||||||
} else {
|
} else {
|
||||||
int cost = dropApples(player.world, pos);
|
int cost = dropApples(player, pos);
|
||||||
|
|
||||||
if (cost > 0) {
|
if (cost > 0) {
|
||||||
iplayer.subtractEnergyCost(cost * 3);
|
iplayer.subtractEnergyCost(cost * 3);
|
||||||
|
@ -149,23 +149,32 @@ public class EarthPonyKickAbility implements Ability<Pos> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int dropApples(World w, BlockPos pos) {
|
private int dropApples(PlayerEntity player, BlockPos pos) {
|
||||||
BlockState log = w.getBlockState(pos);
|
TreeType tree = TreeType.get(player.world.getBlockState(pos));
|
||||||
int size = TreeTraverser.Measurer.measureTree(w, log, pos);
|
|
||||||
|
|
||||||
if (size > 0) {
|
if (tree.countBlocks(player.world, pos) > 0) {
|
||||||
BlockDestructionManager destr = ((BlockDestructionManager.Source)w).getDestructionManager();
|
|
||||||
TreeTraverser.Measurer.getParts(w, log, pos).forEach(position -> {
|
|
||||||
destr.damageBlock(position, 4);
|
|
||||||
});
|
|
||||||
|
|
||||||
List<ItemEntity> capturedDrops = Lists.newArrayList();
|
List<ItemEntity> capturedDrops = Lists.newArrayList();
|
||||||
|
|
||||||
dropApplesPart(capturedDrops, new ArrayList<BlockPos>(), w, log, pos, 0);
|
tree.traverse(player.world, pos, (world, state, position, recurse) -> {
|
||||||
|
affectBlockChange(player, position);
|
||||||
|
}, (world, state, position, recurse) -> {
|
||||||
|
affectBlockChange(player, position);
|
||||||
|
|
||||||
|
if (world.getBlockState(position.down()).isAir()) {
|
||||||
|
WorldEvent.play(WorldEvent.DESTROY_BLOCK, world, position, state);
|
||||||
|
capturedDrops.add(new ItemEntity(world,
|
||||||
|
position.getX() + world.random.nextFloat(),
|
||||||
|
position.getY() - 0.5,
|
||||||
|
position.getZ() + world.random.nextFloat(),
|
||||||
|
tree.pickRandomStack()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
capturedDrops.forEach(item -> {
|
capturedDrops.forEach(item -> {
|
||||||
item.setToDefaultPickupDelay();
|
item.setToDefaultPickupDelay();
|
||||||
w.spawnEntity(item);
|
player.world.spawnEntity(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
return capturedDrops.size() / 3;
|
return capturedDrops.size() / 3;
|
||||||
|
@ -174,27 +183,10 @@ public class EarthPonyKickAbility implements Ability<Pos> {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void dropApplesPart(List<ItemEntity> drops, List<BlockPos> done, World w, BlockState log, BlockPos pos, int level) {
|
private void affectBlockChange(PlayerEntity player, BlockPos position) {
|
||||||
if (!done.contains(pos)) {
|
BlockDestructionManager destr = ((BlockDestructionManager.Source)player.world).getDestructionManager();
|
||||||
done.add(pos);
|
|
||||||
pos = TreeTraverser.Ascender.ascendTree(w, log, pos, false);
|
|
||||||
if (level < 10 && TreeTraverser.isWoodOrLeaf(w, log, pos)) {
|
|
||||||
BlockState state = w.getBlockState(pos);
|
|
||||||
|
|
||||||
if (state.getBlock() instanceof LeavesBlock && w.getBlockState(pos.down()).isAir()) {
|
destr.damageBlock(position, 4);
|
||||||
WorldEvent.play(WorldEvent.DESTROY_BLOCK, w, pos, state);
|
|
||||||
drops.add(new ItemEntity(w,
|
|
||||||
pos.getX() + w.random.nextFloat(),
|
|
||||||
pos.getY() - 0.5,
|
|
||||||
pos.getZ() + w.random.nextFloat(),
|
|
||||||
TreeType.get(log).pickRandomStack()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
PosHelper.all(pos, p -> {
|
|
||||||
dropApplesPart(drops, done, w, log, p, level + 1);
|
|
||||||
}, Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.minelittlepony.unicopia.util;
|
package com.minelittlepony.unicopia.util;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Spliterator;
|
import java.util.Spliterator;
|
||||||
import java.util.Spliterators.AbstractSpliterator;
|
import java.util.Spliterators.AbstractSpliterator;
|
||||||
|
@ -19,6 +20,8 @@ import net.minecraft.world.World;
|
||||||
|
|
||||||
public interface PosHelper {
|
public interface PosHelper {
|
||||||
|
|
||||||
|
Direction[] HORIZONTAL = Arrays.stream(Direction.values()).filter(d -> d.getAxis().isHorizontal()).toArray(Direction[]::new);
|
||||||
|
|
||||||
static Vec3d offset(Vec3d a, Vec3i b) {
|
static Vec3d offset(Vec3d a, Vec3i b) {
|
||||||
return a.add(b.getX(), b.getY(), b.getZ());
|
return a.add(b.getX(), b.getY(), b.getZ());
|
||||||
}
|
}
|
||||||
|
|
9
src/main/resources/data/unicopia/tree_types/acacia.json
Normal file
9
src/main/resources/data/unicopia/tree_types/acacia.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"logs": [ "minecraft:acacia_log", "minecraft:acacia_wood" ],
|
||||||
|
"leaves": [ "minecraft:acacia_leaves" ],
|
||||||
|
"drops": [
|
||||||
|
{ "weight": 1, "item": "unicopia:rotten_apple" },
|
||||||
|
{ "weight": 2, "item": "unicopia:sweet_apple" },
|
||||||
|
{ "weight": 5, "item": "unicopia:green_apple" }
|
||||||
|
]
|
||||||
|
}
|
9
src/main/resources/data/unicopia/tree_types/birch.json
Normal file
9
src/main/resources/data/unicopia/tree_types/birch.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"logs": [ "minecraft:birch_log", "minecraft:birch_wood" ],
|
||||||
|
"leaves": [ "minecraft:birch_leaves" ],
|
||||||
|
"drops": [
|
||||||
|
{ "weight": 1, "item": "unicopia:rotten_apple" },
|
||||||
|
{ "weight": 2, "item": "unicopia:sweet_apple" },
|
||||||
|
{ "weight": 5, "item": "unicopia:green_apple" }
|
||||||
|
]
|
||||||
|
}
|
10
src/main/resources/data/unicopia/tree_types/dark_oak.json
Normal file
10
src/main/resources/data/unicopia/tree_types/dark_oak.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"logs": [ "minecraft:dark_oak_log", "minecraft:dark_oak_wood" ],
|
||||||
|
"leaves": [ "minecraft:dark_oak_leaves" ],
|
||||||
|
"wideTrunk": true,
|
||||||
|
"drops": [
|
||||||
|
{ "weight": 1, "item": "unicopia:rottenn_apple" },
|
||||||
|
{ "weight": 2, "item": "unicopia:sweet_apple" },
|
||||||
|
{ "weight": 5, "item": "unicopia:zap_apple" }
|
||||||
|
]
|
||||||
|
}
|
10
src/main/resources/data/unicopia/tree_types/jungle.json
Normal file
10
src/main/resources/data/unicopia/tree_types/jungle.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"logs": [ "minecraft:jungle_log", "minecraft:jungle_wood" ],
|
||||||
|
"leaves": [ "minecraft:jungle_leaves" ],
|
||||||
|
"wideTrunk": true,
|
||||||
|
"drops": [
|
||||||
|
{ "weight": 5, "item": "unicopia:green_apple" },
|
||||||
|
{ "weight": 2, "item": "unicopia:sweet_apple" },
|
||||||
|
{ "weight": 1, "item": "unicopia:zap_apple" }
|
||||||
|
]
|
||||||
|
}
|
9
src/main/resources/data/unicopia/tree_types/oak.json
Normal file
9
src/main/resources/data/unicopia/tree_types/oak.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"logs": [ "minecraft:oak_log", "minecraft:oak_wood" ],
|
||||||
|
"leaves": [ "minecraft:oak_leaves" ],
|
||||||
|
"drops": [
|
||||||
|
{ "weight": 1, "item": "unicopia:rotten_apple" },
|
||||||
|
{ "weight": 2, "item": "unicopia:green_apple" },
|
||||||
|
{ "weight": 3, "item": "minecraft:apple" }
|
||||||
|
]
|
||||||
|
}
|
10
src/main/resources/data/unicopia/tree_types/spruce.json
Normal file
10
src/main/resources/data/unicopia/tree_types/spruce.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"logs": [ "minecraft:spruce_log", "minecraft:spruce_wood" ],
|
||||||
|
"leaves": [ "minecraft:spruce_leaves" ],
|
||||||
|
"wideTrunk": true,
|
||||||
|
"drops": [
|
||||||
|
{ "weight": 1, "item": "unicopia:sour_apple" },
|
||||||
|
{ "weight": 2, "item": "unicopia:green_apple" },
|
||||||
|
{ "weight": 4, "item": "unicopia:rotten_apple" }
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in a new issue