diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3e39fa030..06c8429a2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,6 +25,7 @@
* Added Bone Blocks recipe to the Electric Press
* Added thai translations
* Dried Kelp Blocks can now be used in the Coal Generator
+* Added Industrial Miner
#### Changes
* Fixed a few memory leaks
diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/core/multiblocks/MultiBlock.java b/src/main/java/io/github/thebusybiscuit/slimefun4/core/multiblocks/MultiBlock.java
index d7541c9a3..5a360cd17 100644
--- a/src/main/java/io/github/thebusybiscuit/slimefun4/core/multiblocks/MultiBlock.java
+++ b/src/main/java/io/github/thebusybiscuit/slimefun4/core/multiblocks/MultiBlock.java
@@ -122,6 +122,16 @@ public class MultiBlock {
}
}
+ // This ensures that the Industrial Miner is still recognized while operating
+ if (a == Material.PISTON) {
+ return a == b || b == Material.MOVING_PISTON;
+ }
+
+ // This ensures that the Industrial Miner is still recognized while operating
+ if (b == Material.PISTON) {
+ return a == b || a == Material.MOVING_PISTON;
+ }
+
if (b != a) {
return false;
}
diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/IndustrialMiner.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/IndustrialMiner.java
new file mode 100644
index 000000000..0da967977
--- /dev/null
+++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/IndustrialMiner.java
@@ -0,0 +1,198 @@
+package io.github.thebusybiscuit.slimefun4.implementation.items.multiblocks;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
+
+import org.apache.commons.lang.Validate;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.Tag;
+import org.bukkit.block.Block;
+import org.bukkit.block.BlockFace;
+import org.bukkit.enchantments.Enchantment;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import io.github.thebusybiscuit.cscorelib2.chat.ChatColors;
+import io.github.thebusybiscuit.cscorelib2.item.CustomItem;
+import io.github.thebusybiscuit.slimefun4.api.MinecraftVersion;
+import me.mrCookieSlime.Slimefun.SlimefunPlugin;
+import me.mrCookieSlime.Slimefun.Objects.Category;
+import me.mrCookieSlime.Slimefun.Objects.SlimefunItem.abstractItems.MachineFuel;
+import me.mrCookieSlime.Slimefun.Objects.SlimefunItem.multiblocks.MultiBlockMachine;
+import me.mrCookieSlime.Slimefun.api.SlimefunItemStack;
+
+/**
+ * The {@link IndustrialMiner} is a {@link MultiBlockMachine} that can mine any
+ * ores it finds in a given range underneath where it was placed.
+ *
+ * And for those of you who are wondering... yes this is the replacement for the
+ * long-time deprecated Digital Miner.
+ *
+ * @author TheBusyBiscuit
+ *
+ */
+public class IndustrialMiner extends MultiBlockMachine {
+
+ protected final Map activeMiners = new HashMap<>();
+ protected final List fuelTypes = new ArrayList<>();
+
+ private final int range;
+ private final boolean silkTouch;
+
+ public IndustrialMiner(Category category, SlimefunItemStack item, Material baseMaterial, boolean silkTouch, int range) {
+ super(category, item, new ItemStack[] { null, null, null, new CustomItem(Material.PISTON, "Piston (facing up)"), new ItemStack(Material.CHEST), new CustomItem(Material.PISTON, "Piston (facing up)"), new ItemStack(baseMaterial), new ItemStack(SlimefunPlugin.getMinecraftVersion().isAtLeast(MinecraftVersion.MINECRAFT_1_14) ? Material.BLAST_FURNACE : Material.FURNACE), new ItemStack(baseMaterial) }, new ItemStack[0], BlockFace.UP);
+
+ this.range = range;
+ this.silkTouch = silkTouch;
+
+ registerDefaultFuelTypes();
+ }
+
+ /**
+ * This returns whether this {@link IndustrialMiner} will output ores as they are.
+ * Similar to the Silk Touch {@link Enchantment}.
+ *
+ * @return Whether to treat ores with Silk Touch
+ */
+ public boolean hasSilkTouch() {
+ return silkTouch;
+ }
+
+ /**
+ * This method returns the range of the {@link IndustrialMiner}.
+ * The total area will be determined by the range multiplied by 2 plus the actual center
+ * of the machine.
+ *
+ * So a range of 3 will make the {@link IndustrialMiner} affect an area of 7x7 blocks.
+ * 3 on all axis, plus the center of the machine itself.
+ *
+ * @return The range of this {@link IndustrialMiner}
+ */
+ public int getRange() {
+ return range;
+ }
+
+ /**
+ * This registers the various types of fuel that can be used in the
+ * {@link IndustrialMiner}.
+ */
+ protected void registerDefaultFuelTypes() {
+ // Coal & Charcoal
+ fuelTypes.add(new MachineFuel(4, new ItemStack(Material.COAL)));
+ fuelTypes.add(new MachineFuel(4, new ItemStack(Material.CHARCOAL)));
+
+ fuelTypes.add(new MachineFuel(40, new ItemStack(Material.COAL_BLOCK)));
+ fuelTypes.add(new MachineFuel(10, new ItemStack(Material.DRIED_KELP_BLOCK)));
+ fuelTypes.add(new MachineFuel(4, new ItemStack(Material.BLAZE_ROD)));
+
+ // Logs
+ for (Material mat : Tag.LOGS.getValues()) {
+ fuelTypes.add(new MachineFuel(1, new ItemStack(mat)));
+ }
+ }
+
+ /**
+ * This method returns the outcome that mining certain ores yields.
+ *
+ * @param ore
+ * The {@link Material} of the ore that was mined
+ *
+ * @return The outcome when mining this ore
+ */
+ public ItemStack getOutcome(Material ore) {
+ if (hasSilkTouch()) {
+ return new ItemStack(ore);
+ }
+
+ Random random = ThreadLocalRandom.current();
+
+ switch (ore) {
+ case COAL_ORE:
+ return new ItemStack(Material.COAL);
+ case DIAMOND_ORE:
+ return new ItemStack(Material.DIAMOND);
+ case EMERALD_ORE:
+ return new ItemStack(Material.EMERALD);
+ case NETHER_QUARTZ_ORE:
+ return new ItemStack(Material.QUARTZ);
+ case REDSTONE_ORE:
+ return new ItemStack(Material.REDSTONE, 4 + random.nextInt(2));
+ case LAPIS_ORE:
+ return new ItemStack(Material.LAPIS_LAZULI, 4 + random.nextInt(4));
+ default:
+ // This includes Iron and Gold ore
+ return new ItemStack(ore);
+ }
+ }
+
+ /**
+ * This registers a new fuel type for this {@link IndustrialMiner}.
+ *
+ * @param ores
+ * The amount of ores this allows you to mine
+ * @param item
+ * The item that shall be consumed
+ */
+ public void addFuelType(int ores, ItemStack item) {
+ Validate.isTrue(ores > 1 && ores % 2 == 0, "The amount of ores must be at least 2 and a multiple of 2.");
+ fuelTypes.add(new MachineFuel(ores / 2, item));
+ }
+
+ @Override
+ public String getLabelLocalPath() {
+ return "guide.tooltips.recipes.generator";
+ }
+
+ @Override
+ public List getDisplayRecipes() {
+ List list = new ArrayList<>();
+
+ for (MachineFuel fuel : fuelTypes) {
+ ItemStack item = fuel.getInput().clone();
+ ItemMeta im = item.getItemMeta();
+ List lore = new ArrayList<>();
+ lore.add(ChatColors.color("&8\u21E8 &7Lasts for max. " + fuel.getTicks() + " Ores"));
+ im.setLore(lore);
+ item.setItemMeta(im);
+ list.add(item);
+ }
+
+ return list;
+ }
+
+ @Override
+ public void onInteract(Player p, Block b) {
+ if (activeMiners.containsKey(b.getLocation())) {
+ SlimefunPlugin.getLocal().sendMessage(p, "machines.INDUSTRIAL_MINER.already-running");
+ return;
+ }
+
+ Block chest = b.getRelative(BlockFace.UP);
+ Block[] pistons = findPistons(chest);
+
+ int mod = getRange();
+ Location start = b.getLocation().clone().add(-mod, -1, -mod);
+ Location end = b.getLocation().clone().add(mod, -1, mod);
+
+ IndustrialMinerInstance instance = new IndustrialMinerInstance(this, p.getUniqueId(), chest, pistons, start, end);
+ instance.start(b);
+ }
+
+ private Block[] findPistons(Block chest) {
+ Block northern = chest.getRelative(BlockFace.NORTH);
+
+ if (northern.getType() == Material.PISTON) {
+ return new Block[] { northern, chest.getRelative(BlockFace.SOUTH) };
+ }
+ else {
+ return new Block[] { chest.getRelative(BlockFace.WEST), chest.getRelative(BlockFace.EAST) };
+ }
+ }
+
+}
diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/IndustrialMinerInstance.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/IndustrialMinerInstance.java
new file mode 100644
index 000000000..6b5a3f556
--- /dev/null
+++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/IndustrialMinerInstance.java
@@ -0,0 +1,333 @@
+package io.github.thebusybiscuit.slimefun4.implementation.items.multiblocks;
+
+import java.util.UUID;
+import java.util.logging.Level;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Effect;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.Particle;
+import org.bukkit.Sound;
+import org.bukkit.block.Block;
+import org.bukkit.block.BlockFace;
+import org.bukkit.block.Chest;
+import org.bukkit.block.data.type.Piston;
+import org.bukkit.block.data.type.PistonHead;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+
+import io.github.thebusybiscuit.cscorelib2.inventory.InvUtils;
+import io.github.thebusybiscuit.cscorelib2.inventory.ItemUtils;
+import io.github.thebusybiscuit.cscorelib2.protection.ProtectableAction;
+import io.github.thebusybiscuit.cscorelib2.scheduling.TaskQueue;
+import me.mrCookieSlime.Slimefun.SlimefunPlugin;
+import me.mrCookieSlime.Slimefun.Objects.SlimefunItem.abstractItems.MachineFuel;
+import me.mrCookieSlime.Slimefun.api.Slimefun;
+
+class IndustrialMinerInstance implements Runnable {
+
+ private final IndustrialMiner miner;
+ private final UUID owner;
+
+ private int fuel = 0;
+ private int ores = 0;
+ private boolean running = false;
+
+ private final Block chest;
+ private final Block[] pistons;
+
+ private final Location start;
+ private final Location end;
+ private final int height;
+
+ private int x;
+ private int z;
+
+ public IndustrialMinerInstance(IndustrialMiner miner, UUID owner, Block chest, Block[] pistons, Location start, Location end) {
+ this.miner = miner;
+ this.owner = owner;
+
+ this.chest = chest;
+ this.pistons = pistons;
+
+ this.start = start;
+ this.end = end;
+
+ this.height = start.getBlockY();
+ this.x = start.getBlockX();
+ this.z = start.getBlockZ();
+ }
+
+ /**
+ * This starts the {@link IndustrialMiner} at the given {@link Block}.
+ *
+ * @param b
+ * The {@link Block} which marks the center of this {@link IndustrialMiner}
+ */
+ public void start(Block b) {
+ miner.activeMiners.put(b.getLocation(), this);
+ running = true;
+
+ warmUp();
+ }
+
+ /**
+ * This method stops the {@link IndustrialMiner}.
+ */
+ public void stop() {
+ running = false;
+ miner.activeMiners.remove(chest.getRelative(BlockFace.DOWN).getLocation());
+ }
+
+ /**
+ * This method stops the {@link IndustrialMiner} with an error message.
+ * The error message is a path to the location in Slimefun's localization files.
+ *
+ * @param error
+ * The error message to send
+ */
+ public void stop(String error) {
+ Player p = Bukkit.getPlayer(owner);
+
+ if (p != null) {
+ SlimefunPlugin.getLocal().sendMessage(p, error);
+ }
+
+ stop();
+ }
+
+ /**
+ * This method starts the warm-up animation for the {@link IndustrialMiner}.
+ */
+ private void warmUp() {
+ fuel = consumeFuel();
+
+ if (fuel <= 0) {
+ // This Miner has not enough fuel.
+ stop("machines.INDUSTRIAL_MINER.no-fuel");
+ return;
+ }
+
+ TaskQueue queue = new TaskQueue();
+
+ queue.thenRun(4, () -> setPistonState(pistons[0], true));
+ queue.thenRun(10, () -> setPistonState(pistons[0], false));
+
+ queue.thenRun(8, () -> setPistonState(pistons[1], true));
+ queue.thenRun(10, () -> setPistonState(pistons[1], false));
+
+ queue.thenRun(6, () -> setPistonState(pistons[0], true));
+ queue.thenRun(9, () -> setPistonState(pistons[0], false));
+
+ queue.thenRun(4, () -> setPistonState(pistons[1], true));
+ queue.thenRun(7, () -> setPistonState(pistons[1], false));
+
+ queue.thenRun(3, () -> setPistonState(pistons[0], true));
+ queue.thenRun(4, () -> setPistonState(pistons[0], false));
+
+ queue.thenRun(2, () -> setPistonState(pistons[1], true));
+ queue.thenRun(3, () -> setPistonState(pistons[1], false));
+
+ queue.thenRun(1, () -> setPistonState(pistons[0], true));
+ queue.thenRun(3, () -> setPistonState(pistons[0], false));
+
+ queue.thenRun(1, () -> setPistonState(pistons[1], true));
+ queue.thenRun(3, () -> setPistonState(pistons[1], false));
+
+ queue.thenRun(1, this);
+ queue.execute(SlimefunPlugin.instance);
+ }
+
+ @Override
+ public void run() {
+ if (!running) {
+ return;
+ }
+
+ try {
+ TaskQueue queue = new TaskQueue();
+
+ queue.thenRun(1, () -> setPistonState(pistons[0], true));
+ queue.thenRun(3, () -> setPistonState(pistons[0], false));
+
+ queue.thenRun(1, () -> setPistonState(pistons[1], true));
+ queue.thenRun(3, () -> setPistonState(pistons[1], false));
+
+ queue.thenRun(() -> {
+ Block furnace = chest.getRelative(BlockFace.DOWN);
+ furnace.getWorld().playEffect(furnace.getLocation(), Effect.STEP_SOUND, Material.STONE);
+
+ for (int y = height; y > 0; y--) {
+ Block b = start.getWorld().getBlockAt(x, y, z);
+
+ if (!SlimefunPlugin.getProtectionManager().hasPermission(Bukkit.getOfflinePlayer(owner), b, ProtectableAction.BREAK_BLOCK)) {
+ stop("machines.INDUSTRIAL_MINER.no-permission");
+ return;
+ }
+
+ if (b.getType().name().endsWith("_ORE") && push(miner.getOutcome(b.getType()))) {
+ furnace.getWorld().playEffect(furnace.getLocation(), Effect.STEP_SOUND, b.getType());
+ furnace.getWorld().playSound(furnace.getLocation(), Sound.ENTITY_ARROW_HIT_PLAYER, 0.2F, 1F);
+
+ b.setType(Material.AIR);
+ fuel--;
+ ores++;
+
+ // Repeat the same column when we hit an ore.
+ Slimefun.runSync(this, 5);
+ return;
+ }
+ }
+
+ nextColumn();
+ });
+
+ queue.execute(SlimefunPlugin.instance);
+ }
+ catch (Exception e) {
+ Slimefun.getLogger().log(Level.SEVERE, "An Error occured while running an Industrial Miner", e);
+ stop();
+ }
+ }
+
+ /**
+ * This advanced the {@link IndustrialMiner} to the next column
+ */
+ private void nextColumn() {
+ if (x < end.getBlockX()) {
+ x++;
+ }
+ else if (z < end.getBlockZ()) {
+ x = start.getBlockX();
+ z++;
+ }
+ else {
+ stop();
+
+ Player p = Bukkit.getPlayer(owner);
+
+ if (p != null) {
+ p.playSound(p.getLocation(), Sound.ENTITY_ARROW_HIT_PLAYER, 0.4F, 1F);
+ SlimefunPlugin.getLocal().sendMessage(p, "machines.INDUSTRIAL_MINER.finished", msg -> msg.replace("%ores%", String.valueOf(ores)));
+ }
+
+ return;
+ }
+
+ Slimefun.runSync(this, 5);
+ }
+
+ private boolean push(ItemStack outcome) {
+ if (fuel < 1) {
+ fuel = consumeFuel();
+ }
+
+ if (fuel > 0) {
+ if (chest.getType() == Material.CHEST) {
+ Inventory inv = ((Chest) chest.getState()).getBlockInventory();
+
+ if (InvUtils.fits(inv, outcome)) {
+ inv.addItem(outcome);
+ return true;
+ }
+ else {
+ stop("machines.INDUSTRIAL_MINER.chest-full");
+ }
+ }
+ else {
+ // The chest has been destroyed
+ stop("machines.INDUSTRIAL_MINER.destroyed");
+ }
+ }
+ else {
+ stop("machines.INDUSTRIAL_MINER.no-fuel");
+ }
+
+ return false;
+ }
+
+ /**
+ * This consumes fuel from the given {@link Chest}.
+ *
+ * @return The gained fuel value
+ */
+ private int consumeFuel() {
+ if (chest.getType() == Material.CHEST) {
+ Inventory inv = ((Chest) chest.getState()).getBlockInventory();
+
+ for (int i = 0; i < inv.getSize(); i++) {
+ for (MachineFuel fuelType : miner.fuelTypes) {
+ ItemStack item = inv.getContents()[i];
+
+ if (fuelType.test(item)) {
+ ItemUtils.consumeItem(item, false);
+ return fuelType.getTicks();
+ }
+ }
+ }
+ }
+
+ return 0;
+ }
+
+ private void setPistonState(Block block, boolean extended) {
+ if (!running) {
+ return;
+ }
+
+ try {
+ // Smoke Particles around the Chest for dramatic effect
+ Location particleLoc = chest.getLocation().clone().add(0, -1, 0);
+ block.getWorld().spawnParticle(Particle.SMOKE_NORMAL, particleLoc, 16, 1, 1, 1, 0);
+
+ if (block.getType() == Material.MOVING_PISTON) {
+ // Yeah it isn't really cool when this happens
+ block.getRelative(BlockFace.UP).setType(Material.AIR);
+ }
+ else if (block.getType() == Material.PISTON) {
+ Block above = block.getRelative(BlockFace.UP);
+
+ if (above.isEmpty() || above.getType() == Material.PISTON_HEAD) {
+ Piston piston = (Piston) block.getBlockData();
+
+ if (piston.getFacing() == BlockFace.UP) {
+ piston.setExtended(extended);
+ block.setBlockData(piston, false);
+
+ // Updating the Piston Head
+ if (extended) {
+ PistonHead head = (PistonHead) Material.PISTON_HEAD.createBlockData();
+ head.setFacing(BlockFace.UP);
+
+ block.getRelative(BlockFace.UP).setBlockData(head, false);
+ }
+ else {
+ block.getRelative(BlockFace.UP).setType(Material.AIR);
+ }
+
+ block.getWorld().playSound(block.getLocation(), extended ? Sound.BLOCK_PISTON_EXTEND : Sound.BLOCK_PISTON_CONTRACT, 0.2F, 1F);
+ }
+ else {
+ // The pistons must be facing upwards
+ stop("machines.INDUSTRIAL_MINER.piston-facing");
+ }
+ }
+ else {
+ // The pistons must be facing upwards
+ stop("machines.INDUSTRIAL_MINER.piston-space");
+ }
+ }
+ else {
+ // The piston has been destroyed
+ stop("machines.INDUSTRIAL_MINER.destroyed");
+ }
+ }
+ catch (Exception e) {
+ Slimefun.getLogger().log(Level.SEVERE, "An Error occured while moving a Piston for an Industrial Miner", e);
+ stop();
+ }
+ }
+
+}
diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/setup/SlimefunItemSetup.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/setup/SlimefunItemSetup.java
index cd010a72f..ec70c458a 100644
--- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/setup/SlimefunItemSetup.java
+++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/setup/SlimefunItemSetup.java
@@ -135,6 +135,7 @@ import io.github.thebusybiscuit.slimefun4.implementation.items.multiblocks.Autom
import io.github.thebusybiscuit.slimefun4.implementation.items.multiblocks.Compressor;
import io.github.thebusybiscuit.slimefun4.implementation.items.multiblocks.EnhancedCraftingTable;
import io.github.thebusybiscuit.slimefun4.implementation.items.multiblocks.GrindStone;
+import io.github.thebusybiscuit.slimefun4.implementation.items.multiblocks.IndustrialMiner;
import io.github.thebusybiscuit.slimefun4.implementation.items.multiblocks.Juicer;
import io.github.thebusybiscuit.slimefun4.implementation.items.multiblocks.MagicWorkbench;
import io.github.thebusybiscuit.slimefun4.implementation.items.multiblocks.MakeshiftSmeltery;
@@ -1021,6 +1022,8 @@ public final class SlimefunItemSetup {
.register(plugin);
new AutomatedPanningMachine(categories.basicMachines).register(plugin);
+
+ new IndustrialMiner(categories.basicMachines, SlimefunItems.INDUSTRIAL_MINER, Material.IRON_BLOCK, false, 3).register(plugin);
new SlimefunItem(categories.magicalArmor, SlimefunItems.BOOTS_OF_THE_STOMPER, RecipeType.ARMOR_FORGE,
new ItemStack[] {null, null, null, new ItemStack(Material.YELLOW_WOOL), null, new ItemStack(Material.YELLOW_WOOL), new ItemStack(Material.PISTON), null, new ItemStack(Material.PISTON)})
diff --git a/src/main/resources/languages/messages_en.yml b/src/main/resources/languages/messages_en.yml
index d13fdf0ee..397bb69bb 100644
--- a/src/main/resources/languages/messages_en.yml
+++ b/src/main/resources/languages/messages_en.yml
@@ -16,11 +16,11 @@ commands:
reset-target: '&cYour Knowledge has been reset'
backpack:
- description: Retrieve an existing backpack
- invalid-id: '&4The backpack id must be a non-negative number!'
- player-never-joined: '&4No player with that name has ever joined the server!'
- backpack-does-not-exist: '&4That backpack does not exist!'
- restored-backpack-given: '&bBackpack restored successfully! Added to your inventory!'
+ description: Retrieve a copy of an existing backpack
+ invalid-id: '&4The id must be a non-negative number!'
+ player-never-joined: '&4No player with that name could be found!'
+ backpack-does-not-exist: '&4The specified backpack does not exist!'
+ restored-backpack-given: '&aYour backpack has been restored and was added to your inventory!'
guide:
locked: 'LOCKED'
@@ -202,7 +202,17 @@ machines:
CARGO_NODES:
must-be-placed: '&4Must be placed onto a chest or machine!'
-
+
+ INDUSTRIAL_MINER:
+ no-fuel: '&cYour Industrial Miner ran out of fuel! Put your fuel into the chest above.'
+ piston-facing: '&cYour Industrial Miner requires pistons to face upwards!'
+ piston-space: '&cThe two pistons need to have an empty block above them!'
+ destroyed: '&cYour Industrial Miner seems to have been destroyed.'
+ already-running: '&cThis Industrial Miner is already running!'
+ full-chest: '&cThe Chest of your Industrial Miner is full!'
+ no-permission: '&4You do not seem to have permission to operate an Industrial Miner here!'
+ finished: '&eYour Industrial Miner has finished! It obtained a total of %ores% ore(s)!'
+
anvil:
not-working: '&4You cannot use Slimefun Items in an anvil!'
@@ -210,9 +220,6 @@ backpack:
already-open: '&cSorry, this Backpack is open somewhere else!'
no-stack: '&cYou cannot stack Backpacks'
-miner:
- no-ores: '&eSorry, I could not find any Ores nearby!'
-
workbench:
not-enhanced: '&4You cannot use Slimefun Items in a normal workbench'
diff --git a/src/main/resources/languages/researches_en.yml b/src/main/resources/languages/researches_en.yml
index 47c20ddcf..3160e811f 100644
--- a/src/main/resources/languages/researches_en.yml
+++ b/src/main/resources/languages/researches_en.yml
@@ -231,3 +231,5 @@ slimefun:
kelp_cookie: Tasty Kelp
makeshift_smeltery: Improvised Smeltery
tree_growth_accelerator: Faster Trees
+ industrial_miner: Industrial Mining
+ advanced_industrial_miner: Better Mining