diff --git a/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyKickAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyKickAbility.java index 667511be..657c9708 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyKickAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyKickAbility.java @@ -25,6 +25,7 @@ import net.minecraft.block.entity.BeehiveBlockEntity; import net.minecraft.entity.ItemEntity; import net.minecraft.entity.passive.BeeEntity; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; import net.minecraft.particle.BlockStateParticleEffect; import net.minecraft.particle.ParticleTypes; import net.minecraft.util.math.BlockPos; @@ -65,7 +66,7 @@ public class EarthPonyKickAbility implements Ability { @Override public boolean canApply(Pony player, Pos data) { BlockPos pos = data.pos(); - TreeType tree = TreeType.get(player.getWorld().getBlockState(pos)); + TreeType tree = TreeType.at(pos, player.getWorld()); return tree != TreeType.NONE && tree.findBase(player.getWorld(), pos).map(base -> { return tree.countBlocks(player.getWorld(), pos) > 0; @@ -94,7 +95,7 @@ public class EarthPonyKickAbility implements Ability { if (destr.getBlockDestruction(pos) + 4 >= BlockDestructionManager.MAX_DAMAGE) { if (!harmed || player.world.random.nextInt(30) == 0) { - TreeType.get(player.world.getBlockState(pos)).traverse(player.world, pos, (w, state, p, recurseLevel) -> { + TreeType.at(pos, player.world).traverse(player.world, pos, (w, state, p, recurseLevel) -> { if (recurseLevel < 5) { w.breakBlock(p, true); } else { @@ -150,10 +151,9 @@ public class EarthPonyKickAbility implements Ability { } private int dropApples(PlayerEntity player, BlockPos pos) { - TreeType tree = TreeType.get(player.world.getBlockState(pos)); + TreeType tree = TreeType.at(pos, player.world); if (tree.countBlocks(player.world, pos) > 0) { - List capturedDrops = Lists.newArrayList(); tree.traverse(player.world, pos, (world, state, position, recurse) -> { @@ -162,13 +162,17 @@ public class EarthPonyKickAbility implements Ability { affectBlockChange(player, position); if (world.getBlockState(position.down()).isAir()) { - WorldEvent.play(WorldEvent.DESTROY_BLOCK, world, position, state); - capturedDrops.add(new ItemEntity(world, + ItemStack stack = tree.pickRandomStack(state); + if (!stack.isEmpty()) { + 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() + stack )); + } } }); 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 f629d108..24980e7c 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,52 +4,37 @@ 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; import java.util.function.Supplier; import net.minecraft.block.BlockState; -import net.minecraft.block.LeavesBlock; import net.minecraft.item.ItemStack; 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 { - public static final TreeType NONE = new TreeType( +public interface TreeType { + TreeType NONE = new TreeTypeImpl( new Identifier("unicopia", "none"), false, new Weighted>(), Set.of(), Set.of() ); - private static final Direction[] WIDE_DIRS = new Direction[] { Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST }; + Direction[] WIDE_DIRS = new Direction[] { Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST }; - private final Identifier name; - private final boolean wideTrunk; - private final Set logs; - private final Set leaves; - private final Weighted> pool; - - TreeType(Identifier name, boolean wideTrunk, Weighted> pool, Set logs, Set leaves) { - this.name = name; - this.wideTrunk = wideTrunk; - this.pool = pool; - this.logs = logs; - this.leaves = leaves; - } - - public void traverse(World w, BlockPos start, Reactor consumer) { + default void traverse(World w, BlockPos start, Reactor consumer) { traverse(w, start, consumer, consumer); } - public void traverse(World w, BlockPos start, Reactor logConsumer, Reactor leavesConsumer) { + default 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 logs, Set leaves, World w, BlockPos start, int recurseLevel, int maxRecurse, Reactor logConsumer, Reactor leavesConsumer) { + default void traverse(Set logs, Set leaves, World w, BlockPos start, int recurseLevel, int maxRecurse, Reactor logConsumer, Reactor leavesConsumer) { if (this == NONE) { return; } @@ -90,7 +75,7 @@ public final class TreeType { /** * Recursively locates the base of the tree. */ - public Optional findBase(World w, BlockPos pos) { + default Optional findBase(World w, BlockPos pos) { return findBase(new HashSet(), w, new BlockPos.Mutable(pos.getX(), pos.getY(), pos.getZ())); } @@ -104,7 +89,7 @@ public final class TreeType { done.add(pos.move(Direction.DOWN).toImmutable()); } - if (wideTrunk) { + if (isWide()) { 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); @@ -117,7 +102,7 @@ public final class TreeType { /** * Counts the number of logs and leaves present in the targeted tree. */ - public int countBlocks(World w, BlockPos pos) { + default int countBlocks(World w, BlockPos pos) { if (this == NONE) { return 0; } @@ -140,7 +125,7 @@ public final class TreeType { /** * Locates the top of the tree's trunk. Usually the point where wood meets leaves. */ - public BlockPos findCanopy(World w, BlockPos pos) { + 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; @@ -151,38 +136,66 @@ public final class TreeType { return pos; } - public boolean isLeaves(BlockState state) { - return findMatch(leaves, state) && (!state.contains(LeavesBlock.PERSISTENT) || !state.get(LeavesBlock.PERSISTENT)); + /** + * 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)); } - public boolean isLog(BlockState state) { - return findMatch(logs, state); - } + boolean isLeaves(BlockState state); - public boolean matches(BlockState state) { + boolean isLog(BlockState state); + + default boolean matches(BlockState state) { return isLeaves(state) || isLog(state); } - public ItemStack pickRandomStack() { - return pool.get().map(Supplier::get).orElse(ItemStack.EMPTY); + ItemStack pickRandomStack(BlockState state); + + boolean isWide(); + + static TreeType at(BlockPos pos, World world) { + return TreeTypeLoader.INSTANCE.get(world.getBlockState(pos), pos, world); } - public static TreeType get(BlockState state) { + static TreeType of(BlockState state) { return TreeTypeLoader.INSTANCE.get(state); } - @Override - public boolean equals(Object o) { - return o instanceof TreeType && name.compareTo(((TreeType)o).name) == 0; - } + static TreeType of(TreeType logs, TreeType leaves) { + if (logs == NONE || leaves == NONE || Objects.equals(logs, leaves)) { + return logs; + } + return new TreeType() { + @Override + public boolean isLeaves(BlockState state) { + return leaves.isLeaves(state); + } - private static boolean findMatch(Set ids, BlockState state) { - return ids.contains(Registry.BLOCK.getId(state.getBlock())); - } + @Override + public boolean isLog(BlockState state) { + return logs.isLog(state); + } - @Override - public int hashCode() { - return name.hashCode(); + @Override + public ItemStack pickRandomStack(BlockState state) { + return (isLeaves(state) ? leaves : logs).pickRandomStack(state); + } + + @Override + public boolean isWide() { + return logs.isWide(); + } + }; } public interface Reactor { 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 new file mode 100644 index 00000000..4ebd4b08 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeTypeImpl.java @@ -0,0 +1,66 @@ +package com.minelittlepony.unicopia.ability.data.tree; + +import com.minelittlepony.unicopia.util.Weighted; + +import java.util.Set; +import java.util.function.Supplier; + +import net.minecraft.block.BlockState; +import net.minecraft.block.LeavesBlock; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.Registry; + +public final class TreeTypeImpl implements TreeType { + private final Identifier name; + private final boolean wideTrunk; + private final Set logs; + private final Set leaves; + private final Weighted> pool; + + TreeTypeImpl(Identifier name, boolean wideTrunk, Weighted> pool, Set logs, Set leaves) { + this.name = name; + this.wideTrunk = wideTrunk; + this.pool = pool; + this.logs = logs; + this.leaves = leaves; + } + + @Override + public boolean isLeaves(BlockState state) { + return findMatch(leaves, state) && isNonPersistent(state); + } + + @Override + public boolean isLog(BlockState state) { + return findMatch(logs, state); + } + + @Override + public boolean isWide() { + return wideTrunk; + } + + @Override + public ItemStack pickRandomStack(BlockState state) { + return pool.get().map(Supplier::get).orElse(ItemStack.EMPTY); + } + + @Override + public boolean equals(Object o) { + return o instanceof TreeTypeImpl && name.compareTo(((TreeTypeImpl)o).name) == 0; + } + + private static boolean findMatch(Set ids, BlockState state) { + return ids.contains(Registry.BLOCK.getId(state.getBlock())); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + static boolean isNonPersistent(BlockState state) { + return !state.contains(LeavesBlock.PERSISTENT) || !state.get(LeavesBlock.PERSISTENT); + } +} 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 8f38390c..cb58a19a 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 @@ -11,19 +11,24 @@ 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.PosHelper; import com.minelittlepony.unicopia.util.Weighted; import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener; import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.LeavesBlock; import net.minecraft.item.ItemStack; import net.minecraft.resource.JsonDataLoader; import net.minecraft.resource.ResourceManager; +import net.minecraft.tag.BlockTags; import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.profiler.Profiler; import net.minecraft.util.registry.Registry; +import net.minecraft.world.World; public class TreeTypeLoader extends JsonDataLoader implements IdentifiableResourceReloadListener { - private static final Identifier ID = new Identifier("unicopia", "data/tree_type"); public static final Gson GSON = new GsonBuilder() .registerTypeAdapter(Identifier.class, new ToStringAdapter<>(Identifier::new)) @@ -33,6 +38,9 @@ public class TreeTypeLoader extends JsonDataLoader implements IdentifiableResour private final Set entries = new HashSet<>(); + private final TreeType any1x = createDynamic(false); + private final TreeType any2x = createDynamic(true); + TreeTypeLoader() { super(GSON, "tree_types"); } @@ -42,8 +50,57 @@ public class TreeTypeLoader extends JsonDataLoader implements IdentifiableResour return ID; } + public TreeType get(BlockState state, BlockPos pos, World world) { + 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; + }); + } + public TreeType get(BlockState state) { - return entries.stream().filter(type -> type.matches(state)).findFirst().orElse(TreeType.NONE); + return entries.stream() + .filter(type -> type.matches(state)) + .findFirst() + .orElse(TreeType.NONE); + } + + private TreeType createDynamic(boolean wide) { + return new TreeType() { + @Override + public boolean isLeaves(BlockState state) { + return (state.isIn(BlockTags.LEAVES) || state.getBlock() instanceof LeavesBlock || entries.stream().anyMatch(t -> t.isLeaves(state))) && TreeTypeImpl.isNonPersistent(state); + } + + @Override + public boolean isLog(BlockState state) { + return state.isIn(BlockTags.LOGS_THAT_BURN) || entries.stream().anyMatch(t -> t.isLog(state)); + } + + @Override + public ItemStack pickRandomStack(BlockState state) { + TreeType type = get(state); + if (type == TreeType.NONE) { + type = get(Blocks.OAK_LOG.getDefaultState()); + } + return type.pickRandomStack(state); + } + + @Override + public boolean isWide() { + return wide; + } + }; } @Override @@ -55,7 +112,7 @@ public class TreeTypeLoader extends JsonDataLoader implements IdentifiableResour TreeTypeDef typeDef = GSON.fromJson(entry.getValue(), TreeTypeDef.class); if (typeDef != null) { - entries.add(new TreeType( + entries.add(new TreeTypeImpl( entry.getKey(), typeDef.wideTrunk, typeDef.getWeighted(new Weighted>()),