fixed tree kicking and improved performance when traversing trees

This commit is contained in:
Sollace 2023-09-12 11:25:41 +01:00
parent c7cdca3ff4
commit 226e80a89b
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
22 changed files with 278 additions and 231 deletions

View file

@ -149,30 +149,24 @@ public class EarthPonyKickAbility implements Ability<Pos> {
public boolean apply(Pony iplayer, Pos data) {
BlockPos pos = data.pos();
TreeType tree = TreeType.at(pos, iplayer.asWorld());
if (tree == TreeType.NONE || tree.findBase(iplayer.asWorld(), pos)
.map(base -> tree.countBlocks(iplayer.asWorld(), pos) > 0)
.orElse(false)) {
return false;
}
TreeType treeType = TreeType.at(pos, iplayer.asWorld());
iplayer.setAnimation(Animation.KICK, Animation.Recipient.ANYONE);
iplayer.subtractEnergyCost(tree == TreeType.NONE ? 1 : 3);
iplayer.subtractEnergyCost(treeType == TreeType.NONE ? 1 : 3);
return treeType.collectBlocks(iplayer.asWorld(), pos).filter(tree -> {
ParticleUtils.spawnParticle(iplayer.asWorld(), UParticles.GROUND_POUND, data.vec(), Vec3d.ZERO);
PlayerEntity player = iplayer.asEntity();
if (BlockDestructionManager.of(player.getWorld()).getBlockDestruction(pos) + 4 >= BlockDestructionManager.MAX_DAMAGE) {
if (player.getWorld().random.nextInt(30) == 0) {
tree.traverse(player.getWorld(), pos, (w, state, p, recurseLevel) -> {
if (recurseLevel < 5) {
tree.logs().forEach(player.getWorld(), (w, state, p) -> {
w.breakBlock(p, true);
} else {
});
tree.leaves().forEach(player.getWorld(), (w, state, p) -> {
Block.dropStacks(w.getBlockState(p), w, p);
w.setBlockState(p, Blocks.AIR.getDefaultState(), 3);
}
w.setBlockState(p, Blocks.AIR.getDefaultState(), Block.NOTIFY_ALL);
});
}
@ -186,6 +180,7 @@ public class EarthPonyKickAbility implements Ability<Pos> {
}
return true;
}).isPresent();
}
@Override
@ -195,37 +190,32 @@ public class EarthPonyKickAbility implements Ability<Pos> {
@Override
public void coolDown(Pony player, AbilitySlot slot) {
player.asEntity().getHungerManager().addExhaustion(0.1F);
}
private int dropApples(PlayerEntity player, BlockPos pos) {
TreeType tree = TreeType.at(pos, player.getWorld());
if (tree.countBlocks(player.getWorld(), pos) > 0) {
List<ItemEntity> capturedDrops = new ArrayList<>();
tree.traverse(player.getWorld(), pos, (world, state, position, recurse) -> {
TreeType treeType = TreeType.at(pos, player.getWorld());
return treeType.collectBlocks(player.getWorld(), pos).map(tree -> {
tree.logs().forEach(player.getWorld(), (world, state, position) -> {
affectBlockChange(player, position);
}, (world, state, position, recurse) -> {
});
int[] dropCount = {0};
tree.leaves().forEach(player.getWorld(), (world, state, position) -> {
affectBlockChange(player, position);
List<ItemEntity> drops = buckBlock(tree, state, world, position)
if (!buckBlock(treeType, state, world, position)
.filter(i -> !i.isEmpty())
.map(stack -> createDrop(stack, position, world))
.toList();
if (!drops.isEmpty()) {
.map(stack -> createDrop(stack, position, world, dropCount))
.toList().isEmpty()) {
world.syncWorldEvent(WorldEvents.BLOCK_BROKEN, position, Block.getRawIdFromState(state));
capturedDrops.addAll(drops);
}
});
capturedDrops.forEach(player.getWorld()::spawnEntity);
return capturedDrops.size() / 3;
return dropCount[0] / 3;
}).orElse(0);
}
return 0;
}
private ItemEntity createDrop(ItemStack stack, BlockPos pos, World world) {
private ItemEntity createDrop(ItemStack stack, BlockPos pos, World world, int[] dropCount) {
ItemEntity entity = new ItemEntity(world,
pos.getX() + world.random.nextFloat(),
pos.getY() - 0.5,
@ -233,10 +223,12 @@ public class EarthPonyKickAbility implements Ability<Pos> {
stack
);
entity.setToDefaultPickupDelay();
world.spawnEntity(entity);
dropCount[0]++;
return entity;
}
private Stream<ItemStack> buckBlock(TreeType tree, BlockState treeState, World world, BlockPos position) {
private Stream<ItemStack> buckBlock(TreeType treeType, BlockState treeState, World world, BlockPos position) {
if (treeState.getBlock() instanceof Buckable buckable) {
return buckable.onBucked((ServerWorld)world, treeState, position).stream();
@ -246,7 +238,7 @@ public class EarthPonyKickAbility implements Ability<Pos> {
BlockState below = world.getBlockState(down);
if (below.isAir()) {
return Stream.of(tree.pickRandomStack(world.random, treeState));
return Stream.of(treeType.pickRandomStack(world.random, treeState));
}
if (below.getBlock() instanceof Buckable buckable) {
@ -259,7 +251,7 @@ public class EarthPonyKickAbility implements Ability<Pos> {
private void affectBlockChange(PlayerEntity player, BlockPos position) {
BlockDestructionManager.of(player.getWorld()).damageBlock(position, 4);
PosHelper.all(position, p -> {
PosHelper.fastAll(position, p -> {
BlockState s = player.getWorld().getBlockState(p);
if (s.getBlock() instanceof BeehiveBlock) {

View file

@ -0,0 +1,105 @@
package com.minelittlepony.unicopia.ability.data.tree;
import java.util.Optional;
import com.minelittlepony.unicopia.util.PosHelper;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.World;
public class TreeTraverser {
private static final Direction[] WIDE_DIRS = new Direction[] {
Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST
};
private final TreeType type;
private PosHelper.PositionRecord logs = new PosHelper.PositionRecord();
private PosHelper.PositionRecord leaves = new PosHelper.PositionRecord();
private final int maxRecurse = 50;
public TreeTraverser(TreeType type) {
this.type = type;
}
public PosHelper.PositionRecord collectLogs(World w, BlockPos pos) {
traverse(w, pos.mutableCopy());
return logs;
}
public PosHelper.PositionRecord collectLeaves(World w, BlockPos pos) {
traverse(w, findCanopy(w, pos));
return leaves;
}
public void traverse(World w, BlockPos pos) {
traverse(w, pos.mutableCopy());
}
private void traverse(World w, BlockPos.Mutable pos) {
logs = new PosHelper.PositionRecord();
leaves = new PosHelper.PositionRecord();
innerTraverse(w, pos, 0);
}
private void innerTraverse(World w, BlockPos.Mutable pos, int recurseLevel) {
if (recurseLevel >= maxRecurse || logs.hasVisited(pos) || leaves.hasVisited(pos)) {
return;
}
BlockState state = w.getBlockState(pos);
boolean yay = false;
if (type.isLeaves(state)) {
leaves.visit(pos);
yay = true;
} else if (type.isLog(state)) {
logs.visit(pos);
yay = true;
}
if (yay) {
PosHelper.fastAll(pos, p -> innerTraverse(w, p, recurseLevel + 1), WIDE_DIRS);
}
}
/**
* Locates the top of the tree's trunk. Usually the point where wood meets leaves.
*/
private BlockPos.Mutable findCanopy(World w, BlockPos pos) {
BlockPos.Mutable mutable = pos.mutableCopy();
while (type.isLog(w.getBlockState(mutable.move(Direction.UP)))) {
if (PosHelper.fastAny(mutable, p -> type.isLeaves(w.getBlockState(p)), PosHelper.HORIZONTAL)) {
break;
}
}
return mutable.move(Direction.DOWN);
}
public Optional<BlockPos> findBase(World w, BlockPos pos) {
logs.clear();
return findBase(w, pos.mutableCopy());
}
private Optional<BlockPos> findBase(World w, BlockPos.Mutable pos) {
if (logs.hasVisited(pos) || !type.isLog(w.getBlockState(pos))) {
return Optional.empty();
}
do {
logs.visit(pos);
} while (type.isLog(w.getBlockState(pos.move(Direction.DOWN))));
pos.move(Direction.UP);
if (type.isWide()) {
PosHelper.fastAll(pos, p -> findBase(w, p)
.filter(a -> a.getY() < pos.getY())
.ifPresent(pos::set), PosHelper.HORIZONTAL);
}
return Optional.of(logs.visit(pos).toImmutable());
}
}

View file

@ -4,7 +4,6 @@ import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.util.PosHelper;
import com.minelittlepony.unicopia.util.Weighted;
import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@ -12,159 +11,11 @@ import java.util.Set;
import net.minecraft.block.BlockState;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.World;
public interface TreeType {
TreeType NONE = new TreeTypeImpl(
Unicopia.id("none"),
false,
Set.of(),
Set.of(),
Weighted.of(),
0
);
Direction[] WIDE_DIRS = new Direction[] { Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST };
default void traverse(World w, BlockPos start, Reactor consumer) {
traverse(w, start, consumer, consumer);
}
default void traverse(World w, BlockPos start, Reactor logConsumer, Reactor leavesConsumer) {
traverse(new HashSet<>(), new HashSet<>(), w, start, 0, 50, logConsumer, leavesConsumer);
}
default void traverse(Set<BlockPos> logs, Set<BlockPos> leaves, World w, BlockPos start, int recurseLevel, int maxRecurse, Reactor logConsumer, Reactor leavesConsumer) {
if (this == NONE) {
return;
}
findBase(w, start).ifPresent(base -> {
traverseInner(logs, leaves, w, base, 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.
*/
default Optional<BlockPos> findBase(World w, BlockPos pos) {
return findBase(new HashSet<>(), w, new BlockPos.Mutable(pos.getX(), pos.getY(), pos.getZ()));
}
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 (isWide()) {
PosHelper.all(pos.toImmutable(), p -> findBase(done, w, p.mutableCopy())
.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.
*/
default int countBlocks(World w, BlockPos pos) {
if (this == NONE) {
return 0;
}
Set<BlockPos> logs = new HashSet<>();
Set<BlockPos> leaves = new HashSet<>();
findBase(w, pos).ifPresent(base -> traverseInner(logs, leaves, w, base, 0, 50, null, null));
int logCount = logs.size();
logs.clear();
leaves.clear();
traverseInner(logs, leaves, w, findCanopy(w, pos), 0, 50, null, null);
int leafCount = leaves.size();
return logCount <= (leafCount / 2) ? logCount + leafCount : 0;
}
/**
* Locates the top of the tree's trunk. Usually the point where wood meets leaves.
*/
default 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;
}
/**
* Finds the tree type of the leaves on top of this tree, independent of what this tree expects for its leaves.
*/
default TreeType findLeavesType(World w, BlockPos pos) {
while (isLog(w.getBlockState(pos.up()))) {
if (PosHelper.any(pos, p -> isLeaves(w.getBlockState(p)), PosHelper.HORIZONTAL)) {
return this;
}
pos = pos.up();
}
return of(w.getBlockState(pos.up()));
}
boolean isLeaves(BlockState state);
boolean isLog(BlockState state);
default boolean matches(BlockState state) {
return isLeaves(state) || isLog(state);
}
ItemStack pickRandomStack(Random random, BlockState state);
boolean isWide();
TreeType NONE = new TreeTypeImpl(Unicopia.id("none"), false, Set.of(), Set.of(), Weighted.of(), 0, 0.5F);
static TreeType at(BlockPos pos, World world) {
return TreeTypes.get(world.getBlockState(pos), pos, world);
@ -198,10 +49,45 @@ public interface TreeType {
public boolean isWide() {
return logs.isWide();
}
@Override
public float leavesRatio() {
return logs.leavesRatio();
}
};
}
public interface Reactor {
void react(World w, BlockState state, BlockPos pos, int recurseLevel);
boolean isLeaves(BlockState state);
boolean isLog(BlockState state);
default boolean matches(BlockState state) {
return isLeaves(state) || isLog(state);
}
ItemStack pickRandomStack(Random random, BlockState state);
boolean isWide();
/**
* The minimum leaves to logs ratio for this tree.
*/
float leavesRatio();
/**
* Counts the number of logs and leaves present in the targeted tree.
*/
default Optional<Tree> collectBlocks(World w, BlockPos pos) {
if (this == NONE) {
return Optional.empty();
}
TreeTraverser traverser = new TreeTraverser(this);
return traverser.findBase(w, pos).map(base -> {
PosHelper.PositionRecord logs = traverser.collectLogs(w, base);
PosHelper.PositionRecord leaves = traverser.collectLeaves(w, base);
return logs.size() <= (leaves.size() * leavesRatio()) ? new Tree(logs, leaves) : null;
});
}
record Tree(PosHelper.PositionRecord logs, PosHelper.PositionRecord leaves) { }
}

View file

@ -17,7 +17,8 @@ public record TreeTypeImpl (
Set<Identifier> logs,
Set<Identifier> leaves,
Supplier<Optional<Supplier<ItemStack>>> pool,
int rarity
int rarity,
float leavesRatio
) implements TreeType {
@Override
public boolean isLeaves(BlockState state) {

View file

@ -64,6 +64,7 @@ public class TreeTypeLoader extends JsonDataLoader implements IdentifiableResour
final Set<Drop> drops;
final boolean wideTrunk;
final int rarity;
final float leavesRatio;
public TreeTypeDef(PacketByteBuf buffer) {
logs = new HashSet<>(buffer.readList(PacketByteBuf::readIdentifier));
@ -71,6 +72,7 @@ public class TreeTypeLoader extends JsonDataLoader implements IdentifiableResour
drops = new HashSet<>(buffer.readList(Drop::new));
wideTrunk = buffer.readBoolean();
rarity = buffer.readInt();
leavesRatio = buffer.readFloat();
}
public TreeType toTreeType(Identifier id) {
@ -80,7 +82,8 @@ public class TreeTypeLoader extends JsonDataLoader implements IdentifiableResour
Objects.requireNonNull(logs, "TreeType must have logs"),
Objects.requireNonNull(leaves, "TreeType must have leaves"),
Weighted.of(drops),
rarity
rarity,
leavesRatio
);
}
@ -90,6 +93,7 @@ public class TreeTypeLoader extends JsonDataLoader implements IdentifiableResour
buffer.writeCollection(drops, (a, b) -> b.write(a));
buffer.writeBoolean(wideTrunk);
buffer.writeInt(rarity);
buffer.writeFloat(leavesRatio);
}
static class Drop implements Weighted.Buildable<Supplier<ItemStack>> {

View file

@ -12,6 +12,7 @@ import net.minecraft.item.ItemStack;
import net.minecraft.registry.tag.BlockTags;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.World;
@ -29,18 +30,8 @@ public class TreeTypes {
return entries.stream()
.filter(type -> type.matches(state))
.findFirst()
.map(type -> TreeType.of(type, type.findLeavesType(world, pos)))
.orElseGet(() -> {
if (any1x.matches(state)) {
if (PosHelper.any(pos, p -> world.getBlockState(p).isOf(state.getBlock()), PosHelper.HORIZONTAL)) {
return any2x;
}
return any1x;
}
return TreeType.NONE;
});
.map(type -> TreeType.of(type, findLeavesType(type, world, pos)))
.orElseGet(() -> any1x.matches(state) ? (PosHelper.fastAny(pos, p -> world.getBlockState(p).isOf(state.getBlock()), PosHelper.HORIZONTAL) ? any2x : any1x) : TreeType.NONE);
}
static TreeType get(BlockState state) {
@ -50,6 +41,16 @@ public class TreeTypes {
.orElse(TreeType.NONE);
}
private static TreeType findLeavesType(TreeType baseType, World w, BlockPos pos) {
BlockPos.Mutable mutable = pos.mutableCopy();
while (baseType.isLog(w.getBlockState(mutable.move(Direction.UP)))) {
if (PosHelper.fastAny(mutable, p -> baseType.isLeaves(w.getBlockState(p)), PosHelper.HORIZONTAL)) {
return baseType;
}
}
return TreeType.of(w.getBlockState(mutable));
}
private static TreeType createDynamic(boolean wide) {
return new TreeType() {
@Override
@ -75,6 +76,11 @@ public class TreeTypes {
public boolean isWide() {
return wide;
}
@Override
public float leavesRatio() {
return 0.5F;
}
};
}
}

View file

@ -97,7 +97,7 @@ public record Altar(BlockPos origin, Set<BlockPos> pillars) {
}
private static boolean checkSlab(World world, BlockPos pos) {
return !PosHelper.any(pos, p -> !isObsidian(world, p), HORIZONTALS);
return !PosHelper.fastAny(pos, p -> !isObsidian(world, p), HORIZONTALS);
}
private static boolean checkPillarPair(World world, BlockPos.Mutable center, BlockRotation rotation, Consumer<BlockPos> pillarPosCollector) {

View file

@ -11,10 +11,10 @@ import java.util.stream.StreamSupport;
import com.google.common.collect.Lists;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Direction.Axis;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.Vec3i;
import net.minecraft.world.BlockView;
@ -29,30 +29,36 @@ public interface PosHelper {
}
static BlockPos findSolidGroundAt(World world, BlockPos pos, int signum) {
while (world.isInBuildLimit(pos) && (world.isAir(pos) || !world.getBlockState(pos).canPlaceAt(world, pos))) {
pos = pos.offset(Axis.Y, -signum);
BlockPos.Mutable mutable = pos.mutableCopy();
while (world.isInBuildLimit(mutable) && (world.isAir(mutable) || !world.getBlockState(mutable).canPlaceAt(world, mutable))) {
mutable.move(Direction.DOWN, signum);
}
return pos;
return mutable.toImmutable();
}
static void all(BlockPos origin, Consumer<BlockPos> consumer, Direction... directions) {
BlockPos.Mutable mutable = origin.mutableCopy();
static void fastAll(BlockPos origin, Consumer<BlockPos.Mutable> consumer, Direction... directions) {
final BlockPos immutable = origin instanceof BlockPos.Mutable m ? m.toImmutable() : origin;
final BlockPos.Mutable mutable = origin instanceof BlockPos.Mutable m ? m : origin.mutableCopy();
for (Direction facing : directions) {
mutable.set(origin);
consumer.accept(mutable.move(facing));
mutable.set(immutable);
}
}
static boolean any(BlockPos origin, Predicate<BlockPos> consumer, Direction... directions) {
BlockPos.Mutable mutable = origin.mutableCopy();
static boolean fastAny(BlockPos origin, Predicate<BlockPos> consumer, Direction... directions) {
final BlockPos immutable = origin instanceof BlockPos.Mutable m ? m.toImmutable() : origin;
final BlockPos.Mutable mutable = origin instanceof BlockPos.Mutable m ? m : origin.mutableCopy();
try {
for (Direction facing : directions) {
mutable.set(origin);
if (consumer.test(mutable.move(facing))) {
if (consumer.test(mutable.set(immutable).move(facing))) {
return true;
}
}
return false;
} finally {
mutable.set(immutable);
}
}
static Stream<BlockPos> adjacentNeighbours(BlockPos origin) {
@ -82,4 +88,37 @@ public interface PosHelper {
mutablePos.move(chainDirection.getOpposite());
return mutablePos.toImmutable();
}
public final class PositionRecord {
private final LongOpenHashSet visitedPositions = new LongOpenHashSet();
public BlockPos visit(BlockPos pos) {
visitedPositions.add(pos.asLong());
return pos;
}
public boolean hasVisited(BlockPos pos) {
return visitedPositions.contains(pos.asLong());
}
public int size() {
return visitedPositions.size();
}
public void clear() {
visitedPositions.clear();
}
public void forEach(World world, Reactor reactor) {
forEach(new BlockPos.Mutable(), pos -> reactor.react(world, world.getBlockState(pos), pos));
}
public void forEach(BlockPos.Mutable mutable, Consumer<BlockPos> consumer) {
visitedPositions.forEach(l -> consumer.accept(mutable.set(l)));
}
public interface Reactor {
void react(World w, BlockState state, BlockPos pos);
}
}
}

View file

@ -2,6 +2,7 @@
"logs": [ "minecraft:acacia_log", "minecraft:acacia_wood" ],
"leaves": [ "minecraft:acacia_leaves" ],
"rarity": 5,
"leavesRatio": 0.5,
"drops": [
{ "weight": 1, "item": "unicopia:rotten_apple" },
{ "weight": 2, "item": "unicopia:sweet_apple" },

View file

@ -2,6 +2,7 @@
"logs": [ "minecraft:oak_log", "minecraft:oak_wood" ],
"leaves": [ "minecraft:azalea_leaves", "minecraft:flowering_azalea_leaves" ],
"rarity": 5,
"leavesRatio": 0.5,
"drops": [
{ "weight": 1, "item": "unicopia:sour_apple" },
{ "weight": 2, "item": "unicopia:green_apple" },

View file

@ -2,6 +2,7 @@
"logs": [ "minecraft:birch_log", "minecraft:birch_wood" ],
"leaves": [ "minecraft:birch_leaves" ],
"rarity": 5,
"leavesRatio": 0.5,
"drops": [
{ "weight": 1, "item": "unicopia:rotten_apple" },
{ "weight": 2, "item": "unicopia:sweet_apple" },

View file

@ -2,6 +2,7 @@
"logs": [ "minecraft:cherry_log", "minecraft:cherry_wood" ],
"leaves": [ "minecraft:cherry_leaves" ],
"rarity": 7,
"leavesRatio": 0.5,
"drops": [
{ "weight": 1, "item": "minecraft:pink_petals" }
]

View file

@ -3,6 +3,7 @@
"leaves": [ "minecraft:dark_oak_leaves" ],
"wideTrunk": true,
"rarity": 5,
"leavesRatio": 0.5,
"drops": [
{ "weight": 1, "item": "unicopia:rotten_apple" },
{ "weight": 2, "item": "unicopia:sweet_apple" },

View file

@ -2,5 +2,6 @@
"logs": [ "minecraft:oak_log", "minecraft:oak_wood" ],
"leaves": [ "unicopia:green_apple_leaves" ],
"wideTrunk": false,
"leavesRatio": 0.5,
"drops": []
}

View file

@ -3,6 +3,7 @@
"leaves": [ "minecraft:jungle_leaves" ],
"wideTrunk": true,
"rarity": 5,
"leavesRatio": 0.5,
"drops": [
{ "weight": 5, "item": "unicopia:green_apple" },
{ "weight": 2, "item": "unicopia:sweet_apple" },

View file

@ -2,5 +2,6 @@
"logs": [ "minecraft:jungle_log", "minecraft:jungle_wood" ],
"leaves": [ "unicopia:mango_leaves" ],
"wideTrunk": false,
"leavesRatio": 0.5,
"drops": []
}

View file

@ -2,6 +2,7 @@
"logs": [ "minecraft:oak_log", "minecraft:oak_wood" ],
"leaves": [ "minecraft:oak_leaves" ],
"rarity": 3,
"leavesRatio": 0.5,
"drops": [
{ "weight": 1, "item": "unicopia:rotten_apple" },
{ "weight": 2, "tag": "c:acorns" },

View file

@ -2,5 +2,6 @@
"logs": [ "unicopia:palm_log", "unicopia:palm_wood" ],
"leaves": [ "unicopia:palm_leaves" ],
"wideTrunk": false,
"leavesRatio": 0.5,
"drops": []
}

View file

@ -2,5 +2,6 @@
"logs": [ "minecraft:oak_log", "minecraft:oak_wood" ],
"leaves": [ "unicopia:sour_apple_leaves" ],
"wideTrunk": false,
"leavesRatio": 0.5,
"drops": []
}

View file

@ -3,6 +3,7 @@
"leaves": [ "minecraft:spruce_leaves" ],
"wideTrunk": true,
"rarity": 3,
"leavesRatio": 0.5,
"drops": [
{ "weight": 1, "tag": "c:pinecones" },
{ "weight": 4, "tag": "c:sticks" }

View file

@ -2,5 +2,6 @@
"logs": [ "minecraft:oak_log", "minecraft:oak_wood" ],
"leaves": [ "unicopia:sweet_apple_leaves" ],
"wideTrunk": false,
"leavesRatio": 0.5,
"drops": []
}

View file

@ -2,5 +2,6 @@
"logs": [ "unicopia:zap_log" ],
"leaves": [ "unicopia:zap_leaves" ],
"wideTrunk": false,
"leavesRatio": 0.5,
"drops": []
}