Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Stray Rabbit Tracker #2210

Merged
merged 21 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import at.hannibal2.skyhanni.features.garden.fortuneguide.FFGuideGUI
import at.hannibal2.skyhanni.features.garden.pests.PestFinder
import at.hannibal2.skyhanni.features.garden.pests.PestProfitTracker
import at.hannibal2.skyhanni.features.garden.visitor.GardenVisitorDropStatistics
import at.hannibal2.skyhanni.features.inventory.chocolatefactory.ChocolateFactoryStrayTracker
import at.hannibal2.skyhanni.features.mining.KingTalismanHelper
import at.hannibal2.skyhanni.features.mining.MineshaftPityDisplay
import at.hannibal2.skyhanni.features.mining.powdertracker.PowderTracker
Expand Down Expand Up @@ -272,6 +273,10 @@ object Commands {
"shresetseacreaturetracker",
"Resets the Sea Creature Tracker",
) { SeaCreatureTracker.resetCommand() }
registerCommand(
"shresetstrayrabbittracker",
"Resets the Stray Rabbit Tracker",
) { ChocolateFactoryStrayTracker.resetCommand() }
registerCommand(
"shfandomwiki",
"Searches the fandom wiki with SkyHanni's own method.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,4 +221,14 @@ public class ChocolateFactoryConfig {
@ConfigEditorBoolean
@FeatureToggle
public boolean mythicRabbitRequirement = false;

@Expose
@ConfigOption(name = "Stray Tracker", desc = "Track stray rabbits found in the Chocolate Factory menu.")
@ConfigEditorBoolean
@FeatureToggle
public boolean strayRabbitTracker = true;

@Expose
@ConfigLink(owner = ChocolateFactoryConfig.class, field = "strayRabbitTracker")
public Position strayRabbitTrackerPosition = new Position(300, 300, false, true);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import at.hannibal2.skyhanni.features.garden.pests.PestProfitTracker;
import at.hannibal2.skyhanni.features.garden.pests.VinylType;
import at.hannibal2.skyhanni.features.garden.visitor.VisitorReward;
import at.hannibal2.skyhanni.features.inventory.chocolatefactory.ChocolateFactoryStrayTracker;
import at.hannibal2.skyhanni.features.inventory.chocolatefactory.ChocolateFactoryUpgrade;
import at.hannibal2.skyhanni.features.inventory.wardrobe.WardrobeAPI;
import at.hannibal2.skyhanni.features.mining.MineshaftPityDisplay;
Expand Down Expand Up @@ -151,6 +152,9 @@ public static class PositionChange {

@Expose
public Integer hoppityShopYearOpened = null;

@Expose
public ChocolateFactoryStrayTracker.Data strayTracker = new ChocolateFactoryStrayTracker.Data();
}

@Expose
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
package at.hannibal2.skyhanni.features.inventory.chocolatefactory

import at.hannibal2.skyhanni.events.GuiContainerEvent
import at.hannibal2.skyhanni.events.GuiRenderEvent
import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent
import at.hannibal2.skyhanni.events.SecondPassedEvent
import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule
import at.hannibal2.skyhanni.utils.CollectionUtils.addAsSingletonList
import at.hannibal2.skyhanni.utils.CollectionUtils.addOrPut
import at.hannibal2.skyhanni.utils.DelayedRun
import at.hannibal2.skyhanni.utils.InventoryUtils
import at.hannibal2.skyhanni.utils.ItemUtils.getLore
import at.hannibal2.skyhanni.utils.ItemUtils.itemName
import at.hannibal2.skyhanni.utils.ItemUtils.name
import at.hannibal2.skyhanni.utils.LorenzUtils
import at.hannibal2.skyhanni.utils.NumberUtil.formatLong
import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher
import at.hannibal2.skyhanni.utils.RegexUtils.matches
import at.hannibal2.skyhanni.utils.TimeUtils.format
import at.hannibal2.skyhanni.utils.renderables.Renderable
import at.hannibal2.skyhanni.utils.tracker.SkyHanniTracker
import at.hannibal2.skyhanni.utils.tracker.TrackerData
import com.google.gson.annotations.Expose
import net.minecraftforge.fml.common.eventhandler.EventPriority
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds

@SkyHanniModule
object ChocolateFactoryStrayTracker {

private val config get() = ChocolateFactoryAPI.config
private var claimedStraysSlots = mutableListOf<Int>()

/**
* REGEX-TEST: §9Zero §d§lCAUGHT!
* REGEX-TEST: §6§lGolden Rabbit §d§lCAUGHT!
* REGEX-TEST: §fAudi §d§lCAUGHT!
*/
private val strayCaughtPattern by ChocolateFactoryAPI.patternGroup.pattern(
"stray.caught",
"^§[a-f0-9].* §d§lCAUGHT!",
)

/**
* REGEX-TEST: §7You caught a stray §fMandy §7and §7gained §6+283,574 Chocolate§7!
* REGEX-TEST: §7You caught a stray §aSven §7and gained §7§6+397,004 Chocolate§7!'
*/
private val strayLorePattern by ChocolateFactoryAPI.patternGroup.pattern(
"stray.loreinfo",
"§7You caught a stray (?<rabbit>(?<name>§[^§]*)) .* (?:§7)?§6\\+(?<amount>[\\d,]*) Chocolate§7!",
)

/**
* REGEX-TEST: §7You caught a stray §6§lGolden Rabbit§7! §7You gained §6+13,566,571 Chocolate§7!
*/
private val goldenStrayJackpotMountainPattern by ChocolateFactoryAPI.patternGroup.pattern(
"stray.goldenrawchoc",
"§7You caught a stray §6§lGolden Rabbit§7! §7You gained §6\\+(?<amount>[\\d,]*) Chocolate§7!",
)

/**
* REGEX-TEST: §7You caught a stray §6§lGolden Rabbit§7! §7You caught a glimpse of §6El Dorado§7, §7but he escaped and left behind §7§6313,780 Chocolate§7!
*/
private val goldenStrayDoradoEscape by ChocolateFactoryAPI.patternGroup.pattern(
"stray.goldendoradoescape",
"§7You caught a stray §6§lGolden Rabbit§7! §7You caught a glimpse of §6El Dorado§7, §7but he escaped and left behind §7§6\\+(?<amount>[\\d,]*) Chocolate§7!",
)

/**
* REGEX-TEST: §7You caught a stray §6§lGolden Rabbit§7! §7You caught §6El Dorado §7- quite the elusive rabbit!
*/
private val goldenStrayDoradoCaught by ChocolateFactoryAPI.patternGroup.pattern(
"stray.goldendoradocaught",
"§7You caught a stray §6§lGolden Rabbit§7! §7You caught §6El Dorado §7- quite the elusive rabbit!",
)

/**
* REGEX-TEST: §7You caught a stray §6§lGolden Rabbit§7! §7You caught §6El Dorado§7! Since you §7already have captured him before, §7you gained §6+324,364,585 Chocolate§7.
*/
private val goldenStrayDoradoDuplicate by ChocolateFactoryAPI.patternGroup.pattern(
"stray.goldendoradoduplicate",
"§7You caught a stray §6§lGolden Rabbit§7! §7You caught §6El Dorado§7! Since you §7already have captured him before, §7you gained §6\\+(?<amount>[\\d,]*) Chocolate§7.",
)

/**
* REGEX-TEST: §7You caught a stray §6§lGolden Rabbit§7! §7You gained §6+5 Chocolate §7until the §7end of the SkyBlock year!
*
*/
private val goldenStrayClick by ChocolateFactoryAPI.patternGroup.pattern(
"stray.goldenclick",
"§7You caught a stray §6§lGolden Rabbit§7! §7You gained §6\\+5 Chocolate §7until the §7end of the SkyBlock year!",
)

/**
* REGEX-TEST: §7You caught a stray §9Fish the Rabbit§7! §7You have already found §9Fish the §9Rabbit§7, so you received §655,935,257 §6Chocolate§7!
*/
private val fishTheRabbitPattern by ChocolateFactoryAPI.patternGroup.pattern(
"stray.fish",
"§7You caught a stray (?<color>§.)Fish the Rabbit§7! §7You have already found (?:§.)?Fish the (?:§.)?Rabbit§7, so you received §6(?<amount>[\\d,]*) (?:§6)?Chocolate§7!",
)

private val rarityFormatMap = mapOf(
"common" to "§f",
"uncommon" to "§a",
"rare" to "§9",
"epic" to "§5",
"legendary" to "§6",
)

private val tracker = SkyHanniTracker("Stray Tracker", { Data() }, { it.chocolateFactory.strayTracker })
{ drawDisplay(it) }

class Data : TrackerData() {
override fun reset() {
straysCaught.clear()
straysExtraChocMs.clear()
goldenTypesCaught.clear()
}

@Expose
var straysCaught: MutableMap<String, Int> = mutableMapOf()

@Expose
var straysExtraChocMs: MutableMap<String, Long> = mutableMapOf()

@Expose
var goldenTypesCaught: MutableMap<String, Int> = mutableMapOf()
}

private fun formLoreToSingleLine(lore: List<String>): String {
val notEmptyLines = lore.filter { it.isNotEmpty() }
return notEmptyLines.joinToString(" ")
}

private fun incrementRarity(rarity: String, chocAmount: Long = 0) {
tracker.modify { it.straysCaught.addOrPut(rarity, 1) }
val extraTime = ChocolateFactoryAPI.timeUntilNeed(chocAmount + 1)
tracker.modify { it.straysExtraChocMs.addOrPut(rarity, extraTime.inWholeMilliseconds) }
}

private fun incrementGoldenType(typeCaught: String, amount: Int = 1) {
tracker.modify { it.goldenTypesCaught.addOrPut(typeCaught, amount) }
}

private fun drawDisplay(data: Data): List<List<Any>> = buildList {
val extraChocMs = data.straysExtraChocMs.values.sum().milliseconds
val formattedExtraTime = extraChocMs.let { if (it == 0.milliseconds) "0s" else it.format() }

addAsSingletonList(
Renderable.hoverTips(
"§6§lStray Tracker",
tips = listOf("§a+§b${formattedExtraTime} §afrom strays§7"),
),
)
rarityFormatMap.keys.forEach { rarity ->
extractHoverableOfRarity(rarity, data)?.let { addAsSingletonList(it) }
}
}

private fun extractHoverableOfRarity(rarity: String, data: Data): Renderable? {
val caughtOfRarity = data.straysCaught[rarity]
val caughtString = caughtOfRarity?.toString() ?: return null

val rarityExtraChocMs = data.straysExtraChocMs[rarity]?.milliseconds
val extraChocFormat = rarityExtraChocMs?.format() ?: ""

val colorCode = rarityFormatMap[rarity] ?: ""
val lineHeader = "$colorCode${rarity.substring(0, 1).uppercase()}${rarity.substring(1)}§7: §r$colorCode"
val lineFormat = "${lineHeader}${caughtString}"

return rarityExtraChocMs?.let {
val tip =
"§a+§b$extraChocFormat §afrom $colorCode$rarity strays§7${if (rarity == "legendary") extractGoldenTypesCaught(data) else ""}"
Renderable.hoverTips(Renderable.string(lineFormat), tips = tip.split("\n"))
} ?: Renderable.string(lineFormat)
}

private fun extractGoldenTypesCaught(data: Data): String {
val goldenList = mutableListOf<String>()
data.goldenTypesCaught["sidedish"]?.let {
goldenList.add("§b$it §6Side Dish" + if (it > 1) "es" else "")
}
data.goldenTypesCaught["jackpot"]?.let {
goldenList.add("§b$it §6Chocolate Jackpot" + if (it > 1) "s" else "")
}
data.goldenTypesCaught["mountain"]?.let {
goldenList.add("§b$it §6Chocolate Mountain" + if (it > 1) "s" else "")
}
data.goldenTypesCaught["dorado"]?.let {
goldenList.add((if (it >= 3) "§a" else "§b") + "$it§7/§a3 §6El Dorado §7Sighting" + if (it > 1) "s" else "")
}
data.goldenTypesCaught["stampede"]?.let {
goldenList.add("§b$it §6Stampede" + if (it > 1) "s" else "")
}
data.goldenTypesCaught["goldenclick"]?.let {
goldenList.add("§b$it §6Golden Click" + if (it > 1) "s" else "")
}
return if (goldenList.size == 0) "" else ("\n" + goldenList.joinToString("\n"))
}

@SubscribeEvent
fun onTick(event: SecondPassedEvent) {
if (!isEnabled()) return
InventoryUtils.getItemsInOpenChest().filter {
!claimedStraysSlots.contains(it.slotIndex)
}.forEach {
strayCaughtPattern.matchMatcher(it.stack.name) {
if (it.stack.getLore().isEmpty()) return
claimedStraysSlots.add(it.slotIndex)
val loreLine = formLoreToSingleLine(it.stack.getLore())

// "Base" strays - Common -> Epic, raw choc only reward.
strayLorePattern.matchMatcher(loreLine) {
//Pretty sure base strays max at Epic, but...
val rarity = rarityFormatMap.entries.find { e -> e.value == group("rabbit").substring(0, 2) }?.key ?: "common"
incrementRarity(rarity, group("amount").formatLong())
}

// Fish the Rabbit
fishTheRabbitPattern.matchMatcher(loreLine) {
//Also fairly sure that Fish maxes out at Rare, but...
val rarity = rarityFormatMap.entries.find { e -> e.value == group("color").substring(0, 2) }?.key ?: "common"
incrementRarity(rarity, group("amount").formatLong())
}

// Golden Strays, Jackpot and Mountain, raw choc only reward.
goldenStrayJackpotMountainPattern.matchMatcher(loreLine) {
val amount = group("amount").formatLong().also { am -> incrementRarity("legendary", am) }
val multiplier = amount / ChocolateFactoryAPI.chocolatePerSecond
when (multiplier) {
in 479.0..481.0 -> incrementGoldenType("jackpot")
in 1499.0..1501.0 -> incrementGoldenType("mountain")
}
}

// Golden Strays, "Golden Click"
goldenStrayClick.matchMatcher(loreLine) {
incrementGoldenType("goldenclick")
}

// Golden Strays, hoard/stampede
if (loreLine == "§7You caught a stray §6§lGolden Rabbit§7! §7A hoard of §aStray Rabbits §7has appeared!") {
incrementGoldenType("stampede")
}

// Golden Strays, El Dorado "glimpse" - 1/3 before capture
goldenStrayDoradoEscape.matchMatcher(loreLine) {
incrementRarity("legendary", group("amount").formatLong())
incrementGoldenType("dorado")
}

// Golden Strays, El Dorado caught - 3/3
if (goldenStrayDoradoCaught.matches(loreLine)) {
incrementRarity("legendary")
tracker.modify { t -> t.goldenTypesCaught["dorado"] = 3 }
}

// Golden Strays, El Dorado (duplicate catch)
goldenStrayDoradoDuplicate.matchMatcher(loreLine) {
incrementRarity("legendary", group("amount").formatLong())
tracker.modify { t ->
t.goldenTypesCaught["dorado"] = t.goldenTypesCaught["dorado"]?.plus(1) ?: 4
}
}
}
}
InventoryUtils.getItemsInOpenChest().filter {
claimedStraysSlots.contains(it.slotIndex)
}.forEach {
if (!strayCaughtPattern.matches(it.stack.name)) {
claimedStraysSlots.removeAt(claimedStraysSlots.indexOf(it.slotIndex))
}
}
}

@SubscribeEvent(priority = EventPriority.HIGH)
fun onSlotClick(event: GuiContainerEvent.SlotClickEvent) {
if (!isEnabled()) return
val index = event.slot?.slotIndex ?: return
if (index == -999) return
if (claimedStraysSlots.contains(index)) return

val clickedStack = InventoryUtils.getItemsInOpenChest()
.find { it.slotNumber == event.slot.slotNumber && it.hasStack }
?.stack ?: return
val nameText = (if (clickedStack.hasDisplayName()) clickedStack.displayName else clickedStack.itemName)
if (!nameText.equals("§6§lGolden Rabbit §8- §aSide Dish")) return

claimedStraysSlots.add(index)
incrementGoldenType("sidedish")
incrementRarity("legendary", 0)
DelayedRun.runDelayed(1.seconds) {
claimedStraysSlots.remove(claimedStraysSlots.indexOf(index))
}
}

@SubscribeEvent
fun onBackgroundDraw(event: GuiRenderEvent.ChestGuiOverlayRenderEvent) {
if (!isEnabled()) return
tracker.renderDisplay(config.strayRabbitTrackerPosition)
}

@SubscribeEvent
fun onInventoryOpen(event: InventoryFullyOpenedEvent) {
if (!isEnabled()) return
tracker.firstUpdate()
}

fun resetCommand() {
tracker.resetCommand()
}

private fun isEnabled() = LorenzUtils.inSkyBlock && config.strayRabbitTracker && ChocolateFactoryAPI.inChocolateFactory
}
Loading