diff --git a/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyKickAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyKickAbility.java index 2698b36c..31a67e15 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyKickAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyKickAbility.java @@ -149,43 +149,38 @@ public class EarthPonyKickAbility implements Ability { 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); - ParticleUtils.spawnParticle(iplayer.asWorld(), UParticles.GROUND_POUND, data.vec(), Vec3d.ZERO); + return treeType.collectBlocks(iplayer.asWorld(), pos).filter(tree -> { + ParticleUtils.spawnParticle(iplayer.asWorld(), UParticles.GROUND_POUND, data.vec(), Vec3d.ZERO); - PlayerEntity player = iplayer.asEntity(); + 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) { + if (BlockDestructionManager.of(player.getWorld()).getBlockDestruction(pos) + 4 >= BlockDestructionManager.MAX_DAMAGE) { + if (player.getWorld().random.nextInt(30) == 0) { + 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); + }); + } + + iplayer.subtractEnergyCost(3); + } else { + int cost = dropApples(player, pos); + + if (cost > 0) { + iplayer.subtractEnergyCost(cost / 7F); + } } - iplayer.subtractEnergyCost(3); - } else { - int cost = dropApples(player, pos); - - if (cost > 0) { - iplayer.subtractEnergyCost(cost / 7F); - } - } - - return true; + return true; + }).isPresent(); } @Override @@ -195,37 +190,32 @@ public class EarthPonyKickAbility implements Ability { @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 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 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 0; + return dropCount[0] / 3; + }).orElse(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 { stack ); entity.setToDefaultPickupDelay(); + world.spawnEntity(entity); + dropCount[0]++; return entity; } - private Stream buckBlock(TreeType tree, BlockState treeState, World world, BlockPos position) { + private Stream 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 { 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 { 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) { diff --git a/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeTraverser.java b/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeTraverser.java new file mode 100644 index 00000000..164a4180 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeTraverser.java @@ -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 findBase(World w, BlockPos pos) { + logs.clear(); + return findBase(w, pos.mutableCopy()); + } + + private Optional 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()); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeType.java b/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeType.java index 2f461d2b..1b875f60 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeType.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeType.java @@ -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 logs, Set 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 logs, Set 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 findBase(World w, BlockPos pos) { - return findBase(new HashSet<>(), w, new BlockPos.Mutable(pos.getX(), pos.getY(), pos.getZ())); - } - - private Optional findBase(Set 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 logs = new HashSet<>(); - Set 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 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) { } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeTypeImpl.java b/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeTypeImpl.java index 487ec2d0..bc38d143 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeTypeImpl.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeTypeImpl.java @@ -17,7 +17,8 @@ public record TreeTypeImpl ( Set logs, Set leaves, Supplier>> pool, - int rarity + int rarity, + float leavesRatio ) implements TreeType { @Override public boolean isLeaves(BlockState state) { diff --git a/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeTypeLoader.java b/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeTypeLoader.java index 032b6ad5..a2b2bcc9 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeTypeLoader.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeTypeLoader.java @@ -64,6 +64,7 @@ public class TreeTypeLoader extends JsonDataLoader implements IdentifiableResour final Set 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> { diff --git a/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeTypes.java b/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeTypes.java index d3311fa3..b433cfe5 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeTypes.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeTypes.java @@ -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; + } }; } } diff --git a/src/main/java/com/minelittlepony/unicopia/server/world/Altar.java b/src/main/java/com/minelittlepony/unicopia/server/world/Altar.java index 323512b6..43f3e8fe 100644 --- a/src/main/java/com/minelittlepony/unicopia/server/world/Altar.java +++ b/src/main/java/com/minelittlepony/unicopia/server/world/Altar.java @@ -97,7 +97,7 @@ public record Altar(BlockPos origin, Set 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 pillarPosCollector) { diff --git a/src/main/java/com/minelittlepony/unicopia/util/PosHelper.java b/src/main/java/com/minelittlepony/unicopia/util/PosHelper.java index be63ea62..7e203abd 100644 --- a/src/main/java/com/minelittlepony/unicopia/util/PosHelper.java +++ b/src/main/java/com/minelittlepony/unicopia/util/PosHelper.java @@ -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 consumer, Direction... directions) { - BlockPos.Mutable mutable = origin.mutableCopy(); + static void fastAll(BlockPos origin, Consumer 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 consumer, Direction... directions) { - BlockPos.Mutable mutable = origin.mutableCopy(); - for (Direction facing : directions) { - mutable.set(origin); - if (consumer.test(mutable.move(facing))) { - return true; + static boolean fastAny(BlockPos origin, Predicate 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) { + if (consumer.test(mutable.set(immutable).move(facing))) { + return true; + } } + return false; + } finally { + mutable.set(immutable); } - return false; } static Stream 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 consumer) { + visitedPositions.forEach(l -> consumer.accept(mutable.set(l))); + } + + public interface Reactor { + void react(World w, BlockState state, BlockPos pos); + } + } } diff --git a/src/main/resources/data/unicopia/tree_types/acacia.json b/src/main/resources/data/unicopia/tree_types/acacia.json index fb7a2ddc..500f2dec 100644 --- a/src/main/resources/data/unicopia/tree_types/acacia.json +++ b/src/main/resources/data/unicopia/tree_types/acacia.json @@ -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" }, diff --git a/src/main/resources/data/unicopia/tree_types/azalea.json b/src/main/resources/data/unicopia/tree_types/azalea.json index b33b609a..4a8c9c47 100644 --- a/src/main/resources/data/unicopia/tree_types/azalea.json +++ b/src/main/resources/data/unicopia/tree_types/azalea.json @@ -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" }, diff --git a/src/main/resources/data/unicopia/tree_types/birch.json b/src/main/resources/data/unicopia/tree_types/birch.json index 1026174f..cf2b922b 100644 --- a/src/main/resources/data/unicopia/tree_types/birch.json +++ b/src/main/resources/data/unicopia/tree_types/birch.json @@ -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" }, diff --git a/src/main/resources/data/unicopia/tree_types/cherry.json b/src/main/resources/data/unicopia/tree_types/cherry.json index cb0c9512..47c5761b 100644 --- a/src/main/resources/data/unicopia/tree_types/cherry.json +++ b/src/main/resources/data/unicopia/tree_types/cherry.json @@ -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" } ] diff --git a/src/main/resources/data/unicopia/tree_types/dark_oak.json b/src/main/resources/data/unicopia/tree_types/dark_oak.json index c384ca7c..30355a62 100644 --- a/src/main/resources/data/unicopia/tree_types/dark_oak.json +++ b/src/main/resources/data/unicopia/tree_types/dark_oak.json @@ -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" }, diff --git a/src/main/resources/data/unicopia/tree_types/green_apple.json b/src/main/resources/data/unicopia/tree_types/green_apple.json index cccd1ad4..f986a9a7 100644 --- a/src/main/resources/data/unicopia/tree_types/green_apple.json +++ b/src/main/resources/data/unicopia/tree_types/green_apple.json @@ -2,5 +2,6 @@ "logs": [ "minecraft:oak_log", "minecraft:oak_wood" ], "leaves": [ "unicopia:green_apple_leaves" ], "wideTrunk": false, + "leavesRatio": 0.5, "drops": [] } \ No newline at end of file diff --git a/src/main/resources/data/unicopia/tree_types/jungle.json b/src/main/resources/data/unicopia/tree_types/jungle.json index 26af3da7..a08e5eca 100644 --- a/src/main/resources/data/unicopia/tree_types/jungle.json +++ b/src/main/resources/data/unicopia/tree_types/jungle.json @@ -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" }, diff --git a/src/main/resources/data/unicopia/tree_types/mango.json b/src/main/resources/data/unicopia/tree_types/mango.json index 51af653f..d5a1afd0 100644 --- a/src/main/resources/data/unicopia/tree_types/mango.json +++ b/src/main/resources/data/unicopia/tree_types/mango.json @@ -2,5 +2,6 @@ "logs": [ "minecraft:jungle_log", "minecraft:jungle_wood" ], "leaves": [ "unicopia:mango_leaves" ], "wideTrunk": false, + "leavesRatio": 0.5, "drops": [] } \ No newline at end of file diff --git a/src/main/resources/data/unicopia/tree_types/oak.json b/src/main/resources/data/unicopia/tree_types/oak.json index 4b00e555..29dfdb19 100644 --- a/src/main/resources/data/unicopia/tree_types/oak.json +++ b/src/main/resources/data/unicopia/tree_types/oak.json @@ -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" }, diff --git a/src/main/resources/data/unicopia/tree_types/palm.json b/src/main/resources/data/unicopia/tree_types/palm.json index 069e6e19..a2325a89 100644 --- a/src/main/resources/data/unicopia/tree_types/palm.json +++ b/src/main/resources/data/unicopia/tree_types/palm.json @@ -2,5 +2,6 @@ "logs": [ "unicopia:palm_log", "unicopia:palm_wood" ], "leaves": [ "unicopia:palm_leaves" ], "wideTrunk": false, + "leavesRatio": 0.5, "drops": [] } \ No newline at end of file diff --git a/src/main/resources/data/unicopia/tree_types/sour_apple.json b/src/main/resources/data/unicopia/tree_types/sour_apple.json index b2825d9f..3b36d95b 100644 --- a/src/main/resources/data/unicopia/tree_types/sour_apple.json +++ b/src/main/resources/data/unicopia/tree_types/sour_apple.json @@ -2,5 +2,6 @@ "logs": [ "minecraft:oak_log", "minecraft:oak_wood" ], "leaves": [ "unicopia:sour_apple_leaves" ], "wideTrunk": false, + "leavesRatio": 0.5, "drops": [] } \ No newline at end of file diff --git a/src/main/resources/data/unicopia/tree_types/spruce.json b/src/main/resources/data/unicopia/tree_types/spruce.json index d4f074d4..db03e71b 100644 --- a/src/main/resources/data/unicopia/tree_types/spruce.json +++ b/src/main/resources/data/unicopia/tree_types/spruce.json @@ -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" } diff --git a/src/main/resources/data/unicopia/tree_types/sweet_apple.json b/src/main/resources/data/unicopia/tree_types/sweet_apple.json index e133a32c..04995d29 100644 --- a/src/main/resources/data/unicopia/tree_types/sweet_apple.json +++ b/src/main/resources/data/unicopia/tree_types/sweet_apple.json @@ -2,5 +2,6 @@ "logs": [ "minecraft:oak_log", "minecraft:oak_wood" ], "leaves": [ "unicopia:sweet_apple_leaves" ], "wideTrunk": false, + "leavesRatio": 0.5, "drops": [] } \ No newline at end of file diff --git a/src/main/resources/data/unicopia/tree_types/zap_apple.json b/src/main/resources/data/unicopia/tree_types/zap_apple.json index e6f3ad61..7ca43ce2 100644 --- a/src/main/resources/data/unicopia/tree_types/zap_apple.json +++ b/src/main/resources/data/unicopia/tree_types/zap_apple.json @@ -2,5 +2,6 @@ "logs": [ "unicopia:zap_log" ], "leaves": [ "unicopia:zap_leaves" ], "wideTrunk": false, + "leavesRatio": 0.5, "drops": [] } \ No newline at end of file