Skip to content

Commit

Permalink
Feature: Stray Rabbit Tracker (#2210)
Browse files Browse the repository at this point in the history
Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com>
  • Loading branch information
DavidArthurCole and hannibal002 authored Jul 30, 2024
1 parent bacc6a3 commit 47c0590
Show file tree
Hide file tree
Showing 4 changed files with 334 additions and 0 deletions.
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
}

0 comments on commit 47c0590

Please sign in to comment.