aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/at/hannibal2/skyhanni/SkyHanniMod.java4
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt4
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/Bingo.java15
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/bingo/BingoCardDisplay.kt181
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/bingo/BingoNextStepHelper.kt343
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/bingo/card/BingoGoals.kt4
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/bingo/card/CommunityGoal.kt4
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/bingo/card/PersonalGoal.kt3
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/ChatMessageStep.kt3
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/CollectionStep.kt6
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/CraftStep.kt3
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/IslandVisitStep.kt5
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/ItemsStep.kt4
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/NextStep.kt7
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/ProgressionStep.kt3
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/SkillLevelStep.kt10
16 files changed, 599 insertions, 0 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.java b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.java
index b2361b399..d2c427ff5 100644
--- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.java
+++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.java
@@ -8,6 +8,8 @@ import at.hannibal2.skyhanni.data.repo.RepoManager;
import at.hannibal2.skyhanni.events.FirstConfigLoadedEvent;
import at.hannibal2.skyhanni.features.anvil.AnvilCombineHelper;
import at.hannibal2.skyhanni.features.bazaar.*;
+import at.hannibal2.skyhanni.features.bingo.BingoCardDisplay;
+import at.hannibal2.skyhanni.features.bingo.BingoNextStepHelper;
import at.hannibal2.skyhanni.features.bingo.CompactBingoChat;
import at.hannibal2.skyhanni.features.chat.ChatFilter;
import at.hannibal2.skyhanni.features.chat.PlayerDeathMessages;
@@ -179,6 +181,8 @@ public class SkyHanniMod {
loadModule(new OdgerWaypoint());
loadModule(new TiaRelayHelper());
loadModule(new TiaRelayWaypoints());
+ loadModule(new BingoCardDisplay());
+ loadModule(new BingoNextStepHelper());
Commands.INSTANCE.init();
diff --git a/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt b/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt
index c8e8d8889..787d1000f 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt
+++ b/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt
@@ -4,6 +4,8 @@ import at.hannibal2.skyhanni.SkyHanniMod
import at.hannibal2.skyhanni.config.ConfigEditor
import at.hannibal2.skyhanni.config.commands.SimpleCommand.ProcessCommandRunnable
import at.hannibal2.skyhanni.config.core.GuiScreenElementWrapper
+import at.hannibal2.skyhanni.features.bingo.BingoCardDisplay
+import at.hannibal2.skyhanni.features.bingo.BingoNextStepHelper
import at.hannibal2.skyhanni.features.event.diana.BurrowWarpHelper
import at.hannibal2.skyhanni.features.misc.CollectionCounter
import at.hannibal2.skyhanni.features.misc.MarkedPlayerManager
@@ -40,6 +42,8 @@ object Commands {
registerCommand("shstoplisteners") { LorenzTest.stopListeners() }
registerCommand("shresetburrowwarps") { BurrowWarpHelper.resetDisabledWarps() }
registerCommand("shtrackcollection") { CollectionCounter.command(it) }
+ registerCommand("shreloadbingodata") { BingoCardDisplay.command() }
+ registerCommand("shprintbingohelper") { BingoNextStepHelper.command() }
}
private fun registerCommand(name: String, function: (Array<String>) -> Unit) {
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/Bingo.java b/src/main/java/at/hannibal2/skyhanni/config/features/Bingo.java
index 002841d22..3395dfb8e 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/features/Bingo.java
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/Bingo.java
@@ -1,6 +1,9 @@
package at.hannibal2.skyhanni.config.features;
+import at.hannibal2.skyhanni.config.core.config.Position;
+import at.hannibal2.skyhanni.config.core.config.annotations.ConfigAccordionId;
import at.hannibal2.skyhanni.config.core.config.annotations.ConfigEditorBoolean;
+import at.hannibal2.skyhanni.config.core.config.annotations.ConfigEditorButton;
import at.hannibal2.skyhanni.config.core.config.annotations.ConfigOption;
import com.google.gson.annotations.Expose;
@@ -11,4 +14,16 @@ public class Bingo {
"new area discoveries, and bestiarity upgrades while on bingo.")
@ConfigEditorBoolean
public boolean compactChatMessages = true;
+
+ @Expose
+ @ConfigOption(name = "Bingo Card", desc = "Show the bingo card.")
+ @ConfigEditorBoolean
+ @ConfigAccordionId(id = 0)
+ public boolean bingoCard = false;
+
+ @Expose
+ @ConfigOption(name = "Bingo Card Position", desc = "")
+ @ConfigEditorButton(runnableId = "bingoCardPos", buttonText = "Edit")
+ @ConfigAccordionId(id = 0)
+ public Position bingoCardPos = new Position(10, 10, false, true);
}
diff --git a/src/main/java/at/hannibal2/skyhanni/features/bingo/BingoCardDisplay.kt b/src/main/java/at/hannibal2/skyhanni/features/bingo/BingoCardDisplay.kt
new file mode 100644
index 000000000..ad1e138a8
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/bingo/BingoCardDisplay.kt
@@ -0,0 +1,181 @@
+package at.hannibal2.skyhanni.features.bingo
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.events.LorenzChatEvent
+import at.hannibal2.skyhanni.features.bingo.card.CommunityGoal
+import at.hannibal2.skyhanni.features.bingo.card.PersonalGoal
+import at.hannibal2.skyhanni.utils.InventoryUtils.getInventoryName
+import at.hannibal2.skyhanni.utils.ItemUtils
+import at.hannibal2.skyhanni.utils.ItemUtils.getLore
+import at.hannibal2.skyhanni.utils.ItemUtils.name
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.RenderUtils.renderStrings
+import at.hannibal2.skyhanni.utils.StringUtils.removeColor
+import net.minecraft.client.Minecraft
+import net.minecraft.client.gui.GuiChat
+import net.minecraft.client.gui.inventory.GuiChest
+import net.minecraft.inventory.ContainerChest
+import net.minecraftforge.client.event.RenderGameOverlayEvent
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import net.minecraftforge.fml.common.gameevent.TickEvent
+import java.util.regex.Pattern
+
+class BingoCardDisplay {
+
+ private val MAX_PERSONAL_GOALS = 20
+ private val MAX_COMMUNITY_GOALS = 5
+
+ private var tick = 0
+ private val display = mutableListOf<String>()
+
+ companion object {
+ val personalGoals = mutableListOf<PersonalGoal>()
+ private val communityGoals = mutableListOf<CommunityGoal>()
+
+ private var dirty = true
+
+ fun command() {
+ reload()
+ }
+
+ private fun reload() {
+ personalGoals.clear()
+ communityGoals.clear()
+ dirty = true
+ }
+ }
+
+ @SubscribeEvent
+ fun onTick(event: TickEvent.ClientTickEvent) {
+ if (!LorenzUtils.isBingoProfile) return
+ if (event.phase != TickEvent.Phase.START) return
+
+ tick++
+ if (tick % 5 != 0) return
+
+ val gui = Minecraft.getMinecraft().currentScreen
+ if (gui !is GuiChest) {
+ dirty = true
+ return
+ }
+
+ val chest = gui.inventorySlots as ContainerChest
+ if (chest.getInventoryName() == "Bingo Card") {
+ if (dirty) {
+ readBingoCard(gui)
+ }
+ }
+ }
+
+ private fun readBingoCard(gui: GuiChest) {
+ personalGoals.clear()
+ communityGoals.clear()
+
+ for (slot in gui.inventorySlots.inventorySlots) {
+ val stack = slot?.stack ?: continue
+ val personalGoal = stack.getLore().any { it.endsWith("Personal Goal") }
+ val communityGoal = stack.getLore().any { it.endsWith("Community Goal") }
+ if (!personalGoal && !communityGoal) continue
+ val name = stack.name?.removeColor() ?: continue
+ val lore = stack.getLore()
+ var index = 0
+ val builder = StringBuilder()
+ for (s in lore) {
+ if (index > 1) {
+ if (s == "") break
+ builder.append(s)
+ builder.append(" ")
+ }
+ index++
+ }
+ var description = builder.toString()
+ if (description.endsWith(" ")) {
+ description = description.substring(0, description.length - 1)
+ }
+ if (description.startsWith("§7§7")) {
+ description = description.substring(2)
+ }
+
+ val done = stack.getLore().any { it.contains("GOAL REACHED") }
+ if (personalGoal) {
+ personalGoals.add(PersonalGoal(name, description, done))
+ } else {
+ communityGoals.add(CommunityGoal(name, description, done))
+ }
+ }
+
+ val a = personalGoals.size
+ val b = communityGoals.size
+ if (a == MAX_PERSONAL_GOALS && b == MAX_COMMUNITY_GOALS) {
+ dirty = false
+
+ update()
+ }
+ }
+
+ private fun update() {
+ display.clear()
+
+ display.add("Community Goals")
+ communityGoals.mapTo(display) { " " + it.description + if (it.done) " §aDONE" else "" }
+
+ val todo = personalGoals.filter { !it.done }
+ val done = MAX_PERSONAL_GOALS - todo.size
+ display.add(" ")
+ display.add("Personal Goals: ($done/$MAX_PERSONAL_GOALS done)")
+ todo.mapTo(display) { " " + it.description }
+ }
+
+ private var lastSneak = false
+ private var showHelp = false
+
+ @SubscribeEvent
+ fun onRenderOverlay(event: RenderGameOverlayEvent.Post) {
+ if (event.type != RenderGameOverlayEvent.ElementType.ALL) return
+ if (!LorenzUtils.isBingoProfile) return
+ if (!SkyHanniMod.feature.bingo.bingoCard) return
+
+ val stack = Minecraft.getMinecraft().thePlayer.heldItem //TODO into ItemUtils or InventoryUtils
+ if (ItemUtils.isSkyBlockMenuItem(stack)) {
+ val sneaking = Minecraft.getMinecraft().thePlayer.isSneaking
+ if (lastSneak != sneaking) {
+ lastSneak = sneaking
+ if (sneaking) {
+ showHelp = !showHelp
+ }
+ }
+ }
+ if (showHelp) {
+ SkyHanniMod.feature.bingo.bingoCardPos.renderStrings(BingoNextStepHelper.currentHelp)
+ } else {
+ if (Minecraft.getMinecraft().currentScreen !is GuiChat) {
+ SkyHanniMod.feature.dev.debugPos.renderStrings(display)
+ }
+ }
+ }
+
+ @SubscribeEvent
+ fun onChat(event: LorenzChatEvent) {
+ if (!LorenzUtils.isBingoProfile) return
+
+ val message = event.message
+ //§6§lBINGO GOAL COMPLETE! §r§eRaw Salmon Collector
+ val pattern = Pattern.compile("§6§lBINGO GOAL COMPLETE! §r§e(.*)")
+
+ val matcher = pattern.matcher(message)
+
+ if (matcher.matches()) {
+ val name = matcher.group(0)
+ println("name: '$name'")
+ for (goal in personalGoals) {
+ println("goal: '" + goal.displayName + "'")
+ }
+ personalGoals
+ .filter { it.displayName == name }
+ .forEach {
+ it.done = true
+ update()
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/features/bingo/BingoNextStepHelper.kt b/src/main/java/at/hannibal2/skyhanni/features/bingo/BingoNextStepHelper.kt
new file mode 100644
index 000000000..ea04aa1e5
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/bingo/BingoNextStepHelper.kt
@@ -0,0 +1,343 @@
+package at.hannibal2.skyhanni.features.bingo
+
+import at.hannibal2.skyhanni.data.HyPixelData
+import at.hannibal2.skyhanni.data.IslandType
+import at.hannibal2.skyhanni.data.SkillExperience
+import at.hannibal2.skyhanni.events.LorenzChatEvent
+import at.hannibal2.skyhanni.events.ProfileApiDataLoadedEvent
+import at.hannibal2.skyhanni.features.bingo.nextstep.*
+import at.hannibal2.skyhanni.utils.InventoryUtils
+import at.hannibal2.skyhanni.utils.ItemUtils.name
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.StringUtils.removeColor
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import net.minecraftforge.fml.common.gameevent.TickEvent
+import java.util.regex.Pattern
+
+class BingoNextStepHelper {
+ private var tick = 0
+ private var dirty = true
+
+ private val itemIslandRequired = mutableMapOf<String, IslandVisitStep>()
+ private val itemRequired = mutableMapOf<String, NextStep>()
+ private val islands = mutableMapOf<IslandType, IslandVisitStep>()
+
+ companion object {
+ private val finalSteps = mutableListOf<NextStep>()
+ private val currentSteps = mutableListOf<NextStep>()
+ val currentHelp = mutableListOf<String>()
+
+ fun command() {
+ updateResult(true)
+ }
+
+ private fun updateResult(print: Boolean = false) {
+ if (print) println()
+ currentSteps.clear()
+ for (step in finalSteps) {
+ printRequirements(step, print)
+ if (print) println()
+ }
+
+ currentHelp.clear()
+ currentHelp.add("Bingo Helper:")
+ for (currentStep in currentSteps) {
+ val text = getName(currentStep)
+ currentHelp.add(" §7$text")
+ if (print) println(text)
+ }
+ if (print) println()
+ }
+
+ private fun printRequirements(step: NextStep, print: Boolean, parentDone: Boolean = false, depth: Int = 0) {
+ if (print) println(getName(step, parentDone, depth))
+ var requirementsToDo = 0
+ for (requirement in step.requirements) {
+ printRequirements(requirement, print, step.done || parentDone, depth + 1)
+ if (!requirement.done) {
+ requirementsToDo++
+ }
+ }
+
+ if (!step.done && !parentDone) {
+ if (requirementsToDo == 0) {
+ if (!currentSteps.contains(step)) {
+ currentSteps.add(step)
+ }
+ }
+ }
+ }
+
+ private fun getName(step: NextStep, parentDone: Boolean = false, depth: Int = 0): String {
+ val prefix = " ".repeat(depth) + if (step.done) "[DONE] " else if (parentDone) "[done] " else ""
+ val suffix = if (step is ProgressionStep) progressDisplay(step) else ""
+ return prefix + step.displayName + suffix
+ }
+
+ private fun progressDisplay(step: ProgressionStep): String {
+ val having = step.amountHaving
+ return if (having > 0) {
+ val needed = step.amountNeeded
+ val percentage = LorenzUtils.formatPercentage(having.toDouble() / needed)
+ val havingFormat = LorenzUtils.formatInteger(having)
+ val neededFormat = LorenzUtils.formatInteger(needed)
+ " $percentage ($havingFormat/$neededFormat)"
+ } else ""
+ }
+ }
+
+ init {
+ reset()
+ }
+
+ @SubscribeEvent
+ fun onTick(event: TickEvent.ClientTickEvent) {
+ if (!LorenzUtils.isBingoProfile) return
+ if (event.phase != TickEvent.Phase.START) return
+
+ tick++
+ if (tick % 20 == 0) {
+ update()
+ updateIslandsVisited()
+ }
+ if (tick % 5 == 0) {
+ updateCurrentSteps()
+ }
+ }
+
+ @SubscribeEvent
+ fun onChat(event: LorenzChatEvent) {
+ if (!HyPixelData.skyBlock) return
+
+ //TODO add thys message
+// if (event.message == "thys message") {
+// thys.done()
+// }
+ }
+
+ private fun updateCurrentSteps() {
+ for (step in currentSteps.toMutableList()) {
+ if (step is ItemsStep) {
+ var totalCount = 0L
+ for ((itemName, multiplier) in step.variants) {
+ val count = InventoryUtils.countItemsInLowerInventory { it.name?.removeColor() == itemName }
+ totalCount += count * multiplier
+ }
+ if (step.amountHaving != totalCount) {
+ step.amountHaving = totalCount
+ if (totalCount >= step.amountNeeded) {
+ step.done()
+ }
+ updateResult()
+ }
+ }
+ if (step is SkillLevelStep) {
+ val expForSkill = SkillExperience.getExpForSkill(step.skillName.lowercase())
+ if (step.amountHaving != expForSkill) {
+ step.amountHaving = expForSkill
+ if (expForSkill >= step.amountNeeded) {
+ step.done()
+ }
+ updateResult()
+ }
+ }
+ }
+ }
+
+ private fun NextStep.done(silent: Boolean = false) {
+ if (done) return
+ done = true
+ updateResult()
+ if (!silent) {
+ LorenzUtils.chat("§e[SkyHanni] A bingo goal step is done! ($displayName)")
+ }
+ }
+
+ private fun updateIslandsVisited() {
+ for (step in islands.values) {
+ val island = step.island
+ if (island == LorenzUtils.skyBlockIsland) {
+ step.done()
+ }
+ }
+ }
+
+ private fun update() {
+ val personalGoals = BingoCardDisplay.personalGoals
+ if (personalGoals.isEmpty()) {
+ if (!dirty) {
+ reset()
+ dirty = true
+ }
+ return
+ }
+
+ if (!dirty) return
+ dirty = false
+
+ for (goal in personalGoals) {
+ val description = goal.description.removeColor()
+
+ val pattern = Pattern.compile("Reach ([0-9]+(?:,\\d+)*) (.*) Collection\\.")
+ val matcher = pattern.matcher(description)
+ if (matcher.matches()) {
+ val amount = matcher.group(1).replace(",", "").toInt()
+ val name = matcher.group(2)
+
+ val collectionStep = CollectionStep(name, amount).apply { finalSteps.add(this) }
+ createItemIslandRequirement(name, collectionStep)
+ continue
+ }
+ if (description == "Craft an Emerald Ring.") {
+ CraftStep("Emerald Ring").apply { finalSteps.add(this) } requires ItemsStep(
+ "32x Enchanted Emerald",
+ "Emerald",
+ 160 * 32,
+ mapOf("Emerald" to 1, "Enchanted Emerald" to 160)
+ ).apply { this requires IslandType.DWARVEN_MINES.getStep() }
+ }
+ if (description == "Obtain a Mathematical Hoe Blueprint.") {
+ CraftStep("Mathematical Hoe Blueprint").apply { finalSteps.add(this) } requires ItemsStep(
+ "32x Jacob's Ticket",
+ "Jacob's Ticket",
+ 32,
+ mapOf("Jacob's Ticket" to 1)
+ ).apply { this requires IslandType.HUB.getStep() }.addItemRequirements()
+ }
+
+ println("No help for goal: '$description'")
+ }
+
+ updateResult()
+ }
+
+ @SubscribeEvent
+ fun onProfileDataLoad(event: ProfileApiDataLoadedEvent) {
+ val profileData = event.profileData
+
+ val visitedZones = profileData["visited_zones"]?.asJsonArray ?: return
+ for (element in visitedZones) {
+ val zoneName = element.asString
+ for (step in islands.values) {
+ val island = step.island
+ if (island.apiName == zoneName) {
+ step.done(true)
+ }
+ }
+ }
+ }
+
+ private fun createItemIslandRequirement(itemName: String, step: NextStep): IslandVisitStep? {
+ val islandReachStep = itemIslandRequired.getOrDefault(itemName, null)
+ if (islandReachStep == null) {
+ println("no island required for item: '$itemName'")
+ return null
+ }
+ step requires islandReachStep
+ return islandReachStep
+ }
+
+ private infix fun NextStep.requires(other: NextStep) {
+ requirements.add(other)
+ }
+
+ private fun IslandType.getStep() = islands.getOrPut(this) { IslandVisitStep(this) }
+
+ private fun reset() {
+ islands.clear()
+ finalSteps.clear()
+
+ itemIslandRequired["Acacia Wood"] = IslandType.THE_PARK.getStep()
+ itemIslandRequired["Redstone"] = IslandType.DEEP_CAVERNS.getStep()
+ itemIslandRequired["Slimeball"] = IslandType.DEEP_CAVERNS.getStep()
+ itemIslandRequired["Emerald"] = IslandType.DEEP_CAVERNS.getStep()
+ itemIslandRequired["Mithril"] = IslandType.DEEP_CAVERNS.getStep()
+
+ IslandType.GOLD_MINES.getStep() requires IslandType.HUB.getStep()
+ IslandType.GOLD_MINES.getStep() requires SkillLevelStep("Mining", 1)
+ IslandType.DEEP_CAVERNS.getStep() requires IslandType.GOLD_MINES.getStep()
+
+ IslandType.DEEP_CAVERNS.getStep() requires SkillLevelStep("Mining", 5)
+
+ val redstoneForThys = ItemsStep(
+ "30x Enchanted Redstone (for Thys)",
+ "Redstone",
+ 160 * 10 * 3,
+ mapOf("Redstone" to 1, "Enchanted Redstone" to 160)
+ ).apply { createItemIslandRequirement(itemName, this) }
+ redstoneForThys requires IslandType.DEEP_CAVERNS.getStep()
+ IslandType.DWARVEN_MINES.getStep() requires redstoneForThys
+ IslandType.DWARVEN_MINES.getStep() requires SkillLevelStep("Mining", 12)
+ IslandType.CRYSTAL_HOLLOWS.getStep() requires IslandType.DWARVEN_MINES.getStep()
+
+ val farmingContest = ChatMessageStep("Farming Contest")
+ farmingContest requires SkillLevelStep("Farming", 10)
+ itemRequired["Jacob's Ticket"] = farmingContest
+
+
+ enchantedCharcoal(7)
+ compactor(7)
+ }
+
+ private fun compactor(amount: Long) {
+ val compactorForMinions = ItemsStep(
+ "Compactor (for Minions)",
+ "Compactor",
+ amount,
+ mapOf("Compactor" to 1)
+ ).apply { finalSteps.add(this) }
+
+ compactorForMinions requires CollectionStep(
+ "Cobblestone",
+ 2_500
+ ).apply { this requires IslandType.HUB.getStep() }
+
+ compactorForMinions requires ItemsStep(
+ "" + (7 * amount) + " Enchanted Cobblestone (For Minions)",
+ "Enchanted Cobblestone",
+ amount * 7 * 160,
+ mapOf("Cobblestone" to 1, "Enchanted Cobblestone" to 160)
+ )
+ compactorForMinions requires ItemsStep(
+ "$amount Enchanted Redstone (For Minions)",
+ "Enchanted Redstone",
+ amount * 160,
+ mapOf("Redstone" to 1, "Enchanted Redstone" to 160)
+ )
+ }
+
+ private fun enchantedCharcoal(amount: Long) {
+ val enchantedCharcoalForMinions = ItemsStep(
+ "Enchanted Charcoal (for Minions)",
+ "Enchanted Charcoal",
+ amount,
+ mapOf("Enchanted Charcoal" to 1)
+ ).apply { finalSteps.add(this) }
+
+ enchantedCharcoalForMinions requires CollectionStep(
+ "Coal",
+ 2_500
+ ).apply { this requires IslandType.GOLD_MINES.getStep() }
+
+ enchantedCharcoalForMinions requires ItemsStep(
+ "Oak Wood (For Minions)",
+ "Oak Wood",
+ amount * 32,
+ mapOf("Oak Wood" to 1)
+ )
+ enchantedCharcoalForMinions requires ItemsStep(
+ "Coal (For Minions)",
+ "Coal",
+ amount * 64 * 2,
+ mapOf("Coal" to 1)
+ )
+ }
+
+ private fun ItemsStep.addItemRequirements(): ItemsStep {
+ val step = itemRequired[itemName]
+ if (step != null) {
+ requirements.add(step)
+ }
+ return this
+ }
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/features/bingo/card/BingoGoals.kt b/src/main/java/at/hannibal2/skyhanni/features/bingo/card/BingoGoals.kt
new file mode 100644
index 000000000..dbcb5bf35
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/bingo/card/BingoGoals.kt
@@ -0,0 +1,4 @@
+package at.hannibal2.skyhanni.features.bingo.card
+
+
+abstract class BingoGoals(val displayName: String, val description: String, var done: Boolean) \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/features/bingo/card/CommunityGoal.kt b/src/main/java/at/hannibal2/skyhanni/features/bingo/card/CommunityGoal.kt
new file mode 100644
index 000000000..63e13eadc
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/bingo/card/CommunityGoal.kt
@@ -0,0 +1,4 @@
+package at.hannibal2.skyhanni.features.bingo.card
+
+class CommunityGoal(displayName: String, description: String, done: Boolean) :
+ BingoGoals(displayName, description, done) \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/features/bingo/card/PersonalGoal.kt b/src/main/java/at/hannibal2/skyhanni/features/bingo/card/PersonalGoal.kt
new file mode 100644
index 000000000..981bb1a20
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/bingo/card/PersonalGoal.kt
@@ -0,0 +1,3 @@
+package at.hannibal2.skyhanni.features.bingo.card
+
+class PersonalGoal(displayName: String, description: String, done: Boolean) : BingoGoals(displayName, description, done) \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/ChatMessageStep.kt b/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/ChatMessageStep.kt
new file mode 100644
index 000000000..e1d548540
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/ChatMessageStep.kt
@@ -0,0 +1,3 @@
+package at.hannibal2.skyhanni.features.bingo.nextstep
+
+class ChatMessageStep(displayName: String): NextStep(displayName) \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/CollectionStep.kt b/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/CollectionStep.kt
new file mode 100644
index 000000000..0ba7dbe8e
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/CollectionStep.kt
@@ -0,0 +1,6 @@
+package at.hannibal2.skyhanni.features.bingo.nextstep
+
+import at.hannibal2.skyhanni.utils.NumberUtil
+
+class CollectionStep(val itemName: String, amountNeeded: Int) :
+ NextStep(NumberUtil.format(amountNeeded) + " $itemName Collection") \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/CraftStep.kt b/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/CraftStep.kt
new file mode 100644
index 000000000..5b7890072
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/CraftStep.kt
@@ -0,0 +1,3 @@
+package at.hannibal2.skyhanni.features.bingo.nextstep
+
+class CraftStep(val itemName: String): NextStep("Craft $itemName") \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/IslandVisitStep.kt b/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/IslandVisitStep.kt
new file mode 100644
index 000000000..c25c440bc
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/IslandVisitStep.kt
@@ -0,0 +1,5 @@
+package at.hannibal2.skyhanni.features.bingo.nextstep
+
+import at.hannibal2.skyhanni.data.IslandType
+
+class IslandVisitStep(val island: IslandType) : NextStep("Visit ${island.displayName}") \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/ItemsStep.kt b/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/ItemsStep.kt
new file mode 100644
index 000000000..e4efa3faa
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/ItemsStep.kt
@@ -0,0 +1,4 @@
+package at.hannibal2.skyhanni.features.bingo.nextstep
+
+class ItemsStep(displayName: String, val itemName: String, amountNeeded: Long, val variants: Map<String, Int>) :
+ ProgressionStep(displayName, amountNeeded) \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/NextStep.kt b/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/NextStep.kt
new file mode 100644
index 000000000..d5077478e
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/NextStep.kt
@@ -0,0 +1,7 @@
+package at.hannibal2.skyhanni.features.bingo.nextstep
+
+abstract class NextStep(
+ val displayName: String,
+ var done: Boolean = false,
+ val requirements: MutableList<NextStep> = mutableListOf()
+)
diff --git a/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/ProgressionStep.kt b/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/ProgressionStep.kt
new file mode 100644
index 000000000..07aaf5f20
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/ProgressionStep.kt
@@ -0,0 +1,3 @@
+package at.hannibal2.skyhanni.features.bingo.nextstep
+
+abstract class ProgressionStep(displayName: String, val amountNeeded: Long, var amountHaving: Long = 0): NextStep(displayName) \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/SkillLevelStep.kt b/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/SkillLevelStep.kt
new file mode 100644
index 000000000..d9ac1bbef
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/bingo/nextstep/SkillLevelStep.kt
@@ -0,0 +1,10 @@
+package at.hannibal2.skyhanni.features.bingo.nextstep
+
+import at.hannibal2.skyhanni.data.SkillExperience
+
+class SkillLevelStep(
+ val skillName: String,
+ val skillLevelNeeded: Int,
+ skillExpNeeded: Long = SkillExperience.getExpForLevel(skillLevelNeeded)
+) :
+ ProgressionStep("$skillName $skillLevelNeeded", skillExpNeeded) \ No newline at end of file