diff --git a/motherlodeminer/motherlodeminer.gradle.kts b/motherlodeminer/motherlodeminer.gradle.kts new file mode 100644 index 0000000..9db745c --- /dev/null +++ b/motherlodeminer/motherlodeminer.gradle.kts @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019 Owain van Brakel + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +version = "1.0.0" + +project.extra["PluginName"] = "Chaos Motherlode Miner" // This is the name that is used in the external plugin manager panel +project.extra["PluginDescription"] = "Diggy, diggy hole" // This is the description that is used in the external plugin manager panel + +dependencies { + compileOnly(project(":util")) +} + +tasks { + jar { + manifest { + attributes(mapOf( + "Plugin-Version" to project.version, + "Plugin-Id" to nameToId(project.extra["PluginName"] as String), + "Plugin-Provider" to project.extra["PluginProvider"], + "Plugin-Description" to project.extra["PluginDescription"], + "Plugin-Dependencies" to + arrayOf( + nameToId("Chaos Util"), + nameToId("iUtils") + ).joinToString(), + "Plugin-License" to project.extra["PluginLicense"] + )) + } + } +} \ No newline at end of file diff --git a/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/Config.java b/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/Config.java new file mode 100644 index 0000000..a73b1e6 --- /dev/null +++ b/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/Config.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018, Andrew EP | ElPinche256 + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package io.reisub.openosrs.motherlodeminer; + +import net.runelite.client.config.Button; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("chaosmotherlodeminer") + +public interface Config extends net.runelite.client.config.Config { + @ConfigItem( + keyName = "startButton", + name = "Start/Stop", + description = "Start the script", + position = 100 + ) + default Button startButton() { + return new Button(); + } +} \ No newline at end of file diff --git a/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/MotherlodeMiner.java b/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/MotherlodeMiner.java new file mode 100644 index 0000000..5aabb5f --- /dev/null +++ b/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/MotherlodeMiner.java @@ -0,0 +1,232 @@ +package io.reisub.openosrs.motherlodeminer; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.Provides; + +import io.reisub.openosrs.motherlodeminer.tasks.*; +import io.reisub.openosrs.util.CScript; +import io.reisub.openosrs.util.Task; +import io.reisub.openosrs.util.Util; +import io.reisub.openosrs.util.enums.Activity; +import io.reisub.openosrs.util.tasks.Eat; +import io.reisub.openosrs.util.tasks.KittenTask; +import io.reisub.openosrs.util.tasks.Run; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.*; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.events.*; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.PluginDependency; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.iutils.iUtils; +import net.runelite.client.plugins.iutils.scene.Position; +import net.runelite.client.plugins.iutils.scripts.iScript; +import org.pf4j.Extension; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +@Extension +@PluginDependency(Util.class) +@PluginDependency(iUtils.class) +@PluginDescriptor( + name = "Chaos Motherlode Miner", + description = "Diggy, diggy hole", + enabledByDefault = false +) +@Slf4j +public class MotherlodeMiner extends CScript { + @Provides + Config provideConfig(ConfigManager configManager) { + return configManager.getConfig(Config.class); + } + + private static final Set MOTHERLODE_MAP_REGIONS = ImmutableSet.of(14679, 14680, 14681, 14935, 14936, 14937, 15191, 15192, 15193); + private static final int UPPER_FLOOR_HEIGHT = -490; + private static final int SACK_LARGE_SIZE = 162; + private static final int SACK_SIZE = 81; + + private int curSackSize, maxSackSize; + + @Getter + @Setter + private boolean sackFull; + + @Getter + private boolean inMlm; + + @Getter + @Setter + private Position currentVeinPosition; + + private ScheduledExecutorService executor; + + @Override + protected void loop() { + game.tick(); + } + + @Override + protected void onStart() { + super.onStart(); + + inMlm = checkInMlm(); + + executor = Executors.newSingleThreadScheduledExecutor(); + addTask(Mine.class); + addTask(GoDown.class); + addTask(Deposit.class); + addTask(FixWheel.class); + addTask(WithdrawSack.class); + addTask(HandleBank.class); + addTask(GoUp.class); + } + + @Override + protected void onStop() { + executor.shutdownNow(); + } + + @Subscribe + private void onGameTick(GameTick event) { + if (currentVeinPosition != null && currentActivity == Activity.MINING) { + if (game.objects().withPosition(currentVeinPosition).withAction("Mine").first() == null) { + currentVeinPosition = null; + setActivity(Activity.IDLE); + } + } + + if (executor != null) { + executor.schedule(() -> { + for (Task t : tasks) { + if (t.validate()) { + log.info(t.getStatus()); + t.execute(); + break; + } + } + + checkActionTimeout(); + }, 0, TimeUnit.MILLISECONDS); + } + } + + @Subscribe + private void onAnimationChanged(AnimationChanged event) { + Actor actor = event.getActor(); + if (actor == null || actor.getName() == null || !actor.getName().equals(game.localPlayer().name())) return; + + int animId = game.localPlayer().animation(); + switch (animId) { + case AnimationID.MINING_MOTHERLODE_BRONZE: + case AnimationID.MINING_MOTHERLODE_IRON: + case AnimationID.MINING_MOTHERLODE_STEEL: + case AnimationID.MINING_MOTHERLODE_BLACK: + case AnimationID.MINING_MOTHERLODE_MITHRIL: + case AnimationID.MINING_MOTHERLODE_ADAMANT: + case AnimationID.MINING_MOTHERLODE_RUNE: + case AnimationID.MINING_MOTHERLODE_DRAGON: + case AnimationID.MINING_MOTHERLODE_DRAGON_OR: + case AnimationID.MINING_MOTHERLODE_DRAGON_UPGRADED: + case AnimationID.MINING_MOTHERLODE_CRYSTAL: + case AnimationID.MINING_MOTHERLODE_GILDED: + case AnimationID.MINING_MOTHERLODE_INFERNAL: + case AnimationID.MINING_MOTHERLODE_3A: + setActivity(Activity.MINING); + break; + } + } + + @Subscribe + private void onGameObjectDespawned(GameObjectDespawned event) { + if (currentActivity == Activity.REPAIRING && event.getGameObject().getName().equals("Broken strut")) { + setActivity(Activity.IDLE); + } + } + + @Subscribe + private void onItemContainerChanged(ItemContainerChanged event) { + if (currentActivity == Activity.DEPOSITING) { + if (!game.inventory().withName("Pay-dirt").exists()) { + setActivity(Activity.IDLE); + } + } else if (currentActivity == Activity.WITHDRAWING) { + if (game.inventory().withId( + ItemID.RUNITE_ORE, + ItemID.ADAMANTITE_ORE, + ItemID.MITHRIL_ORE, + ItemID.GOLD_ORE, + ItemID.COAL, + ItemID.UNCUT_SAPPHIRE, + ItemID.UNCUT_EMERALD, + ItemID.UNCUT_RUBY, + ItemID.UNCUT_DIAMOND, + ItemID.UNCUT_DRAGONSTONE + ).exists()) { + setActivity(Activity.IDLE); + } + } else if (currentActivity == Activity.MINING) { + if (game.inventory().full()) { + setActivity(Activity.IDLE); + currentVeinPosition = null; + } + } + } + + @Subscribe + private void onVarbitChanged(VarbitChanged event) { + if (inMlm) { + refreshSackValues(); + if (curSackSize >= maxSackSize - 26) { + sackFull = true; + } + } + } + + @Subscribe + private void onGameStateChanged(GameStateChanged event) { + if (event.getGameState() == GameState.LOADING) { + inMlm = checkInMlm(); + } else if (event.getGameState() == GameState.LOGIN_SCREEN) { + inMlm = false; + } + } + + private boolean checkInMlm() { + GameState state = game.client.getGameState(); + if (state != GameState.LOGGED_IN && state != GameState.LOADING) { + return false; + } + + int[] currentMapRegions = game.client.getMapRegions(); + + for (int region : currentMapRegions) { + if (!MOTHERLODE_MAP_REGIONS.contains(region)) { + return false; + } + } + + return true; + } + + private void refreshSackValues() { + curSackSize = game.client.getVar(Varbits.SACK_NUMBER); + boolean sackUpgraded = game.client.getVar(Varbits.SACK_UPGRADED) == 1; + maxSackSize = sackUpgraded ? SACK_LARGE_SIZE : SACK_SIZE; + + if (curSackSize == 0) { + sackFull = false; + } + } + + public boolean isUpstairs() { + return Perspective.getTileHeight(game.client, game.client.getLocalPlayer().getLocalLocation(), 0) < UPPER_FLOOR_HEIGHT; + } +} \ No newline at end of file diff --git a/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/tasks/Deposit.java b/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/tasks/Deposit.java new file mode 100644 index 0000000..68ca4d0 --- /dev/null +++ b/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/tasks/Deposit.java @@ -0,0 +1,44 @@ +package io.reisub.openosrs.motherlodeminer.tasks; + +import io.reisub.openosrs.motherlodeminer.MotherlodeMiner; +import io.reisub.openosrs.util.Task; +import io.reisub.openosrs.util.enums.Activity; +import net.runelite.client.plugins.iutils.game.iObject; +import net.runelite.client.plugins.iutils.scene.Position; +import net.runelite.client.plugins.iutils.scene.RectangularArea; + +import javax.inject.Inject; + +public class Deposit extends Task { + @Inject + private MotherlodeMiner plugin; + + private final RectangularArea hopperArea = new RectangularArea( + new Position(3741, 5657, 0), + new Position(3755, 5674, 0) + ); + + @Override + public String getStatus() { + return "Depositing in hopper"; + } + + @Override + public boolean validate() { + return plugin.getCurrentActivity() == Activity.IDLE + && !plugin.isUpstairs() + && game.inventory().withName("Pay-dirt").exists(); + } + + @Override + public void execute() { + iObject hopper = game.objects().withName("Hopper").nearest(); + if (hopper == null) return; + + plugin.setActivity(Activity.DEPOSITING); + + hopper.interact("Deposit"); + game.waitUntil(() -> plugin.getCurrentActivity() == Activity.IDLE, 15); + game.tick(2); + } +} diff --git a/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/tasks/FixWheel.java b/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/tasks/FixWheel.java new file mode 100644 index 0000000..d116c36 --- /dev/null +++ b/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/tasks/FixWheel.java @@ -0,0 +1,39 @@ +package io.reisub.openosrs.motherlodeminer.tasks; + +import io.reisub.openosrs.motherlodeminer.MotherlodeMiner; +import io.reisub.openosrs.util.Task; +import io.reisub.openosrs.util.enums.Activity; +import net.runelite.client.plugins.iutils.game.iObject; + +import javax.inject.Inject; +import java.util.List; + + +public class FixWheel extends Task { + @Inject + private MotherlodeMiner plugin; + + private List brokenStruts; + + @Override + public String getStatus() { + return "Fixing wheel"; + } + + @Override + public boolean validate() { + if (plugin.getCurrentActivity() != Activity.IDLE || plugin.isUpstairs()) return false; + + brokenStruts = game.objects().withName("Broken strut").all(); + + return brokenStruts.size() == 2; + } + + @Override + public void execute() { + plugin.setActivity(Activity.REPAIRING); + + brokenStruts.get(0).interact("Hammer"); + game.waitUntil(() -> plugin.getCurrentActivity() == Activity.IDLE, 30); + } +} diff --git a/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/tasks/GoDown.java b/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/tasks/GoDown.java new file mode 100644 index 0000000..7a77a06 --- /dev/null +++ b/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/tasks/GoDown.java @@ -0,0 +1,42 @@ +package io.reisub.openosrs.motherlodeminer.tasks; + +import io.reisub.openosrs.motherlodeminer.MotherlodeMiner; +import io.reisub.openosrs.util.Task; +import io.reisub.openosrs.util.enums.Activity; +import net.runelite.client.plugins.iutils.game.iObject; +import net.runelite.client.plugins.iutils.scene.Position; +import net.runelite.client.plugins.iutils.scene.RectangularArea; + +import javax.inject.Inject; + +public class GoDown extends Task { + @Inject + private MotherlodeMiner plugin; + + private final RectangularArea miningArea = new RectangularArea( + new Position(3748, 5676, 0), + new Position(3754, 5684, 0) + ); + + @Override + public String getStatus() { + return "Going down"; + } + + @Override + public boolean validate() { + return plugin.getCurrentActivity() == Activity.IDLE + && plugin.isUpstairs() + && game.inventory().full(); + } + + @Override + public void execute() { + iObject ladder = game.objects().withName("Ladder").nearest(); + if (ladder == null) return; + + ladder.interact("Climb"); + game.waitUntil(() -> !plugin.isUpstairs(), 15); + game.tick(); + } +} diff --git a/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/tasks/GoUp.java b/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/tasks/GoUp.java new file mode 100644 index 0000000..b89b84f --- /dev/null +++ b/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/tasks/GoUp.java @@ -0,0 +1,32 @@ +package io.reisub.openosrs.motherlodeminer.tasks; + +import io.reisub.openosrs.motherlodeminer.MotherlodeMiner; +import io.reisub.openosrs.util.Task; +import net.runelite.client.plugins.iutils.game.iObject; + +import javax.inject.Inject; + +public class GoUp extends Task { + @Inject + private MotherlodeMiner plugin; + + @Override + public String getStatus() { + return "Going up"; + } + + @Override + public boolean validate() { + return !plugin.isUpstairs(); + } + + @Override + public void execute() { + iObject ladder = game.objects().withName("Ladder").withAction("Climb").nearest(); + if (ladder == null) return; + + ladder.interact("Climb"); + game.waitUntil(() -> plugin.isUpstairs(), 15); + game.tick(); + } +} diff --git a/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/tasks/HandleBank.java b/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/tasks/HandleBank.java new file mode 100644 index 0000000..4f3477a --- /dev/null +++ b/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/tasks/HandleBank.java @@ -0,0 +1,45 @@ +package io.reisub.openosrs.motherlodeminer.tasks; + +import io.reisub.openosrs.motherlodeminer.MotherlodeMiner; +import io.reisub.openosrs.util.enums.Activity; +import io.reisub.openosrs.util.tasks.BankTask; +import net.runelite.api.ItemID; + +import javax.inject.Inject; +import java.time.Duration; +import java.time.Instant; + +public class HandleBank extends BankTask { + @Inject + private MotherlodeMiner plugin; + + @Override + public boolean validate() { + if (plugin.isUpstairs() || plugin.getCurrentActivity() != Activity.IDLE) return false; + + return isLastBankDurationAgo(Duration.ofSeconds(2)) + && game.inventory().withId( + ItemID.RUNITE_ORE, + ItemID.ADAMANTITE_ORE, + ItemID.MITHRIL_ORE, + ItemID.GOLD_ORE, + ItemID.COAL, + ItemID.UNCUT_SAPPHIRE, + ItemID.UNCUT_EMERALD, + ItemID.UNCUT_RUBY, + ItemID.UNCUT_DIAMOND, + ItemID.UNCUT_DRAGONSTONE + ).exists(); + } + + @Override + public void execute() { + openBank(); + + bank.depositExcept(false, ItemID.HAMMER, ItemID.GOLDEN_NUGGET); + bank.close(); + game.waitUntil(() -> !bank.isOpen(), 3); + + last = Instant.now(); + } +} diff --git a/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/tasks/Mine.java b/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/tasks/Mine.java new file mode 100644 index 0000000..c2904a8 --- /dev/null +++ b/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/tasks/Mine.java @@ -0,0 +1,52 @@ +package io.reisub.openosrs.motherlodeminer.tasks; + +import io.reisub.openosrs.motherlodeminer.MotherlodeMiner; +import io.reisub.openosrs.util.Task; +import io.reisub.openosrs.util.enums.Activity; +import net.runelite.client.plugins.iutils.game.iObject; +import net.runelite.client.plugins.iutils.scene.Position; +import net.runelite.client.plugins.iutils.scene.RectangularArea; + +import javax.inject.Inject; + +public class Mine extends Task { + @Inject + private MotherlodeMiner plugin; + + private final RectangularArea miningArea = new RectangularArea( + new Position(3748, 5676, 0), + new Position(3754, 5684, 0) + ); + + private iObject oreVein; + + @Override + public String getStatus() { + return "Mining"; + } + + @Override + public boolean validate() { + if (plugin.getCurrentActivity() != Activity.IDLE) return false; + + if (!plugin.isUpstairs()) return false; + + oreVein = game.objects() + .withName("Ore vein") + .withAction("Mine") + .filter((obj) -> miningArea.contains(obj.position()) && !obj.position().equals(new Position(3764, 5665, 0))).nearest(); + + return plugin.getCurrentActivity() == Activity.IDLE + && !game.inventory().full() + && oreVein != null; + } + + @Override + public void execute() { + plugin.setCurrentVeinPosition(oreVein.position()); + + oreVein.interact("Mine"); + game.waitUntil(() -> plugin.getCurrentActivity() == Activity.MINING, 15); + + } +} diff --git a/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/tasks/WithdrawSack.java b/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/tasks/WithdrawSack.java new file mode 100644 index 0000000..4c32e80 --- /dev/null +++ b/motherlodeminer/src/main/java/io/reisub/openosrs/motherlodeminer/tasks/WithdrawSack.java @@ -0,0 +1,50 @@ +package io.reisub.openosrs.motherlodeminer.tasks; + +import io.reisub.openosrs.motherlodeminer.MotherlodeMiner; +import io.reisub.openosrs.util.Task; +import io.reisub.openosrs.util.enums.Activity; +import net.runelite.api.ItemID; +import net.runelite.client.plugins.iutils.game.iObject; + +import javax.inject.Inject; + +public class WithdrawSack extends Task { + @Inject + private MotherlodeMiner plugin; + + @Override + public String getStatus() { + return "Withdrawing from sack"; + } + + @Override + public boolean validate() { + return !plugin.isUpstairs() + && plugin.getCurrentActivity() == Activity.IDLE + && plugin.isSackFull() + && !game.inventory().withId( + ItemID.RUNITE_ORE, + ItemID.ADAMANTITE_ORE, + ItemID.MITHRIL_ORE, + ItemID.GOLD_ORE, + ItemID.COAL, + ItemID.UNCUT_SAPPHIRE, + ItemID.UNCUT_EMERALD, + ItemID.UNCUT_RUBY, + ItemID.UNCUT_DIAMOND, + ItemID.UNCUT_DRAGONSTONE + ).exists(); + } + + @Override + public void execute() { + iObject sack = game.objects().withName("Sack").withAction("Search").nearest(); + if (sack == null) return; + + plugin.setActivity(Activity.WITHDRAWING); + + sack.interact("Search"); + + game.waitUntil(() -> plugin.getCurrentActivity() == Activity.IDLE, 30); + } +} diff --git a/release/motherlodeminer-1.0.0.jar b/release/motherlodeminer-1.0.0.jar new file mode 100644 index 0000000..f410bd9 Binary files /dev/null and b/release/motherlodeminer-1.0.0.jar differ