Skip to content

Commit

Permalink
Fix: Strays & Stray Timer (#2994)
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidArthurCole authored Nov 28, 2024
1 parent 96a4fae commit dc42e2d
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import at.hannibal2.skyhanni.SkyHanniMod
import at.hannibal2.skyhanni.api.event.HandleEvent
import at.hannibal2.skyhanni.api.event.HandleEvent.Companion.HIGHEST
import at.hannibal2.skyhanni.events.GuiContainerEvent
import at.hannibal2.skyhanni.events.InventoryCloseEvent
import at.hannibal2.skyhanni.events.InventoryUpdatedEvent
import at.hannibal2.skyhanni.events.LorenzChatEvent
import at.hannibal2.skyhanni.events.SecondPassedEvent
import at.hannibal2.skyhanni.events.hoppity.EggFoundEvent
import at.hannibal2.skyhanni.events.hoppity.RabbitFoundEvent
import at.hannibal2.skyhanni.features.event.hoppity.HoppityEggType.BOUGHT
Expand All @@ -23,11 +24,8 @@ import at.hannibal2.skyhanni.features.inventory.chocolatefactory.ChocolateFactor
import at.hannibal2.skyhanni.features.inventory.chocolatefactory.ChocolateFactoryStrayTracker.duplicatePseudoStrayPattern
import at.hannibal2.skyhanni.features.inventory.chocolatefactory.ChocolateFactoryStrayTracker.formLoreToSingleLine
import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule
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.LorenzRarity
import at.hannibal2.skyhanni.utils.LorenzRarity.DIVINE
import at.hannibal2.skyhanni.utils.LorenzRarity.LEGENDARY
Expand All @@ -46,11 +44,11 @@ import net.minecraft.init.Blocks
import net.minecraft.init.Items
import net.minecraft.inventory.Slot
import net.minecraft.item.Item
import net.minecraft.item.ItemStack
import net.minecraftforge.fml.common.eventhandler.EventPriority
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.full.memberProperties
import kotlin.time.Duration.Companion.seconds

@SkyHanniModule
object HoppityAPI {
Expand Down Expand Up @@ -104,13 +102,10 @@ object HoppityAPI {
* REGEX-TEST: Chocolate Factory
* REGEX-TEST: Chocolate Shop Milestones
* REGEX-TEST: Chocolate Factory Milestones
* REGEX-TEST: Chocolate Breakfast Egg
* REGEX-TEST: Chocolate Lunch Egg
* REGEX-TEST: Chocolate Dinner Egg
*/
private val miscProcessInvPattern by ChocolateFactoryAPI.patternGroup.pattern(
"inventory.misc",
"(?:§.)*Chocolate (?:Shop |(?:Factory|Breakfast|Lunch|Dinner) ?)(?:Milestones|Egg)?",
"(?:§.)*Chocolate (?:Shop |Factory ?)(?:Milestones)?",
)

/**
Expand Down Expand Up @@ -141,13 +136,12 @@ object HoppityAPI {
}
}

private val hoppityDataSet = HoppityStateDataSet()
private val processedStraySlots = mutableListOf<Int>()
private val straySlotIterations = mutableMapOf<Int, Int>()
private val S_GLASS_PANE_ITEM by lazy { Item.getItemFromBlock(Blocks.stained_glass_pane) }
private val CHEST_ITEM by lazy { Item.getItemFromBlock(Blocks.chest) }

val hoppityRarities by lazy { LorenzRarity.entries.filter { it <= DIVINE } }
private val hoppityDataSet = HoppityStateDataSet()
private val processedStraySlots = mutableMapOf<Int, String>()
private val miscProcessableItemTypes by lazy {
listOf(Items.skull, Item.getItemFromBlock(Blocks.stained_glass_pane))
}

fun isHoppityEvent() = (SkyblockSeason.currentSeason == SkyblockSeason.SPRING || SkyHanniMod.feature.dev.debug.alwaysHoppitys)
fun getEventEndMark(): SimpleTimeMark? = if (isHoppityEvent()) {
Expand All @@ -168,30 +162,22 @@ object HoppityAPI {
return (month % 2 == 1) == (day % 2 == 0)
}

private fun addProcessedStraySlot(slot: Slot) {
processedStraySlots.add(slot.slotNumber)
if (straySlotIterations.addOrPut(slot.slotNumber, 1) > 20) return
DelayedRun.runDelayed(1.seconds) {
val noLongerExists = InventoryUtils.getItemsInOpenChest().filter {
shouldProcessStraySlot(it, true)
}.none {
it.slotNumber == slot.slotNumber &&
it.stack?.displayName == slot.stack?.displayName
}
if (noLongerExists) {
processedStraySlots.remove(slot.slotNumber)
straySlotIterations.remove(slot.slotNumber)
} else addProcessedStraySlot(slot)
}
}
private fun shouldProcessStraySlot(slot: Slot, containsBypass: Boolean = false) =
private fun Map<Int, ItemStack>.filterStrayProcessable() = filter { (slotNumber, stack) ->
// Strays can only appear in the first 3 rows of the inventory, excluding the middle slot of the middle row.
slot.slotNumber != 13 && slot.slotNumber in 0..26 &&
slotNumber != 13 && slotNumber in 0..26 &&
// Don't process the same slot twice.
(!processedStraySlots.contains(slot.slotNumber) || containsBypass) &&
slot.stack != null && slot.stack.item != null &&
!processedStraySlots.contains(slotNumber) &&
// Stack must not be null, and must be a skull.
stack.item != null && stack.item == Items.skull &&
// All strays are skulls with a display name, and lore.
slot.stack.hasDisplayName() && slot.stack.item == Items.skull && slot.stack.getLore().isNotEmpty()
stack.hasDisplayName() && stack.getLore().isNotEmpty()
}


private fun Slot.isMiscProcessable() =
// All misc items are skulls or panes, with a display name, and lore.
stack != null && stack.item != null && stack.item in miscProcessableItemTypes &&
stack.hasDisplayName() && stack.getLore().isNotEmpty()

private fun postApiEggFoundEvent(type: HoppityEggType, event: LorenzChatEvent, note: String? = null) {
EggFoundEvent(
Expand All @@ -200,77 +186,67 @@ object HoppityAPI {
note = note
).post()
}
private fun getRarityFromHoppityRarity(rarity: String) = hoppityRarities.firstOrNull {
it.chatColorCode == rarity.substring(0, 2) || it.rawName == rarity.removeColor()

@SubscribeEvent
fun onInventoryClose(event: InventoryCloseEvent) {
processedStraySlots.clear()
}
fun LorenzRarity.toHoppityRarity() = "$chatColorCode§l$rawName"

@SubscribeEvent(priority = EventPriority.HIGH)
fun onTick(event: SecondPassedEvent) {
@SubscribeEvent
fun onInventoryUpdated(event: InventoryUpdatedEvent) {
// Remove any processed stray slots that are no longer in the inventory.
processedStraySlots.entries.removeIf {
it.key !in event.inventoryItems || event.inventoryItems[it.key]?.displayName != it.value
}

// Only process if we're in the Chocolate Factory.
if (!ChocolateFactoryAPI.inChocolateFactory) return
InventoryUtils.getItemsInOpenChest().filter { shouldProcessStraySlot(it) }.forEach {

event.inventoryItems.filterStrayProcessable().forEach { (slotNumber, itemStack) ->
var processed = false
ChocolateFactoryStrayTracker.strayCaughtPattern.matchMatcher(it.stack.displayName) {
ChocolateFactoryStrayTracker.handleStrayClicked(it)
processed = true
ChocolateFactoryStrayTracker.strayCaughtPattern.matchMatcher(itemStack.displayName) {
processed = ChocolateFactoryStrayTracker.handleStrayClicked(slotNumber, itemStack)
when (groupOrNull("name") ?: return@matchMatcher) {
"Fish the Rabbit" -> {
hoppityDataSet.lastName = "§9Fish the Rabbit"
hoppityDataSet.lastRarity = RARE
hoppityDataSet.duplicate = it.stack.getLore().any { line -> duplicatePseudoStrayPattern.matches(line) }
EggFoundEvent(STRAY, it.slotNumber).post()
hoppityDataSet.duplicate = itemStack.getLore().any { line -> duplicatePseudoStrayPattern.matches(line) }
EggFoundEvent(STRAY, slotNumber).post()
}

else -> return@matchMatcher
}
}
ChocolateFactoryStrayTracker.strayDoradoPattern.matchMatcher(formLoreToSingleLine(it.stack.getLore())) {
ChocolateFactoryStrayTracker.strayDoradoPattern.matchMatcher(formLoreToSingleLine(itemStack.getLore())) {
// If the lore contains the escape pattern, we don't want to fire the event.
// There are also 3 separate messages that can match, which is why we need to check the time since the last fire.
if (ChocolateFactoryStrayTracker.doradoEscapeStrayPattern.anyMatches(it.stack.getLore())) return@matchMatcher
if (ChocolateFactoryStrayTracker.doradoEscapeStrayPattern.anyMatches(itemStack.getLore())) return@matchMatcher

// We don't need to do a handleStrayClicked here - the lore from El Dorado is already:
// §6§lGolden Rabbit §d§lCAUGHT!
// Which will trigger the above matcher. We only need to check name here to fire the found event for Dorado.
hoppityDataSet.lastName = "§6El Dorado"
hoppityDataSet.lastRarity = LEGENDARY
hoppityDataSet.duplicate = it.stack.getLore().any { line -> duplicateDoradoStrayPattern.matches(line) }
EggFoundEvent(STRAY, it.slotNumber).post()
hoppityDataSet.duplicate = itemStack.getLore().any { line -> duplicateDoradoStrayPattern.matches(line) }
EggFoundEvent(STRAY, slotNumber).post()
}
if (processed) addProcessedStraySlot(it)
if (processed) processedStraySlots[slotNumber] = itemStack.displayName
}
}

private fun shouldProcessMiscSlot(slot: Slot) =
// Don't process the same slot twice.
!processedStraySlots.contains(slot.slotNumber) &&
slot.stack != null && slot.stack.item != null &&
// All misc items are skulls, panes, or chests with a display name, and lore.
shouldProcessMiscSlotItem(slot.stack.item) &&
slot.stack.hasDisplayName() && slot.stack.getLore().isNotEmpty()

private fun shouldProcessMiscSlotItem(item: Item) =
item == Items.skull || item == S_GLASS_PANE_ITEM || item == CHEST_ITEM

@SubscribeEvent(priority = EventPriority.HIGH)
fun onSlotClick(event: GuiContainerEvent.SlotClickEvent) {
if (!miscProcessInvPattern.matches(InventoryUtils.openInventoryName())) return
val slot = event.slot ?: return
val index = slot.slotIndex.takeIf { it != -999 } ?: return
if (!shouldProcessMiscSlot(slot)) return

val clickedStack = InventoryUtils.getItemsInOpenChest()
.find { it.slotNumber == slot.slotNumber && it.hasStack }
?.stack ?: return
val nameText = (if (clickedStack.hasDisplayName()) clickedStack.displayName else clickedStack.itemName)
val slot = event.slot?.takeIf { it.isMiscProcessable() } ?: return

if (sideDishNamePattern.matches(nameText)) EggFoundEvent(SIDE_DISH, index).post()
if (sideDishNamePattern.matches(slot.stack.displayName)) EggFoundEvent(SIDE_DISH, event.slotId).post()

milestoneNamePattern.matchMatcher(nameText) {
val lore = clickedStack.getLore()
milestoneNamePattern.matchMatcher(slot.stack.displayName) {
val lore = slot.stack.getLore()
if (!claimableMilestonePattern.anyMatches(lore)) return
if (allTimeLorePattern.anyMatches(lore)) EggFoundEvent(CHOCOLATE_FACTORY_MILESTONE, index).post()
if (shopLorePattern.anyMatches(lore)) EggFoundEvent(CHOCOLATE_SHOP_MILESTONE, index).post()
if (allTimeLorePattern.anyMatches(lore)) EggFoundEvent(CHOCOLATE_FACTORY_MILESTONE, event.slotId).post()
if (shopLorePattern.anyMatches(lore)) EggFoundEvent(CHOCOLATE_SHOP_MILESTONE, event.slotId).post()
}
}

Expand Down Expand Up @@ -321,7 +297,7 @@ object HoppityAPI {
HoppityEggsManager.rabbitFoundPattern.matchMatcher(event.message) {
hoppityDataSet.lastName = group("name")
ChocolateFactoryBarnManager.processDataSet(hoppityDataSet)
hoppityDataSet.lastRarity = getRarityFromHoppityRarity(group("rarity"))
hoppityDataSet.lastRarity = LorenzRarity.getByName(group("rarity"))
attemptFireRabbitFound(event)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import at.hannibal2.skyhanni.SkyHanniMod
import at.hannibal2.skyhanni.config.features.event.hoppity.HoppityEggsConfig
import at.hannibal2.skyhanni.events.LorenzChatEvent
import at.hannibal2.skyhanni.features.event.hoppity.HoppityAPI.HoppityStateDataSet
import at.hannibal2.skyhanni.features.event.hoppity.HoppityAPI.toHoppityRarity
import at.hannibal2.skyhanni.features.event.hoppity.HoppityEggType.Companion.resettingEntries
import at.hannibal2.skyhanni.features.inventory.chocolatefactory.ChocolateFactoryAPI
import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule
Expand Down Expand Up @@ -50,7 +49,7 @@ object HoppityEggsCompactChat {
in resettingEntries -> "${hoppityDataSet.lastMeal?.coloredName.orEmpty()} Egg"
else -> "${hoppityDataSet.lastMeal?.coloredName.orEmpty()} Rabbit"
}
val rarityString = hoppityDataSet.lastRarity?.toHoppityRarity() ?: "§C§L???"
val rarityString = hoppityDataSet.lastRarity?.let { "${it.chatColorCode}§l${it.rawName}" } ?: "§C§L???"
val rarityFormat = when {
hoppityDataSet.duplicate && rarityConfig in listOf(RarityType.BOTH, RarityType.DUPE) -> "$rarityString "
!hoppityDataSet.duplicate && rarityConfig in listOf(RarityType.BOTH, RarityType.NEW) -> "$rarityString "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ object HoppityEggsManager {
*/
val rabbitFoundPattern by ChocolateFactoryAPI.patternGroup.pattern(
"rabbit.found",
"§D§LHOPPITY'S HUNT §7You found (?<name>.*) §7\\((?<rarity>.*)§7\\)!",
"§D§LHOPPITY'S HUNT §7You found (?<name>.*) §7\\(§.§L(?<rarity>.*)§7\\)!",
)

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import at.hannibal2.skyhanni.SkyHanniMod
import at.hannibal2.skyhanni.api.event.HandleEvent
import at.hannibal2.skyhanni.events.GuiRenderEvent
import at.hannibal2.skyhanni.events.InventoryCloseEvent
import at.hannibal2.skyhanni.events.IslandChangeEvent
import at.hannibal2.skyhanni.events.LorenzTickEvent
import at.hannibal2.skyhanni.events.hoppity.EggFoundEvent
import at.hannibal2.skyhanni.features.event.hoppity.HoppityAPI
Expand All @@ -29,22 +30,24 @@ object ChocolateFactoryStrayTimer {
private var lastPingTime = SimpleTimeMark.farPast()

@HandleEvent
fun onEggFound(eggFoundEvent: EggFoundEvent) {
val type = eggFoundEvent.type
fun onEggFound(event: EggFoundEvent) {
val type = event.type
// Only reset the timer for meal entries and hitman eggs
if (type !in resettingEntries && type != HoppityEggType.HITMAN) return
timer = 30.seconds
lastTimerSubtraction = null
}

@SubscribeEvent
fun onInventoryClose(event: InventoryCloseEvent) {
if (timer == Duration.ZERO || timer == 30.seconds) return
// Reset the timer if the inventory is closed and the timer is not at 30 seconds
// The 30s stray timer only counts if you stay in the inventory for the full duration
fun onIslandChange(event: IslandChangeEvent) {
timer = Duration.ZERO
}

@SubscribeEvent
fun onInventoryClose(event: InventoryCloseEvent) {
if (timer > Duration.ZERO) timer = 30.seconds
}

@SubscribeEvent
fun onTick(event: LorenzTickEvent) {
if (!HoppityAPI.isHoppityEvent() || !ChocolateFactoryAPI.inChocolateFactory) return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import at.hannibal2.skyhanni.utils.tracker.TrackerData
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.annotations.Expose
import net.minecraft.inventory.Slot
import net.minecraft.item.ItemStack
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
Expand All @@ -45,6 +45,7 @@ object ChocolateFactoryStrayTracker {
private val config get() = ChocolateFactoryAPI.config
private val claimedStraysSlots = mutableListOf<Int>()

// <editor-fold desc="Patterns">
/**
* REGEX-TEST: §9Zero §d§lCAUGHT!
* REGEX-TEST: §6§lGolden Rabbit §d§lCAUGHT!
Expand Down Expand Up @@ -134,6 +135,7 @@ object ChocolateFactoryStrayTracker {
"stray.doradoescape",
".*(?:§.)*(?:but he escaped and left behind|Legend of (?:§.)*El Dorado (?:§.)*grows!).*"
)
// </editor-fold>

private val tracker = SkyHanniTracker("Stray Tracker", { Data() }, { it.chocolateFactory.strayTracker }) {
drawDisplay(it)
Expand Down Expand Up @@ -224,11 +226,11 @@ object ChocolateFactoryStrayTracker {
return if (goldenList.isEmpty()) "" else ("\n" + goldenList.joinToString("\n"))
}

fun handleStrayClicked(slot: Slot) {
if (!isEnabled() || claimedStraysSlots.contains(slot.slotNumber)) return
fun handleStrayClicked(slotNumber: Int, itemStack: ItemStack): Boolean {
if (!isEnabled() || claimedStraysSlots.contains(slotNumber)) return false

claimedStraysSlots.add(slot.slotIndex)
val loreLine = formLoreToSingleLine(slot.stack.getLore())
claimedStraysSlots.add(slotNumber)
val loreLine = formLoreToSingleLine(itemStack.getLore())

// "Base" strays - Common -> Epic, raw choc only reward.
strayLorePattern.matchMatcher(loreLine) {
Expand Down Expand Up @@ -273,6 +275,8 @@ object ChocolateFactoryStrayTracker {
}
incrementGoldenType("dorado")
}

return true
}

@SubscribeEvent
Expand Down

0 comments on commit dc42e2d

Please sign in to comment.