From dcdea84e7acc5431c5257ef8348a77b7d4de9bc7 Mon Sep 17 00:00:00 2001 From: hannibal2 <24389977+hannibal002@users.noreply.github.com> Date: Fri, 12 Apr 2024 12:56:37 +0200 Subject: Backend: Fossil Excavator API (#1431) Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com> --- src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt | 4 +- .../mining/fossilexcavator/FossilExcavator.kt | 243 --------------------- .../mining/fossilexcavator/FossilExcavatorAPI.kt | 44 ++++ .../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/FossilExcavator.kt | 227 +++++++++++++++++++ .../solver/FossilExcavatorSolver.kt | 146 +++++++++++++ .../fossilexcavator/solver/FossilMutation.kt | 21 ++ .../mining/fossilexcavator/solver/FossilShape.kt | 26 +++ .../mining/fossilexcavator/solver/FossilTile.kt | 8 + .../mining/fossilexcavator/solver/FossilType.kt | 187 ++++++++++++++++ 14 files changed, 662 insertions(+), 632 deletions(-) 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/FossilExcavator.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilExcavatorSolver.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/FossilTile.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilType.kt (limited to 'src/main/java/at/hannibal2') diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt index bb1e05cfc..622d8adfa 100644 --- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt +++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt @@ -268,7 +268,8 @@ 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.FossilExcavatorAPI +import at.hannibal2.skyhanni.features.mining.fossilexcavator.solver.FossilExcavator import at.hannibal2.skyhanni.features.mining.powdertracker.PowderTracker import at.hannibal2.skyhanni.features.minion.InfernoMinionFeatures import at.hannibal2.skyhanni.features.minion.MinionCollectLogic @@ -536,6 +537,7 @@ class SkyHanniMod { loadModule(LorenzUtils) loadModule(NEUItems) loadModule(PestAPI) + loadModule(FossilExcavatorAPI) // features loadModule(BazaarOrderHelper()) 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..bb9e2c30b --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/FossilExcavatorAPI.kt @@ -0,0 +1,44 @@ +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.LorenzWorldChangeEvent +import at.hannibal2.skyhanni.utils.InventoryUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland +import at.hannibal2.skyhanni.utils.StringUtils.removeColor +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +object FossilExcavatorAPI { + + 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 + } +} 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/FossilExcavator.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilExcavator.kt new file mode 100644 index 000000000..078e96d54 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilExcavator.kt @@ -0,0 +1,227 @@ +package at.hannibal2.skyhanni.features.mining.fossilexcavator.solver + +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.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 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 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 { + FossilExcavatorSolver.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") + 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) + + possibleFossilsRemaining = fossilsRemaining + FossilExcavator.slotToClick = slotToClick.toSlotIndex() + FossilExcavator.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/FossilExcavatorSolver.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilExcavatorSolver.kt new file mode 100644 index 000000000..18c256529 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilExcavatorSolver.kt @@ -0,0 +1,146 @@ +package at.hannibal2.skyhanni.features.mining.fossilexcavator.solver + +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/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/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() + } + } +} + -- cgit