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,43 +149,38 @@ public class EarthPonyKickAbility implements Ability<Pos> {
public boolean apply(Pony iplayer, Pos data) { public boolean apply(Pony iplayer, Pos data) {
BlockPos pos = data.pos(); BlockPos pos = data.pos();
TreeType tree = TreeType.at(pos, iplayer.asWorld()); TreeType treeType = 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;
}
iplayer.setAnimation(Animation.KICK, Animation.Recipient.ANYONE); 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 (BlockDestructionManager.of(player.getWorld()).getBlockDestruction(pos) + 4 >= BlockDestructionManager.MAX_DAMAGE) {
if (player.getWorld().random.nextInt(30) == 0) { if (player.getWorld().random.nextInt(30) == 0) {
tree.traverse(player.getWorld(), pos, (w, state, p, recurseLevel) -> { tree.logs().forEach(player.getWorld(), (w, state, p) -> {
if (recurseLevel < 5) {
w.breakBlock(p, true); w.breakBlock(p, true);
} else { });
tree.leaves().forEach(player.getWorld(), (w, state, p) -> {
Block.dropStacks(w.getBlockState(p), w, 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); return true;
} else { }).isPresent();
int cost = dropApples(player, pos);
if (cost > 0) {
iplayer.subtractEnergyCost(cost / 7F);
}
}
return true;
} }
@Override @Override
@ -195,37 +190,32 @@ public class EarthPonyKickAbility implements Ability<Pos> {
@Override @Override
public void coolDown(Pony player, AbilitySlot slot) { public void coolDown(Pony player, AbilitySlot slot) {
player.asEntity().getHungerManager().addExhaustion(0.1F);
} }
private int dropApples(PlayerEntity player, BlockPos pos) { private int dropApples(PlayerEntity player, BlockPos pos) {
TreeType tree = TreeType.at(pos, player.getWorld()); TreeType treeType = TreeType.at(pos, player.getWorld());
return treeType.collectBlocks(player.getWorld(), pos).map(tree -> {
if (tree.countBlocks(player.getWorld(), pos) > 0) { tree.logs().forEach(player.getWorld(), (world, state, position) -> {
List<ItemEntity> capturedDrops = new ArrayList<>();
tree.traverse(player.getWorld(), pos, (world, state, position, recurse) -> {
affectBlockChange(player, position); affectBlockChange(player, position);
}, (world, state, position, recurse) -> { });
int[] dropCount = {0};
tree.leaves().forEach(player.getWorld(), (world, state, position) -> {
affectBlockChange(player, position); affectBlockChange(player, position);
List<ItemEntity> drops = buckBlock(tree, state, world, position) if (!buckBlock(treeType, state, world, position)
.filter(i -> !i.isEmpty()) .filter(i -> !i.isEmpty())
.map(stack -> createDrop(stack, position, world)) .map(stack -> createDrop(stack, position, world, dropCount))
.toList(); .toList().isEmpty()) {
if (!drops.isEmpty()) {
world.syncWorldEvent(WorldEvents.BLOCK_BROKEN, position, Block.getRawIdFromState(state)); world.syncWorldEvent(WorldEvents.BLOCK_BROKEN, position, Block.getRawIdFromState(state));
capturedDrops.addAll(drops);
} }
}); });
capturedDrops.forEach(player.getWorld()::spawnEntity); return dropCount[0] / 3;
}).orElse(0);
return capturedDrops.size() / 3;
}
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, ItemEntity entity = new ItemEntity(world,
pos.getX() + world.random.nextFloat(), pos.getX() + world.random.nextFloat(),
pos.getY() - 0.5, pos.getY() - 0.5,
@ -233,10 +223,12 @@ public class EarthPonyKickAbility implements Ability<Pos> {
stack stack
); );
entity.setToDefaultPickupDelay(); entity.setToDefaultPickupDelay();
world.spawnEntity(entity);
dropCount[0]++;
return entity; 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) { if (treeState.getBlock() instanceof Buckable buckable) {
return buckable.onBucked((ServerWorld)world, treeState, position).stream(); return buckable.onBucked((ServerWorld)world, treeState, position).stream();
@ -246,7 +238,7 @@ public class EarthPonyKickAbility implements Ability<Pos> {
BlockState below = world.getBlockState(down); BlockState below = world.getBlockState(down);
if (below.isAir()) { 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) { if (below.getBlock() instanceof Buckable buckable) {
@ -259,7 +251,7 @@ public class EarthPonyKickAbility implements Ability<Pos> {
private void affectBlockChange(PlayerEntity player, BlockPos position) { private void affectBlockChange(PlayerEntity player, BlockPos position) {
BlockDestructionManager.of(player.getWorld()).damageBlock(position, 4); BlockDestructionManager.of(player.getWorld()).damageBlock(position, 4);
PosHelper.all(position, p -> { PosHelper.fastAll(position, p -> {
BlockState s = player.getWorld().getBlockState(p); BlockState s = player.getWorld().getBlockState(p);
if (s.getBlock() instanceof BeehiveBlock) { 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.PosHelper;
import com.minelittlepony.unicopia.util.Weighted; import com.minelittlepony.unicopia.util.Weighted;
import java.util.HashSet;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
@ -12,159 +11,11 @@ import java.util.Set;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.random.Random; import net.minecraft.util.math.random.Random;
import net.minecraft.world.World; import net.minecraft.world.World;
public interface TreeType { public interface TreeType {
TreeType NONE = new TreeTypeImpl( TreeType NONE = new TreeTypeImpl(Unicopia.id("none"), false, Set.of(), Set.of(), Weighted.of(), 0, 0.5F);
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();
static TreeType at(BlockPos pos, World world) { static TreeType at(BlockPos pos, World world) {
return TreeTypes.get(world.getBlockState(pos), pos, world); return TreeTypes.get(world.getBlockState(pos), pos, world);
@ -198,10 +49,45 @@ public interface TreeType {
public boolean isWide() { public boolean isWide() {
return logs.isWide(); return logs.isWide();
} }
@Override
public float leavesRatio() {
return logs.leavesRatio();
}
}; };
} }
public interface Reactor { boolean isLeaves(BlockState state);
void react(World w, BlockState state, BlockPos pos, int recurseLevel);
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> logs,
Set<Identifier> leaves, Set<Identifier> leaves,
Supplier<Optional<Supplier<ItemStack>>> pool, Supplier<Optional<Supplier<ItemStack>>> pool,
int rarity int rarity,
float leavesRatio
) implements TreeType { ) implements TreeType {
@Override @Override
public boolean isLeaves(BlockState state) { public boolean isLeaves(BlockState state) {

View file

@ -64,6 +64,7 @@ public class TreeTypeLoader extends JsonDataLoader implements IdentifiableResour
final Set<Drop> drops; final Set<Drop> drops;
final boolean wideTrunk; final boolean wideTrunk;
final int rarity; final int rarity;
final float leavesRatio;
public TreeTypeDef(PacketByteBuf buffer) { public TreeTypeDef(PacketByteBuf buffer) {
logs = new HashSet<>(buffer.readList(PacketByteBuf::readIdentifier)); 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)); drops = new HashSet<>(buffer.readList(Drop::new));
wideTrunk = buffer.readBoolean(); wideTrunk = buffer.readBoolean();
rarity = buffer.readInt(); rarity = buffer.readInt();
leavesRatio = buffer.readFloat();
} }
public TreeType toTreeType(Identifier id) { 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(logs, "TreeType must have logs"),
Objects.requireNonNull(leaves, "TreeType must have leaves"), Objects.requireNonNull(leaves, "TreeType must have leaves"),
Weighted.of(drops), 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.writeCollection(drops, (a, b) -> b.write(a));
buffer.writeBoolean(wideTrunk); buffer.writeBoolean(wideTrunk);
buffer.writeInt(rarity); buffer.writeInt(rarity);
buffer.writeFloat(leavesRatio);
} }
static class Drop implements Weighted.Buildable<Supplier<ItemStack>> { 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.registry.tag.BlockTags;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.random.Random; import net.minecraft.util.math.random.Random;
import net.minecraft.world.World; import net.minecraft.world.World;
@ -29,18 +30,8 @@ public class TreeTypes {
return entries.stream() return entries.stream()
.filter(type -> type.matches(state)) .filter(type -> type.matches(state))
.findFirst() .findFirst()
.map(type -> TreeType.of(type, type.findLeavesType(world, pos))) .map(type -> TreeType.of(type, findLeavesType(type, world, pos)))
.orElseGet(() -> { .orElseGet(() -> any1x.matches(state) ? (PosHelper.fastAny(pos, p -> world.getBlockState(p).isOf(state.getBlock()), PosHelper.HORIZONTAL) ? any2x : any1x) : TreeType.NONE);
if (any1x.matches(state)) {
if (PosHelper.any(pos, p -> world.getBlockState(p).isOf(state.getBlock()), PosHelper.HORIZONTAL)) {
return any2x;
}
return any1x;
}
return TreeType.NONE;
});
} }
static TreeType get(BlockState state) { static TreeType get(BlockState state) {
@ -50,6 +41,16 @@ public class TreeTypes {
.orElse(TreeType.NONE); .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) { private static TreeType createDynamic(boolean wide) {
return new TreeType() { return new TreeType() {
@Override @Override
@ -75,6 +76,11 @@ public class TreeTypes {
public boolean isWide() { public boolean isWide() {
return wide; 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) { 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) { 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 com.google.common.collect.Lists;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Direction.Axis;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.Vec3i; import net.minecraft.util.math.Vec3i;
import net.minecraft.world.BlockView; import net.minecraft.world.BlockView;
@ -29,30 +29,36 @@ public interface PosHelper {
} }
static BlockPos findSolidGroundAt(World world, BlockPos pos, int signum) { static BlockPos findSolidGroundAt(World world, BlockPos pos, int signum) {
while (world.isInBuildLimit(pos) && (world.isAir(pos) || !world.getBlockState(pos).canPlaceAt(world, pos))) { BlockPos.Mutable mutable = pos.mutableCopy();
pos = pos.offset(Axis.Y, -signum); 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) { static void fastAll(BlockPos origin, Consumer<BlockPos.Mutable> consumer, Direction... directions) {
BlockPos.Mutable mutable = origin.mutableCopy(); 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) { for (Direction facing : directions) {
mutable.set(origin);
consumer.accept(mutable.move(facing)); consumer.accept(mutable.move(facing));
mutable.set(immutable);
} }
} }
static boolean any(BlockPos origin, Predicate<BlockPos> consumer, Direction... directions) { static boolean fastAny(BlockPos origin, Predicate<BlockPos> consumer, Direction... directions) {
BlockPos.Mutable mutable = origin.mutableCopy(); final BlockPos immutable = origin instanceof BlockPos.Mutable m ? m.toImmutable() : origin;
for (Direction facing : directions) { final BlockPos.Mutable mutable = origin instanceof BlockPos.Mutable m ? m : origin.mutableCopy();
mutable.set(origin); try {
if (consumer.test(mutable.move(facing))) { for (Direction facing : directions) {
return true; if (consumer.test(mutable.set(immutable).move(facing))) {
return true;
}
} }
return false;
} finally {
mutable.set(immutable);
} }
return false;
} }
static Stream<BlockPos> adjacentNeighbours(BlockPos origin) { static Stream<BlockPos> adjacentNeighbours(BlockPos origin) {
@ -82,4 +88,37 @@ public interface PosHelper {
mutablePos.move(chainDirection.getOpposite()); mutablePos.move(chainDirection.getOpposite());
return mutablePos.toImmutable(); 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" ], "logs": [ "minecraft:acacia_log", "minecraft:acacia_wood" ],
"leaves": [ "minecraft:acacia_leaves" ], "leaves": [ "minecraft:acacia_leaves" ],
"rarity": 5, "rarity": 5,
"leavesRatio": 0.5,
"drops": [ "drops": [
{ "weight": 1, "item": "unicopia:rotten_apple" }, { "weight": 1, "item": "unicopia:rotten_apple" },
{ "weight": 2, "item": "unicopia:sweet_apple" }, { "weight": 2, "item": "unicopia:sweet_apple" },

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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