Rewrite and improve tree traversal

This commit is contained in:
Sollace 2021-02-13 13:50:24 +02:00
parent bbe7f67b7b
commit 0dc3860966
12 changed files with 344 additions and 277 deletions

View file

@ -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));
}
}

View file

@ -1,75 +1,165 @@
package com.minelittlepony.unicopia;
import java.util.stream.Collectors;
import com.minelittlepony.unicopia.item.UItems;
import com.minelittlepony.unicopia.util.PosHelper;
import com.minelittlepony.unicopia.util.Weighted;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.LeavesBlock;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
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.world.World;
public final class TreeType {
// TODO: move to datapack
private static final Set<TreeType> REGISTRY = new HashSet<>();
public static final TreeType NONE = new TreeType(
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 static final Supplier<ItemStack> SWEET = () -> new ItemStack(UItems.SWEET_APPLE);
private static final Supplier<ItemStack> GREEN = () -> new ItemStack(UItems.GREEN_APPLE);
private static final Supplier<ItemStack> ZAP = () -> new ItemStack(UItems.ZAP_APPLE);
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 Identifier name;
private final boolean wideTrunk;
private final Set<Identifier> logs;
private final Set<Identifier> leaves;
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.wideTrunk = wideTrunk;
this.pool = pool;
this.blocks = Arrays.stream(blocks).map(Registry.BLOCK::getId)
.collect(Collectors.toSet());
REGISTRY.add(this);
this.logs = logs;
this.leaves = leaves;
}
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) {
return blocks.contains(Registry.BLOCK.getId(state.getBlock()));
return isLeaves(state) || isLog(state);
}
public ItemStack pickRandomStack() {
@ -77,7 +167,7 @@ public final class TreeType {
}
public static TreeType get(BlockState state) {
return REGISTRY.stream().filter(type -> type.matches(state)).findFirst().orElse(TreeType.NONE);
return TreeTypeLoader.INSTANCE.get(state);
}
@Override
@ -85,8 +175,16 @@ public final class TreeType {
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
public int hashCode() {
return name.hashCode();
}
public interface Reactor {
void react(World w, BlockState state, BlockPos pos, int recurseLevel);
}
}

View file

@ -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);
});
}
}
}
}

View file

@ -2,6 +2,9 @@ package com.minelittlepony.unicopia;
import net.fabricmc.api.ModInitializer;
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.Logger;
@ -37,6 +40,7 @@ public class Unicopia implements ModInitializer {
AwaitTickQueue.tick(w);
((BlockDestructionManager.Source)w).getDestructionManager().tick();
});
ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(TreeTypeLoader.INSTANCE);
UItems.bootstrap();
UPotions.bootstrap();

View file

@ -1,6 +1,5 @@
package com.minelittlepony.unicopia.ability;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@ -9,29 +8,23 @@ import javax.annotation.Nullable;
import com.google.common.collect.Lists;
import com.minelittlepony.unicopia.BlockDestructionManager;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.TreeTraverser;
import com.minelittlepony.unicopia.TreeType;
import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.ability.data.Pos;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.util.PosHelper;
import com.minelittlepony.unicopia.util.RayTraceHelper;
import com.minelittlepony.unicopia.util.WorldEvent;
import com.minelittlepony.unicopia.util.shape.Shape;
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.LeavesBlock;
import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.particle.BlockStateParticleEffect;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.tag.BlockTags;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
/**
* Earth Pony kicking ability
@ -65,11 +58,11 @@ public class EarthPonyKickAbility implements Ability<Pos> {
if (p.isPresent()) {
BlockPos pos = p.get();
BlockState state = player.getWorld().getBlockState(pos);
TreeType tree = TreeType.get(player.getWorld().getBlockState(pos));
if (state.getBlock().isIn(BlockTags.LOGS)) {
pos = TreeTraverser.Descender.descendTree(player.getWorld(), state, pos).get();
if (TreeTraverser.Measurer.measureTree(player.getWorld(), state, pos) > 0) {
if (tree != TreeType.NONE) {
pos = tree.findBase(player.getWorld(), pos);
if (tree.countBlocks(player.getWorld(), pos) > 0) {
return new Pos(pos);
}
}
@ -101,12 +94,19 @@ public class EarthPonyKickAbility implements Ability<Pos> {
if (destr.getBlockDestruction(pos) + 4 >= BlockDestructionManager.MAX_DAMAGE) {
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);
} else {
int cost = dropApples(player.world, pos);
int cost = dropApples(player, pos);
if (cost > 0) {
iplayer.subtractEnergyCost(cost * 3);
@ -149,23 +149,32 @@ public class EarthPonyKickAbility implements Ability<Pos> {
}
}
private int dropApples(World w, BlockPos pos) {
BlockState log = w.getBlockState(pos);
int size = TreeTraverser.Measurer.measureTree(w, log, pos);
private int dropApples(PlayerEntity player, BlockPos pos) {
TreeType tree = TreeType.get(player.world.getBlockState(pos));
if (size > 0) {
BlockDestructionManager destr = ((BlockDestructionManager.Source)w).getDestructionManager();
TreeTraverser.Measurer.getParts(w, log, pos).forEach(position -> {
destr.damageBlock(position, 4);
});
if (tree.countBlocks(player.world, pos) > 0) {
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 -> {
item.setToDefaultPickupDelay();
w.spawnEntity(item);
player.world.spawnEntity(item);
});
return capturedDrops.size() / 3;
@ -174,27 +183,10 @@ public class EarthPonyKickAbility implements Ability<Pos> {
return 0;
}
private static void dropApplesPart(List<ItemEntity> drops, List<BlockPos> done, World w, BlockState log, BlockPos pos, int level) {
if (!done.contains(pos)) {
done.add(pos);
pos = TreeTraverser.Ascender.ascendTree(w, log, pos, false);
if (level < 10 && TreeTraverser.isWoodOrLeaf(w, log, pos)) {
BlockState state = w.getBlockState(pos);
private void affectBlockChange(PlayerEntity player, BlockPos position) {
BlockDestructionManager destr = ((BlockDestructionManager.Source)player.world).getDestructionManager();
if (state.getBlock() instanceof LeavesBlock && w.getBlockState(pos.down()).isAir()) {
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()
));
}
destr.damageBlock(position, 4);
PosHelper.all(pos, p -> {
dropApplesPart(drops, done, w, log, p, level + 1);
}, Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST);
}
}
}
}

View file

@ -1,5 +1,6 @@
package com.minelittlepony.unicopia.util;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Spliterator;
import java.util.Spliterators.AbstractSpliterator;
@ -19,6 +20,8 @@ import net.minecraft.world.World;
public interface PosHelper {
Direction[] HORIZONTAL = Arrays.stream(Direction.values()).filter(d -> d.getAxis().isHorizontal()).toArray(Direction[]::new);
static Vec3d offset(Vec3d a, Vec3i b) {
return a.add(b.getX(), b.getY(), b.getZ());
}

View 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" }
]
}

View 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" }
]
}

View 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" }
]
}

View 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" }
]
}

View 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" }
]
}

View 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" }
]
}