diff --git a/README.md b/README.md index 6d3354e..7133b05 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ As a challenge I'm trying to make it as user-friendly as possible. (It ain't the - [x] Version "control" - [x] Shared Ender Chest - [x] Shared EC Access control (via config) -- [ ] Vein miner +- [x] Vein miner - [ ] Telekinesis ### Commands diff --git a/src/main/java/wtf/hak/survivalfabric/SurvivalFabric.java b/src/main/java/wtf/hak/survivalfabric/SurvivalFabric.java index 3ff7642..ae64b41 100644 --- a/src/main/java/wtf/hak/survivalfabric/SurvivalFabric.java +++ b/src/main/java/wtf/hak/survivalfabric/SurvivalFabric.java @@ -3,12 +3,18 @@ package wtf.hak.survivalfabric; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerEntityEvents; +import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents; +import net.minecraft.entity.Entity; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import wtf.hak.survivalfabric.commands.ReloadConfigCommand; import wtf.hak.survivalfabric.commands.SlimeChunkCommand; import wtf.hak.survivalfabric.commands.SpectatorCommand; import wtf.hak.survivalfabric.sharedenderchest.SharedEnderChest; +import wtf.hak.survivalfabric.veinminer.VeinMinerEvents; import static wtf.hak.survivalfabric.config.ConfigManager.getConfig; @@ -26,5 +32,18 @@ public class SurvivalFabric implements ModInitializer { if(getConfig().sharedEnderChestEnabled) new SharedEnderChest().onInitialize(); + + if(getConfig().veinMinerEnabled) { + PlayerBlockBreakEvents.BEFORE.register((world, player, pos, state, blockEntity) -> { + if (player instanceof ServerPlayerEntity serverPlayer) { + return VeinMinerEvents.beforeBlockBreak(world, serverPlayer, pos, state); + } + else { + return true; + } + }); + + ServerEntityEvents.ENTITY_LOAD.register(VeinMinerEvents::onEntityLoad); + } } } \ No newline at end of file diff --git a/src/main/java/wtf/hak/survivalfabric/config/Config.java b/src/main/java/wtf/hak/survivalfabric/config/Config.java index 8a70fdc..9456ee0 100644 --- a/src/main/java/wtf/hak/survivalfabric/config/Config.java +++ b/src/main/java/wtf/hak/survivalfabric/config/Config.java @@ -35,6 +35,9 @@ public class Config { public String inSlimeChunkMessage = "§aYou're currently in a slime chunk"; public String notInSlimeChunkMessage = "§cYou're currently not in a slime chunk"; + public boolean veinMinerEnabled = true; + public int maxVeinSize = 99999; + public ScreenHandlerType screenHandlerType() { return switch (sharedEnderChestRows) { case 1 -> ScreenHandlerType.GENERIC_9X1; diff --git a/src/main/java/wtf/hak/survivalfabric/veinminer/Drill.java b/src/main/java/wtf/hak/survivalfabric/veinminer/Drill.java new file mode 100644 index 0000000..7e40255 --- /dev/null +++ b/src/main/java/wtf/hak/survivalfabric/veinminer/Drill.java @@ -0,0 +1,10 @@ +package wtf.hak.survivalfabric.veinminer; + +import net.minecraft.block.BlockState; +import net.minecraft.util.math.BlockPos; + +public interface Drill { + boolean canHandle(BlockState blockState); + boolean isRightTool(BlockPos pos); + boolean drill(BlockPos blockPos); +} \ No newline at end of file diff --git a/src/main/java/wtf/hak/survivalfabric/veinminer/VeinMinerEvents.java b/src/main/java/wtf/hak/survivalfabric/veinminer/VeinMinerEvents.java new file mode 100644 index 0000000..320547a --- /dev/null +++ b/src/main/java/wtf/hak/survivalfabric/veinminer/VeinMinerEvents.java @@ -0,0 +1,59 @@ +package wtf.hak.survivalfabric.veinminer; + +import net.minecraft.block.BlockState; +import net.minecraft.entity.Entity; +import net.minecraft.registry.Registries; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import wtf.hak.survivalfabric.veinminer.drills.LeavesDrill; +import wtf.hak.survivalfabric.veinminer.drills.OreDrill; +import wtf.hak.survivalfabric.veinminer.drills.WoodDrill; + +public class VeinMinerEvents { + + public static boolean beforeBlockBreak(World world, ServerPlayerEntity player, BlockPos pos, BlockState state) { + if (world.isClient) { + return true; + } + + boolean isVeinMining = VeinMinerSession.sessionForPlayer(player) != null; + boolean canVeinMine = player.isInSneakingPose(); + if (canVeinMine && !isVeinMining) { + VeinMinerSession session = VeinMinerSession.start(player, (ServerWorld)world, pos); + boolean shouldContinue = !mine(session); + session.finish(); + return shouldContinue; + } + else { + return true; + } + } + + public static void onEntityLoad(Entity entity, ServerWorld world) { + BlockPos pos = entity.getBlockPos(); + VeinMinerSession session = VeinMinerSession.sessionForPosition(pos); + if (session != null) { + entity.setPos(session.initialPos.getX(), session.initialPos.getY(), session.initialPos.getZ()); + } + } + + private static boolean mine(VeinMinerSession session) { + Drill[] drills = new Drill[] { + new OreDrill(session), + new WoodDrill(session), + new LeavesDrill(session) + }; + + BlockPos pos = session.initialPos; + BlockState blockState = session.world.getBlockState(pos); + for (Drill drill : drills) { + if (drill.canHandle(blockState) && drill.isRightTool(pos)) { + return drill.drill(pos); + } + } + + return false; + } +} \ No newline at end of file diff --git a/src/main/java/wtf/hak/survivalfabric/veinminer/VeinMinerSession.java b/src/main/java/wtf/hak/survivalfabric/veinminer/VeinMinerSession.java new file mode 100644 index 0000000..fa7b6f6 --- /dev/null +++ b/src/main/java/wtf/hak/survivalfabric/veinminer/VeinMinerSession.java @@ -0,0 +1,66 @@ +package wtf.hak.survivalfabric.veinminer; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; + +public class VeinMinerSession { + private static ArrayList sessions = new ArrayList<>(); + + public ServerPlayerEntity player; + public ServerWorld world; + public Set positions; + public BlockPos initialPos; + + public static VeinMinerSession sessionForPlayer(ServerPlayerEntity player) { + for (var session: sessions) { + if (session.player == player) { + return session; + } + } + return null; + } + + public static VeinMinerSession sessionForPosition(BlockPos position) { + for (var session: sessions) { + if (session.positions.contains(position)) { + return session; + } + } + return null; + } + + public static VeinMinerSession start(ServerPlayerEntity player, ServerWorld world, BlockPos initialPos) { + var session = new VeinMinerSession(player, world, initialPos); + sessions.add(session); + return session; + } + + private static void finish(VeinMinerSession session) { + sessions.remove(session); + } + + private VeinMinerSession(ServerPlayerEntity player, ServerWorld world, BlockPos initialPos) { + this.player = player; + this.world = world; + this.initialPos = initialPos; + this.positions = new HashSet<>(); + positions.add(initialPos); + } + + public void addPosition(BlockPos pos) { + positions.add(pos); + } + + public void removePosition(BlockPos pos) { + positions.remove(pos); + } + + public void finish() { + finish(this); + } +} \ No newline at end of file diff --git a/src/main/java/wtf/hak/survivalfabric/veinminer/drills/DrillBase.java b/src/main/java/wtf/hak/survivalfabric/veinminer/drills/DrillBase.java new file mode 100644 index 0000000..8d13d44 --- /dev/null +++ b/src/main/java/wtf/hak/survivalfabric/veinminer/drills/DrillBase.java @@ -0,0 +1,133 @@ +package wtf.hak.survivalfabric.veinminer.drills; +import net.minecraft.block.BlockState; + +import java.util.ArrayList; + +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.MutableText; +import net.minecraft.text.PlainTextContent; +import net.minecraft.text.Text; +import net.minecraft.util.math.BlockPos; +import wtf.hak.survivalfabric.veinminer.Drill; +import wtf.hak.survivalfabric.veinminer.VeinMinerSession; + +import static wtf.hak.survivalfabric.SurvivalFabric.LOGGER; + +public class DrillBase implements Drill { + + protected VeinMinerSession session; + + public DrillBase(VeinMinerSession session) { + this.session = session; + } + + @Override + public boolean canHandle(BlockState blockId) { + return false; + } + + @Override + public boolean drill(BlockPos blockPos) { + return false; + } + + @Override + public boolean isRightTool(BlockPos pos) { + var blockState = session.world.getBlockState(pos); + return session.player.getMainHandStack().isSuitableFor(blockState); + } + + protected interface ForXYZHandler { + public void handle(BlockPos pos); + } + + protected interface ForXYZCounter { + public int handle(BlockPos pos); + } + + protected void forXYZ(BlockPos pos, int max, ForXYZHandler handler) { + forXYZ(pos, max, handlerPos -> { + handler.handle(handlerPos); + return 0; + }, false); + } + + protected int forXYZ(BlockPos pos, int max, ForXYZCounter handler) { + return forXYZ(pos, max, handler, false); + } + + protected void forXYZ(BlockPos pos, int max, ForXYZHandler handler, boolean forceVertical) { + forXYZ(pos, max, handlerPos -> { + handler.handle(handlerPos); + return 0; + }, forceVertical); + } + + protected int forXYZ(BlockPos pos, int max, ForXYZCounter handler, boolean forceVertical) { + ArrayList offsets = new ArrayList(); + for (int d = 0; d <= max; ++d) { + offsets.add(d); + if (d != -d) { + offsets.add(-d); + } + } + + String[] order = new String[] { "x", "y", "z" }; + if (forceVertical) { + order = new String[] { "y", "x", "z" }; + } + else { + ServerPlayerEntity player = session.player; + boolean majorPitchChange = player.getPitch() < -45.0 || player.getPitch() > 45.0; + boolean majorYawChange = (player.getYaw() > 45.0 && player.getYaw() < 135.0) || (player.getYaw() < -45.0 && player.getYaw() > -135.0); + if (majorPitchChange) { + if (majorYawChange) { + order = new String[] { "y", "z", "x" }; + } + else { + order = new String[] { "y", "x", "z" }; + } + } + else { + if (majorYawChange) { + order = new String[] { "z", "y", "x" }; + } + } + } + + int counter = 0; + for (int i1: offsets) { + for (int i2: offsets) { + for (int i3: offsets) { + int ix = order[0] == "x" ? i1 : order[1] == "x" ? i2 : i3; + int iy = order[0] == "y" ? i1 : order[1] == "y" ? i2 : i3; + int iz = order[0] == "z" ? i1 : order[1] == "z" ? i2 : i3; + + int px = pos.getX() + ix; + int py = pos.getY() + iy; + int pz = pos.getZ() + iz; + + counter += handler.handle(new BlockPos(px, py, pz)); + } + } + } + + return counter; + } + + protected boolean tryBreakBlock(BlockPos blockPos) { + session.addPosition(blockPos); + boolean success = isRightTool(blockPos) && session.player.interactionManager.tryBreakBlock(blockPos); + if (!success) { + session.removePosition(blockPos); + } + return success; + } + + protected void _log(String message) { + PlainTextContent literal = new PlainTextContent.Literal(message); + Text text = MutableText.of(literal); + session.player.sendMessage(text); + LOGGER.info(message); + } +} \ No newline at end of file diff --git a/src/main/java/wtf/hak/survivalfabric/veinminer/drills/LeavesDrill.java b/src/main/java/wtf/hak/survivalfabric/veinminer/drills/LeavesDrill.java new file mode 100644 index 0000000..6718108 --- /dev/null +++ b/src/main/java/wtf/hak/survivalfabric/veinminer/drills/LeavesDrill.java @@ -0,0 +1,62 @@ +package wtf.hak.survivalfabric.veinminer.drills; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import wtf.hak.survivalfabric.veinminer.VeinMinerSession; + +import java.util.ArrayDeque; + +import static wtf.hak.survivalfabric.config.ConfigManager.getConfig; + +public class LeavesDrill extends DrillBase { + + public LeavesDrill(VeinMinerSession session) { + super(session); + } + + public static final TagKey leavesTag = TagKey.of(RegistryKeys.BLOCK, Identifier.of("survivalfabric", "leaves")); + + @Override + public boolean canHandle(BlockState blockState) { + return blockState.isIn(leavesTag); + } + + @Override + public boolean drill(BlockPos startPos) { + ServerWorld world = session.world; + Block initialBlock = world.getBlockState(startPos).getBlock(); + int brokenLeaves = 0; + ArrayDeque pending = new ArrayDeque(); + pending.add(startPos); + + while (!pending.isEmpty() && brokenLeaves < getConfig().maxVeinSize) { + BlockPos leavesPos = pending.remove(); + Block leavesBlock = world.getBlockState(leavesPos).getBlock(); + if (tryBreakBlock(leavesPos)) { + if (leavesBlock == initialBlock) { + brokenLeaves += 1; + } + + if (leavesBlock == initialBlock) { + // look around current block + forXYZ(leavesPos, 1, newPos -> { + BlockState newBlockState = world.getBlockState(newPos); + Block newBlock = newBlockState.getBlock(); + boolean isSameOreBlock = newBlock == leavesBlock; + if (!pending.contains(newPos) && isSameOreBlock) { + pending.add(newPos); + } + }); + } + } + } + + return true; + } + +} \ No newline at end of file diff --git a/src/main/java/wtf/hak/survivalfabric/veinminer/drills/OreDrill.java b/src/main/java/wtf/hak/survivalfabric/veinminer/drills/OreDrill.java new file mode 100644 index 0000000..fec8e6f --- /dev/null +++ b/src/main/java/wtf/hak/survivalfabric/veinminer/drills/OreDrill.java @@ -0,0 +1,64 @@ +package wtf.hak.survivalfabric.veinminer.drills; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.Identifier; + +import java.util.ArrayDeque; + +import net.minecraft.registry.Registries; +import net.minecraft.util.math.BlockPos; +import wtf.hak.survivalfabric.veinminer.VeinMinerSession; + +import static wtf.hak.survivalfabric.config.ConfigManager.getConfig; + +public class OreDrill extends DrillBase { + + public OreDrill(VeinMinerSession session) { + super(session); + } + + public static final TagKey oreTag = TagKey.of(RegistryKeys.BLOCK, Identifier.of("survivalfabric", "ore")); + + @Override + public boolean canHandle(BlockState blockState) { + return blockState.isIn(oreTag); + } + + @Override + public boolean drill(BlockPos startPos) { + ServerWorld world = session.world; + Block initialBlock = world.getBlockState(startPos).getBlock(); + int brokenOre = 0; + ArrayDeque pending = new ArrayDeque(); + pending.add(startPos); + + while (!pending.isEmpty() && brokenOre < getConfig().maxVeinSize) { + BlockPos orePos = pending.remove(); + Block oreBlock = world.getBlockState(orePos).getBlock(); + if (tryBreakBlock(orePos)) { + if (oreBlock == initialBlock) { + brokenOre += 1; + } + + if (oreBlock == initialBlock) { + // look around current block + forXYZ(orePos, 1, newPos -> { + BlockState newBlockState = world.getBlockState(newPos); + Block newBlock = newBlockState.getBlock(); + boolean isSameOreBlock = newBlock == oreBlock; + if (!pending.contains(newPos) && isSameOreBlock) { + pending.add(newPos); + } + }); + } + } + } + + return true; + } + +} \ No newline at end of file diff --git a/src/main/java/wtf/hak/survivalfabric/veinminer/drills/WoodDrill.java b/src/main/java/wtf/hak/survivalfabric/veinminer/drills/WoodDrill.java new file mode 100644 index 0000000..1e57a4c --- /dev/null +++ b/src/main/java/wtf/hak/survivalfabric/veinminer/drills/WoodDrill.java @@ -0,0 +1,85 @@ +package wtf.hak.survivalfabric.veinminer.drills; + +import net.minecraft.block.BlockState; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.Identifier; + + +import java.util.ArrayDeque; + +import net.minecraft.block.Block; +import net.minecraft.registry.Registries; +import net.minecraft.util.math.BlockPos; +import wtf.hak.survivalfabric.veinminer.VeinMinerSession; + +import static wtf.hak.survivalfabric.config.ConfigManager.getConfig; + +public class WoodDrill extends DrillBase { + + public WoodDrill(VeinMinerSession session) { + super(session); + } + + public static final TagKey woodTag = TagKey.of(RegistryKeys.BLOCK, Identifier.of("survivalfabric", "wood")); + + @Override + public boolean canHandle(BlockState blockState) { + return blockState.isIn(woodTag); + } + + @Override + public boolean drill(BlockPos startPos) { + ServerWorld world = session.world; + int broken = 0; + ArrayDeque pendingLogs = new ArrayDeque(); + ArrayDeque logBlocks = new ArrayDeque(); + pendingLogs.add(startPos); + + String leavesBlockId = Registries.BLOCK.getId(world.getBlockState(startPos).getBlock()).toString().replace("_log", "_leaves"); + + while (!pendingLogs.isEmpty() && broken < getConfig().maxVeinSize) { + BlockPos woodPos = pendingLogs.remove(); + Block woodBlock = world.getBlockState(woodPos).getBlock(); + + if (tryBreakBlock(woodPos)) { + logBlocks.add(woodPos); + broken += 1; + + // look around current block + forXYZ(woodPos, 1, newPos -> { + Block newBlock = world.getBlockState(newPos).getBlock(); + if (newBlock == woodBlock && !pendingLogs.contains(newPos)) { + pendingLogs.add(newPos); + } + }, true); + } + } + + // second round, leaves + // The pending blocks are all air now, + ArrayDeque pendingLeaves = logBlocks; + while (!pendingLeaves.isEmpty() && broken < getConfig().maxVeinSize) { + // remove the immediately surrounding leaves around the log blocks + broken += forXYZ(pendingLeaves.remove(), 1, newPos -> { + int brokenLeaves = 0; + Block newBlock = world.getBlockState(newPos).getBlock(); + String newBlockId = Registries.BLOCK.getId(newBlock).toString(); + if (newBlockId.equals(leavesBlockId)) { + if (tryBreakBlock(newPos)) { + brokenLeaves += 1; + } + } + return brokenLeaves; + }, true); + } + + return true; + } + + private boolean isLeaf(Block block) { + return Registries.BLOCK.getId(block).toString().endsWith("_leaves"); + } + +} \ No newline at end of file diff --git a/src/main/resources/data/survivalfabric/tags/block/leaves.json b/src/main/resources/data/survivalfabric/tags/block/leaves.json new file mode 100644 index 0000000..a93e89d --- /dev/null +++ b/src/main/resources/data/survivalfabric/tags/block/leaves.json @@ -0,0 +1,5 @@ +{ + "values": [ + "#minecraft:leaves" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/survivalfabric/tags/block/ore.json b/src/main/resources/data/survivalfabric/tags/block/ore.json new file mode 100644 index 0000000..c64a298 --- /dev/null +++ b/src/main/resources/data/survivalfabric/tags/block/ore.json @@ -0,0 +1,14 @@ +{ + "values": [ + "#minecraft:iron_ores", + "#minecraft:gold_ores", + "#minecraft:lapis_ores", + "#minecraft:redstone_ores", + "#minecraft:diamond_ores", + "#minecraft:emerald_ores", + "#minecraft:coal_ores", + "#minecraft:copper_ores", + "minecraft:ancient_debris", + "minecraft:magma_block" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/survivalfabric/tags/block/wood.json b/src/main/resources/data/survivalfabric/tags/block/wood.json new file mode 100644 index 0000000..ff878bd --- /dev/null +++ b/src/main/resources/data/survivalfabric/tags/block/wood.json @@ -0,0 +1,5 @@ +{ + "values": [ + "#minecraft:logs" + ] +} \ No newline at end of file