From 6c02dfed05d21d80a90624c432f372cba7f35767 Mon Sep 17 00:00:00 2001 From: hannibal2 <24389977+hannibal002@users.noreply.github.com> Date: Sat, 13 Apr 2024 22:05:36 +0200 Subject: New Feature: Excavation Profit Tracker (#1432) Co-authored-by: Empa <42304516+ItsEmpa@users.noreply.github.com> Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com> --- src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt | 8 +- .../skyhanni/config/ConfigUpdaterMigrator.kt | 2 +- .../mining/ExcavatorProfitTrackerConfig.java | 40 ++++ .../features/mining/FossilExcavatorConfig.java | 24 +- .../mining/FossilExcavatorSolverConfig.java | 28 +++ .../config/storage/ProfileSpecificStorage.java | 6 +- .../events/mining/FossilExcavationEvent.kt | 5 + .../fossilexcavator/ExcavatorProfitTracker.kt | 212 ++++++++++++++++++ .../mining/fossilexcavator/FossilExcavator.kt | 243 --------------------- .../mining/fossilexcavator/FossilExcavatorAPI.kt | 107 +++++++++ .../fossilexcavator/FossilExcavatorSolver.kt | 146 ------------- .../mining/fossilexcavator/FossilMutation.kt | 21 -- .../features/mining/fossilexcavator/FossilShape.kt | 26 --- .../features/mining/fossilexcavator/FossilTile.kt | 8 - .../features/mining/fossilexcavator/FossilType.kt | 187 ---------------- .../fossilexcavator/solver/FossilMutation.kt | 21 ++ .../mining/fossilexcavator/solver/FossilShape.kt | 26 +++ .../mining/fossilexcavator/solver/FossilSolver.kt | 146 +++++++++++++ .../fossilexcavator/solver/FossilSolverDisplay.kt | 233 ++++++++++++++++++++ .../mining/fossilexcavator/solver/FossilTile.kt | 8 + .../mining/fossilexcavator/solver/FossilType.kt | 187 ++++++++++++++++ .../java/at/hannibal2/skyhanni/utils/ItemUtils.kt | 8 +- .../at/hannibal2/skyhanni/utils/UtilsPatterns.kt | 2 +- .../skyhanni/utils/tracker/SkyHanniItemTracker.kt | 2 +- 24 files changed, 1037 insertions(+), 659 deletions(-) create mode 100644 src/main/java/at/hannibal2/skyhanni/config/features/mining/ExcavatorProfitTrackerConfig.java create mode 100644 src/main/java/at/hannibal2/skyhanni/config/features/mining/FossilExcavatorSolverConfig.java create mode 100644 src/main/java/at/hannibal2/skyhanni/events/mining/FossilExcavationEvent.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/ExcavatorProfitTracker.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilExcavator.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilExcavatorAPI.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilExcavatorSolver.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilMutation.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilShape.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilTile.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilType.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilMutation.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilShape.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilSolver.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilSolverDisplay.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilTile.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilType.kt (limited to 'src') diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt index 170dfb524..248d5fb50 100644 --- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt +++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt @@ -271,7 +271,9 @@ import at.hannibal2.skyhanni.features.mining.crystalhollows.CrystalHollowsNamesI import at.hannibal2.skyhanni.features.mining.crystalhollows.CrystalHollowsWalls import at.hannibal2.skyhanni.features.mining.eventtracker.MiningEventDisplay import at.hannibal2.skyhanni.features.mining.eventtracker.MiningEventTracker -import at.hannibal2.skyhanni.features.mining.fossilexcavator.FossilExcavator +import at.hannibal2.skyhanni.features.mining.fossilexcavator.ExcavatorProfitTracker +import at.hannibal2.skyhanni.features.mining.fossilexcavator.FossilExcavatorAPI +import at.hannibal2.skyhanni.features.mining.fossilexcavator.solver.FossilSolverDisplay import at.hannibal2.skyhanni.features.mining.powdertracker.PowderTracker import at.hannibal2.skyhanni.features.minion.InfernoMinionFeatures import at.hannibal2.skyhanni.features.minion.MinionCollectLogic @@ -539,6 +541,7 @@ class SkyHanniMod { loadModule(LorenzUtils) loadModule(NEUItems) loadModule(PestAPI) + loadModule(FossilExcavatorAPI) // features loadModule(BazaarOrderHelper()) @@ -663,7 +666,8 @@ class SkyHanniMod { loadModule(GardenCropMilestoneDisplay) loadModule(GardenCustomKeybinds) loadModule(ChickenHeadTimer()) - loadModule(FossilExcavator) + loadModule(FossilSolverDisplay) + loadModule(ExcavatorProfitTracker()) loadModule(GardenOptimalSpeed()) loadModule(GardenLevelDisplay()) loadModule(FarmingWeightDisplay()) diff --git a/src/main/java/at/hannibal2/skyhanni/config/ConfigUpdaterMigrator.kt b/src/main/java/at/hannibal2/skyhanni/config/ConfigUpdaterMigrator.kt index 7907c0f37..75569686c 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/ConfigUpdaterMigrator.kt +++ b/src/main/java/at/hannibal2/skyhanni/config/ConfigUpdaterMigrator.kt @@ -11,7 +11,7 @@ import com.google.gson.JsonPrimitive object ConfigUpdaterMigrator { val logger = LorenzLogger("ConfigMigration") - const val CONFIG_VERSION = 35 + const val CONFIG_VERSION = 36 fun JsonElement.at(chain: List, init: Boolean): JsonElement? { if (chain.isEmpty()) return this if (this !is JsonObject) return null diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/mining/ExcavatorProfitTrackerConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/mining/ExcavatorProfitTrackerConfig.java new file mode 100644 index 000000000..6a3886411 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/mining/ExcavatorProfitTrackerConfig.java @@ -0,0 +1,40 @@ +package at.hannibal2.skyhanni.config.features.mining; + +import at.hannibal2.skyhanni.config.FeatureToggle; +import at.hannibal2.skyhanni.config.core.config.Position; +import com.google.gson.annotations.Expose; +import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorBoolean; +import io.github.notenoughupdates.moulconfig.annotations.ConfigLink; +import io.github.notenoughupdates.moulconfig.annotations.ConfigOption; + +public class ExcavatorProfitTrackerConfig { + + @Expose + @ConfigOption( + name = "Enabled", + desc = "Count all drops you gain while excavating in the Fossil Research Center." + ) + @ConfigEditorBoolean + @FeatureToggle + public boolean enabled = true; + + @Expose + @ConfigOption( + name = "Track Glacite Powder", + desc = "Track Glacite Powder gained as well (no profit, but progress)." + ) + @ConfigEditorBoolean + public boolean trackGlacitePowder = true; + + @Expose + @ConfigOption( + name = "Track Fossil Dust", + desc = "Track Fossil Dust and use it for profit calculation." + ) + @ConfigEditorBoolean + public boolean showFossilDust = true; + + @Expose + @ConfigLink(owner = ExcavatorProfitTrackerConfig.class, field = "enabled") + public Position position = new Position(-380, 150, false, true); +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/mining/FossilExcavatorConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/mining/FossilExcavatorConfig.java index d9a3ac7ba..f1b70c6b3 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/mining/FossilExcavatorConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/mining/FossilExcavatorConfig.java @@ -1,29 +1,19 @@ package at.hannibal2.skyhanni.config.features.mining; -import at.hannibal2.skyhanni.config.FeatureToggle; -import at.hannibal2.skyhanni.config.core.config.Position; import com.google.gson.annotations.Expose; -import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorBoolean; -import io.github.notenoughupdates.moulconfig.annotations.ConfigLink; +import io.github.notenoughupdates.moulconfig.annotations.Accordion; import io.github.notenoughupdates.moulconfig.annotations.ConfigOption; public class FossilExcavatorConfig { @Expose - @ConfigOption(name = "Fossil Excavator Helper", desc = "Helps you find fossils in the fossil excavator. " + - "§eWill always solve if you have at least 18 clicks. Solves everything except Spine, Ugly and Helix in 16 clicks.") - @ConfigEditorBoolean - @FeatureToggle - public boolean enabled = true; + @ConfigOption(name = "Fossil Excavator Solver", desc = "") + @Accordion + public FossilExcavatorSolverConfig solver = new FossilExcavatorSolverConfig(); @Expose - @ConfigOption(name = "Show Percentage", desc = "Shows percentage chance that next click will be a fossil. " + - "§eThis assumes there is a fossil hidden in the dirt.") - @ConfigEditorBoolean - public boolean showPercentage = true; - - @Expose - @ConfigLink(owner = FossilExcavatorConfig.class, field = "enabled") - public Position position = new Position(183, 212, false, true); + @ConfigOption(name = "Excavator Profit Tracker", desc = "") + @Accordion + public ExcavatorProfitTrackerConfig profitTracker = new ExcavatorProfitTrackerConfig(); } diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/mining/FossilExcavatorSolverConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/mining/FossilExcavatorSolverConfig.java new file mode 100644 index 000000000..6b881f7d7 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/mining/FossilExcavatorSolverConfig.java @@ -0,0 +1,28 @@ +package at.hannibal2.skyhanni.config.features.mining; + +import at.hannibal2.skyhanni.config.FeatureToggle; +import at.hannibal2.skyhanni.config.core.config.Position; +import com.google.gson.annotations.Expose; +import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorBoolean; +import io.github.notenoughupdates.moulconfig.annotations.ConfigLink; +import io.github.notenoughupdates.moulconfig.annotations.ConfigOption; + +public class FossilExcavatorSolverConfig { + + @Expose + @ConfigOption(name = "Fossil Excavator Helper", desc = "Helps you find fossils in the fossil excavator. " + + "§eWill always solve if you have at least 18 clicks. Solves everything except Spine, Ugly and Helix in 16 clicks.") + @ConfigEditorBoolean + @FeatureToggle + public boolean enabled = true; + + @Expose + @ConfigOption(name = "Show Percentage", desc = "Shows percentage chance that next click will be a fossil. " + + "§eThis assumes there is a fossil hidden in the dirt.") + @ConfigEditorBoolean + public boolean showPercentage = true; + + @Expose + @ConfigLink(owner = FossilExcavatorSolverConfig.class, field = "enabled") + public Position position = new Position(183, 212, false, true); +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java b/src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java index 7e40825fe..0ec04b195 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java +++ b/src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java @@ -20,9 +20,10 @@ import at.hannibal2.skyhanni.features.garden.farming.ArmorDropTracker; import at.hannibal2.skyhanni.features.garden.farming.DicerRngDropTracker; import at.hannibal2.skyhanni.features.garden.farming.lane.FarmingLane; import at.hannibal2.skyhanni.features.garden.fortuneguide.FarmingItems; -import at.hannibal2.skyhanni.features.garden.pests.VinylType; import at.hannibal2.skyhanni.features.garden.pests.PestProfitTracker; +import at.hannibal2.skyhanni.features.garden.pests.VinylType; import at.hannibal2.skyhanni.features.garden.visitor.VisitorReward; +import at.hannibal2.skyhanni.features.mining.fossilexcavator.ExcavatorProfitTracker; import at.hannibal2.skyhanni.features.mining.powdertracker.PowderTracker; import at.hannibal2.skyhanni.features.misc.trevor.TrevorTracker; import at.hannibal2.skyhanni.features.rift.area.westvillage.VerminTracker; @@ -384,6 +385,9 @@ public class ProfileSpecificStorage { @Expose public List kingsTalkedTo = new ArrayList<>(); + + @Expose + public ExcavatorProfitTracker.Data fossilExcavatorProfitTracker = new ExcavatorProfitTracker.Data(); } @Expose diff --git a/src/main/java/at/hannibal2/skyhanni/events/mining/FossilExcavationEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/mining/FossilExcavationEvent.kt new file mode 100644 index 000000000..b672def95 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/events/mining/FossilExcavationEvent.kt @@ -0,0 +1,5 @@ +package at.hannibal2.skyhanni.events.mining + +import at.hannibal2.skyhanni.events.LorenzEvent + +class FossilExcavationEvent(val loot: List>): LorenzEvent() diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/ExcavatorProfitTracker.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/ExcavatorProfitTracker.kt new file mode 100644 index 000000000..b2bc4a910 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/ExcavatorProfitTracker.kt @@ -0,0 +1,212 @@ +package at.hannibal2.skyhanni.features.mining.fossilexcavator + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.IslandChangeEvent +import at.hannibal2.skyhanni.events.mining.FossilExcavationEvent +import at.hannibal2.skyhanni.utils.ChatUtils +import at.hannibal2.skyhanni.utils.CollectionUtils.addAsSingletonList +import at.hannibal2.skyhanni.utils.ItemUtils.itemName +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland +import at.hannibal2.skyhanni.utils.NEUInternalName +import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName +import at.hannibal2.skyhanni.utils.NEUItems.getPrice +import at.hannibal2.skyhanni.utils.NumberUtil +import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.StringUtils +import at.hannibal2.skyhanni.utils.renderables.Renderable +import at.hannibal2.skyhanni.utils.tracker.ItemTrackerData +import at.hannibal2.skyhanni.utils.tracker.SkyHanniItemTracker +import com.google.gson.annotations.Expose +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.inventory.GuiChest +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class ExcavatorProfitTracker { + + private val config get() = SkyHanniMod.feature.mining.fossilExcavator.profitTracker + + private val tracker = SkyHanniItemTracker( + "Fossil Excavation Profit Tracker", + { Data() }, + { it.mining.fossilExcavatorProfitTracker }) { drawDisplay(it) } + + class Data : ItemTrackerData() { + override fun resetItems() { + timesExcavated = 0 + glacitePowderGained = 0 + fossilDustGained = 0 + } + + override fun getDescription(timesGained: Long): List { + val percentage = timesGained.toDouble() / timesExcavated + val dropRate = LorenzUtils.formatPercentage(percentage.coerceAtMost(1.0)) + return listOf( + "§7Dropped §e${timesGained.addSeparators()} §7times.", + "§7Your drop rate: §c$dropRate." + ) + } + + override fun getCoinName(item: TrackedItem) = "" + + override fun getCoinDescription(item: TrackedItem): List { + return listOf( + "" + ) + } + + @Expose + var timesExcavated = 0L + var glacitePowderGained = 0L + var fossilDustGained = 0L + } + + private val scrapItem = "SUSPICIOUS_SCRAP".asInternalName() + + private fun drawDisplay(data: Data): List> = buildList { + addAsSingletonList("§e§lFossil Excavation Profit Tracker") + var profit = tracker.drawItems(data, { true }, this) + + val timesExcavated = data.timesExcavated + addAsSingletonList( + Renderable.hoverTips( + "§7Times excavated: §e${timesExcavated.addSeparators()}", + listOf("§7You excavated §e${timesExcavated.addSeparators()} §7times.") + ) + ) + + profit = addScrap(timesExcavated, profit) + if (config.showFossilDust) { + profit = addFossilDust(data.fossilDustGained, profit) + } + if (config.trackGlacitePowder) { + addGlacitePowder(data) + } + + addAsSingletonList(tracker.addTotalProfit(profit, data.timesExcavated, "excavation")) + + tracker.addPriceFromButton(this) + } + + private fun MutableList>.addFossilDust( + fossilDustGained: Long, + profit: Double, + ): Double { + if (fossilDustGained <= 0) return profit + // TODO use same price source as profit tracker + val pricePer = scrapItem.getPrice() / 500 + val fossilDustPrice = pricePer * fossilDustGained + addAsSingletonList( + Renderable.hoverTips( + "§7${NumberUtil.format(fossilDustGained)}x §fFossil Dust§7: §6${NumberUtil.format(fossilDustPrice)}", + listOf( + "§7You gained §6${NumberUtil.format(fossilDustPrice)} coins §7in total", + "§7for all §e$fossilDustGained §fFossil Dust", + "§7you have collected.", + "", + "§7Price Per Fossil Dust: §6${NumberUtil.format(pricePer)}" + ) + ) + ) + return profit + fossilDustPrice + } + + private fun MutableList>.addGlacitePowder(data: Data) { + val glacitePowderGained = data.glacitePowderGained + if (glacitePowderGained <= 0) return + addAsSingletonList( + Renderable.hoverTips( + "§bGlacite Powder§7: §e${glacitePowderGained.addSeparators()}", + listOf( + "§7No real profit,", + "§7but still nice to see! Right?", + ) + ) + ) + } + + private fun MutableList>.addScrap( + timesExcavated: Long, + profit: Double, + ): Double { + if (timesExcavated <= 0) return profit + // TODO use same price source as profit tracker + val scrapPrice = timesExcavated * scrapItem.getPrice() + val name = StringUtils.pluralize(timesExcavated.toInt(), scrapItem.itemName) + addAsSingletonList( + Renderable.hoverTips( + "${scrapItem.itemName} §7price: §c-${NumberUtil.format(scrapPrice)}", + listOf( + "§7You paid §c${NumberUtil.format(scrapPrice)} coins §7in total", + "§7for all §e$timesExcavated $name", + "§7you have used." + ) + ) + ) + return profit - scrapPrice + } + + @SubscribeEvent + fun onFossilExcavation(event: FossilExcavationEvent) { + if (!isEnabled()) return + for ((name, amount) in event.loot) { + addItem(name, amount) + } + tracker.modify { + it.timesExcavated++ + } + } + + private fun addItem(name: String, amount: Int) { + if (name == "§bGlacite Powder") { + if (config.trackGlacitePowder) { + tracker.modify { + it.glacitePowderGained += amount + } + } + return + } + if (name == "§fFossil Dust") { + if (config.showFossilDust) { + tracker.modify { + it.fossilDustGained += amount + } + } + return + } + + val internalName = NEUInternalName.fromItemNameOrNull(name) + if (internalName == null) { + ChatUtils.debug("no price for exavator profit: '$name'") + return + } + // TODO use primitive item stacks in trackers + tracker.addItem(internalName, amount) + } + + @SubscribeEvent + fun onRenderOverlay(event: GuiRenderEvent) { + if (!isEnabled()) return + val inChest = Minecraft.getMinecraft().currentScreen is GuiChest + if (inChest) { + // Only show in excavation menu + if (!FossilExcavatorAPI.inExcavatorMenu) { + return + } + } + + tracker.renderDisplay(config.position) + } + + @SubscribeEvent + fun onIslandChange(event: IslandChangeEvent) { + if (event.newIsland == IslandType.DWARVEN_MINES) { + tracker.firstUpdate() + } + } + + fun isEnabled() = IslandType.DWARVEN_MINES.isInIsland() && config.enabled + && LorenzUtils.skyBlockArea == "Fossil Research Center" +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilExcavator.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilExcavator.kt deleted file mode 100644 index e1c46e12e..000000000 --- a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilExcavator.kt +++ /dev/null @@ -1,243 +0,0 @@ -package at.hannibal2.skyhanni.features.mining.fossilexcavator - -import at.hannibal2.skyhanni.SkyHanniMod -import at.hannibal2.skyhanni.SkyHanniMod.Companion.coroutineScope -import at.hannibal2.skyhanni.data.IslandType -import at.hannibal2.skyhanni.events.GuiContainerEvent -import at.hannibal2.skyhanni.events.GuiRenderEvent -import at.hannibal2.skyhanni.events.InventoryCloseEvent -import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent -import at.hannibal2.skyhanni.events.LorenzTickEvent -import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent -import at.hannibal2.skyhanni.events.RenderInventoryItemTipEvent -import at.hannibal2.skyhanni.utils.InventoryUtils -import at.hannibal2.skyhanni.utils.ItemUtils.getLore -import at.hannibal2.skyhanni.utils.LorenzColor -import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland -import at.hannibal2.skyhanni.utils.LorenzUtils.round -import at.hannibal2.skyhanni.utils.RenderUtils.highlight -import at.hannibal2.skyhanni.utils.RenderUtils.renderString -import at.hannibal2.skyhanni.utils.RenderUtils.renderStrings -import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher -import at.hannibal2.skyhanni.utils.StringUtils.removeColor -import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern -import kotlinx.coroutines.launch -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent - -object FossilExcavator { - - private val config get() = SkyHanniMod.feature.mining.fossilExcavator - - private val patternGroup = RepoPattern.group("mining.fossilexcavator") - private val chargesRemainingPattern by patternGroup.pattern( - "chargesremaining", - "Chisel Charges Remaining: (?\\d+)" - ) - private val fossilProgressPattern by patternGroup.pattern( - "fossilprogress", - "Fossil Excavation Progress: (?[\\d.]+%)" - ) - - private var inInventory = false - private var inExcavatorMenu = false - - private var foundPercentage = false - private var percentage: String? = null - - var maxCharges = 0 - private var chargesRemaining = 0 - private var possibleFossilsRemaining = 0 - - private var slotToClick: Int? = null - private var correctPercentage: String? = null - - private var isNotPossible = false - private var isCompleted = false - - private var inventoryItemNames = listOf() - - private const val NOT_POSSIBLE_STRING = "§cNo possible fossils on board." - private const val SOLVED_STRING = "§aFossil found, get all the loot you can." - private const val FOSSILS_REMAINING_STRING = "§ePossible fossils remaining: " - private const val CHARGES_REMAINING_STRING = "§eCharges remaining: " - - var possibleFossilTypes = setOf() - - @SubscribeEvent - fun onInventoryOpen(event: InventoryFullyOpenedEvent) { - if (!isEnabled()) return - if (event.inventoryName != "Fossil Excavator") return - inInventory = true - } - - @SubscribeEvent - fun onWorldChange(event: LorenzWorldChangeEvent) { - clearData() - } - - @SubscribeEvent - fun onInventoryClose(event: InventoryCloseEvent) { - clearData() - } - - private fun clearData() { - inInventory = false - inExcavatorMenu = false - foundPercentage = false - percentage = null - chargesRemaining = 0 - slotToClick = null - correctPercentage = null - isNotPossible = false - isCompleted = false - inventoryItemNames = emptyList() - possibleFossilTypes = emptySet() - } - - @SubscribeEvent - fun onTick(event: LorenzTickEvent) { - if (!isEnabled()) return - if (!inInventory) return - val slots = InventoryUtils.getItemsInOpenChest() - val itemNames = slots.map { it.stack.displayName.removeColor() } - if (itemNames != inventoryItemNames) { - inventoryItemNames = itemNames - inExcavatorMenu = itemNames.any { it == "Start Excavator" } - if (inExcavatorMenu) return - - updateData() - } - } - - private fun updateData() { - val fossilLocations = mutableSetOf() - val dirtLocations = mutableSetOf() - - var foundChargesRemaining = false - for (slot in InventoryUtils.getItemsInOpenChest()) { - val stack = slot.stack - val slotIndex = slot.slotIndex - val stackName = stack.displayName.removeColor() - val isDirt = stackName == "Dirt" - val isFossil = stackName == "Fossil" - when { - isDirt -> dirtLocations.add(slotIndex) - isFossil -> fossilLocations.add(slotIndex) - else -> continue - } - - if (!foundChargesRemaining) { - for (line in stack.getLore()) { - chargesRemainingPattern.matchMatcher(line.removeColor()) { - chargesRemaining = group("charges").toInt() - if (maxCharges == 0) maxCharges = chargesRemaining - foundChargesRemaining = true - } - } - } - - if (!isFossil || foundPercentage) continue - for (line in stack.getLore()) { - fossilProgressPattern.matchMatcher(line.removeColor()) { - foundPercentage = true - percentage = group("progress") - } - } - } - - coroutineScope.launch { - FossilExcavatorSolver.findBestTile(fossilLocations, dirtLocations, percentage) - } - } - - @SubscribeEvent - fun onSlotClick(event: GuiContainerEvent.SlotClickEvent) { - if (!isEnabled()) return - if (!inInventory) return - if (inExcavatorMenu) return - - event.makePickblock() - - val slot = event.slot ?: return - if (slot.slotIndex == slotToClick) { - slotToClick = null - correctPercentage = null - } - } - - @SubscribeEvent - fun onBackgroundDrawn(event: GuiContainerEvent.BackgroundDrawnEvent) { - if (!isEnabled()) return - if (!inInventory) return - if (inExcavatorMenu) return - if (slotToClick == null) return - - for (slot in InventoryUtils.getItemsInOpenChest()) { - if (slot.slotIndex == slotToClick) { - slot highlight LorenzColor.GREEN - } - } - } - - @SubscribeEvent - fun onRenderItemTip(event: RenderInventoryItemTipEvent) { - if (!isEnabled()) return - if (!inInventory) return - if (!config.showPercentage) return - if (slotToClick != event.slot.slotNumber) return - if (inExcavatorMenu) return - val correctPercentage = correctPercentage ?: return - - event.stackTip = correctPercentage - event.offsetX = 10 - event.offsetY = 10 - } - - @SubscribeEvent - fun onBackgroundDraw(event: GuiRenderEvent.ChestGuiOverlayRenderEvent) { - if (!isEnabled()) return - if (!inInventory) return - - if (inExcavatorMenu) { - // render here so they can move it around. As if you press key while doing the excavator you lose the scrap - config.position.renderString("§eExcavator solver gui", posLabel = "Fossil Excavator") - return - } - - val displayList = mutableListOf() - - when { - isNotPossible -> displayList.add(NOT_POSSIBLE_STRING) - isCompleted -> displayList.add(SOLVED_STRING) - else -> displayList.add("$FOSSILS_REMAINING_STRING§a$possibleFossilsRemaining") - } - displayList.add("$CHARGES_REMAINING_STRING§a$chargesRemaining") - - if (possibleFossilTypes.isNotEmpty()) { - displayList.add("§ePossible Fossil types:") - for (fossil in possibleFossilTypes) { - displayList.add("§7- ${fossil.displayName}") - } - } - - config.position.renderStrings(displayList, posLabel = "Fossil Excavator") - } - - fun nextData(slotToClick: FossilTile, correctPercentage: Double, fossilsRemaining: Int) { - val formattedPercentage = (correctPercentage * 100).round(1) - - this.possibleFossilsRemaining = fossilsRemaining - this.slotToClick = slotToClick.toSlotIndex() - this.correctPercentage = "§2$formattedPercentage%" - } - - fun showError() { - isNotPossible = true - } - - fun showCompleted() { - isCompleted = true - } - - private fun isEnabled() = IslandType.DWARVEN_MINES.isInIsland() && config.enabled -} diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilExcavatorAPI.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilExcavatorAPI.kt new file mode 100644 index 000000000..40971c87e --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilExcavatorAPI.kt @@ -0,0 +1,107 @@ +package at.hannibal2.skyhanni.features.mining.fossilexcavator + +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.events.InventoryCloseEvent +import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent +import at.hannibal2.skyhanni.events.InventoryUpdatedEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent +import at.hannibal2.skyhanni.events.mining.FossilExcavationEvent +import at.hannibal2.skyhanni.utils.InventoryUtils +import at.hannibal2.skyhanni.utils.ItemUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland +import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher +import at.hannibal2.skyhanni.utils.StringUtils.matches +import at.hannibal2.skyhanni.utils.StringUtils.removeColor +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +object FossilExcavatorAPI { + + private val patternGroup = RepoPattern.group("mining.fossil.excavator") + private val chatPatternGroup = patternGroup.group("chat") + + /** + * REGEX-TEST: §r§6§lEXCAVATION COMPLETE + */ + private val startPattern by chatPatternGroup.pattern("start", " {2}§r§6§lEXCAVATION COMPLETE ") + + /** + * REGEX-TEST: §a§l▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ + */ + private val endPattern by chatPatternGroup.pattern("end", "§a§l▬{64}") + + /** + * REGEX-TEST: §r§6Tusk Fossil + */ + private val itemPattern by chatPatternGroup.pattern("item", " {4}§r(?.+)") + + /** + * REGEX-TEST: §cYou didn't find anything. Maybe next time! + */ + private val emptyPattern by chatPatternGroup.pattern("empty", "§cYou didn't find anything. Maybe next time!") + + private var inLoot = false + private val loot = mutableListOf>() + + var inInventory = false + var inExcavatorMenu = false + + @SubscribeEvent + fun onInventoryOpen(event: InventoryFullyOpenedEvent) { + if (!IslandType.DWARVEN_MINES.isInIsland()) return + if (event.inventoryName != "Fossil Excavator") return + inInventory = true + } + + @SubscribeEvent + fun onInventoryUpdated(event: InventoryUpdatedEvent) { + if (!inInventory) return + val slots = InventoryUtils.getItemsInOpenChest() + val itemNames = slots.map { it.stack.displayName.removeColor() } + inExcavatorMenu = itemNames.any { it == "Start Excavator" } + } + + @SubscribeEvent + fun onWorldChange(event: LorenzWorldChangeEvent) { + inInventory = false + inExcavatorMenu = false + } + + @SubscribeEvent + fun onInventoryClose(event: InventoryCloseEvent) { + inInventory = false + inExcavatorMenu = false + } + + @SubscribeEvent + fun onChat(event: LorenzChatEvent) { + if (!IslandType.DWARVEN_MINES.isInIsland()) return + + val message = event.message + + if (emptyPattern.matches(message)) { + FossilExcavationEvent(emptyList()).postAndCatch() + } + + + if (startPattern.matches(message)) { + inLoot = true + return + } + + if (!inLoot) return + + if (endPattern.matches(message)) { + FossilExcavationEvent(loot.toList()).postAndCatch() + loot.clear() + inLoot = false + return + } + + val pair = itemPattern.matchMatcher(message) { + ItemUtils.readItemAmount(group("item")) + } ?: return + loot.add(pair) + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilExcavatorSolver.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilExcavatorSolver.kt deleted file mode 100644 index b12e33674..000000000 --- a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilExcavatorSolver.kt +++ /dev/null @@ -1,146 +0,0 @@ -package at.hannibal2.skyhanni.features.mining.fossilexcavator - -object FossilExcavatorSolver { - /* - to be used when they have less than 18 clicks - - solves 361/404 in at most 16 clicks - - solves 396/404 in at most 17 clicks - - solves 400/404 in at most 18 clicks - This is why it is not used all the time - */ - private val riskyStartingSequence: Set> = setOf( - Triple(FossilTile(4, 2), 0.515, 404), - Triple(FossilTile(5, 3), 0.393, 196), - Triple(FossilTile(3, 2), 0.513, 119), - Triple(FossilTile(7, 2), 0.345, 58), - Triple(FossilTile(1, 3), 0.342, 38), - Triple(FossilTile(3, 4), 0.6, 25), - Triple(FossilTile(5, 1), 0.8, 10), - Triple(FossilTile(4, 3), 1.0, 2), - ) - - // once they have 18 chisels solves all in 18 clicks - private val safeStartingSequence: Set> = setOf( - Triple(FossilTile(4, 2), 0.515, 404), - Triple(FossilTile(5, 4), 0.413, 196), - Triple(FossilTile(3, 3), 0.461, 115), - Triple(FossilTile(5, 2), 0.387, 62), - Triple(FossilTile(3, 1), 0.342, 38), - Triple(FossilTile(7, 3), 0.48, 25), - Triple(FossilTile(1, 2), 0.846, 13), - Triple(FossilTile(3, 4), 1.0, 2), - ) - - private var currentlySolving = false - - private fun getCurrentSequence(): Set> { - return if (FossilExcavator.maxCharges < 18) { - riskyStartingSequence - } else { - safeStartingSequence - } - } - - private fun isPositionInStartSequence(position: FossilTile): Boolean { - return getCurrentSequence().any { it.first == position } - } - - fun findBestTile(fossilLocations: Set, dirtLocations: Set, percentage: String?) { - if (currentlySolving) return - currentlySolving = true - - val invalidPositions: MutableSet = mutableSetOf() - for (i in 0..53) { - if (i !in fossilLocations && i !in dirtLocations) { - invalidPositions.add(FossilTile(i)) - } - } - val foundPositions = fossilLocations.map { FossilTile(it) }.toSet() - - val needsMoveSequence = foundPositions.isEmpty() && invalidPositions.all { isPositionInStartSequence(it) } - - if (needsMoveSequence) { - val movesTaken = invalidPositions.size - if (movesTaken >= getCurrentSequence().size) { - FossilExcavator.showError() - currentlySolving = false - return - } - - val nextMove = getCurrentSequence().elementAt(movesTaken) - FossilExcavator.nextData(nextMove.first, nextMove.second, nextMove.third) - currentlySolving = false - return - } - - val possibleClickPositions: MutableMap = mutableMapOf() - var totalPossibleTiles = 0.0 - - val possibleFossilTypes = if (percentage == null) FossilType.entries else { - val possibleFossils = FossilType.getByPercentage(percentage) - FossilExcavator.possibleFossilTypes = possibleFossils.toSet() - possibleFossils - } - - for (x in 0..8) { - for (y in 0..5) { - for (fossil in possibleFossilTypes) { - for (mutation in fossil.possibleMutations) { - val newPosition = mutation.modification(fossil.fossilShape).moveTo(x, y) - if (!isValidFossilPosition(newPosition, invalidPositions, foundPositions)) { - continue - } - - totalPossibleTiles++ - for (position in newPosition.tiles) { - possibleClickPositions.compute(position) { _, v -> v?.plus(1) ?: 1 } - } - } - } - } - } - - possibleClickPositions.filter { it.key in foundPositions }.forEach { - possibleClickPositions.remove(it.key) - } - - val bestPosition = possibleClickPositions.maxByOrNull { it.value } ?: run { - if (fossilLocations.isNotEmpty()) { - FossilExcavator.showCompleted() - currentlySolving = false - return - } - - FossilExcavator.showError() - currentlySolving = false - return - } - - val nextMove = bestPosition.key - val correctPercentage = bestPosition.value / totalPossibleTiles - currentlySolving = false - FossilExcavator.nextData(nextMove, correctPercentage, totalPossibleTiles.toInt()) - } - - private fun isValidFossilPosition( - fossil: FossilShape, - invalidPositions: Set, - foundPositions: Set - ): Boolean { - if (fossil.tiles.any { !isValidPosition(it, invalidPositions) }) { - return false - } - - for (pos in foundPositions) { - if (!fossil.tiles.contains(pos)) { - return false - } - } - return true - } - - private fun isValidPosition(fossil: FossilTile, invalidPositions: Set): Boolean { - if (fossil in invalidPositions) return false - return fossil.x >= 0 && fossil.y >= 0 && fossil.x < 9 && fossil.y < 6 - } -} diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilMutation.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilMutation.kt deleted file mode 100644 index f039b2ef0..000000000 --- a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilMutation.kt +++ /dev/null @@ -1,21 +0,0 @@ -package at.hannibal2.skyhanni.features.mining.fossilexcavator - -enum class FossilMutation(val modification: (FossilShape) -> FossilShape) { - ROTATE_0({ positions -> positions.rotate(0) }), - ROTATE_90({ positions -> positions.rotate(90) }), - ROTATE_180({ positions -> positions.rotate(180) }), - ROTATE_270({ positions -> positions.rotate(270) }), - FLIP_ROTATE_0({ positions -> positions.rotate(0).flipShape() }), - FLIP_ROTATE_90({ positions -> positions.rotate(90).flipShape() }), - FLIP_ROTATE_180({ positions -> positions.rotate(180).flipShape() }), - FLIP_ROTATE_270({ positions -> positions.rotate(270).flipShape() }); - - companion object { - val onlyRotation = listOf( - ROTATE_0, - ROTATE_90, - ROTATE_180, - ROTATE_270 - ) - } -} diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilShape.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilShape.kt deleted file mode 100644 index c30bb9175..000000000 --- a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilShape.kt +++ /dev/null @@ -1,26 +0,0 @@ -package at.hannibal2.skyhanni.features.mining.fossilexcavator - -data class FossilShape(val tiles: List) { - fun width() = tiles.maxOf { it.x } - tiles.minOf { it.x } - fun height() = tiles.maxOf { it.y } - tiles.minOf { it.y } - - fun moveTo(x: Int, y: Int): FossilShape { - return FossilShape(tiles.map { FossilTile(it.x + x, it.y + y) }) - } - - fun rotate(degree: Int): FossilShape { - val width = this.width() - val height = this.height() - return when (degree) { - 90 -> FossilShape(tiles.map { FossilTile(it.y, width - it.x) }) - 180 -> FossilShape(tiles.map { FossilTile(width - it.x, height - it.y) }) - 270 -> FossilShape(tiles.map { FossilTile(height - it.y, it.x) }) - else -> this - } - } - - fun flipShape(): FossilShape { - val height = this.height() - return FossilShape(tiles.map { FossilTile(it.x, height - it.y) }) - } -} diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilTile.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilTile.kt deleted file mode 100644 index 1c6ba7070..000000000 --- a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilTile.kt +++ /dev/null @@ -1,8 +0,0 @@ -package at.hannibal2.skyhanni.features.mining.fossilexcavator - -data class FossilTile(val x: Int, val y: Int) { - - constructor(slotIndex: Int) : this(slotIndex % 9, slotIndex / 9) - - fun toSlotIndex() = x + y * 9 -} diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilType.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilType.kt deleted file mode 100644 index 208dfac96..000000000 --- a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilType.kt +++ /dev/null @@ -1,187 +0,0 @@ -package at.hannibal2.skyhanni.features.mining.fossilexcavator - -enum class FossilType( - val displayName: String, - val totalTiles: Int, - val firstPercentage: String, - val fossilShape: FossilShape, - val possibleMutations: List -) { - TUSK( - "Tusk", 8, "12.5%", - FossilShape( - listOf( - FossilTile(0, 2), - FossilTile(0, 3), - FossilTile(0, 4), - FossilTile(1, 1), - FossilTile(2, 0), - FossilTile(3, 1), - FossilTile(3, 3), - FossilTile(4, 2) - ) - ), - FossilMutation.entries - ), - WEBBED( - "Webbed", 10, "10%", - FossilShape( - listOf( - FossilTile(0, 2), - FossilTile(1, 1), - FossilTile(2, 0), - FossilTile(3, 0), - FossilTile(3, 1), - FossilTile(3, 2), - FossilTile(3, 3), - FossilTile(4, 0), - FossilTile(5, 1), - FossilTile(6, 2), - ) - ), - listOf( - FossilMutation.ROTATE_0, - FossilMutation.FLIP_ROTATE_0, - ) - ), - CLUB( - "Club", 11, "9.1%", - FossilShape( - listOf( - FossilTile(0, 2), - FossilTile(0, 3), - FossilTile(1, 2), - FossilTile(1, 3), - FossilTile(2, 1), - FossilTile(3, 0), - FossilTile(4, 0), - FossilTile(5, 0), - FossilTile(6, 0), - FossilTile(6, 2), - FossilTile(7, 1), - ) - ), - listOf( - FossilMutation.ROTATE_0, - FossilMutation.ROTATE_180, - FossilMutation.FLIP_ROTATE_0, - FossilMutation.FLIP_ROTATE_180, - ) - ), - SPINE( - "Spine", 12, "8.3%", - FossilShape( - listOf( - FossilTile(0, 2), - FossilTile(1, 1), - FossilTile(1, 2), - FossilTile(2, 0), - FossilTile(2, 1), - FossilTile(2, 2), - FossilTile(3, 0), - FossilTile(3, 1), - FossilTile(3, 2), - FossilTile(4, 1), - FossilTile(4, 2), - FossilTile(5, 2), - ) - ), - FossilMutation.onlyRotation - ), - CLAW( - "Claw", 13, "7.7%", - FossilShape( - listOf( - FossilTile(0, 3), - FossilTile(1, 2), - FossilTile(1, 4), - FossilTile(2, 1), - FossilTile(2, 3), - FossilTile(3, 1), - FossilTile(3, 2), - FossilTile(3, 4), - FossilTile(4, 0), - FossilTile(4, 1), - FossilTile(4, 2), - FossilTile(4, 3), - FossilTile(5, 1), - ) - ), - FossilMutation.entries - ), - FOOTPRINT( - "Footprint", 13, "7.7%", - FossilShape( - listOf( - FossilTile(0, 2), - FossilTile(1, 1), - FossilTile(1, 2), - FossilTile(1, 3), - FossilTile(2, 1), - FossilTile(2, 2), - FossilTile(2, 3), - FossilTile(3, 0), - FossilTile(3, 2), - FossilTile(3, 4), - FossilTile(4, 0), - FossilTile(4, 2), - FossilTile(4, 4), - ) - ), - FossilMutation.onlyRotation - ), - HELIX( - "Helix", 14, "7.1%", - FossilShape( - listOf( - FossilTile(0, 0), - FossilTile(0, 1), - FossilTile(0, 2), - FossilTile(0, 4), - FossilTile(1, 0), - FossilTile(1, 2), - FossilTile(1, 4), - FossilTile(2, 0), - FossilTile(2, 4), - FossilTile(3, 0), - FossilTile(3, 1), - FossilTile(3, 2), - FossilTile(3, 3), - FossilTile(3, 4), - ) - ), - FossilMutation.entries - ), - UGLY( - "Ugly", 16, "6.2%", - FossilShape( - listOf( - FossilTile(0, 1), - FossilTile(1, 0), - FossilTile(1, 1), - FossilTile(1, 2), - FossilTile(2, 0), - FossilTile(2, 1), - FossilTile(2, 2), - FossilTile(2, 3), - FossilTile(3, 0), - FossilTile(3, 1), - FossilTile(3, 2), - FossilTile(3, 3), - FossilTile(4, 0), - FossilTile(4, 1), - FossilTile(4, 2), - FossilTile(5, 1), - ) - ), - FossilMutation.onlyRotation - ), - ; - - companion object { - fun getByPercentage(percentage: String): MutableList { - return entries.filter { it.firstPercentage == percentage }.toMutableList() - } - } -} - diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilMutation.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilMutation.kt new file mode 100644 index 000000000..724e2a5aa --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilMutation.kt @@ -0,0 +1,21 @@ +package at.hannibal2.skyhanni.features.mining.fossilexcavator.solver + +enum class FossilMutation(val modification: (FossilShape) -> FossilShape) { + ROTATE_0({ positions -> positions.rotate(0) }), + ROTATE_90({ positions -> positions.rotate(90) }), + ROTATE_180({ positions -> positions.rotate(180) }), + ROTATE_270({ positions -> positions.rotate(270) }), + FLIP_ROTATE_0({ positions -> positions.rotate(0).flipShape() }), + FLIP_ROTATE_90({ positions -> positions.rotate(90).flipShape() }), + FLIP_ROTATE_180({ positions -> positions.rotate(180).flipShape() }), + FLIP_ROTATE_270({ positions -> positions.rotate(270).flipShape() }); + + companion object { + val onlyRotation = listOf( + ROTATE_0, + ROTATE_90, + ROTATE_180, + ROTATE_270 + ) + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilShape.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilShape.kt new file mode 100644 index 000000000..ae8b25593 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilShape.kt @@ -0,0 +1,26 @@ +package at.hannibal2.skyhanni.features.mining.fossilexcavator.solver + +data class FossilShape(val tiles: List) { + fun width() = tiles.maxOf { it.x } - tiles.minOf { it.x } + fun height() = tiles.maxOf { it.y } - tiles.minOf { it.y } + + fun moveTo(x: Int, y: Int): FossilShape { + return FossilShape(tiles.map { FossilTile(it.x + x, it.y + y) }) + } + + fun rotate(degree: Int): FossilShape { + val width = this.width() + val height = this.height() + return when (degree) { + 90 -> FossilShape(tiles.map { FossilTile(it.y, width - it.x) }) + 180 -> FossilShape(tiles.map { FossilTile(width - it.x, height - it.y) }) + 270 -> FossilShape(tiles.map { FossilTile(height - it.y, it.x) }) + else -> this + } + } + + fun flipShape(): FossilShape { + val height = this.height() + return FossilShape(tiles.map { FossilTile(it.x, height - it.y) }) + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilSolver.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilSolver.kt new file mode 100644 index 000000000..7af22d701 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilSolver.kt @@ -0,0 +1,146 @@ +package at.hannibal2.skyhanni.features.mining.fossilexcavator.solver + +object FossilSolver { + /* + to be used when they have less than 18 clicks + - solves 361/404 in at most 16 clicks + - solves 396/404 in at most 17 clicks + - solves 400/404 in at most 18 clicks + This is why it is not used all the time + */ + private val riskyStartingSequence: Set> = setOf( + Triple(FossilTile(4, 2), 0.515, 404), + Triple(FossilTile(5, 3), 0.393, 196), + Triple(FossilTile(3, 2), 0.513, 119), + Triple(FossilTile(7, 2), 0.345, 58), + Triple(FossilTile(1, 3), 0.342, 38), + Triple(FossilTile(3, 4), 0.6, 25), + Triple(FossilTile(5, 1), 0.8, 10), + Triple(FossilTile(4, 3), 1.0, 2), + ) + + // once they have 18 chisels solves all in 18 clicks + private val safeStartingSequence: Set> = setOf( + Triple(FossilTile(4, 2), 0.515, 404), + Triple(FossilTile(5, 4), 0.413, 196), + Triple(FossilTile(3, 3), 0.461, 115), + Triple(FossilTile(5, 2), 0.387, 62), + Triple(FossilTile(3, 1), 0.342, 38), + Triple(FossilTile(7, 3), 0.48, 25), + Triple(FossilTile(1, 2), 0.846, 13), + Triple(FossilTile(3, 4), 1.0, 2), + ) + + private var currentlySolving = false + + private fun getCurrentSequence(): Set> { + return if (FossilSolverDisplay.maxCharges < 18) { + riskyStartingSequence + } else { + safeStartingSequence + } + } + + private fun isPositionInStartSequence(position: FossilTile): Boolean { + return getCurrentSequence().any { it.first == position } + } + + fun findBestTile(fossilLocations: Set, dirtLocations: Set, percentage: String?) { + if (currentlySolving) return + currentlySolving = true + + val invalidPositions: MutableSet = mutableSetOf() + for (i in 0..53) { + if (i !in fossilLocations && i !in dirtLocations) { + invalidPositions.add(FossilTile(i)) + } + } + val foundPositions = fossilLocations.map { FossilTile(it) }.toSet() + + val needsMoveSequence = foundPositions.isEmpty() && invalidPositions.all { isPositionInStartSequence(it) } + + if (needsMoveSequence) { + val movesTaken = invalidPositions.size + if (movesTaken >= getCurrentSequence().size) { + FossilSolverDisplay.showError() + currentlySolving = false + return + } + + val nextMove = getCurrentSequence().elementAt(movesTaken) + FossilSolverDisplay.nextData(nextMove.first, nextMove.second, nextMove.third) + currentlySolving = false + return + } + + val possibleClickPositions: MutableMap = mutableMapOf() + var totalPossibleTiles = 0.0 + + val possibleFossilTypes = if (percentage == null) FossilType.entries else { + val possibleFossils = FossilType.getByPercentage(percentage) + FossilSolverDisplay.possibleFossilTypes = possibleFossils.toSet() + possibleFossils + } + + for (x in 0..8) { + for (y in 0..5) { + for (fossil in possibleFossilTypes) { + for (mutation in fossil.possibleMutations) { + val newPosition = mutation.modification(fossil.fossilShape).moveTo(x, y) + if (!isValidFossilPosition(newPosition, invalidPositions, foundPositions)) { + continue + } + + totalPossibleTiles++ + for (position in newPosition.tiles) { + possibleClickPositions.compute(position) { _, v -> v?.plus(1) ?: 1 } + } + } + } + } + } + + possibleClickPositions.filter { it.key in foundPositions }.forEach { + possibleClickPositions.remove(it.key) + } + + val bestPosition = possibleClickPositions.maxByOrNull { it.value } ?: run { + if (fossilLocations.isNotEmpty()) { + FossilSolverDisplay.showCompleted() + currentlySolving = false + return + } + + FossilSolverDisplay.showError() + currentlySolving = false + return + } + + val nextMove = bestPosition.key + val correctPercentage = bestPosition.value / totalPossibleTiles + currentlySolving = false + FossilSolverDisplay.nextData(nextMove, correctPercentage, totalPossibleTiles.toInt()) + } + + private fun isValidFossilPosition( + fossil: FossilShape, + invalidPositions: Set, + foundPositions: Set + ): Boolean { + if (fossil.tiles.any { !isValidPosition(it, invalidPositions) }) { + return false + } + + for (pos in foundPositions) { + if (!fossil.tiles.contains(pos)) { + return false + } + } + return true + } + + private fun isValidPosition(fossil: FossilTile, invalidPositions: Set): Boolean { + if (fossil in invalidPositions) return false + return fossil.x >= 0 && fossil.y >= 0 && fossil.x < 9 && fossil.y < 6 + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilSolverDisplay.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilSolverDisplay.kt new file mode 100644 index 000000000..590575ecd --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilSolverDisplay.kt @@ -0,0 +1,233 @@ +package at.hannibal2.skyhanni.features.mining.fossilexcavator.solver + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.SkyHanniMod.Companion.coroutineScope +import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.events.GuiContainerEvent +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.InventoryCloseEvent +import at.hannibal2.skyhanni.events.LorenzTickEvent +import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent +import at.hannibal2.skyhanni.events.RenderInventoryItemTipEvent +import at.hannibal2.skyhanni.features.mining.fossilexcavator.FossilExcavatorAPI +import at.hannibal2.skyhanni.utils.InventoryUtils +import at.hannibal2.skyhanni.utils.ItemUtils.getLore +import at.hannibal2.skyhanni.utils.LorenzColor +import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland +import at.hannibal2.skyhanni.utils.LorenzUtils.round +import at.hannibal2.skyhanni.utils.RenderUtils.highlight +import at.hannibal2.skyhanni.utils.RenderUtils.renderString +import at.hannibal2.skyhanni.utils.RenderUtils.renderStrings +import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher +import at.hannibal2.skyhanni.utils.StringUtils.removeColor +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern +import kotlinx.coroutines.launch +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +object FossilSolverDisplay { + + private val config get() = SkyHanniMod.feature.mining.fossilExcavator.solver + + private val patternGroup = RepoPattern.group("mining.fossilexcavator") + private val chargesRemainingPattern by patternGroup.pattern( + "chargesremaining", + "Chisel Charges Remaining: (?\\d+)" + ) + private val fossilProgressPattern by patternGroup.pattern( + "fossilprogress", + "Fossil Excavation Progress: (?[\\d.]+%)" + ) + + private val inExcavatorMenu get() = FossilExcavatorAPI.inExcavatorMenu + + private var foundPercentage = false + private var percentage: String? = null + + var maxCharges = 0 + private var chargesRemaining = 0 + private var possibleFossilsRemaining = 0 + + private var slotToClick: Int? = null + private var correctPercentage: String? = null + + private var isNotPossible = false + private var isCompleted = false + + private var inventoryItemNames = listOf() + + private const val NOT_POSSIBLE_STRING = "§cNo possible fossils on board." + private const val SOLVED_STRING = "§aFossil found, get all the loot you can." + private const val FOSSILS_REMAINING_STRING = "§ePossible fossils remaining: " + private const val CHARGES_REMAINING_STRING = "§eCharges remaining: " + + var possibleFossilTypes = setOf() + + @SubscribeEvent + fun onWorldChange(event: LorenzWorldChangeEvent) { + clearData() + } + + @SubscribeEvent + fun onInventoryClose(event: InventoryCloseEvent) { + clearData() + } + + private fun clearData() { + foundPercentage = false + percentage = null + chargesRemaining = 0 + slotToClick = null + correctPercentage = null + isNotPossible = false + isCompleted = false + inventoryItemNames = emptyList() + possibleFossilTypes = emptySet() + } + + @SubscribeEvent + fun onTick(event: LorenzTickEvent) { + if (!isEnabled()) return + val slots = InventoryUtils.getItemsInOpenChest() + val itemNames = slots.map { it.stack.displayName.removeColor() } + if (itemNames != inventoryItemNames) { + inventoryItemNames = itemNames + if (inExcavatorMenu) return + + updateData() + } + } + + private fun updateData() { + val fossilLocations = mutableSetOf() + val dirtLocations = mutableSetOf() + + var foundChargesRemaining = false + for (slot in InventoryUtils.getItemsInOpenChest()) { + val stack = slot.stack + val slotIndex = slot.slotIndex + val stackName = stack.displayName.removeColor() + val isDirt = stackName == "Dirt" + val isFossil = stackName == "Fossil" + when { + isDirt -> dirtLocations.add(slotIndex) + isFossil -> fossilLocations.add(slotIndex) + else -> continue + } + + if (!foundChargesRemaining) { + for (line in stack.getLore()) { + chargesRemainingPattern.matchMatcher(line.removeColor()) { + chargesRemaining = group("charges").toInt() + if (maxCharges == 0) maxCharges = chargesRemaining + foundChargesRemaining = true + } + } + } + + if (!isFossil || foundPercentage) continue + for (line in stack.getLore()) { + fossilProgressPattern.matchMatcher(line.removeColor()) { + foundPercentage = true + percentage = group("progress") + } + } + } + + coroutineScope.launch { + FossilSolver.findBestTile(fossilLocations, dirtLocations, percentage) + } + } + + @SubscribeEvent + fun onSlotClick(event: GuiContainerEvent.SlotClickEvent) { + if (!isEnabled()) return + if (inExcavatorMenu) return + + event.makePickblock() + + val slot = event.slot ?: return + if (slot.slotIndex == slotToClick) { + slotToClick = null + correctPercentage = null + } + } + + @SubscribeEvent + fun onBackgroundDrawn(event: GuiContainerEvent.BackgroundDrawnEvent) { + if (!isEnabled()) return + if (inExcavatorMenu) return + if (slotToClick == null) return + + for (slot in InventoryUtils.getItemsInOpenChest()) { + if (slot.slotIndex == slotToClick) { + slot highlight LorenzColor.GREEN + } + } + } + + @SubscribeEvent + fun onRenderItemTip(event: RenderInventoryItemTipEvent) { + if (!isEnabled()) return + if (!config.showPercentage) return + if (slotToClick != event.slot.slotNumber) return + if (inExcavatorMenu) return + val correctPercentage = correctPercentage ?: return + + event.stackTip = correctPercentage + event.offsetX = 10 + event.offsetY = 10 + } + + @SubscribeEvent + fun onBackgroundDraw(event: GuiRenderEvent.ChestGuiOverlayRenderEvent) { + if (!isEnabled()) return + + if (inExcavatorMenu) { + // render here so they can move it around. As if you press key while doing the excavator you lose the scrap + config.position.renderString("§eExcavator solver gui", posLabel = "Fossil Excavator Solver") + return + } + + val displayList = mutableListOf() + + when { + isNotPossible -> displayList.add(NOT_POSSIBLE_STRING) + isCompleted -> displayList.add(SOLVED_STRING) + else -> displayList.add("${FOSSILS_REMAINING_STRING}§a$possibleFossilsRemaining") + } + displayList.add("${CHARGES_REMAINING_STRING}§a$chargesRemaining") + + if (possibleFossilTypes.isNotEmpty()) { + displayList.add("§ePossible Fossil types:") + for (fossil in possibleFossilTypes) { + displayList.add("§7- ${fossil.displayName}") + } + } + + config.position.renderStrings(displayList, posLabel = "Fossil Excavator Solver") + } + + @SubscribeEvent + fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { + event.move(36, "mining.fossilExcavator", "mining.fossilExcavator.solver") + } + + fun nextData(slotToClick: FossilTile, correctPercentage: Double, fossilsRemaining: Int) { + val formattedPercentage = (correctPercentage * 100).round(1) + + possibleFossilsRemaining = fossilsRemaining + FossilSolverDisplay.slotToClick = slotToClick.toSlotIndex() + FossilSolverDisplay.correctPercentage = "§2$formattedPercentage%" + } + + fun showError() { + isNotPossible = true + } + + fun showCompleted() { + isCompleted = true + } + + private fun isEnabled() = IslandType.DWARVEN_MINES.isInIsland() && config.enabled && FossilExcavatorAPI.inInventory +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilTile.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilTile.kt new file mode 100644 index 000000000..e671633cb --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilTile.kt @@ -0,0 +1,8 @@ +package at.hannibal2.skyhanni.features.mining.fossilexcavator.solver + +data class FossilTile(val x: Int, val y: Int) { + + constructor(slotIndex: Int) : this(slotIndex % 9, slotIndex / 9) + + fun toSlotIndex() = x + y * 9 +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilType.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilType.kt new file mode 100644 index 000000000..e97271dfb --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilType.kt @@ -0,0 +1,187 @@ +package at.hannibal2.skyhanni.features.mining.fossilexcavator.solver + +enum class FossilType( + val displayName: String, + val totalTiles: Int, + val firstPercentage: String, + val fossilShape: FossilShape, + val possibleMutations: List +) { + TUSK( + "Tusk", 8, "12.5%", + FossilShape( + listOf( + FossilTile(0, 2), + FossilTile(0, 3), + FossilTile(0, 4), + FossilTile(1, 1), + FossilTile(2, 0), + FossilTile(3, 1), + FossilTile(3, 3), + FossilTile(4, 2) + ) + ), + FossilMutation.entries + ), + WEBBED( + "Webbed", 10, "10%", + FossilShape( + listOf( + FossilTile(0, 2), + FossilTile(1, 1), + FossilTile(2, 0), + FossilTile(3, 0), + FossilTile(3, 1), + FossilTile(3, 2), + FossilTile(3, 3), + FossilTile(4, 0), + FossilTile(5, 1), + FossilTile(6, 2), + ) + ), + listOf( + FossilMutation.ROTATE_0, + FossilMutation.FLIP_ROTATE_0, + ) + ), + CLUB( + "Club", 11, "9.1%", + FossilShape( + listOf( + FossilTile(0, 2), + FossilTile(0, 3), + FossilTile(1, 2), + FossilTile(1, 3), + FossilTile(2, 1), + FossilTile(3, 0), + FossilTile(4, 0), + FossilTile(5, 0), + FossilTile(6, 0), + FossilTile(6, 2), + FossilTile(7, 1), + ) + ), + listOf( + FossilMutation.ROTATE_0, + FossilMutation.ROTATE_180, + FossilMutation.FLIP_ROTATE_0, + FossilMutation.FLIP_ROTATE_180, + ) + ), + SPINE( + "Spine", 12, "8.3%", + FossilShape( + listOf( + FossilTile(0, 2), + FossilTile(1, 1), + FossilTile(1, 2), + FossilTile(2, 0), + FossilTile(2, 1), + FossilTile(2, 2), + FossilTile(3, 0), + FossilTile(3, 1), + FossilTile(3, 2), + FossilTile(4, 1), + FossilTile(4, 2), + FossilTile(5, 2), + ) + ), + FossilMutation.onlyRotation + ), + CLAW( + "Claw", 13, "7.7%", + FossilShape( + listOf( + FossilTile(0, 3), + FossilTile(1, 2), + FossilTile(1, 4), + FossilTile(2, 1), + FossilTile(2, 3), + FossilTile(3, 1), + FossilTile(3, 2), + FossilTile(3, 4), + FossilTile(4, 0), + FossilTile(4, 1), + FossilTile(4, 2), + FossilTile(4, 3), + FossilTile(5, 1), + ) + ), + FossilMutation.entries + ), + FOOTPRINT( + "Footprint", 13, "7.7%", + FossilShape( + listOf( + FossilTile(0, 2), + FossilTile(1, 1), + FossilTile(1, 2), + FossilTile(1, 3), + FossilTile(2, 1), + FossilTile(2, 2), + FossilTile(2, 3), + FossilTile(3, 0), + FossilTile(3, 2), + FossilTile(3, 4), + FossilTile(4, 0), + FossilTile(4, 2), + FossilTile(4, 4), + ) + ), + FossilMutation.onlyRotation + ), + HELIX( + "Helix", 14, "7.1%", + FossilShape( + listOf( + FossilTile(0, 0), + FossilTile(0, 1), + FossilTile(0, 2), + FossilTile(0, 4), + FossilTile(1, 0), + FossilTile(1, 2), + FossilTile(1, 4), + FossilTile(2, 0), + FossilTile(2, 4), + FossilTile(3, 0), + FossilTile(3, 1), + FossilTile(3, 2), + FossilTile(3, 3), + FossilTile(3, 4), + ) + ), + FossilMutation.entries + ), + UGLY( + "Ugly", 16, "6.2%", + FossilShape( + listOf( + FossilTile(0, 1), + FossilTile(1, 0), + FossilTile(1, 1), + FossilTile(1, 2), + FossilTile(2, 0), + FossilTile(2, 1), + FossilTile(2, 2), + FossilTile(2, 3), + FossilTile(3, 0), + FossilTile(3, 1), + FossilTile(3, 2), + FossilTile(3, 3), + FossilTile(4, 0), + FossilTile(4, 1), + FossilTile(4, 2), + FossilTile(5, 1), + ) + ), + FossilMutation.onlyRotation + ), + ; + + companion object { + fun getByPercentage(percentage: String): MutableList { + return entries.filter { it.firstPercentage == percentage }.toMutableList() + } + } +} + diff --git a/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt index 94558d949..5104c52b3 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt @@ -12,6 +12,7 @@ import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.isRecombobulated import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher import at.hannibal2.skyhanni.utils.StringUtils.matches import at.hannibal2.skyhanni.utils.StringUtils.removeColor +import at.hannibal2.skyhanni.utils.StringUtils.removeResets import com.google.gson.GsonBuilder import com.google.gson.JsonObject import net.minecraft.client.Minecraft @@ -293,9 +294,9 @@ object ItemUtils { fun readItemAmount(originalInput: String): Pair? { // This workaround fixes 'Tubto Cacti I Book' - val input = if (originalInput.endsWith(" Book")) { + val input = (if (originalInput.endsWith(" Book")) { originalInput.replace(" Book", "") - } else originalInput + } else originalInput).removeResets() if (itemAmountCache.containsKey(input)) { return itemAmountCache[input]!! @@ -313,9 +314,6 @@ object ItemUtils { string = string.substring(2) val matcher = UtilsPatterns.readAmountAfterPattern.matcher(string) if (!matcher.matches()) { - println("") - println("input: '$input'") - println("string: '$string'") return null } diff --git a/src/main/java/at/hannibal2/skyhanni/utils/UtilsPatterns.kt b/src/main/java/at/hannibal2/skyhanni/utils/UtilsPatterns.kt index 97a4ab51c..e6a244f09 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/UtilsPatterns.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/UtilsPatterns.kt @@ -53,7 +53,7 @@ object UtilsPatterns { ) val readAmountAfterPattern by patternGroup.pattern( "item.amount.behind", - "(?(?:['\\w-]+ ?)+)(?:§8x(?[\\d,]+))?" + "(?(?:§.)*(?:[^§] ?)+)(?:§8x(?[\\d,]+))?" ) val timeAmountPattern by patternGroup.pattern( diff --git a/src/main/java/at/hannibal2/skyhanni/utils/tracker/SkyHanniItemTracker.kt b/src/main/java/at/hannibal2/skyhanni/utils/tracker/SkyHanniItemTracker.kt index a64471e75..46bb84f04 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/tracker/SkyHanniItemTracker.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/tracker/SkyHanniItemTracker.kt @@ -183,7 +183,7 @@ class SkyHanniItemTracker( } fun addTotalProfit(profit: Double, totalAmount: Long, action: String): Renderable { - val profitFormat = profit.addSeparators() + val profitFormat = profit.toInt().addSeparators() val profitPrefix = if (profit < 0) "§c" else "§6" val tips = if (totalAmount > 0) { -- cgit