aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCalMWolfs <94038482+CalMWolfs@users.noreply.github.com>2024-04-12 15:24:15 +1000
committerGitHub <noreply@github.com>2024-04-12 07:24:15 +0200
commitc52e49c3be13630c872bcb4d2beac13959fbe557 (patch)
tree2b06e9223f7d6ec8026e53aeb0adda3ab339c8ac /src
parent863a323d84901a3f0960f39667f95f7cbfb5d31d (diff)
downloadskyhanni-c52e49c3be13630c872bcb4d2beac13959fbe557.tar.gz
skyhanni-c52e49c3be13630c872bcb4d2beac13959fbe557.tar.bz2
skyhanni-c52e49c3be13630c872bcb4d2beac13959fbe557.zip
Feature: Fossil Excavator (#1427)
Diffstat (limited to 'src')
-rw-r--r--src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/mining/FossilExcavatorConfig.java29
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/mining/MiningConfig.java5
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/mining/crystalhollows/CrystalHollowsWalls.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilExcavator.kt243
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilExcavatorSolver.kt146
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilMutation.kt21
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilShape.kt26
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilTile.kt8
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilType.kt187
10 files changed, 668 insertions, 1 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt
index ce729c24d..bb1e05cfc 100644
--- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt
+++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt
@@ -268,6 +268,7 @@ 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.powdertracker.PowderTracker
import at.hannibal2.skyhanni.features.minion.InfernoMinionFeatures
import at.hannibal2.skyhanni.features.minion.MinionCollectLogic
@@ -658,6 +659,7 @@ class SkyHanniMod {
loadModule(GardenCropMilestoneDisplay)
loadModule(GardenCustomKeybinds)
loadModule(ChickenHeadTimer())
+ loadModule(FossilExcavator)
loadModule(GardenOptimalSpeed())
loadModule(GardenLevelDisplay())
loadModule(FarmingWeightDisplay())
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
new file mode 100644
index 000000000..d9a3ac7ba
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/mining/FossilExcavatorConfig.java
@@ -0,0 +1,29 @@
+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 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;
+
+ @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);
+
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/mining/MiningConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/mining/MiningConfig.java
index 2741955dc..5beafceb0 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/features/mining/MiningConfig.java
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/mining/MiningConfig.java
@@ -34,6 +34,11 @@ public class MiningConfig {
public AreaWallsConfig crystalHollowsAreaWalls = new AreaWallsConfig();
@Expose
+ @ConfigOption(name = "Fossil Excavator", desc = "")
+ @Accordion
+ public FossilExcavatorConfig fossilExcavator = new FossilExcavatorConfig();
+
+ @Expose
@ConfigOption(name = "Highlight Commission Mobs", desc = "Highlight Mobs that are part of active commissions.")
@ConfigEditorBoolean
@FeatureToggle
diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/crystalhollows/CrystalHollowsWalls.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/crystalhollows/CrystalHollowsWalls.kt
index 914f8393a..08af9199f 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/mining/crystalhollows/CrystalHollowsWalls.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/mining/crystalhollows/CrystalHollowsWalls.kt
@@ -16,7 +16,7 @@ import java.awt.Color
class CrystalHollowsWalls {
- val config get() = SkyHanniMod.feature.mining.crystalHollowsAreaWalls
+ private val config get() = SkyHanniMod.feature.mining.crystalHollowsAreaWalls
fun isEnabled() = config.enabled && IslandType.CRYSTAL_HOLLOWS.isInIsland()
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
new file mode 100644
index 000000000..e1c46e12e
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilExcavator.kt
@@ -0,0 +1,243 @@
+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: (?<charges>\\d+)"
+ )
+ private val fossilProgressPattern by patternGroup.pattern(
+ "fossilprogress",
+ "Fossil Excavation Progress: (?<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<String>()
+
+ 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<FossilType>()
+
+ @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<Int>()
+ val dirtLocations = mutableSetOf<Int>()
+
+ 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<String>()
+
+ 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/FossilExcavatorSolver.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilExcavatorSolver.kt
new file mode 100644
index 000000000..b12e33674
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilExcavatorSolver.kt
@@ -0,0 +1,146 @@
+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<Triple<FossilTile, Double, Int>> = 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<Triple<FossilTile, Double, Int>> = 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<Triple<FossilTile, Double, Int>> {
+ return if (FossilExcavator.maxCharges < 18) {
+ riskyStartingSequence
+ } else {
+ safeStartingSequence
+ }
+ }
+
+ private fun isPositionInStartSequence(position: FossilTile): Boolean {
+ return getCurrentSequence().any { it.first == position }
+ }
+
+ fun findBestTile(fossilLocations: Set<Int>, dirtLocations: Set<Int>, percentage: String?) {
+ if (currentlySolving) return
+ currentlySolving = true
+
+ val invalidPositions: MutableSet<FossilTile> = 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<FossilTile, Int> = 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<FossilTile>,
+ foundPositions: Set<FossilTile>
+ ): 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<FossilTile>): 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
new file mode 100644
index 000000000..f039b2ef0
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilMutation.kt
@@ -0,0 +1,21 @@
+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
new file mode 100644
index 000000000..c30bb9175
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilShape.kt
@@ -0,0 +1,26 @@
+package at.hannibal2.skyhanni.features.mining.fossilexcavator
+
+data class FossilShape(val tiles: List<FossilTile>) {
+ 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
new file mode 100644
index 000000000..1c6ba7070
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilTile.kt
@@ -0,0 +1,8 @@
+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
new file mode 100644
index 000000000..208dfac96
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilType.kt
@@ -0,0 +1,187 @@
+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<FossilMutation>
+) {
+ 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<FossilType> {
+ return entries.filter { it.firstPercentage == percentage }.toMutableList()
+ }
+ }
+}
+